mkdist: archive the .map beside the .pdb, and stop shipping d3dx9_43.dll twice

Symbols only matter if they survive the build that produced them, so archive
BOTH per version, not just the PDB. The .map is the one that gets used in
practice: tools/symcrash.py resolves btl4+0xNNNN from plain text with no
debugger installed, which is the normal state of an operator box -- this
machine has no cdb either. Warns loudly if either file is missing, since that
silently means a crash in that build can never be read.

Also: d3dx9_43.dll was written into the zip twice. It exists both next to the
exe and in redist/, and both paths land at build/Release/<name> in the archive,
so the entry was duplicated -- about 2 MB wasted and a duplicate-name warning
from Python's zipfile that some extractors also complain about. Redist entries
now skip anything already taken from beside the exe.

Neither the pdb nor the map can reach a zip: .gitignore covers *.pdb and dist/,
and the archive only ever picks up .dll from those directories.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-07-28 20:14:22 -05:00
co-authored by Claude Fable 5
parent 68ffe556ce
commit 2cbf9cd88f
+24 -10
View File
@@ -100,8 +100,13 @@ def main():
# d3dx9 + MSVC runtimes -- carried in redist/, shipped next to the exe;
# the first zips included them and machines without the redists fail
# to load the exe otherwise).
# ...but SKIP any that was already taken from beside the exe: both land at
# build/Release/<name> in the archive, so shipping both wrote a duplicate
# zip entry (d3dx9_43.dll, ~2 MB wasted, and some extractors warn).
beside_exe = {os.path.basename(p).lower() for p in exes}
redist = ["redist/" + f for f in sorted(os.listdir("redist"))
if f.lower().endswith(".dll")] if os.path.isdir("redist") else []
if f.lower().endswith(".dll")
and f.lower() not in beside_exe] if os.path.isdir("redist") else []
total = 0
with zipfile.ZipFile(zpath, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as z:
@@ -172,15 +177,24 @@ def main():
% (len(bats) + len(exes) + len(tracked), total / 1048576.0,
zsz / 1048576.0))
# Crash-forensics: archive this build's PDB next to the zip (operator-side
# only, never shipped) so the [crash] btl4+0xNNNN offsets in player logs
# stay resolvable per distributed version.
pdb = "build/Release/btl4.pdb"
if os.path.exists(pdb):
import shutil
pdb_dest = zpath[:-4] + ".pdb"
shutil.copyfile(pdb, pdb_dest)
print(" symbols archived: %s" % pdb_dest)
# Crash-forensics: archive this build's symbols next to the zip (operator-
# side only, never shipped) so the [crash] btl4+0xNNNN offsets in player
# logs stay resolvable per distributed version. BOTH files:
# .pdb full symbols, for cdb/WinDbg
# .map plain text -- tools/symcrash.py resolves an offset with NO
# debugger installed, which is the common case on an operator box.
# Keep them: once a version is in players' hands its symbols are the only
# way to read a crash it produces.
import shutil
for src, ext in (("build/Release/btl4.pdb", ".pdb"),
("build/Release/btl4.map", ".map")):
if os.path.exists(src):
dest = zpath[:-4] + ext
shutil.copyfile(src, dest)
print(" symbols archived: %s" % dest)
else:
print(" WARNING: %s missing -- a crash in this build will NOT be "
"resolvable (need /Zi + /DEBUG + /MAP)" % src)
if __name__ == "__main__":
main()