TR2IO.C


He never admited but all we suspect that Yury Zivago and the Rosetta Stone ahutor are the same person, after some weeks that the rosseta stone document was released this guy came with a program called TRueview, a program that render the whole level in similar way used in game, however you need a fast machine with a fast videocard to be able to run the program smootly.

The Whole executable y source code can be found somwhere at the Epp2 web site, i am posting here just the module where the TR file is opened. This is in "C" code but it can be useful to learn about the TR file format and implement your own reader in the language programming that you are using, you will found in this source code a lot a usefull info about the TR game. Enjoy!.

Turbo Pascal.



 
 

/*
 * tr2io.c   By Yuri Zivago.
 *
 * Tomb Raider II input/output routines for TRueView
 *
 *** CURRENTLY LACKING:
 ***     Better error control (specifically, any malloc() or fread() error
 ***                           just unceremoniously exits the program)
 ***     Correct handling for unknowns (obviously, I can't correctly
 ***                           handle what I don't understand)
 */
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <memory.h>
#include <string.h>

#include "config.h"

#ifdef INCLUDE_TR4_SUPPORT
// ZLIB #defines and #include for TR4 compression support
#define _WINDOWS 1
#define ZLIB_DLL 1
#include "zlib.h"
#endif // INCLUDE_TR4_SUPPORT

#include "tr2port.h"
#include "tr2.h"

/*
 * definitions, declarations, prototypes, etc.
 */
// prototype of routine to extract the mesh structures from the raw mesh data
static void ioExtractMeshes(bitu8     *MeshData,
                            bitu32    NumMeshPointers,
                            bitu32    *MeshPointers,
                            TR2_Level *Level);
// prototype of hex dump routine
void HexDump(unsigned char *buffer, int len, int offset, char *prefix, FILE *fp);

/***
 *** Code and Macros to handle Big Endian and Little Endian systems
 ***
 *** Users with big-endian CPUs (e.g. Mac) need to
 *** #define BIG_ENDIAN_CPU before this point (see TR2PORT.H).
 ***/

#ifdef BIG_ENDIAN_CPU   // don't include this code if we're on an Intel CPU

/*
 * Swap16(bitu16 *value)
 *
 * Inverts the bytes of a 16-bit value (to handle big-endian CPUs)
 */
static void Swap16(bitu16 *value)
{
   union {
      bitu16 u16;
      bitu8  u8[2];
      } swap;
   bitu8 ch;

   swap.u16 = *value;
   ch = swap.u8[0];
   swap.u8[0] = swap.u8[1];
   swap.u8[1] = ch;
   *value = swap.u16;
}

/*
 * Swap32(bitu32 *value)
 *
 * Inverts the bytes of a 32-bit value (to handle big-endian CPUs)
 */
static void Swap32(bitu32 *value)
{
   union {
      bitu32 u32;
      bitu8  u8[4];
      } swap;
   bitu8 ch;

   swap.u32 = *value;
   ch = swap.u8[0];
   swap.u8[0] = swap.u8[3];
   swap.u8[3] = ch;
   ch = swap.u8[1];
   swap.u8[1] = swap.u8[2];
   swap.u8[2] = ch;
   *value = swap.u32;
}

/*
 * Generic byte swapper (calls Swap16 or Swap32 based on <size>)
 */
static void Swap16_32(bitu32 *value, int size)
{
   if (size == sizeof(bitu16))
      Swap16((bitu16 *)value);
   else // size == sizeof(bitu32)
      Swap32(value);
}

#endif   // BIG_ENDIAN_CPU

/*
 * Macros for handling endian-ness.
 * Note that if we're on a LITTLE_ENDIAN_CPU (e.g. Intel), this expands
 * to nothing (thus introducing no overhead).
 * Mac users need to #define BIG_ENDIAN_CPU so that all integral values
 * longer than 8 bits are handled correctly.
 */
#ifdef   BIG_ENDIAN_CPU
#define  CONVERT_ENDIAN(x, size) Swap16_32((bitu32 *)(x), (size))
#else    // LITTLE_ENDIAN_CPU (e.g. Intel) (default - don't do anything)
#define  CONVERT_ENDIAN(x, size)
#endif   // BIG_ENDIAN_CPU

/*
 * TR2alloc()
 *
 * Wrapper for malloc() - if malloc() returns NULL, we can handle
 * it here (all I do right now is exit).
 */
void *TR2alloc(int size)
{
   void *ReturnValue;

   if (size == 0)
      return(NULL);
//
// I've been told that MacOS has problems here, and that doubling
// the allocation size (e.g. ReturnValue = malloc(size * 2)) "fixes"
// the problem (or at least masks it well)...
//
#ifdef MACINTOSHPC
   size *= 2;
#endif
   if ((ReturnValue = malloc(size)) == NULL) {
      fprintf(stderr, "malloc(%d) failed, exiting...\n", size);
      size = 1 / (int)ReturnValue;  // do a divide-by-zero so we can get into the debugger here
      exit(1);
      }
   memset(ReturnValue, 0x00, size); // initialise it to NULL (better safe than sorry)
   return(ReturnValue);
}

/*
 * TR2_Version_type TR2getVersion(bitu32 version)
 *
 * Returns TombRaider_1..TombRaider_3 or TombRaider_VersionUnknown based on <version>
 */
TR2_Version_type TR2getVersion(bitu32 version)
{
   int i;
   static struct {
      bitu32 LongVersion;
      TR2_Version_type Version;
      } TR2_Versions[] = {
         { 0x00000020, TombRaider_1 },
         { 0x0000002d, TombRaider_2 },
         { 0xff080038, TombRaider_3 },
         { 0xff180038, TombRaider_3 },
         { 0xfffffff0, TombRaider_4 }, // bogus
         { 0x00345254, TombRaider_4 }, // "TR4\0"
         { 0x00000000, TombRaider_UnknownVersion }
      };

   for (i = 0; TR2_Versions[i].LongVersion != 0; ++i) {
      if (TR2_Versions[i].LongVersion == version)
         break;
      }
   return(TR2_Versions[i].Version);
}

static bitu8  *TR4_LevelData;
static bitu32 TR4_LevelDataOffset = 0;
static bitu32 TR4_LevelDataSize;
static TR2_Version_type LevelVersion = TombRaider_2;

/*
 * TR2fread()
 *
 * Wrapper for fread() - if fread() fails to return the number of
 * bytes requested, print a message and exit.
 */
static int TR2fread(void *buffer, size_t size, size_t count, FILE *fp)
{
   int NumRead;

   if (LevelVersion == TombRaider_4) {
      NumRead = count;
      NumRead *= size;
      if ((TR4_LevelDataOffset + NumRead) <= TR4_LevelDataSize) {
         memcpy(buffer, &TR4_LevelData[TR4_LevelDataOffset], NumRead);
         TR4_LevelDataOffset += NumRead;
         return(count);
         }
      else {
         fprintf(stderr, "(TR4) fread(buffer, %d, %d, fp) returned %d\n", size, count, NumRead);
         exit(2);
         }
      }
   NumRead = fread(buffer, size, count, fp);
   if ((size_t)NumRead != count) {
      fprintf(stderr, "fread(buffer, %d, %d, fp) returned %d\n", size, count, NumRead);
      exit(2);
      }
   return(NumRead);
}

/*
 * MapTR1ItemToTR2Item()
 *
 * Does what its name implies - maps TR1 ItemIDs to TR2 ItemIDs
 */
static int MapTR1ItemToTR2Item(int ItemID)
{
   int ReturnValue;
   static int TR1toTR2map[300] = {
      0,    //   0:0 Lara
      1,    //   1:1 Lara pistol animation
      3,    //   2:3 Lara shotgun animation
      4,    //   3:4 Lara magnum animation
      5,    //   4:5 Lara Uzi animation
      0,    //   5:? Lara / Lara's home appearance / Lara wounded / Lara turned to gold
      0,    //   6:? Lara's evil twin mutant
      36,   //   7:36 (15?) Wolf
      37,   //   8:37 Bear
      38,   //   9:38? Bat
      0,    //  10: Crocodile (on land)
      0,    //  11: Crocodile (in water)
      39,   //  12:39? Lion (male)
      39,   //  13:39? Lion (female)
      39,   //  14:39? Panther
      45,   //  15:45? Gorilla
      21,   //  16:21? Giant Rat (on land)
      21,   //  17:21? Giant Rat (in water)
      214,  //  18:214 Tyrannosaur
      0,    //  19:? Raptor
      0,    //  20: Winged mummy (unused) / Winged mutant
      0,    //  21: Lara's hips
      0,    //  22: Lara's hips
      0,    //  23: Centaur mutant
      0,    //  24: Mummy
      0,    //  25: ?
      0,    //  26: ?
      0,    //  27: Larson
      0,    //  28:? (can't die until later) Pierre
      0,    //  29: Skateboard
      0,    //  30: Skateboard kid
      0,    //  31: Cowboy
      0,    //  32: "Mr. T"
      0,    //  33: Winged Natla (actually, Natla with a winged mutant)
      0,    //  34: Giant mutant
      55,   //  35:55 Collapsible floor
      0,    //  36: Swinging blade
      59,   //  37:59 Spikes
      60,   //  38:60 Boulder
      61,   //  39:61 Dart
      62,   //  40:62 Wall-mounted dartgun
      63,   //  41:63? Door (opens upward)
      64,   //  42:64 Slamming doors
      0,    //  43: Sword of Damocles
      0,    //  44: Thor's hammer's handle
      0,    //  45: Thor's hammer's block
      0,    //  46: Hanging ball? / Some kind of box?
      0,    //  47: Metal rod? / Powered mining cart
      67,   //  48:67 Movable cubical block (pushable)
      68,   //  49:68 Movable cubical block (pushable)
      69,   //  50:69 Movable cubical block (pushable)
      70,   //  51:70 Movable cubical block (pushable)
      0,    //  52: Movable tall block
      0,    //  53: Pieces of something?
      0,    //  54: Sword of Damocles
      103,  //  55:103 Above-water switch
      105,  //  56:105 Underwater switch
      106,  //  57:106 Door
      107,  //  58:107 Door
      108,  //  59:108 Door
      109,  //  60:109 Door
      110,  //  61:110 Door
      111,  //  62:111 Door
      112,  //  63:112 Door
      113,  //  64:113 Door
      0,    //  65: Trapdoor (opens downward)
      0,    //  66: Trapdoor (opens downward)
      0,    //  67: ?
      117,  //  68:117 Bridge (flat)
      118,  //  69:118 Bridge (slope = 1)
      119,  //  70:119 Bridge (slope = 2)
      120,  //  71:120 Passport (opening up)
      121,  //  72:121? Compass
      0,    //  73: ?
      0,    //  74: Cogs (animated)
      0,    //  75: Cogs (animated)
      0,    //  76: Cogs (animated)
      0,    //  77: Lara in CS / Scion holder in CS
      0,    //  78: Larson in CS / Natla in CS / Scion holder in CS
      0,    //  79: Larson's gun in CS / Scion in CS / Natla in CS
      0,    //  80: Scion in CS
      133,  //  81:133 Passport (closed)
      134,  //  82:134 N-thingy (Playstation memory card?)
      0,    //  83: Save crystal
      135,  //  84:135 * Pistols
      136,  //  85:136 * Shotgun
      137,  //  86:137 * Magnums
      138,  //  87:138 * Uzis
      0,    //  88:? * Pistol ammo(?)
      143,  //  89:143 * Shotgun ammo
      144,  //  90:144 * Magnum ammo
      145,  //  91:145 * Uzi ammo
      0,    //  92: ?
      149,  //  93:149 * Small medipack
      150,  //  94:150 * Large medipack
      153,  //  95:153 Sunglasses
      154,  //  96:154 Cassette player and headphones
      155,  //  97:155 Direction keys
      0,    //  98: ?
      157,  //  99:157 Pistol
      158,  // 100:158 Shotgun
      159,  // 101:159 Magnum
      160,  // 102:160 Uzi
      164,  // 103:164 Pistol ammo(?)
      165,  // 104:165 Shotgun ammo
      166,  // 105:166 Magnum ammo
      167,  // 106:167 Uzi ammo
      0,    // 107: ?
      171,  // 108:171 Small medipack
      172,  // 109:172 Large medipack
      174,  // 110:174 * Key 1
      175,  // 111:175 * Key 2
      176,  // 112:176 * Key 3
      177,  // 113:177 * Key 4
      178,  // 114:178 Key 1
      179,  // 115:179 Key 2
      180,  // 116:180 Key 3
      181,  // 117:181 Key 4
      182,  // 118:182 Lock 1 empty
      183,  // 119:183 Lock 2 empty
      184,  // 120:184 Lock 3 empty
      185,  // 121:185 Lock 4 empty
      186,  // 122:186 Lock 1 full
      187,  // 123:187 Lock 2 full
      188,  // 124:188 Lock 3 full
      189,  // 125:189 Lock 4 full
      205,  // 126:205 * Key 5
      207,  // 127:207 Key 5
      0,    // 128: Lara's hips
      206,  // 129:206 * Key 6
      193,  // 130:193 * Key 7
      194,  // 131:194 * Key 8
      195,  // 132:195 * Key 9
      208,  // 133:208 Key 6
      197,  // 134:197 Key 7
      198,  // 135:198 Key 8
      199,  // 136:199 Key 9
      0,    // 137:? Lock 6
      201,  // 138:201 Lock 7
      202,  // 139:202 Lock 8
      203,  // 140:203 Lock 9
      0,    // 141: ?
      0,    // 142: ?
      0,    // 143: * Scion Piece
      0,    // 144: ?
      0,    // 145: ?
      0,    // 146: Complete Scion
      0,    // 147: Scion Holder
      0,    // 148: ?
      0,    // 149: ?
      0,    // 150: Scion Piece
      229,  // 151:229 * Flare(?) / Explosion
      0,    // 152: ?
      230,  // 153:230 * Splash
      0,    // 154: ?
      231,  // 155:231 * Bubbles
      231,  // 156:231? * Bubbles
      0,    // 157: ?
      233,  // 158:233 * Blood splatter
      0,    // 159: ?
      234,  // 160:234? * Flying disk
      0,    // 161: Centaur statue
      0,    // 162: Shack suspended from wire rope
      0,    // 163: Mutant egg and holder (normal size)
      238,  // 164:238 * Bullet hit
      239,  // 165:239 * Sparkle
      240,  // 166:240 Gunflare
      0,    // 167: ?
      0,    // 168: ?
      0,    // 169: Lara's hips
      0,    // 170: Lara's hips
      0,    // 171: ?
      0,    // 172: Mutant bullet
      0,    // 173: Mutant grenade
      0,    // 174: ?
      0,    // 175: ?
      0,    // 176: * Splatter
      0,    // 177: Lara's hips
      252,  // 178:252 * Fire
      0,    // 179: Lara's hips
      0,    // 180: Flowing Atlantean lava
      0,    // 181: Mutant egg and holder (big)
      14,   // 182:14? Motorboat
      0,    // 183: Lara's hips
      0,    // 184: ?
      0,    // 185: ?
      0,    // 186: ?
      0,    // 187: ?
      0,    // 188: ?
      0,    // 189: Shrinking wedge?
      255,  // 190:255 * Standard symbols
      0,    // 191: * Plant 1
      0,    // 192: * Plant 2
      0,    // 193: * Plant 3
      0,    // 194: * Plant 4
      0,    // 195: * Plant 5
      0,    // 196: ?
      0,    // 197: ?
      0,    // 198: ?
      0,    // 199: ?
      0,    // 200: * Bag 1
      0,    // 201: ?
      0,    // 202: ?
      0,    // 203: ?
      0,    // 204: * Bag 2
      0,    // 205: ?
      0,    // 206: ?
      241,  // 207:241 Gunflare
      0,    // 208: ?
      0,    // 209: ?
      0,    // 210: ?
      0,    // 211: ?
      0,    // 212: * Rock 1
      0,    // 213: * Rock 2
      0,    // 214: * Rock 3
      0,    // 215: * Bag 3
      0,    // 216: * Pottery 1
      0,    // 217: * Pottery 2
      0,    // 218: ?
      0,    // 219: ?
      0,    // 220: ?
      0,    // 221: ?
      0,    // 222: ?
      0,    // 223: ?
      0,    // 224: ?
      0,    // 225: ?
      0,    // 226: ?
      0,    // 227: ?
      0,    // 228: ?
      0,    // 229: ?
      0,    // 230: ?
      0,    // 231: * Painted pot
      0,    // 232: ?
      0,    // 233: * Inca mummy
      0,    // 234: ?
      0,    // 235: ?
      0,    // 236: * Pottery 3
      0,    // 237: * Pottery 4
      0,    // 238: * Pottery 5
      0,    // 239: * Pottery 6
      };

   ReturnValue = TR1toTR2map[ItemID];
   return((ReturnValue == 0 && ItemID != 0) ? ItemID : ReturnValue);
}

/*
 * The level reader.  Takes a filename (presumably SOMETHING.TR2),
 * reads and parses the file into dynamically allocated structures.
 * Returns a pointer to the whole parsed level.
 */
TR2_Level *ReadTR2level(char *FileName)
{
   TR2_Level *Level;
   FILE      *fp;
   int i,
       j;
#ifdef BIG_ENDIAN_CPU
   int k;   // index variable for endian conversion
#endif   // BIG_ENDIAN_CPU
   bitu32    NumMeshDataWords,
             NumMeshPointers,
             *MeshPointerList,
             DataOffset,
             DataSize;
   bitu8     *RawMeshData;

   /* try to open the file */
   if ((fp = fopen(FileName, READBINARY)) == NULL) {
      perror(FileName);
      return(NULL);
      }

   /* allocate the base structure */
   Level = (TR2_Level *)TR2alloc(sizeof(TR2_Level));

   /* initialise the filename field */
   /* (unfortunately, strdup() isn't universally available...) */
   Level->FileName = (char *)TR2alloc(strlen(FileName) + 1);
   strcpy(Level->FileName, FileName);

   /* read the version */
   TR2fread(&Level->Version, sizeof(Level->Version), 1, fp);
   CONVERT_ENDIAN(&Level->Version, sizeof(Level->Version));
   Level->EngineVersion = TR2getVersion(Level->Version); // TombRaider_1, TombRaider_2, TombRaider_3
   LevelVersion = Level->EngineVersion;

   if (Level->EngineVersion == TombRaider_4) {
#ifdef INCLUDE_TR4_SUPPORT
      bitu16 tmp16;
      bitu32 CompressedSize,
             UncompressedSize;
      bitu8  *CompressedData;
      int    zerr;

      // Allow TR2fread() to really read the file
      LevelVersion = TombRaider_2;

      // read three unknown bitu16s
      TR2fread(&tmp16, sizeof(tmp16), 1, fp);
      TR2fread(&tmp16, sizeof(tmp16), 1, fp);
      TR2fread(&tmp16, sizeof(tmp16), 1, fp);

      // read the sizes of the 32-bit textures
      TR2fread(&UncompressedSize, sizeof(UncompressedSize), 1, fp);
      TR2fread(&CompressedSize, sizeof(CompressedSize), 1, fp);
      // skip the 32-bit textures
      fseek(fp, CompressedSize, SEEK_CUR);

      // read in the 16-bit textures, set NumTextiles
      TR2fread(&UncompressedSize, sizeof(UncompressedSize), 1, fp);
      TR2fread(&CompressedSize, sizeof(CompressedSize), 1, fp);
      Level->NumTextiles = UncompressedSize / sizeof(tr2_textile16);
      Level->Textile16 = (struct tr2_textile16_struct *)TR2alloc(sizeof(tr2_textile16) * Level->NumTextiles);
      // allocate a temporary buffer for decompression
      CompressedData = (bitu8 *)TR2alloc(CompressedSize);
      TR2fread(CompressedData, CompressedSize, 1, fp);
      // decompress the textures
      zerr = uncompress((bitu8 *)Level->Textile16, &UncompressedSize, CompressedData, CompressedSize);
      // free the temporary buffer
      free((char *)CompressedData);

      // read the sizes of the remaining textures
      TR2fread(&UncompressedSize, sizeof(UncompressedSize), 1, fp);
      TR2fread(&CompressedSize, sizeof(CompressedSize), 1, fp);
      // skip the remaining textures
      fseek(fp, CompressedSize, SEEK_CUR);

      // read the sizes of the level data
      TR2fread(&UncompressedSize, sizeof(UncompressedSize), 1, fp);
      TR2fread(&CompressedSize, sizeof(CompressedSize), 1, fp);
      // allocate a temporary buffer for decompression
      CompressedData = (bitu8 *)TR2alloc(CompressedSize);
      TR2fread(CompressedData, CompressedSize, 1, fp);
      TR4_LevelData = (bitu8 *)TR2alloc(UncompressedSize);
      // decompress the level data
      zerr = uncompress(TR4_LevelData, &UncompressedSize, CompressedData, CompressedSize);
      TR4_LevelDataOffset = 0;
      TR4_LevelDataSize = UncompressedSize;

      // Force TR2fread() to read the decompressed data
      LevelVersion = TombRaider_4;
#else    // !INCLUDE_TR4_SUPPORT
      fprintf(stderr, "Sorry, TR4 support was not compiled into this viewer.\n");
      return(NULL);
#endif   // INCLUDE_TR4_SUPPORT
      }

   if (Level->EngineVersion == TombRaider_2 || Level->EngineVersion == TombRaider_3) {
      /* read the 8-bit palette */
#ifdef MACINTOSHPC
      bitu8 MacColour3[3];
      for (i = 0; i < 256; ++i) {
         TR2fread(MacColour3, 3, 1, fp);
         Level->Palette8[i].Red = MacColour3[0];
         Level->Palette8[i].Green = MacColour3[1];
         Level->Palette8[i].Blue = MacColour3[2];
         }
#else
      TR2fread(Level->Palette8, sizeof(tr2_colour), 256, fp);
#endif

      /* read 16-bit palette */
      TR2fread(Level->Palette16, sizeof(Level->Palette16), 1, fp);
      }

   if (Level->EngineVersion != TombRaider_4) {
      /* read the textiles */
      TR2fread(&Level->NumTextiles, sizeof(Level->NumTextiles), 1, fp);
      CONVERT_ENDIAN(&Level->NumTextiles, sizeof(Level->NumTextiles));
      /* 8-bit textiles come first */
      Level->Textile8 = (struct tr2_textile8_struct *)TR2alloc(sizeof(tr2_textile8) * Level->NumTextiles);
      TR2fread(Level->Textile8, sizeof(tr2_textile8), Level->NumTextiles, fp);
      /* 16-bit textiles come second */
      Level->Textile16 = (struct tr2_textile16_struct *)TR2alloc(sizeof(tr2_textile16) * Level->NumTextiles);
      if (Level->EngineVersion != TombRaider_1) {
         TR2fread(Level->Textile16, sizeof(tr2_textile16), Level->NumTextiles, fp);
#ifdef BIG_ENDIAN_CPU
         /* convert 16-bit textiles to big-endian format, if appropriate */
         for (i = 0; i < (int)Level->NumTextiles; ++i) {
            for (j = 0; j < (256 * 256); ++j) {
               CONVERT_ENDIAN(&Level->Textile16[i].Tile[j], sizeof(Level->Textile16[0].Tile[0]));
               }
            }
#endif
         }
      }

   /* 32-bit unknown - seems to always be 0 */
   TR2fread(&Level->UnknownT, sizeof(Level->UnknownT), 1, fp);
   CONVERT_ENDIAN(&Level->UnknownT, sizeof(Level->UnknownT));

   /* read raw room data */
   TR2fread(&Level->NumRooms, sizeof(Level->NumRooms), 1, fp);
   CONVERT_ENDIAN(&Level->NumRooms, sizeof(Level->NumRooms));
   DataSize = Level->NumRooms * sizeof(tr2_room);
   Level->Rooms = (struct tr2_room_struct *)TR2alloc(DataSize);

   /* extract room details */
   for (i = 0; i < Level->NumRooms; ++i) {
      /* read RoomInfo */
      TR2fread(&Level->Rooms[i].info, sizeof(tr2_room_info), 1, fp);
      CONVERT_ENDIAN(&Level->Rooms[i].info.x, sizeof(Level->Rooms[0].info.x));
      CONVERT_ENDIAN(&Level->Rooms[i].info.z, sizeof(Level->Rooms[0].info.z));
      CONVERT_ENDIAN(&Level->Rooms[i].info.yBottom, sizeof(Level->Rooms[0].info.yBottom));
      CONVERT_ENDIAN(&Level->Rooms[i].info.yTop, sizeof(Level->Rooms[0].info.yTop));

      /* read raw data for rest of room */
      TR2fread(&Level->Rooms[i].NumDataWords, sizeof(Level->Rooms[0].NumDataWords), 1, fp);
      CONVERT_ENDIAN(&Level->Rooms[i].NumDataWords, sizeof(Level->Rooms[0].NumDataWords));
      Level->Rooms[i].Data = (bitu8 *)TR2alloc(sizeof(bitu16) * Level->Rooms[i].NumDataWords);
      TR2fread(Level->Rooms[i].Data, sizeof(bitu16), Level->Rooms[i].NumDataWords, fp);

      /* identify vertices */
      DataOffset = 0;
      Level->Rooms[i].RoomData.NumVertices = *(bit16 *)(Level->Rooms[i].Data);
      CONVERT_ENDIAN(&Level->Rooms[i].RoomData.NumVertices, sizeof(Level->Rooms[0].RoomData.NumVertices));
      DataOffset += sizeof(Level->Rooms[0].RoomData.NumVertices);
      DataSize = Level->Rooms[i].RoomData.NumVertices * sizeof(tr2_vertex_room);
      if (Level->Rooms[i].RoomData.NumVertices > 0) {
         Level->Rooms[i].RoomData.Vertices = (tr2_vertex_room *)TR2alloc(DataSize);
         if (Level->EngineVersion == TombRaider_1) {
            DataSize = Level->Rooms[i].RoomData.NumVertices * (sizeof(tr2_vertex_room) - 4);
            for (j = 0; j < Level->Rooms[i].RoomData.NumVertices; ++j) {
               memcpy(&Level->Rooms[i].RoomData.Vertices[j], Level->Rooms[i].Data + DataOffset + (j * (sizeof(tr2_vertex_room) - 4)), sizeof(tr2_vertex_room) - 4);
               // ??? Adjust for what's missing?
               Level->Rooms[i].RoomData.Vertices[j].Lighting2 = Level->Rooms[i].RoomData.Vertices[j].Lighting1;
               Level->Rooms[i].RoomData.Vertices[j].Attributes = 0;
               }
            }
         else {
            memcpy(Level->Rooms[i].RoomData.Vertices, Level->Rooms[i].Data + DataOffset, DataSize);
            }
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].RoomData.NumVertices; ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Vertices[j].Vertex.x, sizeof(Level->Rooms[0].RoomData.Vertices[0].Vertex.x));
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Vertices[j].Vertex.y, sizeof(Level->Rooms[0].RoomData.Vertices[0].Vertex.y));
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Vertices[j].Vertex.z, sizeof(Level->Rooms[0].RoomData.Vertices[0].Vertex.z));
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Vertices[j].Lighting1, sizeof(Level->Rooms[0].RoomData.Vertices[0].Lighting1));
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Vertices[j].Attributes, sizeof(Level->Rooms[0].RoomData.Vertices[0].Attributes));
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Vertices[j].Lighting2, sizeof(Level->Rooms[0].RoomData.Vertices[0].Lighting2));
            }
#endif   // BIG_ENDIAN_CPU
         }
      DataOffset += DataSize;

      /* identify rectangles */
      Level->Rooms[i].RoomData.NumRectangles = *(bit16 *)(Level->Rooms[i].Data + DataOffset);
      CONVERT_ENDIAN(&Level->Rooms[i].RoomData.NumRectangles, sizeof(Level->Rooms[0].RoomData.NumRectangles));
      DataOffset += sizeof(Level->Rooms[0].RoomData.NumRectangles);
      DataSize = Level->Rooms[i].RoomData.NumRectangles * sizeof(tr2_face4);
      if (Level->Rooms[i].RoomData.NumRectangles > 0) {
         Level->Rooms[i].RoomData.Rectangles = (tr2_face4 *)TR2alloc(DataSize);
         memcpy(Level->Rooms[i].RoomData.Rectangles, Level->Rooms[i].Data + DataOffset, DataSize);
         if (Level->EngineVersion >= TombRaider_3) {
            int j;
            for (j = 0; j < Level->Rooms[i].RoomData.NumRectangles; ++j) {
               Level->Rooms[i].RoomData.Rectangles[j].Texture &= 0x7fff;
               }
            }
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].RoomData.NumRectangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Rectangles[j].Vertices[k], sizeof(Level->Rooms[0].RoomData.Rectangles[0].Vertices[0]));
               }
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Rectangles[j].Texture, sizeof(Level->Rooms[0].RoomData.Rectangles[0].Texture));
            }
#endif   // BIG_ENDIAN_CPU
         }
      DataOffset += DataSize;

      /* identify triangles */
      Level->Rooms[i].RoomData.NumTriangles = *(bit16 *)(Level->Rooms[i].Data + DataOffset);
      CONVERT_ENDIAN(&Level->Rooms[i].RoomData.NumTriangles, sizeof(Level->Rooms[0].RoomData.NumTriangles));
      DataOffset += sizeof(Level->Rooms[0].RoomData.NumTriangles);
      DataSize = Level->Rooms[i].RoomData.NumTriangles * sizeof(tr2_face3);
      if (Level->Rooms[i].RoomData.NumTriangles > 0) {
         Level->Rooms[i].RoomData.Triangles = (tr2_face3 *)TR2alloc(DataSize);
         memcpy(Level->Rooms[i].RoomData.Triangles, Level->Rooms[i].Data + DataOffset, DataSize);
         if (Level->EngineVersion >= TombRaider_3) {
            int j;
            for (j = 0; j < Level->Rooms[i].RoomData.NumTriangles; ++j) {
               Level->Rooms[i].RoomData.Triangles[j].Texture &= 0x7fff;
               }
            }
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].RoomData.NumTriangles; ++j) {
            for (k = 0; k < 3; ++k) {
               CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Triangles[j].Vertices[k], sizeof(Level->Rooms[0].RoomData.Triangles[0].Vertices[0]));
               }
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Triangles[j].Texture, sizeof(Level->Rooms[0].RoomData.Triangles[0].Texture));
            }
#endif   // BIG_ENDIAN_CPU
         }
      DataOffset += DataSize;

      /* identify sprites */
      Level->Rooms[i].RoomData.NumSprites = *(bit16 *)(Level->Rooms[i].Data + DataOffset);
      CONVERT_ENDIAN(&Level->Rooms[i].RoomData.NumSprites, sizeof(Level->Rooms[0].RoomData.NumSprites));
      DataOffset += sizeof(Level->Rooms[0].RoomData.NumSprites);
      DataSize = Level->Rooms[i].RoomData.NumSprites * sizeof(tr2_room_sprite);
      if (Level->Rooms[i].RoomData.NumSprites > 0) {
         Level->Rooms[i].RoomData.Sprites = (tr2_room_sprite *)TR2alloc(DataSize);
         memcpy(Level->Rooms[i].RoomData.Sprites, Level->Rooms[i].Data + DataOffset, DataSize);
         if (Level->EngineVersion >= TombRaider_3) {
            int j;
            for (j = 0; j < Level->Rooms[i].RoomData.NumSprites; ++j) {
               Level->Rooms[i].RoomData.Sprites[j].Texture &= 0x7fff;
               }
            }
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].RoomData.NumSprites; ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Sprites[j].Vertex, sizeof(Level->Rooms[0].RoomData.Sprites[0].Vertex));
            CONVERT_ENDIAN(&Level->Rooms[i].RoomData.Sprites[j].Texture, sizeof(Level->Rooms[0].RoomData.Sprites[0].Texture));
            }
#endif   // BIG_ENDIAN_CPU
         }
      /* free the raw room data */
      free(Level->Rooms[i].Data);
      Level->Rooms[i].Data = NULL;  // mark it so we don't accidentally free it again later

      /* read door info */
      TR2fread(&Level->Rooms[i].NumPortals, sizeof(Level->Rooms[0].NumPortals), 1, fp);
      CONVERT_ENDIAN(&Level->Rooms[i].NumPortals, sizeof(Level->Rooms[0].NumPortals));
      Level->Rooms[i].Portals = (struct tr2_room_portal_struct *)TR2alloc(sizeof(tr2_room_portal) * Level->Rooms[i].NumPortals);
      TR2fread(Level->Rooms[i].Portals, sizeof(tr2_room_portal), Level->Rooms[i].NumPortals, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].NumPortals; ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].AdjoiningRoom, sizeof(Level->Rooms[0].Portals[0].AdjoiningRoom));
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Normal.x, sizeof(Level->Rooms[0].Portals[0].Normal.x));
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Normal.y, sizeof(Level->Rooms[0].Portals[0].Normal.y));
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Normal.z, sizeof(Level->Rooms[0].Portals[0].Normal.z));
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Vertices[k].x, sizeof(Level->Rooms[0].Portals[0].Vertices[0].x));
               CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Vertices[k].y, sizeof(Level->Rooms[0].Portals[0].Vertices[0].y));
               CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Vertices[k].z, sizeof(Level->Rooms[0].Portals[0].Vertices[0].z));
               }
            }
#endif   // BIG_ENDIAN_CPU

      /* read sector info */
      TR2fread(&Level->Rooms[i].NumZsectors, sizeof(Level->Rooms[0].NumZsectors), 1, fp);
      CONVERT_ENDIAN(&Level->Rooms[i].NumZsectors, sizeof(Level->Rooms[0].NumZsectors));
      TR2fread(&Level->Rooms[i].NumXsectors, sizeof(Level->Rooms[0].NumXsectors), 1, fp);
      CONVERT_ENDIAN(&Level->Rooms[i].NumXsectors, sizeof(Level->Rooms[0].NumXsectors));
      Level->Rooms[i].SectorList = (struct tr2_room_sector_struct *)TR2alloc(sizeof(tr2_room_sector) * Level->Rooms[i].NumZsectors * Level->Rooms[i].NumXsectors);
      TR2fread(Level->Rooms[i].SectorList, sizeof(tr2_room_sector), Level->Rooms[i].NumZsectors * Level->Rooms[i].NumXsectors, fp);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < (Level->Rooms[i].NumZsectors * Level->Rooms[i].NumXsectors); ++j) {
         CONVERT_ENDIAN(&Level->Rooms[i].SectorList[j].FDindex, sizeof(Level->Rooms[0].SectorList[0].FDindex));
         CONVERT_ENDIAN(&Level->Rooms[i].SectorList[j].BoxIndex, sizeof(Level->Rooms[0].SectorList[0].BoxIndex));
         }
#endif   // BIG_ENDIAN_CPU

      /* read room lighting & mode */
      if (Level->EngineVersion >= TombRaider_3) {
         TR2fread(&Level->Rooms[i].Intensity1, 4, 1, fp);
         // Fake TR2 record:
         Level->Rooms[i].LightMode = 0;
         }
      else if (Level->EngineVersion == TombRaider_1) {
         TR2fread(&Level->Rooms[i].Intensity1, 2, 1, fp);   // Is this intensity or LightMode?
         Level->Rooms[i].Intensity2 = Level->Rooms[i].Intensity1;
         Level->Rooms[i].LightMode = 0;
         }
      else {   // TR2
         TR2fread(&Level->Rooms[i].Intensity1, 6, 1, fp);
         }

      /* read room lighting info */
      TR2fread(&Level->Rooms[i].NumLights, sizeof(Level->Rooms[0].NumLights), 1, fp);
      CONVERT_ENDIAN(&Level->Rooms[i].NumLights, sizeof(Level->Rooms[0].NumLights));
      if (Level->Rooms[i].NumLights > 0) {
//         Level->Rooms[i].Lights = (struct tr2_room_light_struct *)TR2alloc(sizeof(tr2_room_light) * Level->Rooms[i].NumLights);
Level->Rooms[i].Lights = (struct tr2_room_light_struct *)TR2alloc(48 * Level->Rooms[i].NumLights);
         if (Level->EngineVersion == TombRaider_1) {
            for (j = 0; j < Level->Rooms[i].NumLights; ++j) {
               TR2fread(&Level->Rooms[i].Lights[j].x, sizeof(Level->Rooms[0].Lights[0].x), 3, fp); // x, y, z
               TR2fread(&Level->Rooms[i].Lights[j].Intensity1, sizeof(bitu16), 1, fp); // Intensity1
               Level->Rooms[i].Lights[j].Intensity2 = Level->Rooms[i].Lights[j].Intensity1;
               TR2fread(&Level->Rooms[i].Lights[j].Fade1, sizeof(bitu32), 1, fp); // Fade1
               Level->Rooms[i].Lights[j].Fade2 = Level->Rooms[i].Lights[j].Fade1;
               }
            }
         else if (Level->EngineVersion == TombRaider_4) {
            TR2fread(Level->Rooms[i].Lights, 46, Level->Rooms[i].NumLights, fp);
            }
         else {
            TR2fread(Level->Rooms[i].Lights, sizeof(tr2_room_light), Level->Rooms[i].NumLights, fp);
            }
         }
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < Level->Rooms[i].NumLights; ++j) {
         CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].x, sizeof(bit32));
         CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].y, sizeof(bit32));
         CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].z, sizeof(bit32));
         CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Intensity1, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Intensity2, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Fade1, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Fade2, sizeof(bitu32));
         }
#endif   // BIG_ENDIAN_CPU

      /* read Static Mesh Data */
      TR2fread(&Level->Rooms[i].NumStaticMeshes, sizeof(bitu16), 1, fp);
      CONVERT_ENDIAN(&Level->Rooms[i].NumStaticMeshes, sizeof(bitu16));
      if (Level->Rooms[i].NumStaticMeshes > 0) {
         Level->Rooms[i].StaticMeshes = (tr2_room_staticmesh *)TR2alloc(Level->Rooms[i].NumStaticMeshes * sizeof(tr2_room_staticmesh));
         if (Level->EngineVersion == TombRaider_1) {
            for (j = 0; j < Level->Rooms[i].NumStaticMeshes; ++j) {
               TR2fread(&Level->Rooms[i].StaticMeshes[j], 18, 1, fp); // account for the missing .Intensity2
               Level->Rooms[i].StaticMeshes[j].ObjectID = Level->Rooms[i].StaticMeshes[j].Intensity2;
               Level->Rooms[i].StaticMeshes[j].Intensity2 = Level->Rooms[i].StaticMeshes[j].Intensity1;
               }
            }
         else {
            TR2fread(Level->Rooms[i].StaticMeshes, sizeof(tr2_room_staticmesh), Level->Rooms[i].NumStaticMeshes, fp);
            }
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].NumStaticMeshes; ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].x, sizeof(bit32));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].y, sizeof(bit32));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].z, sizeof(bit32));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].ObjectID, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].Rotation, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].Intensity1, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].Intensity2, sizeof(bit16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      TR2fread(&Level->Rooms[i].AlternateRoom, sizeof(bit16), 1, fp);
      CONVERT_ENDIAN(&Level->Rooms[i].AlternateRoom, sizeof(bit16));
      TR2fread(&Level->Rooms[i].Flags, sizeof(bit16), 1, fp);
      CONVERT_ENDIAN(&Level->Rooms[i].Flags, sizeof(bit16));
      /* read TR3 room light colour */
      if (Level->EngineVersion >= TombRaider_3) {
         // we force this to be 3 bytes (instead of just sizeof(RoomLightColour)) for Macs and others that can't handle odd-length structures...
         TR2fread(&Level->Rooms[i].RoomLightColour, 3 /* sizeof(Level->Rooms[i].RoomLightColour) */, 1, fp);
         }
      }

   /* read floor data */
   /*
    * Really, FloorData should be a per-sector dynamic allocation;  however,
    * that requires a parser that can accurately determine where one sector's
    * FloorData ends and another's begins.  Until we have that, we'll stick to
    * this crude (but effective) method...
    */
   TR2fread(&Level->NumFloorData, sizeof(Level->NumFloorData), 1, fp);
   CONVERT_ENDIAN(&Level->NumFloorData, sizeof(Level->NumFloorData));
   if (Level->NumFloorData > 0) {
      Level->FloorData = (bitu16 *)TR2alloc(sizeof(bitu16) * Level->NumFloorData);
      TR2fread(Level->FloorData, sizeof(bitu16), Level->NumFloorData, fp);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < (int)Level->NumFloorData; ++j) {
         CONVERT_ENDIAN(&Level->FloorData[j], sizeof(bitu16));
         }
#endif   // BIG_ENDIAN_CPU
      }

   /* read mesh data */
   TR2fread(&NumMeshDataWords, sizeof(NumMeshDataWords), 1, fp);
   CONVERT_ENDIAN(&NumMeshDataWords, sizeof(NumMeshDataWords));
   RawMeshData = (bitu8 *)TR2alloc(sizeof(bitu16) * NumMeshDataWords);
   TR2fread(RawMeshData, sizeof(bitu16), NumMeshDataWords, fp);
   // Endian-conversion of this data occurs in ioExtractMeshes()

   /* read mesh pointers */
   TR2fread(&NumMeshPointers, sizeof(NumMeshPointers), 1, fp);
   CONVERT_ENDIAN(&NumMeshPointers, sizeof(NumMeshPointers));
   MeshPointerList = (bitu32 *)TR2alloc(sizeof(bitu32) * NumMeshPointers);
   TR2fread(MeshPointerList, sizeof(bitu32), NumMeshPointers, fp);
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)NumMeshPointers; ++j) {
      CONVERT_ENDIAN(&MeshPointerList[j], sizeof(bitu32));
      }
#endif   // BIG_ENDIAN_CPU

   /* extract meshes */
   ioExtractMeshes(RawMeshData, NumMeshPointers, MeshPointerList, Level);
   free(RawMeshData);
   free(MeshPointerList);

   /* read animations */
   TR2fread(&Level->NumAnimations, sizeof(Level->NumAnimations), 1, fp);
   CONVERT_ENDIAN(&Level->NumAnimations, sizeof(Level->NumAnimations));
   if (Level->NumAnimations > 0) {
//      Level->Animations = (struct tr2_animation_struct *)TR2alloc(sizeof(tr2_animation) * Level->NumAnimations);
Level->Animations = (struct tr2_animation_struct *)TR2alloc(40 * Level->NumAnimations);
      if (Level->EngineVersion == TombRaider_4) {
         for (i = 0; i < (int)Level->NumAnimations; ++i) {
            bitu8 junk[16];
            TR2fread(&Level->Animations[i].FrameOffset, 16, 1, fp);
            TR2fread(junk, 8, 1, fp);
            TR2fread(&Level->Animations[i].FrameStart, 16, 1, fp);
            }
         }
      else {
         TR2fread(Level->Animations, sizeof(tr2_animation), Level->NumAnimations, fp);
         }
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)Level->NumAnimations; ++j) {
      CONVERT_ENDIAN(&Level->Animations[j].FrameOffset, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->Animations[j].FrameStart, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Animations[j].FrameEnd, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Animations[j].StateID, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Animations[j].NextAnimation, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Animations[j].NextFrame, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Animations[j].NumStateChanges, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Animations[j].StateChangeOffset, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Animations[j].NumAnimCommands, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Animations[j].AnimCommand, sizeof(bitu16));
      }
#endif   // BIG_ENDIAN_CPU

   /* read state changes */
   TR2fread(&Level->NumStateChanges, sizeof(Level->NumStateChanges), 1, fp);
   CONVERT_ENDIAN(&Level->NumStateChanges, sizeof(Level->NumStateChanges));
   if (Level->NumStateChanges > 0) {
      Level->StateChanges = (struct tr2_state_change_struct *)TR2alloc(sizeof(tr2_state_change) * Level->NumStateChanges);
      TR2fread(Level->StateChanges, sizeof(tr2_state_change), Level->NumStateChanges, fp);
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)Level->NumStateChanges; ++j) {
      CONVERT_ENDIAN(&Level->StateChanges[j].StateID, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->StateChanges[j].NumAnimDispatches, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->StateChanges[j].AnimDispatch, sizeof(bitu16));
      }
#endif   // BIG_ENDIAN_CPU

   /* read AnimDispatches */
   TR2fread(&Level->NumAnimDispatches, sizeof(Level->NumAnimDispatches), 1, fp);
   CONVERT_ENDIAN(&Level->NumAnimDispatches, sizeof(Level->NumAnimDispatches));
   if (Level->NumAnimDispatches > 0) {
      Level->AnimDispatches = (struct tr2_anim_dispatch_struct *)TR2alloc(sizeof(tr2_anim_dispatch) * Level->NumAnimDispatches);
      TR2fread(Level->AnimDispatches, sizeof(tr2_anim_dispatch), Level->NumAnimDispatches, fp);
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)Level->NumAnimDispatches; ++j) {
      CONVERT_ENDIAN(&Level->AnimDispatches[j].Low, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->AnimDispatches[j].High, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->AnimDispatches[j].NextAnimation, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->AnimDispatches[j].NextFrame, sizeof(bitu16));
      }
#endif   // BIG_ENDIAN_CPU

   /* read anim commands */
   TR2fread(&Level->NumAnimCommands, sizeof(Level->NumAnimCommands), 1, fp);
   CONVERT_ENDIAN(&Level->NumAnimCommands, sizeof(Level->NumAnimCommands));
   if (Level->NumAnimCommands > 0) {
      Level->AnimCommands = (struct tr2_anim_command_struct *)TR2alloc(sizeof(tr2_anim_command) * Level->NumAnimCommands);
      TR2fread(Level->AnimCommands, sizeof(tr2_anim_command), Level->NumAnimCommands, fp);
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)Level->NumAnimCommands; ++j) {
      CONVERT_ENDIAN(&Level->AnimCommands[j].Value, sizeof(bitu16));
      }
#endif   // BIG_ENDIAN_CPU

   /* read MeshTrees */
   TR2fread(&Level->NumMeshTrees, sizeof(Level->NumMeshTrees), 1, fp);
   CONVERT_ENDIAN(&Level->NumMeshTrees, sizeof(Level->NumMeshTrees));
   if (Level->NumMeshTrees > 0) {
      Level->MeshTrees = (struct tr2_meshtree_struct *)TR2alloc(Level->NumMeshTrees * sizeof(bitu32));
      TR2fread(Level->MeshTrees, sizeof(bit32), Level->NumMeshTrees, fp);
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)(Level->NumMeshTrees / sizeof(tr2_meshtree)); ++j) {
      CONVERT_ENDIAN(&Level->MeshTrees[j].Flags, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->MeshTrees[j].x, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->MeshTrees[j].y, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->MeshTrees[j].z, sizeof(bitu32));
      }
#endif   // BIG_ENDIAN_CPU

   /* read frames */
   TR2fread(&Level->NumFrames, sizeof(Level->NumFrames), 1, fp);
   CONVERT_ENDIAN(&Level->NumFrames, sizeof(Level->NumFrames));
   if (Level->NumFrames > 0) {
      Level->Frames = (bitu16 *)TR2alloc(sizeof(bitu16) * Level->NumFrames);
      TR2fread(Level->Frames, sizeof(bitu16), Level->NumFrames, fp);
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)Level->NumFrames; ++j) {
      CONVERT_ENDIAN(&Level->Frames[j], sizeof(bitu16));
      }
#endif   // BIG_ENDIAN_CPU
      if (Level->EngineVersion == TombRaider_1) {
         // re-format the Frames[] to look like TR2 frames
         int NumFrames;
         for (j = 0; j < (int)Level->NumAnimations; ++j) {
            int fo = Level->Animations[j].FrameOffset / 2;
            Level->Animations[j].FrameSize = (Level->Frames[fo + 9] * 2) + 10;
            }
         for (i = 0; i < (int)Level->NumFrames; ) {
            i += 9;  // point to NumFrames;
            j = i;   // get rid of (overwrite) NumFrames
            NumFrames = Level->Frames[i++];
            while (NumFrames--) {
               Level->Frames[j++] = Level->Frames[i + 1];   // reverse the words as we go
               Level->Frames[j++] = Level->Frames[i];
               i += 2;
               }
            }
         }
      }

   /* read moveables */
   TR2fread(&Level->NumMoveables, sizeof(Level->NumMoveables), 1, fp);
   CONVERT_ENDIAN(&Level->NumMoveables, sizeof(Level->NumMoveables));
   if (Level->NumMoveables > 0) {
      Level->Moveables = (struct tr2_moveable_struct *)TR2alloc(sizeof(tr2_moveable) * Level->NumMoveables);
      TR2fread(Level->Moveables, sizeof(tr2_moveable), Level->NumMoveables, fp);
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)Level->NumMoveables; ++j) {
      CONVERT_ENDIAN(&Level->Moveables[j].ObjectID, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->Moveables[j].NumMeshes, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Moveables[j].StartingMesh, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Moveables[j].MeshTree, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->Moveables[j].FrameOffset, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->Moveables[j].Animation, sizeof(bitu16));
      }
#endif   // BIG_ENDIAN_CPU

   TR2fread(&Level->NumStaticMeshes, sizeof(Level->NumStaticMeshes), 1, fp);
   CONVERT_ENDIAN(&Level->NumStaticMeshes, sizeof(Level->NumStaticMeshes));
   if (Level->NumStaticMeshes > 0) {
      Level->StaticMeshes = (tr2_staticmesh *)TR2alloc(Level->NumStaticMeshes * sizeof(tr2_staticmesh));
      TR2fread(Level->StaticMeshes, sizeof(tr2_staticmesh), Level->NumStaticMeshes, fp);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < (int)Level->NumStaticMeshes; ++j) {
         int j1, j2;
         CONVERT_ENDIAN(&Level->StaticMeshes[j].ObjectID, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->StaticMeshes[j].StartingMesh, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->StaticMeshes[j].Flags, sizeof(bitu16));
         for (j1 = 0; j1 < 2; ++j1) {
            for (j2 = 0; j2 < 2; ++j2) {
               CONVERT_ENDIAN(&Level->StaticMeshes[j].BoundingBox[j1][j2].x, sizeof(bitu16));
               CONVERT_ENDIAN(&Level->StaticMeshes[j].BoundingBox[j1][j2].y, sizeof(bitu16));
               CONVERT_ENDIAN(&Level->StaticMeshes[j].BoundingBox[j1][j2].z, sizeof(bitu16));
               }
            }
         }
#endif
      }

   if (Level->EngineVersion < TombRaider_3) {
      /* read object textures */
      TR2fread(&Level->NumObjectTextures, sizeof(Level->NumObjectTextures), 1, fp);
      CONVERT_ENDIAN(&Level->NumObjectTextures, sizeof(Level->NumObjectTextures));
      if (Level->NumObjectTextures > 0) {
         Level->ObjectTextures = (struct tr2_object_texture_struct *)TR2alloc(sizeof(tr2_object_texture) * Level->NumObjectTextures);
         TR2fread(Level->ObjectTextures, sizeof(tr2_object_texture), Level->NumObjectTextures, fp);
         }
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < (int)Level->NumObjectTextures; ++j) {
         CONVERT_ENDIAN(&Level->ObjectTextures[j].TransparencyFlags, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->ObjectTextures[j].Tile, sizeof(bitu16));
         }
#endif   // BIG_ENDIAN_CPU
      }

   if (Level->EngineVersion == TombRaider_4) {
      bitu8 zzbuf[4];
      TR2fread(zzbuf, 1, 3, fp); // skip "SPR"
      }

   /* read sprite textures */
   TR2fread(&Level->NumSpriteTextures, sizeof(Level->NumSpriteTextures), 1, fp);
   CONVERT_ENDIAN(&Level->NumSpriteTextures, sizeof(Level->NumSpriteTextures));
   if (Level->NumSpriteTextures > 0) {
      Level->SpriteTextures = (struct tr2_sprite_texture_struct *)TR2alloc(sizeof(tr2_sprite_texture) * Level->NumSpriteTextures);
      TR2fread(Level->SpriteTextures, sizeof(tr2_sprite_texture), Level->NumSpriteTextures, fp);
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)Level->NumSpriteTextures; ++j) {
      CONVERT_ENDIAN(&Level->SpriteTextures[j].Tile, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->SpriteTextures[j].LeftSide, sizeof(bit16));
      CONVERT_ENDIAN(&Level->SpriteTextures[j].TopSide, sizeof(bit16));
      CONVERT_ENDIAN(&Level->SpriteTextures[j].RightSide, sizeof(bit16));
      CONVERT_ENDIAN(&Level->SpriteTextures[j].BottomSide, sizeof(bit16));
      }
#endif   // BIG_ENDIAN_CPU

   /* read sprite texture data (?) */
   TR2fread(&Level->NumSpriteSequences, sizeof(Level->NumSpriteSequences), 1, fp);
   CONVERT_ENDIAN(&Level->NumSpriteSequences, sizeof(Level->NumSpriteSequences));
   if (Level->NumSpriteSequences > 0) {
      Level->SpriteSequences = (struct tr2_sprite_sequence_struct *)TR2alloc(Level->NumSpriteSequences * sizeof(tr2_sprite_sequence));
      TR2fread(Level->SpriteSequences, sizeof(tr2_sprite_sequence), Level->NumSpriteSequences, fp);
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < (int)Level->NumSpriteSequences; ++j) {
      CONVERT_ENDIAN(&Level->SpriteSequences[j].ObjectID, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->SpriteSequences[j].NegativeLength, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->SpriteSequences[j].Offset, sizeof(bitu16));
      }
#endif   // BIG_ENDIAN_CPU

   /* read cameras */
   TR2fread(&Level->NumCameras, sizeof(Level->NumCameras), 1, fp);
   CONVERT_ENDIAN(&Level->NumCameras, sizeof(Level->NumCameras));
   if (Level->NumCameras > 0) {
      Level->Cameras = (tr2_camera *)TR2alloc(sizeof(tr2_camera) * Level->NumCameras);
      TR2fread(Level->Cameras, sizeof(tr2_camera), Level->NumCameras, fp);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < Level->NumCameras; ++j) {
         CONVERT_ENDIAN(&Level->Cameras[j].x, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Cameras[j].y, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Cameras[j].z, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Cameras[j].Room, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Cameras[j].Unknown1, sizeof(bitu16));
         }
#endif
      }

   /* read sound effects (?) */
      TR2fread(&Level->NumSoundSources, sizeof(Level->NumSoundSources), 1, fp);
   CONVERT_ENDIAN(&Level->NumSoundSources, sizeof(Level->NumSoundSources));
   if (Level->NumSoundSources > 0) {
//      Level->SoundSources = (tr2_sound_source *)TR2alloc(sizeof(tr2_sound_source) * Level->NumSoundSources);
Level->SoundSources = (tr2_sound_source *)TR2alloc(40 * Level->NumSoundSources);
   if (Level->EngineVersion == TombRaider_4) {
      TR2fread(Level->SoundSources, 40, Level->NumSoundSources, fp);
      }
   else {
      TR2fread(Level->SoundSources, sizeof(tr2_sound_source), Level->NumSoundSources, fp);
      }
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < Level->NumSoundSources; ++j) {
         CONVERT_ENDIAN(&Level->SoundSources[j].x, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->SoundSources[j].y, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->SoundSources[j].z, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->SoundSources[j].SoundID, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->SoundSources[j].Flags, sizeof(bitu16));
         }
#endif   // BIG_ENDIAN_CPU
      }

   if (Level->EngineVersion == TombRaider_4) {  ////////////////////////////////////////
      bitu32 NumZZ;
      bitu8  zzbuf[16];
      TR2fread(&NumZZ, 1, sizeof(NumZZ), fp);
      while (NumZZ--) {
         TR2fread(zzbuf, 1, 16, fp);
         }
      }

   /* read boxes */
   TR2fread(&Level->NumBoxes, sizeof(Level->NumBoxes), 1, fp);
   CONVERT_ENDIAN(&Level->NumBoxes, sizeof(Level->NumBoxes));
   if (Level->NumBoxes > 0) {
      Level->Boxes = (tr2_box *)TR2alloc(sizeof(tr2_box) * Level->NumBoxes);
      if (Level->EngineVersion == TombRaider_1) {
#ifdef MACINTOSHPC
#pragma options align=mac68k
#endif
#ifdef INTELPC // this is actually a Microsoft Visual C++ thing...
#pragma pack(push,foo,1)
#endif
         struct tr1_box {
            bit32 Zmin, Zmax, Xmin, Xmax;
            bit16 TrueFloor, OverlapIndex;
            } *tr1box;
         tr1box = (struct tr1_box *)TR2alloc(sizeof(struct tr1_box) * Level->NumBoxes);
         TR2fread(tr1box, sizeof(struct tr1_box), Level->NumBoxes, fp);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < Level->NumBoxes; ++j) {
         CONVERT_ENDIAN(&tr1box[j].Zmin, sizeof(bitu32));
         CONVERT_ENDIAN(&tr1box[j].Zmax, sizeof(bitu32));
         CONVERT_ENDIAN(&tr1box[j].Xmin, sizeof(bitu32));
         CONVERT_ENDIAN(&tr1box[j].Xmax, sizeof(bitu32));
         }
#endif
         for (j = 0; j < Level->NumBoxes; ++j) {
            Level->Boxes[j].Zmin = tr1box[j].Zmin / 1024;
            Level->Boxes[j].Zmax = tr1box[j].Zmax / 1024;
            Level->Boxes[j].Xmin = tr1box[j].Xmin / 1024;
            Level->Boxes[j].Xmax = tr1box[j].Xmax / 1024;
            Level->Boxes[j].TrueFloor = tr1box[j].TrueFloor;
            Level->Boxes[j].OverlapIndex = tr1box[j].OverlapIndex;
            }
         free(tr1box);
#ifdef MACINTOSHPC
#pragma options align=reset
#endif
#ifdef INTELPC // this is actually a Microsoft Visual C++ thing...
#pragma pack(pop,foo)
#endif
         }
      else {
         TR2fread(Level->Boxes, sizeof(tr2_box), Level->NumBoxes, fp);
         }
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < Level->NumBoxes; ++j) {
         CONVERT_ENDIAN(&Level->Boxes[j].TrueFloor, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Boxes[j].OverlapIndex, sizeof(bitu16));
         }
#endif
      }

   /* read overlaps (?) */
      TR2fread(&Level->NumOverlaps, sizeof(Level->NumOverlaps), 1, fp);
   CONVERT_ENDIAN(&Level->NumOverlaps, sizeof(Level->NumOverlaps));
   if (Level->NumOverlaps > 0) {
      Level->Overlaps = (bit16 *)TR2alloc(sizeof(bitu16) * Level->NumOverlaps);
      TR2fread(Level->Overlaps, sizeof(bitu16), Level->NumOverlaps, fp);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < Level->NumOverlaps; ++j) {
         CONVERT_ENDIAN(&Level->Overlaps[j], sizeof(bitu16));
         }
#endif
      }

   /* read Zones */
   if (Level->NumBoxes > 0) {
      Level->Zones = (bit16 *)TR2alloc(20 * Level->NumBoxes);
      if (Level->EngineVersion == TombRaider_1) {
         TR2fread(Level->Zones, 12, Level->NumBoxes, fp);
         }
      else {
         TR2fread(Level->Zones, 20, Level->NumBoxes, fp);
         }
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < (Level->NumBoxes * 20); ++j) {
         CONVERT_ENDIAN(&Level->Zones[j], sizeof(bitu16));
         }
#endif   // BIG_ENDIAN_CPU
      }

   /* read animation textures (?) */
   TR2fread(&Level->NumAnimatedTextures, sizeof(Level->NumAnimatedTextures), 1, fp);
   CONVERT_ENDIAN(&Level->NumAnimatedTextures, sizeof(Level->NumAnimatedTextures));
   if (Level->NumAnimatedTextures > 0) {
      Level->AnimatedTextures = (bit16 *)TR2alloc(sizeof(bit16) * Level->NumAnimatedTextures);
      TR2fread(Level->AnimatedTextures, sizeof(bit16), Level->NumAnimatedTextures, fp);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < Level->NumAnimatedTextures; ++j) {
         CONVERT_ENDIAN(&Level->AnimatedTextures[j], sizeof(bitu16));
         }
#endif   // BIG_ENDIAN_CPU
      }

   if (Level->EngineVersion >= TombRaider_3) {
      /* read object textures */
      if (Level->EngineVersion == TombRaider_4) {
         bitu8 zzbuf[4];
         TR2fread(zzbuf, 1, 4, fp); // skip "TEX"  //!! this should be 3, but we have a bug...
         }
      TR2fread(&Level->NumObjectTextures, sizeof(Level->NumObjectTextures), 1, fp);
      CONVERT_ENDIAN(&Level->NumObjectTextures, sizeof(Level->NumObjectTextures));
      if (Level->NumObjectTextures > 0) {
//         Level->ObjectTextures = (struct tr2_object_texture_struct *)TR2alloc(sizeof(tr2_object_texture) * Level->NumObjectTextures);
Level->ObjectTextures = (struct tr2_object_texture_struct *)TR2alloc(38 * Level->NumObjectTextures);
         if (Level->EngineVersion == TombRaider_4) {
            TR2fread(Level->ObjectTextures, 38, Level->NumObjectTextures, fp);
               {
               int jjj, kkk;
               struct tr4ot {
                  bitu16 Unknown1;
                  bitu32 Tile;
                  struct tr2_object_texture_vert_struct Vertices[4];
                  bitu8  filler[16];
                  } *tr4ot_ptr = (struct tr4ot *)Level->ObjectTextures;
               for (jjj = 0; jjj < (int)Level->NumObjectTextures; ++jjj) {
                  Level->ObjectTextures[jjj].TransparencyFlags = tr4ot_ptr[jjj].Unknown1;
                  Level->ObjectTextures[jjj].Tile = (bitu16)tr4ot_ptr[jjj].Tile & 0x7fff;
                  for (kkk = 0; kkk < 4; ++kkk) {
                     Level->ObjectTextures[jjj].Vertices[kkk].Xcoordinate = tr4ot_ptr[jjj].Vertices[kkk].Xcoordinate;
                     Level->ObjectTextures[jjj].Vertices[kkk].Xpixel = tr4ot_ptr[jjj].Vertices[kkk].Xpixel;
                     Level->ObjectTextures[jjj].Vertices[kkk].Ycoordinate = tr4ot_ptr[jjj].Vertices[kkk].Ycoordinate;
                     Level->ObjectTextures[jjj].Vertices[kkk].Ypixel = tr4ot_ptr[jjj].Vertices[kkk].Ypixel;
                     }
                  }
               }
            }
         else {
            TR2fread(Level->ObjectTextures, sizeof(tr2_object_texture), Level->NumObjectTextures, fp);
            }
         }
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < (int)Level->NumObjectTextures; ++j) {
         CONVERT_ENDIAN(&Level->ObjectTextures[j].TransparencyFlags, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->ObjectTextures[j].Tile, sizeof(bitu16));
         }
#endif   // BIG_ENDIAN_CPU
      }

   /* read items */
   TR2fread(&Level->NumItems, sizeof(Level->NumItems), 1, fp);
   CONVERT_ENDIAN(&Level->NumItems, sizeof(Level->NumItems));
   if (Level->NumItems > 0) {
      Level->Items = (struct tr2_item_struct *)TR2alloc(sizeof(tr2_item) * Level->NumItems);
      if (Level->EngineVersion == TombRaider_1) {
         for (i = 0; i < Level->NumItems; ++i) {
            TR2fread(&Level->Items[i], sizeof(tr2_item) - 2, 1, fp);
            Level->Items[i].Flags = Level->Items[i].Intensity2;
            Level->Items[i].Intensity2 = Level->Items[i].Intensity1;
            }
         }
      else {
         TR2fread(Level->Items, sizeof(tr2_item), Level->NumItems, fp);
         }
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < Level->NumItems; ++j) {
      CONVERT_ENDIAN(&Level->Items[j].ObjectID, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Items[j].Room, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Items[j].x, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->Items[j].y, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->Items[j].z, sizeof(bitu32));
      CONVERT_ENDIAN(&Level->Items[j].Angle, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Items[j].Intensity1, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Items[j].Intensity2, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Items[j].Flags, sizeof(bitu16));
      }
#endif   // BIG_ENDIAN_CPU

   /* read LightMaps */
   Level->LightMap = (bitu8 *)TR2alloc(32 * 256);
   if (Level->EngineVersion != TombRaider_4) {
      TR2fread(Level->LightMap, 32, 256, fp);
      }

   if (Level->EngineVersion == TombRaider_1) {
      /* read the 8-bit palette */
#ifdef MACINTOSHPC
      bitu8 MacColour3[3];
      for (i = 0; i < 256; ++i) {
         TR2fread(MacColour3, 3, 1, fp);
         Level->Palette8[i].Red = MacColour3[0];
         Level->Palette8[i].Green = MacColour3[1];
         Level->Palette8[i].Blue = MacColour3[2];
         }
#else
      TR2fread(Level->Palette8, sizeof(tr2_colour), 256, fp);
#endif
      // build 16-bit textiles from 8-bit (no better colours, but creates consistent .TR2 file)
      for (i = 0; i < (int)Level->NumTextiles; ++i) {
         bitu16 argb;
         double colour_tmp;
         for (j = 0; j < (256 * 256); ++j) {
            colour_tmp = Level->Palette8[Level->Textile8[i].Tile[j]].Red & 0x3f;
            colour_tmp = colour_tmp * 31.0 / 63.0;
            argb = ((int)colour_tmp) << 10;
            colour_tmp = Level->Palette8[Level->Textile8[i].Tile[j]].Green & 0x3f;
            colour_tmp = colour_tmp * 31.0 / 63.0;
            argb |= ((int)colour_tmp) << 5;
            colour_tmp = Level->Palette8[Level->Textile8[i].Tile[j]].Blue & 0x3f;
            colour_tmp = colour_tmp * 31.0 / 63.0;
            argb |= ((int)colour_tmp);
            argb &= 0x7fff;   // ???
            if (Level->Textile8[i].Tile[j] != 0)
               argb |= 0x8000;
            Level->Textile16[i].Tile[j] = argb;
            }
         }
      }

   /* read cinematic frames */
   if (Level->EngineVersion == TombRaider_4) {
      bitu32 NumCinematicFrames; // it's now bitu32
      TR2fread(&NumCinematicFrames, sizeof(NumCinematicFrames), 1, fp);
      Level->NumCinematicFrames = (bitu16)NumCinematicFrames;
      CONVERT_ENDIAN(&Level->NumCinematicFrames, sizeof(Level->NumCinematicFrames));
      if (Level->NumCinematicFrames > 0) {
         Level->CinematicFrames = (tr2_cinematic_frame *)TR2alloc(24 * Level->NumCinematicFrames);  // now 24 bytes
         TR2fread(Level->CinematicFrames, 24, Level->NumCinematicFrames, fp);   // now 24 bytes
         // There may or may not be endian conversion required here - I have
         // no idea what this data is.
         }
      }
   else {
      TR2fread(&Level->NumCinematicFrames, sizeof(Level->NumCinematicFrames), 1, fp);
      CONVERT_ENDIAN(&Level->NumCinematicFrames, sizeof(Level->NumCinematicFrames));
      if (Level->NumCinematicFrames > 0) {
         Level->CinematicFrames = (tr2_cinematic_frame *)TR2alloc(sizeof(tr2_cinematic_frame) * Level->NumCinematicFrames);
         TR2fread(Level->CinematicFrames, sizeof(tr2_cinematic_frame), Level->NumCinematicFrames, fp);
         // There may or may not be endian conversion required here - I have
         // no idea what this data is.
         }
      }

   /* read demodata (?) */
   TR2fread(&Level->NumDemoData, sizeof(Level->NumDemoData), 1, fp);
   CONVERT_ENDIAN(&Level->NumDemoData, sizeof(Level->NumDemoData));
   if (Level->NumDemoData > 0) {
      Level->DemoData = (bitu8 *)TR2alloc(1 * Level->NumDemoData);
      TR2fread(Level->DemoData, 1, Level->NumDemoData, fp);
      // There may or may not be endian conversion required here - I have
      // no idea what this data is.
      }

   /* read SoundMap */
   Level->SoundMap = (bit16 *)TR2alloc(370 * sizeof(bit16));
   if (Level->EngineVersion == TombRaider_1) {
      TR2fread(Level->SoundMap, sizeof(bit16), 256, fp);
      memset(Level->SoundMap, 0, 370 * sizeof(bit16));   ////////////////////// KLUDGE!!!
      }
   else {
      if (Level->EngineVersion == TombRaider_4) {
         TR2fread(Level->SoundMap, sizeof(bit16), 370, fp);
         }
      else {
         TR2fread(Level->SoundMap, sizeof(bit16), 370, fp);
         }
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < 370; ++j) {
      CONVERT_ENDIAN(&Level->SoundMap[j], sizeof(bitu16));
      }
#endif

   /* read SoundDetails */
   TR2fread(&Level->NumSoundDetails, sizeof(Level->NumSoundDetails), 1, fp);
   CONVERT_ENDIAN(&Level->NumSoundDetails, sizeof(Level->NumSoundDetails));
   if (Level->NumSoundDetails > 0) {
      Level->SoundDetails = (struct tr2_sound_details_struct *)TR2alloc(sizeof(tr2_sound_details) * Level->NumSoundDetails);
      TR2fread(Level->SoundDetails, sizeof(tr2_sound_details), Level->NumSoundDetails, fp);
      }
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < Level->NumSoundDetails; ++j) {
      CONVERT_ENDIAN(&Level->SoundDetails[j].Sample, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->SoundDetails[j].Volume, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->SoundDetails[j].SoundRange, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->SoundDetails[j].Flags, sizeof(bitu16));
      }
#endif   // BIG_ENDIAN_CPU

#ifdef NEVER_COMPILE_THIS
   if (Level->EngineVersion == TombRaider_1) {
      bitu32 AIFFsize;
      /* read (skip) sound samples (AIFF) */
      TR2fread(&AIFFsize, sizeof(AIFFsize), 1, fp);
      CONVERT_ENDIAN(&AIFFsize, sizeof(AIFFsize));
      fseek(fp, SEEK_CUR, AIFFsize);
      }
#endif

   if (Level->EngineVersion != TombRaider_1) {  // KLUDGE!
      /* read sampleindices */
      TR2fread(&Level->NumSampleIndices, sizeof(Level->NumSampleIndices), 1, fp);
      CONVERT_ENDIAN(&Level->NumSampleIndices, sizeof(Level->NumSampleIndices));
      if (Level->NumSampleIndices > 0) {
         Level->SampleIndices = (bit32 *)TR2alloc(sizeof(bitu32) * Level->NumSampleIndices);
         TR2fread(Level->SampleIndices, sizeof(bitu32), Level->NumSampleIndices, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumSampleIndices; ++j) {
            CONVERT_ENDIAN(&Level->SampleIndices[j], sizeof(bitu32));
            }
#endif
         }
      }

   if (Level->EngineVersion == TombRaider_4) {
      free(TR4_LevelData);
      }

#ifdef NEVER_COMPILE_THIS
   // convert ItemIDs (for writing a .PHD file as a .TR2 file - doesn't usually work)
   if (Level->EngineVersion == TombRaider_1) {
      for (i = 0; i < (int)Level->NumItems; ++i) {
         Level->Items[i].ObjectID = MapTR1ItemToTR2Item(Level->Items[i].ObjectID);
         }
      for (i = 0; i < (int)Level->NumMoveables; ++i) {
         Level->Moveables[i].ObjectID = MapTR1ItemToTR2Item(Level->Moveables[i].ObjectID);
         }
      for (i = 0; i < (int)Level->NumStaticMeshes; ++i) {
         Level->StaticMeshes[i].ObjectID = MapTR1ItemToTR2Item(Level->StaticMeshes[i].ObjectID);
         }
      for (i = 0; i < (int)Level->NumSpriteSequences; ++i) {
         Level->SpriteSequences[i].ObjectID = MapTR1ItemToTR2Item(Level->SpriteSequences[i].ObjectID);
         }
      }
#endif

   fclose(fp);

   return(Level);
}
 

/*
 * ioExtractMeshes()
 *
 * Reads through raw mesh data and extracts the details of each mesh,
 * allocating memory as needed.
 */
static void ioExtractMeshes(bitu8     *MeshData,
                            bitu32    NumMeshPointers,
                            bitu32    *MeshPointers,
                            TR2_Level *Level)
{
   bitu32 size,
          i;
#ifdef   BIG_ENDIAN_CPU
   bitu32 j;   // index variable for endian conversion
   int    k;
#endif   // BIG_ENDIAN_CPU
   bitu8  *MeshPointer;
   int    NegativeSize;

   /* alloc space for mesh */
   Level->NumMeshes = NumMeshPointers;
   Level->Meshes = (struct tr2_mesh_struct *)TR2alloc(sizeof(tr2_mesh) * Level->NumMeshes);

   for (i = 0; i < NumMeshPointers; ++i) {
      /* get mesh start */
      MeshPointer = &MeshData[MeshPointers[i]];

      /* get Centre + Unknowns */
      memcpy(&Level->Meshes[i].Centre.x, MeshPointer, 10);
      CONVERT_ENDIAN(&Level->Meshes[i].Centre.x, sizeof(Level->Meshes[i].Centre.x));
      CONVERT_ENDIAN(&Level->Meshes[i].Centre.y, sizeof(Level->Meshes[i].Centre.y));
      CONVERT_ENDIAN(&Level->Meshes[i].Centre.z, sizeof(Level->Meshes[i].Centre.z));
      // depending on the interpretation of the unknowns that follow the Centre
      // element, more endian conversion may be necessary
      MeshPointer += 10;

      /* get number of vertices */
      memcpy(&Level->Meshes[i].NumVertices, MeshPointer, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumVertices, sizeof(Level->Meshes[i].NumVertices));
      MeshPointer += sizeof(bitu16);
      Level->Meshes[i].NumVertices = (bit16)abs(Level->Meshes[i].NumVertices);

      /* get vertex list */
      size = sizeof(tr2_vertex) * Level->Meshes[i].NumVertices;
      Level->Meshes[i].Vertices = (tr2_vertex *)TR2alloc(size);
      memcpy(Level->Meshes[i].Vertices, MeshPointer, size);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < (bitu32)Level->Meshes[i].NumVertices; ++j) {
         CONVERT_ENDIAN(&Level->Meshes[i].Vertices[j].x, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Meshes[i].Vertices[j].y, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Meshes[i].Vertices[j].z, sizeof(bitu16));
         }
#endif   // BIG_ENDIAN_CPU
      MeshPointer += size;

      /* get number of normals */
      memcpy(&Level->Meshes[i].NumNormals, MeshPointer, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumNormals, sizeof(Level->Meshes[i].NumNormals));
      MeshPointer += sizeof(bitu16);
      NegativeSize = (Level->Meshes[i].NumNormals < 0);
      Level->Meshes[i].NumNormals = (bit16)abs(Level->Meshes[i].NumNormals);

      /* get normal list */
      if (NegativeSize) {
         NegativeSize = 0;
         size = Level->Meshes[i].NumNormals * sizeof(bitu16);
         Level->Meshes[i].MeshLights = (bit16 *)TR2alloc(size);
         memcpy(Level->Meshes[i].MeshLights, MeshPointer, size);
         }
      else {
         size = sizeof(tr2_vertex) * Level->Meshes[i].NumNormals;
         Level->Meshes[i].Normals = (tr2_vertex *)TR2alloc(size);
         memcpy(Level->Meshes[i].Normals, MeshPointer, size);
         }
#ifdef BIG_ENDIAN_CPU
      if (Level->Meshes[i].MeshLights == NULL) {
         for (j = 0; j < (bitu32)Level->Meshes[i].NumNormals; ++j) {
            CONVERT_ENDIAN(&Level->Meshes[i].Normals[j].x, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Meshes[i].Normals[j].y, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Meshes[i].Normals[j].z, sizeof(bitu16));
            }
         }
      else {
         for (j = 0; j < (bitu32)Level->Meshes[i].NumNormals; ++j) {
            CONVERT_ENDIAN(&Level->Meshes[i].MeshLights[j], sizeof(bitu16));
            }
         }
#endif   // BIG_ENDIAN_CPU
      MeshPointer += size;

      /* get number of textured rectangles */
      memcpy(&Level->Meshes[i].NumTexturedRectangles, MeshPointer, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumTexturedRectangles, sizeof(Level->Meshes[i].NumTexturedRectangles));
      MeshPointer += sizeof(bitu16);
      Level->Meshes[i].NumTexturedRectangles = (bit16)abs(Level->Meshes[i].NumTexturedRectangles);

      size = sizeof(tr2_face4) * Level->Meshes[i].NumTexturedRectangles;
      Level->Meshes[i].TexturedRectangles = (tr2_face4 *)TR2alloc(size);

      /* get list of textured rectangles */
      if (Level->Meshes[i].NumTexturedRectangles > 0) {
         if (Level->EngineVersion == TombRaider_4) {
            int j;
            for (j = 0; j < Level->Meshes[i].NumTexturedRectangles; ++j) {
               memcpy(&Level->Meshes[i].TexturedRectangles[j], MeshPointer, sizeof(tr2_face4));
               MeshPointer += sizeof(tr2_face4) + sizeof(bitu16);
               }
            }
         else {
            memcpy(Level->Meshes[i].TexturedRectangles, MeshPointer, size);
            }
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (bitu32)Level->Meshes[i].NumTexturedRectangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].TexturedRectangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].TexturedRectangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         if (Level->EngineVersion != TombRaider_4)
            MeshPointer += size;
         }

      /* get number of textured triangles */
      memcpy(&Level->Meshes[i].NumTexturedTriangles, MeshPointer, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumTexturedTriangles, sizeof(Level->Meshes[i].NumTexturedTriangles));
      MeshPointer += sizeof(bitu16);
      Level->Meshes[i].NumTexturedTriangles = (bit16)abs(Level->Meshes[i].NumTexturedTriangles);

      size = sizeof(tr2_face3) * Level->Meshes[i].NumTexturedTriangles;
      Level->Meshes[i].TexturedTriangles = (tr2_face3 *)TR2alloc(size);

      /* get list of textured triangles */
      if (Level->Meshes[i].NumTexturedTriangles > 0) {
         if (Level->EngineVersion == TombRaider_4) {
            int j;
            for (j = 0; j < Level->Meshes[i].NumTexturedTriangles; ++j) {
               memcpy(&Level->Meshes[i].TexturedTriangles[j], MeshPointer, sizeof(tr2_face3));
               MeshPointer += sizeof(tr2_face3) + sizeof(bitu16);
               }
            }
         else {
            memcpy(Level->Meshes[i].TexturedTriangles, MeshPointer, size);
            }
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (bitu32)Level->Meshes[i].NumTexturedTriangles; ++j) {
            for (k = 0; k < 3; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].TexturedTriangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].TexturedTriangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         if (Level->EngineVersion != TombRaider_4)
            MeshPointer += size;
         }

      if (Level->EngineVersion == TombRaider_4) {  ////////////////////////////////
         Level->Meshes[i].NumColouredRectangles = 0;
         Level->Meshes[i].NumColouredTriangles = 0;
         MeshPointer += 2;
         continue;
         }

      /* get number of coloured rectangles */
      memcpy(&Level->Meshes[i].NumColouredRectangles, MeshPointer, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumColouredRectangles, sizeof(Level->Meshes[i].NumColouredRectangles));
      MeshPointer += sizeof(bitu16);
      Level->Meshes[i].NumColouredRectangles = (bit16)abs(Level->Meshes[i].NumColouredRectangles);

      size = sizeof(tr2_face4) * Level->Meshes[i].NumColouredRectangles;
      Level->Meshes[i].ColouredRectangles = (tr2_face4 *)TR2alloc(size);

      /* get list of coloured rectangles */
      if (Level->Meshes[i].NumColouredRectangles > 0) {
         memcpy(Level->Meshes[i].ColouredRectangles, MeshPointer, size);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (bitu32)Level->Meshes[i].NumColouredRectangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].ColouredRectangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].ColouredRectangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         MeshPointer += size;
         }

      /* get number of coloured triangles */
      memcpy(&Level->Meshes[i].NumColouredTriangles, MeshPointer, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumColouredTriangles, sizeof(Level->Meshes[i].NumColouredTriangles));
      MeshPointer += sizeof(bitu16);
      Level->Meshes[i].NumColouredTriangles = (bit16)abs(Level->Meshes[i].NumColouredTriangles);

      size = sizeof(tr2_face3) * Level->Meshes[i].NumColouredTriangles;
      Level->Meshes[i].ColouredTriangles = (tr2_face3 *)TR2alloc(size);

      /* get list of coloured triangles */
      if (Level->Meshes[i].NumColouredTriangles > 0) {
         memcpy(Level->Meshes[i].ColouredTriangles, MeshPointer, size);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (bitu32)Level->Meshes[i].NumColouredTriangles; ++j) {
            for (k = 0; k < 3; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].ColouredTriangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].ColouredTriangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         MeshPointer += size;
         }
      }
}

/*
 * FreeIfUsed()
 *
 * Frees a block of memory if the pointer is non-NULL.
 * (worker-bee for FreeTR2level)
 */
void FreeIfUsed(void *mem)
{
   if (mem != NULL)
      free(mem);
}

/*
 * FreeTR2level()
 *
 * Frees a TR2_Level structure in its entirety, including the
 * base structure.
 */
void FreeTR2level(TR2_Level *Level)
{
   int i;

   FreeIfUsed(Level->FileName);
   FreeIfUsed(Level->Textile8);
   FreeIfUsed(Level->Textile16);

   /* Rooms have internal allocated structures...*/
   for (i = 0; i < Level->NumRooms; ++i) {
      FreeIfUsed(Level->Rooms[i].RoomData.Vertices);
      FreeIfUsed(Level->Rooms[i].RoomData.Rectangles);
      FreeIfUsed(Level->Rooms[i].RoomData.Triangles);
      FreeIfUsed(Level->Rooms[i].RoomData.Sprites);
      FreeIfUsed(Level->Rooms[i].Data);   // this _should_ already be free (NULL)
      FreeIfUsed(Level->Rooms[i].Portals);
      FreeIfUsed(Level->Rooms[i].SectorList);
      FreeIfUsed(Level->Rooms[i].Lights);
      FreeIfUsed(Level->Rooms[i].StaticMeshes);
      }
   FreeIfUsed(Level->Rooms);

   FreeIfUsed(Level->FloorData);

   /* Meshes have internal allocated structures...*/
   for (i = 0; i < Level->NumMeshes; ++i) {
      FreeIfUsed(Level->Meshes[i].Vertices);
      FreeIfUsed(Level->Meshes[i].Normals);
      FreeIfUsed(Level->Meshes[i].MeshLights);
      FreeIfUsed(Level->Meshes[i].TexturedRectangles);
      FreeIfUsed(Level->Meshes[i].TexturedTriangles);
      FreeIfUsed(Level->Meshes[i].ColouredRectangles);
      FreeIfUsed(Level->Meshes[i].ColouredTriangles);
      }
   FreeIfUsed(Level->Meshes);

   FreeIfUsed(Level->Animations);
   FreeIfUsed(Level->StateChanges);
   FreeIfUsed(Level->AnimDispatches);
   FreeIfUsed(Level->AnimCommands);
   FreeIfUsed(Level->MeshTrees);
   FreeIfUsed(Level->Frames);
   FreeIfUsed(Level->Moveables);
   FreeIfUsed(Level->StaticMeshes);
   FreeIfUsed(Level->ObjectTextures);
   FreeIfUsed(Level->SpriteTextures);
   FreeIfUsed(Level->SpriteSequences);
   FreeIfUsed(Level->Cameras);
   FreeIfUsed(Level->SoundSources);
   FreeIfUsed(Level->Boxes);
   FreeIfUsed(Level->Overlaps);
   FreeIfUsed(Level->Zones);
   FreeIfUsed(Level->AnimatedTextures);
   FreeIfUsed(Level->Items);
   FreeIfUsed(Level->LightMap);
   FreeIfUsed(Level->CinematicFrames);
   FreeIfUsed(Level->DemoData);
   FreeIfUsed(Level->SoundMap);
   FreeIfUsed(Level->SoundDetails);
   FreeIfUsed(Level->SampleIndices);

   FreeIfUsed(Level);
}

/*
 * ioLocateDuplicateMesh(int which)
 *
 * Scans Level->Meshes[] for an exact match of Level->Meshes[which]
 * (occurring prior to <which>).  Returns index of match, or -1 if
 * no match was found.
 */
static int ioLocateDuplicateMesh(TR2_Level *Level, int which)
{
   int i,
       size;

   for (i = 0; i < which; ++i) {
      if (Level->Meshes[i].NumVertices == Level->Meshes[which].NumVertices &&
          Level->Meshes[i].NumNormals == Level->Meshes[which].NumNormals &&
          Level->Meshes[i].NumTexturedRectangles == Level->Meshes[which].NumTexturedRectangles &&
          Level->Meshes[i].NumTexturedTriangles == Level->Meshes[which].NumTexturedTriangles &&
          Level->Meshes[i].NumColouredRectangles == Level->Meshes[which].NumColouredRectangles &&
          Level->Meshes[i].NumColouredTriangles == Level->Meshes[which].NumColouredTriangles) {
         /* counts match, see if data matches */
         if (memcmp(&Level->Meshes[i].Centre.x, &Level->Meshes[which].Centre.x, 10))
            return(-1);
         size = Level->Meshes[i].NumVertices * sizeof(tr2_vertex);
         if (memcmp(Level->Meshes[i].Vertices, Level->Meshes[which].Vertices, size))
            return(-1);
         if (Level->Meshes[i].MeshLights != NULL || Level->Meshes[which].MeshLights != NULL) {
            if (Level->Meshes[i].MeshLights == NULL || Level->Meshes[which].MeshLights == NULL)
               return(-1);
            size = Level->Meshes[i].NumNormals * 2;
            if (memcmp(Level->Meshes[i].MeshLights, Level->Meshes[which].MeshLights, size))
               return(-1);
            }
         else {
            if (Level->Meshes[i].Normals == NULL || Level->Meshes[which].Normals == NULL)
               return(-1);
            size = Level->Meshes[i].NumNormals * sizeof(tr2_vertex);
            if (memcmp(Level->Meshes[i].Normals, Level->Meshes[which].Normals, size))
               return(-1);
            }
         size = Level->Meshes[i].NumTexturedRectangles * sizeof(tr2_face4);
         if (size > 0 && memcmp(Level->Meshes[i].TexturedRectangles, Level->Meshes[which].TexturedRectangles, size))
            return(-1);
         size = Level->Meshes[i].NumTexturedTriangles * sizeof(tr2_face3);
         if (size > 0 && memcmp(Level->Meshes[i].TexturedTriangles, Level->Meshes[which].TexturedTriangles, size))
            return(-1);
         size = Level->Meshes[i].NumColouredRectangles * sizeof(tr2_face4);
         if (size > 0 && memcmp(Level->Meshes[i].ColouredRectangles, Level->Meshes[which].ColouredRectangles, size))
            return(-1);
         size = Level->Meshes[i].NumColouredTriangles * sizeof(tr2_face3);
         if (size > 0 && memcmp(Level->Meshes[i].ColouredTriangles, Level->Meshes[which].ColouredTriangles, size))
            return(-1);
         return(i);
         }
      }
   return(-1);
}
 

/*
 * ioGenerateRawMeshData()
 *
 * Creates raw mesh data from meshes in *Level.
 * Returns number of words in MeshData;  also, as parameters, returns
 * a pointer to an (allocated) array of raw mesh data, and a pointer to
 * an (allocated) array of mesh pointers.  Caller is responsible for
 * freeing these arrays.
 *
 * One odd note - sometimes, one of the counter fields (NumVertices,
 * NumNormals, NumTexturedRectangles, NumTexturedTriangles,
 * NumColouredRectangles, NumColouredTriangles) is NEGATIVE.  However,
 * simply taking the absolute value of the number seems to work just
 * fine (with the exception of NumNormals - see comment below).  Surely
 * this is being used to indicate something, but what?  This routine
 * always uses the positive value.
 *
 * Seems there's an opportunity to optimise here - if two given sets of
 * mesh data are identical, we can simply write them once and point both
 * associated MeshPointers at the single set of data...
 * Could use a "SameAs()" function that scans all meshes BEFORE this one;
 * if this one is identical to another, just set this mesh pointer to that
 * data;  if it's unique, write it out.
 */
static bitu32 ioGenerateRawMeshData(TR2_Level *Level, bitu8 **MeshData, bitu32 **MeshPointers)
{
   bitu32 NumMeshWords;
   bitu32 size,
          i;
   bitu8  *TempPointer;
   bitu16 Temp16;
   int    j;
#ifdef BIG_ENDIAN_CPU
   int    k;
#endif

   *MeshPointers = (bitu32 *)TR2alloc(Level->NumMeshes * sizeof(bitu32));

   /* figure out the size of the raw data */
   for (i = 0, size = 0; (long)i < Level->NumMeshes; ++i) {
      size += 10 + (6 * sizeof(bitu16));  // size of unknowns and counts (e.g. NumVertices, NumTexturedTriangles, etc.)
      size += Level->Meshes[i].NumVertices * sizeof(tr2_vertex);
      if (Level->Meshes[i].MeshLights != NULL)
         size += Level->Meshes[i].NumNormals * sizeof(bitu16);
      else
         size += Level->Meshes[i].NumNormals * sizeof(tr2_vertex);
      size += Level->Meshes[i].NumTexturedRectangles * sizeof(tr2_face4);
      size += Level->Meshes[i].NumTexturedTriangles * sizeof(tr2_face3);
      size += Level->Meshes[i].NumColouredRectangles * sizeof(tr2_face4);
      size += Level->Meshes[i].NumColouredTriangles * sizeof(tr2_face3);
      size += 8;  // fudge for 4-byte alignment
      }
   *MeshData = (bitu8 *)TR2alloc(size);
   NumMeshWords = size / sizeof(bitu16);

   /* build the raw data array */
   for (i = 0, TempPointer = *MeshData; (long)i < Level->NumMeshes; ++i) {
      /* set current mesh pointer */
      if ((j = ioLocateDuplicateMesh(Level, i)) != -1) {
         (*MeshPointers)[i] = (*MeshPointers)[j];
         continue;
         }
      else
         (*MeshPointers)[i] = TempPointer - *MeshData;

      /* handle Centre + unknowns */
      CONVERT_ENDIAN(&Level->Meshes[i].Centre.x, sizeof(Level->Meshes[i].Centre.x));
      CONVERT_ENDIAN(&Level->Meshes[i].Centre.y, sizeof(Level->Meshes[i].Centre.y));
      CONVERT_ENDIAN(&Level->Meshes[i].Centre.z, sizeof(Level->Meshes[i].Centre.z));
      // depending on the interpretation of the unknowns that follow the Centre
      // element, more endian conversion may be necessary
      memcpy(TempPointer, &Level->Meshes[i].Centre.x, 10);
      CONVERT_ENDIAN(&Level->Meshes[i].Centre.x, sizeof(Level->Meshes[i].Centre.x));
      CONVERT_ENDIAN(&Level->Meshes[i].Centre.y, sizeof(Level->Meshes[i].Centre.y));
      CONVERT_ENDIAN(&Level->Meshes[i].Centre.z, sizeof(Level->Meshes[i].Centre.z));
      // depending on the interpretation of the unknowns that follow the Centre
      // element, more endian conversion may be necessary
      TempPointer += 10;

      /* Handle Vertices */
      CONVERT_ENDIAN(&Level->Meshes[i].NumVertices, sizeof(Level->Meshes[i].NumVertices));
      memcpy(TempPointer, &Level->Meshes[i].NumVertices, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumVertices, sizeof(Level->Meshes[i].NumVertices));
      TempPointer += sizeof(bitu16);

      size = sizeof(tr2_vertex) * Level->Meshes[i].NumVertices;
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < Level->Meshes[i].NumVertices; ++j) {
         CONVERT_ENDIAN(&Level->Meshes[i].Vertices[j].x, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Meshes[i].Vertices[j].y, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Meshes[i].Vertices[j].z, sizeof(bitu16));
         }
#endif   // BIG_ENDIAN_CPU
      memcpy(TempPointer, Level->Meshes[i].Vertices, size);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < Level->Meshes[i].NumVertices; ++j) {
         CONVERT_ENDIAN(&Level->Meshes[i].Vertices[j].x, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Meshes[i].Vertices[j].y, sizeof(bitu16));
         CONVERT_ENDIAN(&Level->Meshes[i].Vertices[j].z, sizeof(bitu16));
         }
#endif   // BIG_ENDIAN_CPU
      TempPointer += size;

      /* Handle Normals */
      Temp16 = Level->Meshes[i].NumNormals;
      if (Level->Meshes[i].MeshLights != NULL)
         Temp16 *= -1;
      CONVERT_ENDIAN(&Temp16, sizeof(Temp16));
      memcpy(TempPointer, &Temp16, sizeof(bitu16));
      TempPointer += sizeof(bitu16);

      if (Level->Meshes[i].MeshLights != NULL) {
         size = sizeof(bit16) * Level->Meshes[i].NumNormals;
         // don't know if there is any endian conversion here or not...
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumNormals; ++j) {
            CONVERT_ENDIAN(&Level->Meshes[i].MeshLights[j], sizeof(bitu16));
            }
#endif
         memcpy(TempPointer, Level->Meshes[i].MeshLights, size);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumNormals; ++j) {
            CONVERT_ENDIAN(&Level->Meshes[i].MeshLights[j], sizeof(bitu16));
            }
#endif
         }
      else {   // "normal" Normals
         size = sizeof(tr2_vertex) * Level->Meshes[i].NumNormals;
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumNormals; ++j) {
            CONVERT_ENDIAN(&Level->Meshes[i].Normals[j].x, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Meshes[i].Normals[j].y, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Meshes[i].Normals[j].z, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         memcpy(TempPointer, Level->Meshes[i].Normals, size);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumNormals; ++j) {
            CONVERT_ENDIAN(&Level->Meshes[i].Normals[j].x, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Meshes[i].Normals[j].y, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Meshes[i].Normals[j].z, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }
      TempPointer += size;

      /* Handle Textured Rectangles */
      CONVERT_ENDIAN(&Level->Meshes[i].NumTexturedRectangles, sizeof(Level->Meshes[i].NumTexturedRectangles));
      memcpy(TempPointer, &Level->Meshes[i].NumTexturedRectangles, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumTexturedRectangles, sizeof(Level->Meshes[i].NumTexturedRectangles));
      TempPointer += sizeof(bitu16);

      if (Level->Meshes[i].NumTexturedRectangles > 0) {
         size = sizeof(tr2_face4) * Level->Meshes[i].NumTexturedRectangles;
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumTexturedRectangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].TexturedRectangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].TexturedRectangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         memcpy(TempPointer, Level->Meshes[i].TexturedRectangles, size);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumTexturedRectangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].TexturedRectangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].TexturedRectangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TempPointer += size;
         }

      /* Handle Textured Triangles */
      CONVERT_ENDIAN(&Level->Meshes[i].NumTexturedTriangles, sizeof(Level->Meshes[i].NumTexturedTriangles));
      memcpy(TempPointer, &Level->Meshes[i].NumTexturedTriangles, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumTexturedTriangles, sizeof(Level->Meshes[i].NumTexturedTriangles));
      TempPointer += sizeof(bitu16);

      if (Level->Meshes[i].NumTexturedTriangles > 0) {
         size = sizeof(tr2_face3) * Level->Meshes[i].NumTexturedTriangles;
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumTexturedTriangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].TexturedTriangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].TexturedTriangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         memcpy(TempPointer, Level->Meshes[i].TexturedTriangles, size);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumTexturedTriangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].TexturedTriangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].TexturedTriangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TempPointer += size;
         }

      /* Handle Coloured Rectangles */
      CONVERT_ENDIAN(&Level->Meshes[i].NumColouredRectangles, sizeof(Level->Meshes[i].NumColouredRectangles));
      memcpy(TempPointer, &Level->Meshes[i].NumColouredRectangles, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumColouredRectangles, sizeof(Level->Meshes[i].NumColouredRectangles));
      TempPointer += sizeof(bitu16);

      if (Level->Meshes[i].NumColouredRectangles > 0) {
         size = sizeof(tr2_face4) * Level->Meshes[i].NumColouredRectangles;
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumColouredRectangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].ColouredRectangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].ColouredRectangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         memcpy(TempPointer, Level->Meshes[i].ColouredRectangles, size);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumColouredRectangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].ColouredRectangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].ColouredRectangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TempPointer += size;
         }

      /* Handle Coloured Triangles */
      CONVERT_ENDIAN(&Level->Meshes[i].NumColouredTriangles, sizeof(Level->Meshes[i].NumColouredTriangles));
      memcpy(TempPointer, &Level->Meshes[i].NumColouredTriangles, sizeof(bitu16));
      CONVERT_ENDIAN(&Level->Meshes[i].NumColouredTriangles, sizeof(Level->Meshes[i].NumColouredTriangles));
      TempPointer += sizeof(bitu16);

      if (Level->Meshes[i].NumColouredTriangles > 0) {
         size = sizeof(tr2_face3) * Level->Meshes[i].NumColouredTriangles;
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumColouredTriangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].ColouredTriangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].ColouredTriangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         memcpy(TempPointer, Level->Meshes[i].ColouredTriangles, size);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Meshes[i].NumColouredTriangles; ++j) {
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Meshes[i].ColouredTriangles[j].Vertices[k], sizeof(bitu16));
               }
            CONVERT_ENDIAN(&Level->Meshes[i].ColouredTriangles[j].Texture, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TempPointer += size;
         }
      size = (TempPointer - *MeshData) & 0x03;
      if (size) {  // round up
         TempPointer += (4 - size);
         }
      }

   NumMeshWords = (TempPointer - *MeshData) / sizeof(bitu16);
   return(NumMeshWords);
}

/*
 * TR2fwrite()
 *
 * Wrapper for fwrite() - if fwrite() fails to return the number of
 * bytes requested, print a message and exit.
 */
static int TR2fwrite(void *buffer, size_t size, size_t count, FILE *fp)
{
   int ReturnValue;

   ReturnValue = fwrite(buffer, size, count, fp);
   if ((size_t)ReturnValue != count) {
      perror("TR2fwrite");
      fprintf(stderr, "fwrite(buffer, %d, %d, fp) failed.\n", size, count);
      exit(3);
      }
   return(ReturnValue);
}

/*
 * The level writer.  Takes a level and filename (presumably SOMETHING.TR2),
 * writes the level to the file in TR2 format.
 * Note that endian-ness conversion takes place twice: once to fix the values
 * before writing, and once to put them back again afterward (assuming you
 * want to keep manipulating the same level after a save).
 *
 * Returns   0 if all went well
 *          -1 if there were any errors (writes to stderr in that case)
 */
int WriteTR2level(TR2_Level *Level, char *FileName)
{
   FILE   *fp;
   int    i,
#ifdef BIG_ENDIAN_CPU
          j,
          k,
#endif // BIG_ENDIAN_CPU
          len,
          ReturnValue;
   bitu8  *TempBuffer,
          *TempPointer;
   bitu32 NumMeshDataWords,
          NumMeshPointers,
          *MeshPointers;

   if ((fp = fopen(FileName, WRITEBINARY)) != NULL) {
      /* write the version */
//      CONVERT_ENDIAN(&Level->Version, sizeof(Level->Version));
//      TR2fwrite(&Level->Version, sizeof(Level->Version), 1, fp);
//      CONVERT_ENDIAN(&Level->Version, sizeof(Level->Version));
// temporary hack - always write "TR2 versions"
NumMeshDataWords = 0x0000002d;
TR2fwrite(&NumMeshDataWords, sizeof(bitu32), 1, fp);

      /* write the 8-bit palette */
#ifdef MACINTOSHPC
      for (i = 0; i < 256; ++i) {
         TR2fwrite(&Level->Palette8[i].Red, 1, 1, fp);
         TR2fwrite(&Level->Palette8[i].Green, 1, 1, fp);
         TR2fwrite(&Level->Palette8[i].Blue, 1, 1, fp);
         }
#else
      TR2fwrite(Level->Palette8, sizeof(tr2_colour), 256, fp);
#endif
      /* write 16-bit palette */
      TR2fwrite(Level->Palette16, sizeof(Level->Palette16), 1, fp);

      /* write the textiles */
      CONVERT_ENDIAN(&Level->NumTextiles, sizeof(Level->NumTextiles));
      TR2fwrite(&Level->NumTextiles, sizeof(Level->NumTextiles), 1, fp);
      CONVERT_ENDIAN(&Level->NumTextiles, sizeof(Level->NumTextiles));
      /* 8-bit textiles come first */
      TR2fwrite(Level->Textile8, sizeof(tr2_textile8), Level->NumTextiles, fp);
      /* 16-bit textiles come second */
#ifdef BIG_ENDIAN_CPU
      /* convert 16-bit textiles to big-endian format, if appropriate */
      for (i = 0; i < (int)Level->NumTextiles; ++i) {
         for (j = 0; j < (256 * 256); ++j) {
            CONVERT_ENDIAN(&Level->Textile16[i].Tile[j], sizeof(bitu16));
            }
         }
#endif
      TR2fwrite(Level->Textile16, sizeof(tr2_textile16), Level->NumTextiles, fp);
#ifdef BIG_ENDIAN_CPU
      /* convert 16-bit textiles to big-endian format, if appropriate */
      for (i = 0; i < (int)Level->NumTextiles; ++i) {
         for (j = 0; j < (256 * 256); ++j) {
            CONVERT_ENDIAN(&Level->Textile16[i].Tile[j], sizeof(bitu16));
            }
         }
#endif

      /* write 32-bit unknown */
      CONVERT_ENDIAN(&Level->UnknownT, sizeof(Level->UnknownT));
      TR2fwrite(&Level->UnknownT, sizeof(Level->UnknownT), 1, fp);
      CONVERT_ENDIAN(&Level->UnknownT, sizeof(Level->UnknownT));

      /* write room count */
      CONVERT_ENDIAN(&Level->NumRooms, sizeof(Level->NumRooms));
      TR2fwrite(&Level->NumRooms, sizeof(Level->NumRooms), 1, fp);
      CONVERT_ENDIAN(&Level->NumRooms, sizeof(Level->NumRooms));

      /* write room details */
      for (i = 0; i < Level->NumRooms; ++i) {
         /* write RoomInfo */
         CONVERT_ENDIAN(&Level->Rooms[i].info.x, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Rooms[i].info.z, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Rooms[i].info.yBottom, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Rooms[i].info.yTop, sizeof(bitu32));
         TR2fwrite(&Level->Rooms[i].info, sizeof(tr2_room_info), 1, fp);
         CONVERT_ENDIAN(&Level->Rooms[i].info.x, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Rooms[i].info.z, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Rooms[i].info.yBottom, sizeof(bitu32));
         CONVERT_ENDIAN(&Level->Rooms[i].info.yTop, sizeof(bitu32));

         /* Build raw data for rest of room */
         len = sizeof(bitu16) +
               (Level->Rooms[i].RoomData.NumVertices * sizeof(tr2_vertex_room)) +
               sizeof(bitu16) +
               (Level->Rooms[i].RoomData.NumRectangles * sizeof(tr2_face4)) +
               sizeof(bitu16) +
               (Level->Rooms[i].RoomData.NumTriangles * sizeof(tr2_face3)) +
               sizeof(bitu16) +
               (Level->Rooms[i].RoomData.NumSprites * sizeof(tr2_room_sprite));
         TempBuffer = (bitu8 *)TR2alloc(len);   // allocate space for the raw data

         /* write out the number of data words to follow */
         Level->Rooms[i].NumDataWords = len / sizeof(bitu16);
         CONVERT_ENDIAN(&Level->Rooms[i].NumDataWords, sizeof(bitu32));
         TR2fwrite(&Level->Rooms[i].NumDataWords, sizeof(bitu32), 1, fp);
         CONVERT_ENDIAN(&Level->Rooms[i].NumDataWords, sizeof(bitu32));

         TempPointer = TempBuffer;

         /* copy vertices */
         len = sizeof(bitu16);
         *(bitu16 *)TempPointer = Level->Rooms[i].RoomData.NumVertices;
         CONVERT_ENDIAN(TempPointer, len);
         TempPointer += len;

         len = Level->Rooms[i].RoomData.NumVertices * sizeof(tr2_vertex_room);
         if (Level->Rooms[i].RoomData.NumVertices > 0) {
            memcpy(TempPointer, Level->Rooms[i].RoomData.Vertices, len);
#ifdef BIG_ENDIAN_CPU
            for (i = 0; i < Level->Rooms[i].RoomData.NumVertices; ++i) {
               CONVERT_ENDIAN(((tr2_vertex_room *)TempPointer)[i].Vertex.x, sizeof(bit16));
               CONVERT_ENDIAN(((tr2_vertex_room *)TempPointer)[i].Vertex.y, sizeof(bit16));
               CONVERT_ENDIAN(((tr2_vertex_room *)TempPointer)[i].Vertex.z, sizeof(bit16));
               // the following might be 6*bitu8, meaning that conversion is inappropriate
               CONVERT_ENDIAN(((tr2_vertex_room *)TempPointer)[i].Lighting1, sizeof(bit16));
               CONVERT_ENDIAN(((tr2_vertex_room *)TempPointer)[i].Attributes, sizeof(bitu16));
               CONVERT_ENDIAN(((tr2_vertex_room *)TempPointer)[i].Lighting2, sizeof(bit16));
               }
#endif   // BIG_ENDIAN_CPU
            }
         TempPointer += len;

         /* copy rectangles */
         len = sizeof(bitu16);
         *(bitu16 *)TempPointer = Level->Rooms[i].RoomData.NumRectangles;
         CONVERT_ENDIAN(TempPointer, len);
         TempPointer += len;

         len = Level->Rooms[i].RoomData.NumRectangles * sizeof(tr2_face4);
         if (Level->Rooms[i].RoomData.NumRectangles > 0) {
            memcpy(TempPointer, Level->Rooms[i].RoomData.Rectangles, len);
#ifdef BIG_ENDIAN_CPU
            for (j = 0; j < Level->Rooms[i].RoomData.NumRectangles; ++j) {
               for (k = 0; k < 4; ++k) {
                  CONVERT_ENDIAN(((tr2_face4 *)TempPointer)[j].Vertices[k], sizeof(bit16));
                  }
               CONVERT_ENDIAN(((tr2_face4 *)TempPointer)[j].Texture, sizeof(bit16));
               }
#endif   // BIG_ENDIAN_CPU
            }
         TempPointer += len;

         /* copy triangles */
         len = sizeof(bitu16);
         *(bitu16 *)TempPointer = Level->Rooms[i].RoomData.NumTriangles;
         CONVERT_ENDIAN(TempPointer, len);
         TempPointer += len;

         len = Level->Rooms[i].RoomData.NumTriangles * sizeof(tr2_face3);
         if (Level->Rooms[i].RoomData.NumTriangles > 0) {
            memcpy(TempPointer, Level->Rooms[i].RoomData.Triangles, len);
#ifdef BIG_ENDIAN_CPU
            for (j = 0; j < Level->Rooms[i].RoomData.NumTriangles; ++j) {
               for (k = 0; k < 3; ++k) {
                  CONVERT_ENDIAN(((tr2_face3 *)TempPointer)[j].Vertices[k], sizeof(bit16));
                  }
               CONVERT_ENDIAN(((tr2_face3 *)TempPointer)[j].Texture, sizeof(bit16));
               }
#endif   // BIG_ENDIAN_CPU
            }
         TempPointer += len;

         /* copy sprites */
         len = sizeof(bitu16);
         *(bitu16 *)TempPointer = Level->Rooms[i].RoomData.NumSprites;
         CONVERT_ENDIAN(TempPointer, len);
         TempPointer += len;

         len = Level->Rooms[i].RoomData.NumSprites * sizeof(tr2_room_sprite);
         if (Level->Rooms[i].RoomData.NumSprites > 0) {
            memcpy(TempPointer, Level->Rooms[i].RoomData.Sprites, len);
#ifdef BIG_ENDIAN_CPU
            for (j = 0; j < Level->Rooms[i].RoomData.NumSprites; ++j) {
               CONVERT_ENDIAN(((tr2_room_sprite *)TempPointer)[j].Vertex, sizeof(bit16));
               CONVERT_ENDIAN(((tr2_room_sprite *)TempPointer)[j].Texture, sizeof(bit16));
               }
#endif   // BIG_ENDIAN_CPU
            }

         /* write out the raw data */
         TR2fwrite(TempBuffer, Level->Rooms[i].NumDataWords, sizeof(bitu16), fp);

         /* get rid of our temporary buffer */
         free(TempBuffer);
         TempBuffer = NULL;

         /* write Portal info */
         CONVERT_ENDIAN(&Level->Rooms[i].NumPortals, sizeof(bitu16));
         TR2fwrite(&Level->Rooms[i].NumPortals, sizeof(bitu16), 1, fp);
         CONVERT_ENDIAN(&Level->Rooms[i].NumPortals, sizeof(bitu16));

#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].NumPortals; ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].AdjoiningRoom, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Normal.x, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Normal.y, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Normal.z, sizeof(bit16));
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Vertices[k].x, sizeof(bit16));
               CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Vertices[k].y, sizeof(bit16));
               CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Vertices[k].z, sizeof(bit16));
               }
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->Rooms[i].Portals, sizeof(tr2_room_portal), Level->Rooms[i].NumPortals, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].NumPortals; ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].AdjoiningRoom, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Normal.x, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Normal.y, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Normal.z, sizeof(bit16));
            for (k = 0; k < 4; ++k) {
               CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Vertices[k].x, sizeof(bit16));
               CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Vertices[k].y, sizeof(bit16));
               CONVERT_ENDIAN(&Level->Rooms[i].Portals[j].Vertices[k].z, sizeof(bit16));
               }
            }
#endif   // BIG_ENDIAN_CPU

         /* write sector info */
         CONVERT_ENDIAN(&Level->Rooms[i].NumZsectors, sizeof(bitu16));
         TR2fwrite(&Level->Rooms[i].NumZsectors, sizeof(bitu16), 1, fp);
         CONVERT_ENDIAN(&Level->Rooms[i].NumZsectors, sizeof(bitu16));

         CONVERT_ENDIAN(&Level->Rooms[i].NumXsectors, sizeof(bitu16));
         TR2fwrite(&Level->Rooms[i].NumXsectors, sizeof(bitu16), 1, fp);
         CONVERT_ENDIAN(&Level->Rooms[i].NumXsectors, sizeof(bitu16));

#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (Level->Rooms[i].NumZsectors * Level->Rooms[i].NumXsectors); ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].SectorList[j].FDindex, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Rooms[i].SectorList[j].BoxIndex, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->Rooms[i].SectorList, sizeof(tr2_room_sector), Level->Rooms[i].NumZsectors * Level->Rooms[i].NumXsectors, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (Level->Rooms[i].NumZsectors * Level->Rooms[i].NumXsectors); ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].SectorList[j].FDindex, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Rooms[i].SectorList[j].BoxIndex, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU

         /* write Intensity1, Intensity2, LightMode */
         TR2fwrite(&Level->Rooms[i].Intensity1, 6, 1, fp);

         /* write room lighting info */
         CONVERT_ENDIAN(&Level->Rooms[i].NumLights, sizeof(bitu16));
         TR2fwrite(&Level->Rooms[i].NumLights, sizeof(bitu16), 1, fp);
         CONVERT_ENDIAN(&Level->Rooms[i].NumLights, sizeof(bitu16));

         if (Level->Rooms[i].NumLights > 0) {
#ifdef BIG_ENDIAN_CPU
            for (j = 0; j < Level->Rooms[i].NumLights; ++j) {
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].x, sizeof(bit32));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].y, sizeof(bit32));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].z, sizeof(bit32));
               // Depending on the interpretation of the following elements, this endian
               // conversion may not be correct.
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Intensity1, sizeof(bitu16));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Intensity2, sizeof(bitu16));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Fade1, sizeof(bitu32));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Fade2, sizeof(bitu32));
               }
#endif   // BIG_ENDIAN_CPU
            TR2fwrite(Level->Rooms[i].Lights, sizeof(tr2_room_light), Level->Rooms[i].NumLights, fp);
#ifdef BIG_ENDIAN_CPU
            for (j = 0; j < Level->Rooms[i].NumLights; ++j) {
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].x, sizeof(bit32));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].y, sizeof(bit32));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].z, sizeof(bit32));
               // Depending on the interpretation of the following elements, this endian
               // conversion may not be correct.
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Intensity1, sizeof(bitu16));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Intensity2, sizeof(bitu16));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Fade1, sizeof(bitu32));
               CONVERT_ENDIAN(&Level->Rooms[i].Lights[j].Fade2, sizeof(bitu32));
               }
#endif   // BIG_ENDIAN_CPU
            }

         /* write Static Mesh Data */
         CONVERT_ENDIAN(&Level->Rooms[i].NumStaticMeshes, sizeof(bitu16));
         TR2fwrite(&Level->Rooms[i].NumStaticMeshes, sizeof(bitu16), 1, fp);
         CONVERT_ENDIAN(&Level->Rooms[i].NumStaticMeshes, sizeof(bitu16));
         if (Level->Rooms[i].NumStaticMeshes > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].NumStaticMeshes; ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].x, sizeof(bit32));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].y, sizeof(bit32));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].z, sizeof(bit32));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].ObjectID, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].Rotation, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].Intensity1, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].Intensity2, sizeof(bit16));
            }
#endif   // BIG_ENDIAN_CPU
            TR2fwrite(Level->Rooms[i].StaticMeshes, sizeof(tr2_room_staticmesh), Level->Rooms[i].NumStaticMeshes, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->Rooms[i].NumStaticMeshes; ++j) {
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].x, sizeof(bit32));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].y, sizeof(bit32));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].z, sizeof(bit32));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].ObjectID, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].Rotation, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].Intensity1, sizeof(bit16));
            CONVERT_ENDIAN(&Level->Rooms[i].StaticMeshes[j].Intensity2, sizeof(bit16));
            }
#endif   // BIG_ENDIAN_CPU
            }

         /* write miscellaneous unknowns (thought to be lighting-related) */
         CONVERT_ENDIAN(&Level->Rooms[i].AlternateRoom, sizeof(bit16));
         TR2fwrite(&Level->Rooms[i].AlternateRoom, sizeof(bit16), 1, fp);
         CONVERT_ENDIAN(&Level->Rooms[i].AlternateRoom, sizeof(bit16));

         CONVERT_ENDIAN(&Level->Rooms[i].Flags, sizeof(bit16));
         TR2fwrite(&Level->Rooms[i].Flags, sizeof(bit16), 1, fp);
         CONVERT_ENDIAN(&Level->Rooms[i].Flags, sizeof(bit16));
         }

      /* write floor data */
      /*
       * Really, FloorData should be a per-sector dynamic allocation;  however,
       * that requires a parser that can accurately determine where one sector's
       * FloorData ends and another's begins.  Until we have that, we'll stick to
       * this crude (but effective) method...
       */
      CONVERT_ENDIAN(&Level->NumFloorData, sizeof(Level->NumFloorData));
      TR2fwrite(&Level->NumFloorData, sizeof(Level->NumFloorData), 1, fp);
      CONVERT_ENDIAN(&Level->NumFloorData, sizeof(Level->NumFloorData));
      if (Level->NumFloorData > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumFloorData; ++j) {
            CONVERT_ENDIAN(&Level->FloorData[j], sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->FloorData, sizeof(bitu16), Level->NumFloorData, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumFloorData; ++j) {
            CONVERT_ENDIAN(&Level->FloorData[j], sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* generate raw mesh data */
      NumMeshDataWords = ioGenerateRawMeshData(Level, &TempBuffer, &MeshPointers);
      NumMeshPointers = Level->NumMeshes;

      /* write mesh data */
      CONVERT_ENDIAN(&NumMeshDataWords, sizeof(NumMeshDataWords));
      TR2fwrite(&NumMeshDataWords, sizeof(NumMeshDataWords), 1, fp);
      CONVERT_ENDIAN(&NumMeshDataWords, sizeof(NumMeshDataWords));
      TR2fwrite(TempBuffer, sizeof(bitu16), NumMeshDataWords, fp);

      /* write mesh pointers */
      CONVERT_ENDIAN(&NumMeshPointers, sizeof(NumMeshPointers));
      TR2fwrite(&NumMeshPointers, sizeof(NumMeshPointers), 1, fp);
      CONVERT_ENDIAN(&NumMeshPointers, sizeof(NumMeshPointers));
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < (int)NumMeshPointers; ++j) {
         CONVERT_ENDIAN(&MeshPointers[j], sizeof(bitu32));
         }
#endif   // BIG_ENDIAN_CPU
      TR2fwrite(MeshPointers, sizeof(bitu32), NumMeshPointers, fp);
#ifdef BIG_ENDIAN_CPU
      for (j = 0; j < (int)NumMeshPointers; ++j) {
         CONVERT_ENDIAN(&MeshPointers[j], sizeof(bitu32));
         }
#endif   // BIG_ENDIAN_CPU
      /* free temporary arrays */
      free(TempBuffer);
      TempBuffer = NULL;
      free(MeshPointers);
      MeshPointers = NULL;

      /* write animations */
      CONVERT_ENDIAN(&Level->NumAnimations, sizeof(Level->NumAnimations));
      TR2fwrite(&Level->NumAnimations, sizeof(Level->NumAnimations), 1, fp);
      CONVERT_ENDIAN(&Level->NumAnimations, sizeof(Level->NumAnimations));
      if (Level->NumAnimations > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumAnimations; ++j) {
            CONVERT_ENDIAN(&Level->Animations[j].FrameOffset, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Animations[j].FrameStart, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].FrameEnd, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].StateID, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].NextAnimation, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].NextFrame, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].NumStateChanges, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].StateChangeOffset, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].NumAnimCommands, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].AnimCommand, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->Animations, sizeof(tr2_animation), Level->NumAnimations, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumAnimations; ++j) {
            CONVERT_ENDIAN(&Level->Animations[j].FrameOffset, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Animations[j].FrameStart, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].FrameEnd, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].StateID, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].NextAnimation, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].NextFrame, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].NumStateChanges, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].StateChangeOffset, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].NumAnimCommands, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Animations[j].AnimCommand, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write state changes */
      CONVERT_ENDIAN(&Level->NumStateChanges, sizeof(Level->NumStateChanges));
      TR2fwrite(&Level->NumStateChanges, sizeof(Level->NumStateChanges), 1, fp);
      CONVERT_ENDIAN(&Level->NumStateChanges, sizeof(Level->NumStateChanges));
      if (Level->NumStateChanges > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumStateChanges; ++j) {
            CONVERT_ENDIAN(&Level->StateChanges[j].StateID, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->StateChanges[j].NumAnimDispatches, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->StateChanges[j].AnimDispatch, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->StateChanges, sizeof(tr2_state_change), Level->NumStateChanges, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumStateChanges; ++j) {
            CONVERT_ENDIAN(&Level->StateChanges[j].StateID, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->StateChanges[j].NumAnimDispatches, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->StateChanges[j].AnimDispatch, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write anim dispatches */
      CONVERT_ENDIAN(&Level->NumAnimDispatches, sizeof(Level->NumAnimDispatches));
      TR2fwrite(&Level->NumAnimDispatches, sizeof(Level->NumAnimDispatches), 1, fp);
      CONVERT_ENDIAN(&Level->NumAnimDispatches, sizeof(Level->NumAnimDispatches));
      if (Level->NumAnimDispatches > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumAnimDispatches; ++j) {
            CONVERT_ENDIAN(&Level->AnimDispatches[j].Low, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->AnimDispatches[j].High, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->AnimDispatches[j].NextAnimation, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->AnimDispatches[j].NextFrame, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->AnimDispatches, sizeof(tr2_anim_dispatch), Level->NumAnimDispatches, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumAnimDispatches; ++j) {
            CONVERT_ENDIAN(&Level->AnimDispatches[j].Low, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->AnimDispatches[j].High, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->AnimDispatches[j].NextAnimation, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->AnimDispatches[j].NextFrame, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write AnimCommands */
      CONVERT_ENDIAN(&Level->NumAnimCommands, sizeof(Level->NumAnimCommands));
      TR2fwrite(&Level->NumAnimCommands, sizeof(Level->NumAnimCommands), 1, fp);
      CONVERT_ENDIAN(&Level->NumAnimCommands, sizeof(Level->NumAnimCommands));
      if (Level->NumAnimCommands > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumAnimCommands; ++j) {
            CONVERT_ENDIAN(&Level->AnimCommands[j].Value, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->AnimCommands, sizeof(tr2_anim_command), Level->NumAnimCommands, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumAnimCommands; ++j) {
            CONVERT_ENDIAN(&Level->AnimCommands[j].Value, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write MeshTrees */
      CONVERT_ENDIAN(&Level->NumMeshTrees, sizeof(Level->NumMeshTrees));
      TR2fwrite(&Level->NumMeshTrees, sizeof(Level->NumMeshTrees), 1, fp);
      CONVERT_ENDIAN(&Level->NumMeshTrees, sizeof(Level->NumMeshTrees));
      if (Level->NumMeshTrees > 0) {
#ifdef BIG_ENDIAN_CPU
         bitu32 *u32ptr = (bitu32 *)&Level->MeshTrees[0];
         for (j = 0; j < (int)Level->NumMeshTrees; ++j) {
            CONVERT_ENDIAN(&u32ptr[j], sizeof(bitu32));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->MeshTrees, sizeof(bitu32), Level->NumMeshTrees, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumMeshTrees; ++j) {
            CONVERT_ENDIAN(&u32ptr[j], sizeof(bitu32));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write frames */
      CONVERT_ENDIAN(&Level->NumFrames, sizeof(Level->NumFrames));
      TR2fwrite(&Level->NumFrames, sizeof(Level->NumFrames), 1, fp);
      CONVERT_ENDIAN(&Level->NumFrames, sizeof(Level->NumFrames));
      if (Level->NumFrames > 0) {
         TR2fwrite(Level->Frames, sizeof(bitu16), Level->NumFrames, fp);
         // there is probably some endian correction needed here, but I don't
         // currently know the frame structure...
         }

      /* write moveables */
      CONVERT_ENDIAN(&Level->NumMoveables, sizeof(Level->NumMoveables));
      TR2fwrite(&Level->NumMoveables, sizeof(Level->NumMoveables), 1, fp);
      CONVERT_ENDIAN(&Level->NumMoveables, sizeof(Level->NumMoveables));
      if (Level->NumMoveables > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumMoveables; ++j) {
            CONVERT_ENDIAN(&Level->Moveables[j].ObjectID, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Moveables[j].NumMeshes, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Moveables[j].StartingMesh, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Moveables[j].MeshTree, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Moveables[j].FrameOffset, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Moveables[j].Animation, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->Moveables, sizeof(tr2_moveable), Level->NumMoveables, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumMoveables; ++j) {
            CONVERT_ENDIAN(&Level->Moveables[j].ObjectID, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Moveables[j].NumMeshes, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Moveables[j].StartingMesh, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Moveables[j].MeshTree, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Moveables[j].FrameOffset, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Moveables[j].Animation, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      CONVERT_ENDIAN(&Level->NumStaticMeshes, sizeof(Level->NumStaticMeshes));
      TR2fwrite(&Level->NumStaticMeshes, sizeof(Level->NumStaticMeshes), 1, fp);
      CONVERT_ENDIAN(&Level->NumStaticMeshes, sizeof(Level->NumStaticMeshes));
      if (Level->NumStaticMeshes > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumStaticMeshes; ++j) {
            int j1, j2;
            CONVERT_ENDIAN(&Level->StaticMeshes[j].ObjectID, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->StaticMeshes[j].StartingMesh, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->StaticMeshes[j].Flags, sizeof(bitu16));
            for (j1 = 0; j1 < 2; ++j1) {
               for (j2 = 0; j2 < 2; ++j2) {
                  CONVERT_ENDIAN(&Level->StaticMeshes[j].BoundingBox[j1][j2].x, sizeof(bitu16));
                  CONVERT_ENDIAN(&Level->StaticMeshes[j].BoundingBox[j1][j2].y, sizeof(bitu16));
                  CONVERT_ENDIAN(&Level->StaticMeshes[j].BoundingBox[j1][j2].z, sizeof(bitu16));
                  }
               }
            }
#endif
         TR2fwrite(Level->StaticMeshes, sizeof(tr2_staticmesh), Level->NumStaticMeshes, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumStaticMeshes; ++j) {
            int j1, j2;
            CONVERT_ENDIAN(&Level->StaticMeshes[j].ObjectID, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->StaticMeshes[j].StartingMesh, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->StaticMeshes[j].Flags, sizeof(bitu16));
            for (j1 = 0; j1 < 2; ++j1) {
               for (j2 = 0; j2 < 2; ++j2) {
                  CONVERT_ENDIAN(&Level->StaticMeshes[j].BoundingBox[j1][j2].x, sizeof(bitu16));
                  CONVERT_ENDIAN(&Level->StaticMeshes[j].BoundingBox[j1][j2].y, sizeof(bitu16));
                  CONVERT_ENDIAN(&Level->StaticMeshes[j].BoundingBox[j1][j2].z, sizeof(bitu16));
                  }
               }
            }
#endif
         }

      /* write object textures */
      CONVERT_ENDIAN(&Level->NumObjectTextures, sizeof(Level->NumObjectTextures));
      TR2fwrite(&Level->NumObjectTextures, sizeof(Level->NumObjectTextures), 1, fp);
      CONVERT_ENDIAN(&Level->NumObjectTextures, sizeof(Level->NumObjectTextures));
      if (Level->NumObjectTextures > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumObjectTextures; ++j) {
            CONVERT_ENDIAN(&Level->ObjectTextures[j].TransparencyFlags, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->ObjectTextures[j].Tile, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->ObjectTextures, sizeof(tr2_object_texture), Level->NumObjectTextures, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumObjectTextures; ++j) {
            CONVERT_ENDIAN(&Level->ObjectTextures[j].TransparencyFlags, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->ObjectTextures[j].Tile, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write sprite textures */
      CONVERT_ENDIAN(&Level->NumSpriteTextures, sizeof(Level->NumSpriteTextures));
      TR2fwrite(&Level->NumSpriteTextures, sizeof(Level->NumSpriteTextures), 1, fp);
      CONVERT_ENDIAN(&Level->NumSpriteTextures, sizeof(Level->NumSpriteTextures));
      if (Level->NumSpriteTextures > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumSpriteTextures; ++j) {
            CONVERT_ENDIAN(&Level->SpriteTextures[j].Tile, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SpriteTextures[j].LeftSide, sizeof(bit16));
            CONVERT_ENDIAN(&Level->SpriteTextures[j].TopSide, sizeof(bit16));
            CONVERT_ENDIAN(&Level->SpriteTextures[j].RightSide, sizeof(bit16));
            CONVERT_ENDIAN(&Level->SpriteTextures[j].BottomSide, sizeof(bit16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->SpriteTextures, sizeof(tr2_sprite_texture), Level->NumSpriteTextures, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumSpriteTextures; ++j) {
            CONVERT_ENDIAN(&Level->SpriteTextures[j].Tile, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SpriteTextures[j].LeftSide, sizeof(bit16));
            CONVERT_ENDIAN(&Level->SpriteTextures[j].TopSide, sizeof(bit16));
            CONVERT_ENDIAN(&Level->SpriteTextures[j].RightSide, sizeof(bit16));
            CONVERT_ENDIAN(&Level->SpriteTextures[j].BottomSide, sizeof(bit16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write sprite texture data (?) */
      CONVERT_ENDIAN(&Level->NumSpriteSequences, sizeof(Level->NumSpriteSequences));
      TR2fwrite(&Level->NumSpriteSequences, sizeof(Level->NumSpriteSequences), 1, fp);
      CONVERT_ENDIAN(&Level->NumSpriteSequences, sizeof(Level->NumSpriteSequences));
      if (Level->NumSpriteSequences > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumSpriteSequences; ++j) {
            CONVERT_ENDIAN(&Level->SpriteSequences[j].ObjectID, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->SpriteSequences[j].NegativeLength, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SpriteSequences[j].Offset, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->SpriteSequences, sizeof(tr2_sprite_sequence), Level->NumSpriteSequences, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumSpriteSequences; ++j) {
            CONVERT_ENDIAN(&Level->SpriteSequences[j].ObjectID, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->SpriteSequences[j].NegativeLength, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SpriteSequences[j].Offset, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write cameras (?) */
      CONVERT_ENDIAN(&Level->NumCameras, sizeof(Level->NumCameras));
      TR2fwrite(&Level->NumCameras, sizeof(Level->NumCameras), 1, fp);
      CONVERT_ENDIAN(&Level->NumCameras, sizeof(Level->NumCameras));
      if (Level->NumCameras > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < (int)Level->NumCameras; ++j) {
            CONVERT_ENDIAN(&Level->Cameras[j].x, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Cameras[j].y, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Cameras[j].z, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Cameras[j].Room, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Cameras[j].Unknown1, sizeof(bitu16));
            }
#endif
         TR2fwrite(Level->Cameras, sizeof(tr2_camera), Level->NumCameras, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumCameras; ++j) {
            CONVERT_ENDIAN(&Level->Cameras[j].x, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Cameras[j].y, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Cameras[j].z, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Cameras[j].Room, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Cameras[j].Unknown1, sizeof(bitu16));
            }
#endif
         }

      /* write sound effects */
      CONVERT_ENDIAN(&Level->NumSoundSources, sizeof(Level->NumSoundSources));
      TR2fwrite(&Level->NumSoundSources, sizeof(Level->NumSoundSources), 1, fp);
      CONVERT_ENDIAN(&Level->NumSoundSources, sizeof(Level->NumSoundSources));
      if (Level->NumSoundSources > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumSoundSources; ++j) {
            CONVERT_ENDIAN(&Level->SoundSources[j].x, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->SoundSources[j].y, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->SoundSources[j].z, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->SoundSources[j].SoundID, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SoundSources[j].Flags, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->SoundSources, sizeof(tr2_sound_source), Level->NumSoundSources, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumSoundSources; ++j) {
            CONVERT_ENDIAN(&Level->SoundSources[j].x, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->SoundSources[j].y, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->SoundSources[j].z, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->SoundSources[j].SoundID, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SoundSources[j].Flags, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write boxes (?) */
      CONVERT_ENDIAN(&Level->NumBoxes, sizeof(Level->NumBoxes));
      TR2fwrite(&Level->NumBoxes, sizeof(Level->NumBoxes), 1, fp);
      CONVERT_ENDIAN(&Level->NumBoxes, sizeof(Level->NumBoxes));
      if (Level->NumBoxes > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumBoxes; ++j) {
            CONVERT_ENDIAN(&Level->Boxes[j].TrueFloor, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Boxes[j].OverlapIndex, sizeof(bitu16));
            }
#endif
         TR2fwrite(Level->Boxes, sizeof(tr2_box), Level->NumBoxes, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumBoxes; ++j) {
            CONVERT_ENDIAN(&Level->Boxes[j].TrueFloor, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Boxes[j].OverlapIndex, sizeof(bitu16));
            }
#endif
         }

      /* write overlaps (?) */
      CONVERT_ENDIAN(&Level->NumOverlaps, sizeof(Level->NumOverlaps));
      TR2fwrite(&Level->NumOverlaps, sizeof(Level->NumOverlaps), 1, fp);
      CONVERT_ENDIAN(&Level->NumOverlaps, sizeof(Level->NumOverlaps));
      if (Level->NumOverlaps > 0) {
         TR2fwrite(Level->Overlaps, 2, Level->NumOverlaps, fp);
         // There may or may not be endian conversion required here - I have
         // no idea what this data is.
         }

      /* write Zones (?) */
      if (Level->NumBoxes > 0) {
         TR2fwrite(Level->Zones, 20, Level->NumBoxes, fp);
         // There may or may not be endian conversion required here - I have
         // no idea what this data is.
         }

      /* write animation textures (?) */
      CONVERT_ENDIAN(&Level->NumAnimatedTextures, sizeof(Level->NumAnimatedTextures));
      TR2fwrite(&Level->NumAnimatedTextures, sizeof(Level->NumAnimatedTextures), 1, fp);
      CONVERT_ENDIAN(&Level->NumAnimatedTextures, sizeof(Level->NumAnimatedTextures));
      if (Level->NumAnimatedTextures > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumAnimatedTextures; ++j) {
            CONVERT_ENDIAN(&Level->AnimatedTextures[j], sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->AnimatedTextures, sizeof(bit16), Level->NumAnimatedTextures, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumAnimatedTextures; ++j) {
            CONVERT_ENDIAN(&Level->AnimatedTextures[j], sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write items (?) */
      CONVERT_ENDIAN(&Level->NumItems, sizeof(Level->NumItems));
      TR2fwrite(&Level->NumItems, sizeof(Level->NumItems), 1, fp);
      CONVERT_ENDIAN(&Level->NumItems, sizeof(Level->NumItems));
      if (Level->NumItems > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumItems; ++j) {
            CONVERT_ENDIAN(&Level->Items[j].ObjectID, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].Room, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].x, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Items[j].y, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Items[j].z, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Items[j].Angle, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].Intensity1, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].Intensity2, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].Flags, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->Items, sizeof(tr2_item), Level->NumItems, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumItems; ++j) {
            CONVERT_ENDIAN(&Level->Items[j].ObjectID, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].Room, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].x, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Items[j].y, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Items[j].z, sizeof(bitu32));
            CONVERT_ENDIAN(&Level->Items[j].Angle, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].Intensity1, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].Intensity2, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->Items[j].Flags, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write LightMaps */
      TR2fwrite(Level->LightMap, 32, 256, fp);

      /* write cinematic frames */
      CONVERT_ENDIAN(&Level->NumCinematicFrames, sizeof(Level->NumCinematicFrames));
      TR2fwrite(&Level->NumCinematicFrames, sizeof(Level->NumCinematicFrames), 1, fp);
      CONVERT_ENDIAN(&Level->NumCinematicFrames, sizeof(Level->NumCinematicFrames));
      if (Level->NumCinematicFrames > 0) {
         TR2fwrite(Level->CinematicFrames, 16, Level->NumCinematicFrames, fp);
         // There may or may not be endian conversion required here - I have
         // no idea what this data is.
         }

      /* write demodata (?) */
      CONVERT_ENDIAN(&Level->NumDemoData, sizeof(Level->NumDemoData));
      TR2fwrite(&Level->NumDemoData, sizeof(Level->NumDemoData), 1, fp);
      CONVERT_ENDIAN(&Level->NumDemoData, sizeof(Level->NumDemoData));
      if (Level->NumDemoData > 0) {
         TR2fwrite(Level->DemoData, 1, Level->NumDemoData, fp);
         // There may or may not be endian conversion required here - I have
         // no idea what this data is.
         }

      /* write SoundMap */
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < 370; ++j) {
      CONVERT_ENDIAN(&Level->SoundMap[j], sizeof(bitu16));
      }
#endif
      TR2fwrite(Level->SoundMap, sizeof(bit16), 370, fp);
#ifdef BIG_ENDIAN_CPU
   for (j = 0; j < 370; ++j) {
      CONVERT_ENDIAN(&Level->SoundMap[j], sizeof(bitu16));
      }
#endif

      /* write SoundDetails */
      CONVERT_ENDIAN(&Level->NumSoundDetails, sizeof(Level->NumSoundDetails));
      TR2fwrite(&Level->NumSoundDetails, sizeof(Level->NumSoundDetails), 1, fp);
      CONVERT_ENDIAN(&Level->NumSoundDetails, sizeof(Level->NumSoundDetails));
      if (Level->NumSoundDetails > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumSoundDetails; ++j) {
            CONVERT_ENDIAN(&Level->SoundDetails[j].Sample, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SoundDetails[j].Volume, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SoundDetails[j].SoundRange, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SoundDetails[j].Flags, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         TR2fwrite(Level->SoundDetails, sizeof(tr2_sound_details), Level->NumSoundDetails, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumSoundDetails; ++j) {
            CONVERT_ENDIAN(&Level->SoundDetails[j].Sample, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SoundDetails[j].Volume, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SoundDetails[j].SoundRange, sizeof(bitu16));
            CONVERT_ENDIAN(&Level->SoundDetails[j].Flags, sizeof(bitu16));
            }
#endif   // BIG_ENDIAN_CPU
         }

      /* write sampleindices */
      CONVERT_ENDIAN(&Level->NumSampleIndices, sizeof(Level->NumSampleIndices));
      TR2fwrite(&Level->NumSampleIndices, sizeof(Level->NumSampleIndices), 1, fp);
      CONVERT_ENDIAN(&Level->NumSampleIndices, sizeof(Level->NumSampleIndices));
      if (Level->NumSampleIndices > 0) {
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumSampleIndices; ++j) {
            CONVERT_ENDIAN(&Level->SampleIndices[j], sizeof(bitu32));
            }
#endif
         TR2fwrite(Level->SampleIndices, sizeof(bitu32), Level->NumSampleIndices, fp);
#ifdef BIG_ENDIAN_CPU
         for (j = 0; j < Level->NumSampleIndices; ++j) {
            CONVERT_ENDIAN(&Level->SampleIndices[j], sizeof(bitu32));
            }
#endif
         }

      fclose(fp);
      ReturnValue = 0;
      }
   else {
      perror(FileName);
      ReturnValue = -1;
      }
   return(ReturnValue);
}

/*
 * HexDump() does what its name implies - a 16-byte-wide dump, with offsets
 * starting at the specified value.
 */
void HexDump(unsigned char *buffer, int len, int offset, char *prefix, FILE *fp)
{
   int i;

   if (len != 0) {   // don't display anything if it's zero-length
      do {
         fprintf(fp, "%s%04x   ", prefix, offset);
         offset += 16;
         for (i = 0; i < 16 && len--; ++i) {
            fprintf(fp, "%02x ", *buffer++);
            }
         fprintf(fp, "\n");
         } while (len > 0);
      }
}

/*
 * DumpTR2level(TR2_Level *Level, char *FileName)
 *
 * Creates <FileName> and dumps readable ASCII level data to it.
 * Print an easy-to-find marker (e.g. "-=-=-=-=-=" at every heading.
 */
int DumpTR2level(TR2_Level *Level, char *FileName)
{
   FILE *fp;
   int  ReturnValue,
        i,
        j,
        k,
        l;

   if ((fp = fopen(FileName, WRITETEXT)) != NULL) {
      fprintf(fp, "Dump of data for '%s'\n\n", Level->FileName);
      fprintf(fp, "Version = %ld (0x%08lx)\n", Level->Version, Level->Version);
      fprintf(fp, "8-bit palette:\n");
      for (i = 0; i < 256; ++i) {
         fprintf(fp, "   Palette8[%3d]: Red %3d  Green %3d  Blue %3d\n", i, Level->Palette8[i].Red, Level->Palette8[i].Green, Level->Palette8[i].Blue);
         }
      fprintf(fp, "16-bit palette: -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
      HexDump((bitu8 *)Level->Palette16, sizeof(Level->Palette16), 0, "", fp);
// skip textiles - hex dumps of graphic bitmaps aren't much fun to look at...
fprintf(fp, "(Skipped %d 8-bit Textiles) -=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumTextiles);
fprintf(fp, "(Skipped %d 16-bit Textiles) -=-=-=-=-=-=-=-=-=-=-=\n", Level->NumTextiles);
      fprintf(fp, "UnknownT = %ld (0x%08lx)\n", Level->UnknownT, Level->UnknownT);
      fprintf(fp, "NumRooms: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumRooms);
      for (i = 0; i < Level->NumRooms; ++i) {
         fprintf(fp, "Room %d:  ---===---\n", i);
         fprintf(fp, "   Info: X %d  Z %d  yBottom %d  yTop %d\n", Level->Rooms[i].info.x, Level->Rooms[i].info.z, Level->Rooms[i].info.yBottom, Level->Rooms[i].info.yTop);
         fprintf(fp, "   NumVertices: %d\n", Level->Rooms[i].RoomData.NumVertices);
         for (j = 0; j < Level->Rooms[i].RoomData.NumVertices; ++j) {
            fprintf(fp, "      Vertex[%3d]: (%5d, %5d, %5d)  Lighting1 %d  Attributes 0x%04x  Lighting2 %d\n", j,
                   Level->Rooms[i].RoomData.Vertices[j].Vertex.x,
                   Level->Rooms[i].RoomData.Vertices[j].Vertex.y,
                   Level->Rooms[i].RoomData.Vertices[j].Vertex.z,
                   Level->Rooms[i].RoomData.Vertices[j].Lighting1,
                   Level->Rooms[i].RoomData.Vertices[j].Attributes,
                   Level->Rooms[i].RoomData.Vertices[j].Lighting2);
            }
         fprintf(fp, "   NumRectangles: %d\n", Level->Rooms[i].RoomData.NumRectangles);
         for (j = 0; j < Level->Rooms[i].RoomData.NumRectangles; ++j) {
            fprintf(fp, "      Rectangles[%3d]: Vertices (%3d %3d %3d %3d)  Texture %d\n", j,
                   Level->Rooms[i].RoomData.Rectangles[j].Vertices[0],
                   Level->Rooms[i].RoomData.Rectangles[j].Vertices[1],
                   Level->Rooms[i].RoomData.Rectangles[j].Vertices[2],
                   Level->Rooms[i].RoomData.Rectangles[j].Vertices[3],
                   Level->Rooms[i].RoomData.Rectangles[j].Texture);

            }
         fprintf(fp, "   NumTriangles: %d\n", Level->Rooms[i].RoomData.NumTriangles);
         for (j = 0; j < Level->Rooms[i].RoomData.NumTriangles; ++j) {
            fprintf(fp, "      Triangles[%3d]: Vertices (%3d %3d %3d %3d)  Texture %d\n", j,
                   Level->Rooms[i].RoomData.Triangles[j].Vertices[0],
                   Level->Rooms[i].RoomData.Triangles[j].Vertices[1],
                   Level->Rooms[i].RoomData.Triangles[j].Vertices[2],
                   Level->Rooms[i].RoomData.Triangles[j].Texture);

            }
         fprintf(fp, "   NumSprites: %d\n", Level->Rooms[i].RoomData.NumSprites);
         for (j = 0; j < Level->Rooms[i].RoomData.NumSprites; ++j) {
            fprintf(fp, "      Sprites[%3d]: Vertex %3d  Texture %d\n", j,
                   Level->Rooms[i].RoomData.Sprites[j].Vertex,
                   Level->Rooms[i].RoomData.Sprites[j].Texture);

            }
         fprintf(fp, "   NumPortals: %d\n", Level->Rooms[i].NumPortals);
         for (j = 0; j < Level->Rooms[i].NumPortals; ++j) {
            fprintf(fp, "      Portals[%2d]: Adjoining Room %2d  Normal (%3d, %3d, %3d)\n", j,
                   Level->Rooms[i].Portals[j].AdjoiningRoom,
                   Level->Rooms[i].Portals[j].Normal.x,
                   Level->Rooms[i].Portals[j].Normal.y,
                   Level->Rooms[i].Portals[j].Normal.z);
            fprintf(fp, "                 Vertices (%5d, %5d, %5d) (%5d, %5d, %5d) (%5d, %5d, %5d) (%5d, %5d, %5d)\n",
                   Level->Rooms[i].Portals[j].Vertices[0].x,
                   Level->Rooms[i].Portals[j].Vertices[0].y,
                   Level->Rooms[i].Portals[j].Vertices[0].z,
                   Level->Rooms[i].Portals[j].Vertices[1].x,
                   Level->Rooms[i].Portals[j].Vertices[1].y,
                   Level->Rooms[i].Portals[j].Vertices[1].z,
                   Level->Rooms[i].Portals[j].Vertices[2].x,
                   Level->Rooms[i].Portals[j].Vertices[2].y,
                   Level->Rooms[i].Portals[j].Vertices[2].z,
                   Level->Rooms[i].Portals[j].Vertices[3].x,
                   Level->Rooms[i].Portals[j].Vertices[3].y,
                   Level->Rooms[i].Portals[j].Vertices[3].z);
            }
         fprintf(fp, "   NumZsectors: %d  NumXsectors: %d\n", Level->Rooms[i].NumZsectors, Level->Rooms[i].NumXsectors);
         for (j = 0; j < (Level->Rooms[i].NumZsectors * Level->Rooms[i].NumXsectors); ++j) {
            fprintf(fp, "      Sector[%3d]: FDindex %5d  BoxIndex %d  RoomBelow %3d  Floor %4d  RoomAbove %3d  Ceiling %4d\n", j,
                    Level->Rooms[i].SectorList[j].FDindex,
                    Level->Rooms[i].SectorList[j].BoxIndex,
                    Level->Rooms[i].SectorList[j].RoomBelow,
                    Level->Rooms[i].SectorList[j].Floor,
                    Level->Rooms[i].SectorList[j].RoomAbove,
                    Level->Rooms[i].SectorList[j].Ceiling);
            }
         fprintf(fp, "   SectorData (6):\n");
         HexDump((bitu8 *)&Level->Rooms[i].Intensity1, 6, 0, "      ", fp);
         fprintf(fp, "   NumLights: %d\n", Level->Rooms[i].NumLights);
         for (j = 0; j < Level->Rooms[i].NumLights; ++j) {
            fprintf(fp, "      Light %2d: pos (%5d, %5d, %5d) Intensity1 %d  Intensity2 %d  Fade1 %d  Fade2 %d\n", j,
                    Level->Rooms[i].Lights[j].x, Level->Rooms[i].Lights[j].y, Level->Rooms[i].Lights[j].z,
                    Level->Rooms[i].Lights[j].Intensity1, Level->Rooms[i].Lights[j].Intensity2,
                    Level->Rooms[i].Lights[j].Fade1, Level->Rooms[i].Lights[j].Fade2);
            }
         fprintf(fp, "   NumStaticMeshes: %d\n", Level->Rooms[i].NumStaticMeshes);
         for (j = 0; j < Level->Rooms[i].NumStaticMeshes; ++j) {
            fprintf(fp, "      StaticMesh %2d: pos (%6d, %6d, %6d)  Rotation 0x%04x  Unknown2 %d  Unknown3 %d  ObjectID %d\n", j,
                    Level->Rooms[i].StaticMeshes[j].x, Level->Rooms[i].StaticMeshes[j].y, Level->Rooms[i].StaticMeshes[j].z,
                    Level->Rooms[i].StaticMeshes[j].Rotation, Level->Rooms[i].StaticMeshes[j].Intensity1, Level->Rooms[i].StaticMeshes[j].Intensity2,
                    Level->Rooms[i].StaticMeshes[j].ObjectID);
            }
//         HexDump(Level->Rooms[i].LightData, Level->Rooms[i].NumLightData * 20, 0, "      ", fp);
         fprintf(fp, "   AlternateRoom: %d  Flags: %d\n", Level->Rooms[i].AlternateRoom, Level->Rooms[i].Flags);
         if (Level->EngineVersion >= TombRaider_3) {
            fprintf(fp, "   RoomLightColour: R %d G %d B %d\n", Level->Rooms[i].RoomLightColour.Red, Level->Rooms[i].RoomLightColour.Green, Level->Rooms[i].RoomLightColour.Blue);
            }
         }
#ifdef NEVER_COMPILE_THIS
      fprintf(fp, "NumFloorData: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumFloorData);
      for (i = 0; i < (int)Level->NumFloorData; ++i) {
         fprintf(fp, "   FloorData[%4d] = 0x%04x\n", i, Level->FloorData[i]);
         }
#endif
      // FloorData parser
      fprintf(fp, "Parsed FloorData: -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
      fprintf(fp, "   FloorData[   0] = 0x%04x (ignored)\n", Level->FloorData[0]);
      for (i = 1; i < (int)Level->NumFloorData; ) {
         bitu8 Function, SubFunction, More;

         Function = Level->FloorData[i] & 0xff;
         SubFunction = (Level->FloorData[i] & 0x7fff) >> 8;
         More = ((Level->FloorData[i] & 0x8000) == 0);
         fprintf(fp, "   FloorData[%4d]: 0x%04x ", i, Level->FloorData[i]);
         ++i;
         switch (Function) {
            case 0x01 : // door
               fprintf(fp, "0x%04x  Door to room %d (SubFunction 0x%02x)\n", Level->FloorData[i], Level->FloorData[i], SubFunction);
               ++i;     // skip room number
               break;
            case 0x02 : // floor slant
               fprintf(fp, "0x%04x  Floor Slant   UD %4d  LR %4d (SubFunction 0x%02x)\n", Level->FloorData[i], (bit8)((Level->FloorData[i] >> 8) & 0xff), (bit8)(Level->FloorData[i] & 0xff), SubFunction);
               ++i;     // skip slant value
               break;
            case 0x03 : // ceiling slant
               fprintf(fp, "0x%04x  Ceiling Slant UD %4d  LR %4d (SubFunction 0x%02x)\n", Level->FloorData[i], (bit8)((Level->FloorData[i] >> 8) & 0xff), (bit8)(Level->FloorData[i] & 0xff), SubFunction);
               ++i;     // skip slant value
               break;
            case 0x04 : // fdlist action
               fprintf(fp, "0x%04x  ", Level->FloorData[i]);
               switch (SubFunction) {
                  case 0x00 :
                     fprintf(fp, "Run FDlist (ACTIVATE)");
                     break;
                  case 0x01 :
                     fprintf(fp, "If Lara on Ground, Run FDlist (ACTIVATE)");
                     break;
                  case 0x02 :
                     fprintf(fp, "Activate/Deactivate FDlist+1 if item %d active/inactive", Level->FloorData[i + 1] & 0x03ff);
                     break;
                  case 0x03 :
                     fprintf(fp, "Activate FDlist+1 if item %d active", Level->FloorData[i + 1] & 0x03ff);
                     break;
                  case 0x04 :
                     fprintf(fp, "Activate FDlist+1 if item %d is picked up", Level->FloorData[i + 1] & 0x03ff);
                     break;
                  case 0x06 :
                     fprintf(fp, "If Lara on Ground, Run FDlist (DEactivate)");
                     break;
                  case 0x09 :
                     fprintf(fp, "Run FDlist (DEactivate)");
                     break;
                  default :
                     fprintf(fp, "[unknown FDlist SubFunction 0x%02x]", SubFunction);
                     break;
                  }
               fprintf(fp, " (unknown1 = %d)\n", Level->FloorData[i]);
               ++i;     // skip unknown1
               j = 0;
               do {
                  bitu8 command;
                  fprintf(fp, "       FDlist[%3d]: 0x%04x ", j++, Level->FloorData[i]);
                  command = (Level->FloorData[i] >> 10) & 0x1f;
                  switch (command) {
                     case 0x00 :
                        fprintf(fp, "Activate/Deactivate item %d\n", Level->FloorData[i] & 0x03ff);
                        break;
                     case 0x01 :
                        fprintf(fp, "Switch to camera [%d]\n", Level->FloorData[i] & 0x03ff);
                        break;
                     case 0x02 :
                        k = Level->FloorData[i] & 0x03ff;
                        fprintf(fp, "Camera Delay %d sec, %s, unk bit %d\n", k & 0xff, (k & 0x100) ? "ONCE" : "Every Time", (k & 0x200) != 0);
                        break;
                     case 0x06 :
                        fprintf(fp, "Look at item %d\n", Level->FloorData[i] & 0x03ff);
                        break;
                     case 0x07 :
                        fprintf(fp, "End Level\n");
                        break;
                     default :
                        fprintf(fp, "Unknown command 0x%02x, data 0x%04x\n", command, Level->FloorData[i] & 0x03ff);
                        break;
                     }
                  } while ((Level->FloorData[i++] & 0x8000) == 0);
               break;
            case 0x05 : // kills Lara
               fprintf(fp, "        Kills Lara - SubFunction 0x%02x\n", SubFunction);
               break;
            case 0x06 : // used for walls that are climbable
               fprintf(fp, "        Climbable Wall%s:", (SubFunction == 1 || SubFunction == 2 || SubFunction == 4 || SubFunction == 8) ? "" : "s");
               if (SubFunction & 0x02)
                  fprintf(fp, " +X");
               if (SubFunction & 0x01)
                  fprintf(fp, " +Z");
               if (SubFunction & 0x08)
                  fprintf(fp, " -X");
               if (SubFunction & 0x04)
                  fprintf(fp, " -Z");
               fprintf(fp, "\n");
               break;
            }
         if (!More)
            fprintf(fp, "   --- end ---\n");
         }
      fprintf(fp, "NumMeshes: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumMeshes);
      for (i = 0; i < Level->NumMeshes; ++i) {
         fprintf(fp, "Meshes[%3d]: Centre (%5d, %5d, %5d)\n", i, Level->Meshes[i].Centre.x, Level->Meshes[i].Centre.y, Level->Meshes[i].Centre.z);
         fprintf(fp, "      CollisionSize: 0x08lx\n", Level->Meshes[i].CollisionSize);
         fprintf(fp, "      NumVertices: %d\n", Level->Meshes[i].NumVertices);
         for (j = 0; j < Level->Meshes[i].NumVertices; ++j) {
            fprintf(fp, "         Vertex[%3d]: (%5d, %5d, %5d)\n", j,
                   Level->Meshes[i].Vertices[j].x,
                   Level->Meshes[i].Vertices[j].y,
                   Level->Meshes[i].Vertices[j].z);
            }
         fprintf(fp, "      NumNormals: %d\n", Level->Meshes[i].NumNormals * ((Level->Meshes[i].MeshLights != NULL) ? -1 : 1));
         for (j = 0; j < Level->Meshes[i].NumNormals; ++j) {
            if (Level->Meshes[i].MeshLights != NULL) {
               fprintf(fp, "         MeshLights[%3d]: %5d\n", j, Level->Meshes[i].MeshLights[j]);
               }
            else {
               fprintf(fp, "         Normal[%3d]: (%5d, %5d, %5d)\n", j,
                       Level->Meshes[i].Normals[j].x,
                       Level->Meshes[i].Normals[j].y,
                       Level->Meshes[i].Normals[j].z);
               }
            }
         fprintf(fp, "   NumTexturedRectangles: %d\n", Level->Meshes[i].NumTexturedRectangles);
         for (j = 0; j < Level->Meshes[i].NumTexturedRectangles; ++j) {
            fprintf(fp, "         TexturedRectangles[%3d]: Vertices (%3d %3d %3d %3d) Texture %d\n", j,
                   Level->Meshes[i].TexturedRectangles[j].Vertices[0],
                   Level->Meshes[i].TexturedRectangles[j].Vertices[1],
                   Level->Meshes[i].TexturedRectangles[j].Vertices[2],
                   Level->Meshes[i].TexturedRectangles[j].Vertices[3],
                   Level->Meshes[i].TexturedRectangles[j].Texture);

            }
         fprintf(fp, "   NumTexturedTriangles: %d\n", Level->Meshes[i].NumTexturedTriangles);
         for (j = 0; j < Level->Meshes[i].NumTexturedTriangles; ++j) {
            fprintf(fp, "         TexturedTriangles[%3d]: Vertices (%3d %3d %3d) Texture %d\n", j,
                   Level->Meshes[i].TexturedTriangles[j].Vertices[0],
                   Level->Meshes[i].TexturedTriangles[j].Vertices[1],
                   Level->Meshes[i].TexturedTriangles[j].Vertices[2],
                   Level->Meshes[i].TexturedTriangles[j].Texture);

            }
         fprintf(fp, "   NumColouredRectangles: %d\n", Level->Meshes[i].NumColouredRectangles);
         for (j = 0; j < Level->Meshes[i].NumColouredRectangles; ++j) {
            fprintf(fp, "         ColouredRectangles[%3d]: Vertices (%3d %3d %3d %3d) Colour 0x%02x (=%d?)\n", j,
                   Level->Meshes[i].ColouredRectangles[j].Vertices[0],
                   Level->Meshes[i].ColouredRectangles[j].Vertices[1],
                   Level->Meshes[i].ColouredRectangles[j].Vertices[2],
                   Level->Meshes[i].ColouredRectangles[j].Vertices[3],
                   Level->Meshes[i].ColouredRectangles[j].Texture,
                   Level->Meshes[i].ColouredRectangles[j].Texture & 0xff);

            }
         fprintf(fp, "   NumColouredTriangles: %d\n", Level->Meshes[i].NumColouredTriangles);
         for (j = 0; j < Level->Meshes[i].NumColouredTriangles; ++j) {
            fprintf(fp, "         ColouredTriangles[%3d]: Vertices (%3d %3d %3d) Colour 0x%02x (=%d?)\n", j,
                   Level->Meshes[i].ColouredTriangles[j].Vertices[0],
                   Level->Meshes[i].ColouredTriangles[j].Vertices[1],
                   Level->Meshes[i].ColouredTriangles[j].Vertices[2],
                   Level->Meshes[i].ColouredTriangles[j].Texture,
                   Level->Meshes[i].ColouredTriangles[j].Texture & 0xff);

            }
         }
      fprintf(fp, "NumAnimations: %d\n", Level->NumAnimations);
      for (i = 0; i < (int)Level->NumAnimations; ++i) {
         fprintf(fp, "Animations[%3d]: FrameOffset %d  (FrameIndex %d)  Unknown1 %d  FrameSize %d\n", i,
                 Level->Animations[i].FrameOffset, Level->Animations[i].FrameOffset / 2, Level->Animations[i].Unknown1, Level->Animations[i].FrameSize);
         fprintf(fp, "          Unknown2 (10):\n");
         HexDump((bitu8 *)&Level->Animations[i].Unknown1, 8, 0, "          ", fp);
         fprintf(fp, "          FrameStart %d  FrameEnd %d\n", Level->Animations[i].FrameStart, Level->Animations[i].FrameEnd);
         fprintf(fp, "          AnimationChain Anim:Frame %d:%d\n",
                 Level->Animations[i].NextAnimation,
                 Level->Animations[i].NextFrame);
         fprintf(fp, "          StartingStateChange %d  NumStateChanges %d\n",
                 Level->Animations[i].StateChangeOffset,
                 Level->Animations[i].NumStateChanges);
         fprintf(fp, "          NumAnimCommands %d  AnimCommands %d\n",
                 Level->Animations[i].NumAnimCommands,
                 Level->Animations[i].AnimCommand);
         }
      fprintf(fp, "NumStateChanges: %d  (", Level->NumStateChanges);
      if (Level->NumStateChanges > 0) {
            for (i = 0, j = 0, k = 0x7fffffff; i < (int)Level->NumStateChanges; ++i) {
               if (Level->StateChanges[i].StateID > j)
                  j = Level->StateChanges[i].StateID;
               if (Level->StateChanges[i].StateID < k)
                  k = Level->StateChanges[i].StateID;
               }
            fprintf(fp, "MinMax %d..%d", k, j);
         }
      fprintf(fp, ") -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
      for (i = 0; i < (int)Level->NumStateChanges; ++i) {
         fprintf(fp, "   StateChanges[%3d]: StateID %3d NumAnimDispatches %2d AnimDispatch %d\n", i,
                 Level->StateChanges[i].StateID,
                 Level->StateChanges[i].NumAnimDispatches,
                 Level->StateChanges[i].AnimDispatch);
         }
      fprintf(fp, "NumAnimDispatches: %d (", Level->NumAnimDispatches);
      if (Level->NumAnimDispatches > 0) {
         for (i = 0, j = 0, k = 0x7fffffff; i < (int)Level->NumAnimDispatches; ++i) {
            if (Level->AnimDispatches[i].Low > j)
               j = Level->AnimDispatches[i].Low;
            if (Level->AnimDispatches[i].Low < k)
               k = Level->AnimDispatches[i].Low;
           }
         fprintf(fp, "MinMax %d..%d", k, j);
         }
      fprintf(fp, ") -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
      for (i = 0; i < (int)Level->NumAnimDispatches; ++i) {
         fprintf(fp, "   AnimDispatches[%3d]: Low %5d  High %5d  NextAnimation %5d  NextFrame %5d\n", i,
                 Level->AnimDispatches[i].Low,
                 Level->AnimDispatches[i].High,
                 Level->AnimDispatches[i].NextAnimation,
                 Level->AnimDispatches[i].NextFrame);
         }
      for (i = 0, j = 0, k = 0x7fffffff; i < (int)Level->NumAnimCommands; ++i) {
         if (Level->AnimCommands[i].Value > j)
            j = Level->AnimCommands[i].Value;
         if (Level->AnimCommands[i].Value < k)
            k = Level->AnimCommands[i].Value;
         }
      fprintf(fp, "NumAnimCommands: %d (min %d  max %d) -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumAnimCommands, k, j);
      for (i = 0; i < (int)Level->NumAnimCommands; ++i) {
         fprintf(fp, "   AnimCommands[%4d] Value %6d (0x%04x)\n", i, Level->AnimCommands[i].Value, (bitu16)Level->AnimCommands[i].Value);
         }
      fprintf(fp, "NumMeshTrees: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumMeshTrees);
      for (i = 0; i < (int)(Level->NumMeshTrees / sizeof(tr2_meshtree)); ++i) {
         fprintf(fp, "   MeshTrees[%4d] Flags 0x%04x  x %d  y %d  z %d\n", i,
                 Level->MeshTrees[i].Flags,
                 Level->MeshTrees[i].x, Level->MeshTrees[i].y, Level->MeshTrees[i].z);
         }
      for (i = 0, j = 0, k = 0x7fffffff; i < (int)Level->NumFrames; ++i) {
         if (Level->Frames[i] > j)
            j = Level->Frames[i];
         if (Level->Frames[i] < k)
            k = Level->Frames[i];
         }
      fprintf(fp, "NumFrames: %d (min %d  max %d) -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumFrames, k, j);
      for (i = 0; i < (int)Level->NumFrames; ++i) {
         fprintf(fp, "   Frames[%6d]: %5d (0x%04x) (%6d)\n", i, Level->Frames[i], Level->Frames[i], (bit16)Level->Frames[i]);
         }
      fprintf(fp, "NumMoveables: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumMoveables);
      for (i = 0; i < (int)Level->NumMoveables; ++i) {
         fprintf(fp, "   Moveables[%3d]: ObjectID %d (0x%08x)\n", i,
                 Level->Moveables[i].ObjectID,
                 Level->Moveables[i].ObjectID);
         fprintf(fp, "            NumMeshes %d  StartingMesh %d  MeshTree %d\n",
                 Level->Moveables[i].NumMeshes,
                 Level->Moveables[i].StartingMesh,
                 Level->Moveables[i].MeshTree);
         fprintf(fp, "            FrameOffset %d  Animation %d\n",
                 Level->Moveables[i].FrameOffset,
                 Level->Moveables[i].Animation);
         }
      fprintf(fp, "NumStaticMeshes: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumStaticMeshes);
#ifdef NEVER_COMPILE_THIS
      for (i = 0; i < (int)Level->NumStaticMeshes; ++i) {
         fprintf(fp, "   StaticMeshes[%3d]: ObjectID %d  Unknown1 %d  StartingMesh %d\n", i,
                 Level->StaticMeshes[i].ObjectID, Level->StaticMeshes[i].Unknown1, Level->StaticMeshes[i].StartingMesh);
         for (j = 0; j < 13; ++j) {
            fprintf(fp, "      Unknown2[%2d]: %5d (0x%04x) (%6d)\n", j, Level->StaticMeshes[i].Unknown2[j], Level->StaticMeshes[i].Unknown2[j], (bit16)Level->StaticMeshes[i].Unknown2[j]);
            }
         }
#endif
      fprintf(fp, "NumObjectTextures: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumObjectTextures);
      for (i = 0; i < (int)Level->NumObjectTextures; ++i) {
         fprintf(fp, "   ObjectTextures[%4d]: TransparencyFlags %d  Tile %d\n", i, Level->ObjectTextures[i].TransparencyFlags, Level->ObjectTextures[i].Tile);
         for (j = 0; j < 4; ++j) {
            fprintf(fp, "                 Vertex %2d: XCoord %3d  XPixel %3d  Ycoord %3d  Ypixel %3d\n", j,
                    Level->ObjectTextures[i].Vertices[j].Xcoordinate,
                    Level->ObjectTextures[i].Vertices[j].Xpixel,
                    Level->ObjectTextures[i].Vertices[j].Ycoordinate,
                    Level->ObjectTextures[i].Vertices[j].Ypixel);
            }
         }
      fprintf(fp, "NumSpriteTextures: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumSpriteTextures);
      for (i = 0; i < (int)Level->NumSpriteTextures; ++i) {
         fprintf(fp, "   SpriteTextures[%3d]: Tile %d  X %d  Y %d\n", i,
                 Level->SpriteTextures[i].Tile,
                 Level->SpriteTextures[i].x,
                 Level->SpriteTextures[i].y);
         fprintf(fp, "                 Width %3d  Height %3d\n",
                 Level->SpriteTextures[i].Width,
                 Level->SpriteTextures[i].Height);
         fprintf(fp, "                 LeftSide %3d  TopSide %3d  RightSide %3d  BottomSide %3d\n",
                 Level->SpriteTextures[i].LeftSide,
                 Level->SpriteTextures[i].TopSide,
                 Level->SpriteTextures[i].RightSide,
                 Level->SpriteTextures[i].BottomSide);
         }
      fprintf(fp, "NumSpriteSequences: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumSpriteSequences);
      for (i = 0; i < (int)Level->NumSpriteSequences; ++i) {
         fprintf(fp, "   SpriteSequences[%2d]: ObjectID %d (0x%08x)  NegativeLength %4d  Offset %3d (0x%04x)\n",
                 i, Level->SpriteSequences[i].ObjectID, Level->SpriteSequences[i].ObjectID, Level->SpriteSequences[i].NegativeLength, Level->SpriteSequences[i].Offset, (bitu16)Level->SpriteSequences[i].Offset);
         }
      fprintf(fp, "NumCameras: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumCameras);
      for (i = 0; i < Level->NumCameras; ++i) {
         fprintf(fp, "   Cameras[%2d]: ", i);
         fprintf(fp, "X %d  Y %d  Z %d  Room %d  Index2 %d\n",
                 Level->Cameras[i].x, Level->Cameras[i].y, Level->Cameras[i].z, Level->Cameras[i].Room, Level->Cameras[i].Unknown1);
         }
      fprintf(fp, "NumSoundSources: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumSoundSources);
      for (i = 0; i < Level->NumSoundSources; ++i) {
         fprintf(fp, "   SoundSources[%d]: x %ld  y %ld  z %ld  SoundMapIndex %d  Flags 0x%04x\n", i,
                 Level->SoundSources[i].x, Level->SoundSources[i].y, Level->SoundSources[i].z,
                 Level->SoundSources[i].SoundID, Level->SoundSources[i].Flags);
         }
      fprintf(fp, "NumBoxes: %d ", Level->NumBoxes);
      {
         int MinZ0 = 999,
             MinZ1 = 999,
             MaxZ0 = -999,
             MaxZ1 = -999,
             MinX0 = 999,
             MinX1 = 999,
             MaxX0 = -999,
             MaxX1 = -999;
         for (i = 0; i < Level->NumBoxes; ++i) {
            if (Level->Boxes[i].Zmin < MinZ0)
               MinZ0 = Level->Boxes[i].Zmin;
            if (Level->Boxes[i].Zmin > MaxZ0)
               MaxZ0 = Level->Boxes[i].Zmin;
            if (Level->Boxes[i].Zmax < MinZ1)
               MinZ1 = Level->Boxes[i].Zmax;
            if (Level->Boxes[i].Zmax > MaxZ1)
               MaxZ1 = Level->Boxes[i].Zmax;
            if (Level->Boxes[i].Xmin < MinX0)
               MinX0 = Level->Boxes[i].Xmin;
            if (Level->Boxes[i].Xmin > MaxX0)
               MaxX0 = Level->Boxes[i].Xmin;
            if (Level->Boxes[i].Xmax < MinX1)
               MinX1 = Level->Boxes[i].Xmax;
            if (Level->Boxes[i].Xmax > MaxX1)
               MaxX1 = Level->Boxes[i].Xmax;
            }
         fprintf(fp, "Zmin %d/%d  Zmax %d/%d  Xmin %d/%d  Xmax %d/%d ", MinZ0, MaxZ0, MinZ1, MaxZ1, MinX0, MaxX0, MinX1, MaxX1);
      }
      for (i = 0, j = 0, k = 0x7fffffff; i < Level->NumBoxes; ++i) {
         if (Level->Boxes[i].TrueFloor > j)
            j = Level->Boxes[i].TrueFloor;
         if (Level->Boxes[i].TrueFloor < k)
            k = Level->Boxes[i].TrueFloor;
         }
      fprintf(fp, "TrueFloor %d/%d ", k, j);
      {
      int BoxFloors[2048], BoxIx = 0, jjj;
      for (i = 0, l = 0; i < Level->NumBoxes; ++i) {
         int found = FALSE;
         for (jjj = 0; jjj < BoxIx && !found; ++jjj) {
            if (Level->Boxes[i].TrueFloor == BoxFloors[jjj])
               found = TRUE;
            }
         if (!found)
            BoxFloors[BoxIx++] = Level->Boxes[i].TrueFloor;
         }
      fprintf(fp, "(%d unique TrueFloors) ", BoxIx);
      }
      for (i = l, j = 0, k = 0x7fffffff; i < Level->NumBoxes; ++i) {
         if (Level->Boxes[i].OverlapIndex > j)
            j = Level->Boxes[i].OverlapIndex;
         if (Level->Boxes[i].OverlapIndex < k)
            k = Level->Boxes[i].OverlapIndex;
         }
      fprintf(fp, "OverlapIndex %d/%d ", k, j);
      fprintf(fp, "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
      for (i = 0; i < Level->NumBoxes; ++i) {
         fprintf(fp, "   Boxes[%4d]: (Z %3d %3d, X %3d %3d)  TrueFloor %6d  OverlapIndex %5d", i,
                 Level->Boxes[i].Zmin, Level->Boxes[i].Zmax, Level->Boxes[i].Xmin, Level->Boxes[i].Xmax,
                 Level->Boxes[i].TrueFloor, Level->Boxes[i].OverlapIndex & 0x7fff);
         if (Level->Boxes[i].OverlapIndex & 0x8000)
            fprintf(fp, " | 0x8000");
         fprintf(fp, "\n");
         }
      for (i = 0, j = 0, k = 0x7fffffff; i < Level->NumOverlaps; ++i) {
         if ((Level->Overlaps[i] & 0x7fff) > j)
            j = Level->Overlaps[i] & 0x7fff;
         if ((Level->Overlaps[i] & 0x7fff) < k)
            k = Level->Overlaps[i] & 0x7fff;
         }
      if (Level->NumOverlaps == 0) {
         j = 0;
         k = 0;
         }
      fprintf(fp, "NumOverlaps: %d (min %d  max %d)", Level->NumOverlaps, k, j);
      j = 99999;
      k = 0;
      for (i = 0; i < Level->NumOverlaps; ) {
         l = 1;
         while (!(Level->Overlaps[i++] & 0x8000))
            ++l;
         if (l < j)
            j = l;
         if (l > k)
            k = l;
         }
      fprintf(fp, " (runs: shortest %d  longest %d) -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", j, k);
      for (i = 0; i < Level->NumOverlaps; ++i) {
         fprintf(fp, "   Overlaps[%4d]: %6d (0x%04x) (%5d)\n", i, Level->Overlaps[i], (bitu16)Level->Overlaps[i], Level->Overlaps[i] & 0x7fff);
         }

      fprintf(fp, "Zones: MinMax ");
      for (l = 0; l < 10; ++l) {
         for (i = 0, j = 0, k = 0x7fffffff; i < Level->NumBoxes; ++i) {
            if (Level->Zones[(l * Level->NumBoxes) + i] > j)
               j = Level->Zones[(l * Level->NumBoxes) + i];
            if (Level->Zones[(l * Level->NumBoxes) + i] < k)
               k = Level->Zones[(l * Level->NumBoxes) + i];
            }
         fprintf(fp, "%3d/%3d ", k, j);
         }
      fprintf(fp, " Num %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumBoxes);

      for (i = 0; i < Level->NumBoxes; ++i) {
        fprintf(fp, "   Zones[%4d]: ", i);
        for (j = 0; j < 10; ++j) {
           fprintf(fp, "%6d ", Level->Zones[(j * Level->NumBoxes) + i]);
           }
        fprintf(fp, "\n");
        }
      fprintf(fp, "NumAnimatedTextures: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumAnimatedTextures);
      for (i = 0; i < Level->NumAnimatedTextures; ++i) {
         fprintf(fp, "   AnimatedTextures[%2d]: %6d (0x%04x)\n", i, Level->AnimatedTextures[i], (bitu16)Level->AnimatedTextures[i]);
         }
      fprintf(fp, "NumItems: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumItems);
      for (i = 0; i < Level->NumItems; ++i) {
         fprintf(fp, "   Items[%3d]: ObjectID %d (0x%04x)  Room %d\n", i, Level->Items[i].ObjectID, Level->Items[i].ObjectID, Level->Items[i].Room);
         fprintf(fp, "        position (%5d, %5d, %5d)\n", Level->Items[i].x, Level->Items[i].y, Level->Items[i].z);
         fprintf(fp, "        Angle %d  Intensity1 %d  Intensity2 %d  Flags %d\n",
                 Level->Items[i].Angle,
                 Level->Items[i].Intensity1,
                 Level->Items[i].Intensity2,
                 Level->Items[i].Flags);
         }
      fprintf(fp, "LightMap (32 * 256) -=-=-=-=-=-=-=-=-=-=-=-\n");
      for (i = 0; i < 32; ++i) {
         fprintf(fp, "LightMap[%d]:\n", i);
         HexDump(&Level->LightMap[i * 256], 256, 0, "   ", fp);
         }
      fprintf(fp, "NumCinematicFrames: %d (%d bytes) -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumCinematicFrames, Level->NumCinematicFrames * 16);
      for (i = 0; i < Level->NumCinematicFrames; ++i) {
         fprintf(fp, "   CinematicFrames[%3d]: ", i);
         HexDump((bitu8 *)&Level->CinematicFrames[i * 8], 16, i * 16, "", fp);
         fprintf(fp, "           [%3d]: %6d %6d %6d %6d %6d %6d %6d %6d\n", i,
                 Level->CinematicFrames[(i * 8) + 0],
                 Level->CinematicFrames[(i * 8) + 1],
                 Level->CinematicFrames[(i * 8) + 2],
                 Level->CinematicFrames[(i * 8) + 3],
                 Level->CinematicFrames[(i * 8) + 4],
                 Level->CinematicFrames[(i * 8) + 5],
                 Level->CinematicFrames[(i * 8) + 6],
                 Level->CinematicFrames[(i * 8) + 7]);
         }
      fprintf(fp, "NumDemoData: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumDemoData);
      HexDump(Level->DemoData, Level->NumDemoData, 0, "   ", fp);
      fprintf(fp, "SoundMap -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
      for (i = 0; i < 370; ++i)
         fprintf(fp, "   SoundMap[%3d] = %d\n", i, Level->SoundMap[i]);

      fprintf(fp, "NumSoundDetails: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumSoundDetails);
      for (i = 0; i < Level->NumSoundDetails; ++i) {
         fprintf(fp, "   SoundDetails[%3d]: Sample %3d  Volume %d  Unknown1 %6d  Unknown2 0x%04x (flags1 %d / numsamp %d / flags2 %d)\n", i,
                 Level->SoundDetails[i].Sample, Level->SoundDetails[i].Volume, Level->SoundDetails[i].SoundRange,
                 Level->SoundDetails[i].Flags, (Level->SoundDetails[i].Flags & 0xf000) >> 24,
                 (Level->SoundDetails[i].Flags & 0x0ffc) >> 2, Level->SoundDetails[i].Flags & 0x0003);
         }
      fprintf(fp, "NumSampleIndices: %d -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", Level->NumSampleIndices);
      for (i = 0; i < Level->NumSampleIndices; ++i) {
         fprintf(fp, "   SampleIndices[%3d]: %6d (0x%08x)\n", i, Level->SampleIndices[i], Level->SampleIndices[i]);
         }

      fclose(fp);
      ReturnValue = 0;
      }
   else {
      perror(FileName);
      ReturnValue = -1;
      }
   return(ReturnValue);
}
 
 
 

Hosted by www.Geocities.ws

1