Advertisement
BehemothProgrammer

TurokEX Map Format Specifications

Jul 3rd, 2019
591
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.87 KB | None | 0 0
  1. //
  2. // Turok EX Map Format Specifications
  3. //
  4. // By BehemothProgrammer
  5. // Revised: 07/03/19
  6. //
  7.  
  8. //-----------------------------------------------------------------------------
  9. //
  10. // What is this document for?
  11. //
  12. //-----------------------------------------------------------------------------
  13.  
  14. This technical document explains the Turok Dinosaur Hunter Remastered .map file
  15. format and is targeted towards users with programming experience.
  16.  
  17. //-----------------------------------------------------------------------------
  18. //
  19. // Map Format
  20. //
  21. //-----------------------------------------------------------------------------
  22.  
  23. Map files (.MAP) are binary files that are read with offset addresses to data structures.
  24. It uses a kexArchive format which is also based directly on Iguana's archive format for their file structure in Turok 1 and 2.
  25. Having a good idea of how the archive format works will allow you to be able to parse the file,
  26. including data from the Turok 1 and 2 roms which applies to CARTDATA.DAT in Turok 1. And for N64 roms,
  27. same format except that the dwords are in big endian format.
  28.  
  29. There are two types of archive formats: indexed and data sets. I'll start off with indexed:
  30.  
  31. In an indexed archive the header always begins with 4 bytes(int) which is the number of offsets. Then for N offsets you got 4 bytes(int).
  32. And there is 1 offset added to the end of the offsets that points to the end of the archive.
  33. Each offset is relative to the beginning of the archives header.
  34. These offsets can lead to 1 of 3 things; another indexed archive, a data set archive, or arbitrary data.
  35. If you want to determine the size of the data it's offsetting to, you can subtract the next offset with the current one.
  36. The archives have padded the data in rounds of 8/16 bytes.
  37.  
  38. Data sets are similar, except that they begin with 8 bytes. first 4 bytes(int) being the stride size, and the second 4 bytes(int) is the count.
  39. Following that is a glob of bytes which is stride size * count (the data structure)
  40.  
  41. //-----------------------------------------------------------------------------
  42. //
  43. // Step by step walkthrough of reading a map
  44. //
  45. //-----------------------------------------------------------------------------
  46.  
  47. So let's start by opening a .map file in a hex editor of your choice.
  48. The first thing you'll see is a indexed archive so that means the very first 4 bytes(int) in the file is the root archive headers number of offsets.
  49.  
  50. You'll see that the value should be 7, for 7 offsets. (It'll say 8 if there's visibility tables for the map)
  51. Now let's read the first offset(the maps version number) for this root indexed archive(the next 4 bytes(int)).
  52. Go to that byte in your hex editor at 0 (start of header) + the first offset value.
  53. Which will take you to arbitrary data, 4 bytes(int), which we know is the maps version number and it's value should be 1.
  54.  
  55. Now let's go back to our root indexed archive and follow the second offset which will take us to another indexed archive which we already know has
  56. offsets related to the maps World Properties. This world indexed archive has 3 offsets.
  57.  
  58. The first offset in the world archive leads to arbitrary data: Sunlight Direction - 12 bytes (3 floats, xyz)
  59. The second offset in the world archive leads to arbitrary data: Sunlight Color - 16 bytes (4 floats, rgba)
  60. The third offset in the world archive leads to arbitrary data: Ambient Color - 16 bytes (4 floats, rgba)
  61.  
  62. Now let's go back to our root indexed archive and follow the third offset which will take us to a dataset archive which we know
  63. is just a string for the World Sky Material. Remember this is a dataset archive. We read the archive header;
  64. the first 4 bytes(int) being the size of each object in bytes, in this example is 29 (for: skies/skyMaterials/sky_brown and a 0 null byte at the end).
  65. the second 4 bytes(int) being the number of objects which is 1 (for 1 string).
  66. We then read the next 29 bytes as chars for our string of the World Sky Material (and you probably want to remove or not read the last 0 null character from the string).
  67.  
  68. Now let's go back to our root indexed archive and follow the fourth offset which will take us to a indexed archive (Collision) which has 3 offsets.
  69. The first offset leads to a dataset archive that is all the vertices in the map used for our sectors.
  70. Again, read it's header like you did with the World Sky Material string.
  71. the first 4 bytes(int) is the size in bytes that each vertice uses (which is 16 bytes)
  72. the second 4 bytes(int) being the number of vertices in the map.
  73.  
  74. name bytes notes
  75. ---------------------------------------------------------
  76. Position 12 (3 floats) xyz
  77. Height 4 (float) The ceiling height
  78.  
  79. Let's follow the second offset in the Collision indexed archive which takes us to a dataset archive (SectorSets).
  80. Read the header to get the size(which is 64) and count.
  81.  
  82. name bytes notes
  83. ---------------------------------------------------------
  84. flags 4 (uint)
  85. fog color 4 (4 bytes) rgba
  86. water color 4 (4 bytes) rgba
  87. fog zfar 4 (float)
  88. water zfar 4 (float)
  89. water height 4 (float)
  90. sky height 4 (float)
  91. sky speed 4 (float)
  92. blend length 4 (float)
  93. args 12 (6 int16)
  94. floor impact id 1 (byte)
  95. wall impact id 1 (byte)
  96. ambience 1 (byte)
  97. automap color ID 1 (byte)
  98. music 1 (byte)
  99. cull bits 1 (byte) Kex editor assigns a default value of 255
  100. unused 2 (2 bytes) unused bytes. both are always 0
  101.  
  102. Let's follow the third offset in the Collision indexed archive which takes us to a dataset archive (Sectors).
  103. Read the header to get the size(which is 18) and count. IMPORTANT NOTE: The kex editor saves sectors of size 18 bytes, however the original maps only stored 16 bytes of data that did not include draw order, which was added in TurokEX. So the size might be 16 or 18 depending on if it's from the original turok map editor or the kex editor.
  104.  
  105. name bytes notes
  106. ---------------------------------------------------------
  107. SectorSet Index 2 (uint16)
  108. flags 2 (uint16) This is exactly the same as the first 16 bits in the SectorSet flags
  109. indices 6 (3 uint16) The 3 vertice indexes this sector uses
  110. edge links 6 (3 uint16) The 3 edges of this sector. The index to the other sector that shares an edge with the current sector. If it's 0xffff then nothing is linked and the edge is a solid wall.
  111. draw order 2 (int16) Prioritizes automap colors (in kex editor saved maps only)
  112.  
  113. Now let's go back to our root indexed archive and follow the fifth offset which will take us to a indexed archive (Grid Bounds) which has 3 offsets.
  114. The first offset leads to a dataset archive that is the grid bounds size width and height. Read the header. Size and count should both be 2.
  115.  
  116. name bytes notes
  117. ---------------------------------------------------------
  118. width 2 (int16) number of grid bounds on the x axis
  119. height 2 (int16) number of grid bounds on the y axis
  120.  
  121. The second offset in the Grid Bounds indexed archive leads to a dataset archive that is all the grid bounds min x and y position.
  122. Read the header. Size(which is 8) and count (which should be width*height).
  123.  
  124. name bytes notes
  125. ---------------------------------------------------------
  126. min x 4 (float)
  127. min y 4 (float)
  128.  
  129. The third offset in the Grid Bounds indexed archive leads to a dataset archive that is all the grid bounds max x and y position.
  130. Read the header. Size(which is 8) and count (which should be width*height).
  131.  
  132. name bytes notes
  133. ---------------------------------------------------------
  134. max x 4 (float)
  135. max y 4 (float)
  136.  
  137. Now let's go back to our root indexed archive and follow the sixth offset which will take us to a indexed archive (Grid Sections).
  138. The number of offsets is the number of grid sections.
  139. Each offset leads to a indexed archive which is the static meshes that the grid section contains and it has 2 offsets.
  140. The static meshes archives first offset leads to a dataset archive that is all the static meshes properties in this grid section.
  141. Read the header. Size(which is 88) and count.
  142.  
  143. name bytes notes
  144. ---------------------------------------------------------
  145. origin 12 (3 floats) xyz
  146. scale 12 (3 floats) xyz
  147. rotation 16 (4 floats) quaternion xyzw
  148. bounding box min 12 (3 floats) xyz
  149. bounding box max 12 (3 floats) xyz
  150. texture index 4 (int)
  151. flags 4 (uint)
  152. sector index 4 (int)
  153. collision radius 4 (float)
  154. collision height 4 (float)
  155. cull bits 4 (int) cull bits was used for vis culling and its only used in Hub 5. It's just a arbitrary flag. If the flag matches the bits set for the sector you're viewing in, then they're culled out. cull bits were manually set by hand by the developers. Kex Editor always sets this to a value of -1
  156.  
  157. The second offset in the static meshes archive leads to a indexed archive (static meshes model files)
  158. The number of offsets is the number of static meshes in the grid section.
  159. Each offset leads to a dataset archive that is the string used for the static meshes model path.
  160.  
  161. Now let's go back to our root indexed archive and follow the seventh offset which will take us to a indexed archive (Actors) which has 3 offsets.
  162. The first offset leads to a dataset archive that is all the actors in the map properties. Read the header. Size(which is 140) and count.
  163.  
  164. name bytes notes
  165. ---------------------------------------------------------
  166. type 4 (int)
  167. origin 12 (3 floats) xyz
  168. scale 12 (3 floats) xyz
  169. yaw 4 (float) in rads
  170. sector index 4 (int)
  171. bounding box min 12 (3 floats) xyz
  172. bounding box max 12 (3 floats) xyz
  173. sight range 4 (float) squared
  174. sight fov 4 (float) in rads
  175. loud range 4 (float) squared
  176. quiet range 4 (float) squared
  177. attack range 4 (float) squared
  178. fly height 4 (float) squared
  179. collision width 4 (float)
  180. collision wall width 4 (float)
  181. collision height 4 (float)
  182. collision dead height 4 (float) also known as Step Height
  183. melee range 4 (float) squared
  184. leash radius 4 (float) squared
  185. trigger delay 4 (float)
  186. species mask 4 (int) unused in Turok EX. Kex editor sets this value to 255.
  187. Spawn Flags 1 4 (uint)
  188. Spawn Flags 2 4 (uint)
  189. Spawn Flags 3 2 (uint16)
  190. Health 2 (int16)
  191. TID 2 (int16)
  192. Target TID 2 (int16)
  193. Max Regenerations 2 (int16)
  194. Attack Chance 1 (byte)
  195. Trigger Anim 1 (byte)
  196. Variant 1 (sbyte)
  197. Texture 1 (sbyte)
  198. Params 1 1 (sbyte)
  199. Params 2 1 (sbyte)
  200.  
  201. The second offset in the actors archive leads to a indexed archive (actors model files)
  202. The number of offsets is the number of actors in the map.
  203. Each offset leads to a dataset archive that is the string used for the actors model path.
  204.  
  205. The third offset in the actors archive leads to a indexed archive (actors anim files)
  206. The number of offsets is the number of actors in the map.
  207. Each offset leads to a dataset archive that is the string used for the actors anim path.
  208.  
  209.  
  210. If there was only 7 offsets in the root indexed archive then you've read through the entire file! If you have an eighth offset then that will lead
  211. to the maps Visibility tables dataset archive which answers the question: which staticmeshes are visible from this sector.
  212. Size 328 (example. number of static meshes / 8 (for each bit) then ceil it plus padding)
  213. Count 4207 (example. This is number of sectors in the map)
  214.  
  215.  
  216. Before you start writing your code to read/write the map you pretty much have to have
  217. a class/system for handling indexed/data set archives in place before hand.
  218.  
  219. Working C# code I've made for reading and writing Turok 1 and 2 PC Remastered map files can be found in this Unity package.
  220. I highly suggest looking at T1MapMono.cs and Archive.cs if you're having trouble understanding.
  221. https://www.dropbox.com/s/0v95pis1skotk62/TurokEditors.unitypackage?dl=0
  222.  
  223. //-----------------------------------------------------------------------------
  224. //
  225. // Enums
  226. //
  227. //-----------------------------------------------------------------------------
  228.  
  229. enum ActorSpawnFlags1
  230. {
  231. Solid = (1 << 0),
  232. ProjectileAttack1 = (1 << 1),
  233. Leader = (1 << 2),
  234. SnapToFloor = (1 << 3),
  235. ExplosionDeath = (1 << 4),
  236. ClimbWalls = (1 << 5),
  237. ProjectileAttack2 = (1 << 6),
  238. NoRepeatExplosion = (1 << 7),
  239. DieOnExplosion = (1 << 8),
  240. Flocker = (1 << 9),
  241. SlowEnemy = (1 << 10),
  242. RandomResurrection = (1 << 11),
  243. RandomFeignDeath = (1 << 12),
  244. Kamikaze = (1 << 13),
  245. AvoidPlayers = (1 << 14),
  246. FloatInWaterOnDeath = (1 << 15),
  247. Teleport = (1 << 16),
  248. CastShadow = (1 << 17),
  249. TeleportWait = (1 << 18),
  250. UseStrongAttacks = (1 << 19),
  251. UseWeakAttacks = (1 << 20),
  252. Sniper = (1 << 21),
  253. MeltOnDeath = (1 << 22),
  254. AvoidWater = (1 << 23),
  255. Flying = (1 << 24),
  256. TeleportAvoidWater = (1 << 25),
  257. TeleportAvoidCliffs = (1 << 26),
  258. TriggerStuff = (1 << 27),
  259. CannotCauseAFight = (1 << 28),
  260. NoWallCollision = (1 << 29),
  261. ScreenShake = (1 << 30),
  262. RespawnAnimation = (1 << 31)
  263. }
  264.  
  265. enum ActorSpawnFlags2
  266. {
  267. DropItemMask1 = (1 << 0), //Explosive Shells
  268. DropItemMask2 = (1 << 1), //Grenade
  269. DropItemMask3 = (1 << 2), //Medium Health
  270. DropItemMask4 = (1 << 3), //Full Health
  271. DropItemMask5 = (1 << 4), //Ultra Health
  272. DropItemMask6 = (1 << 5), //Small Health
  273. DropItemMask7 = (1 << 6), //Large Health
  274. DropItemMask8 = (1 << 7), //Minigun ammo
  275. DropItemMask9 = (1 << 8), //Mortal Wound
  276. DropItemMask10 = (1 << 9), //Rockets
  277. DropItemMask11 = (1 << 10), //Shotgun Shells
  278. DropItemMask12 = (1 << 11), //Energy Cell
  279. DropItemMask13 = (1 << 12), //Large Energy Cell
  280. DropItemMask14 = (1 << 13), //Clip
  281. RemoveOnCompletion = (1 << 14),
  282. NoBlood = (1 << 15),
  283. HoldTriggerAnimation = (1 << 16),
  284. ProjectileAttack3 = (1 << 17),
  285. ProjectileAttack4 = (1 << 18),
  286. DropItemOnDamage = (1 << 19),
  287. NoAutomapDraw = (1 << 20),
  288. AlternateMoves = (1 << 21),
  289. AntiAliasingReduced = (1 << 22),
  290. AntiAliasingFull = (1 << 23),
  291. ProjectileAttack5 = (1 << 24),
  292. ProjectileAttack6 = (1 << 25),
  293. MortalWoundImpact = (1 << 26),
  294. StayInWater = (1 << 27),
  295. DeviceWarpDeath = (1 << 28),
  296. StoreWarpReturn = (1 << 29),
  297. ProjectileAttack7 = (1 << 30),
  298. ProjectileAttack8 = (1 << 31)
  299. }
  300.  
  301. enum ActorSpawnFlags3
  302. {
  303. ReturnWarp = (1 << 0),
  304. PlayTriggerAnimOnce = (1 << 1),
  305. RegenerateFromStart = (1 << 2),
  306. WalkInStraightLine = (1 << 3),
  307. KillOutsideOfView = (1 << 4),
  308. NoThinker = (1 << 5),
  309. AvoidPlayers2 = (1 << 6),
  310. NoViolentDeath = (1 << 7),
  311. ProjectileAttack9 = (1 << 8),
  312. ProjectileAttack10 = (1 << 9),
  313. MakeSpawnAnimVisible = (1 << 10),
  314. NoDrawOnCamera = (1 << 11)
  315. }
  316.  
  317. enum SpeciesMask //Unused. Here for completion.
  318. {
  319. Raptor = (1 << 0),
  320. Human1 = (1 << 1),
  321. SaberTiger = (1 << 2),
  322. Dimetrodon = (1 << 3),
  323. Triceratops = (1 << 4),
  324. Moschops = (1 << 5),
  325. Pteranodon = (1 << 6),
  326. Subterranean = (1 << 7),
  327. Leaper = (1 << 8),
  328. Alien = (1 << 9),
  329. Hulk = (1 << 10),
  330. Robot = (1 << 11),
  331. MantisBoss = (1 << 12)
  332. }
  333.  
  334. enum SectorSetFlags
  335. {
  336. Water = (1 << 0),
  337. Block = (1 << 1),
  338. Toggle = (1 << 2),
  339. Cliff = (1 << 3),
  340. Climb = (1 << 4),
  341. OneSided = (1 << 5),
  342. Ceiling = (1 << 6),
  343. Crawl = (1 << 7),
  344. EnterCrawl = (1 << 8),
  345. Hidden = (1 << 9),
  346. Entered = (1 << 10),
  347. Secret = (1 << 11),
  348. Restricted = (1 << 12),
  349. SlopeTest = (1 << 13),
  350. DeathPit = (1 << 14),
  351. Mapped = (1 << 15),
  352. Event = (1 << 16),
  353. Reapeatable = (1 << 17),
  354. Teleport = (1 << 18),
  355. Damage = (1 << 19),
  356. DrawSky = (1 << 20),
  357. TeleportAir = (1 << 21),
  358. Lava = (1 << 22),
  359. EventSound = (1 << 23),
  360. AntiGravity = (1 << 24),
  361. Ladder = (1 << 25),
  362. Checkpoint = (1 << 26),
  363. SaveGame = (1 << 27),
  364. WarpReturn = (1 << 28),
  365. ShallowWater = (1 << 29),
  366. DrawSun = (1 << 30),
  367. StoreWarpReturn = (1 << 31)
  368. }
  369.  
  370. enum StaticMeshFlags
  371. {
  372. Collide = (1 << 0),
  373. NoPolyCollision = (1 << 1),
  374. NoDrawCamera = (1 << 2)
  375. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement