Initial commit
I can parse some of the data files, and extract individual frames from .obj files. I can't yet convert those frames into something viewable.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/load
|
||||||
|
/orig
|
407
README.md
Normal file
407
README.md
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
# WH40K: Chaos Gate
|
||||||
|
|
||||||
|
Re-implementation of the WH40K binary using SDL.
|
||||||
|
|
||||||
|
**You must have a copy of the original game data to use this project**
|
||||||
|
|
||||||
|
Files:
|
||||||
|
|
||||||
|
```
|
||||||
|
Anim/
|
||||||
|
WarHammer.ani # Doesn't seem to be a RIFF file. 398M so very important.
|
||||||
|
* There's a pcx image header at `dd ... bs=1 skip=213` but it seems to be a false alert
|
||||||
|
* Hits for "AmigaOS bitmap font"... probably a false positive
|
||||||
|
* Lots of 8-byte reads when loading stuff in the mission editor
|
||||||
|
* Some ~4K reads, havent found one corresponding to a known format yet
|
||||||
|
Assign/
|
||||||
|
*.asn # Unknown, seems to be related to .obj files
|
||||||
|
Cursor/
|
||||||
|
*.ani # RIFF data
|
||||||
|
*.cur # Presumably standard windows-format non-animated cursors
|
||||||
|
Data/
|
||||||
|
*.dat # plaintext files defining properties of objects. No single format
|
||||||
|
PARSED:
|
||||||
|
Accounting.dat # key = value => internal/data/accounting.go
|
||||||
|
AniObjDef.dat # animated object definitions
|
||||||
|
GenericData.dat # Generic Game Settings
|
||||||
|
IN PROGRESS:
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
ChaNames.dat # list of character names
|
||||||
|
Coordinates.dat # Weapon Firing Coordinates
|
||||||
|
Defs.dat # defines properties for objects and tiles, each of which seems to have an id
|
||||||
|
GDestroy.dat # table of what destroys what?
|
||||||
|
HasAction.dat # "Are there animation for each of the character" - list of booleans
|
||||||
|
MiniMap.dat # lots of seemingly random numbers. IDs?
|
||||||
|
MissionBriefing.dat # Contains all Campaign Mission Briefing Text. Sections: "CAMPAIGN MISSION X ... END CAMPAIGN MISSION X"
|
||||||
|
PWeight.dat # Personality weights for the individual character types
|
||||||
|
Random_AI.dat # contains the percentage of the different AI types for each of the different Chaos Squad Types
|
||||||
|
RandomPlanets.dat # Campaign Primary and Secondary Objectives
|
||||||
|
Sounds.dat # Sound Effect Data
|
||||||
|
SpellDef.dat # SPELL DEFINITIONS
|
||||||
|
StdWeap.dat # SQUAD STANDARD WEAPONS
|
||||||
|
Ultnames.dat # List of names for ultramarines
|
||||||
|
VehicDef.dat # VEHICLE DEFINITIONS
|
||||||
|
WeapDef.dat # Weapon definitions
|
||||||
|
PROBABLY NOT NEEDED:
|
||||||
|
BugHunt.dat # Contains SMF text for Bug hunt random missions
|
||||||
|
Credits.dat # list of credits
|
||||||
|
GenArm.dat # "Random campaign armory levels - space marine"
|
||||||
|
HeroArm.dat # "Random campaign armory levels - Veteran"
|
||||||
|
MHeroArm.dat # "Random campaign armory levels - Veteran"
|
||||||
|
PlanetDesc.dat # well-documented params. random mission generator params per planet.
|
||||||
|
RandChar.dat # Random character matrix. Information on what goes into a character matrix.
|
||||||
|
RandSMFtext.dat # SuperMacro Objectives for Campaign Random Mission Generator.
|
||||||
|
SpArm.dat # "RANDOM CAMPAIGN ARMORY LEVELS - Veteran"
|
||||||
|
VetArm.dat # "RANDOM CAMPAIGN ARMORY LEVELS - Veteran"
|
||||||
|
|
||||||
|
*.chk # checksums? Mentions all the .dat files
|
||||||
|
*.cyc # ColorCycle DataFile.
|
||||||
|
*.dta # localized strings and things
|
||||||
|
Encyclopedia.dta # encyclopedia entries
|
||||||
|
KeyMap.dta # unknown
|
||||||
|
Keymap.dta # unknown
|
||||||
|
USEng.dta # Localized strings
|
||||||
|
|
||||||
|
EquipmentMenuData # gzip-compressed, presumably to do with (initial?) squad configuration
|
||||||
|
Filters/
|
||||||
|
wh40k.flt # Audio filter(s?)
|
||||||
|
Fonts/
|
||||||
|
cboxfont # unknown
|
||||||
|
*.fnt # ASCII text. Map keycodes to sprites?
|
||||||
|
*.spr # sprites, presumably
|
||||||
|
Idx/
|
||||||
|
WarHammer.idx # unknown, 1.8M
|
||||||
|
Maps/
|
||||||
|
*.MAP # gzip-compressed, must be map definition, contains `WHMAP` constant and character names
|
||||||
|
*.TXT # scenario text
|
||||||
|
Menu/
|
||||||
|
*.mnu # plain text, linked to .obj file, contains definitions
|
||||||
|
*.obj # unknown, not 3D .obj format
|
||||||
|
Misc/
|
||||||
|
occlusio.lis # plain text, presumably occlusion mappings?
|
||||||
|
MultiMaps/
|
||||||
|
*.MAP # Like Maps/
|
||||||
|
*.TXT
|
||||||
|
Obj/
|
||||||
|
*.obj # must be visual data of some sort, one per placeable map object?
|
||||||
|
Pic/
|
||||||
|
*.pcx # Standard .pcx format
|
||||||
|
RandomMaps/
|
||||||
|
*.chk # multiplayer. worry about these another day
|
||||||
|
*.dat
|
||||||
|
Save_G/
|
||||||
|
*.sav # savedata, gzip-compressed, custom format
|
||||||
|
*.txt # Seems to be a copy of one of Maps/*.txt
|
||||||
|
Sets/
|
||||||
|
Data.chk # checksums? Mentions all the .set files
|
||||||
|
*.set # plain text, related to maps, lists a set of .obj files for that map?
|
||||||
|
SMK/
|
||||||
|
*.smk # Videos: RAD Game Tools Smacker Multimedia version 2
|
||||||
|
Sounds/
|
||||||
|
wh40k.ds # 0xffffffff then a list of .wav file names. Some sort of index?
|
||||||
|
Wav/
|
||||||
|
*.wav # normal WAV files, all mentioned in Sounds/wh40k.ds
|
||||||
|
```
|
||||||
|
|
||||||
|
WH40K.exe is the existing game engine
|
||||||
|
|
||||||
|
WH40K_TD.exe is the map editor. Allows things to be saved as .MAP or as .SMF
|
||||||
|
("Super Macro File")
|
||||||
|
|
||||||
|
"Mission Setup" includes information about available squad types
|
||||||
|
|
||||||
|
|
||||||
|
From EquipDef.cpp Dumo: CEquipment we learn the following object types:
|
||||||
|
|
||||||
|
0. DELETED
|
||||||
|
1. WEAPON
|
||||||
|
2. GRENADE
|
||||||
|
3. MEDIPACK
|
||||||
|
4. SCANNER
|
||||||
|
5. GENESEED
|
||||||
|
6. CLIP
|
||||||
|
7. DOOR KEY
|
||||||
|
8. DOOR KEY
|
||||||
|
9. DOOR KEY
|
||||||
|
10. DOOR KEY
|
||||||
|
|
||||||
|
And we learn they can be "on"....
|
||||||
|
|
||||||
|
0. CHARACTER
|
||||||
|
1. VEHICLE
|
||||||
|
2. CANISTER
|
||||||
|
|
||||||
|
|
||||||
|
Hypothesis: Idx/WarHammer.idx points objects into bitmap data in Anim/WarHammer.ani
|
||||||
|
|
||||||
|
We can use WH40K_TD.exe and investigate reads of .idx followed by reads of .ani
|
||||||
|
to test this.
|
||||||
|
|
||||||
|
Obj/*.obj and Assign/*.asn files seem to have some identity
|
||||||
|
|
||||||
|
|
||||||
|
WH40K_TD.exe opens files in this order:
|
||||||
|
|
||||||
|
1. Data/USEng.dta
|
||||||
|
1. WH40K_TD.exe (?)
|
||||||
|
1. Cursor/Cursors.cur
|
||||||
|
1. pread64(fd, 23, 0) = 23
|
||||||
|
1. _llseek(fd, 0, [0], SEEK_CUR) = 0
|
||||||
|
1. _llseek(fd, 0, [0], SEEK_CUR) = 0
|
||||||
|
1. _llseek(fd, 40666, [40666], SEEK_SET) = 0
|
||||||
|
1. _llseek(fd, 0, [0], SEEK_SET) = 0
|
||||||
|
1. close(fd) = 0
|
||||||
|
1. read(fd, "\x26\x00\x00\x00\x20\x00\x00\x00\x30\x01\x00\x00\x50\x01\x00\x00\x8a\x9d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 32) = 32
|
||||||
|
1. (...)
|
||||||
|
1. _llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Cursor/Cursors.cur>, 39868, [39868], SEEK_SET) = 0
|
||||||
|
1. read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Cursor/Cursors.cur>, "\x27\x00\x1f\x00\x22\x00\x24\x00\x00\x00\x00\x00\x06\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x03\x81\x1f\x80\x1a\x81\x1f\x80\x03\x00\x80\x02\x83\x1f\x32\x1f\x80\x18\x83\x1f\x32\x1f\x80\x02\x00\x80\x01\x85\x1f\x32\x37\x39\x1f\x80\x16\x85\x1f\x32\x37"..., 798) = 798
|
||||||
|
1. (some statting of Idx/WarHammer.idx, no reading that I saw)
|
||||||
|
1. Anim/WarHammer.ani
|
||||||
|
1. read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\x7e\xdf\x02\x00\x20\x00\x00\x00\xf0\xfb\x16\x00\x10\xfc\x16\x00\xa5\x4a\xc3\x18\x00\x00\x00\x00\x00\x00\x00\x00\x28\x92\x52\x00", 32) = 32
|
||||||
|
1. (some clones of child procs, I didn't follow them)
|
||||||
|
1. Sounds/wh40k.ds
|
||||||
|
1. pread64(31</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Sounds/wh40k.ds>, "\xff\xff\xff\xff\x08\x60\x06\x00\x61\x6d\x62\x5f\x30\x31\x5f\x6a\x75\x6e\x67\x6c\x65\x2e\x77", 23, 0) = 23
|
||||||
|
1. read(31</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Sounds/wh40k.ds>, "\xff\xff\xff\xff\x08\x60\x06\x00\x61\x6d\x62\x5f\x30\x31\x5f\x6a\x75\x6e\x67\x6c\x65\x2e\x77\x61\x76\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"..., 417792) = 417792
|
||||||
|
1. read(31</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Sounds/wh40k.ds>, "\xe5\x03\x00\x00\x00\x58\x02\x00\x05\x10\x00\x50\xfe\x5e\xff\x00\x72\x03\x3d\x89\x12\x30\x00\x1f\x9e\xe2\x10\xf4\x32\xdd\xef\xf1\x42\xff\x0f\xc0\x12\x9c\xf2\x70\xeb\x01\x41\x04\x20\xad\xf1\x20\x11\x60\xce\xf3\x20\xc1\x42\xfa\x01\x32\x22\x0c\xcd\x00\x0b\xfe"..., 4096) = 4096
|
||||||
|
1. Data/Sounds.dat
|
||||||
|
1. pread64(34</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/Sounds.dat>, "#**********************", 23, 0) = 23
|
||||||
|
1. ...
|
||||||
|
1. Pic/wh40k.pcx
|
||||||
|
1. read(34</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Pic/wh40k.pcx>, "\x0a\x05\x01\x08\x00\x00\x00\x00\x7f\x02\xdf\x01\x48\x00\x48\x00\x00\x00\x00\x80\x00\x00\x00\x80\x00\x80\x80\x00\x00\x00\x80\x80\x00\x80\x00\x80\x80\xc0\xc0\xc0\xc0\xdc\xc0\xa6\xca\xf0\xff\xff\xff\xf0\xf0\xf0\xdd\xdd\xdd\xcb\xcb\xcb\xbb\xbb\xbb\xb2\xb2\xb2"..., 168509) = 168509
|
||||||
|
1. Sets/*
|
||||||
|
1. (lots of statting these)
|
||||||
|
1. Data/Randchar.dat
|
||||||
|
1. pread64(34</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/RandChar.dat>, "#**********************", 23, 0) = 23
|
||||||
|
1. read(34</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/RandChar.dat>, "#***************************************************************"..., 4096) = 4096
|
||||||
|
1. ...
|
||||||
|
1. Data/WeapDef.dat
|
||||||
|
1. pread64(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/WeapDef.dat>, "#**********************", 23, 0) = 23
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/WeapDef.dat>, "#***************************************************************"..., 4096) = 4096
|
||||||
|
1. ...
|
||||||
|
1. Data/SpellDef.dat
|
||||||
|
1. pread64(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/SpellDef.dat>, "#**********************", 23, 0) = 23
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/SpellDef.dat>, "#***************************************************************"..., 4096) = 4096
|
||||||
|
1. ...
|
||||||
|
1. Data/AniObDef.dat
|
||||||
|
1. pread64(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/AniObDef.dat>, "# ******** ANIMATED OBJ", 23, 0) = 23
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/AniObDef.dat>, "# ******** ANIMATED OBJECT DEFINITIONS **************\r\n#\t\t0 : **"..., 4096) = 4096
|
||||||
|
1. ...
|
||||||
|
1. Data/VehicDef.dat
|
||||||
|
1. pread64(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/VehicDef.dat>, "# ******** VEHICLE DEFI", 23, 0) = 23
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/VehicDef.dat>, "# ******** VEHICLE DEFINITIONS **************\r\n#\t\t0 : *** VEHICL"..., 4096) = 4096
|
||||||
|
1. ...
|
||||||
|
1. Data/StdWeap.dat
|
||||||
|
1. pread64(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/StdWeap.dat>, "# ******** SQUAD STANDA", 23, 0) = 23
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Data/StdWeap.dat>, "# ******** SQUAD STANDARD WEAPONS **************\r\n#\t\t0 : *** SQU"..., 4096) = 4096
|
||||||
|
1. ...
|
||||||
|
1. Data/Ultnames.dat
|
||||||
|
1. Data/Chanames.dat
|
||||||
|
1. Data/keymap.dta
|
||||||
|
1. Filters/wh40k.flt
|
||||||
|
1. _llseek(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Filters/wh40k.flt>, 0, [0], SEEK_SET) = 0
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Filters/wh40k.flt>, "\x01\x00\x00\x00", 4) = 4
|
||||||
|
1. _llseek(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Filters/wh40k.flt>, 4, [4], SEEK_SET) = 0
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Filters/wh40k.flt>, "\x4d\x79\x46\x69\x6c\x74\x65\x72\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"..., 72) = 72
|
||||||
|
1. _llseek(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Filters/wh40k.flt>, 1444, [1444], SEEK_SET) = 0
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Filters/wh40k.flt>, "\x1f\x1e\x7f\x77\x1e\x1e\x1e\x1c\x1c\x1c\x1c\x1c\x1c\x1c\x1c\x1d\x1d\x1d\x1d\x1d\x1d\x1d\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1f\x1f\x1f\x4f\x4f\x1d\x1d\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1f\x1f\x1f\x1f\x1f\xaf\xaf\xaf\x97\x97\x97\x97\x1e\x1e\x1e\x1e\x1e\x1e\x1f\x1f\x1f"..., 327680) = 327680
|
||||||
|
1. Misc/occlusio.lis
|
||||||
|
1. pread64(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Misc/occlusio.lis>, "62 # Number of Absol", 23, 0) = 23
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Misc/occlusio.lis>, "62 # Number of Absolute Deltas.\r\n # These Deltas are off"..., 4096) = 982
|
||||||
|
1. read(35</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Misc/occlusio.lis>, "", 3114) = 0
|
||||||
|
1. Data/GDestroy.dat
|
||||||
|
1. (stat Obj/destroy.obj)
|
||||||
|
1. Data/minimap.dat
|
||||||
|
1. Misc/occlusio.list
|
||||||
|
1. Obj/specials.obj
|
||||||
|
1. Obj/Man_Shadow.obj
|
||||||
|
1. Sets/map01.set
|
||||||
|
1. Data/Defs.dat
|
||||||
|
1. Assign/jungtil.asn
|
||||||
|
1. pread64(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Assign/jungtil.asn>, "# jungle floor\r\n# jungt", 23, 0) = 23
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Assign/jungtil.asn>, "# jungle floor\r\n# jungtil.obj/.asn\r\n# /--> d:\\warflics\\mission"..., 4096) = 408
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Assign/jungtil.asn>, "", 3688) = 0
|
||||||
|
1. Obj/jungtil.obj
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\x12\x00\x00\x00\x20\x00\x00\x00\x90\x00\x00\x00\xb0\x00\x00\x00\x88\x17\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 32) = 32
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 32, [32], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\x00\x00\x00\x00\xcf\x01\x00\x00\xcf\x01\x00\x00\x72\x11\x00\x00\x41\x13\x00\x00\x55\x11\x00\x00\x96\x24\x00\x00\x74\x11\x00\x00\x0a\x36\x00\x00\x76\x11\x00\x00\x80\x47\x00\x00\x6d\x11\x00\x00\xed\x58\x00\x00\x78\x11\x00\x00\x65\x6a\x00\x00\x70\x11\x00\x00"..., 144) = 144
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 176, [176], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\xb7\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x04\x1f\x80\x3e\x00\x80\x3c\x08\x1f\x80\x3c\x00\x80\x3a\x0c\x1f\x80\x3a\x00\x80\x38\x10\x1f\x80\x38\x00\x80\x36\x14\x1f\x80\x36\x00\x80\x34\x18\x1f\x80"..., 463) = 463
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 639, [639], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x5a\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6e\x6d\x6e\x76\x76\x6e\x80\x3c\x00\x80\x3a\x84\xbf\x76\x6e\x76\x04\x6d\x84\x6e\x76\x7d\x97\x80\x3a"..., 4466) = 4466
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 5105, [5105], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x3d\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6e\x6d\x6e\x76\x97\x6d\x80\x3c\x00\x80\x3a\x8c\xbf\x1e\x6c\x6d\x6d\x6e\x6d\x76\x6e\x97\x1c\x76\x80"..., 4437) = 4437
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 9542, [9542], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x5c\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x76\x7e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6d\x6e\x6d\x6e\x76\x97\x80\x3c\x00\x80\x3a\x83\x6d\x1e\x6c\x03\x6d\x86\x6e\x96\x76\x6d\x1c\x97\x80"..., 4468) = 4468
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 14010, [14010], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x5e\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x6e\x97\x6e\x76\x67\x97\x76\x80\x3c\x00\x80\x3a\x8c\x7d\xbf\x1e\x7f\x6e\x6e\x1e\x7d\x96\x6e\x7f\x97\x80"..., 4470) = 4470
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 18480, [18480], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x55\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x65\x6e\x1e\x7c\x80\x3e\x00\x80\x3c\x88\x66\x6e\x6b\x8f\x1c\x67\x6d\x6c\x80\x3c\x00\x80\x3a\x8c\x75\x1c\x6e\x6e\xad\x8f\x1c\x8f\x6d\x1c\x7d\x1c\x80"..., 4461) = 4461
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 22941, [22941], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x60\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x75\x76\x6e\x6d\x6e\x97\x97\x80\x3c\x00\x80\x3a\x85\x6b\x6b\x6e\x96\x76\x04\x6d\x83\x6e\x6b\x97\x80\x3a"..., 4472) = 4472
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 27413, [27413], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x58\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x76\x76\x1e\x80\x3e\x00\x80\x3c\x88\x8f\x6e\x6d\x6e\x96\x6e\x76\x67\x80\x3c\x00\x80\x3a\x87\x6b\x1a\x8f\x75\x96\x1c\x76\x03\x6e\x82\x6d\x6e\x80"..., 4464) = 4464
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 31877, [31877], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x65\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6c\x6e\x97\x1c\x80\x3e\x00\x80\x3c\x88\x6d\x6e\x76\x6e\x76\x6d\x97\x1c\x80\x3c\x00\x80\x3a\x8c\x6d\x6d\x96\xad\x76\x96\x6e\x76\x6e\x6e\x97\x6e\x80"..., 4477) = 4477
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 36354, [36354], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x61\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\x75\x67\x6e\x76\x6d\x6e\x97\x1c\x80\x3c\x00\x80\x3a\x8c\x8f\x76\xad\x97\x6e\x6d\x6e\xbe\x6e\x76\x6e\x7d\x80"..., 4473) = 4473
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 40827, [40827], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x60\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6e\x6d\x6d\x6e\x97\x1c\x80\x3c\x00\x80\x3a\x82\x76\x97\x03\x6d\x87\xbe\x6e\x76\x96\x6e\x1e\x6d\x80"..., 4472) = 4472
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 45299, [45299], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x3d\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6e\x6d\x6e\x76\x97\x6d\x80\x3c\x00\x80\x3a\x8c\xbf\x1e\x6c\x6d\x6d\x6e\x6d\x76\x6e\x97\x1c\x76\x80"..., 4437) = 4437
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 49736, [49736], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\xb7\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x04\x1f\x80\x3e\x00\x80\x3c\x08\x1f\x80\x3c\x00\x80\x3a\x0c\x1f\x80\x3a\x00\x80\x38\x10\x1f\x80\x38\x00\x80\x36\x14\x1f\x80\x36\x00\x80\x34\x18\x1f\x80"..., 463) = 463
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 50199, [50199], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x83\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6e\x6d\x6e\x76\x76\x6e\x80\x3c\x00\x80\x3a\x84\xbf\x76\x6e\x76\x04\x6d\x84\x6e\x76\x7d\x97\x80\x3a"..., 4251) = 4251
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 54450, [54450], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\xe6\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6e\x6d\x6e\x76\x76\x6e\x80\x3c\x00\x80\x3a\x84\xbf\x76\x6e\x76\x04\x6d\x84\x6e\x76\x7d\x97\x80\x3a"..., 4350) = 4350
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 58800, [58800], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\x9e\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6e\x6d\x6e\x76\x76\x6e\x80\x3c\x00\x80\x3a\x84\xbf\x76\x6e\x76\x04\x6d\x84\x6e\x76\x7d\x97\x80\x3a"..., 4278) = 4278
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 63078, [63078], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\xd1\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6e\x6d\x6e\x76\x76\x6e\x80\x3c\x00\x80\x3a\x84\xbf\x76\x6e\x76\x04\x6d\x84\x6e\x76\x7d\x97\x80\x3a"..., 4329) = 4329
|
||||||
|
1. _llseek(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, 67407, [67407], SEEK_SET) = 0
|
||||||
|
1. read(36</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Obj/jungtil.obj>, "\xd1\x00\x42\x01\x80\x00\x3f\x00\x00\x00\x00\x00\xd1\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3e\x84\x6d\x6c\x6e\x1e\x80\x3e\x00\x80\x3c\x88\xbf\x76\x6e\x6d\x6e\x76\x76\x6e\x80\x3c\x00\x80\x3a\x84\xbf\x76\x6e\x76\x04\x6d\x84\x6e\x76\x7d\x97\x80\x3a"..., 4329) = 4329
|
||||||
|
1. Assign/h_jung4.asn
|
||||||
|
1. Obj/h_jung4.obj
|
||||||
|
1. (more assign + obj pairs)
|
||||||
|
1. Data/Cycle.cyc
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jungtil.asn references 18 frames (0-17) and a `jungtil.flc`, which is an AutoDesk format
|
||||||
|
|
||||||
|
We read jungtil.obj in about that number of blocks. Presumably each is a frame
|
||||||
|
in some format or another, but seemingly *not* FLC.
|
||||||
|
|
||||||
|
Here's our list of reads:
|
||||||
|
|
||||||
|
| Offset | Size | Gap | Purpose |
|
||||||
|
|--------|------|-----|---------|
|
||||||
|
| 0 | 32 | 0 | Main header
|
||||||
|
| 32 | 144 | 0 | Frame headers
|
||||||
|
| 176 | 463 | 0 | Frame 1
|
||||||
|
| 639 | 4466 | 0 | ...
|
||||||
|
| 5105 | 4437 | 0 |
|
||||||
|
| 9542 | 4468 | 0 |
|
||||||
|
| 14010 | 4470 | 0 |
|
||||||
|
| 18480 | 4461 | 0 |
|
||||||
|
| 22941 | 4472 | 0 |
|
||||||
|
| 27413 | 4464 | 0 |
|
||||||
|
| 31877 | 4477 | 0 |
|
||||||
|
| 36354 | 4473 | 0 |
|
||||||
|
| 40827 | 4472 | 0 |
|
||||||
|
| 45299 | 4437 | 0 |
|
||||||
|
| 49736 | 463 | 0 |
|
||||||
|
| 50199 | 4251 | 0 |
|
||||||
|
| 54450 | 4350 | 0 |
|
||||||
|
| 58800 | 4278 | 0 |
|
||||||
|
| 63078 | 4329 | 0 |
|
||||||
|
| 67407 | 4329 | 0 | Frame 18
|
||||||
|
|
||||||
|
20 reads in total.
|
||||||
|
|
||||||
|
The following chunks are identical:
|
||||||
|
|
||||||
|
```
|
||||||
|
* 176 and 49736 (all 463-byte frames)
|
||||||
|
* 63078 and 67407 (all 4329-byte frames)
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's the hex of the first 32 bytes:
|
||||||
|
|
||||||
|
```
|
||||||
|
0 1 2 3 4 5 6 7 8 9 a b c d e f
|
||||||
|
0x0000 12 00 00 00 20 00 00 00 90 00 00 00 B0 00 00 00
|
||||||
|
0x0010 88 17 01 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
```
|
||||||
|
|
||||||
|
* Offset 0 = 0x12 = 18 = the total number of frames ?
|
||||||
|
* Offset 4 = 0x20 = 32 = the size of the this block ?
|
||||||
|
* Offset 8 = 0x90 = 144 = the size of the next block ?
|
||||||
|
* Offset c = 0xB0 = 176 = the offset of the third read, or the size of the first two blocks combined ?
|
||||||
|
|
||||||
|
Need to unpack a few more objs to check what these numbers do. They could all
|
||||||
|
be 32-bit LE integers too.
|
||||||
|
|
||||||
|
0x10-0x13 as a 32-bit LE is 71560.
|
||||||
|
|
||||||
|
Total file size (71736) - 32 - 144 = 71560 = size of all frames combined
|
||||||
|
|
||||||
|
Obj/j_tree02.obj doesn't seem to adhere to these rules at all...
|
||||||
|
|
||||||
|
Then the next 144 bytes:
|
||||||
|
|
||||||
|
```
|
||||||
|
0 1 2 3 4 5 6 7
|
||||||
|
0x0020 00 00 00 00 CF 01 00 00 = 0, 463
|
||||||
|
0x0028 CF 01 00 00 72 11 00 00 = 463, 4466
|
||||||
|
0x0030 41 13 00 00 55 11 00 00 = 4929, 4437
|
||||||
|
0x0038 96 24 00 00 74 11 00 00 = ...
|
||||||
|
0x0040 0A 36 00 00 76 11 00 00
|
||||||
|
0x0048 80 47 00 00 6D 11 00 00
|
||||||
|
0x0050 ED 58 00 00 78 11 00 00
|
||||||
|
0x0058 65 6A 00 00 70 11 00 00
|
||||||
|
0x0060 D5 7B 00 00 7D 11 00 00
|
||||||
|
0c0068 52 8D 00 00 79 11 00 00
|
||||||
|
0x0070 CB 9E 00 00 78 11 00 00
|
||||||
|
0x0078 43 B0 00 00 55 11 00 00
|
||||||
|
0x0080 98 C1 00 00 CF 01 00 00
|
||||||
|
0x0088 67 C3 00 00 9B 10 00 00
|
||||||
|
0x0090 02 D4 00 00 FE 10 00 00
|
||||||
|
0x0098 00 E5 00 00 B6 10 00 00
|
||||||
|
0x00A0 B6 F5 00 00 E9 10 00 00
|
||||||
|
0x00A8 9F 06 01 00 E9 10 00 00
|
||||||
|
```
|
||||||
|
|
||||||
|
8 bytes per frame (8*18=144).
|
||||||
|
|
||||||
|
First four bytes in each line are the position relative to the start of the
|
||||||
|
first frame (i.e., minus the size of the first two blocks).
|
||||||
|
|
||||||
|
Last four bytes in each line are the total size of the frame, header + data.
|
||||||
|
|
||||||
|
|
||||||
|
Then there are 18 blocks of varying size. These must be the frames. First 32
|
||||||
|
bytes of all 18 blocks has a lot of similarities:
|
||||||
|
|
||||||
|
```
|
||||||
|
0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f (0c,0d) (18,19) (1a,1b) (1c,1d) (1e,1f)
|
||||||
|
00176.0463 D1 00 42 01 80 00 3F 00 00 00 00 00 B7 01 00 00 00 00 00 00 00 00 00 00 80 3E 04 1F 80 3E 00 80 439 16000 7940 16000 32768
|
||||||
|
00639.4466 D1 00 42 01 80 00 3F 00 00 00 00 00 5A 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 4442 16000 28036 28268 32798
|
||||||
|
05105.4437 D1 00 42 01 80 00 3F 00 00 00 00 00 3D 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 4413 16000 28036 28268 32798
|
||||||
|
09542.4468 D1 00 42 01 80 00 3F 00 00 00 00 00 5C 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 76 7E 80 4444 16000 28036 30316 32894
|
||||||
|
14010.4470 D1 00 42 01 80 00 3F 00 00 00 00 00 5E 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 4446 16000 28036 28268 32798
|
||||||
|
18480.4461 D1 00 42 01 80 00 3F 00 00 00 00 00 55 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 65 6E 1E 7C 80 4437 16000 25988 7790 32892
|
||||||
|
22941.4472 D1 00 42 01 80 00 3F 00 00 00 00 00 60 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 4448 16000 28036 28268 32798
|
||||||
|
27413.4464 D1 00 42 01 80 00 3F 00 00 00 00 00 58 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 76 76 1E 80 ... 16000 28036 30326 32798
|
||||||
|
31877.4477 D1 00 42 01 80 00 3F 00 00 00 00 00 65 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6C 6E 97 1C 80 16000 27780 38766 32796
|
||||||
|
36354.4473 D1 00 42 01 80 00 3F 00 00 00 00 00 61 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 16000 28036 28268 32798
|
||||||
|
40827.4472 D1 00 42 01 80 00 3F 00 00 00 00 00 60 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 16000 28036 28268 32798
|
||||||
|
45299.4437 D1 00 42 01 80 00 3F 00 00 00 00 00 3D 11 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 16000 28036 28268 32798
|
||||||
|
49736.0463 D1 00 42 01 80 00 3F 00 00 00 00 00 B7 01 00 00 00 00 00 00 00 00 00 00 80 3E 04 1F 80 3E 00 80 16000 7940 16000 32768
|
||||||
|
50199.4251 D1 00 42 01 80 00 3F 00 00 00 00 00 83 10 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 16000 28036 28268 32798
|
||||||
|
54450.4350 D1 00 42 01 80 00 3F 00 00 00 00 00 E6 10 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 16000 28036 28268 32798
|
||||||
|
58800.4278 D1 00 42 01 80 00 3F 00 00 00 00 00 9E 10 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 16000 28036 28268 32798
|
||||||
|
63078.4329 D1 00 42 01 80 00 3F 00 00 00 00 00 D1 10 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 16000 28036 28268 32798
|
||||||
|
67407.4329 D1 00 42 01 80 00 3F 00 00 00 00 00 D1 10 00 00 00 00 00 00 00 00 00 00 80 3E 84 6D 6C 6E 1E 80 16000 28036 28268 32798
|
||||||
|
IDENTICAL IDENTICAL IDENTICAL LE SIZE? IDENTICAL IDENTICAL
|
||||||
|
V 21102801 4128896 0
|
||||||
|
vv [209, 322] [128, 63] [0,0]
|
||||||
|
```
|
||||||
|
|
||||||
|
Remainder of each block is highly variable, so probably pixel data, and
|
||||||
|
(some of?) these 32 bytes are header data
|
||||||
|
|
||||||
|
bytes 4-7 could be dimension?
|
||||||
|
|
||||||
|
Tiles are 64x64 pixels (I think?), displayed in an isometric fashion, so I am
|
||||||
|
wrong or it's something else.
|
||||||
|
|
||||||
|
bytes 12 & 13 as LE 16-bit (or 12-15 as LE 32-bit) is always 24 bytes under full
|
||||||
|
block size.
|
||||||
|
|
||||||
|
Perhaps we have 24 bytes of header and that's the header-exclusive pixel data size?
|
||||||
|
Last 8 bits would then actually be the start of the image, but there's a lot
|
||||||
|
of similarity between them...
|
||||||
|
|
||||||
|
Now, how to convert the pixel data to an image?!?!
|
63
cmd/load/main.go
Normal file
63
cmd/load/main.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"ur.gs/chaos-gate/internal/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
dataPath := filepath.Join(*gamePath, "Data")
|
||||||
|
accountingPath := filepath.Join(dataPath, "Accounting.dat")
|
||||||
|
genericDataPath := filepath.Join(dataPath, "GenericData.dat")
|
||||||
|
aniObDefPath := filepath.Join(dataPath, "AniObDef.dat")
|
||||||
|
|
||||||
|
objDataPath := filepath.Join(*gamePath, "Obj")
|
||||||
|
|
||||||
|
log.Printf("Loading %s...", accountingPath)
|
||||||
|
accounting, err := data.LoadAccounting(accountingPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to parse %s: %s", accountingPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%s: %+v", accountingPath, accounting)
|
||||||
|
|
||||||
|
log.Printf("Loading %s...", aniObDefPath)
|
||||||
|
animated, err := data.LoadAnimatedObjectDefinitions(aniObDefPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to parse %s: %s", genericDataPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%s: %+v", aniObDefPath, animated)
|
||||||
|
|
||||||
|
log.Printf("Loading %s...", genericDataPath)
|
||||||
|
genericData, err := data.LoadGeneric(genericDataPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to parse %s: %s", genericDataPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%s: %+v", genericDataPath, genericData)
|
||||||
|
|
||||||
|
// TODO: Obj/cpiece.rec isn't loaded by this. Do we need it? How do we know?
|
||||||
|
log.Printf("Loading %s...", objDataPath)
|
||||||
|
objects, err := data.LoadObjects(objDataPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to parse %s: %s", objDataPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Objects in %s:", objDataPath)
|
||||||
|
for key, obj := range objects {
|
||||||
|
log.Printf("\t%s: %+v", key, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Tzeentch: %+v", objects["TZEENTCH.OBJ"])
|
||||||
|
log.Printf("TFrame 0: %+v", objects["TZEENTCH.OBJ"].Frames[0])
|
||||||
|
}
|
5
cmd/wh40k/main.go
Normal file
5
cmd/wh40k/main.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
}
|
81
doc/WarHammer.ani.md
Normal file
81
doc/WarHammer.ani.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
Adding a Librarian to the mission builder performs these seeks and reads:
|
||||||
|
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509440, [509440], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\fM\266\th\16\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509448, [509448], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "t[\266\t\376\16\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509456, [509456], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "rj\266\tg\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509464, [509464], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\331y\266\t\251\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509472, [509472], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\202\211\266\t\273\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509480, [509480], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "=\231\266\t\10\20\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509488, [509488], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "E\251\266\t\321\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509496, [509496], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\26\271\266\t\1\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509504, [509504], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\27\310\266\t\304\16\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509512, [509512], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\333\326\266\t\343\16\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509520, [509520], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\276\345\266\t\f\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509528, [509528], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\312\364\266\tA\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509536, [509536], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\v\4\267\t\246\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509440, [509440], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\fM\266\th\16\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164448540, [164448540], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\367\0\n\0015\0T\0\0\0\0\0P\16\0\0\324q;\1\0\0\0\0\200\23\207**+*+"..., 3688) = 3688
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509448, [509448], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "t[\266\t\376\16\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164452228, [164452228], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\365\0\10\0017\0W\0\0\0\0\0\346\16\0\0\324q;\1\0\0\0\0\200\25\3*\212+*,"..., 3838) = 3838
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509456, [509456], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "rj\266\tg\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164456066, [164456066], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\364\0\10\0019\0Z\0\0\0\0\0O\17\0\0\324q;\1\0\0\0\0\200\30\201*\5+\200\33"..., 3943) = 3943
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509464, [509464], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\331y\266\t\251\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164460009, [164460009], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\356\0\7\1B\0[\0\0\0\0\0\221\17\0\0\324q;\1\0\0\0\0\200\"\201*\200\37\0\200"..., 4009) = 4009
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509472, [509472], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\202\211\266\t\273\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164464018, [164464018], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\356\0\n\1C\0\\\0\0\0\0\0\243\17\0\0\324q;\1\0\0\0\0\200#\3)\3+\200\32"..., 4027) = 4027
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509480, [509480], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "=\231\266\t\10\20\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164468045, [164468045], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\354\0\t\1C\0Z\0\0\0\0\0\360\17\0\0\324q;\1\0\0\0\0\200$\201*\200\36\0\200"..., 4104) = 4104
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509488, [509488], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "E\251\266\t\321\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164472149, [164472149], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\356\0\t\1?\0V\0\0\0\0\0\271\17\0\0\324q;\1\0\0\0\0\200\35\212&&H)*"..., 4049) = 4049
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509496, [509496], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\26\271\266\t\1\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164476198, [164476198], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\366\0\10\0015\0[\0\0\0\0\0\351\16\0\0\324q;\1\0\0\0\0\200\20\201*\4+\206*"..., 3841) = 3841
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509504, [509504], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\27\310\266\t\304\16\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164480039, [164480039], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\367\0\7\0013\0[\0\0\0\0\0\254\16\0\0\324q;\1\0\0\0\0\200\33\210+,+,,"..., 3780) = 3780
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509512, [509512], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\333\326\266\t\343\16\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164483819, [164483819], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\370\0\7\1A\0_\0\0\0\0\0\313\16\0\0\324q;\1\0\0\0\0\200\34\203,,*\200\""..., 3811) = 3811
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509520, [509520], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\276\345\266\t\f\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164487630, [164487630], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\370\0\7\1H\0`\0\0\0\0\0\364\16\0\0\324q;\1\0\0\0\0\200\35\201,\200*\0\200"..., 3852) = 3852
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509528, [509528], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\312\364\266\tA\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164491482, [164491482], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\373\0\10\1;\0Z\0\0\0\0\0)\17\0\0\324q;\1\0\0\0\0\200\36\202+,\200\33\0"..., 3905) = 3905
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 509536, [509536], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\v\4\267\t\246\17\0\0", 8) = 8
|
||||||
|
_llseek(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, 164495387, [164495387], SEEK_SET) = 0
|
||||||
|
read(15</home/lupine/.wine/drive_c/GOG Games/ChaosGate/Anim/WarHammer.ani>, "\366\0\n\0018\0Y\0\0\0\0\0\216\17\0\0\324q;\1\0\0\0\0\200\32\t+\205*\4,"..., 4006) = 4006
|
||||||
|
|
52
internal/data/accounting.go
Normal file
52
internal/data/accounting.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Comments are `//`, start of line only, for accounting
|
||||||
|
var (
|
||||||
|
comment = []byte("//")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Accounting map[string]string
|
||||||
|
|
||||||
|
func LoadAccounting(filename string) (Accounting, error) {
|
||||||
|
scanLines, err := fileToScanner(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(Accounting)
|
||||||
|
|
||||||
|
for scanLines.Scan() {
|
||||||
|
if err := parse(out, scanLines.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanLines.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(into Accounting, line []byte) error {
|
||||||
|
if len(line) == 0 || bytes.Equal(line[0:2], comment) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := bytes.Index(line, []byte("="))
|
||||||
|
key := string(line[0:idx])
|
||||||
|
value := string(bytes.Trim(line[idx+1:len(line)], "\t\r\n"))
|
||||||
|
|
||||||
|
if _, ok := into[key]; ok {
|
||||||
|
return errors.New("Duplicate key: " + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
into[key] = value
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
127
internal/data/animated_object_definitions.go
Normal file
127
internal/data/animated_object_definitions.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
// "fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cell struct {
|
||||||
|
X int // FIXME: This is just a guess, but I think it's a 3D coordinate system
|
||||||
|
Y int
|
||||||
|
Z int
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnimatedObject struct {
|
||||||
|
CentreCell Cell
|
||||||
|
Direction int
|
||||||
|
Type int
|
||||||
|
AnimationID int
|
||||||
|
AnimationGroup int
|
||||||
|
AnimatedType int
|
||||||
|
AnimationIDs CompassPoints // 8 of these, down to MoveBits. Compass points?
|
||||||
|
InCells CompassPoints
|
||||||
|
VisualBits CompassPoints
|
||||||
|
MoveBits CompassPoints
|
||||||
|
Visibility int
|
||||||
|
Protection int
|
||||||
|
MinDelay int // in milliseconds
|
||||||
|
DelayRange int // in milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadAnimatedObjectDefinitions(filename string) ([]AnimatedObject, error) {
|
||||||
|
var out []AnimatedObject
|
||||||
|
|
||||||
|
scanLines, err := fileToScanner(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
obj, err := consumeAnimatedObjectDefinition(scanLines)
|
||||||
|
if err == io.EOF {
|
||||||
|
return out, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeAnimatedObjectDefinition(scanner *bufio.Scanner) (AnimatedObject, error) {
|
||||||
|
var out AnimatedObject
|
||||||
|
var err error
|
||||||
|
|
||||||
|
out.CentreCell, err = consumeCell(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := consumeIntPtrs(
|
||||||
|
scanner,
|
||||||
|
&out.Direction, &out.Type, &out.AnimationID, &out.AnimationGroup, &out.AnimatedType,
|
||||||
|
); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8-int arrays
|
||||||
|
out.AnimationIDs, err = consumeCompassPoints(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.InCells, err = consumeCompassPoints(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.VisualBits, err = consumeCompassPoints(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.MoveBits, err = consumeCompassPoints(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := consumeIntPtrs(
|
||||||
|
scanner,
|
||||||
|
&out.Visibility, &out.Protection, &out.MinDelay, &out.DelayRange,
|
||||||
|
); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeCell(scanner *bufio.Scanner) (Cell, error) {
|
||||||
|
out := Cell{}
|
||||||
|
str, err := consumeString(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(str, " ")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return out, errors.New("Malformed cell definition")
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.X, err = strconv.Atoi(parts[0]); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Y, err = strconv.Atoi(parts[1]); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Z, err = strconv.Atoi(parts[2]); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
107
internal/data/data.go
Normal file
107
internal/data/data.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hashComment = []byte("#")
|
||||||
|
|
||||||
|
type CompassPoints struct {
|
||||||
|
N int
|
||||||
|
NE int
|
||||||
|
E int
|
||||||
|
SE int
|
||||||
|
S int
|
||||||
|
SW int
|
||||||
|
W int
|
||||||
|
NW int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Range struct {
|
||||||
|
Start int
|
||||||
|
End int
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileToScanner(filename string) (*bufio.Scanner, error) {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bufio.NewScanner(bytes.NewReader(data)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeString(scanner *bufio.Scanner) (string, error) {
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Bytes()
|
||||||
|
|
||||||
|
if len(line) == 0 || line[0] == hashComment[0] { // Most .dat files use # for comments
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
comment := bytes.Index(line, hashComment)
|
||||||
|
if comment > 0 {
|
||||||
|
line = line[0:comment]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bytes.TrimRight(line, "\r\n\t ")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := scanner.Err()
|
||||||
|
if err == nil {
|
||||||
|
return "", io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeInt(scanner *bufio.Scanner) (int, error) {
|
||||||
|
str, err := consumeString(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.Atoi(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeIntPtr(to *int, scanner *bufio.Scanner) error {
|
||||||
|
val, err := consumeInt(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*to = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeIntPtrs(scanner *bufio.Scanner, ptrs ...*int) error {
|
||||||
|
for _, ptr := range ptrs {
|
||||||
|
if err := consumeIntPtr(ptr, scanner); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeRange(scanner *bufio.Scanner) (Range, error) {
|
||||||
|
var out Range
|
||||||
|
|
||||||
|
err := consumeIntPtrs(scanner, &out.Start, &out.End)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeCompassPoints(scanner *bufio.Scanner) (CompassPoints, error) {
|
||||||
|
var out CompassPoints
|
||||||
|
|
||||||
|
err := consumeIntPtrs(
|
||||||
|
scanner,
|
||||||
|
&out.N, &out.NE, &out.E, &out.SE, &out.S, &out.SW, &out.W, &out.NW,
|
||||||
|
)
|
||||||
|
|
||||||
|
return out, err
|
||||||
|
}
|
339
internal/data/generic.go
Normal file
339
internal/data/generic.go
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
type ActionPointEnum int
|
||||||
|
type SettingEnum int
|
||||||
|
type TransitionFrameEnum int
|
||||||
|
type SquadTypeEnum int // just for the multiplayer choose units menu
|
||||||
|
type VehicleTypeEnum int // just for the multiplayer choose units screen
|
||||||
|
type OptionEnum int // differs from settings in that these are defaults changeable from the UI
|
||||||
|
type SpellEnum int
|
||||||
|
type AIPriorityEnum int // FIXME: no idea what this is for
|
||||||
|
type RngRangeEnum int
|
||||||
|
|
||||||
|
// VEHICLE_WALK_TRANSITION_FRAME entries
|
||||||
|
|
||||||
|
const (
|
||||||
|
APCastSpell ActionPointEnum = 0
|
||||||
|
APWalk = 1
|
||||||
|
APRun = 2
|
||||||
|
APTurn = 3
|
||||||
|
APCrouch = 4
|
||||||
|
APStand = 5
|
||||||
|
APOpenDoor = 6
|
||||||
|
APDropItem = 7
|
||||||
|
APPickupItem = 8
|
||||||
|
APThrowItem = 9
|
||||||
|
APUseMedikit = 10
|
||||||
|
APUseScanner = 11
|
||||||
|
APJump = 12
|
||||||
|
APThrowGrenade = 13
|
||||||
|
APHalfTurn = 14
|
||||||
|
APToClearJammedWeapon = 15
|
||||||
|
APHandToHandRound = 16
|
||||||
|
APToUseSwitch = 17
|
||||||
|
APToUseKey = 18
|
||||||
|
APToBoardDisembark = 19
|
||||||
|
APWhenOnFire = 20
|
||||||
|
APWhenBroken = 21
|
||||||
|
APMax = 22
|
||||||
|
|
||||||
|
SettingOccasionalSoundPercent SettingEnum = 0
|
||||||
|
SettingOccasionalMovementSound = 1
|
||||||
|
SettingFullJumpFuel = 2
|
||||||
|
SettingMaxJumpDistance = 3
|
||||||
|
SettingFlyFramesPerCell = 4
|
||||||
|
SettingPowerArmorRating = 5
|
||||||
|
SettingTerminatorArmorRating = 6
|
||||||
|
SettingBloodThirsterArmorRating = 7
|
||||||
|
SettingDemonicArmorRating = 8
|
||||||
|
SettingPrimitiveArmorRating = 9
|
||||||
|
SettingHorrorArmorRating = 10
|
||||||
|
SettingArmorDice = 11
|
||||||
|
SettingJumpPixelsPerFrame = 12
|
||||||
|
SettingCharacterAnimationDelay = 13
|
||||||
|
SettingVehicleAnimationDelay = 14
|
||||||
|
SettingLoopingEffectsDelay = 15
|
||||||
|
SettingFireAnimationDelay = 16
|
||||||
|
SettingCharacterFireTransparency = 17
|
||||||
|
SettingMissileDrawOffset = 18 // pixels per missing piece
|
||||||
|
SettingWeaponJamPercent = 19 // to two decimal places
|
||||||
|
SettingScanFrequency = 20
|
||||||
|
SettingCoverSearchRadius = 21
|
||||||
|
SettingDialogAutoCloseTime = 22
|
||||||
|
SettingPointBlankBallisticSkillBonus = 23
|
||||||
|
SettingPointBlankRange = 24
|
||||||
|
SettingMinimumOverwatchShotPercent = 25
|
||||||
|
SettingMultipleSekectDragTolerance = 26
|
||||||
|
SettingStandingGuardPostRadius = 27
|
||||||
|
SettingMaxDemonFlyDistance = 28
|
||||||
|
SettingSpellLoopingVolume = 29 // percentage, 0 decimal places
|
||||||
|
SettingPercentOfChaosInSecondaryQuadrant = 30
|
||||||
|
SettingNumberOfRandomCrates = 31
|
||||||
|
SettingMax = 32
|
||||||
|
|
||||||
|
TFDeletedVehicle TransitionFrameEnum = 0
|
||||||
|
TFUltraRhino = 1
|
||||||
|
TFUltraPredator = 2
|
||||||
|
TFUltraLandSpeeder = 3
|
||||||
|
TFUltraDreadnought = 4
|
||||||
|
TFChaosRhino = 5
|
||||||
|
TFChaosPredator = 6
|
||||||
|
TFChaosDreadnought = 7
|
||||||
|
TFMax = 8
|
||||||
|
|
||||||
|
SquadTerminator SquadTypeEnum = 0
|
||||||
|
SquadTactical = 1
|
||||||
|
SquadAssault = 2
|
||||||
|
SquadDevastator = 3
|
||||||
|
SquadChaos = 4
|
||||||
|
SquadChaosKhorne = 5
|
||||||
|
SquadChaosTerminator = 6
|
||||||
|
SquadChaosCultise = 7
|
||||||
|
SquadLibrarian = 8
|
||||||
|
SquadTechmarine = 9
|
||||||
|
SquadApothecary = 10
|
||||||
|
SquadChaplain = 11
|
||||||
|
SquadCaptain = 12
|
||||||
|
SquadChaosLord = 13
|
||||||
|
SquadChaosChaplain = 14
|
||||||
|
SquadChaosSorcerer = 15
|
||||||
|
SauadChaosBlodThirster = 16
|
||||||
|
SquadChaosBlootLetter = 17
|
||||||
|
SquadChaosFleshHound = 18
|
||||||
|
SquadChaosLordOfChange = 19
|
||||||
|
SquadChaosFlamer = 20
|
||||||
|
SquadChaosPinkHorror = 21
|
||||||
|
SquadChaosBlueHorror = 22
|
||||||
|
SquadMax = 23
|
||||||
|
|
||||||
|
VehicleUltraRhino VehicleTypeEnum = 0
|
||||||
|
VehicleUltraPredator = 1
|
||||||
|
VehicleUltraLandSpeeder = 2
|
||||||
|
VehicleUltraDreadnought = 3
|
||||||
|
VehicleChaosRhino = 4
|
||||||
|
VehicleChaosPredator = 5
|
||||||
|
VehicleChaosDreadnought = 6
|
||||||
|
VehicleMax = 7
|
||||||
|
|
||||||
|
OptionMovies OptionEnum = 0
|
||||||
|
OptionMusic = 1
|
||||||
|
OptionCombatVoices = 2
|
||||||
|
OptionGrid = 3
|
||||||
|
OptionShowPaths = 4
|
||||||
|
OptionPointSave = 5
|
||||||
|
OptionAutoCutLevel = 6
|
||||||
|
OptionShowUnitAnimations = 7
|
||||||
|
OptionCombatResolution = 8
|
||||||
|
OptionMusicVolume = 9 // percentage, 0df
|
||||||
|
OptionSoundEffectsVolume = 10 // percentage, 0df
|
||||||
|
OptionUnitAnimationSpeed = 11 // percentage, 0df, see CHARCTER ANIMATION DELAY / VEHICLE ANIMATION DELAY for worst case value
|
||||||
|
OptionEffectAnimationSpeed = 12 // percentage, 0d, see LOOPING_EFFECTS_DELAY for worst case value
|
||||||
|
OptionMax = 13
|
||||||
|
|
||||||
|
SpellHellfire SpellEnum = 0
|
||||||
|
SpellLightningArc = 1
|
||||||
|
SpellGate = 2
|
||||||
|
SpellAssail = 3
|
||||||
|
SpellDisplacement = 4
|
||||||
|
SpellMachineCurse = 5
|
||||||
|
SpellScan = 6
|
||||||
|
SpellQuickening = 7
|
||||||
|
SpellStrengthOfMind = 8
|
||||||
|
SpellSmite = 9
|
||||||
|
SpellIronArm = 10
|
||||||
|
SpellSalamandar = 11
|
||||||
|
SpellTeleport = 12
|
||||||
|
SpellPsychicShield = 13
|
||||||
|
SpellDestroyDemon = 14
|
||||||
|
SpellHolocaust = 15
|
||||||
|
SpellScourge = 16
|
||||||
|
SpellPurge = 17
|
||||||
|
SpellAuraOfFire = 18
|
||||||
|
SpellVortex = 19
|
||||||
|
SpellStormOfWrath = 20
|
||||||
|
SpellAuraOfFortitude = 21
|
||||||
|
SpellPinkFireOfTzeentch = 22
|
||||||
|
SpellBoltOfChange = 23
|
||||||
|
SpellTzeentchFireStore = 24
|
||||||
|
SpellMax = 25
|
||||||
|
|
||||||
|
AIPrioritySpecialCharacter = 0
|
||||||
|
AIPriorityVehicle = 1
|
||||||
|
AIPriorityTerminator = 2
|
||||||
|
AIPriorityHeavyWeapon = 3
|
||||||
|
AIPriorityRegular = 4
|
||||||
|
AIPriorityCultist = 5
|
||||||
|
AIPriorityMax = 6
|
||||||
|
|
||||||
|
RngRangeVehicleHillHeight RngRangeEnum = 0
|
||||||
|
RngRangeVehicleHillWidth = 1
|
||||||
|
RngRangeVehicleHillDensity = 2
|
||||||
|
RngRangeVehicleLargeObjectDensity = 3
|
||||||
|
RngRangeVehicleSmallObjectDensity = 4
|
||||||
|
RngRangeNoVehicleHillHeight = 5
|
||||||
|
RngRangeNoVehicleHillWidth = 6
|
||||||
|
RngRangeNoVehicleHillDensity = 7
|
||||||
|
RngRangeNoVehicleLargeObjectDensity = 8
|
||||||
|
RngRangeNoVehicleSmallObjectDensity = 9
|
||||||
|
RngRangeMax = 10
|
||||||
|
|
||||||
|
// FIXME: is this really a hardcoded value?
|
||||||
|
NumCampaignScenarios = 15
|
||||||
|
)
|
||||||
|
|
||||||
|
type Generic struct {
|
||||||
|
ActionPoints map[ActionPointEnum]int
|
||||||
|
Settings map[SettingEnum]int
|
||||||
|
VehicleWalkTransitionFrames map[TransitionFrameEnum]CompassPoints
|
||||||
|
MPChooseUnitSquadLimits map[SquadTypeEnum]int
|
||||||
|
|
||||||
|
// I'm assuming this is to do with MP becauses squads are. I could be wrong.
|
||||||
|
MPChooseVehicleLimits map[VehicleTypeEnum]int
|
||||||
|
|
||||||
|
Options map[OptionEnum]int
|
||||||
|
|
||||||
|
CampaignMaps []string
|
||||||
|
CampaignEndMissionWavs []string
|
||||||
|
|
||||||
|
AICoherentForce int // Range that defnines a coherent for for the AI. No idea
|
||||||
|
AISpellDeltaPriorityWeights map[SpellEnum]int // AI delta priority weights for each spell
|
||||||
|
AIPriority map[AIPriorityEnum]int
|
||||||
|
AICommandInfiniteLoopCounterWatermark int // Messing with this can cripple the API...
|
||||||
|
|
||||||
|
RngRanges map[RngRangeEnum]Range
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consume these values from the file
|
||||||
|
func LoadGeneric(filename string) (*Generic, error) {
|
||||||
|
scanLines, err := fileToScanner(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &Generic{
|
||||||
|
ActionPoints: make(map[ActionPointEnum]int),
|
||||||
|
Settings: make(map[SettingEnum]int),
|
||||||
|
VehicleWalkTransitionFrames: make(map[TransitionFrameEnum]CompassPoints),
|
||||||
|
MPChooseUnitSquadLimits: make(map[SquadTypeEnum]int),
|
||||||
|
MPChooseVehicleLimits: make(map[VehicleTypeEnum]int),
|
||||||
|
Options: make(map[OptionEnum]int),
|
||||||
|
AISpellDeltaPriorityWeights: make(map[SpellEnum]int),
|
||||||
|
AIPriority: make(map[AIPriorityEnum]int),
|
||||||
|
RngRanges: make(map[RngRangeEnum]Range),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Various action point values, first to last
|
||||||
|
for ap := ActionPointEnum(0); ap < APMax; ap++ {
|
||||||
|
val, err := consumeInt(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.ActionPoints[ap] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other miscellaneous data fields, first to last
|
||||||
|
for setting := SettingEnum(0); setting < SettingMax; setting++ {
|
||||||
|
val, err := consumeInt(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Settings[setting] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vehicle walk transition frames. Whatever they are.
|
||||||
|
for tf := TransitionFrameEnum(0); tf < TFMax; tf++ {
|
||||||
|
val, err := consumeCompassPoints(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.VehicleWalkTransitionFrames[tf] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
for st := SquadTypeEnum(0); st < SquadMax; st++ {
|
||||||
|
val, err := consumeInt(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.MPChooseUnitSquadLimits[st] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
for vt := VehicleTypeEnum(0); vt < VehicleMax; vt++ {
|
||||||
|
val, err := consumeInt(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.MPChooseVehicleLimits[vt] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
for op := OptionEnum(0); op < OptionMax; op++ {
|
||||||
|
val, err := consumeInt(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Options[op] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < NumCampaignScenarios; i++ {
|
||||||
|
val, err := consumeString(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.CampaignMaps = append(out.CampaignMaps, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < NumCampaignScenarios; i++ {
|
||||||
|
val, err := consumeString(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: "none" is a special-case value here
|
||||||
|
out.CampaignEndMissionWavs = append(out.CampaignEndMissionWavs, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.AICoherentForce, err = consumeInt(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for sp := SpellEnum(0); sp < SpellMax; sp++ {
|
||||||
|
val, err := consumeInt(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.AISpellDeltaPriorityWeights[sp] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
for ai := AIPriorityEnum(0); ai < AIPriorityMax; ai++ {
|
||||||
|
val, err := consumeInt(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.AIPriority[ai] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
out.AICommandInfiniteLoopCounterWatermark, err = consumeInt(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for rg := RngRangeEnum(0); rg < RngRangeMax; rg++ {
|
||||||
|
val, err := consumeRange(scanLines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.RngRanges[rg] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
267
internal/data/object.go
Normal file
267
internal/data/object.go
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// FIXME: My poor understanding of the .obj format prevents me from
|
||||||
|
// successfully loading these files
|
||||||
|
objBlacklist = []string{
|
||||||
|
/* Lots of unexpected magic values, disable the check for now
|
||||||
|
"15_rocks.obj", // Unexpected magic value: 19726564 (expected 21102801)
|
||||||
|
"2_cath.obj", // Unexpected magic value: 21364973 (expected 21102801)
|
||||||
|
"2nd_flor.obj", // Unexpected magic value: 22151377 (expected 21102801)
|
||||||
|
"3_cath.obj", // Unexpected magic value: 21102809 (expected 21102801)
|
||||||
|
"4_cath.obj", // Unexpected magic value: 21496017 (expected 21102801)
|
||||||
|
"BODIES.obj", // Unexpected magic value: 21627103 (expected 21102801)
|
||||||
|
"BRDG_TIL.OBJ", // Unexpected magic value: 17957074 (expected 21102801)
|
||||||
|
"Cheveron.obj", // Unexpected magic value: 10879118 (expected 21102801)
|
||||||
|
|
||||||
|
"Heavy_Plasma_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Heavy_Plasma_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Heavy_Plasma_Pain_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Heavy_Plasma_Pain_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
|
||||||
|
"Ht_drt.obj", // Unexpected magic value: 22806737 (expected 21102801)
|
||||||
|
"Ht_grs.obj", // Unexpected magic value: 22806737 (expected 21102801)
|
||||||
|
|
||||||
|
"IVY02.OBJ", // Unexpected magic value: 18481399 (expected 21102801)
|
||||||
|
"J_top2.obj", // Unexpected magic value: 22347993 (expected 21102801)
|
||||||
|
|
||||||
|
"Lascannon_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Lascannon_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Lascannon_Pain_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Lascannon_Pain_Mask.obj", // Unexpected magic value: 14811136 (expected 21102801)
|
||||||
|
|
||||||
|
"Man_Shadow.obj", // Unexpected magic value: 22479091 (expected 21102801)
|
||||||
|
|
||||||
|
"Melta_Effect.obj", // Unexpected magic value: 14745777 (expected 21102801)
|
||||||
|
"Melta_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Melta_Pain_Effect.obj", // Unexpected magic value: 14745777 (expected 21102801)
|
||||||
|
"Melta_Pain_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
|
||||||
|
"Multi_Melta_Effect.obj",
|
||||||
|
"Multi_Melta_Mask.obj",
|
||||||
|
"Multi_Melta_Pain_Effect.obj",
|
||||||
|
"Multi_Melta_Pain_Mask.obj",
|
||||||
|
|
||||||
|
"Plasma_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Plasma_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Plasma_Pain_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
"Plasma_Pain_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
|
||||||
|
|
||||||
|
"TZEENTCH.OBJ", // Unexpected magic value: 18088209 (expected 21102801)
|
||||||
|
|
||||||
|
"altar.obj", // Unexpected magic value: 18219222 (expected 21102801)
|
||||||
|
*/
|
||||||
|
|
||||||
|
"j_tree2.obj", // ObjectHeader is completely empty
|
||||||
|
"inven.obj", // Main header padding contains unknown values: [134744072 134744072 134744072]
|
||||||
|
}
|
||||||
|
objFrameMagic = uint32(0x014200D1)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sort.Strings(objBlacklist)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FrameHeader struct {
|
||||||
|
Magic uint32
|
||||||
|
Width uint16 // FIXME: I'm not certain this is what these are. If they are, they may be the wrong way around
|
||||||
|
Height uint16
|
||||||
|
Padding1 uint32 // I don't think this is used. Could be wrong.
|
||||||
|
PixelSize uint32 // Size of PixelData, excluding this frame header
|
||||||
|
Padding2 uint64 // I don't think this is used either. Could be wrong.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FrameHeader) Check(expectedSize uint32) error {
|
||||||
|
// There seem to be different frame types, keyed by the magic value?
|
||||||
|
// if f.Magic != objFrameMagic {
|
||||||
|
// return fmt.Errorf("Unexpected magic value: %d (expected %d)", f.Magic, objFrameMagic)
|
||||||
|
// }
|
||||||
|
|
||||||
|
if f.Padding1 != 0 || f.Padding2 != 0 {
|
||||||
|
return fmt.Errorf("Frame header padding contains unknown values: %d %d", f.Padding1, f.Padding2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove 24 bytes from passed-in size to account for the header
|
||||||
|
if f.PixelSize != expectedSize-24 {
|
||||||
|
return fmt.Errorf("Advertised pixel size: %d differs from expected: %v", f.PixelSize, expectedSize-24)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Frame struct {
|
||||||
|
FrameHeader
|
||||||
|
|
||||||
|
PixelData []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type frameInfoHeader struct {
|
||||||
|
Offset uint32 // Offset of the frame relative to the frame data segment
|
||||||
|
Size uint32 // Size of the frame in bytes, including the header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f frameInfoHeader) Check() error {
|
||||||
|
if f.Size < 24 {
|
||||||
|
return fmt.Errorf("Unexpected frame size: %d (expected >= 24)", f.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectHeader totals 32 bytes on disk
|
||||||
|
type ObjectHeader struct {
|
||||||
|
NumFrames uint32 // How many frames does this object have?
|
||||||
|
MainHeaderSize uint32 // Number of bytes taken by this header. Should always be `32`
|
||||||
|
FrameInfoSize uint32 // Number of bytes taken up by the next block. 8 * NumFrames
|
||||||
|
FrameDataOffset uint32 // The starting point of the frame data
|
||||||
|
FrameDataSize uint32 // frame data should take up this many bytes
|
||||||
|
|
||||||
|
Padding [3]uint32 // Unused, as far as I can see
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h ObjectHeader) ExpectedFrameInfoSize() uint32 {
|
||||||
|
return h.NumFrames * 8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h ObjectHeader) Check() error {
|
||||||
|
if h.MainHeaderSize != 32 {
|
||||||
|
return fmt.Errorf("Unexpected main header size: %d (expected 32)", h.MainHeaderSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ExpectedFrameInfoSize() != h.FrameInfoSize {
|
||||||
|
return fmt.Errorf("Unexpected frame info size: %d (expected %d)", h.FrameInfoSize, h.ExpectedFrameInfoSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.Padding[0] != 0 || h.Padding[1] != 0 || h.Padding[2] != 0 {
|
||||||
|
return fmt.Errorf("Main header padding contains unknown values: %+v", h.Padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Object struct {
|
||||||
|
ObjectHeader
|
||||||
|
|
||||||
|
Filename string
|
||||||
|
Frames []*Frame
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadObject(filename string) (*Object, error) {
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
out := &Object{Filename: filename}
|
||||||
|
if err := binary.Read(f, binary.LittleEndian, &out.ObjectHeader); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := out.ObjectHeader.Check(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now load all frames into memory
|
||||||
|
|
||||||
|
framesInfo := make([]frameInfoHeader, out.NumFrames)
|
||||||
|
if err := binary.Read(f, binary.LittleEndian, &framesInfo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := f.Seek(int64(out.FrameDataOffset), io.SeekStart); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this might - *might* - load interstitial data we don't really
|
||||||
|
// need, so wasting memory.
|
||||||
|
data, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewReader(data)
|
||||||
|
|
||||||
|
for i, frameInfo := range framesInfo {
|
||||||
|
fmt.Println("Loading frame %s:%d", filename, i)
|
||||||
|
if err := frameInfo.Check(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := buf.Seek(int64(frameInfo.Offset), io.SeekStart); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
frame := &Frame{}
|
||||||
|
|
||||||
|
if err := binary.Read(buf, binary.LittleEndian, &frame.FrameHeader); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := frame.Check(frameInfo.Size); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's safe to assume that a `bytes.Reader` will always satisfy the
|
||||||
|
// requested read size.
|
||||||
|
frame.PixelData = make([]byte, frame.PixelSize)
|
||||||
|
if _, err := buf.Read(frame.PixelData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Frames = append(out.Frames, frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readObjFrame(f io.Reader, obj *Object) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadObjects(dir string) (map[string]*Object, error) {
|
||||||
|
fis, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(map[string]*Object, len(fis))
|
||||||
|
|
||||||
|
for _, fi := range fis {
|
||||||
|
filename := filepath.Join(dir, fi.Name())
|
||||||
|
basename := filepath.Base(filename)
|
||||||
|
extname := filepath.Ext(filename)
|
||||||
|
|
||||||
|
// Don't try to load non-.obj files
|
||||||
|
if !strings.EqualFold(extname, ".obj") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
i := sort.SearchStrings(objBlacklist, basename)
|
||||||
|
if i >= len(objBlacklist) || objBlacklist[i] == basename {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := LoadObject(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %v", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out[basename] = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
Reference in New Issue
Block a user