17 ม.ค. 2556

Facebook UI มันชอบไปอยู่หลัง Unity Web Player

กรณีที่มันอยู่ในหน้า Web ของมันเองบางทีเจอบางทีก็ไม่เจอ Chrome , IE , Firefox ก็ผลออกมาไม่เหมือนกัน

แต่กรณีที่มันอยู่ใน Facebook Iframe ไอตัว Facebook UI ที่มันเด้งขึ้นมา มันนจะไปอยู่ข้างหลัง Unity Web Player ทันที
แบบนี้!


เพราะฉะนั้นถ้าเอามันมาไว้ข้างหน้าไม่ได้ ก็ ซ่อน Unity Web Player ซะเลย ระหว่างที่ แสดง Facebook UI

DIV Container ของ Unity Web Player

      

 <div class="content">
<div id="unityPlayer" name="unityPlayer">
<div class="missing">
<a href="http://unity3d.com/webplayer/" title="Unity Web Player. Install now!">
                        <img alt="Unity Web Player. Install now!" height="63" src="http://webplayer.unity3d.com/installation/getunity.png" width="193" />
                    </a>
                </div>
</div>
</div>


สมมติจะ เรียก UI AppRequest
และ ระหว่าง ที่  แสดง UI ใช้ JQuery.hide() แต่ ถ้าใช้ method นี้ ตอน JQuery.show();  ตัว Unity Web Player จะโหลด Game ใหม่
เลขใช้แบบ แก้ Width,Height เป็น 0 แทน

ตอน Call Facebook UI  อันนี้เป็น script ใน web page ใช้ Application.ExternalCall() เอาจาก script ใน  Unity3d

    
function uiAppRequest( title, message ) {
 $('#unityPlayer').width(0);
 $('#unityPlayer').height(0);
 FB.ui(
  {
    method:  'apprequests',
                 message: message,
                 title:  title
      }, 
      function(response) {
       $('#unityPlayer').width(800);
   $('#unityPlayer').height(600);
       var unity = unityObject.getObjectById( "unityPlayer" );
       if (!response || response.error) {
        unity.SendMessage("Facebook", "onUiAppRequestFail", JSON.stringify( response ) );
      } else {
        unity.SendMessage("Facebook", "onUiAppRequestSuccessful", JSON.stringify( response ) );
      }     
   }
 );
}


Unity Web Player หายไป แล้ว Facebook UI แสดงแทน

อธิบาย 
- เมื่อ Unity3D เรียก Facebook UI ก็ให้  width = height = 0 เลย 
- และ Add Callback Function เมื่อ UI Completed ให้มีส่วนที่สั่งให้ Unity Web Player กลับมาแสดงอีกครั้ง

15 ม.ค. 2556

แก้ไขไฟล์ LDT ของเกม Latale

โชคดีอีกแล้วที่เกม Latale ไม่ได้ Encrypt ไฟล์ LDT เพราะฉะนั้นไม่จำเป็นต้อง Debug
ไฟล์ LDT เปรียบเสมือนฐานข้อมูลของเกม Latale ทั้งหมด
ไฟล์ LDT เมื่อ Unpack ออกมาจากไฟล์ SPF แล้วก็สามารถทำความเข้าใจคร่าวๆด้วย HEX Editor ได้เลย

เริ่มจากไฟล์ที่เล็กที่สุด เปิดมาก็จะหน้าตาแนวๆนี้ 


จะเจอ Text  ที่เขียนว่า _Name และ _IconIdx อันนี้ เดาว่าน่าจะเป็น Column Name
ซึ่งแต่ละ Column Name จะมีขนาด 64 Byte

และ 12 Byte ก่อน Column Name คือ 
00 00 00 00  02 00 00 00  10 00 00 00

ความรู้ทั่วไป ปกติแล้ว Binary ไฟล์ส่วนใหญ่จะต้องบอกข้อมมูลพวกนี้ ใน Header ไฟล์
- Byte ที่ไว้แสดงว่าเป็น ไฟล์ เฉพาะ ที่โปรแกรมใดๆจะอ่านได้ เช่น PK ของ Winzip , PNG ของไฟล์รูป PNG
- ขนาดของ Data 
- CRC ของ Data 
- รายละเอียดอื่นๆ ที่บอกถึงรายละเอียดที่สามารถนำมาประกอบการอ่าน Binary Data
-- เช่นไฟล์รูป ควรจะมี Width และ Height เป็นส่วนสำคัญ
-- ไฟล์หนัง ควรจะมี ชนิด Encode และ ชนิด Encapsulation และ จำนวน Layer หรือ ภาพกี่ Byte เสียงกี่ Byte ต่อการอ่าน 1 Blocks

แต่ไฟล์ LDT นี้ Header หลักๆ มี 3 ตัว คือ 

1.  [00 00 00 00] ซึ่ง ไม่รู้ว่ามันคืออะไร แต่ทุกไฟล์ LDT ส่วนใหญ่จะเป็น [00 00 00 00]
2.  [02 00 00 00] เดาว่ามันเป็น จำนวน Column ของข้อมูลนี้ เพราะ เจอ แค่ 2 Column ใน Hex
3.  [10 00 00 00] อันนี้อาจจะเป็นอะไรก็ได้ ตัวอย่างเช่น 
-- Block size ของข้อมูลแต่ละ Rows 
-- Rows Size ของข้อมูลใน ไฟล์นี้
-- แค่เลข Version เฉยๆ
-- Junk ทำไวหลอก
-- Reserved Byte เผื่อไว้ก่อน 

แต่จะให้น้ำหนักไปที่ Rows Size หรือ Block Size ของข้อมูลมากกว่า เพราะคงไม่มีใครอยากออกแบบไฟล์ Binary ที่ Generate ออกมาแล้วนำกลับไปอ่านแบบ Binary ยากๆ 

*กรณีที่ [10 00 00 00] ไม่ใช่ จำนวน Rows หรือ Blocks Size วิธีการอ่าน Binary นี้จะแตกต่างออกไป เพราะ การแบ่ง ข้อมูล Binary ออกเป็นส่วนๆ ต้องอาศัย ตัว Separator ที่ถูกกำหนดแล้วว่ามันจะไม่มี Byte Separator  อยู่ใน ข้อมูลแน่ๆ ไม่งั้น การอ่านกลับไป จะผิด

เช่นข้อมูล 
01 00 FF FF 02 00 FF FF 03 00
ตัวแบ่งข้อมูลคือ FF FF
กรณีที่ไม่มี  Rows Size หรือ Block Size ก็จะอ่านไปเรื่อยๆ จนกว่าจะเจอ FF FF จึงเรื่มข้อมูลชิ้นใหม่
แต่ถ้ามีข้อมูลที่ เป็น FF FF แล้ว จะกลายเป็น
01 00 FF FF 02 00 FF FF 03 00 FF FF FF FF
การอ่าน Binary กลับมาใช้งานจะผิดทันที เพราะ โปรแกรมเข้าใจ ว่า FF FF หลังสุดคือตัวแบ่ง ข้อมูลเฉยๆ

ที่ทำให้ต้องให้นำหนัก Rows Size หรือ Block Size เพราะข้อมูลแบบนี้ ส่วนใหญ่จะออกแบบมาให้คล้ายๆตาราง มี Column และเก็บข้อมูลหลายๆ Rows

และเมื่อไล่ลงไปดูล่างๆ เห็นได้ชัดว่า จำนวน Column ไม่ได้ Flexible แต่ถูก Fixed ไว้ที่ไม่เกิน 128 Column เพราะเริ่มมีข้อมูลใหม่ ที่ Offset  0x200C 
*จาก Offset 0x0C ไป ถึง 0x200C มีขนาด 0x2000 และหารด้วยขนาด MAX LENGTH ชื่อ Column = 64 ได้ 128 Column


ดูต่อไปที่ Offset 0x200C จะเห็นเป็นตัวเลข DWORD หลายๆตัว ก็เลยเดาว่าเป็น Type ของ Column 
เพราะ 4 * 128(Column) = 0x220C  ซึ่ง 0x220C น่าจะคือจุดเริ่มต้อของ Data พอดี
จะมีค่าระหว่าง 1 - 4 



ต่อไป ดูที่ Offset 0x220C น่าจะเป็นจุดเริ่มต้นของ Data ซึ่งวิเคราะห์ คร่าวๆใน Hex Edittor แล้ว แต่ละ Rows จะมีขนาด Byte ไม่เท่ากัน  ทั้งนี้เพราะ ขนาด String ที่ยาวไม่เท่ากัน


กลับไปที่ Field ที่ 3 ของ Header  [10 00 00 00]  ไม่ใช่ Block Size แน่ๆ จิงเหลือแค่ Rows Size เท่านั้นที่เป็นไปได้ 

อีกทั้ง ข้อมูลชนิด String จะมีความพิเศษกว่าข้อมูอื่นตรงที่ จะมี 2 Byte นำหน้าที่บอกขนาด ของความยาวข้อความ และตามด้วย ข้อความ โดยที่ไม่มี  \0 ตามหลัง
ทำให้เพิ่มความมั่นใจเรื่อง Column Type ได้มากขึ้น เพราะ จำนวนต้องมีตัวบอกว่า Column ใดเป็นข้อมูลชนิดใด

ไฟล์ตัวอย่างตอนนี้ มี Column Type 1 และ 3 เมื่อเปรียบเทียบกับข้อมูลที่มีถึง 3 ส่วน ของ Data Row
[XX XX XX XX][YY YY][ZZ ZZ ZZ ZZ]
[01 00 00 00] [0B 00 4D 61 69 6E 74 65 6E 61 6E 63 65] [01 00 00 00]
[02 00 00 00] [06 00 4E 6F 74 69 63 65] [02 00 00 00]

แต่เมื่อลองดู LDT หลายๆ ไฟล์  จะพบว่า 
[XX XX XX XX] คือ ID  แต่ไม่ระบบว่าเป็น ColumnType ใด และไม่มีชื่อใน Column Name
Column Type 1 คือข้อมูลชนิด String
Column Type 2 คือข้อมูลชนิด Boolean เพราะ Data มีแค่ 1 กับ 0
Column Type 3 คือข้อมูลชนิด Signed integer
Column Type 4 คือข้อมูลชนิด Float

structure ในภาษา C จึงมีหน้าตาแบบนี้

#define MAX_COLUMN 128
#define MAX_COLUMN_NAME 64

#define OFFSET_COLUMN_NAME 0xC
#define OFFSET_COLUMN_TYPE 0x200C
#define OFFSET_DATA 0x220C

struct FILE_HEAD
{
 DWORD FileHead;
 DWORD ColumnSize;
 DWORD RowSize;
 CHAR ColumnName[MAX_COLUMN][MAX_COLUMN_NAME];
 DWORD ColumnType[MAX_COLUMN]; //1 String , 2 Boolean, 3 Signed Int,4 Float
};


Code อ่านไฟล์ LDT เพื่อ Convert เป็น CSV 

FILE * fp_ldt = NULL;
 FILE * fp_csv = NULL;

 char filename[128];
 char * path1 = "DATA\\LDT\\%s.LDT";
 char * path2 = "DATA\\LDT\\%s.CSV";
 char * _file = "ITEM_3";
 sprintf(filename,path1,_file);
 PrintFError("Openfile %s ret = %s\n",filename,strerror(fopen_s(&fp_ldt,filename,"rb")));

 if( fp_ldt )
 {
  FILE_HEAD fh_ldt;
  ZeroMemory(&fh_ldt,sizeof(FILE_HEAD));

  fread(&fh_ldt,sizeof(FILE_HEAD),1,fp_ldt);

  PrintFError("FILE HEAD : Row %d Column %d \n",fh_ldt.RowSize,fh_ldt.ColumnSize);

  fseek( fp_ldt, 0, SEEK_SET );
  for(int i = 0 ; i < fh_ldt.ColumnSize && i < MAX_COLUMN ; ++i)
  {
   //seek to name and read
   fseek( fp_ldt, OFFSET_COLUMN_NAME + (i * MAX_COLUMN_NAME), SEEK_SET );
   fread(&fh_ldt.ColumnName[i],1,MAX_COLUMN_NAME,fp_ldt);
   fh_ldt.ColumnName[i][MAX_COLUMN_NAME - 1] = 0;

   //seek to type and read
   fseek( fp_ldt, OFFSET_COLUMN_TYPE + (i * sizeof(DWORD)), SEEK_SET );
   fread(&fh_ldt.ColumnType[i],1,sizeof(DWORD),fp_ldt);
   PrintFError("-- Column %s Type %d \n",fh_ldt.ColumnName[i],fh_ldt.ColumnType[i]);
  }

  //seek to data
  fseek( fp_ldt, OFFSET_DATA, SEEK_SET );
  DATA_TABLE _DATA_TABLE;
  WORD _TmpStringLength;
  DWORD _TmpDword;
  for(int i = 0 ; i < fh_ldt.RowSize  ; ++i)
  {
   //ID
   FILE_DATA_COLUMN * _COLUMN_ID = new FILE_DATA_COLUMN();
   _COLUMN_ID->ColumnType = 0;
   fread(&_TmpDword,1,sizeof(DWORD),fp_ldt);
   _COLUMN_ID->DATA = (void*)_TmpDword;

   DATA_ROW* _DATA_ROW = new DATA_ROW();
   _DATA_ROW->push_back(_COLUMN_ID);

   for(int j = 0 ; j < fh_ldt.ColumnSize && j < MAX_COLUMN ; ++j)
   {
    FILE_DATA_COLUMN * _COLUMN = new FILE_DATA_COLUMN();
    _COLUMN->ColumnType = fh_ldt.ColumnType[j]; //Set Column Type

    switch(_COLUMN->ColumnType)
    {
    case 1:
     {
      fread(&_TmpStringLength,1,sizeof(WORD),fp_ldt);
      CHAR * _Ln = new CHAR[_TmpStringLength + 1];
      fread(_Ln,1,_TmpStringLength,fp_ldt);
      _Ln[_TmpStringLength] = 0;
      for(int k = 0 ; k < _TmpStringLength ; ++k)
      {
       if( _Ln[k] == ',' )
        _Ln[k] = ' ';
       else if( _Ln[k] == '\n' )
        _Ln[k] = ' ';
       else if( _Ln[k] == '\r' )
        _Ln[k] = ' ';
      }
      _COLUMN->DATA = _Ln;
      break;
     }
    case 2 : case 3 : case 4:
     {
      fread(&_TmpDword,1,sizeof(DWORD),fp_ldt);
      _COLUMN->DATA = (void*)_TmpDword;
      break;
     }
    default:
     {
      PrintFError("-- Unknown Column Type \n");
      break;
     }
    }//switch

    _DATA_ROW->push_back(_COLUMN);
   }//col

   _DATA_TABLE.push_back(_DATA_ROW);
  }//row

  //Convert
  sprintf(filename,path2,_file);
  PrintFError("Openfile %s ret = %s\n",filename,strerror(fopen_s(&fp_csv,filename,"w+")));
  if( fp_csv )
  {
   //Write column type
   fprintf(fp_csv,"0,");
   for(int j = 0 ; j < fh_ldt.ColumnSize && j < MAX_COLUMN ; ++j)
   {
    fprintf(fp_csv,"%d,",fh_ldt.ColumnType[j]);
   }
   fprintf(fp_csv,"\n");

   //Write column name
   fprintf(fp_csv,"_ID,");
   for(int j = 0 ; j < fh_ldt.ColumnSize && j < MAX_COLUMN ; ++j)
   {
    fprintf(fp_csv,"%s,",fh_ldt.ColumnName[j]);
   }
   fprintf(fp_csv,"\n");

   //Write Data
   DATA_TABLE::const_iterator it_row = _DATA_TABLE.begin();
   for( ; it_row != _DATA_TABLE.end() ; ++it_row )
   {
    DATA_ROW* _DATA_ROW = *it_row;
    DATA_ROW::const_iterator it_col = _DATA_ROW->begin();
    for( ; it_col != _DATA_ROW->end() ; ++it_col )
    {
     FILE_DATA_COLUMN * _COLUMN = *it_col;
     switch(_COLUMN->ColumnType)
     {
     case 0:
      {
       fprintf(fp_csv,"%d,",(DWORD)_COLUMN->DATA);
       break;
      }
     case 1:
      {
       fprintf(fp_csv,"%s,",(CHAR*)_COLUMN->DATA);
       delete [] _COLUMN->DATA; 
       break;
      }
     case 2 : 
      {
       fprintf(fp_csv,"%d,",(DWORD)_COLUMN->DATA);
       break;
      }
     case 3 : 
      {
       fprintf(fp_csv,"%d,",(int)_COLUMN->DATA);
       break;
      }
     case 4 : 
      {
       float _d = 0.0f;
       DWORD  _b = (DWORD)_COLUMN->DATA;
       __asm
       {
        mov eax,_b;
        //lea ebx,_d;
        mov dword ptr[_d],eax
       }
       fprintf(fp_csv,"%0.2f,",(float)_d);
       break;
      }
     }//switch

     delete _COLUMN;
    }//loop col
    _DATA_ROW->clear();
    delete _DATA_ROW;
    fprintf(fp_csv,"\n");
   }//loop row

   _DATA_TABLE.clear();
   fclose(fp_csv);
  }

  fclose(fp_ldt);
 }


หน้าตา ข้อมูลที่แปลงเป็น CSV แล้ว


จากนั้นจึงแกไขข้อมูลใน CSV แล้วแปลงกลับเป็น LDT ด้วยตัวอย่าง Code นี้ 

        
FILE * fp_ldt = NULL;
 FILE * fp_csv = NULL;

 char filename[128];
 sprintf(filename,"C:\\DATA\\LDT\\ITEM_3.CSV");
 PrintFError("Openfile CSV %s ret = %s\n",filename,strerror(fopen_s(&fp_csv,filename,"rb")));

 sprintf(filename,"C:\\DATA\\LDT\\ITEM_3_NEW.LDT");
 PrintFError("Writefile %s ret = %s\n",filename,strerror(fopen_s(&fp_ldt,filename,"wb")));

 if( fp_csv && fp_ldt )
 {
  FILE_HEAD fh_ldt;
  ZeroMemory(&fh_ldt,sizeof(FILE_HEAD));
  char _line[1024 * 10];
  char* _end = new char[64];
  memset((_end),0x20,64);
  _end[0] = 'E';
  _end[1] = 'N';
  _end[2] = 'D';

  //ColumnType
  int _idx = 0;
  if ( fgets (_line , 1024 * 10 , fp_csv) != NULL )
  {
   char * pch;
   pch = strtok (_line,",");
   pch = strtok (NULL, ","); //Cut off _ID
   while (pch != NULL)
   {
    if( strlen(pch) > 0 )
    {
     fh_ldt.ColumnType[_idx++] = atoi(pch);
     PrintFError ("%s\n",pch);
     pch = strtok (NULL, ",");
    }
   }
  }
  fh_ldt.ColumnSize = _idx - 1; 
  //ColumnName
  _idx = 0;
  if ( fgets (_line , 1024 * 10, fp_csv) != NULL )
  {
   char * pch;
   pch = strtok (_line,",");
   pch = strtok (NULL, ","); //Cut off _ID
   while (pch != NULL)
   {
    if( strlen(pch) > 0 )
    {
     memset((fh_ldt.ColumnName[_idx]),0x20,64);
     StringCchCopyA((fh_ldt.ColumnName[_idx]),64,pch);
     //PrintFError ("%s\n",pch);
     _idx++;
     pch = strtok (NULL, ",");
    }
   }
  }

  fwrite (&fh_ldt , 1 ,sizeof(FILE_HEAD) , fp_ldt );
  fflush(fp_ldt);

  int _row = 0;
  while(!feof(fp_csv))
  {
   if ( fgets (_line , 1024* 10 , fp_csv) != NULL )
   {
    _idx = 0;
    char * pch;
    pch = strtok_single  (_line,",");
    while (pch != NULL)
    {
     if( _idx == 0 )
     {
      DWORD _ID = atoi(pch);
      fwrite( &_ID ,1, 4 ,fp_ldt);
     }
     else
     {
      int _ColumnType = fh_ldt.ColumnType[_idx - 1];
      if( _ColumnType == 1 )
      {
       WORD _STR_SIZE = (WORD)strlen(pch);
       fwrite( &_STR_SIZE ,1, 2 ,fp_ldt);
       fwrite( pch ,1, strlen(pch) ,fp_ldt);
      }
      else if( _ColumnType == 2 || _ColumnType == 3 )
      {
       DWORD _TEMP = (DWORD)atoi(pch);
       fwrite( &_TEMP ,1, 4 ,fp_ldt);
      }
      else if( _ColumnType == 4 )
      {
       float _TEMP = (float)atof(pch);
       fwrite( &_TEMP ,1, 4 ,fp_ldt);
      }
     }
     _idx++;
     pch = strtok_single  (NULL, ",");
     fflush(fp_ldt);
    }
    _row++;
   }
  }

  fwrite( _end ,1, 64 ,fp_ldt);
  fflush(fp_ldt);

  fh_ldt.RowSize = _row;
  fseek( fp_ldt, 0, SEEK_SET );
  fwrite (&fh_ldt , 1 ,sizeof(FILE_HEAD) , fp_ldt );

  fclose(fp_csv);
  fclose(fp_ldt);
 }



Extract ไฟล์ Pack Latale

เนื่องด้วยไฟล์ Pack ของเกม Latale ไม่ได้ Encrypt
เพราะฉะนั้นมาว่าแค่การ Binary Analysis

เริ่มจากไฟล์ขนาดเล็กก่อน *เพราะมันดูง่าย

CLAIRE.SPF

ใส่ Hex Editor เลย


เปิดมาก็งงเล็กน้อยเพราะเจอแต่ Header ของไฟล์ PNG เต็มๆ ทั้งๆที่ Pack ไฟล์ส่วนใหญ่จะต้องมี Header ที่บอกรายละเอียดของ Pack ไฟล์นั้นๆ ลองเลื่อนลงมาดู ล่างสุดละกัน


แปลกแต่จริง สรุปว่า Header มันอยู่ล่างสุดของไฟล์ (*แต่ก็ขอเรียก Header) เลื่อนไปดูแล้วเดาว่า มันสิ้นสุดที่ไหน


นับว่าโชคดีมาก ที่ จุดสิ้นสุดของ Data มันคือ Footer ของไฟล์ PNG มันมีเอกสารในเน็ต ทำให้รู้ว่า Footer ของ PNG มันคือ 49 45 4E 44 AE 42 60 82  -  (IEND) + 0x826042AE ดูอ้างอิงในเน็ตได้ที่ 

http://www.xbdev.net/image_formats/png/index.php  
const int CHUNK_CRC32_IEND = 0xae426082;

ทำให้รู้ว่า Header ของ Pack ไฟล์เริ่มที่  Offset  0x10573D
คาดว่า ข้อมูลของ แต่ละไฟล์ จะเป็นตามรูป 140 byte ต่อ 1 ไฟล์


จินตนาการเป็น structured ในภาษา C 
1. Path ของ file เดาว่า คนออกแบบตั้ง กำหนด Length เป็น แบบ 16,32,64,128 เดาไปก่อน

ได้
struct FILE_PACK_ELEMENT
{
CHAR FileName[128];
        ......
};

ยังเหลือ 76 22 00 00 76 22 00 00 02 00 00 0C
*ต้องเข้าใจ ข้อมูลแบบ Binary  พวก WORD , DWORD , BYTE ว่ามันมีขนาดเท่าไหร่

อย่าง 76 22 00 00 ให้สงสัยมันว่าเป็น DWORD ไปเลย เพราะ ใช้ 4 Byte 
ส่วน 76 22 00 00 อีกตัวก็คง DWORD 
เนื่องจากสงสัยว่า สองตัวนี้ มีตัวนึง ที่เป็น ที่อยู่ Offset ของ ไฟล์ ที่บอกไว้ใน FileName แน่ๆ
ส่วนอีกตัวมันต้องเป็น Size ของไฟล์ หรือ จุดสิ้นสุด Offset ของไฟล์  และคงไม่มีใครเก็บ Offset หรือ File Size เป็น WORD แน่ๆ  เพราะมันไม่พอ

ลองเปรียบเทียบกับไฟล์แรกที่อยู่ใน Pack  ก็พบว่า เป็น

00 00 00 00 76 22 00 00 01 00 00 0C

แต่ DWORD ชุดแรก เป็น 0 ซึ่ง ก็หมายความว่า Filed นี้ Offset แน่ๆ เพราะ FACE01_01.PNG มันอยู่บนสุดของไฟล์ Pack และมันตั้งอยู่ที่ Offset 0 ของไฟล์ Pack
เหลือ  76 22 00 00 ซึ่งยังไม่แน่ใจว่าเป็น FileSize หรือ Offset ที่บอกจุดสุดท้ายของไฟล์กันแน่

ลองไปเปรีบยเทียบ ไฟล์ที่ 3 เพื่อหาว่า อันที่ 3 มันคืออะไรกันแน่ระหว่าง  FileSize หรือ Offset
ไปดูไฟล์สุดท้ายเลย  2F ED 0F 00 0E 6A 00 00 0F 00 00 0C  <-- 0E 6A 00 00 มันอยู่ไฟล์ สุดท้าย แต่มันมีค่าแค่ 0x6A0E เป็นไปไม่ได้เลยที่มันจะเป็น Offset เพราะ ฉะนั้นมันคือ FileSize

ตอนนี้ได้ 
struct FILE_PACK_ELEMENT
{
CHAR FileName[128];
DWORD FilePos; 
DWORD FileSize;
        ....
};


ยังเหลือ  02 00 00 0C  ซึ่งมันคงไม่ใช่ DWORD เพราะหน้าตามันแปลกไป และมันก็ไม่ใช่ Signed Int ไม่ใช่ Float ด้วย เพราะมันไม่ได้หน้าตาแบบนี้ใน แบบ Binary เลยสงสัยว่ามันเป็น  WORD
จะสังเกตได้ว่า ไฟล์แรก มันคือ 01 00 ไฟล์ที่สองคือ 02 00  ไฟล์ต่อไป คือ 03 00 มันคือ!! FileIdx แน่ๆ

ตอนนี้ได้ 
struct FILE_PACK_ELEMENT
{
CHAR FileName[128];
DWORD FilePos; 
DWORD FileSize;
 WORD FileIdx;
};



ก็เหลือ 00 0C เป็นไปได้ทั้ง WORD และ BYTE 2 ตัว 
* สังเกต 0C เพราะ ทุกไฟล์มี 0C เหมือนกันหมด 

ได้
struct FILE_PACK_ELEMENT
{
CHAR FileName[128];
DWORD FilePos; 
DWORD FileSize;
WORD FileIdx;
BYTE Unknow;
BYTE Unknow2;
};

ยังเหลือส่วนที่เป็น Header ของ Pack ไฟล์ที่ไม่เกี่ยวกับ รายละเอียดแต่ละไฟล์อีก  140 Byte 



34 08 00 00 มาจาก 4 Byte แรก ที่คิดว่ามันน่าจะเป็น Type DWORD
ลองคิดว่า 0x00 00 08 34 (2100)  มันจะเป็นอะไรได้บ้าง อาจจะเป็น

- เนื่องจากยังไม่มี ส่วนไหนบอกจำนวน File ใน Pack ไฟล์ ก็สงสัยว่าเป็น จำนวนไฟล์ทั้งหมดใน Pack file  แต่ ตัดทิ้งได้เลย เพราะนับคร่าวๆ เอาจาก Hex ของ FILE_PACK_ELEMENT ได้แค่ 15 ไฟล์
- หรืออาจจะเป็น  Time stamp หรือ Version ซึ่ง ค่า 2100 ไม่ใช่ Time Stamp แน่ๆ แต่อาจจะเป็น Version 
- หรืออาจจะเป็น Size ของ Data  ก็ไม่ใช่อีกเพราะมันแค่ 2100 Byte เป็นไปไม่ได้


- หรืออาจจะเป็น Size ของ Header ทั้งหมด แต่ Size ของ Header ทั้งหมดมันคือ 2240
- แต่ถ้า Size ของ Header เฉพาะที่เกี่ยวกับ File List จะเป็น 2100 พอดี เป็นได้ได้ว่า 4 Byte แรกคือ Size ของ Header เฉพาะส่วนที่เกี่ยวกับ File List 

ต่อไปคือ 0C 00 00 00  คุ้นๆว่า มันเหมือนกับที่มี 0C อยู่ใน ทุก FILE_PACK_ELEMENT แต่เมื่อเปิด SPF ไฟล์อื่นๆ ปราฏว่ามันไม่ซำกัน อนมาณได้ว่ามันคือเลข PackFileIdx

*ส่วน 00 00 00 00 อีกหลายตัวไม่สามารถหาความหมายได้
และท้ายสุดของไฟล์ Pack
คือ C9 EA 9D 3B = 1000205001
สังเกตได้ว่า พอเป็นเลขฐาน 10 มันดูสวยงามผิดปกติ ดูใน SPF ไฟล์อื่น มันคล้ายๆกับเป็นเลขของวันที่ หรือ Version ก็ได้

จะได้ structured ในภาษา C


//PACK FILE
struct FILE_TAIL_PACK
{
DWORD FileListSize;
DWORD PackNo;
DWORD UnknowArr[32]; //*
DWORD DateVersion;
};

และรู้แล้วว่า Byte สุดท้าย ของ

ได้
struct FILE_PACK_ELEMENT
{
CHAR FileName[128];
DWORD FilePos; 
DWORD FileSize;
WORD FileIdx;
BYTE Unknow;
BYTE PackFileIdx;  //<---
};

เขียนโปรแกรมอ่าน และ Extract

เพื่ออ่าน Header ล่างสุดของไฟล์

fseek( fp_spf,0, SEEK_END );
_FileSize = ftell( fp_spf );
fseek( fp_spf, _FileSize - sizeof(FILE_TAIL_PACK) , SEEK_SET );
FILE_TAIL_PACK _Head;
fread(&_Head,sizeof(FILE_TAIL_PACK),1,fp_spf);

เก็บรายละเอียด List File ที่ที่ Pack จาก Header 

std::vector<FILE_PACK_ELEMENT*> _FileList;
if( _Head.FileListSize > 0 )
{
fseek( fp_spf, _FileSize - _Head.FileListSize - sizeof(FILE_TAIL_PACK) , SEEK_SET );
for(int i = 0 ; i <  _Head.FileListSize / sizeof(FILE_PACK_ELEMENT) ; ++i)
{
FILE_PACK_ELEMENT * _FileEle = new FILE_PACK_ELEMENT();
fread(_FileEle,sizeof(FILE_PACK_ELEMENT),1,fp_spf);
_FileList.push_back(_FileEle);
}
}


หลังจากนี้ ก็ ทำการ Extract ไฟล์ จาก Pack โดยอาศัยข้อมูลใน _FileList ที่มีทั้ง Path ของไฟล์และ Offset และ Size 




บังคับให้ Latale ไม่อ่าน Pack File

เป็นเกมออนไลน์ จากเว็บ http://latale.ogplanet.com/
ไม่อยากให้มันอ่าน Pack ไฟล์แต่อยากให้มันอ่าน File Resource ตรงๆ เพราะอยากแก้พวก รูป นู่นนี่นั่นเอง
เพราะแก้ Resource เกมตอนที่มันอยู่ใน รูปของ Pack ไฟล์ไม่ได้

ปกติแล้วจะ  Debug แบบง่ายสุดๆ ก็หา ข้อความที่ Reference เอาใน All reference text string.
แล้วค่อยไล่ Assembly ไปเรื่อยๆ แต่คราวนี้ Latale Client มันไม่มี reference text string ของชื่อไฟล์พวก AJJIYA.SPF,BANX.SPF,BARY.SPF,..

แต่ไม่เป็นไร มีวิธีอื่น คือ หา ที่มัน Call Api พวก CreateFile เอา


Break ให้หมด แล้ว Run 


น่าจะใช่ที่ต้องการ แล้วก็ไปดูที่ Call Stack


0x008B9AC0 น่าสนใจ เพราะ มัน ทำต่อจาก void main() เลย ก็ลองไป Debug มันดู ว่ามัน Call อะไรยังงัย ปรากฏว่าใน Loop มันมีสั่งโหลด File SPF แน่ๆ 


แต่จะเห็นว่า ใน Folder ไม่มี File TESTPACK.SPF เลยลองกด F9 ไปอีกที ปรากฏว่ามันไม่ได้ไป Break ทีหัว Function 0x008B9AC0 แสดงว่า Loop ที่ Address 008B9B27 เป็นตัวโหลด ทุก ไฟล์ SPF ใน Function เดียว ก็กด F9 ไปเรื่อยๆ จนเริ่มเกม สังเกตว่า ไม่มี การ Createfile หรือ CreateFileMapping  พวก Resource ไฟล์ต่างๆ ใน Pack เลย แล้วมันทำยังงัย?

พอโหลดครบทุกไฟล์มันก็ Set Flag ที่  0x01AE8594 เป็น 1 ถ้า 0xADE8E0 เป็น 1


เริ่ม จินตนาการได้แล้ว ว่า 0xADE8E0 (eax+7C)  น่าจะเป็น Flag ที่อธิบายว่าให้โหลด Pack ไฟล์ หรือโหลด Resource ไฟล์ตรงๆ 
*ถ้าเป็น 0 โหลด ไฟล์ตรงๆ 
*ถ้าเป็น 1 โหลด Pack ไฟล์
และถ้า โหลด Pack ไฟล์ครบแล้วให้ Set  Flag ที่ mov byte ptr ds:[eax+7C],1
แต่ยังไม่ชัวส์ เรามา Break ที่ On Access Address 0xADE8E0  ดีกว่า ดูว่ามันจะ Break ตอนไหน 

Break ตอนโหลด Resource File จริงๆด้วย


จินตนาการเริ่มมีแววจริง 
กลับไปสนใจที่ Address ที่บังคับ Jump ข้ามไปจบ Function การโหลด SPF เลย 











มันเอาอะไร มาจาก Pointer 0xADE8E0 ?   แถม Return true ถ้า 0xADE8E0 =  0 อีกตะหาก

ดู Ref. มันแล้ว Break ให้หมด



ที่แรก! หลังจาก Run ใหม่ Set Flag เฉยๆ มันเป็น 1 อยู่แสดงว่า มันเข้า Loop โหลด SPF ทุกไฟล์ แน่ๆ 
แก้มันเป็น 0 ซะเลยลองไม่ให้มันโหลด SPF แล้วดูว่าจะเกิดอะไรขึ้น


ลองเปรียบเทียบ Function โหลด Resource หลังจากแก้ ตามรูปข้างบนไปแล้ว
แบบบังคับโหลด SPF
1. โหลด Resource ด้วย Function นึง   0xADE8E0 = 1


 2. โหลด Resource ด้วย Function นึง   0xADE8E0 = 0



สรุปมันโหลดเหมือนกัน แต่ คนละแบบ แบบนึงโหลดจาก File ตรงๆ อีกแบบนึง โหลดจาก Memory 

เพราะฉะนั้นแก้แค่ Flag 0xADE8E0 เป็น 0  มันก็ไม่โหลด Pack ไฟล์แล้ว