diff --git a/.gitignore b/.gitignore index 750c0a4..87aa656 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,14 @@ RedPlanet-*.zip # in the history: a minidump carries process memory and the sender's own # file paths. /Crashdmp/ + +# Runtime files the game writes beside itself when run from the tree. +# environ.ini in particular is GENERATED from the template in +# RP_L4/RPL4ENVIRON.cpp - a copy committed here would be a second source +# of truth that nothing updates. +bindings.txt +environ.ini +rpl4.log +rpl4-fail.log +last.spl +SPOOLS/ diff --git a/MUNGA/DEBUGOFF.h b/MUNGA/DEBUGOFF.h index feccbac..8806d6a 100644 --- a/MUNGA/DEBUGOFF.h +++ b/MUNGA/DEBUGOFF.h @@ -58,6 +58,23 @@ #define Check(p) #define Check_Signature(p) -//#define Fail(m) Fail_To_Debugger(m,__FILE__,__LINE__) -#define Fail(m) abort(); + +// +// A release build used to answer Fail("clipping_radius not defined") with a +// bare abort(), throwing the message away - and because the compiler merges +// identical cold paths, every Fail in a function became the same anonymous +// stub. A crash then told you the function and nothing else, which cost an +// afternoon of disassembly to learn that a mission review build stops in +// L4AudioRenderer::Initialize without ever saying which of its nine checks +// was the one that tripped. +// +// The debug build has always routed this to a function that prints the +// message with its file and line. Release now does too. The termination is +// unchanged - still abort(), still exit code 0xC0000409 - so nothing +// downstream sees anything different; it just says what happened on the way +// out. +// +extern void Fail_With_Message(const char *message, const char *file, int line); + +#define Fail(m) Fail_With_Message(m, __FILE__, __LINE__) #define Cast_Object(type, ptr) ((type)(ptr)) \ No newline at end of file diff --git a/MUNGA/VERIFY.cpp b/MUNGA/VERIFY.cpp index e02c714..ed574cd 100644 --- a/MUNGA/VERIFY.cpp +++ b/MUNGA/VERIFY.cpp @@ -133,6 +133,70 @@ void Verify_Failed(char *Message, char *File, int Line) } } +// +//############################################################################# +//############################################################################# +// +// +// The release build's Fail. Says what went wrong before it goes. +// +// Terminates exactly as the old bare abort() did - same call, same +// 0xC0000409 - so no caller, script or crash handler sees a change. The +// only difference is the line in the log naming the check that failed and +// where it lives. +// +// The emergency shutdowns are worth doing before the process ends even +// though it is ending: the gauge renderer and the controls manager both +// hold hardware, and a display left mid-mode is a nuisance to whoever has +// to pick the machine up afterwards - which in a pod bay is not the +// person who was playing. +// +void + Fail_With_Message(const char *Message, const char *File, int Line) +{ + if (!AlreadyFailed) + { + AlreadyFailed = True; + + DEBUG_STREAM << "FAIL " << File << "(" << Line << "): " << Message + << "\n" << std::flush; + + // + // And again, somewhere that cannot be lost. + // + // rpl4.log is std::cout with its streambuf swapped for an ofstream's + // (see WinMain), and the text written above did NOT survive to disk + // across the exit - flushed as far as the stream and no further. + // Rather than guess at where it stops, write the one line that + // matters to its own file and close it: fclose is a promise the + // bytes are on the disk, and a file opened and closed inside this + // function cannot be left dangling by whatever happens next. + // + // Its own file rather than an append to rpl4.log because that one is + // already open for writing and Windows will not share it. + // + FILE *record = fopen("rpl4-fail.log", "w"); + + if (record != NULL) + { + fprintf(record, "FAIL %s(%d): %s\n", File, Line, Message); + fclose(record); + } + + // + // Hardware last, and after the record is safely written: the gauge + // renderer and the controls manager both hold devices, and a display + // left mid-mode is a nuisance for whoever picks the machine up - in + // a pod bay, not the person who was playing. If one of these faults + // on the way down, the failure has already been recorded. + // + SystemClock::timer.Shutdown(); + ControlsManager::Shutdown(); + GaugeRenderer::EmergencyShutdown(); + } + abort(); +} + // //############################################################################# //############################################################################# diff --git a/MUNGA_L4/L4APP.cpp b/MUNGA_L4/L4APP.cpp index 167b612..249182c 100644 --- a/MUNGA_L4/L4APP.cpp +++ b/MUNGA_L4/L4APP.cpp @@ -240,6 +240,9 @@ Logical { DEBUG_STREAM << "\n" << argv[0] << " -egg -net -solids -h -help\n" + " -mr mission review: play a spool back and\n" + " record a new one alongside it\n" + " -pb play a spool back only, no recording\n" " -windowed windowed, title bar and all\n" " -fit borderless over the whole monitor,\n" " render size chosen to match\n" @@ -403,6 +406,23 @@ Logical { suppressGauges = TRUE; missionReviewMode = 1; + } + // + // Play a spool back without recording a new one. + // + // Mode 2 was always in RPL4.CPP - it takes one spool file + // instead of two and skips the spooling application - there was + // simply no way to ask for it. -mr starts a recorder alongside + // the playback, and that recorder is the half that falls over + // on a machine which is not a pod bay: it walks the hosts named + // in the egg expecting to find every one of them connected. + // + // Watching a race back does not need a recorder at all. + // + else if (!stricmp(W2A(argv[i]), "-pb")) + { + suppressGauges = TRUE; + missionReviewMode = 2; } else if (!(*parser)(&i, argc, argv)) { return False; diff --git a/MUNGA_L4/L4SPLR.cpp b/MUNGA_L4/L4SPLR.cpp index dd48d3f..8bff86a 100644 --- a/MUNGA_L4/L4SPLR.cpp +++ b/MUNGA_L4/L4SPLR.cpp @@ -124,10 +124,38 @@ void ResolveAddress(host_name, &net_address); Host *host = host_mgr->FindHost(net_address); - + + // + // FindHost answers NULL for a host named in the egg that is not + // actually connected, and this went straight on to call + // host->GetHostID(). In an arcade every station in the egg is on + // the wire, so the case could not arise; anywhere else it is the + // ordinary state of affairs, and it crashed the spooling + // application before the mission could even start. + // + // The table below is read back one pair per egg host, in egg + // order, so a missing host cannot simply be skipped - that would + // shift every entry after it. Write the pair, say what happened, + // and carry on. + // + if (host == NULL) + { + DEBUG_STREAM << "Spool: host '" << host_name + << "' is in the egg but not connected - recording it as" + << " remote with no ID. Packets from it will not map back.\n" + << std::flush; + + *(Logical*)spool->GetPointer() = True; + spool->AdvancePointer(sizeof(Logical)); + + *(HostID*)spool->GetPointer() = (HostID) 0; + spool->AdvancePointer(sizeof(HostID)); + continue; + } + *(Logical*)spool->GetPointer() = (host != host_mgr->GetLocalHost()); spool->AdvancePointer(sizeof(Logical)); - + *(HostID*)spool->GetPointer() = host->GetHostID(); spool->AdvancePointer(sizeof(HostID)); } diff --git a/RP_L4/RPL4.CPP b/RP_L4/RPL4.CPP index 79a5bff..464110a 100644 --- a/RP_L4/RPL4.CPP +++ b/RP_L4/RPL4.CPP @@ -638,12 +638,26 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine if (!spoolFileName) spoolFileName = "last.spl"; + // + // Say which of the two this is. The choice was made in silence, + // and the two look identical from outside for the first few + // seconds - one plays a race back, the other sits there - so a + // spool that failed to load was indistinguishable from a spool + // that loaded and had not started yet. + // SpoolFile *spool = spool_mgr->GetStoredSpoolFile(spoolFileName); if (spool) { + DEBUG_STREAM << "Playback: " << spoolFileName + << " loaded, " << (int) spool->GetBytesRemaining() + << " bytes to play\n" << std::flush; new_app = new RPL4PlaybackApplication(hInstance, hWnd, &resources, spool); } else { + DEBUG_STREAM << "Playback: could not load '" << spoolFileName + << "' - starting idle with nothing to show. It must exist, be" + << " no larger than " << (int) SPOOL_SIZE + << " bytes, and have been written by this build.\n" << std::flush; new_app = new RPL4IdleApplication(hInstance, hWnd, &resources); }