W3P: the fake header, the missing HM3W and the 30,000 fake files
(how to open maps like Zombie Defense 0.25xmas_fix_w3p, and hide the junk from MPQ Editor)
Hello.This guide is for maps protected with the protector that leaves _w3p or w3pro in the file name. It comes in two strengths.
The light one only writes 02 01 20 00 into dwHeaderSize, which puts MPQ Editor into read-only mode. That is a 4-byte fix and it is covered at the end.
The heavy one is what this guide is really about. Our example is Zombie_Defense_0.25xmas_fix_w3p.w3x (51,073,855 bytes). It has no HM3W header, a fake MPQ header in the usual place, the real one hidden a bit further down, a decoy script, and about 30,000 fake files that make MPQ Editor, "Extract all" and most converters useless. The map itself plays fine, because the game ignores every one of these tricks.
Before we start
- What is HM3W?
- The first 512 bytes of a .w3x: "HM3W", 4 zero bytes, the map name, the map flags and the player count. The World Editor and the map list read it. The MPQ archive starts right after it, at 0x200.
- 32 bytes that start with 4D 50 51 1A ("MPQ\x1A"). They say where the hash table and the block table are, and how big they are. Every position in the archive is counted from the header, not from the start of the file.
- A header that starts with 4D 50 51 1B ("MPQ\x1B"). StarCraft II uses it to point at the real MPQ header. Warcraft III does not read it at all. The StormLib source says it plainly: Warcraft III does not check for user data, and many map protectors abuse that.
- It checks every multiple of 0x200 from the start of the file. It takes the first "MPQ\x1A" whose header size is at least 0x20, unless its hash table or block table would start past the end of the file. That one is thrown away and the search goes on. This last rule is the whole trick.
- HxD
- MPQ Editor (Ladik's)
- For the fake files: Python 3 and the script at the bottom of this post. There is no sane way to deal with 30,000 entries by hand.
Step 1: Recognise it
Open the map in HxD and look at the first 0x420 bytes:Code: Select all
000000 4D 50 51 1B 39 05 00 00 39 05 00 00 39 05 00 00 MPQ.9...9...9...
000010 42 DC BC C2 45 F6 B2 3C 5F 80 F3 D4 7C 0D 54 38 (random bytes up to 0x1FF)
...
000200 4D 50 51 1A 02 01 20 00 38 B9 ED 01 00 00 03 00 MPQ.....8.......
000210 D6 44 8B 3A 6F 86 8D 81 15 6D 87 61 11 3C 4B 7D (random bytes up to 0x3FF)
...
000400 4D 50 51 1A 20 00 00 00 40 4F FB 02 00 00 03 00 MPQ.....@O......
000410 FF DA F3 02 FF DA 03 03 00 00 01 00 44 77 00 00 ............Dw..- 0x000: no "HM3W". Instead there is a user data header, "MPQ\x1B", with all three of its fields set to 0x539 (1337). 0x539 is not even a multiple of 0x200, and what sits there is random bytes.
- 0x200: an MPQ header that is fake. The signature is right and the header size is 02 01 20 00 = 0x00200102, which passes the "at least 0x20" test. Everything after that is random: the hash table would start at 0x200 + 0x3A8B44D6, which is about 980 MB into a 51 MB file, and the table sizes are in the billions.
- 0x400: the real header. It is clean:
- 0x404: 20 00 00 00 = dwHeaderSize 32. Fine.
- 0x408: 40 4F FB 02 = dwArchiveSize. Wrong, but the game ignores it.
- 0x40C: 00 00 = format version 0. 0x40E: 03 00 = sector size 512 << 3 = 4096.
- 0x410: FF DA F3 02 = dwHashTablePos. 0x414: FF DA 03 03 = dwBlockTablePos.
- 0x418: 00 00 01 00 = 65,536 hash entries. 0x41C: 44 77 00 00 = 30,532 block entries.
Symptoms you will have seen:
- Converters and old tools say the file is not a valid MPQ, crash, or show an empty file list. They stopped at 0x200 and read the tables from nowhere.
- MPQ Editor opens it but lists about 30,500 files, almost all of them "File0000xxxx.xxx".
- "Extract all" never ends, and the disk fills up.
- Extracting Scripts\war3map.j fails, while war3map.j works.
Step 2: What the protection actually does
1) The HM3W header is replaced with user data.Warcraft III never reads user data, so the map still plays. But anything that checks for HM3W no longer sees a map, and anything that follows the user data (a tool that thinks it is a StarCraft II archive) lands on garbage at 0x539.
2) A fake header at 0x200.
The game finds it first, sees that its tables would start past the end of the file, drops it and keeps looking. It finds the real header at 0x400. A tool that just takes the first "MPQ\x1A" it finds never gets there. The 02 01 20 00 in its header size is the same value the light variant writes into the real header, which is how you know both come from the same protector.
3) About 30,000 fake files.
The block table has 30,532 entries and the hash table points at 30,530 of them. Only 529 are real files. The fakes come in three kinds:
- Twins: exact copies of a real file's block entry (same position, same sizes, same flags) under a random name. Almost every real file has between 1 and 11 of them (8 have none). 2,014 in total. They read fine, because they are the real data.
- Near twins: copies of a real entry with the sizes shifted by a few bytes. The data is real but the sizes are not, so they cannot be read.
- Random slices: entries that point at random places inside the archive, with random sizes, and about 8,000 of them flagged as encrypted.
4) A decoy script.
Scripts\war3map.j has the exact unpacked size of the real war3map.j (1,778,027 bytes), but its sector offset table is random numbers, so it cannot be read. The game loads war3map.j. A tool that prefers Scripts\war3map.j fails or hangs.
5) No (listfile).
Nothing unusual here, but it means the real files show up as unnamed too, until you load a listfile.
Step 3: Removing it
Method A: the script (recommended)Save the script from the spoiler at the bottom as w3p_fix.py, put it next to your map and run:
Code: Select all
python w3p_fix.py MyMap.w3x
python w3p_fix.py MyMap.w3x --clean
python w3p_fix.py MyMap.w3x --clean --listfile=names.txt--clean also hides the fake files. It needs no names, and on our map it hides 27,987 of the 30,530 entries. With a listfile it also hides the twins of every file the listfile names: 29,066 hidden, 1,464 left. The 1,464 are the 529 real files plus the twins of real files whose names are unknown. Those twins stay on purpose: if you do not know a file's name, you cannot tell which of its twins is the entry the game opens, and deleting the wrong one breaks the file.
How does it know a file is fake? A real file must be readable the way the game reads it. For a compressed file that means: the sector offset table starts with its own size, only goes up, and ends exactly at the packed size of the block, and every sector unpacks to exactly the size it should. A file that fails this cannot be opened by the game, so hiding it changes nothing in game. The same goes for an entry that covers part of a file that does pass: two real files never share bytes. Encrypted files with no known name are checked the same way StormLib reads them, by recovering the key from the sector table.
I checked this against 14,039 real files from 12 maps (Chinese KK maps, Korean RPGs from m16, Wurst maps and these ones) and every single one passed. On this map, the only named file that fails is the decoy Scripts\war3map.j, which is exactly what should happen.
"Hiding" means: the hash entry of each fake gets the block index FF FF FF FE (deleted) and its block entry becomes all zeros. A deleted entry does not break the search for other names, so every real name still resolves to the same data, and MPQ Editor reuses deleted slots when you add a file. No file data is moved or changed.
Where do I get a listfile? The bigger the better. Any of the well known Warcraft III listfiles helps, and names mined from the map's own script and object data help more. On this map I recovered 293 names.
Method B: by hand in HxD
The header part is easy to do by hand. The fake files are not (30,000 entries), so for those use the script, or just live with them (see Step 4). Work on a copy.
▬ 3.1: write the HM3W header over the user data ▬
- Select 0x000 to 0x1FF and fill it with zeros.
- At 0x000 type 48 4D 33 57 00 00 00 00 ("HM3W" and four zeros).
- At 0x008 type the map name as text, then one 00.
- Right after that 00: the map flags (4 bytes) and the player count (4 bytes), both little-endian. You find both in war3map.w3i. For this map: flags 68 DC 03 00 (0x0003DC68) and players 10 00 00 00 (16).
▬ 3.2: kill the fake header ▬
- At 0x200, replace 4D 50 51 1A with 00 00 00 00.
▬ 3.3: optional, fix dwArchiveSize ▬
- At 0x408 write the file size minus 0x400. Here 51,073,855 - 1,024 = 0x030B4F3F, which is 3F 4F 0B 03.
Step 4: Check your work
- Open the fixed map in MPQ Editor. It must open in edit mode. Without --clean you still see about 30,500 entries. With --clean about 2,500, and with --clean and a listfile about 1,460.
- Load your listfile in MPQ Editor so the real names show up.
- Extract war3map.j and open it in a text editor. It must start with
Code: Select all
globals - Compare with the original in HxD (Analysis, File compare). The header fix changes only the first 0x204 bytes and 0x408-0x40B. --clean also changes the hash table and the block table (the last 1.5 MB of the file). Nothing in between may change.
Note: the World Editor wants more than the headers. This map's script was run through an optimizer, so the trigger files are gone (war3map.wtg, war3map.wct). Opening it in the World Editor is a separate job.
The light variant
Castle Fight DE beta 9.33, Forest Defense 0.21g and Sunken Defense 05r have none of the above. Their real header sits at 0x200 as usual, and the only change is dwHeaderSize:Code: Select all
000200 4D 50 51 1A 02 01 20 00 ...Two more things you may run into with these maps:
- They use a sector size of 16 MB (0x20E = 0F 00), so every file is one sector, and Castle Fight packs some files with bzip2 (the sector starts with 10 42 5A 68, the byte 0x10 followed by "BZh"). Tools that only know zlib fail on those files. MPQ Editor reads them fine.
- Castle Fight fills its whole hash table: 65,536 entries for 272 files, up to 280 entries per file. Nothing is fake data, but with no free slot MPQ Editor cannot add a file. python w3p_fix.py CastleFight.w3x --aliases --listfile=names.txt marks the extra entries of every named file as deleted (32,135 on that map) and leaves the name's own entry alone.
Summary
- Recognise: "MPQ\x1B" at 0x000 instead of HM3W, "MPQ\x1A 02 01 20 00" at 0x200 with nonsense after it, and the real header at 0x400.
- Understand: the game ignores user data and skips a header whose tables are outside the file. The fakes are entries the game never opens.
- Fix: write HM3W over 0x000-0x1FF, zero the signature at 0x200, optionally fix dwArchiveSize at 0x408. Hide the fake files with the script.
- Check: MPQ Editor opens it in edit mode, war3map.j extracts as text, and nothing outside the headers and tables changed.
Have fun.
Spoiler: