Skip to content

Commit

Permalink
[saco] Implement archive functions
Browse files Browse the repository at this point in the history
  • Loading branch information
dashr9230 committed Nov 8, 2023
1 parent 916d4d0 commit 8defa84
Show file tree
Hide file tree
Showing 20 changed files with 1,139 additions and 27 deletions.
85 changes: 85 additions & 0 deletions saco/archive/ArchiveCommon.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@

#pragma once

#include <windows.h>
#include "Stream.h"

// SAA_FILE_ID := {'S', 'A', 'M', 'P'} ignoring first 3 bits of each char
// first 3 bits are 010 anyhow :)
#define SAA_FILE_ID 0x83433
#define SAA_FILE_VERSION 2

#define SAA_MAX_ENTRIES 256

#define SAA_MAX_FAKEDATA 120

typedef struct _SAA_ENTRY
{
DWORD dwFileNameHash;
int field_4;
} SAA_ENTRY;

typedef struct _SAA_FILE_HEADER
{
// This is a fake header
struct VER1_HEADER
{
DWORD dwSAAV;
DWORD dwFileCount;
WORD wFakeData[SAA_MAX_FAKEDATA];
} headerV1; /* 248 bytes */

struct VER2_HEADER
{
union
{
struct
{
DWORD dwSAMPID : 20;
DWORD dwVersion : 3;
DWORD dwSignSize : 8;
DWORD dwPadding1 : 1;
};
DWORD dwCompleteID;
};
union
{
struct
{
DWORD dwPadding2 : 5;
DWORD dwInvalidIndex : 8;
DWORD dwPadding3 : 19;
};
DWORD dwXORKey;
};
} headerV2; /* 8 bytes */

DWORD dwFakeDataSize;

_SAA_FILE_HEADER()
{
dwFakeDataSize = SAA_MAX_FAKEDATA;
}

DWORD SizeOf()
{
return(sizeof(DWORD)*2 + sizeof(WORD)*dwFakeDataSize + sizeof(VER2_HEADER));
}

bool VerifyIdentifier()
{
return ((headerV2.dwSAMPID == SAA_FILE_ID) &&
(headerV2.dwVersion == SAA_FILE_VERSION));
}

void XorV2Identifier() {
this->headerV2.dwCompleteID ^= this->headerV2.dwXORKey;
}

void Read(CAbstractStream *pStream)
{
pStream->Read(&headerV1, sizeof(DWORD)*2 + sizeof(WORD)*dwFakeDataSize);
pStream->Read(&headerV2, sizeof(VER2_HEADER));
}

} SAA_FILE_HEADER;
128 changes: 128 additions & 0 deletions saco/archive/ArchiveFS.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@

#include "ArchiveFS.h"

#include "CryptoContext.h"
#include "KeyPair.h"
#include "Signer.h"
#include "Hasher.h"
#include "TinyEncrypt.h"

//------------------------------------

CArchiveFS::CArchiveFS(void)
{
m_dwNumEntries = SAA_MAX_ENTRIES;
m_bLoaded = false;
m_bEntriesLoaded = false;
}

//------------------------------------

CArchiveFS::CArchiveFS(DWORD dwNumEntries, DWORD dwFDSize)
{
m_dwNumEntries = dwNumEntries;
m_bLoaded = false;
m_bEntriesLoaded = false;

m_Header.dwFakeDataSize = dwFDSize;
}

//------------------------------------

void CArchiveFS::LoadEntries()
{
// Get the file signature, verify it... use the result to decode the entries table

// Verify the Archive Signature, and decode the Entry block
CCryptoContext context;
CKeyPair keyPair(&context);
CHasher hasher(&context);
CSigner signer;
CTinyEncrypt tinyEnc;
DWORD i;

// 1. Load the signature from the file
DWORD dwSignSize = 128; //m_Header.headerV2.dwSignSize;
BYTE *pbSignature;
DWORD dwSignDataEnd;

pbSignature = new BYTE[dwSignSize];
m_pStream->Seek(-(INT)dwSignSize, CAbstractStream::SeekEnd);
dwSignDataEnd = m_pStream->Tell();
m_pStream->Read(pbSignature, dwSignSize);

// 2. Hash the stuff (excluding the header and signature!)
BYTE *pbReadData;
DWORD dwReadSize;
const DWORD dwReadBlockSize = 10 * 1024; // 10kb

m_pStream->Seek(m_Header.SizeOf()); // start from the actual data section
pbReadData = new BYTE[dwReadBlockSize];
for(i=m_Header.SizeOf(); i<dwSignDataEnd; ) {
dwReadSize = m_pStream->Read(pbReadData, dwReadBlockSize);
if (i+dwReadSize > dwSignDataEnd)
hasher.AddData(dwSignDataEnd - i, pbReadData);
else
hasher.AddData(dwReadSize, pbReadData);
i += dwReadSize;
}
delete[] pbReadData;

// 3. Load the key and verify the signature
BOOL bVerified;

keyPair.LoadFromMemory(RSA_PUB_KEY_SIZE, (BYTE*)RSA_PUB_KEY, RSA_XOR_KEY);
signer.SetSignature(dwSignSize, pbSignature);
bVerified = signer.VerifySignature(&hasher, &keyPair);

delete[] pbSignature;

// Set the obfuscation decoding mask based on the bVerified value
m_dwObfsMask = -((INT)bVerified); // if its 1 (true), then 0xffffffff, else 0.

// 4. Decode the TEA encrypted archive entry

m_pStream->Seek((dwSignDataEnd - m_dwNumEntries*sizeof(SAA_ENTRY)));
DWORD dwFilePos = m_pStream->Tell();
m_pStream->Read(m_pEntries, sizeof(SAA_ENTRY), m_dwNumEntries);
dwFilePos = m_pStream->Tell();

tinyEnc.SetKey((BYTE*)TEA_KEY, TEA_XOR_KEY);
tinyEnc.DecryptData(sizeof(SAA_ENTRY)*m_dwNumEntries, reinterpret_cast<BYTE*>(m_pEntries));

// 5. Build a binary tree of the entries.. it makes searching for files faster (since we have a
// huge index with fake entries)
for(i=0; i<m_dwNumEntries; i++) {
m_EntryBTreeRoot.AddEntry(&m_pEntries[i]);
}

// Done.

m_bEntriesLoaded = true;
}

//------------------------------------

bool CArchiveFS::Load(char* szFileName)
{
if (m_bLoaded)
Unload();

m_pStream = new CFileStream(szFileName, CFileStream::TypeBinary, CFileStream::ModeRead);

m_Header.Read(m_pStream);

m_Header.XorV2Identifier();

m_bLoaded = true;

if (!m_Header.VerifyIdentifier()) {
Unload();
return false;
}

return true;
}

//------------------------------------

88 changes: 88 additions & 0 deletions saco/archive/ArchiveFS.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@

#pragma once

#include <windows.h>

#include "ArchiveCommon.h"
#include "Stream.h"

#include "../mod.h"

#include "../filesystem.h"

typedef struct _AFS_ENTRYBT_NODE
{
SAA_ENTRY* pEntry;
_AFS_ENTRYBT_NODE* pLNode;
_AFS_ENTRYBT_NODE* pRNode;
BYTE* pbData;

_AFS_ENTRYBT_NODE()
{
this->pEntry = NULL;
this->pLNode = NULL;
this->pRNode = NULL;
this->pbData = NULL;
}

_AFS_ENTRYBT_NODE(SAA_ENTRY* pSAAEntry)
{
this->pEntry = pSAAEntry;
this->pLNode = NULL;
this->pRNode = NULL;
this->pbData = NULL;
}

void AddEntry(SAA_ENTRY* pSAAEntry)
{
if (this->pEntry == NULL) {
this->pEntry = pSAAEntry;
} else {
if (pSAAEntry->dwFileNameHash < this->pEntry->dwFileNameHash) {
if (this->pLNode == NULL)
this->pLNode = new _AFS_ENTRYBT_NODE(pSAAEntry);
else
this->pLNode->AddEntry(pSAAEntry);
} else {
if (this->pRNode == NULL)
this->pRNode = new _AFS_ENTRYBT_NODE(pSAAEntry);
else
this->pRNode->AddEntry(pSAAEntry);
}
}
}

} AFS_ENTRYBT_NODE;

class CArchiveFS // size: 2357
: public CFileSystem
{
private:
bool m_bLoaded;
CAbstractStream *m_pStream;
bool m_bEntriesLoaded;
SAA_FILE_HEADER m_Header;
SAA_ENTRY m_pEntries[SAA_MAX_ENTRIES];
AFS_ENTRYBT_NODE m_EntryBTreeRoot;
DWORD m_dwObfsMask;
DWORD m_dwNumEntries;

void LoadEntries();

public:
CArchiveFS(void);
CArchiveFS(DWORD dwNumEntries, DWORD dwFDSize);

virtual bool Load(char* szFileName);

// 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() {};
};
61 changes: 61 additions & 0 deletions saco/archive/CryptoContext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

#include "CryptoContext.h"
#include "CryptoFns.h"

//------------------------------------

DWORD CCryptoContext::ms_dwRefCount = 0;
DWORD CCryptoContext::ms_dwProviderType = PROV_RSA_FULL;
LPTSTR CCryptoContext::ms_szProviderName = NULL;
LPTSTR CCryptoContext::ms_szContainerName = (LPTSTR)"SAMP";

//------------------------------------

CCryptoContext::CCryptoContext(void)
{
/*
if (!ms_hAdvApi32)
{
ms_hAdvApi32 = LoadLibrary("advapi32.dll");
}
*/

// Open existing context, if not found, create one!
if ( !CRYPT(AcquireContext)(&m_hCryptProv, ms_szContainerName, ms_szProviderName, ms_dwProviderType, 0) ) {
if( !CRYPT(AcquireContext)(&m_hCryptProv, ms_szContainerName, ms_szProviderName, ms_dwProviderType, CRYPT_NEWKEYSET) ) {
throw(1);
}
}

ms_dwRefCount++;

}

//------------------------------------

CCryptoContext::~CCryptoContext(void)
{
// Release the context
CRYPT(ReleaseContext)(m_hCryptProv, 0);
ms_dwRefCount--;

if (ms_dwRefCount == 0) {
/*
// Free the library
if (ms_hAdvApi32)
FreeLibrary(ms_hAdvApi32);
*/

// Delete the context
CRYPT(AcquireContext)(&m_hCryptProv, ms_szContainerName, ms_szProviderName, ms_dwProviderType, CRYPT_DELETEKEYSET);
}
}

//------------------------------------

HCRYPTPROV CCryptoContext::GetProvider()
{
return m_hCryptProv;
}

//------------------------------------
21 changes: 21 additions & 0 deletions saco/archive/CryptoContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

#pragma once

#include <windows.h>

class CCryptoContext
{
private:
static DWORD ms_dwRefCount;
static DWORD ms_dwProviderType;
static LPTSTR ms_szProviderName;
static LPTSTR ms_szContainerName;

HCRYPTPROV m_hCryptProv;

public:
CCryptoContext(void);
~CCryptoContext(void);

HCRYPTPROV GetProvider();
};
3 changes: 3 additions & 0 deletions saco/archive/CryptoFns.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

#define CRYPT_FN_CPP
#include "CryptoFns.h"
Loading

0 comments on commit 8defa84

Please sign in to comment.