#include "xentax.h"
#include "x_file.h"
#include "x_findfile.h"
#include "x_stream.h"
#include "x_dds.h"
#include "x_smc.h"
#include "ps3_one_piece.h"

#define X_SYSTEM PS3
#define X_GAME   OnePiece

namespace X_SYSTEM { namespace X_GAME {

#define GUSELESS 0
#define GARCHIVE 1
#define G1TG0050 2
#define G1TG0060 3
#define G1CT0001 4
#define G1M_0034 5

class extractor {
 private :
  std::string pathname;
 private :
  bool debug;
  std::ofstream dfile;
 private :
  bool unpack(const std::string& filename)const;
  char getFileType(std::ifstream& ifile)const;
  bool processBIN(const std::string& filename);
  bool processG1T(binary_stream& bs, const std::string& filepath);
  bool processG1M(binary_stream& bs, const std::string& filepath);
 private :
  bool processG1MG0044(const binary_stream& bs, SIMPLEMODELCONTAINER& smc);
 public :
  bool extract(void);
 public :
  extractor(const char* pn) : pathname(pn) {}
 ~extractor() {}
};

};};

namespace X_SYSTEM { namespace X_GAME {

bool extractor::unpack(const std::string& filename)const
{
 // open file
 using namespace std;
 ifstream ifile;
 ifile.open(filename.c_str(), ios::binary);
 if(!ifile) return error("Could not open file.");

 // read header
 uint32 headMagic = BE_read_uint32(ifile); if(ifile.fail()) return error("Read failure.");
 uint32 headFiles = BE_read_uint32(ifile); if(ifile.fail()) return error("Read failure.");
 uint32 headScale = BE_read_uint32(ifile); if(ifile.fail()) return error("Read failure.");
 uint32 headUnk01 = BE_read_uint32(ifile); if(ifile.fail()) return error("Read failure.");

 // validate header
 if(headMagic != 0x077DF9) return error("Invalid header signature.");
 if(headFiles >= 0xFFFF) return error("No dynasty warriors game contains this many files.");
 if(headScale != 0x0800) return error("File assets should occur on 0x800 boundaries.");

 // read file table
 deque<pair<uint32, uint32>> filetable;
 for(size_t i = 0; i < headFiles; i++) {
     uint32 param1 = BE_read_uint32(ifile); // 0x00
     uint32 param2 = BE_read_uint32(ifile); // scaled offset
     uint32 param3 = BE_read_uint32(ifile); // filesize
     uint32 param4 = BE_read_uint32(ifile); // 0x00
     pair<uint32, uint32> p;
     p.first  = param2; if(ifile.fail()) return error("Read failure.");
     p.second = param3; if(ifile.fail()) return error("Read failure.");
     filetable.push_back(p);
    }

 // create subdirectory string
 string subdpath = GetShortFilename(filename);
 for(size_t i = 0; i < subdpath.length(); i++) if(subdpath[i] == '.') subdpath[i] = '_';

 // create save path
 string savepath = GetPathnameFromFilename(filename);
 savepath += subdpath;
 savepath += "\\";
 CreateDirectoryA(savepath.c_str(), NULL);

 // for each item in the file table
 for(size_t i = 0; i < filetable.size(); i++)
    {
     // move to file position
     size_t position = filetable[i].first*headScale;
     size_t filesize = filetable[i].second;
     ifile.seekg(position);
     if(ifile.fail()) return error("Failed to seek file position.");

     // create file
     cout << "Reading file " << (i + 1) << " of " << filetable.size() << "." << endl;
     stringstream name;
     name << savepath << setfill('0') << setw(4) << i << ".bin";
     ofstream ofile(name.str().c_str(), ios::binary);
     if(!ofile) return error("Failed to create bin file.");

     // small files
     if(filesize < 0x2000000) // 32 MB
       {
        // read data
        boost::shared_array<char> data(new char[filesize]);
        ifile.read(data.get(), filesize);
        if(ifile.fail()) return error("Read failure.");
        
        // save data
        ofile.write(data.get(), filesize);
        if(ofile.fail()) return error("Failed to write to bin file.");
       }
     // large files
     else
       {
        // create buffer
        cout << " NOTE: This is a large file and might take a while to save." << endl;
        uint32 buffersize = 0x1000000; // 16 MB
        boost::shared_array<char> data(new char[buffersize]);

        // read and copy data
        uint32 bytes_left = filesize;
        while(bytes_left)
             {
              // specify how much to read
              uint32 datasize = buffersize;
              if(bytes_left < buffersize) datasize = bytes_left;

              // read data
              ifile.read(data.get(), datasize);
              if(ifile.fail()) return error("Read failure.");
              
              // save data
              ofile.write(data.get(), datasize);
              if(ofile.fail()) return error("Failed to write to bin file.");

              // update
              bytes_left -= datasize;
             }
       }
    }

 cout << endl;
 return true;
}

char extractor::getFileType(std::ifstream& ifile)const
{
 // get filesize
 using namespace std;
 ifile.seekg(0, ios::end);
 uint32 filesize = (uint32)ifile.tellg();
 ifile.seekg(0, ios::beg);

 // read first uint32
 uint32 head01 = BE_read_uint32(ifile);
 if(ifile.fail()) {
    error("getFileType(): Read failure.");
    return GUSELESS;
   }

 // G1M_0034?
 if(head01 == 0x47314D5F) {
    uint32 head02 = BE_read_uint32(ifile);
    if(ifile.fail()) {
       error("getFileType(): Read failure.");
       return GUSELESS;
      }
    if(head02 == 0x30303334) return G1M_0034;
    return GUSELESS;
   }

 // G1TG0050 or G1TG0060?
 if(head01 == 0x47315447) {
    uint32 head02 = BE_read_uint32(ifile);
    if(ifile.fail()) {
       error("getFileType(): Read failure.");
       return GUSELESS;
      }
    if(head02 == 0x30303530) return G1TG0050;
    if(head02 == 0x30303630) return G1TG0060;
    return GUSELESS;
   }

 // G1CT0001?
 if(head01 == 0x47314354) {
    uint32 head02 = BE_read_uint32(ifile);
    if(ifile.fail()) {
       error("getFileType(): Read failure.");
       return GUSELESS;
      }
    if(head02 == 0x30303031) return G1CT0001;
    return GUSELESS;
   }

 // archive?
 if((head01 == 0) || !(head01 < 0x2FF)) return GUSELESS; // largest is 0x2D1

 // can we read all offsets?
 if(filesize < head01*sizeof(uint32)) return GUSELESS;

 // read offsets
 boost::shared_array<uint32> offsets(new uint32[head01]);
 BE_read_array(ifile, offsets.get(), head01);
 if(ifile.fail()) {
    error("getFileType(): Read failure.");
    return GUSELESS;
   }

 // check first offset
 uint32 prev = offsets[0];
 if((prev == 0) || !(prev < filesize)) return GUSELESS;

 // check other offsets
 for(uint32 i = 1; i < head01; i++) {
     if(offsets[i] == 0) return false;
     if(offsets[i] < offsets[i - 1]) return GUSELESS;
     if(!(offsets[i] < filesize)) return GUSELESS;
    }

 // move back to the beginning if it is!
 ifile.seekg(0, ios::beg);
 return GARCHIVE;
}

bool extractor::processBIN(const std::string& filename)
{
 // open file
 using namespace std;
 ifstream ifile;
 ifile.open(filename.c_str(), ios::binary);
 if(!ifile) return error("processBIN: Could not open file.");

 // is this an archive?
 char ftype = getFileType(ifile);
 if(ifile.fail()) return error("processBIN: Seek failure.");

 // compute filesize
 ifile.seekg(0, ios::end);
 if(ifile.fail()) return error("processBIN: Seek failure.");
 uint32 filesize = (uint32)ifile.tellg();
 ifile.seekg(0, ios::beg);
 if(ifile.fail()) return error("processBIN: Seek failure.");

 // depending on file type
 switch(ftype) {
   case(GUSELESS) : break;
   case(GARCHIVE) : break;
   case(G1TG0050) : {
        string filepath = GetPathnameFromFilename(filename);
        filepath += GetShortFilenameWithoutExtension(filename);
        boost::shared_array<char> data(new char[filesize]);
        ifile.read(data.get(), filesize);
        if(ifile.fail()) return error("processBin: Read failure.");
        binary_stream bs(data, filesize);
        //processG1T(bs, filepath);
        break;
       }
   case(G1TG0060) : {
        string filepath = GetPathnameFromFilename(filename);
        filepath += GetShortFilenameWithoutExtension(filename);
        boost::shared_array<char> data(new char[filesize]);
        ifile.read(data.get(), filesize);
        if(ifile.fail()) return error("processBin: Read failure.");
        binary_stream bs(data, filesize);
        //processG1T(bs, filepath);
        break;
       }
   case(G1CT0001) : {
        break;
       }
   case(G1M_0034) : {
        string filepath = GetPathnameFromFilename(filename);
        filepath += GetShortFilenameWithoutExtension(filename);
        boost::shared_array<char> data(new char[filesize]);
        ifile.read(data.get(), filesize);
        if(ifile.fail()) return error("processBin: Read failure.");
        binary_stream bs(data, filesize);
        if(!processG1M(bs, filepath)) return false;
        break;
       }
  }

/*
 // create buffer
 uint32 buffersize = 0x1000000; // 16 MB
 boost::shared_array<char> data(new char[buffersize]);

 uint32 foundModel = 0;
 uint32 foundG1TG5 = 0;
 uint32 foundG1TG6 = 0;

 // read and copy data
 uint32 bytes_left = filesize;
 while(bytes_left)
      {
       // specify how much to read
       uint32 datasize = buffersize;
       if(bytes_left < buffersize) datasize = bytes_left;

       // read data
       ifile.read(data.get(), datasize);
       if(ifile.fail()) {
          cout << "filesize, datasize = " << filesize << ", " << datasize << endl;
          return error("Read failure.");
         }
       
       // find data
       binary_stream bs(data, datasize);
       if(bs.search("G1M_0034", 8) != binary_stream::npos) foundModel++;
       if(bs.search("G1TG0050", 8) != binary_stream::npos) foundG1TG5++;
       if(bs.search("G1TG0060", 8) != binary_stream::npos) foundG1TG6++;

       // update
       bytes_left -= datasize;
      }

 if(debug && (foundModel || foundG1TG5 || foundG1TG6)) {
    dfile << filename << endl;
    if(ftype == GARCHIVE) dfile << "GARCHIVE" << endl;
    if(ftype == GUSELESS) dfile << "GUSELESS" << endl;
    if(ftype == G1M_0034) dfile << "G1M_0034" << endl;
    if(ftype == G1CT0001) dfile << "G1CT0001" << endl;
    if(ftype == G1TG0050) dfile << "G1TG0050" << endl;
    if(ftype == G1TG0060) dfile << "G1TG0060" << endl;
    if(foundModel) dfile << "Models: YES" << endl;
    if(foundG1TG5) dfile << "G1TG5s: YES" << endl;
    if(foundG1TG6) dfile << "G1TG6s: YES" << endl;
   }
*/

 return true;
}

bool extractor::processG1T(binary_stream& bs, const std::string& filepath)
{
 using namespace std;

 // create folder to save files
 CreateDirectoryA(filepath.c_str(), NULL);

 // read header
 uint32 magic = bs.BE_read_uint32();
 uint32 version = bs.BE_read_uint32();
 uint32 section_size = bs.BE_read_uint32();
 uint32 table_offset = bs.BE_read_uint32();
 uint32 n_textures = bs.BE_read_uint32();

 // validate header
 if(magic != 0x47315447) return error("Expecting G1TG section.");
 if(!(version == 0x30303530 || version == 0x30303630)) return error("Invalid G1TG version.");
 if(n_textures == 0) return error("Invalid number of textures.");

 // move to table
 bs.seek(table_offset);
 if(bs.fail()) return error("Stream seek failure.");

 // read offset table
 deque<size_t> offset_list;
 for(size_t i = 0; i < n_textures; i++) {
     offset_list.push_back(bs.BE_read_uint32());
     if(bs.fail()) return error("Stream read failure.");
    }

 // process textures
 for(size_t i = 0; i < offset_list.size(); i++)
    {
     bs.seek(table_offset + offset_list[i]);
     if(bs.fail()) return error("Stream seek failure.");

     // read texture information
     // 0x08 bytes
     uint08 b1 = bs.BE_read_uint08(); // unknown
     uint08 b2 = bs.BE_read_uint08(); // texture type
     uint08 b3 = bs.BE_read_uint08(); // dx/dy
     uint08 b4 = bs.BE_read_uint08(); // unknown
     uint16 s1 = bs.BE_read_uint16(); // unknown
     uint16 s2 = bs.BE_read_uint16(); // unknown

     // header is extended depending on s2
     // 0x0C bytes
     if(s2 == 0x1201) { // version == 0x30303630) {
        uint32 v1 = bs.BE_read_uint32(); // 12
        uint32 v2 = bs.BE_read_uint32(); // 0
        uint32 v3 = bs.BE_read_uint32(); // 0 or 1
       }

     // number of bytes in header
     uint32 headerBytes = 0x08;
     if(s2 == 0x1201) headerBytes += 0x0C;

     // compute dimensions
     uint32 temp1 = ((b3 & 0xF0) >> 4);
     uint32 temp2 = ((b3 & 0x0F));
     uint32 dx = 1 << temp1;
     uint32 dy = 1 << temp2;

     // create texture file
     stringstream filename;
     filename << filepath << setfill('0') << setw(3) << i << ".dds";
     ofstream ofile(filename.str().c_str(), ios::binary);

     // save texture
     if(b2 == 0)
       {
        // read texture
        DWORD filesize = UncompressedDDSFileSize(dx, dy, 0, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
        boost::shared_array<char> buffer(new char[filesize]);
        bs.read((char*)buffer.get(), filesize);

        // save texture
        DDS_HEADER ddsh;
        CreateUncompressedDDSHeader(dx, dy, 0, 0xFF000000, 0xFF0000, 0xFF00, 0xFF, FALSE, &ddsh);
        DWORD signature = 0x20534444;
        ofile.write((char*)&signature, sizeof(signature));
        ofile.write((char*)&ddsh, sizeof(ddsh));
        ofile.write((char*)buffer.get(), filesize);
       }
     // UNCOMPRESSED DDS (PS3 works good)
     else if(b2 == 1)
       {
        // read texture
        DWORD filesize = UncompressedDDSFileSize(dx, dy, 0, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);
        boost::shared_array<char> buffer(new char[filesize]);
        bs.read((char*)buffer.get(), filesize);

        // save texture
        DDS_HEADER ddsh;
        CreateUncompressedDDSHeader(dx, dy, 0, 0xFF000000, 0xFF0000, 0xFF00, 0xFF, FALSE, &ddsh);
        DWORD signature = 0x20534444;
        ofile.write((char*)&signature, sizeof(signature));
        ofile.write((char*)&ddsh, sizeof(ddsh));
        ofile.write((char*)buffer.get(), filesize);
       }
     // DXT1 (PS3 works good)
     else if(b2 == 6)
       {
        // read texture
        DWORD filesize = DXT1Filesize(dx, dy, 0);
        boost::shared_array<char> buffer(new char[filesize]);
        bs.read((char*)buffer.get(), filesize);

        // save texture
        DDS_HEADER ddsh;
        CreateDXT1Header(dx, dy, 0, FALSE, &ddsh);
        DWORD signature = 0x20534444;
        ofile.write((char*)&signature, sizeof(signature));
        ofile.write((char*)&ddsh, sizeof(ddsh));
        ofile.write((char*)buffer.get(), filesize);
       }
     // DXT5 (PS3 works good)
     else if(b2 == 8)
       {
        // read texture
        DWORD filesize = DXT5Filesize(dx, dy, 0);
        boost::shared_array<char> buffer(new char[filesize]);
        bs.read((char*)buffer.get(), filesize);

        // save texture
        DDS_HEADER ddsh;
        CreateDXT5Header(dx, dy, 0, FALSE, &ddsh);
        DWORD signature = 0x20534444;
        ofile.write((char*)&signature, sizeof(signature));
        ofile.write((char*)&ddsh, sizeof(ddsh));
        ofile.write((char*)buffer.get(), filesize);
       }
     else {
        stringstream msg;
        msg << "Unsupported texture type #" << (uint32)b2 << ".";
        return error(msg.str().c_str());
       }
    }

 return true;
}

bool extractor::processG1M(binary_stream& bs, const std::string& filepath)
{
 using namespace std;

 // create model container
 SIMPLEMODELCONTAINER smc;

 // create folder to save files
 CreateDirectoryA(filepath.c_str(), NULL);

 // read header
 uint32 head01 = bs.BE_read_uint32(); // magic
 uint32 head02 = bs.BE_read_uint32(); // version
 uint32 head03 = bs.BE_read_uint32(); // total section size
 uint32 head04 = bs.BE_read_uint32(); // start offset
 uint32 head05 = bs.BE_read_uint32(); // 0x00
 uint32 head06 = bs.BE_read_uint32(); // number of chunks to read

 // validate header
 if(head01 != 0x47314D5F) return error("Expecting G1M_ section.");
 if(head02 != 0x30303334) return error("Invalid G1M_ version.");
 if(head03 == 0) return error("Invalid G1M_.");
 if(head04 == 0) return error("Invalid G1M_.");

 // move to start
 bs.seek(head04);
 if(bs.fail()) return error("processG1M: Seek failure.");

 // read chunks
 for(uint32 i = 0; i < head06; i++)
    {
     // read first chunk
     uint32 chunkname = bs.BE_read_uint32(); // chunk name
     uint32 chunkvers = bs.BE_read_uint32(); // chunk version
     uint32 chunksize = bs.BE_read_uint32(); // chunk size

     // process chunk
     switch(chunkname) {
       case(0x47314D46) : { // G1MF
            bs.move(chunksize - 0x0C);
            break;
           }
       case(0x47314D53) : { // G1MS
            bs.move(chunksize - 0x0C);
            break;
           }
       case(0x47314D4D) : { // G1MM
            bs.move(chunksize - 0x0C);
            break;
           }
       case(0x47314D47) : { // G1MG
            if(!processG1MG0044(bs, smc)) return false;
            bs.move(chunksize - 0x0C);
            break;
           }
       case(0x45585452) : { // EXTR
            bs.move(chunksize - 0x0C);
            break;
           }
       default : {
            stringstream ss;
            ss << "processG1M: Unknown chunk 0x" << std::hex << chunkname << std::dec << ".";
            return error(ss.str().c_str());
           }
      }
    }

 return true;
}

bool extractor::processG1MG0044(const binary_stream& bs, SIMPLEMODELCONTAINER& smc)
{
 using namespace std;

 // copy stream
 binary_stream stream(bs);
 if(stream.fail()) return error("Cannot copy binary stream.");

 // read header
 uint32 platform = stream.BE_read_uint32();
 uint32 unknown1 = stream.BE_read_uint32();
 real32 min_x = stream.BE_read_real32();
 real32 min_y = stream.BE_read_real32();
 real32 min_z = stream.BE_read_real32();
 real32 max_x = stream.BE_read_real32();
 real32 max_y = stream.BE_read_real32();
 real32 max_z = stream.BE_read_real32();
 uint32 sections = stream.BE_read_uint32();

 // validate header
 if(platform != 0x50533300) return error("Only PS3 version of game is supported.");
 if(sections == 0) return error("Invalid number of sections.");

 // section information
 struct G1MG0044_ITEM {
  uint32 type;
  uint32 size;
  boost::shared_array<char> data;
 };

 // read section information
 deque<G1MG0044_ITEM> items;
 for(size_t i = 0; i < sections; i++) {
     G1MG0044_ITEM item;
     item.type = stream.BE_read_uint32();
     item.size = stream.BE_read_uint32();
     item.data.reset(new char[item.size - 0x8]);
     stream.read(item.data.get(), item.size - 0x8);
     items.push_back(item);
    }

 // for each section
 for(size_t i = 0; i < items.size(); i++)
    {
     // vertex section
     if(items[i].type == 0x00010004)
       {
        // binary stream from data
        binary_stream ss(items[i].data, items[i].size - 0x8);
        ss.seek(0);

        // read vertex sections
        uint32 n_meshes = ss.BE_read_uint32();
        for(size_t j = 0; j < n_meshes; j++)
           {
            // read mesh vertex info
            uint32 unknown1 = ss.BE_read_uint32();
            uint32 vertsize = ss.BE_read_uint32();
            uint32 vertices = ss.BE_read_uint32();
            uint32 unknown2 = ss.BE_read_uint32();

            // buffer name
            stringstream name;
            name << "vb_" << setfill('0') << setw(2) << i;

            // set vertex buffer properties
            VTX_BUFFER vb;
            vb.flags = 0;
            vb.name = name.str();
            vb.elem = vertices;
            vb.data.reset(new VERTEX[vertices]);

            // set vertex buffer flags
            if(vertsize == 0x10) {
               vb.flags |= VERTEX_POSITION;
               vb.flags |= VERTEX_NORMAL;
               vb.flags |= VERTEX_UV;
              }
            else if(vertsize == 0x14) {
               vb.flags |= VERTEX_POSITION;
               vb.flags |= VERTEX_NORMAL;
               vb.flags |= VERTEX_UV;
              }
            else if(vertsize == 0x18) {
               vb.flags |= VERTEX_POSITION;
               vb.flags |= VERTEX_NORMAL;
              }
            else if(vertsize == 0x1C) {
               vb.flags |= VERTEX_POSITION;
               vb.flags |= VERTEX_NORMAL;
               vb.flags |= VERTEX_UV;
              }
            else if(vertsize == 0x20) {
               vb.flags |= VERTEX_POSITION;
               vb.flags |= VERTEX_NORMAL;
               vb.flags |= VERTEX_UV;
              }
            else if(vertsize == 0x24) {
               vb.flags |= VERTEX_POSITION;
               vb.flags |= VERTEX_NORMAL;
               vb.flags |= VERTEX_UV;
              }
            else
               return error("Unknown vertex format.");

            // read vertices
            for(size_t k = 0; k < vertices; k++)
               {
                VERTEX vertex;
                if(vertsize == 0x10) {
                   vertex.vx = ss.BE_read_real16();
                   vertex.vy = ss.BE_read_real16();
                   vertex.vz = ss.BE_read_real16();
                   ss.BE_read_real16();
                   vertex.nx = ss.BE_read_real16();
                   vertex.ny = ss.BE_read_real16();
                   vertex.nz = ss.BE_read_real16();
                   ss.BE_read_real16();
                  }
                else if(vertsize == 0x14) {
                   vertex.vx = ss.BE_read_real16();
                   vertex.vy = ss.BE_read_real16();
                   vertex.vz = ss.BE_read_real16();
                   ss.BE_read_real16();
                   vertex.nx = ss.BE_read_real16();
                   vertex.ny = ss.BE_read_real16();
                   vertex.nz = ss.BE_read_real16();
                   ss.BE_read_real16();
                   vertex.tu = ss.BE_read_real16();
                   vertex.tv = ss.BE_read_real16();
                  }
                else if(vertsize == 0x18) {
                   vertex.vx = ss.BE_read_real32();
                   vertex.vy = ss.BE_read_real32();
                   vertex.vz = ss.BE_read_real32();
                   vertex.nx = ss.BE_read_real16();
                   vertex.ny = ss.BE_read_real16();
                   vertex.nz = ss.BE_read_real16();
                   ss.BE_read_real16();
                   ss.BE_read_real32();
                  }
                else if(vertsize == 0x1C) {
                   vertex.vx = ss.BE_read_real32();
                   vertex.vy = ss.BE_read_real32();
                   vertex.vz = ss.BE_read_real32();
                   vertex.nx = ss.BE_read_real16();
                   vertex.ny = ss.BE_read_real16();
                   vertex.nz = ss.BE_read_real16();
                   ss.BE_read_real16();
                   ss.BE_read_real32();
                   vertex.tu = ss.BE_read_real16();
                   vertex.tv = ss.BE_read_real16();
                  }
                else if(vertsize == 0x20) {
                   vertex.vx = ss.BE_read_real32();
                   vertex.vy = ss.BE_read_real32();
                   vertex.vz = ss.BE_read_real32();
                   vertex.nx = ss.BE_read_real16();
                   vertex.ny = ss.BE_read_real16();
                   vertex.nz = ss.BE_read_real16();
                   ss.BE_read_real16();
                   ss.BE_read_real32();
                   ss.BE_read_real16();
                   ss.BE_read_real16();
                   vertex.tu = ss.BE_read_real16();
                   vertex.tv = ss.BE_read_real16();
                  }
                else if(vertsize == 0x24) {
                   vertex.vx = ss.BE_read_real32();
                   vertex.vy = ss.BE_read_real32();
                   vertex.vz = ss.BE_read_real32();
                   vertex.nx = ss.BE_read_real32();
                   vertex.ny = ss.BE_read_real32();
                   vertex.nz = ss.BE_read_real32();
                   ss.BE_read_real32();
                   vertex.tu = ss.BE_read_real32();
                   vertex.tv = ss.BE_read_real32();
                  }

                // save vertex
                vb.data[k] = vertex;
               }
           }
       }
     // index buffer section
     else if(items[i].type == 0x00010007)
       {
        // binary stream from data
        binary_stream ss(items[i].data, items[i].size - 0x8);
        ss.seek(0);

        // read face sections
        uint32 n_meshes = ss.BE_read_uint32();
        uint32 vb_index = 0;
        for(size_t j = 0; j < n_meshes; j++)
           {
            // read face data
            uint32 numindex = ss.BE_read_uint32();
            uint32 datatype = ss.BE_read_uint32();
            uint32 unknown1 = ss.BE_read_uint32();

            // set index buffer properties
            IDX_BUFFER ib;
            ib.type = FACE_TYPE_TRISTRIP;
            ib.elem = numindex;
            if(datatype == 0x10) ib.format = FACE_FORMAT_UINT_16;
            else if(datatype == 0x20) ib.format = FACE_FORMAT_UINT_32;
            else return error("Unknown index buffer data format.");

            // set index buffer name
            stringstream surface;
            surface << "surface_" << setfill('0') << setw(3) << j << ends;
            ib.name = surface.str();

            // determine index buffer data type size
            unsigned int typesize = 0;
            if(datatype == 0x10) typesize = sizeof(uint16);
            else if(datatype == 0x20) typesize = sizeof(uint32);
            else return error("Unknown index buffer data type.");

            // read face data
            unsigned int total_bytes = ib.elem*typesize;
            ib.data.reset(new char[total_bytes]);
            if(ib.format == FACE_FORMAT_UINT_16) ss.BE_read_array(reinterpret_cast<uint16*>(ib.data.get()), ib.elem);
            else if(ib.format == FACE_FORMAT_UINT_32) ss.BE_read_array(reinterpret_cast<uint32*>(ib.data.get()), ib.elem);

            // test face data
            uint32 min_index = 0;
            uint32 max_index = 0;
            if(ib.format == FACE_FORMAT_UINT_16) {
               //uint16 a; minimum(reinterpret_cast<uint16*>(ib.data.get()), ib.elem, a);
               //uint16 b; maximum(reinterpret_cast<uint16*>(ib.data.get()), ib.elem, b);
               //min_index = a;
               //max_index = b;
              }
            else if(ib.format == FACE_FORMAT_UINT_32) {
               //uint32 a; minimum(reinterpret_cast<uint32*>(ib.data.get()), ib.elem, a);
               //uint32 b; maximum(reinterpret_cast<uint32*>(ib.data.get()), ib.elem, b);
               //min_index = a;
               //max_index = b;
              }
            cout << " min index = " << min_index << endl;
            cout << " max index = " << max_index << endl;

            // set vertex buffer reference
            //if(min_index == 0) vb_index++; // there has got to be a better way
            //if(vb_index > 0) ib.reference = vb_index - 1;
            //else return error("Unexpected vertex buffer reference.");

            // save face data
            //fdlist.push_back(ib);
           }
       }
    }

 return true;
}

bool extractor::extract(void)
{
 using namespace std;
 debug = true;

 bool unpackA = false;
 bool unpackB = false;
 bool unpackC = false;
 bool doBin = true;

 cout << "STAGE 1" << endl;
 if(unpackA) {
    cout << "Unpacking .A files..." << endl;
    deque<string> filelist;
    BuildFilenameList(filelist, ".A", pathname.c_str());
    for(size_t i = 0; i < filelist.size(); i++) {
        cout << "Processing file " << (i + 1) << " of " << filelist.size() << ": " << filelist[i] << "." << endl;
        if(!unpack(filelist[i])) return false;
       }
    cout << endl;
   }

 cout << "STAGE 2" << endl;
 if(unpackB) {
    cout << "Unpacking .B files..." << endl;
    deque<string> filelist;
    BuildFilenameList(filelist, ".B", pathname.c_str());
    for(size_t i = 0; i < filelist.size(); i++) {
        cout << "Processing file " << (i + 1) << " of " << filelist.size() << ": " << filelist[i] << "." << endl;
        if(!unpack(filelist[i])) return false;
       }
    cout << endl;
   }

 cout << "STAGE 3" << endl;
 if(unpackC) {
    cout << "Unpacking .C files..." << endl;
    deque<string> filelist;
    BuildFilenameList(filelist, ".C", pathname.c_str());
    for(size_t i = 0; i < filelist.size(); i++) {
        cout << "Processing file " << (i + 1) << " of " << filelist.size() << ": " << filelist[i] << "." << endl;
        if(!unpack(filelist[i])) return false;
       }
    cout << endl;
   }

 cout << "STAGE 4" << endl;
 if(doBin) {
    cout << "Analyzing .bin files..." << endl;
    deque<string> filelist;
    BuildFilenameList(filelist, ".bin", pathname.c_str());
    for(size_t i = 0; i < filelist.size(); i++) {
        cout << "Processing file " << (i + 1) << " of " << filelist.size() << ": " << filelist[i] << "." << endl;
        if(!processBIN(filelist[i])) return false;
       }
    cout << endl;
   }

 return true;
}

};};

namespace X_SYSTEM { namespace X_GAME {

bool extract(void)
{
 char pathname[MAX_PATH];
 GetModulePathname(pathname, MAX_PATH);
 return extract(pathname);
}

bool extract(const char* pathname)
{
 return extractor(pathname).extract();
}

};};