Map Archive Import Pipeline
How the r2010 collection was brought from FileUniverse into TA Archive: 1,674 maps downloaded, virus-scanned, metadata-extracted, minimap-rendered and integrated β including every encoding trap the internet set along the way.
Results at a glance
Download: skip the browser
FileUniverse sits behind Cloudflare, but individual zip files can be fetched with plain urllib + a Chrome User-Agent:
req = urllib.request.Request(url, headers={"User-Agent":
"Mozilla/5.0 (X11; Linux x86_64) ... Chrome/131.0.0.0 Safari/537.36"})
data = urllib.request.urlopen(req, timeout=180).read()
curl and HEAD requests get 403; a full GET with the Chrome UA gets through. No Playwright required.
Validating a map zip
A playable TA map must contain at least one of:
| Extension | Meaning |
|---|---|
.ufo | r2010 package (HPI container, magic bytes HAPI) β usually contains TNT + OTA internally |
.tnt | Terrain data (may need a separate .ota) |
.ota | Mission parameters (players, size, AI profiles) |
Virus scanning: clamscan --no-summary -i <zip> β exit 0 = clean, exit 1 = infected. Infected files are quarantined, never integrated.
Integration order (get this right or it breaks)
- Move zips into
static/downloads/maps/(spaces β underscores). - Render minimaps from TNT heightmap indices through the 256-color
PALETTE.PALintostatic/minimaps_color/. - Extract metadata β size, players, description. Two sources:
- OTA text (loose file or hidden inside the UFO, see below)
- README
.txtinside the zip ("Map Size: 15 x 15","Size:14 x 4","numplayers=Two or Four;")
- Percent-encode all URL fields in
data/maps_all.json(#β%23,[/]β%5B/%5D, non-ASCIIβUTF-8-percent). - Build to a staging directory first; only deploy to live after verification.
maps_all.json, the new maps silently get no minimap and no metadata. Always run extraction after the entry-add step, or re-run it.Where the OTA hides
The r2010 packagers stored the OTA inside the first 65536-byte SQSH blob of the UFO β mission parameters followed by zero-padding, not as its own small chunk. A scanner that only inspects “small blobs” will wrongly conclude the OTA is missing. The OTA text (missionname=, numplayers=, size=) can be found by decompressing every SQSH blob (zlib for flag 2, LZ77 for flag 1) and searching for missionname in the result.
Consequence: maps without an obviously visible OTA are still playable β TA reads the OTA from the blob.
URL-encoding traps
# hash 20x20) had raw # in their URLs. Browsers treat # as a fragment marker and cut the URL: the minimap request becomes /minimaps_color/ β 404, downloads fail. Fix: %23 everywhere, in zip, minimap and minimap_color.Other cases found in the wild:
| Raw | Encoded | Cause |
|---|---|---|
# | %23 | HTML fragment trap |
[ / ] | %5B / %5D | IPv6-host parser confusion |
Γ© Γ Γ | %82 %a2 %8c | CP850/DOS encoding β FileUniverse stores filenames in DOS codepage |
& | %26 | HTML-entity escapes from scraped listings (&) |
mΓΌ | stored as m%fc | CP850 again; rename the local file to mu_... for a safe filesystem path |
When a Wayback-derived URL fails with 404, check whether the listing page stored the filename as raw CP850 percent-bytes β substitute those exact bytes and the download works.
Ghost entries
static/downloads/maps/ contains subdirectories (cavedog/, tam/, incoming/, β¦). An importer that runs over os.listdir without filtering creates ghost map-cards with empty names, no minimap, no details β they appear sorted first on the page. Only .zip files count as maps.
Source collection status
Updated 2026-09-17 after the September sourcing sweep. Live ledger:
~/ta-maps-fu/SOURCING_LEDGER.md (19 tracked sources with status).
| Source | State |
|---|---|
FileUniverse maps/ (r2010, 1,674 zips) | fully integrated |
FileUniverse maps-1 (author folders, ~69 dirs) | fully integrated (902 files, 15 leftovers, 2 deferred) |
FileUniverse maps-2 | pending β needs Wayback Machine listings (IA was offline during the sweep) |
ModDB /games/total-annihilation/downloads + /addons | fully integrated: 73 download files + 23 map addons md5-verified; 26 unit/mod versions with complete version histories (Total Mayhem, TA:Escalation, TA:Enhanced, The Secret Forces) |
ModDB mod-specific /mods/*/downloads (11 TA mods) | fully integrated: 55 new maps extracted from the ten maps*.ccx HAPI containers of TA: Ultimate War; the rest deduplicated against existing fingerprints |
| Cavedog mirror (tauniverse.com) | checked β only the 15 bonus-unit installers we already quarantine |
| zx.net.nz Cavedog mirror | checked β 15 EXE installers, identical to our quarantine manifest, nothing new |
| TAMEC (the big 1998β2002 archive) | not integrated β the biggest remaining gap. Survives only via Wayback Machine snapshots of planetannihilation.com/tamec; a ModDB feature-pack (TAMEC2004) exists but contains feature files, not maps |
| Unit archives (20 map-typed collections, 222 UFOs) | swept for hidden maps: 55 found (TA: Ultimate War ccx containers), the rest fingerprint-duplicates |
Sourcing workflow notes
Things that worked (or bit us) during the sweep, beyond what the integration order above covers:
- Content fingerprints before integration. Every candidate zip gets a
SHA-256 of its minimap block (or the whole TNT for loose pairs) and is
checked against
_fingerprints.json. In practice about half of the “new” files from any source turn out to be re-zipped duplicates β the Mayhem Map Packs appeared byte-different at zip level while their UFO payloads were identical, so compare archive contents, not the wrapper. - Metadata lives with the download. The ModDB scrape carried uploader, upload date, MD5 and description per file, which made verification (re-hash on arrival) and provenance records free. Scraping sources without metadata costs that forever.
- The browser detour. Cloudflare-managed challenges reject headless
browsers outright. The working setup: a real desktop browser on a second
machine solves the challenge once, then plain
rsyncmoves the files to the server. Files arrive with an MD5 manifest and get re-verified server side. - Hidden maps inside unit collections. Unit archives routinely carry
map packs under mod-specific types (
ufo-container,HAPI-container,nested-zip,map-pack). A fingerprint sweep over every UFO in the units tree found 55 previously unknown maps in one mod’s ten.ccxcontainers β everything else was already integrated. Worth re-running whenever new unit collections land.
Tools
The import pipeline scripts live in ~/ta-maps-fu/ (_fu_*.py), all resumable with state in _state.json. Metadata source of truth: ~/ta-halle/data/maps_all.json.
Minimap integrity audit (Sep 2026)
Every map card’s minimap can be verified against ground truth: the 252Γ252
palette-index block at ptrMm inside the map’s .tnt. Three lessons from a
full 3,784-card audit:
- Never extract TNTs by scanning for chunk markers. A scan-and-merge
heuristic that concatenates every decompressed chunk of an archive builds a
byte stream that straddles file boundaries. For multi-map packs (Zombieland
Map Pack V2: 9 maps in one UFO) the misaligned stream renders tile graphics
as the minimap β a diagonal stripe pattern, diagnostic because 1,024-byte
tiles sliced at 252 bytes per row drift 16 pixels per tile. Always read the
HPI directory and extract the first
.tntfile properly. - A “wrong” minimap is not always corrupt. On pack cards the image may be a real minimap of a sibling map in the same pack (2 of 57 mismatches). Classify by comparing the live PNG against every TNT minimap in the archive before calling it garbage.
- Audit helpers must resolve URLs before failing. Percent-encoded
(
%C3%B3) and renamed (%23β_hash_) filenames made 41 cards look “missing” to a naiveos.path.existscheck; all but the encoding issue were fine.urllib.parse.unquotethe URL field first.
Verification tooling: ~/ta-maps-fu/_mm_audit.py (all cards vs TNT truth),
_mm_triage.py (sibling vs garbage classification), _mm_fix.py (re-render
through the directory-based extraction chain), _mm_7z_check.py (zips with
exotic compression methods that python’s zipfile cannot read β extract via
7z e). Pixel-perfect reference for the renderer: author-shipped PCX captures
(252Γ252) inside the Zombieland pack match the TNT minimap block byte for byte.
Exploded index (~/ta-extract)
Almost everything in a TA download is hidden inside containers (zip β UFO/HPI
β SQSH chunks) with mixed compression, and every pipeline pass used to pay the
full decompress cost again. ~/ta-maps-fu/_explode.py therefore builds a
regenerable cache:
~/ta-extract/<section>/<zip-stem>/β small metadata files (.fbi,.tdf,.ota,.gaf,.txt,.bos,.cob, < 4 MB) with their inner container paths preserved; big payloads (.tnt,.3do,.wav) stay manifest-only__pack.jsonper pack (full inner file list) and_index.jsonlglobally- the canonical zips under
~/ta-live/downloadsare NEVER touched β the cache is disposable and rebuilt withpython3 _explode.py maps units
Consumers use ~/ta-maps-fu/_exstore.py (lookup/lookup_bytes): it returns
the extracted files or None, in which case the caller falls back to full
container parsing. The lookup also returns None when a pack’s manifest holds
“too big” meta files, so the cache can never silently hide candidates.
Measured: an OTA lookup drops from 100β300 ms (decompress) to ~3 ms.
Trap: gadget GAFs (unit interface icons, entry version 3) report frame-count 0
while holding icon triples β _pack_pics.gaf_icons scans frame items instead
of trusting the count field.