Welcome to Yumao′s Blog.
最近FFxiv的大陸服客戶端正式發佈
就突發奇想的能不能將客戶端拆包
提取出一部分的數據
或者直接提取出中文素材
能讓國際服調用這樣
所以就開始研究SqPack的拆包問題
這裏主要會先分析下SqPack的文件結構
每個大包由一個index(地址)文件和dat0(數據)文件組成
每個文件的開頭可以看到明碼的SqPack打包印記
在index文件中可以找到個別規律
用心找找就可以找到對應的地址碼
從而找到地步的文件列表
看起來index文件的開始行數在0x400地址
之前的數據大概都是頭說明以及一部分的sha1
這裏先將如何從index的頭找到下面對應的文件列表吧
先是從0x400開始 我們從0a0000.win32.index文件入手
可以看到內容如下
1 2 3 | 000400: 00 04 00 00 01 00 00 00 00 08 00 00 f0 e3 00 00 000410: 2e ec 33 ea 09 e7 e4 be 24 07 a4 8a 88 ae 0f 5d 000420: 80 cf 2c ed 00 00 00 00 00 00 00 00 00 00 00 00 |
然後就開始臆測文件數據含義
1 2 3 4 5 | 0x400 - 0x403 未知(可能是文件頭條數) 0x404 - 0x407 未知 0x408 - 0x40b 可能是文件的地址(經驗證的確是) 0x40c - 0x40f 可能是文件列表的長度 0x410 - 0x423 文件sha1散列 20 字節長度 |
然後從0x450開始又看到數據
按照之前的規律解析發現文件少了4字節頭
但是後面又是符合規則的
臆測第一部分有一部分未知數據去除
然後從0x49c開始又發現數據
但是發現之後的文件信息相較第一部分信息
都少了8個字節頭
也就是兩個Unknow
ok 這樣的話就可以找到指針規律了
然後就開始寫代碼解析下頭吧~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | static String filePath = "0a0000.win32.index" ; public static void main(String[] args) throws Exception { //數據量比較大 使用raf容易定位操作 RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r" ); long startPoint = 0x400 ; byte [] hexBytes; // 從sha1Hex位置判斷此列是否生效 randomAccessFile.seek(startPoint + 20 ); while (randomAccessFile.readByte() != 0 ) { // 此列有效 進行讀取 int length = getIndexSegmentCount(randomAccessFile,startPoint); // 設置相符的數組長度 hexBytes = new byte [length]; // 進行數據的轉儲 randomAccessFile.seek(startPoint); for ( int i = 0 ; i < length; i++) { hexBytes[i] = randomAccessFile.readByte(); } //數據處理出口 getFileSegmentLine(getIndexSegment(hexBytes),randomAccessFile); // 非常重要 偏移指針 進入下壹個列表項 startPoint = 44 + length + startPoint; randomAccessFile.seek(startPoint + 20 ); } randomAccessFile.close(); } private static void getFileSegmentLine( long [] ad,RandomAccessFile randomAccessFile) throws Exception{ //按照理論來說 列表長度爲16的整數倍 每條數據16個字節 //所以就直接定位到數據開始部分 進行16個字節劃分 randomAccessFile.seek(ad[ 0 ]); byte [] hexBytes = new byte [ 16 ]; for ( int j= 0 ;j<ad[ 1 ]/ 16 ;j++){ for ( long i= 0 ;i< 16 ;i++){ hexBytes[( int )i] = randomAccessFile.readByte(); } //處理數據 System.out.println(HexUtils.Bytes2HexString(hexBytes)); getFIleSegment(hexBytes); //減少數據進行測試 // break; } } private static int getIndexSegmentCount(RandomAccessFile randomAccessFile, long startPoint) throws Exception{ // 獲取此列的長度樣式 暫時發現壹共三個樣式 int length; randomAccessFile.seek(startPoint + 4 + 4 + 4 + 4 + 19 ); if (randomAccessFile.readByte() != 0 ) { length = 4 + 4 + 4 + 4 + 20 ; } else { randomAccessFile.seek(startPoint + 4 + 4 + 4 + 19 ); if (randomAccessFile.readByte() != 0 ) { length = 4 + 4 + 4 + 20 ; } else { randomAccessFile.seek(startPoint + 4 + 4 + 19 ); if (randomAccessFile.readByte() != 0 ) { length = 4 + 4 + 20 ; } else { length = 0 ; } } } return length; } private static long [] getIndexSegment( byte [] hex) { // 從byte[]提取文件列表起始地址以及長度 // 生成long數組 長度爲2 long l[] = { 0 , 0 }; // 臨時容器 byte [] tmp = new byte [ 8 ]; int co = 0 ; // 獲取數組長度 兼容各種類型 int length = hex.length; // sha1字符串長度爲20 所以可以截取到sha1之前的8位有效值 // 前四位是文件列表初始地址 後四位是文件列表的長度 for ( int i = length - 25 ; i > length - 25 - 4 ; i--) { // 地址 tmp[co++] = hex[i]; } for ( int i = length - 21 ; i > length - 21 - 4 ; i--) { // 長度 tmp[co++] = hex[i]; } l[ 0 ] = Long.parseLong(HexUtils.Bytes2HexString(tmp).replace( " " , "" ) .substring( 0 , 8 ), 16 ); l[ 1 ] = Long.parseLong(HexUtils.Bytes2HexString(tmp).replace( " " , "" ) .substring( 8 ), 16 ); return l; // 返回的long數組前地址後長度 } |
測試運行結果:
1 2 3 4 | 60 68 0E 00 0F 71 8E 08 30 76 0A 00 00 00 00 00 4D 75 27 01 0F 71 8E 08 80 5C 0A 00 00 00 00 00 26 20 E3 05 0F 71 8E 08 00 60 0A 00 00 00 00 00 05 56 42 06 0F 71 8E 08 00 E1 0A 00 00 00 00 00 |
順帶放出HexUtils的代碼
是自己寫的爲了容易測試使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | private final static byte [] hex = "0123456789ABCDEF" .getBytes(); private static int parse( char c) { if (c >= 'a' ) return (c - 'a' + 10 ) & 0x0f ; if (c >= 'A' ) return (c - 'A' + 10 ) & 0x0f ; return (c - '0' ) & 0x0f ; } // 從字節數組到十六進制字符串轉換 public static String Bytes2HexString( byte [] b) { byte [] buff = new byte [ 3 * b.length]; for ( int i = 0 ; i < b.length; i++) { buff[ 3 * i] = hex[(b[i] >> 4 ) & 0x0f ]; buff[ 3 * i + 1 ] = hex[b[i] & 0x0f ]; buff[ 3 * i + 2 ] = 45 ; } String re = new String(buff); return re.replace( "-" , " " ); } |