diff --git a/emulator/render-bridge/gauge-ab/grab.ps1 b/emulator/render-bridge/gauge-ab/grab.ps1 new file mode 100644 index 00000000..bb743e4f --- /dev/null +++ b/emulator/render-bridge/gauge-ab/grab.ps1 @@ -0,0 +1,52 @@ +param( + [Parameter(Mandatory=$true)][string]$Out, + [string]$Match = "DOSBox-X" +) + +Add-Type @" +using System; +using System.Runtime.InteropServices; +public class W { + [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); + [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r); + [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); + [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h); + [DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern int GetWindowText(IntPtr h, System.Text.StringBuilder s, int n); + [DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc cb, IntPtr p); + public delegate bool EnumProc(IntPtr h, IntPtr p); + [StructLayout(LayoutKind.Sequential)] public struct RECT { public int L, T, R, B; } +} +"@ + +$found = @() +$cb = [W+EnumProc]{ + param($h, $p) + if ([W]::IsWindowVisible($h)) { + $sb = New-Object System.Text.StringBuilder 512 + [void][W]::GetWindowText($h, $sb, 512) + $t = $sb.ToString() + if ($t -like "*$Match*") { $script:found += ,@($h, $t) } + } + return $true +} +[void][W]::EnumWindows($cb, [IntPtr]::Zero) + +if ($found.Count -eq 0) { Write-Output "NO-WINDOW"; exit 1 } + +Add-Type -AssemblyName System.Drawing +$i = 0 +foreach ($f in $found) { + $h = $f[0]; $title = $f[1] + $r = New-Object W+RECT + [void][W]::GetWindowRect($h, [ref]$r) + $w = $r.R - $r.L; $ht = $r.B - $r.T + if ($w -le 0 -or $ht -le 0) { continue } + $bmp = New-Object System.Drawing.Bitmap $w, $ht + $g = [System.Drawing.Graphics]::FromImage($bmp) + $g.CopyFromScreen($r.L, $r.T, 0, 0, (New-Object System.Drawing.Size $w, $ht)) + $path = if ($found.Count -eq 1) { $Out } else { $Out -replace '\.png$', "_$i.png" } + $bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png) + $g.Dispose(); $bmp.Dispose() + Write-Output "SAVED $path ${w}x${ht} [$title]" + $i++ +} diff --git a/emulator/render-bridge/gauge-ab/vis_rec.conf b/emulator/render-bridge/gauge-ab/vis_rec.conf new file mode 100644 index 00000000..db622833 --- /dev/null +++ b/emulator/render-bridge/gauge-ab/vis_rec.conf @@ -0,0 +1,30 @@ +[sdl] +output=opengl +[dosbox] +memsize=32 +machine=svga_s3 +[cpu] +core=normal +cputype=pentium +cycles=20000 +[serial] +serial1=disabled +serial2=disabled +[autoexec] +mount c "C:\VWE\TeslaRel410\restoration\build410\run\image" +c: +set L4CONTROLS=RIO,KEYBOARD +set BT_MECH_LOG=1 +set HEAPSIZE=15000000 +set L4TIMER= +set L4SOUND=OFF +set L4GAUGE=640x480x16 +set L4PLASMA= +set L4VIDEO=OFF +set BLASTER=A220 I5 D1 H5 P330 T6 +set TEMP=c:\ +32rtm.exe -x +BTL4REC.EXE -egg test.egg > OUTREC.TXT +echo GAME-RC=%errorlevel% >> OUT.TXT +32rtm.exe -u +exit diff --git a/emulator/render-bridge/gauge-ab/vis_shp.conf b/emulator/render-bridge/gauge-ab/vis_shp.conf new file mode 100644 index 00000000..3b95f8a8 --- /dev/null +++ b/emulator/render-bridge/gauge-ab/vis_shp.conf @@ -0,0 +1,30 @@ +[sdl] +output=opengl +[dosbox] +memsize=32 +machine=svga_s3 +[cpu] +core=normal +cputype=pentium +cycles=20000 +[serial] +serial1=disabled +serial2=disabled +[autoexec] +mount c "C:\VWE\TeslaRel410\restoration\build410\run\image" +c: +set L4CONTROLS=RIO,KEYBOARD +set BT_MECH_LOG=1 +set HEAPSIZE=15000000 +set L4TIMER= +set L4SOUND=OFF +set L4GAUGE=640x480x16 +set L4PLASMA= +set L4VIDEO=OFF +set BLASTER=A220 I5 D1 H5 P330 T6 +set TEMP=c:\ +32rtm.exe -x +BTL4SHP.EXE -egg test.egg > OUTSHP.TXT +echo GAME-RC=%errorlevel% >> OUT.TXT +32rtm.exe -u +exit diff --git a/restoration/source410/BT/POWERSUB.CPP b/restoration/source410/BT/POWERSUB.CPP index 97757a28..0f660557 100644 --- a/restoration/source410/BT/POWERSUB.CPP +++ b/restoration/source410/BT/POWERSUB.CPP @@ -116,6 +116,17 @@ PoweredSubsystem::PoweredSubsystem( thermalResistivityCoefficient = subsystem_resource->thermalResistivityCoefficient; startTime = subsystem_resource->startTime; + + // + // Cache the authored aux-screen block NOW -- the resource memory dies + // with the Mech ctor's stream buffer. + // + auxScreenNumber = subsystem_resource->auxScreenNumber; + auxScreenPlacement = subsystem_resource->auxScreenPlacement; + Str_Copy(auxScreenLabel, subsystem_resource->auxScreenLabel, + sizeof(auxScreenLabel)); + Str_Copy(engScreenLabel, subsystem_resource->engScreenLabel, + sizeof(engScreenLabel)); startTimer = startTime; voltageScale = 1.0f; diff --git a/restoration/source410/BT/POWERSUB.HPP b/restoration/source410/BT/POWERSUB.HPP index d84ddb92..4200ad0c 100644 --- a/restoration/source410/BT/POWERSUB.HPP +++ b/restoration/source410/BT/POWERSUB.HPP @@ -154,6 +154,23 @@ virtual void DeathReset(Logical full_reset); + // + // The AUTHORED aux-screen assignment (streamed). CACHED at ctor + // time because MechSubsystem::resource points into the Mech ctor's + // padded stream copy, which is FREED before the ctor returns -- the + // 5.3.24 use-after-free landmine. The cockpit's engineering panels + // key their screen number, background placement and title label off + // these. + // + int + GetAuxScreenNumber() const { Check(this); return auxScreenNumber; } + int + GetAuxScreenPlacement() const { Check(this); return auxScreenPlacement; } + const char* + GetAuxScreenLabel() const { Check(this); return auxScreenLabel; } + const char* + GetEngScreenLabel() const { Check(this); return engScreenLabel; } + Subsystem* ResolveVoltageSource() { return voltageSource.Resolve(); } int @@ -233,6 +250,10 @@ SubsystemConnection voltageSource; AlarmIndicator electricalStateAlarm; AlarmIndicator modeAlarm; + int auxScreenNumber; + int auxScreenPlacement; + char auxScreenLabel[64]; + char engScreenLabel[64]; Scalar thermalResistivityCoefficient; Scalar startTime; Scalar startTimer; diff --git a/restoration/source410/BT_L4/BTL4GAU2.CPP b/restoration/source410/BT_L4/BTL4GAU2.CPP index b71d1bbc..0b380084 100644 --- a/restoration/source410/BT_L4/BTL4GAU2.CPP +++ b/restoration/source410/BT_L4/BTL4GAU2.CPP @@ -1163,6 +1163,13 @@ static Logical } } +// +// The AUTHORED aux screen (binary sub+0x1dc). PoweredSubsystem caches the +// streamed block at ctor time (its resource memory dies with the Mech +// ctor's stream buffer), so the panels land on the screens the artists +// assigned -- not in roster order. A non-powered panel subsystem (or a +// model that authored nothing) falls back to the sequential walk. +// static int AuxScreenAssignmentOf( Entity *mech, @@ -1174,6 +1181,16 @@ static int position = 0, count = mech->GetSubsystemCount(); + if (subsystem->IsDerivedFrom(PoweredSubsystem::ClassDerivations)) + { + int authored = + ((PoweredSubsystem *)subsystem)->GetAuxScreenNumber(); + if (authored > 0) + { + return authored; + } + } + for (i = 1; i < count; i++) // slot 0 = the controls mapper { Subsystem @@ -1748,7 +1765,22 @@ void num.Draw(&localView, 0.0f); } - //---- subsystem label bitmap @(0x125,0x172): SKIPPED (no live label). + //---- subsystem label bitmap @(0x125,0x172): the AUTHORED aux-screen + // label, cached on PoweredSubsystem at ctor time (the streamed + // resource dies with the Mech ctor's buffer). This is the panel + // title art -- "SENSOR CLUSTER", "MYOMERS", the weapon name and + // range strip. + // + if (sub->IsDerivedFrom(PoweredSubsystem::ClassDerivations)) + { + const char *label = + ((PoweredSubsystem *)sub)->GetAuxScreenLabel(); + if (label != NULL && *label != '') + { + localView.SetColor(0xff); + DrawPortBackground(&localView, warehouse, 0x125, 0x172, label); + } + } //---- type-specific label cells (the authentic ClassID dispatch) switch (sub->GetClassID()) @@ -1868,13 +1900,32 @@ SubsystemCluster::SubsystemCluster( } // - // Background strip art: the binary picked image_names[auxScreenPlacement] - // (sub+0x1e0). STAND-IN: no live placement source (see the aux-screen - // ledger) -> the backdrop child is not built. image_names stays in the - // signature for the day the placement feed lands. + // Background strip art: the binary picks image_names[auxScreenPlacement] + // (sub+0x1e0) -- LIVE now that PoweredSubsystem caches the authored + // placement (the resource itself is freed with the Mech ctor's stream + // buffer). A model that authored nothing keeps the bare frame. // - (void)image_names; background = NULL; + { + int placement = -1; + if (subsystem_in != NULL + && subsystem_in->IsDerivedFrom(PoweredSubsystem::ClassDerivations)) + { + placement = + ((PoweredSubsystem *)subsystem_in)->GetAuxScreenPlacement(); + } + if (placement >= 0 && placement < 8 && image_names != NULL + && image_names[placement] != NULL) + { + background = new BackgroundBitmap( + mfd_mode, renderer_in, owner_ID, mfd_port, + x, y, image_names[placement], + 1, // opaque + 0xff, 0 // fg / bg + ); + Register_Object(background); + } + } //------------------------------------------------------------ // Paint the panel frame image into the MFD port. diff --git a/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md b/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md index 18ed62b7..e347bb21 100644 --- a/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md +++ b/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md @@ -475,3 +475,43 @@ not state) and Searchlight/LightOn (Searchlight is a memberless PowerWatcher subclass) -- both fall to the zero cell and draw static, which is correct until those bricks exist. Gauge fight + smoke + novice all clean. + +## 5.3.28 -- THE VISUAL A/B PASS (first side-by-side vs the shipped exe) + +RIG (repeatable): both exes live in the same mount reading the SAME GAUGE +content -- run/image/BTL4SHP.EXE (the shipped ALPHA_1 binary) and +BTL4REC.EXE (ours); confs run/vis_shp.conf + run/vis_rec.conf (gauges on: +L4GAUGE=640x480x16, HEAPSIZE=15000000, machine=svga_s3, output=opengl). +Capture: scratchpad/grab.ps1 (Win32 window grab) at ~70s, then a PIL diff. +With L4VIDEO off the DOSBox window IS the 640x480x16 gauge framebuffer -- +the packed buffer the VDB splits into the six heads -- so no VPX needed. + +RESULT: **our reconstructed exe draws the cockpit.** Measured against the +shipped framebuffer (chrome cropped): + + first run 90.1% pixels identical, 82% of shipped's lit pixels lit + after fix 93.2% pixels identical, 85% coverage + +Every difference in the first run traced to ONE root cause: the authored +aux-screen block (number / placement / label) lives in the subsystem +resource, which is FREED with the Mech ctor's stream buffer (the 5.3.24 +use-after-free landmine). Both this tree AND the BT411 port had stubbed it +("PoweredSubsystem keeps NO live copy") -- which is why BT411's displays +show the same artifacting. FIX: PoweredSubsystem now CACHES the block at +ctor time (auxScreenNumber / auxScreenPlacement / auxScreenLabel / +engScreenLabel + accessors), and btl4gau2 uses the authored screen instead +of a roster-order stand-in and builds the placement strip art. Panels +immediately moved onto their authored screens (the weapon recharge dials +land on the same panels as the shipped exe; mech icons appear on the +sensor/myomer panels; the generator letters correct to their authored +screens). + +STILL DIFFERING (all donor-inherited stubs, unchanged by this pass): +the panel TITLE art ("SENSOR CLUSTER" / "MYOMERS" / "ERMED LASER RANGE +500M" / "TYPE II 65 TONS"), the pilot name ("Aeolus"), and some lamp +frames ("OFF x" vs the shipped state digits). The label blit is now wired +through GetAuxScreenLabel() but draws nothing on bhk1 -- so those titles +come from the q-strip statusImage path (the 8 qblh*.pcc typeStrings +VehicleSubSystems passes to DrawStatusCells), NOT from auxScreenLabel. +That is the next thread. Name bitmaps stay inert (CreateMutantPixelmap8 / +LookupPlayerNameBitmap bring-up stubs).