From 17aa0c8eee832882239dc350b5e0db3befd1c3c3 Mon Sep 17 00:00:00 2001 From: RD42 <42702181+dashr9230@users.noreply.github.com> Date: Tue, 20 Feb 2024 23:37:17 +0800 Subject: [PATCH] [saco] Implement CArchiveFS class member functions --- saco/archive/ArchiveCommon.h | 11 +- saco/archive/ArchiveFS.cpp | 249 +++++++++++++++++++++++++++++++++++ saco/archive/ArchiveFS.h | 53 ++++++-- 3 files changed, 302 insertions(+), 11 deletions(-) diff --git a/saco/archive/ArchiveCommon.h b/saco/archive/ArchiveCommon.h index 70b83ee..9349421 100644 --- a/saco/archive/ArchiveCommon.h +++ b/saco/archive/ArchiveCommon.h @@ -8,6 +8,7 @@ // first 3 bits are 010 anyhow :) #define SAA_FILE_ID 0x83433 #define SAA_FILE_VERSION 2 +#define SAA_BLOCK_SIZE 2048 #define SAA_MAX_ENTRIES 256 @@ -16,7 +17,15 @@ typedef struct _SAA_ENTRY { DWORD dwFileNameHash; - int field_4; + union + { + struct + { + DWORD dwPrevEntry : 8; // index to previous entry (link to fake entry if none) + DWORD dwFileSize : 24; // 24bits = max filesize of 16mb + }; + DWORD dwDataBlock; + }; } SAA_ENTRY; typedef struct _SAA_FILE_HEADER diff --git a/saco/archive/ArchiveFS.cpp b/saco/archive/ArchiveFS.cpp index cf0a992..8067b37 100644 --- a/saco/archive/ArchiveFS.cpp +++ b/saco/archive/ArchiveFS.cpp @@ -6,6 +6,11 @@ #include "Signer.h" #include "Hasher.h" #include "TinyEncrypt.h" +#include "Obfuscator.h" + +//------------------------------------ + +DWORD CArchiveFS::ms_dwHashInit = OBFUSCATE_DATA(0x9E3779B9); //------------------------------------ @@ -29,6 +34,77 @@ CArchiveFS::CArchiveFS(DWORD dwNumEntries, DWORD dwFDSize) //------------------------------------ +CArchiveFS::~CArchiveFS(void) +{ +} + +//------------------------------------ + +DWORD CArchiveFS::HashString(PCHAR szString) +{ + // This is an implementation of the Jenkins hash + +# define mix(a,b,c) \ + { \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ + } + + register BYTE* k = (BYTE*)szString; + register DWORD initval = 0x12345678; + register DWORD length; + + length = (DWORD)strlen(szString); + + register DWORD a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = UNOBFUSCATE_DATA(ms_dwHashInit); /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) + { + a += (k[0] +((DWORD)k[1]<<8) +((DWORD)k[2]<<16) +((DWORD)k[3]<<24)); + b += (k[4] +((DWORD)k[5]<<8) +((DWORD)k[6]<<16) +((DWORD)k[7]<<24)); + c += (k[8] +((DWORD)k[9]<<8) +((DWORD)k[10]<<16)+((DWORD)k[11]<<24)); + mix(a,b,c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 11: c+=((DWORD)k[10]<<24); + case 10: c+=((DWORD)k[9]<<16); + case 9 : c+=((DWORD)k[8]<<8); + /* the first byte of c is reserved for the length */ + case 8 : b+=((DWORD)k[7]<<24); + case 7 : b+=((DWORD)k[6]<<16); + case 6 : b+=((DWORD)k[5]<<8); + case 5 : b+=k[4]; + case 4 : a+=((DWORD)k[3]<<24); + case 3 : a+=((DWORD)k[2]<<16); + case 2 : a+=((DWORD)k[1]<<8); + case 1 : a+=k[0]; + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +//------------------------------------ + void CArchiveFS::LoadEntries() { // Get the file signature, verify it... use the result to decode the entries table @@ -126,3 +202,176 @@ bool CArchiveFS::Load(char* szFileName) //------------------------------------ +bool CArchiveFS::Load(BYTE* pbData, DWORD nLength) +{ + if (m_bLoaded) + Unload(); + + m_pStream = new CMemoryStream(pbData, nLength); + + m_Header.Read(m_pStream); + + m_Header.XorV2Identifier(); + + m_bLoaded = true; + + if (!m_Header.VerifyIdentifier()) { + Unload(); + return false; + } + + return true; +} + +//------------------------------------ + +void CArchiveFS::Unload() +{ + if (!m_bLoaded) + return; + + delete m_pStream; + m_pStream = NULL; + + m_bLoaded = false; + m_bEntriesLoaded = false; +} + +//------------------------------------ + +DWORD CArchiveFS::GetFileIndex(DWORD dwFileHash) +{ + if (!m_bEntriesLoaded) + LoadEntries(); + + AFS_ENTRYBT_NODE* node = m_EntryBTreeRoot.FindEntry(dwFileHash); + + if (node == NULL) { + return FS_INVALID_FILE; + } + + SAA_ENTRY saaEntry = *(node->pEntry); // Always make a copy of saaEntry before decrypting it + // Otherwise, the data decryption will get messed up + + saaEntry.dwDataBlock = UNOBFUSCATE_DATA(saaEntry.dwDataBlock) ^ (saaEntry.dwFileNameHash & this->m_dwObfsMask); + if (node->pEntry == &(m_pEntries[saaEntry.dwPrevEntry])) + return FS_INVALID_FILE; + + // Okay, we got a file. + // TODO: It might be wise at this point to start a thread to decrypt the data + // Chances are if the index was requested, data for it will be requested. + + // Painfully evil conversion from SAA_ENTRY* to DWORD + DWORD* ppEntry = reinterpret_cast(&node); + return *ppEntry; +} + +//------------------------------------ + +DWORD CArchiveFS::GetFileIndex(char* szFileName) +{ + // PRE: szFileName must be the filename only (no paths!) + + if (!m_bEntriesLoaded) + LoadEntries(); + + CHAR szFileNameLC[MAX_PATH]; + strcpy(szFileNameLC, szFileName); + _strlwr(szFileNameLC); + + DWORD dwHash = this->HashString(szFileNameLC); + + DWORD dwIndex = GetFileIndex(dwHash); + +#ifdef _DEBUG + if (dwIndex != FS_INVALID_FILE) + { + CHAR szDebugMsg[1024]; + sprintf(szDebugMsg, "ArchiveFS: Requested file: %s...\n", szFileNameLC); + OutputDebugString(szDebugMsg); + } +#endif + + return dwIndex; +} + +//------------------------------------ + +DWORD CArchiveFS::GetFileSize(DWORD dwFileIndex) +{ + AFS_ENTRYBT_NODE* node = *(reinterpret_cast(&dwFileIndex)); + + SAA_ENTRY saaEntry = *(node->pEntry); // Make a copy! + saaEntry.dwDataBlock = UNOBFUSCATE_DATA(saaEntry.dwDataBlock) ^ (saaEntry.dwFileNameHash & this->m_dwObfsMask); + return saaEntry.dwFileSize; +} + +//------------------------------------ + +BYTE* CArchiveFS::GetFileData(DWORD dwFileIndex) +{ + CTinyEncrypt tinyEnc; + + AFS_ENTRYBT_NODE* node = *(reinterpret_cast(&dwFileIndex)); + + SAA_ENTRY saaEntry = *(node->pEntry); // Make a copy! + saaEntry.dwDataBlock = UNOBFUSCATE_DATA(saaEntry.dwDataBlock) ^ (saaEntry.dwFileNameHash & this->m_dwObfsMask); + + DWORD dwFileSize; + + if (node->pbData != NULL) { + return node->pbData; + } else { + // Alloc memory (in blocks!) + dwFileSize = saaEntry.dwFileSize; + if (dwFileSize % SAA_BLOCK_SIZE != 0) + dwFileSize += (SAA_BLOCK_SIZE - (dwFileSize % SAA_BLOCK_SIZE)); + + node->pbData = new BYTE[dwFileSize]; + + // Determine offset to data + SAA_ENTRY prevEntry; + DWORD dwDataOffset = m_Header.SizeOf(); + + if (saaEntry.dwPrevEntry != m_Header.headerV2.dwInvalidIndex) { + prevEntry = saaEntry; + do { + prevEntry = m_pEntries[prevEntry.dwPrevEntry]; + prevEntry.dwDataBlock = UNOBFUSCATE_DATA(prevEntry.dwDataBlock) ^ (prevEntry.dwFileNameHash & this->m_dwObfsMask); + + dwFileSize = prevEntry.dwFileSize; + if (dwFileSize % SAA_BLOCK_SIZE != 0) + dwFileSize += (SAA_BLOCK_SIZE - (dwFileSize % SAA_BLOCK_SIZE)); + dwDataOffset += dwFileSize; + + } while(prevEntry.dwPrevEntry != m_Header.headerV2.dwInvalidIndex); + } + + m_pStream->Seek(dwDataOffset); + + // Load the data in blocks and decrypt it + BYTE* pbTEAKey = reinterpret_cast(this->m_pEntries) + + (saaEntry.dwFileNameHash % (sizeof(SAA_ENTRY)*m_dwNumEntries-TEA_KEY_SIZE)); + + tinyEnc.SetKey(pbTEAKey, 0); + + for(DWORD i=0; iRead(node->pbData+i, SAA_BLOCK_SIZE); + tinyEnc.DecryptData(SAA_BLOCK_SIZE, node->pbData+i); + } + + return node->pbData; + + } +} + +void CArchiveFS::UnloadData(DWORD dwFileIndex) +{ + AFS_ENTRYBT_NODE* node = *(reinterpret_cast(&dwFileIndex)); + + if (node->pbData != NULL) + { + delete[] node->pbData; + node->pbData = NULL; + } +} diff --git a/saco/archive/ArchiveFS.h b/saco/archive/ArchiveFS.h index 98ce22f..e86bac6 100644 --- a/saco/archive/ArchiveFS.h +++ b/saco/archive/ArchiveFS.h @@ -10,6 +10,8 @@ #include "../filesystem.h" +#define FS_INVALID_FILE 0xFFFFFFFF + typedef struct _AFS_ENTRYBT_NODE { SAA_ENTRY* pEntry; @@ -52,6 +54,35 @@ typedef struct _AFS_ENTRYBT_NODE } } + _AFS_ENTRYBT_NODE* FindEntry(DWORD dwHash) + { + if (this->pEntry->dwFileNameHash == dwHash) { + return this; + } else { + if (dwHash < this->pEntry->dwFileNameHash) { + if (this->pLNode == NULL) + return NULL; + else + return this->pLNode->FindEntry(dwHash); + } else { + if (this->pRNode == NULL) + return NULL; + else + return this->pRNode->FindEntry(dwHash); + } + } + } + + ~_AFS_ENTRYBT_NODE() + { + if (this->pLNode != NULL) + delete this->pLNode; + if (this->pRNode != NULL) + delete this->pRNode; + if (this->pbData != NULL) + delete[] this->pbData; + } + } AFS_ENTRYBT_NODE; class CArchiveFS // size: 2357 @@ -69,20 +100,22 @@ private: void LoadEntries(); + static DWORD ms_dwHashInit; + DWORD HashString(PCHAR szString); + public: CArchiveFS(void); CArchiveFS(DWORD dwNumEntries, DWORD dwFDSize); + virtual ~CArchiveFS(void); virtual bool Load(char* szFileName); + virtual bool Load(BYTE* pbData, DWORD nLength); + virtual void Unload(); - // TODO: CArchiveFS vftable 100E9AA8 - void CArchiveFS__sub_10065590() {}; - void CArchiveFS__sub_100654A0() {}; - void CArchiveFS__sub_10064E10() {}; - void CArchiveFS__sub_10064EC0() {}; - void CArchiveFS__sub_10064F20() {}; - void CArchiveFS__sub_10064F60() {}; - void CArchiveFS__sub_10064D30() {}; - void CArchiveFS__sub_10064E40() {}; - void CArchiveFS__sub_10065150() {}; + virtual DWORD GetFileIndex(DWORD dwFileHash); + virtual DWORD GetFileIndex(char* szFileName); + virtual DWORD GetFileSize(DWORD dwFileIndex); + virtual BYTE* GetFileData(DWORD dwFileIndex); + + virtual void UnloadData(DWORD dwFileIndex); };