#include "xentax.h"
#include "x_file.h"
#include "x_findfile.h"
#include "x_dds.h"
#include "x_smc.h"
#include "x_lwo.h"
#include "ps3_touchshot.h"

#define X_SYSTEM PS3
#define X_GAME   TOUCHSHOT

namespace X_SYSTEM { namespace X_GAME {

class extractor {
 private :
  std::string pathname;
 private :
  bool processCAT(const std::string& filename);
  bool processCAN(const std::string& filename);
  bool processGTF(const std::string& filename);
  bool processTMD(const std::string& filename);
 public :
  bool extract(void);
 public :
  extractor(const char* pn) : pathname(pn) {}
 ~extractor() {}
};

};};

namespace X_SYSTEM { namespace X_GAME {

bool extractor::extract(void)
{
 using namespace std;
 bool doCAT = true;
 bool doCAN = true;
 bool doGTF = true;
 bool doTMD = true;

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

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

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

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

 return true;
}

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

 // read header
 std::deque<std::pair<size_t, size_t>> header;
 for(;;) {
     uint32 p01 = BE_read_uint32(ifile); if(ifile.fail()) return error("Read failure.");
     uint32 p02 = BE_read_uint32(ifile); if(ifile.fail()) return error("Read failure.");
     if(p01 == 0xFFFFFFFF && p02 == 0xFFFFFFFF) break;
     if(header.size() > 1000) return error("Unexpected number of CAT sections.");
     header.push_back(std::pair<size_t, size_t>(p01, p02));
    }

 // create directory to store files
 string pakpath = GetPathnameFromFilename(filename);
 pakpath += GetShortFilenameWithoutExtension(filename);
 pakpath += "\\";
 CreateDirectoryA(pakpath.c_str(), NULL);

 // process files
 for(size_t i = 0; i < header.size(); i++)
    {
     // move to data
     ifile.seekg(header[i].first);
     if(ifile.fail()) return error("Seek failure.");
     if(header[i].second == 0) continue;

     // read data
     boost::shared_array<char> data(new char[header[i].second]);
     ifile.read(data.get(), header[i].second);
     if(ifile.fail()) return error("Read failure.");

     // create filename
     stringstream ss;
     ss << pakpath << setfill('0') << setw(3) << i << ".";
     uint32 value = *reinterpret_cast<uint32*>(data.get());
     switch(value) {
       case(0x746D6430) : ss << "tmd"; break;
       case(0x746D6F30) : ss << "tmo"; break;
       case(0xFF000202) : ss << "gtf"; break;
       case(0xFF010102) : ss << "gtf"; break;
       default : ss << "tmp";
      }

     // save data
     ofstream ofile(ss.str().c_str(), ios::binary);
     ofile.write(data.get(), header[i].second);
    }

 return true;
}

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

 // filesize
 ifile.seekg(0, ios::end);
 size_t filesize = (size_t)ifile.tellg();
 ifile.seekg(0, ios::beg);

 // read header
 struct triple { size_t offset; size_t size; string name; };
 std::deque<triple> header;
 size_t position = 0;
 for(;;)
    {
     // seek position
     ifile.seekg(position);
     if(ifile.fail()) return error("Seek failure.");

     // read item
     uint32 p01 = BE_read_uint32(ifile); if(ifile.fail()) return error("Read failure.");
     uint32 p02 = BE_read_uint32(ifile); if(ifile.fail()) return error("Read failure.");
     if(p01 == 0xFFFFFFFF && p02 == 0xFFFFFFFF) break;

     // name follows
     bool is_name = false;
     uint32 namelen = 0;
     if(p01 < p02) {
        if((p02 - p01) == 0x10) { namelen = 0x10; is_name = true; }
        else if((p02 - p01) == 0x20) { namelen = 0x20; is_name = true; }
        else if((p02 - p01) == 0x30) { namelen = 0x30; is_name = true; }
        else if(p02 == 0xFFFFFFFF) { namelen = filesize - p01; is_name = true; }
       }

     // extract name
     if(is_name) {
        ifile.seekg(p01);
        char buffer[1024];
        read_string(ifile, buffer, 1024);
        header.back().name = buffer;
       }
     else {
        triple item;
        item.offset = p01;
        item.size = p02;
        header.push_back(item);
       }

     // move position
     if(is_name) position += 4;
     else position += 8;
    }

 // create directory to store files
 string sendpath = GetPathnameFromFilename(filename);
 sendpath += GetShortFilenameWithoutExtension(filename);
 sendpath += "\\";
 CreateDirectoryA(sendpath.c_str(), NULL);

 // process files
 for(size_t i = 0; i < header.size(); i++)
    {
     // move to data
     ifile.seekg(header[i].offset);
     if(ifile.fail()) return error("Seek failure.");
     if(header[i].size == 0) continue;

     // read data
     boost::shared_array<char> data(new char[header[i].size]);
     ifile.read(data.get(), header[i].size);
     if(ifile.fail()) return error("Read failure.");

     // create filename
     stringstream ss;
     if(header[i].name.length())
        ss << sendpath << header[i].name;
     else {
        ss << sendpath << setfill('0') << setw(3) << i << ".";
        uint32 value = *reinterpret_cast<uint32*>(data.get());
        switch(value) {
          case(0x746D6430) : ss << "tmd"; break;
          case(0x746D6F30) : ss << "tmo"; break;
          case(0xFF000202) : ss << "gtf"; break;
          case(0xFF010102) : ss << "gtf"; break;
          default : ss << "tmp";
         }
       }

     // save data
     ofstream ofile(ss.str().c_str(), ios::binary);
     ofile.write(data.get(), header[i].size);
    }

 return true;
}

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

 // filesize
 ifile.seekg(0, ios::end);
 size_t filesize = (size_t)ifile.tellg();
 ifile.seekg(0, ios::beg);

 // 0x020101FF (magic)
 // 0x000FD200 (filesize) 1036800
 // 0x00000001 (number of files)
 // 0x00000000

 // 0x020101FF
 // 00000080 000FD200 A6-01-0200 0000AAE4 0780-0438 00010000 00000F00 00000000 00000000 (x*y/2 = filesize)
 // usually x*y*4 = filesize
 // so filesize is shrunk by 8!
 // so instead of 32 bits per pixel this is 4 bits per pixel!

 // 0x020200FF
 // 00000100 00080000 86-01-0200 0000AAE4 0400-0400 00010000 00000000 00000000 00000001
 // 00080100 00010000 88-01-0200 0000AAE4 0100-0100 00010000 00000000 00000000 00000002
 // 00090100 00000800 86-01-0200 0000AAE4 0040-0040 00010000 00000000 00000000 00000003
 // 00090900 00100000 88-01-0200 0000AAE4 0400-0400 00010000 00000000 00000000 00000004
 // 00190900 00008000 86-01-0200 0000AAE4 0100-0100 00010000 00000000 00000000 00000005
 // 00198900 00000800 86-01-0200 0000AAE4 0040-0040 00010000 00000000 00000000 00000000

 struct GTFITEM {
  uint32 offset;
  uint32 filesize;
  uint08 unk01;
  uint08 unk02; // number of mipmap levels including top level
  uint16 unk03;
  uint32 unk04; // 0xAAE4
  uint16 dx;
  uint16 dy;
  uint32 unk05; // 0x10000
  uint32 unk06; // 0x00
  uint32 unk07; // 0x00
  uint32 index;
 };

 // read header
 uint32 magic = BE_read_uint32(ifile);
 uint32 datasize = BE_read_uint32(ifile);
 uint32 numfiles = BE_read_uint32(ifile);
 BE_read_uint32(ifile);

 // read file information
 deque<GTFITEM> gtflist;
 for(uint32 i = 0; i < numfiles; i++) {
     GTFITEM item = {
      BE_read_uint32(ifile),
      BE_read_uint32(ifile),
      BE_read_uint08(ifile),
      BE_read_uint08(ifile),
      BE_read_uint16(ifile),
      BE_read_uint32(ifile),
      BE_read_uint16(ifile),
      BE_read_uint16(ifile),
      BE_read_uint32(ifile),
      BE_read_uint32(ifile),
      BE_read_uint32(ifile),
      BE_read_uint32(ifile),
     };
     gtflist.push_back(item);
    }

 // create directory to store files
 string savepath = GetPathnameFromFilename(filename);
 savepath += GetShortFilenameWithoutExtension(filename);
 savepath += "\\";
 CreateDirectoryA(savepath.c_str(), NULL);

 // read file
 for(uint32 i = 0; i < numfiles; i++)
    {
     // compute bits per pixel
     if(gtflist[i].dx == 0 || gtflist[i].dy == 0) return error("Invalid image dimensions.");
     uint32 bpp = 8*gtflist[i].filesize/(gtflist[i].dx*gtflist[i].dy);

     // move to offset
     ifile.seekg(gtflist[i].offset);
     if(ifile.fail()) { error("Seek failure."); continue; }

     // read data
     uint32 ofsize = gtflist[i].filesize;
     boost::shared_array<char> ofdata(new char[ofsize]);
     BE_read_array(ifile, ofdata.get(), ofsize);
     if(ifile.fail()) { error("Read failure."); continue; }

     // DDS format
     uint32 format = gtflist[i].unk01;
     uint32 mipmap = gtflist[i].unk02 - 1;

     // create DDS header
     DDS_HEADER ddsh;
     if(format == 134 || format == 166) CreateDXT1Header(gtflist[i].dx, gtflist[i].dy, mipmap, FALSE, &ddsh);
     else if(format == 135 || format == 136 || format == 168) CreateDXT5Header(gtflist[i].dx, gtflist[i].dy, mipmap, FALSE, &ddsh);
     else if(format == 190) CreateUncompressedDDSHeader(gtflist[i].dx, gtflist[i].dy, 0, 0xFF00, 0xFF0000, 0xFF000000, 0xFF, FALSE, &ddsh);
     else return error("Unknown DDS format.");

     // save data
     stringstream ofname;
     ofname << savepath << setfill('0') << setw(3) << i << ".dds";
     SaveDDSFile(ofname.str().c_str(), ddsh, ofdata, ofsize);
    }

 return true;
}

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

 // filesize
 ifile.seekg(0, ios::end);
 size_t filesize = (size_t)ifile.tellg();
 ifile.seekg(0, ios::beg);

 // variables
 string fn_path = GetPathnameFromFilename(filename);
 string fn_name = GetShortFilenameWithoutExtension(filename);

 // enable debug mode
 bool debug = true;
 ofstream dfile;
 if(debug) {
    stringstream ss;
    ss << fn_path << fn_name << ".txt";
    dfile.open(ss.str().c_str());
   }

 //
 // PROCESS #00
 // INITIALIZE MODEL CONTAINER
 //

 SIMPLEMODELCONTAINER smc;
 smc.vbuffer.flags = 0;
 smc.vbuffer.name = "mesh";
 smc.vbuffer.elem = 0;

 //
 // PROCESS #01
 // READ AND PROCESS FILE HEADER
 //

 // read header
 auto head01 = BE_read_uint32(ifile);
 auto head02 = BE_read_uint32(ifile);
 auto head03 = BE_read_uint32(ifile);
 auto head04 = BE_read_uint32(ifile);
 auto head05 = BE_read_real32(ifile);
 auto head06 = BE_read_real32(ifile);
 auto head07 = BE_read_real32(ifile);
 auto head08 = BE_read_real32(ifile);
 auto head09 = BE_read_real32(ifile);
 auto head10 = BE_read_real32(ifile);
 if(debug) dfile << "HEADER" << endl;
 if(debug) dfile << head01 << endl;
 if(debug) dfile << head02 << endl;
 if(debug) dfile << head03 << endl;
 if(debug) dfile << head04 << endl;
 if(debug) dfile << head05 << endl;
 if(debug) dfile << head06 << endl;
 if(debug) dfile << head07 << endl;
 if(debug) dfile << head08 << endl;
 if(debug) dfile << head09 << endl;
 if(debug) dfile << head10 << endl;
 if(debug) dfile << endl;

 // validate header
 if(head01 != 0x30646D74) return error("Invalid MDL file.");
 if(head04 != 0x28) return error("Invalid MDL file.");

 // determine vertex size
 uint32 vertexsize = 0;
 uint08 indextype = FACE_FORMAT_UINT_16;
 uint08 indexsize = 2;
 switch(head02) {
   case(0x01A7) : vertexsize = 0x0C; cout << "0x01A7" << endl; break; // OK
   case(0x0905) : vertexsize = 0x0C; cout << "0x0905" << endl; break; // OK
   case(0x0925) : vertexsize = 0x0C; cout << "0x0925" << endl; break; // OK
   case(0x09A3) : vertexsize = 0x0C; cout << "0x09A3" << endl; break; // OK
   case(0x09A7) : vertexsize = 0x0C; cout << "0x09A7" << endl; break; // OK
   case(0x09AF) : vertexsize = 0x0C; cout << "0x09AF" << endl; break; // OK
   case(0x09BF) : vertexsize = 0x0C; cout << "0x09BF" << endl; break; // OK
   case(0x09E7) : vertexsize = 0x20; cout << "0x09E7" << endl; break; // OK
   default : {
        stringstream msg;
        msg << "Unknown vertex type " << head02 << ".";
        return error(msg.str().c_str());
       }
  }

 //
 // PROCESS #02
 // READ AND SAVE FILE INFORMATION
 //

 if(debug) dfile << "FILE INFORMATION" << std::endl;
 uint32 fileinfo[30];
 for(size_t i = 0; i < 30; i++) fileinfo[i] = BE_read_uint32(ifile);
 if(debug) {
    // 0x20
    dfile << "offset to surface data: " << fileinfo[0] << endl;
    dfile << "number of surface data items: " << fileinfo[1] << endl;
    // 0x30
    dfile << "offset to surface info : " << fileinfo[2] << endl;
    dfile << "number of surface info items: " << fileinfo[3] << endl;
    dfile << "always 0: " << fileinfo[4] << endl;
    dfile << "always 0: " << fileinfo[5] << endl;
    // 0x40
    dfile << "offset to unknown surface info: " << fileinfo[6] << endl;
    dfile << "number of unknown surface info items: " << fileinfo[7] << endl; // each item is 0x84 bytes
    dfile << "offset to material info: " << fileinfo[8] << endl;
    dfile << "number of material info items: " << fileinfo[9] << endl;
    // 0x50
    dfile << "offset to triangle group info: " << fileinfo[10] << endl;
    dfile << "number of triangle group info items: " << fileinfo[11] << endl;
    dfile << "offset to index buffer: " << fileinfo[12] << endl;
    dfile << "number of triangles: " << fileinfo[13] << endl;
    // 0x60
    dfile << "number of vertices: " << fileinfo[14] << endl;
    dfile << "offset to vertex buffer: " << fileinfo[15] << endl;
    dfile << "offset to unknown data: " << fileinfo[16] << endl; // 0x01A7, 0x09A3, 0x09A7, 0x09AF, 0x09BF, 0x09E7
    dfile << "offset to separate UV data: " << fileinfo[17] << endl; // 0x01A7, 0x0905, 0x0925, 0x09A7, 0x09AF, 0x09BF, 0x09E7
    // 0x70
    dfile << "offset to unknown data: " << fileinfo[18] << endl; // 0x09AF, 0x09BF 
    dfile << "offset to unknown data: " << fileinfo[19] << endl; // 0x09BF
    dfile << "offset to unknown data: " << fileinfo[20] << endl; // 0x01A7, 0x0925, 0x09A3, 0x09A7, 0x09AF, 0x09BF, 0x09E7
    dfile << "offset to unknown data: " << fileinfo[21] << endl; // 0x0905, 0x0925, 0x09A3, 0x09A7, 0x09AF, 0x09BF, 0x09E7
    // 0x80
    dfile << "offset to unknown data: " << fileinfo[22] << endl; // 0x0905, 0x0925, 0x09A3, 0x09A7, 0x09AF, 0x09BF, 0x09E7
    dfile << "offset to joint matrix: " << fileinfo[23] << endl;
    dfile << "number of joint matrix items: " << fileinfo[24] << endl;
    dfile << "offset to joint data: " << fileinfo[25] << endl;
    // 0x90
    dfile << "number of joint items: " << fileinfo[26] << endl;
    dfile << "offset to texture data: " << fileinfo[27] << endl;
    dfile << "number of texture items: " << fileinfo[28] << endl;
    dfile << "always 0: " << fileinfo[29] << endl;
    dfile << std::endl;
   }

 //
 // PROCESS #03
 // READ SURFACE GROUP DATA
 //

 struct SURFACEGROUP {
  real32 bbox_xmin;
  real32 bbox_ymin;
  real32 bbox_zmin;
  real32 bbox_xmax;
  real32 bbox_ymax;
  real32 bbox_zmax;
  uint32 index; // 0, 6, 10, 14, 18, 22
  uint16 elem;  // 6, 4,  4,  4,  4,  4 (22 + 4 = 26 texture associations)
  uint16 type;  // 1, 1,  1,  1,  1,  1
 };
 deque<SURFACEGROUP> surface_data;

 // move to surface information
 if(fileinfo[0])
   {
    if(debug) dfile << "SURFACE GROUPS" << endl;
    ifile.seekg(fileinfo[0]);
    if(ifile.fail()) return error("Seek failure.");

    // process surfaces
    uint32 n_groups = fileinfo[1];
    if(n_groups == 0 || n_groups > 1000) return error("Unexpected number of surfaces.");
    if(debug) dfile << "Number of groups = " << n_groups << std::endl;

    // process surfaces
    for(uint32 i = 0; i < n_groups; i++)
       {
        if(debug) dfile << "SURFACE GROUP " << i << endl;
        auto param01 = BE_read_real32(ifile);
        auto param02 = BE_read_real32(ifile);
        auto param03 = BE_read_real32(ifile);
        auto param04 = BE_read_real32(ifile);
        auto param05 = BE_read_real32(ifile);
        auto param06 = BE_read_real32(ifile);
        if(debug) dfile << "box_min_x = " << param01 << endl;
        if(debug) dfile << "box_min_y = " << param02 << endl;
        if(debug) dfile << "box_min_z = " << param03 << endl;
        if(debug) dfile << "box_max_x = " << param04 << endl;
        if(debug) dfile << "box_max_y = " << param05 << endl;
        if(debug) dfile << "box_max_z = " << param06 << endl;
   
        // read index
        auto param07 = BE_read_uint32(ifile);
        if(debug) dfile << "index = " << param07 << endl;
   
        // read other parameters
        auto param08 = BE_read_uint16(ifile);
        auto param09 = BE_read_uint16(ifile);
        if(debug) dfile << "elem = " << param08 << endl;
        if(debug) dfile << "type = " << param09 << endl;
        if(debug) dfile << endl;

        // set surface information
        SURFACEGROUP sg;
        sg.bbox_xmin = param01;
        sg.bbox_ymin = param02;
        sg.bbox_zmin = param03;
        sg.bbox_xmax = param04;
        sg.bbox_ymax = param05;
        sg.bbox_zmax = param06;
        sg.index = param07;
        sg.elem = param08;
        sg.type = param09;
        surface_data.push_back(sg);
       }
   }

 //
 // PROCESS #04
 // READ SURFACE-TEXTURE ASSOCIATION DATA
 //

 struct TEXTASSINFO {
  uint32 material_index;
  uint32 trigroup_index;
 };
 deque<TEXTASSINFO> textass_data;

 // There are 6 section groups for this data
 // There are 6 textures for this data.
 // There are 3 materials for this data.
 // There are 7 unknown surface information items for this data.
 // There are 7 triangle groups for this data.
 // 0x20XX is a material reference index.
 // 0x30XX is a triangle group reference index.
 // 0x40XX is a ??? reference index.
 // 0x2000 0x4000 0x3000 0x4001 0x3001 0x1000
 // 0x2001 0x4002 0x3002 0x1000
 // 0x2001 0x4003 0x3003 0x1000
 // 0x2001 0x4004 0x3004 0x1000
 // 0x2001 0x4005 0x3005 0x1000
 // 0x2002 0x4006 0x3006 0x1000

 if(fileinfo[2])
   {
    // move to surface information
    if(debug) dfile << "SURFACE-MATERIAL ASSOCIATION" << endl;
    ifile.seekg(fileinfo[2]);
    if(ifile.fail()) return error("Seek failure.");
   
    // read surface information
    for(size_t i = 0; i < surface_data.size(); i++)
       {
        // read group information
        boost::shared_array<uint16> data(new uint16[surface_data[i].elem]);
        BE_read_array(ifile, data.get(), surface_data[i].elem);
        if(ifile.fail()) return error("Read failure.");
   
        uint16 curr_0x2000 = 0xFFFF;
        uint16 curr_0x3000 = 0xFFFF;
        uint16 curr_0x4000 = 0xFFFF;
   
        // for each item read
        for(size_t j = 0; j < surface_data[i].elem; j++)
           {
            if(data[j] == 0x1000)
              {
               curr_0x2000 = 0xFFFF;
               curr_0x3000 = 0xFFFF;
               curr_0x4000 = 0xFFFF;
               if(debug) dfile << "0x1000" << endl;
              }
            else if((data[j] & 0xFF00) == 0x2000)
              {
               curr_0x2000 = (uint16)(data[j] & 0x00FF);
               if(debug) dfile << "0x" << hex << data[j] << " ";
              }
            else if((data[j] & 0xFF00) == 0x3000)
              {
               curr_0x3000 = (uint16)(data[j] & 0x00FF);
               if(debug) dfile << "0x" << hex << data[j] << " ";
               TEXTASSINFO info = { curr_0x2000, curr_0x3000 };
               textass_data.push_back(info);
              }
            else if((data[j] & 0xFF00) == 0x4000)
              {
               curr_0x4000 = (uint16)(data[j] & 0x00FF);
               if(debug) dfile << "0x" << hex << data[j] << " ";
              }
           }
       }
    if(debug) dfile << dec << endl;
    if(debug) dfile << "Number of material-trigroup associations = " << textass_data.size() << endl;
    if(debug) for(size_t i = 0; i < textass_data.size(); i++)  dfile << textass_data[i].material_index << "," << textass_data[i].trigroup_index << endl;
    if(debug) dfile << endl;
   }

 //
 // PROCESS #05
 // READ UNKNOWN SURFACE INFORMATION
 //

 if(fileinfo[6] != 0)
   {
    // move to surface information
    if(debug) dfile << "UNKNOWN SURFACE INFORMATION" << endl;
    ifile.seekg(fileinfo[6]);
    if(ifile.fail()) return error("Seek failure.");
   
    // read data
    uint32 n_items = fileinfo[7];
    if(debug) dfile << "Number of items = " << n_items << endl;
    for(uint32 i = 0; i < n_items; i++) {
        for(size_t j = 0; j < 132; j++) if(debug) dfile << setw(4) << (uint16)BE_read_uint08(ifile) << " ";
        if(debug) dfile << endl;
       }
    if(debug) dfile << endl;
   }

 //
 // PROCESS #06
 // READ MATERIALS
 //

 if(fileinfo[8] != 0)
   {
    // move to surface information
    if(debug) dfile << "MATERIALS" << endl;
    ifile.seekg(fileinfo[8]);
    if(ifile.fail()) return error("Seek failure.");
    
    // read data
    uint32 n_materials = fileinfo[9];
    if(debug) dfile << "Number of materials = " << n_materials << endl;

    for(uint32 i = 0; i < n_materials; i++)
       {
        // read material
        auto p01 = BE_read_uint32(ifile); // 0x05
        auto p02 = BE_read_uint32(ifile); // 0x00
        auto p03 = BE_read_uint32(ifile); // base texture index
        auto p04 = BE_read_real32(ifile); // crap
        auto p05 = BE_read_real32(ifile); // crap
        auto p06 = BE_read_real32(ifile); // crap
        auto p07 = BE_read_real32(ifile); // crap
        auto p08 = BE_read_uint32(ifile); // 0xFFFFFFFF
        auto p09 = BE_read_uint32(ifile); // 0xFFFFFFFF
        auto p10 = BE_read_real32(ifile); // crap
        auto p11 = BE_read_real32(ifile); // crap
        auto p12 = BE_read_real32(ifile); // crap
        auto p13 = BE_read_real32(ifile); // crap
        auto p14 = BE_read_uint32(ifile); // 0xFFFFFFFF
        auto p15 = BE_read_uint32(ifile); // 0xFFFFFFFF
        auto p16 = BE_read_real32(ifile); // crap
        auto p17 = BE_read_real32(ifile); // crap
        auto p18 = BE_read_real32(ifile); // crap
        auto p19 = BE_read_real32(ifile); // std::endl of 3 4xfloats
        auto p20 = BE_read_uint32(ifile); // 0x03
        auto p21 = BE_read_uint32(ifile); // normal map index
        auto p22 = BE_read_real32(ifile); // crap
        auto p23 = BE_read_real32(ifile); // crap
        auto p24 = BE_read_real32(ifile); // crap
        auto p25 = BE_read_real32(ifile); // crap
        auto p26 = BE_read_uint32(ifile); // 0x04
        auto p27 = BE_read_uint32(ifile); // specular map index
        auto p28 = BE_read_real32(ifile); // crap
        auto p29 = BE_read_real32(ifile); // crap
        auto p30 = BE_read_real32(ifile); // crap
        auto p31 = BE_read_real32(ifile); // crap
        auto p32 = BE_read_uint32(ifile); // 0x01
        auto p33 = BE_read_uint32(ifile); // 0x00
        auto p34 = BE_read_uint32(ifile); // 0x01
        auto p35 = BE_read_uint32(ifile); // 0x01
        auto p36 = BE_read_uint32(ifile); // 0x01
        auto p37 = BE_read_real32(ifile); // crap
        auto p38 = BE_read_real32(ifile); // crap
        auto p39 = BE_read_real32(ifile); // crap
        auto p40 = BE_read_real32(ifile); // crap
        auto p41 = BE_read_real32(ifile); // crap
        auto p42 = BE_read_real32(ifile); // crap
        auto p43 = BE_read_real32(ifile); // crap
        auto p44 = BE_read_real32(ifile); // crap
        auto p45 = BE_read_real32(ifile); // crap
        auto p46 = BE_read_real32(ifile); // crap
        auto p47 = BE_read_real32(ifile); // crap
        auto p48 = BE_read_real32(ifile); // crap
        auto p49 = BE_read_real32(ifile); // crap
        auto p50 = BE_read_real32(ifile); // crap
        if(debug) dfile << p01 << " ";
        if(debug) dfile << p02 << " ";
        if(debug) dfile << p03 << " ";
        if(debug) dfile << p08 << " ";
        if(debug) dfile << p09 << " ";
        if(debug) dfile << p14 << " ";
        if(debug) dfile << p15 << " ";
        if(debug) dfile << p20 << " ";
        if(debug) dfile << p21 << " ";
        if(debug) dfile << p26 << " ";
        if(debug) dfile << p27 << " ";
        if(debug) dfile << p32 << " ";
        if(debug) dfile << p33 << " ";
        if(debug) dfile << p34 << " ";
        if(debug) dfile << p35 << " ";
        if(debug) dfile << p36 << " ";
        if(debug) dfile << endl;

        // build material name
        stringstream name;
        name << "material_" << setfill('0') << setw(3) << i;

        // create material
        SMC_MATERIAL mat;
        mat.id = name.str();
        mat.twoside = false;
        mat.basemap = p03;
        mat.normmap = p21;
        mat.specmap = p27;
        mat.tranmap = INVALID_TEXTURE_INDEX;//p03;
        mat.bumpmap = INVALID_TEXTURE_INDEX;
        mat.resmap1 = INVALID_TEXTURE_INDEX;
        mat.resmap2 = INVALID_TEXTURE_INDEX;
        mat.resmap3 = INVALID_TEXTURE_INDEX;
        mat.resmap4 = INVALID_TEXTURE_INDEX;

        // save material
        smc.materials.push_back(mat);
       }
    if(debug) dfile << endl;
   }

 //
 // PROCESS #07
 // READ TRIANGLE GROUP DATA
 //

 struct TRILISTINFO {
  uint32 triangles;
  uint32 offset;
 };
 deque<TRILISTINFO> trilist_data;

 if(fileinfo[10])
   {
    // move to triangle information
    if(debug) dfile << "TRIANGLE GROUPS (TRIANGLES, POSITION)" << endl;
    ifile.seekg(fileinfo[10]);
    if(ifile.fail()) return error("Seek failure.");

    // number of groups
    uint32 n_groups = fileinfo[11];
    if(n_groups == 0) return error("Invalid number of triangle groups.");

    // read triangle information
    for(uint32 i = 0; i < n_groups; i++) {
        uint16 p1 = BE_read_uint16(ifile); // number of triangles
        uint16 p2 = BE_read_uint16(ifile); // always 0x0508
        uint32 p3 = BE_read_uint32(ifile); // triangle offset
        TRILISTINFO info = { p1, p3 };
        trilist_data.push_back(info);
        if(debug) dfile << "trigroup[" << i << "] = (" << info.offset << ", " << info.triangles << ")" << endl;
       }

    if(debug) dfile << endl;
   }

 //
 // PROCESS #08
 // READ INDEX BUFFER
 //

 if(fileinfo[12])
   {
    // move to index buffer
    if(debug) dfile << "INDEX BUFFER DATA" << endl;
    ifile.seekg(fileinfo[12]);
    if(ifile.fail()) return error("Seek failure.");
    
    // number of triangles
    uint32 n_triangles = fileinfo[13];
    if(n_triangles == 0) return error("Invalid number of triangles.");
    
    // process triangle groups
    for(size_t i = 0; i < trilist_data.size(); i++)
       {
        // get texture-material association data
        TEXTASSINFO ai = textass_data[i];
        if(!(ai.material_index < smc.materials.size())) return error("Texture association data is invalid.");
        uint32 ib_material = ai.material_index;
    
        // surface name
        stringstream ss;
        ss << "trigroup_" << setfill('0') << setw(3) << i;
    
        // move to data offset
        uint32 ib_offset = 3*indexsize*trilist_data[i].offset;
        ifile.seekg(fileinfo[12] + ib_offset);
        if(ifile.fail()) return error("Seek failure.");
    
        // read index buffer
        uint32 ib_tris = trilist_data[i].triangles;
        uint32 ib_elem = ib_tris*3;
        uint32 ib_char = ib_elem*indexsize;
        boost::shared_array<char> ib_data(new char[ib_char]);
        if(indextype == FACE_FORMAT_UINT_16) BE_read_array(ifile, reinterpret_cast<uint16*>(ib_data.get()), ib_elem);
        else BE_read_array(ifile, reinterpret_cast<uint32*>(ib_data.get()), ib_elem);
        if(ifile.fail()) return error("Read failure.");
    
        // create index buffer
        SMC_INDEX_BUFFER ib;
        ib.format = indextype;
        ib.type = FACE_TYPE_TRIANGLES;
        ib.name = ss.str();
        ib.elem = ib_elem;
        ib.data = ib_data;
        ib.tris = ib_tris;
        ib.material = ib_material;
    
        // save index buffer
        smc.ibuffer.push_back(ib);
       }
    if(debug) dfile << endl;
   }

 //
 // PROCESS #09
 // READ VERTEX BUFFER
 //

 if(fileinfo[15])
   {
    // number of vertices
    uint32 n_vertices = fileinfo[14];
    if(n_vertices == 0) return error("Invalid number of vertices.");

    // move to vertex buffer
    if(debug) dfile << "VERTEX BUFFER DATA" << std::endl;
    ifile.seekg(fileinfo[15]);
    if(ifile.fail()) return error("Seek failure.");

    // create vertex buffer
    smc.vbuffer.flags = VERTEX_POSITION;
    smc.vbuffer.name = "vbuffer";
    smc.vbuffer.elem = n_vertices;
    smc.vbuffer.data.reset(new SMC_VERTEX[n_vertices]);

    // vertex flags
    if(head02 == 0x01A7) {
       smc.vbuffer.flags |= VERTEX_UV;
    //   smc.vbuffer.flags |= VERTEX_WEIGHTS;
    //   smc.vbuffer.flags |= VERTEX_WMAPIDX;
      }
    else if(head02 == 0x0905) {
       smc.vbuffer.flags |= VERTEX_UV;
      }
    else if(head02 == 0x0925) {
       smc.vbuffer.flags |= VERTEX_UV;
      }
    else if(head02 == 0x09A7) {
       smc.vbuffer.flags |= VERTEX_UV;
      }
    else if(head02 == 0x09AF) {
       smc.vbuffer.flags |= VERTEX_UV;
      }
    else if(head02 == 0x09BF) {
       smc.vbuffer.flags |= VERTEX_UV;
      }
    else if(head02 == 0x09E7) {
       smc.vbuffer.flags |= VERTEX_UV;
      }

    // read vertices
    for(size_t i = 0; i < n_vertices; i++)
       {
        if(head02 == 0x01A7)
          {
           float v01 = BE_read_real32(ifile); //  4
           float v02 = BE_read_real32(ifile); //  8
           float v03 = BE_read_real32(ifile); // 12
           smc.vbuffer.data[i].vx = v01;
           smc.vbuffer.data[i].vy = v02;
           smc.vbuffer.data[i].vz = v03;
           if(debug) dfile << v01 << endl;
           if(debug) dfile << v02 << endl;
           if(debug) dfile << v03 << endl;
           if(debug) dfile << endl;
          }
        else if(head02 == 0x0905)
          {
           float v01 = BE_read_real32(ifile); //  4
           float v02 = BE_read_real32(ifile); //  8
           float v03 = BE_read_real32(ifile); // 12
           smc.vbuffer.data[i].vx = v01;
           smc.vbuffer.data[i].vy = v02;
           smc.vbuffer.data[i].vz = v03;
           if(debug) dfile << v01 << endl;
           if(debug) dfile << v02 << endl;
           if(debug) dfile << v03 << endl;
           if(debug) dfile << endl;
          }
        else if(head02 == 0x0925)
          {
           float v01 = BE_read_real32(ifile); //  4
           float v02 = BE_read_real32(ifile); //  8
           float v03 = BE_read_real32(ifile); // 12
           smc.vbuffer.data[i].vx = v01;
           smc.vbuffer.data[i].vy = v02;
           smc.vbuffer.data[i].vz = v03;
           if(debug) dfile << v01 << endl;
           if(debug) dfile << v02 << endl;
           if(debug) dfile << v03 << endl;
           if(debug) dfile << endl;
          }
        else if(head02 == 0x09A3) // ??? WRONG?
          {
           float v01 = BE_read_real32(ifile); //  4
           float v02 = BE_read_real32(ifile); //  8
           float v03 = BE_read_real32(ifile); // 12
           smc.vbuffer.data[i].vx = v01;
           smc.vbuffer.data[i].vy = v02;
           smc.vbuffer.data[i].vz = v03;
           if(debug) dfile << v01 << endl;
           if(debug) dfile << v02 << endl;
           if(debug) dfile << v03 << endl;
           if(debug) dfile << endl;
          }
        else if(head02 == 0x09A7)
          {
           float v01 = BE_read_real32(ifile); //  4
           float v02 = BE_read_real32(ifile); //  8
           float v03 = BE_read_real32(ifile); // 12
           smc.vbuffer.data[i].vx = v01;
           smc.vbuffer.data[i].vy = v02;
           smc.vbuffer.data[i].vz = v03;
           if(debug) dfile << v01 << endl;
           if(debug) dfile << v02 << endl;
           if(debug) dfile << v03 << endl;
           if(debug) dfile << endl;
          }
        else if(head02 == 0x09AF)
          {
           float v01 = BE_read_real32(ifile); //  4
           float v02 = BE_read_real32(ifile); //  8
           float v03 = BE_read_real32(ifile); // 12
           smc.vbuffer.data[i].vx = v01;
           smc.vbuffer.data[i].vy = v02;
           smc.vbuffer.data[i].vz = v03;
           if(debug) dfile << v01 << endl;
           if(debug) dfile << v02 << endl;
           if(debug) dfile << v03 << endl;
           if(debug) dfile << endl;
          }
        else if(head02 == 0x09BF)
          {
           float v01 = BE_read_real32(ifile); //  4
           float v02 = BE_read_real32(ifile); //  8
           float v03 = BE_read_real32(ifile); // 12
           smc.vbuffer.data[i].vx = v01;
           smc.vbuffer.data[i].vy = v02;
           smc.vbuffer.data[i].vz = v03;
           if(debug) dfile << v01 << endl;
           if(debug) dfile << v02 << endl;
           if(debug) dfile << v03 << endl;
           if(debug) dfile << endl;
          }
        else if(head02 == 0x09E7)
          {
           auto v01 = BE_read_real32(ifile);
           auto v02 = BE_read_real32(ifile);
           auto v03 = BE_read_real32(ifile);
           auto v04 = BE_read_uint16(ifile);
           auto v05 = BE_read_uint16(ifile);
           auto v06 = BE_read_uint16(ifile);
           auto v07 = BE_read_uint16(ifile);
           auto v08 = BE_read_uint16(ifile);
           auto v09 = BE_read_uint16(ifile);
           auto v10 = BE_read_uint32(ifile);
           auto v11 = BE_read_sint16(ifile);
           auto v12 = BE_read_sint16(ifile);
           smc.vbuffer.data[i].vx = v01;
           smc.vbuffer.data[i].vy = v02;
           smc.vbuffer.data[i].vz = v03;
           smc.vbuffer.data[i].tu =  v11/1023.0f;
           smc.vbuffer.data[i].tv = -v12/1023.0f;
           if(debug) dfile << v01 << endl;
           if(debug) dfile << v02 << endl;
           if(debug) dfile << v03 << endl;
           if(debug) dfile << v04 << endl;
           if(debug) dfile << v05 << endl;
           if(debug) dfile << v06 << endl;
           if(debug) dfile << v07 << endl;
           if(debug) dfile << v08 << endl;
           if(debug) dfile << v09 << endl;
           if(debug) dfile << v10 << endl;
           if(debug) dfile << v11 << endl;
           if(debug) dfile << v12 << endl;
           if(debug) dfile << endl;
          }
       }
   }

 //
 // PROCESS #10
 // READ SEPARATE UV DATA
 //

 if(fileinfo[17])
   {
    // move to offset
    if(debug) dfile << "SEPARATE UV DATA" << std::endl;
    ifile.seekg(fileinfo[17]);
    if(ifile.fail()) return error("Seek failure.");
    if(head02 == 0x01A7 || head02 == 0x0905 || head02 == 0x0925 || head02 == 0x09A7 || head02 == 0x09AF || head02 == 0x09BF)
      {
       uint32 n_vertices = fileinfo[14];
       for(size_t i = 0; i < n_vertices; i++) {
           auto v1 = BE_read_sint16(ifile);
           auto v2 = BE_read_sint16(ifile);
           if(debug) dfile << "v1 = " << v1 << endl;
           if(debug) dfile << "v2 = " << v2 << endl;
           smc.vbuffer.data[i].tu =  v1/1024.0f;
           smc.vbuffer.data[i].tv = -v2/1024.0f;
          }
      }
    if(debug) dfile << std::endl;
   }

 //
 // PROCESS #11
 // READ BONE MATRICES
 //
/*
 // process bones
 if(fileinfo[23])
   {
    // move to bone matrices
    if(debug) dfile << "BONE MATRICES" << endl;
    ifile.seekg(fileinfo[23]);
    if(ifile.fail()) return error("Seek failure.");

    // data to save
    std::deque<boost::shared_array<float>> mlist;
    SKELETON skeleton;
    skeleton.set_identifier("skeleton");

    // read matrices
    uint32 n_bones = fileinfo[24];
    if(debug) dfile << "number of bones = " << n_bones << endl;
    for(size_t i = 0; i < n_bones; i++) {
        boost::shared_array<float> m(new float[16]);
        m[ 0] = BE_read_real32(ifile);
        m[ 1] = BE_read_real32(ifile);
        m[ 2] = BE_read_real32(ifile);
        m[ 3] = BE_read_real32(ifile);
        m[ 4] = BE_read_real32(ifile);
        m[ 5] = BE_read_real32(ifile);
        m[ 6] = BE_read_real32(ifile);
        m[ 7] = BE_read_real32(ifile);
        m[ 8] = BE_read_real32(ifile);
        m[ 9] = BE_read_real32(ifile);
        m[10] = BE_read_real32(ifile);
        m[11] = BE_read_real32(ifile);
        m[12] = BE_read_real32(ifile);
        m[13] = BE_read_real32(ifile);
        m[14] = BE_read_real32(ifile);
        m[15] = BE_read_real32(ifile);
        mlist.push_back(m);
        if(debug) dfile << m[ 0] << " " << m[ 1] << " " << m[ 2] << " " << m[ 3] << endl;
        if(debug) dfile << m[ 4] << " " << m[ 5] << " " << m[ 6] << " " << m[ 7] << endl;
        if(debug) dfile << m[ 8] << " " << m[ 9] << " " << m[10] << " " << m[11] << endl;
        if(debug) dfile << m[12] << " " << m[13] << " " << m[14] << " " << m[15] << endl;
        if(debug) dfile << endl;
       }

    // move to bone information
    if(debug) dfile << "BONES" << std::endl;
    ifile.seekg(fileinfo[25]);
    if(ifile.fail()) return error("Seek failure.");
    if(n_bones != fileinfo[26]) return error("Number of joints do not match.");

    // read bone information
    for(size_t i = 0; i < n_bones; i++)
       {
        // joint
        JOINT joint;
        uint32 index = (uint32)i;
   
        // joint name
        char name[5];
        ifile.read(&name[0], 4);
        std::reverse(&name[0], &name[4]);
        name[4] = '\0';

        // make joint name unique
        std::stringstream jnss;
        jnss << name << "_" << setfill('0') << setw(3) << i;
        joint.name = jnss.str();
   
        // joint matrix
        joint.matrix = mlist[i];

        // joint relative positions
        float x = BE_read_real32(ifile);
        float y = BE_read_real32(ifile);
        float z = BE_read_real32(ifile);
        joint.rel_x = x;
        joint.rel_y = y;
        joint.rel_z = z;

        // set parent
        uint32 parent = BE_read_uint32(ifile);
        uint32 xxxxxx = BE_read_uint32(ifile);
        joint.parent = parent;
   
        // debug information
        if(debug) dfile << "index = " << index << endl;   
        if(debug) dfile << "name = " << name << endl;   
        if(debug) dfile << "x = " << x << endl;
        if(debug) dfile << "y = " << y << endl;
        if(debug) dfile << "z = " << z << endl;
        if(debug) dfile << "parent = " << parent << endl;
        if(debug) dfile << "xxxxxx = " << xxxxxx << endl;
        if(debug) dfile << std::endl;

        // insert joint
        if(parent == INVALID_JOINT && i != 0) continue;
        else skeleton.insert(index, joint);
       }

    // print joint tree in debug mode
    if(debug) {
       stringstream ss;
       skeleton.PrintJointTree(ss);
       dfile << ss.str() << endl;
      }

    // create skeleton
    smc.skeletons.push_back(skeleton);
   }
*/

 //
 // PROCESS #12
 // READ TEXTURES
 //

 if(fileinfo[27])
   {
    // move to texture information
    if(debug) dfile << "TEXTURES" << endl;
    ifile.seekg(fileinfo[27]);
    if(ifile.fail()) return error("Seek failure.");
   
    // read texture information
    uint32 n_textures = fileinfo[28];
    for(uint32 i = 0; i < n_textures; i++)
       {
        uint32 param1 = BE_read_uint32(ifile); // index
        uint32 param2 = BE_read_uint32(ifile); // 0
        uint16 param3 = BE_read_uint16(ifile); // 0x0001
        uint16 param4 = BE_read_uint16(ifile); // 0x0040 | 0x0100 | 0x0200 | 0x0400
        uint16 param5 = BE_read_uint16(ifile); // 0x0040 | 0x0100 | 0x0200 | 0x0400
        uint16 param6 = BE_read_uint16(ifile); // 0x2000 | 0xFF00
        if(debug) dfile << "param1 = " << param1 << endl;
        if(debug) dfile << "param2 = " << param2 << endl;
        if(debug) dfile << "param3 = " << param3 << endl;
        if(debug) dfile << "param4 = " << param4 << endl;
        if(debug) dfile << "param5 = " << param5 << endl;
        if(debug) dfile << "param6 = " << param6 << endl;
        if(debug) dfile << std::endl;
   
        // texture id
        stringstream id;
        id << "texture_" << setfill('0') << setw(3) << param1;
   
        // texture filename
        stringstream fn;
        fn << setfill('0') << setw(3) << param1 << ".tga";
   
        // insert texture
        SMC_TEXTURE item = { id.str(), fn.str() };
        smc.textures.push_back(item);
       }
    if(debug) dfile << endl;
   }

 // save geometry
 return SaveLWO(fn_path.c_str(), fn_name.c_str(), smc);
}

};};

namespace X_SYSTEM { namespace X_GAME {

bool extract(void)
{
 std::string pathname = GetModulePathname();
 return extract(pathname.c_str());
}

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

};};