[saco] Implement CArchiveFS class member functions

This commit is contained in:
RD42 2024-02-20 23:37:17 +08:00
parent d0e488b35e
commit 17aa0c8eee
3 changed files with 302 additions and 11 deletions

View File

@ -8,6 +8,7 @@
// first 3 bits are 010 anyhow :) // first 3 bits are 010 anyhow :)
#define SAA_FILE_ID 0x83433 #define SAA_FILE_ID 0x83433
#define SAA_FILE_VERSION 2 #define SAA_FILE_VERSION 2
#define SAA_BLOCK_SIZE 2048
#define SAA_MAX_ENTRIES 256 #define SAA_MAX_ENTRIES 256
@ -16,7 +17,15 @@
typedef struct _SAA_ENTRY typedef struct _SAA_ENTRY
{ {
DWORD dwFileNameHash; 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; } SAA_ENTRY;
typedef struct _SAA_FILE_HEADER typedef struct _SAA_FILE_HEADER

View File

@ -6,6 +6,11 @@
#include "Signer.h" #include "Signer.h"
#include "Hasher.h" #include "Hasher.h"
#include "TinyEncrypt.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() void CArchiveFS::LoadEntries()
{ {
// Get the file signature, verify it... use the result to decode the entries table // 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<DWORD*>(&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<AFS_ENTRYBT_NODE**>(&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<AFS_ENTRYBT_NODE**>(&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<BYTE*>(this->m_pEntries) +
(saaEntry.dwFileNameHash % (sizeof(SAA_ENTRY)*m_dwNumEntries-TEA_KEY_SIZE));
tinyEnc.SetKey(pbTEAKey, 0);
for(DWORD i=0; i<saaEntry.dwFileSize; i+=SAA_BLOCK_SIZE) {
m_pStream->Read(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<AFS_ENTRYBT_NODE**>(&dwFileIndex));
if (node->pbData != NULL)
{
delete[] node->pbData;
node->pbData = NULL;
}
}

View File

@ -10,6 +10,8 @@
#include "../filesystem.h" #include "../filesystem.h"
#define FS_INVALID_FILE 0xFFFFFFFF
typedef struct _AFS_ENTRYBT_NODE typedef struct _AFS_ENTRYBT_NODE
{ {
SAA_ENTRY* pEntry; 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; } AFS_ENTRYBT_NODE;
class CArchiveFS // size: 2357 class CArchiveFS // size: 2357
@ -69,20 +100,22 @@ private:
void LoadEntries(); void LoadEntries();
static DWORD ms_dwHashInit;
DWORD HashString(PCHAR szString);
public: public:
CArchiveFS(void); CArchiveFS(void);
CArchiveFS(DWORD dwNumEntries, DWORD dwFDSize); CArchiveFS(DWORD dwNumEntries, DWORD dwFDSize);
virtual ~CArchiveFS(void);
virtual bool Load(char* szFileName); virtual bool Load(char* szFileName);
virtual bool Load(BYTE* pbData, DWORD nLength);
virtual void Unload();
// TODO: CArchiveFS vftable 100E9AA8 virtual DWORD GetFileIndex(DWORD dwFileHash);
void CArchiveFS__sub_10065590() {}; virtual DWORD GetFileIndex(char* szFileName);
void CArchiveFS__sub_100654A0() {}; virtual DWORD GetFileSize(DWORD dwFileIndex);
void CArchiveFS__sub_10064E10() {}; virtual BYTE* GetFileData(DWORD dwFileIndex);
void CArchiveFS__sub_10064EC0() {};
void CArchiveFS__sub_10064F20() {}; virtual void UnloadData(DWORD dwFileIndex);
void CArchiveFS__sub_10064F60() {};
void CArchiveFS__sub_10064D30() {};
void CArchiveFS__sub_10064E40() {};
void CArchiveFS__sub_10065150() {};
}; };