Files
TeslaRel410/restoration/source410/backdate.py
T
CydandClaude Fable 5 5b35eb973c 4.10 reconstruction: BTL4OPT.EXE links clean and BOOTS to the first staged brick
The reconstructed tree now produces a runnable binary with the authentic
1995 toolchain (BC4.52 / tlink32 / DPMI32):

- BTL4.CPP main TU reconstructed from the 4.11 Ghidra decomp (FUN_0040109c)
  + the 4.10 binary's own string pool + surviving RPL4TOOL.CPP house style;
  probe_main.cpp scaffold retired (BTL4.NOTES.md documents every decoded call)
- L4NET.CPP staged: L4NetworkManager ctor/dtor + 10 vtable-pulled virtuals
  (standalone-benign ones no-op, network ones Fail loudly) + NetNub client
  globals (Net_Common_Ptr=NULL routes L4File to its plain-DOS path)
- build410.sh: libs now built in the AUTHENTIC makefile member order
  (MUNGA.MAK / mungal4.mak / BT.MAK / BTL4.MAK). Order is load-bearing:
  tlink emits static-init records in module pull order, and alphabetical
  order booted into a null-vptr crash (IcomManager::ClassDerivations
  constructing before parent NetworkClient::ClassDerivations). Also fixed
  stage_link to the proven 32-bit lib set (SOSDBXC+SOSMBXC, no WATTCPLG)
- BOXTREE.HPP MemoryBlock unify, BTL4GRND notify stubs, remaining engine
  backfills (audio/gauge/resource/stream TUs) that closed the deep ledger

Smoke test (DOSBox-X + 32RTM, copy of the pod BT tree, our exe swapped in):
  BattleTech v4.10
  BTL4Application::BTL4Application
  l4net.cpp(22): L4NetworkManager -- l4net.cpp not yet reconstructed
Static init, main, -egg parse, BTL4.RES load (version 1.0.6 check passes),
ApplicationManager and the BTL4Application ctor chain all execute real
reconstructed code; boot halts at the first staged Fail() as designed.
Next brick: the real l4net.cpp body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:05:53 -05:00

118 lines
5.7 KiB
Python

#!/usr/bin/env python3
"""
backdate.py -- mechanically back-date a WinTesla-era engine header (BT411/BT412
engine/MUNGA*/X.h) to its 1995 form (source410/MUNGA/X.HPP).
Transforms (the mechanical part -- ALWAYS eyeball the result and write the
.NOTES.md sidecar; content drift is NOT detected here):
#pragma once -> #if !defined(X_HPP) / #define X_HPP ... #endif
#include "y.h" -> guarded #if !defined(Y_HPP) #include <y.hpp> #endif
(system includes like <string.h> are left alone)
Usage: py -3 backdate.py <donor.h> <OUTPUT.HPP>
"""
import os
import re
import sys
def main():
donor, out = sys.argv[1], sys.argv[2]
name = os.path.splitext(os.path.basename(out))[0].upper()
guard = "%s_HPP" % name
text = open(donor, "r", errors="replace").read()
# WinTesla wrapped the 1995 static objects in accessor functions (its
# static-init-order fix); unwrap them back to plain 1995 definitions.
text = re.sub(
r"Derivation\s*\*\s*(\w+)::GetClassDerivations\(\)\s*"
r"\{\s*static\s+Derivation\s+\w+\s*\(([^;]*?)\);\s*return\s*&\w+;\s*\}",
r"Derivation\n\t\1::ClassDerivations(\2);",
text, flags=re.S)
text = re.sub(
r"(?:Receiver::)?MessageHandlerSet\s*&?\s*(\w+)::GetMessageHandlers\(\)\s*"
r"\{\s*static\s+(?:\w+::)?MessageHandlerSet\s+\w+\s*\(([^;]*?)\);\s*return\s*\w+;\s*\}",
r"\1::MessageHandlerSet\n\t\1::MessageHandlers(\2);",
text, flags=re.S)
text = re.sub(
r"(?:\w+::)?AttributeIndexSet\s*&?\s*(\w+)::GetAttributeIndex\(\)\s*"
r"\{\s*static\s+(?:\w+::)?AttributeIndexSet\s+\w+\s*\(([^;]*?)\);\s*return\s*\w+;\s*\}",
r"\1::AttributeIndexSet\n\t\1::AttributeIndex(\2);",
text, flags=re.S)
text = re.sub(
r"MemoryBlock\s*\*\s*(\w+)::GetAllocatedMemory\(\)\s*"
r"\{\s*static\s+MemoryBlock\s+\w+\s*\(([^;]*?)\);\s*return\s*&\w+;\s*\}",
r"MemoryBlock\n\t\1::AllocatedMemory(\2);",
text, flags=re.S)
body = text.splitlines()
res = []
warned = set()
for line in body:
s = line.strip()
if s == "#pragma once":
continue
# local include, possibly with a relative path -- keep the basename
m = re.match(r'#include\s+"(?:[.\\/A-Za-z0-9_]*[\\/])?([A-Za-z0-9_]+)\.h(?:pp)?"', s)
if m:
inc = m.group(1).lower()
g = inc.upper() + "_HPP"
res.append("#\tif !defined(%s)" % g)
res.append("#\t\tinclude <%s.hpp>" % inc)
res.append("#\tendif")
continue
# post-1994 C++ the 4.52 compiler rejects: de-modernize
line = re.sub(r"\b(\w+)::GetClassDerivations\(\)", r"\1::ClassDerivations", line)
line = re.sub(r"\b(\w+)::GetMessageHandlers\(\)", r"\1::MessageHandlers", line)
line = re.sub(r"\b(\w+)::GetAttributeIndex\(\)", r"\1::AttributeIndex", line)
line = re.sub(r"\b(\w+)::GetAllocatedMemory\(\)", r"\1::AllocatedMemory", line)
# header-side: 2007 accessor DECLARATIONS -> 1995 static objects
line = re.sub(r"static\s+Derivation\s*\*\s*GetClassDerivations\(\);",
"static Derivation ClassDerivations;", line)
line = re.sub(r"static\s+(?:\w+::)?MessageHandlerSet\s*&?\s*GetMessageHandlers\(\);",
"static MessageHandlerSet MessageHandlers;", line)
line = re.sub(r"static\s+(?:\w+::)?AttributeIndexSet\s*&?\s*GetAttributeIndex\(\);",
"static AttributeIndexSet AttributeIndex;", line)
line = line.replace("Shutdown(int remainingApps);", "Shutdown(int remainingApps = 0);")
line = re.sub(r"\*?\s*(?<![:\w])GetClassDerivations\(\)", "ClassDerivations", line)
line = re.sub(r"(?<![:\w])GetMessageHandlers\(\)", "MessageHandlers", line)
line = re.sub(r"(?<![:\w])GetAttributeIndex\(\)", "AttributeIndex", line)
line = line.replace("PostQuitMessage(AbortExitCodeID);", 'Fail("Exiting");')
for bad in ("atlbase.h", "windows.h", "winsock", "windows.hpp", "<cstdlib>", "<cstring>", "<cstdio>"):
if bad in line and "#include" in line:
line = ""
break
line = re.sub(r"\bstd::", "", line)
line = re.sub(r"\btrue\b", "True", line)
line = re.sub(r"\bfalse\b", "False", line)
line = re.sub(r"\bbool\b", "Logical", line)
for bad in ("<ostream>",):
if bad in line:
line = None
break
if line is None:
continue
for marker in ("namespace", "static_cast", "reinterpret_cast", "const_cast"):
if marker in s and marker not in warned:
warned.add(marker)
print("WARNING: post-1994 construct '%s' present -- fix by hand" % marker)
res.append(line)
# strip leading blank lines
while res and not res[0].strip():
res.pop(0)
out_text = "\n".join(res)
# cleanups after the accessor unwrap: a deref of the former pointer
# accessor, and doubled class prefixes from the static rename
out_text = re.sub(r"\*\s*(\w+)::ClassDerivations\b", r"\1::ClassDerivations", out_text)
out_text = re.sub(r"\b(\w+)::\1::", r"\1::", out_text)
is_header = out.upper().endswith((".HPP", ".H", ".THP"))
with open(out, "w", newline="\r\n") as f:
if is_header:
f.write("#if !defined(%s)\n#\tdefine %s\n\n" % (guard, guard))
f.write(out_text)
if is_header:
f.write("\n\n#endif\n")
else:
f.write("\n")
print("wrote %s (%d lines from %s)" % (out, len(res) + 5, donor))
if __name__ == "__main__":
main()