============================================================================== OPENVMS PORT CHANGES - ScummVM 1.8.1 Kyra extract ============================================================================== Target : OpenVMS 8.4 Alpha, HP C++ (CXX) V7.3-009, DECwindows X11, SDL 1.2 Build : DESCRIP.MMS (MMK/MMS). No sound, no launcher. 358 objects. Guards : __VMS = platform (compiler / RTL / OS) KYRA_STANDALONE = this build's mode (no launcher, no sound) Note : PROBE_*.CPP are standalone diagnostics, not part of the image. Build flag workarounds are NOT listed here - see DESCRIP.MMS, which documents every per-file /NOOPTIMIZE, LEVEL=1 and /NODEBUG rule. BUT READ SECTION 12 BEFORE ADDING A FLAG: /NOOPTIMIZE is not free and caused a crash it was added to prevent. Files : one file is NEW rather than modified - engines/kyra/screen_delta.cpp (section 1b). One is GENERATED - engines/vms_logo_data.h (section 11). Rebuild helper: REBUILD_LOGO.COM (the section 10 saves fix had one too, REBUILD_SAVES.COM; it has served its purpose and is gone). MMK : DESCRIP.MMS is NOT a dependency of any target, so editing it rebuilds NOTHING - delete the .OBJ by hand. Headers not in $(GLOBAL_HDRS) are invisible to MMK the same way. Use MMK /IGNORE=WARNING (DESCRIP.MMS:130) - a warning-severity compile otherwise ABORTS the target. Never MMK/FORCE: 358 objects is ~8 hours under the emulator. ------------------------------------------------------------------------------ 1. COMPILER BUGS / LANGUAGE LIMITS (__VMS) ------------------------------------------------------------------------------ common/memory.h uninitialized_copy() rewritten as an explicit loop. CXX at /OPTIMIZE=LEVEL=3 MISCOMPILES the upstream "new ((void*)dst++) Type(*first++)" one-liner into an INFINITE LOOP: it drops the first++ increment and hoists the exit test out of the loop. Proven from the /MACHINE_CODE listing. Symptom was a wild ACCVIO at a page boundary. DO NOT RESTORE THE ONE-LINER. common/algorithm.h Same class of bug: ++/-- moved OUT of the assignment expressions in two copy loops. common/singleton.h CXX cannot emit a static DATA member of a class template. The pointer now lives inside a static member FUNCTION (_singletonRef()) instead. A uniform accessor keeps every platform spelled the same. common/hashmap.h USE_HASHMAP_MEMORY_POOL is UNDEFINED on __VMS. The ObjectPool path (placement new + explicit ~T()) crashes the Alpha back-end with %GEM-F-ASSERTION in GEM_CX_EXTEND_LIFETIME when the value type has a non-trivial destructor (FileMap, ADFilePropertiesMap). Not fixable by /NOOPTIMIZE. graphics/scaler/scale2x.h #define __restrict__ away - CXX does not know the graphics/scaler/scale3x.h GCC __restrict__ qualifier. common/scummsys.h scumm_va_copy = plain assignment. On Alpha CXX (pre-C99) va_list is a scalar, so this is correct - same branch as MSVC. config.h int64/uint64 are "long long", NOT "long". OpenVMS Alpha defaults to the LP32 data model, so "long" is only 32 bits and the upstream typedef would silently make every 64-bit type 32-bit and truncate rather than fail to compile. "long long" is a true 64-bit type on both this target and the Linux validation host. common/math.h `register` dropped from intLog2(). A NO-OP: register graphics/VectorRendererSpec.cpp is only a hint in C++ and cannot change codegen. Recorded because the theory behind it - "a long-lived register local crashes GEM_CX_EXTEND_ LIFETIME" - is DISPROVEN (see section 12), and graphics/scaler/hq2x.cpp:106 declares nine `register int`s and compiles fine at LEVEL=3. Do NOT strip `register` elsewhere expecting it to fix a crash. ------------------------------------------------------------------------------ 1b. TEMPLATE-INSTANTIATION CODEGEN CRASHES (__VMS) - the biggest single class ------------------------------------------------------------------------------ The most common way this compiler fails on this codebase. The fault fires while EMITTING a template instantiation, not while compiling the routine that uses it, so the traceback names generate_one_instantiation_output_file and often no source line at all - and /NOOPTIMIZE does NOT help, because GEM_CX (the code expander) and GEM_ST_GET_NAME/GEM_OM_WRITE_GLOBALS (symbol emission at module finalization) run at every optimization level. Two recurring triggers: (i) Common::find() - a free template FUNCTION. Crashes with %GEM-F-ASSERTION in GEM_CX_EXTEND_LIFETIME, or in GEM_ST_GET_NAME <- GEM_OM_WRITE_GLOBALS <- GEM_OM_MODULE_FINI while writing the instantiation's external symbol NAME. (ii) Common::Array - an Array whose element type is a POINTER. Array's freeStorage/pop_back emit a pseudo-destructor call ptr->~T() on a pointer type, which the back-end cannot handle. FIX PATTERN: replace with plain C constructs that instantiate no template. Behaviour is identical in every case below; none of these is a semantic change. common/archive.cpp addSubDirectoriesMatching(): Common::find(begin,end,'/') -> explicit while loop over the iterators. graphics/fontman.cpp assignFontToName(): Common::find over _ownedFonts -> indexed linear search. engines/kyra/screen.cpp Screen ctor: counted distinct _pageMapping values with Common::Array + Common::find -> plain seenPage[SCREEN_PAGE_NUM] dedup. Safe because the values index _pagePtrs, so all are < SCREEN_PAGE_NUM. engines/kyra/screen.h _screenPalette's Common::Array -> raw Palette **_palettes + int _paletteCount. Size is fixed at construction and never resized. engines/kyra/eobcommon.h Common::Array -> const int16 *_dscWallMapping[36]. The ctor push_back()s 36 entries in ONE routine; that plus the pointer-Array pseudo- destructor crashed TWO passes - %SYSTEM-F-ACCVIO in GEM_SR at /OPTIMIZE and %GEM-F-ASSERTION in GEM_CX_EXTEND_LIFETIME at /NOOPTIMIZE. Fixed 36-entry table, ctor-filled, read by index. DE-TEMPLATING (same cause, applied to the port's own code): engines/kyra/screen.h decodeFrameDelta / decodeFrameDeltaPage were engines/kyra/screen_delta.cpp template; noXor is now a plain runtime bool parameter. The compile-time branch was only a hot-loop micro-opt. graphics/VectorRendererSpec.h VectorRendererSpec is NO LONGER A TEMPLATE - PixelType is a `typedef uint16` (only uint16 and uint32 were ever instantiated, and this build needs one). IMPORTANT: this de-templating was NOT the fix for that file's crash and is NOT load-bearing - see section 12. NEW TRANSLATION UNIT - engines/kyra/screen_delta.cpp. The delta-frame decoders were SPLIT OUT of screen.cpp (3780 lines) into their own small TU. De-templating alone was not enough: the crash walked from pass to pass as the code was reshaped (GEM_CX_EXTEND_LIFETIME <-> GEM_FG_FIND_BRANCH) and the traceback showed it firing during screen.cpp's TEMPLATE-INSTANTIATION FLUSH - i.e. an INTERACTION with the many other instantiations in that file, not a fault in the routine. The new TU instantiates no Common containers, and the back-end emits it. It is built /NOOPTIMIZE by an explicit rule in DESCRIP.MMS, and being small it is also cheap to recompile under the emulator. BUILD NOTE: screen_delta.cpp is the 358th object. If it is ever removed, Screen::decodeFrameDelta* lose their definitions and the link fails. ------------------------------------------------------------------------------ 2. SEVERED TRANSITIVE INCLUDES (fallout of the hashmap.h change above) ------------------------------------------------------------------------------ Undefining USE_HASHMAP_MEMORY_POOL removed the hashmap.h -> memorypool.h -> array.h -> textconsole.h chain, so error()/warning() stopped being declared. Explicit #include "common/textconsole.h" added to: backends/timer/default/default-timer.cpp (was fatal: %CXX-E-UNDECLARED) common/installshield_cab.cpp (was fatal) graphics/fonts/ttf.cpp (latent - TU is empty without USE_FREETYPE2, added anyway) engines/kyra/staticres.cpp (common/debug.h, for debug()) ------------------------------------------------------------------------------ 3. MISSING POSIX FACILITIES (__VMS) ------------------------------------------------------------------------------ backends/fs/posix/posix-fs.cpp not included (absent on VMS); MAXPATHLEN comes from scummsys.h instead. struct dirent has no BSD d_type field, so the stat()-based fallback path is forced. backends/platform/sdl/posix/posix.cpp displayLogFile() returns false - no fork(), execlp() or waitpid() on VMS. Convenience feature only; never reached in this build. ------------------------------------------------------------------------------ 3b. THE FORBIDDEN-SYMBOL LAYER vs THE VMS CRTL HEADERS (__VMS) ------------------------------------------------------------------------------ common/forbidden.h macro-redefines banned symbols to an intentionally unparsable token sequence unless the TU first #defines the matching FORBIDDEN_SYMBOL_EXCEPTION_*. On VMS this collides with the CRTL headers themselves: reaches the DECC$TYPES module of SYS$COMMON:[SYSLIB]DECC$RTLDEF.TLB, which DECLARES getenv - and that declaration then fails to parse: %CXX-E-EXPDECL, expected a declaration at line number 1 in module DECC$TYPES of ...DECC$RTLDEF.TLB THE ERROR POINTS AT A SYSTEM TEXT LIBRARY, NOT AT YOUR FILE. The fix is a missing exception in the TU that included , so diff the exception block against a sibling TU that compiles - that is what identified both cases here: backends/fs/posix/posix-fs-factory.cpp FORBIDDEN_SYMBOL_EXCEPTION_getenv backends/fs/chroot/chroot-fs-factory.cpp added. They were the only two TUs including posix-fs.h that lacked it; posix-fs.cpp and chroot-fs.cpp already had it and compiled fine. TRAPS WORTH KNOWING (each cost a round in this port): - FORBIDDEN_SYMBOL_EXCEPTION_unistd_h does NOT cover getcwd. getcwd (forbidden.h:303) needs its OWN exception - see 10(b). - printf is forbidden too (forbidden.h:60). Use warning() for a message that must appear unconditionally; debug() needs -d on the command line. - stdiostream.cpp is exempt wholesale (#define FORBIDDEN_SYMBOL_ALLOW_ALL), so the fopen work in 10(a) needed no exception. ------------------------------------------------------------------------------ 4. STRUCT LAYOUT (__VMS) - was a startup ACCVIO ------------------------------------------------------------------------------ backends/platform/sdl/sdl-sys.h #pragma member_alignment after the SDL includes. The VMS SDL 1.2 headers turn struct packing ON and never turn it off, so every class declared afterwards - including OSystem - got a PACKED layout while TUs that never see SDL got the aligned one. An ODR violation on the program's most-used class: sizeof(OSystem) 28 vs 25, vptr at 24 vs 21. Straddled vptr reads sent control into the wrong vtable. Measured with PROBE_D.CPP. WARNING: this header is NOT in $(GLOBAL_HDRS) (adding it invalidates all 358 objects = ~8h rebuild), so editing it requires a manual MMK CLEAN. ------------------------------------------------------------------------------ 5. NO-SOUND CONSEQUENCES (KYRA_STANDALONE) - runtime hangs, not build errors ------------------------------------------------------------------------------ backends/platform/sdl/sdl.cpp NullSdlMixerManager instead of SdlMixerManager - no SDL audio device is opened. base/commandLine.cpp music/sfx/speech/mute default to true. engines/kyra/sequences_darkmoon.cpp waitForSongNotifier(): fixed 1-second wall-clock wait replaces the music cue. Sound::checkTrigger() (sound.h:261) is a non-overridden "return 0" and every caller waits for index >= 1, so the upstream loop condition is PERMANENTLY TRUE - 13 sites in the EOB2 intro/finale busy-waited (no delayMillis) until a keypress released them. Looked like a slow intro; was a HANG. CONFIRMED FIX - intro now runs correctly. Not needed elsewhere: isPlaying() / voiceIsPlaying() return FALSE with no sound, so those loops fall through harmlessly. ------------------------------------------------------------------------------ 6. PERFORMANCE (KYRA_STANDALONE) - trade-off, not a bug ------------------------------------------------------------------------------ engines/kyra/sequences_lol.cpp LoL intro pump delayMillis(10) -> 30. Each iteration ran checkedPageUpdate(8,4) (screen_v2.cpp), an unconditional 16000- uint32 diff of the whole 64KB page, at up to 100 Hz = ~1.6M iterations/sec. Cost is ALGORITHMIC, not unoptimized code (screen_v2.cpp builds at LEVEL=3). Pacing is unaffected: TIM script and palette fade are both wall-clock driven and catch up. STATUS: in, and the intro is acceptable - but see the SIDE EFFECT note in section 7. Much of the "slow intro" was really the SDL timer thread plus the per-call cost of SDL_GetTicks(), both now gone, so this delayMillis bump may no longer be needed. If the intros are ever revisited, try reverting 30 -> 10 FIRST and re-measure. ------------------------------------------------------------------------------ 7. THE INTERMITTENT ACCVIO (__VMS) - CONFIRMED FIXED ------------------------------------------------------------------------------ Symptom: random %SYSTEM-F-ACCVIO, minutes apart, in EOB1/EOB2/HoF, sometimes before the game even loaded. Five dumps. The game, engine and call site all varied (runLoop / updateWallOfForceTimers / kyra timer update); the two innermost frames NEVER did: PC=0x4C / 0x0 / 0x20000000 / 0x7ADD80A0 (four different bogus PCs) DECC$SHR_EV56 ? ? sdl_systimer SDL_GetTicks +0x54 sdl getMillis Cause: LIBSDL:[SRC.TIMER.UNIX]SDL_systimer.c. SDL_GetTicks() is a thin wrapper that calls gettimeofday() (HAVE_CLOCK_GETTIME undefined) and subtracts a file-static "start" timeval. +0x54 IS that gettimeofday() call - hence the unresolvable DECC$SHR frame beneath it. getMillis() is the hottest call in the program, so it was the first to trip over the bad transfer. backends/platform/sdl/sdl.cpp getMillis() reads the clock DIRECTLY with gettimeofday() instead of SDL_GetTicks(). Identical semantics (ms since first call, wrapping at 2^32 like SDL's uint32) minus SDL's static and the cross-image call. Same idiom as the sibling VMS ports (freesci scicore/tools.c, sarien x11.c). NOT sys$gettim: gettimeofday() is plain C RTL, no starlet.h, no 64-bit VMS-time conversion, file stays portable. CONFIRMED FIX - user reports it resolved. DO NOT ADD SDL_INIT_TIMER TO SDL_Init. An earlier revision of this port did, on the theory that SDL_GetTicks() needs the TIMER subsystem initialised. That theory was WRONG and the change was HARMFUL. This SDL is built with SDL_TIMER_UNIX and WITHOUT SDL_THREADS_DISABLED, so USE_ITIMER is undefined and SDL_SYS_TimerInit() takes the threaded branch: it does SDL_CreateThread(RunTimer), and RunTimer spins "SDL_ThreadedTimerCheck(); SDL_Delay(1)" ~1000x/sec, calling SDL_GetTicks() from that thread while the main thread does the same. So the flag SPAWNS A THREAD. LESSON: this cost a whole test round. Removing SdlTimerManager (below) and adding that flag CANCELLED OUT - upstream only ever initialises TIMER from SdlTimerManager's constructor, so the SDL timer thread was created anyway and the crash recurred unchanged, wasting the experiment. Change ONE thing per 8-hour test cycle, and read the LIBRARY SOURCE (which #ifdef branch actually compiles) before theorising about a library frame. backends/platform/sdl/sdl.cpp Two further changes, made while the timer thread was still the suspect. Neither is the fix; both are correct on their own merits, so they stay: (a) initBackend() installs a plain DefaultTimerManager, not SdlTimerManager, so no 10ms SDL_AddTimer thread is started. Nothing here needs it: the only installTimerProc() callers are audio/fmopl.cpp (RealOPL - never selected; the factory returns mixer-driven EmulatedOPL), audio/mpu401.cpp (needs a MidiDriver, and none is ever built - eobcommon.cpp:386 hardwires SoundAdLibPC, and for LoK/HoF/MR/LoL detectDevice() falls to MT_ADLIB because mt32_device/gm_device default to "null") and audio/softsynth/mt32.cpp (USE_MT32EMU undefined). The slot queue is always empty. NOTE: ENABLE_EVENTRECORDER *IS* defined (config.h:44), so the LIVE branch is the g_eventRec one - the #else is dead code. Editing the #else has no effect. (b) ~OSystem_SDL() destroys the timer manager FIRST. Upstream does it AFTER _graphicsManager, _eventSource, _mixerManager etc., so a live timer callback re-enters a half-destroyed OSystem_SDL (getMillis, lockMutex) - and quit() is "delete this; exit(0)", so that is every exit. The EXIT HANG (was section 10) is now GONE too: the game returns to DCL cleanly, no Ctrl+C. Which of (a)/(b) did it is not separable - they shipped together - but both removed a timer callback that was live during teardown, so the mechanism is no mystery. SIDE EFFECT: play and load are also noticeably FASTER. Expected, not luck. getMillis() is called constantly (every runLoop, every TIM script step, every wait loop) and it now costs one RTL gettimeofday() instead of a cross-image call into LIBSDL; and with no SDL timer thread there is no longer a second thread waking ~1000x/sec (SDL_Delay(1) in RunTimer) stealing cycles from an emulated ES40. This is the "speeds" work reappearing for free after being dropped from scope - the intros were partly paying for that thread. backends/mixer/nullmixer/nullsdl-mixer.cpp "delete _samplesBuf" -> "delete[]"; the buffer is new uint8[_samples*4]. Runs on the exit path. ------------------------------------------------------------------------------ 8. STANDALONE LOADER (KYRA_STANDALONE) ------------------------------------------------------------------------------ base/main.cpp autodetectCurrentDir() replaces the launcher GUI: detects a supported game in the CWD and makes it the active target (FreeSCI/Sarien "run from the game data directory" model). base/commandLine.cpp Fixed 640x480 windowed output: gfx_mode=2x, aspect_ratio=true, fullscreen=false. ------------------------------------------------------------------------------ 9. RUNTIME REQUIREMENTS - NOT source changes, but needed to run ------------------------------------------------------------------------------ RUNSETUP.COM Must be run from the game data directory before KYRA.EXE. It lives on the VMS side, NOT in this source tree - (a) and (b) below are its full contents, so it can be retyped from this section. (a) DECC$FILENAME_UNIX_REPORT / _UNIX_ONLY / _UNIX_NO_VERSION / READDIR_DROPDOTNOTYPE / EFS_CHARSET / ARGV_PARSE_STYLE = ENABLE. ScummVM's file layer is POSIX-only and composes paths with '/', so with the RTL in VMS mode getcwd() returns DKA0:[GAMES.EOB2] and every child path becomes the hybrid DKA0:[GAMES.EOB2]/LEVEL15.INF, which opens nothing. Symptom: "no supported game found in the current directory" with NO per-file diagnostics at all. Deliberately NOT enabled: DECC$EFS_CASE_PRESERVE (detector maps are already case-insensitive). (b) SET FILE/ATTRIBUTES=(RFM:STMLF,RAT:NONE,EBK:971,FFB:36) KYRA.DAT VMS files occupy whole 512-byte blocks and 496676 = 970*512 + 36 is not a block multiple, so a block-oriented transfer puts RMS EOF at 497152. checkKyraDat() takes size()-16 as the payload, reading the digest out of the padding and hashing 476 padding bytes. The DATA IS INTACT - only the byte COUNT was wrong. Symptom: "missing KYRA.DAT or it got corrupted". (c) KYRA foreign command, so options can be passed. RUN takes no arguments, and DCL upcases unquoted ones while ScummVM's option names are lowercase: $ KYRA "--gfx-mode=1x" Both (a) and (b) are process-scoped / per-transfer and need no rebuild. ------------------------------------------------------------------------------ 10. SAVED GAMES NEVER REACHED DISK (__VMS) - four separate defects ------------------------------------------------------------------------------ Symptom: saving and restoring worked WITHIN a run, but no save file existed afterwards, for every game. Every save that appeared to be there was an original from a DOS archive. LoL looked worst (its main menu hides "Load" unless saveFileLoadable(0) succeeds, sequences_lol.cpp:46 / lol.cpp:682) while LoK/HoF/MR looked healthy only because they write a "New Game" slot 0 at startup, and EOB looked healthy only because of defect (c) below. (a) backends/fs/stdiostream.cpp - the file-CREATION defect. makeFromPath() created files with a bare fopen(path,"wb"). On VMS that yields rfm=var,rat=cr - a RECORD file - not a byte stream: each fwrite() becomes a record and the RTL re-inserts terminators on read, so the byte offsets saveload.cpp seeks to stop meaning anything. The CREATE path now passes RMS arguments: ctx=stm,rfm=stmlf,rat=none,fop=sup,mbc=32. Same attribute set RUNSETUP.COM must plant on KYRA.DAT by hand (section 9(b)) - creating a file needs it just as much as reading one does. fop=sup matters independently: without it VMS makes a NEW VERSION (LOL-CD.000;2, ;3, ...) on every save, reads silently take the highest, and saving fails outright once the version limit is reached. The READ path keeps the plain fopen(): game data files are whatever the transfer made them, and forcing attributes on an existing file fails the open. (b) backends/saves/posix/posix-saves.cpp - savepath was never set AT ALL. The POSIX ctor derives it from HOME / XDG_DATA_HOME; VMS has neither, so savePath stayed empty, Posix::assureDirectoryExists() failed on stat("") of the empty prefix, and registerDefault("savepath",...) was never reached. getSavePath() then returned "", and Common::FSNode's String ctor silently maps "" to the CWD (common/fs.cpp:42) - so saves did land in the game directory, but by accident, via a path the program could not report, and every consumer reading ConfMan.get("savepath") directly (debugger.cpp:525, saveload_eob.cpp:989) saw an empty string. The __VMS branch now registers getcwd() explicitly and warning()s it at startup, so "where do the saves go" is answerable from a run. NOTE: --savepath is NOT a workaround on VMS - it takes a POSIX path, not a VMS filespec. Needs FORBIDDEN_SYMBOL_EXCEPTION_getcwd (the unistd_h exception does not cover getcwd), and warning() not printf() (also forbidden in this TU). (c) backends/platform/sdl/posix/posix.cpp - SCUMMVM.INI was never written. getDefaultConfigFileName() took the "return 0" early exit with no HOME/ XDG_CONFIG_HOME. Common::String(NULL) is the EMPTY STRING, so createConfigWriteStream() built FSNode("") -> the CWD *directory* -> fopen() of a directory -> NULL, and ConfigManager::flushToDisk() returns silently on a NULL stream (config-manager.cpp:251). No diagnostic, ever. Now returns _baseConfigName ("scummvm.ini"), relative, i.e. beside the game data. This is what made EOB look fine: importOrigSaves (default true, eobcommon.cpp:577) could never be flipped to false, so eobcommon.cpp:521 re-imported the DOS EOBDATA*.SAV files on EVERY launch. (d) base/main.cpp - a latent bug that (c) was masking, fixed in the same round because fixing (c) ACTIVATES it. autodetectCurrentDir() called GUI::addGameToConf() unconditionally, and that function never replaces: if the preferred target exists it appends -1, -2, ... (gui/launcher.cpp:903). Correct for the launcher (you are *adding* a game), wrong here (this runs on every launch). Once the config actually persisted the target would drift lol-cd -> lol-cd-1 -> lol-cd-2, and since the target IS the save filename prefix (getSavegameFilename() = target + ".%03d"), each run would look for a different set of files and find none of the last run's. Now reuses the existing game domain when one is present, refreshing only "path". STATUS: CONFIRMED FIXED - user reports saved games now survive exit. The four fixes shipped together in one rebuild (stdiostream.obj, posix-saves.obj, posix.obj, main.obj - four objects, no headers, so no MMK CLEAN), so which one was individually decisive is NOT separable from this test. (b) (c) (d) were each proven from the source before the rebuild - definite defects on definite code paths, and (c) alone explains the EOB "phantom saves". (a) was the leading theory for "the file is not there after exit" rather than a proven one; it is now known to be either the cause or harmless, but if the record format ever matters again, confirm it directly: $ DIRECTORY/FULL .000;* "Record format: Stream_LF" is what (a) produces. The startup line added by (b) ("Kyra/VMS: savepath = ...") reports the directory saves actually go to, which is the first thing to check if this area ever regresses. LESSON: on VMS, creating a file is not the same operation as opening one. A POSIX-only file layer will compile, run, and read fine while silently producing files of the wrong RECORD FORMAT - and the failure surfaces only after process rundown, which looks like data loss rather than a bad open. ------------------------------------------------------------------------------ 11. OPENVMS SPLASH-SCREEN BANNER (__VMS) ------------------------------------------------------------------------------ engines/engine.cpp splashScreen() draws an OpenVMS banner directly under the ScummVM logo, so the build identifies itself as the VMS port on sight. Two files: engines/vms_logo_data.h (GENERATED, do not hand-edit) and a #if defined(__VMS) block in engine.cpp. Nothing outside __VMS changes. WHY IT IS A BMP AND NOT THE JPEG: USE_JPEG is #undef in config.h:25, so there is no JPEG decoder in this build at all. The art is converted at DEVELOPMENT time into a C array and decoded at runtime by Image::BitmapDecoder - the same decoder, and the same format, the stock logo_data.h already uses. No new objects, no new library dependency. WHY MS RLE8 AND NOT A RAW BITMAP - THIS IS THE IMPORTANT PART: uncompressed, 300x249 8bpp is a 75778-byte array initializer, about 5100 source lines. That is precisely the input class documented under "Files needing /NOOPTIMIZE" in DESCRIP.MMS as crashing this compiler's back-end in GEM_DF/CSE (staticres.cpp: nothing but enormous static resource tables). Storing the bitmap RLE8- compressed with a 96-colour palette brings it to 39018 bytes / ~2440 lines, so engine.cpp keeps compiling at the normal $(CXXFLAGS) level and does NOT need a $(CXXFLAGS_NOOPT) rule. Do not "simplify" this back to a raw bitmap. Image::BitmapDecoder constraints the generator must satisfy (image/bmp.cpp:57): - starts "BM"; infoSize EXACTLY 40 (Windows v3 only) - height must be POSITIVE, i.e. bottom-up rows ("Right-side up bitmaps not supported" is the warning for the other case) - bitsPerPixel 8, 24 or 32 - compression is read with readUint32BE() at bmp.cpp:96, but createBitmapCodec() matches SWAP_CONSTANT_32(1) (codec.cpp:199), so the two swaps cancel: the field is plain little-endian 01 00 00 00. And from Image::MSRLEDecoder::decode8 (image/codecs/msrle.cpp:51): - decoding starts at the LAST row and DECREMENTS, so emit the TOP row first - do NOT write an end-of-line marker after the final row: decode8 does y-- then tests y<0 and warns "Next line is beyond picture bounds". The stock logo_data.h omits it too. Terminate with end-of-image (00 01) only. REGENERATION RECIPE (from src/, with Python + Pillow, on the build host - not on VMS). Source art: src/vms_logo.jpg, 516x387. 1. COLOUR-KEY the black surround to the splash orange (0xd4,0x75,0x0b), using a luminance ramp between LO=24 and HI=70 rather than a hard threshold - a hard key leaves a dark halo of JPEG ringing around the circle. 2. CROP to the bounding box of everything that is not within L1 distance 12 of that orange, so "300 pixels wide" means 300 pixels of artwork. 3. SCALE to width exactly 300 - the width of logo_data.h's logo, so the two stack flush - with LANCZOS. Height follows the aspect: 249. 4. MEDIAN-FILTER 3x3, then quantize to 95 adaptive colours with dither=NONE. Both steps exist to make RLE runs longer: dithering roughly doubles the encoded size for no visible gain at this scale, and the median filter kills isolated JPEG speckles that break runs. Quality cost is invisible. 5. PIN palette index 95 to EXACTLY (0xd4,0x75,0x0b) and force every pixel that tested as orange in step 2 to that index. Without the pin, quantization drifts the background a shade or two off the overlay fill and the banner shows up as a faint rectangle. 6. Emit a 256-entry palette (unused entries black) like the stock logo, and RLE8-encode bottom-up per the decode8 rules above. VERIFY BEFORE COMMITTING, on the build host: re-parse the generated header and run the bytes through the exact bmp.cpp/msrle.cpp logic, asserting the decoded index plane is IDENTICAL to the quantized one, that no decoder warning path is taken, and that palette[95] is the orange verbatim. This was done for the current file: 300x249, 96 colours, 39018 bytes, pixel-exact, zero warnings. Also compile the header standalone (g++ with a byte typedef and ARRAYSIZE) to catch a malformed initializer before spending a VMS compile on it. REBUILD: one object plus the link. vms_logo_data.h is included by engine.cpp alone and is NOT in $(GLOBAL_HDRS), so nothing else sees it - and because DESCRIP.MMS is not a dependency of any target, MMK will not notice the new header on its own: $ DELETE [.engines]engine.obj;* $ MMK /IGNORE=WARNING ------------------------------------------------------------------------------ 13. OPEN ITEMS ------------------------------------------------------------------------------ - RESOLVED, kept here as method: the intermittent ACCVIO and the exit hang are both FIXED - see section 7. Reading KYRA.MAP was what cracked it, and no rebuild was needed: the link rule at DESCRIP.MMS:429 ALREADY passes /MAP=kyra.map/FULL/CROSS_REFERENCE unconditionally, so KYRA.MAP always exists. (LINKFLAGS at :199 is redundant for this - do not relink for it.) $ SEARCH KYRA.MAP "" -> psect contributions + symbols $ SEARCH/WINDOW=(30,10) KYRA.MAP "" -> psect NAME and neighbours ALPHA MAP GOTCHA that cost a wrong conclusion: in the symbol cross-reference a routine's value is the address of its PROCEDURE DESCRIPTOR, not its entry point. sdl_systimer showed TWO contributions - 416 bytes of descriptors at 0x000F2CE0 and 1012 bytes of CODE at 0x0082A120. Dividing the descriptor psect by the routine count "proves" 32-byte routines and makes a genuine rel PC look impossible. Check the psect ATTRIBUTES (the code one is PIC,CON,REL,LCL,SHR,EXE,NOWRT) before concluding a frame is fabricated. The "line" column IS meaningless for library modules compiled without debug data (it reported 11109, 42994 for a 700-line file) - ignore it, but do not extend that distrust to the rel PC. - engines/kyra/staticres.cpp carries an UNBUILT, UNTESTED __VMS fallback (recoverKyraDatPayloadSize) that recovers KYRA.DAT's true length from the PAK index. Redundant with 9(b). Either build and test it or revert it - do not leave it half-applied. - createLogFile() (backends/platform/sdl/posix/posix.cpp) is the LAST remaining user of the getenv("HOME") + '/' concatenation layer described in 9(a); the config and save paths beside it are now fixed (section 10). It is only reached with --debuglevel, so it is harmless, but it will produce the same empty-path-becomes-a-directory result if it ever is. - sdl-sys.h is not in $(GLOBAL_HDRS) - see the warning in section 4. Same for engines/vms_logo_data.h, but deliberately and harmlessly: only engine.cpp includes it (section 11). - UNTESTED ON HARDWARE: the splash banner (section 11) has been verified only by emulating bmp.cpp/msrle.cpp on the build host and by a standalone g++ compile. It has NOT yet been through a CXX compile or an actual run. First things to check on the next build are in REBUILD_LOGO.COM. - The LoL delayMillis(30) in section 6 is probably no longer needed now that the SDL timer thread is gone. Try reverting 30 -> 10 and re-measure. ==============================================================================