From a630a523e176d0f7d011b0a34dcbd081f4fcd07c Mon Sep 17 00:00:00 2001 From: RD42 <42702181+dashr9230@users.noreply.github.com> Date: Thu, 23 May 2024 23:22:12 +0800 Subject: [PATCH] [arctool2] Add archive tool --- arctool2/ArchiveBuilder.cpp | 454 +++++++++++++++++++++++++++++++++ arctool2/ArchiveBuilder.h | 66 +++++ arctool2/arctool2.cpp | 340 ++++++++++++++++++++++++ arctool2/arctool2.sln | 21 ++ arctool2/arctool2.vcproj | 169 ++++++++++++ saco/archive/ArchiveCommon.h | 22 ++ saco/archive/ArchiveFS.h | 9 +- saco/archive/CryptoContext.cpp | 7 + saco/archive/CryptoContext.h | 5 + saco/archive/KeyPair.cpp | 105 ++++++++ saco/archive/KeyPair.h | 7 + saco/archive/Signer.cpp | 33 +++ saco/archive/Signer.h | 6 + saco/archive/TinyEncrypt.cpp | 93 +++++++ saco/archive/TinyEncrypt.h | 12 + 15 files changed, 1348 insertions(+), 1 deletion(-) create mode 100644 arctool2/ArchiveBuilder.cpp create mode 100644 arctool2/ArchiveBuilder.h create mode 100644 arctool2/arctool2.cpp create mode 100644 arctool2/arctool2.sln create mode 100644 arctool2/arctool2.vcproj diff --git a/arctool2/ArchiveBuilder.cpp b/arctool2/ArchiveBuilder.cpp new file mode 100644 index 0000000..d51c6e3 --- /dev/null +++ b/arctool2/ArchiveBuilder.cpp @@ -0,0 +1,454 @@ + +#include "ArchiveBuilder.h" + +#include "../saco/archive/CryptoContext.h" +#include "../saco/archive/KeyPair.h" +#include "../saco/archive/Signer.h" +#include "../saco/archive/Hasher.h" +#include "../saco/archive/TinyEncrypt.h" +#include "../saco/archive/Obfuscator.h" + +#include + +#define DO_ENCRYPT + +//------------------------------------ + +DWORD CArchiveBuilder::ms_dwHashInit = OBFUSCATE_DATA(0x9E3779B9); + +//------------------------------------ + +CArchiveBuilder::CArchiveBuilder(void) +{ + m_dwNumEntries = SAA_MAX_ENTRIES; + m_dwFDSize = SAA_MAX_FAKEDATA; + m_bProperHeader = TRUE; +} + +//------------------------------------ + +CArchiveBuilder::CArchiveBuilder(DWORD dwNumEntries, DWORD dwFDSize, BOOL bProperHeader) +{ + m_dwNumEntries = dwNumEntries; + m_dwFDSize = dwFDSize; + m_bProperHeader = bProperHeader; +} + +//------------------------------------ + +CArchiveBuilder::~CArchiveBuilder(void) +{ + if (!m_vecEntries.empty()) { + EntryMemStateVector_t::iterator it = m_vecEntries.begin(); + while(it != m_vecEntries.end()) { + AB_ENTRY_MEMSTATE* pEntry = (*it); + fclose(pEntry->fiFile); + delete pEntry; + it++; + } + } +} + +//------------------------------------ + +DWORD CArchiveBuilder::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; + +} + +//------------------------------------ + +PCHAR CArchiveBuilder::ExtractFileName(PCHAR szString) +{ + DWORD dwOffset = (DWORD)strlen(szString); + while((dwOffset > 0) && (szString[dwOffset] != '\\')) + dwOffset--; + if (dwOffset == 0) + return szString; + else + return szString+dwOffset+1; +} + +//------------------------------------ + +DWORD CArchiveBuilder::AddFile(PCHAR szFileName) +{ + + AB_ENTRY_MEMSTATE* pEntry = new AB_ENTRY_MEMSTATE(); + + PCHAR szNameOnly = ExtractFileName(szFileName); + if ((strlen(szNameOnly)+1) > sizeof(pEntry->szFileName)) + throw "Internal Error: Not enough memory allocated for the length of the filename."; + + strcpy(pEntry->szFileName, szNameOnly); + _strlwr(pEntry->szFileName); + pEntry->dwFileNameHash = HashString(pEntry->szFileName); + + FILE* fiFile; + fiFile = fopen(szFileName, "rb"); + if (!fiFile) + throw "Could not load archive data file."; + + pEntry->fiFile = fiFile; + + fseek(fiFile, 0, SEEK_END); + pEntry->dwFileSize = (DWORD)ftell(fiFile); + + fseek(fiFile, 0, SEEK_SET); + + if (pEntry->dwFileSize > 16*1024*1024) + throw "Archive data file is greater than 16MB."; + + m_vecEntries.push_back(pEntry); + + return pEntry->dwFileNameHash; + +} + +//------------------------------------ + +void CArchiveBuilder::BuildEntryTable(SAA_ENTRY *pEntryTable, DWORD dwInvalidIndex) +{ + CCryptoContext context; + BOOL* pbEntryUsed; + + EntryMemStateVector_t::iterator it; + DWORD dwIndex; + AB_ENTRY_MEMSTATE* pEntry; + + // Create some stuff to make things simple + pbEntryUsed = new BOOL[m_dwNumEntries]; + memset(pbEntryUsed, 0, sizeof(BOOL)*m_dwNumEntries); + + // Make the entire table's data random + context.GenerateRandom(m_dwNumEntries*sizeof(SAA_ENTRY), reinterpret_cast(pEntryTable)); + + // Ensure that there are no hash collisions between whats in the entry table and our files + BOOL bCollision = FALSE; + for(DWORD i=0; idwFileNameHash) { + pEntryTable[i].dwFileNameHash = GetRandom(); + bCollision = TRUE; + break; + } + + it++; + } + + if (bCollision) { + bCollision = FALSE; + i--; + } + } + + /* + // Ver1: Set all the invalid entries to the invalid index + for(DWORD i=0; i> 5) & 0xFF; // Take it from the middle! + dwIndex %= m_dwNumEntries; + + // Check if it matches our reserved index for invalid files + if (dwIndex == dwInvalidIndex) + continue; + + // Check to see if any existing entries have this index + if (pbEntryUsed[dwIndex]) + continue; + + break; + } + + pbEntryUsed[dwIndex] = true; + + pEntry->pEntry = &(pEntryTable[dwIndex]); + pEntry->dwEntryTableOffset = dwIndex; + pEntry->pbTEAKey = reinterpret_cast(pEntryTable) + + (pEntry->dwFileNameHash % (sizeof(SAA_ENTRY)*m_dwNumEntries-TEA_KEY_SIZE)); + + pEntry->pEntry->dwFileNameHash = pEntry->dwFileNameHash; + pEntry->pEntry->dwFileSize = pEntry->dwFileSize; + + if (it == m_vecEntries.begin()) { + // Ver2: Set the first index to the invalid index + pEntry->pEntry->dwPrevEntry = dwInvalidIndex; + } else { + pEntry->pEntry->dwPrevEntry = (*(it-1))->dwEntryTableOffset; + } + + it++; + } + + /* + // Ver1: + // Process the first entry finally and set its previous index to + // an invalid entry, but not to our invalid index + it = m_vecEntries.begin(); + while(1) { + dwIndex = (GetRandom() >> 5) & 0xFF; // Take it from the middle! + + // Check if it matches our reserved index for invalid files + if (dwIndex == dwInvalidIndex) + continue; + + // Check to see if any existing entries have this index + if (pbEntryUsed[dwIndex]) + continue; + + break; + } + (*it)->pEntry->dwPrevEntry = dwIndex; + */ + + // Now obfuscate the non-hash part of all our entries + // XOR it with the hash for good measure. + for(DWORD i=0; iSizeOf(), reinterpret_cast(pFileHeader)); + + pFileHeader->dwFakeDataSize = this->m_dwFDSize; + + if (this->m_bProperHeader) + pFileHeader->InitializeDataV1(); + + pFileHeader->InitializeDataV2(); + + pFileHeader->headerV2.dwInvalidIndex %= m_dwNumEntries; + +} + +//------------------------------------ + +void CArchiveBuilder::BuildEntryData(FILE *fiArchive) +{ + CCryptoContext context; + EntryMemStateVector_t::iterator it; + const DWORD dwReadBlockSize = SAA_BLOCK_SIZE; + CTinyEncrypt tinyEncrypt; + + BYTE *pbReadData = new BYTE[dwReadBlockSize]; + + it = m_vecEntries.begin(); + while(it != m_vecEntries.end()) { + + AB_ENTRY_MEMSTATE* pEntry = (*it); + + tinyEncrypt.SetKey(pEntry->pbTEAKey, 0); + + DWORD dwReadSize; + for(DWORD i=0; idwFileSize; i+=dwReadBlockSize) { + dwReadSize = (DWORD)fread(pbReadData, 1, dwReadBlockSize, pEntry->fiFile); + if (dwReadSize < dwReadBlockSize) { + context.GenerateRandom(dwReadBlockSize-dwReadSize, pbReadData+dwReadSize); + } +#ifdef DO_ENCRYPT + tinyEncrypt.EncryptData(dwReadBlockSize, pbReadData); +#endif + fwrite(pbReadData, 1, dwReadBlockSize, fiArchive); + } + + it++; + + } + + delete[] pbReadData; + +} + +//------------------------------------ + +void CArchiveBuilder::SignArchive(FILE *fiArchive, DWORD dwSignatureOffset, SAA_FILE_HEADER *pHeader) +{ + + // Sign the archive file + + CCryptoContext context; + CKeyPair keyPair(&context); + CHasher hasher(&context); + CSigner signer; + + // 1. Hash the stuff + fseek(fiArchive, pHeader->SizeOf(), SEEK_SET); // start from the actual data section + const DWORD dwReadBlockSize = 10 * 1024; // 10kb + BYTE *pbReadData = new BYTE[dwReadBlockSize]; + DWORD dwReadSize; + DWORD dwPos = ftell(fiArchive); + while(!feof(fiArchive)) { + dwReadSize = (DWORD)fread(pbReadData, 1, dwReadBlockSize, fiArchive); + hasher.AddData(dwReadSize, pbReadData); + dwPos = ftell(fiArchive); + } + delete[] pbReadData; + + // 2. Load the key, and sign the hash + keyPair.LoadFromFile("pkey.bin"); + signer.SignHash(&hasher); + keyPair.ReleaseKey(); + + // 3. Write the signature + pHeader->headerV2.dwSignSize = signer.GetSignatureLength(); + fseek(fiArchive, dwSignatureOffset, SEEK_SET); + fwrite(signer.GetSignature(), 1, pHeader->headerV2.dwSignSize, fiArchive); + +} + +//------------------------------------ + +void CArchiveBuilder::WriteArchive(PCHAR szFileName) +{ + SAA_FILE_HEADER header; + SAA_ENTRY *pEntryTable; + FILE* fiArchive; + + fiArchive = fopen(szFileName, "w+b"); + + pEntryTable = new SAA_ENTRY[m_dwNumEntries]; + + // 1. Build the header + BuildHeader(&header); + + // 2. Build the entry table + BuildEntryTable(pEntryTable, header.headerV2.dwInvalidIndex); + + // 3. Encode and write the file data + fseek(fiArchive, header.SizeOf(), SEEK_SET); + BuildEntryData(fiArchive); + + // 4. Encrypt and write the entry table + // (Note that the file data decryption must be done with decrypted entry table used for keys) + CTinyEncrypt tinyEnc; + tinyEnc.LoadKey("skey.bin"); +#ifdef DO_ENCRYPT + tinyEnc.EncryptData(sizeof(SAA_ENTRY)*m_dwNumEntries, reinterpret_cast(pEntryTable)); +#endif + fwrite(pEntryTable, sizeof(SAA_ENTRY), m_dwNumEntries, fiArchive); + + // 5. Sign the data (excluding the header) + fflush(fiArchive); + SignArchive(fiArchive, (DWORD)ftell(fiArchive), &header); + + // 6. Finish the header and write it +#ifdef DO_ENCRYPT + header.XorV2Identifier(); +#endif + header.Write(fiArchive); + + // And we're done. + fclose(fiArchive); + + delete[] pEntryTable; + +} + +//------------------------------------ diff --git a/arctool2/ArchiveBuilder.h b/arctool2/ArchiveBuilder.h new file mode 100644 index 0000000..a7d9697 --- /dev/null +++ b/arctool2/ArchiveBuilder.h @@ -0,0 +1,66 @@ + +#pragma once + +#include +#include +#include + +#include "../saco/archive/ArchiveCommon.h" + +#pragma pack(1) + +typedef struct _AB_ENTRY_MEMSTATE +{ + SAA_ENTRY* pEntry; + + DWORD dwEntryTableOffset; + BYTE* pbTEAKey; + + CHAR szFileName[32]; + DWORD dwFileNameHash; + DWORD dwFileSize; + FILE* fiFile; + + _AB_ENTRY_MEMSTATE() + { + dwEntryTableOffset = 0; + } + +} AB_ENTRY_MEMSTATE; + +#pragma pack() + +typedef std::vector EntryMemStateVector_t; + +class CArchiveBuilder +{ +private: + DWORD m_dwNumEntries; + DWORD m_dwFDSize; + BOOL m_bProperHeader; + + EntryMemStateVector_t m_vecEntries; + + PCHAR m_szPrivKeyFile; + PCHAR m_szTEAKeyFile; + + static DWORD ms_dwHashInit; + DWORD HashString(PCHAR szString); + PCHAR ExtractFileName(PCHAR szString); + DWORD GetRandom(); + + void BuildHeader(SAA_FILE_HEADER* pFileHeader); + void BuildEntryTable(SAA_ENTRY* pEntryTable, DWORD dwInvalidIndex); + void BuildEntryData(FILE* fiArchive); + void SignArchive(FILE *fiArchive, DWORD dwSignatureOffset, SAA_FILE_HEADER *pHeader); + +public: + CArchiveBuilder(void); + CArchiveBuilder(DWORD dwNumEntries, DWORD dwFDSize, BOOL bProperHeader); + ~CArchiveBuilder(void); + + void SetKeyFile(PCHAR szPrivKeyFile, PCHAR szTEAKeyFile); + + DWORD AddFile(PCHAR szFileName); + void WriteArchive(PCHAR szFileName); +}; diff --git a/arctool2/arctool2.cpp b/arctool2/arctool2.cpp new file mode 100644 index 0000000..f6ed750 --- /dev/null +++ b/arctool2/arctool2.cpp @@ -0,0 +1,340 @@ + +#include +#include +#include "../saco/archive/CryptoContext.h" +#include "../saco/archive/KeyPair.h" +#include "../saco/archive/TinyEncrypt.h" +#include "../saco/archive/ArchiveFS.h" +#include "ArchiveBuilder.h" + +void CreateArchive(PCHAR szFileList, PCHAR szArchive) +{ + + CArchiveBuilder archiveBuilder; + + // Load the file list + FILE* fiList; + fiList = fopen(szFileList, "rb"); + if (!fiList) + throw "Could not find file list."; + fseek(fiList, 0, SEEK_END); + DWORD dwFileListSize = ftell(fiList); + CHAR* szFiles = new CHAR[dwFileListSize+1]; + fseek(fiList, 0, SEEK_SET); + fread(szFiles, 1, dwFileListSize, fiList); + szFiles[dwFileListSize] = 0; + fclose(fiList); + + // Load the files and stuff + CHAR* szToken; + szToken = strtok(szFiles, "\r\n"); + while(szToken != NULL) { + if (szToken[0] != 0 && szToken[0] != '#') { + printf("Adding file: %s...\t", szToken); + DWORD hash = archiveBuilder.AddFile(szToken); + printf(" Added, file hash=0x%x\n", hash); + } + szToken = strtok(NULL, "\r\n"); + } + + // Write the archive + printf("Writing archive...\n"); + archiveBuilder.WriteArchive(szArchive); + + // Clean up + delete[] szFiles; + +} + + +void CreateArchiveSingle(PCHAR szFile, PCHAR szArchive) +{ + + CArchiveBuilder archiveBuilder(4, 0, FALSE); + + // Load the file + printf("Adding file: %s...\t", szFile); + DWORD hash = archiveBuilder.AddFile(szFile); + printf(" Added, file hash=0x%x\n", hash); + + // Write the archive + printf("Writing archive...\n"); + archiveBuilder.WriteArchive(szArchive); + +} + +// Helper for test routine +PCHAR ExtractFileName(PCHAR szString) +{ + DWORD dwOffset = (DWORD)strlen(szString); + while((dwOffset > 0) && (szString[dwOffset] != '\\')) + dwOffset--; + if (dwOffset == 0) + return szString; + else + return szString+dwOffset+1; +} + +// Test routine to see if the archive making works properly... +// And for archive verification! +void VerifyArchive(PCHAR szFileList, PCHAR szArchive) +{ + + CArchiveFS fs; + + const DWORD BUFFER_SIZE = 1024; + BYTE* pbBuffer[BUFFER_SIZE]; + + // Load the file list + FILE* fiList; + fiList = fopen(szFileList, "rb"); + if (!fiList) + throw "Could not find file list."; + fseek(fiList, 0, SEEK_END); + DWORD dwFileListSize = ftell(fiList); + CHAR* szFiles = new CHAR[dwFileListSize+1]; + fseek(fiList, 0, SEEK_SET); + fread(szFiles, 1, dwFileListSize, fiList); + szFiles[dwFileListSize] = 0; + fclose(fiList); + + // Load the archive + if (!fs.Load(szArchive)) + throw "Failed to load archive."; + + // Load the files and stuff + CHAR* szToken; + FILE* fiData; + + szToken = strtok(szFiles, "\r\n"); + while(szToken != NULL) { + if (szToken[0] != 0 && szToken[0] != '#') { + + // Get the file data + DWORD dwFileIndex = fs.GetFileIndex(ExtractFileName(szToken)); + + if (dwFileIndex != FS_INVALID_FILE) { + + DWORD dwFileSize = fs.GetFileSize(dwFileIndex); + BYTE* pbFileData = fs.GetFileData(dwFileIndex); + + printf("Verifying file: %s...\t", szToken); + + // Load the actual file and verify the data + fiData = fopen(szToken, "rb"); + + if (dwFileSize == 0) + printf(" Warning: 0 byte file.\n"); + + for(DWORD i=0; i= dwFileSize) { + // The last block, read using our real size + dwReadBytes = dwFileSize % BUFFER_SIZE; + } else { + dwReadBytes = BUFFER_SIZE; + } + dwReadBytes = (DWORD)fread(pbBuffer, 1, dwReadBytes, fiData); + if (memcmp(pbBuffer, pbFileData+i, dwReadBytes) != 0) { + throw " Failed to verify file.\n"; + } + } + fclose(fiData); + + printf(" Verified.\n"); + + } else { + printf(" Warning: File ""%s"" not found in archive.\n", szToken); + } + + } + szToken = strtok(NULL, "\r\n"); + } + + // Close the archive + fs.Unload(); + + // Clean up + delete[] szFiles; +} + +void VerifyArchiveSingle(PCHAR szFile, PCHAR szArchive) +{ + const DWORD BUFFER_SIZE = 1024; + BYTE* pbBuffer[BUFFER_SIZE]; + + CArchiveFS fs(4, 0); + + // Load the archive + if (!fs.Load(szArchive)) + throw "Failed to load archive."; + + // Load the files and stuff + FILE* fiData; + + // Get the file data + DWORD dwFileIndex = fs.GetFileIndex(ExtractFileName(szFile)); + + if (dwFileIndex != FS_INVALID_FILE) { + + DWORD dwFileSize = fs.GetFileSize(dwFileIndex); + BYTE* pbFileData = fs.GetFileData(dwFileIndex); + + printf("Verifying file: %s...\t", szFile); + + // Load the actual file and verify the data + fiData = fopen(szFile, "rb"); + + if (dwFileSize == 0) + printf(" Warning: 0 byte file.\n"); + + for(DWORD i=0; i= dwFileSize) { + // The last block, read using our real size + dwReadBytes = dwFileSize % BUFFER_SIZE; + } else { + dwReadBytes = BUFFER_SIZE; + } + dwReadBytes = (DWORD)fread(pbBuffer, 1, dwReadBytes, fiData); + if (memcmp(pbBuffer, pbFileData+i, dwReadBytes) != 0) { + throw " Failed to verify file.\n"; + } + } + fclose(fiData); + + printf(" Verified.\n"); + + } else { + printf(" Warning: File ""%s"" not found in archive.\n", szFile); + } + + + // Close the archive + fs.Unload(); + +} + +void GenerateKeys() +{ + CCryptoContext context; + CKeyPair keyPair(&context); + CTinyEncrypt tinyEncrypt; + BYTE pbTEAKey[TEA_KEY_SIZE]; + + keyPair.GenerateKey(); + keyPair.WriteToFile("pkey.bin"); + keyPair.WriteCHeaderFile("pkey.h"); + keyPair.ReleaseKey(); + + context.GenerateRandom(TEA_KEY_SIZE, pbTEAKey); + tinyEncrypt.SetKey(pbTEAKey, 0); + tinyEncrypt.WriteKey("skey.bin"); + tinyEncrypt.WriteCHeaderFile("skey.h"); +} + +void PrintBanner() +{ + printf("SA:MP Archive 2 Tool\n"); + printf("Copyright(C) 2006, SA:MP Team\n"); + printf("Built on " __DATE__ " at " __TIME__ ".\n\n"); +} + +void PrintUsage() +{ + printf("Usage:\n"); + printf(" -c [filelist.txt] [samp.saa]\n \tGenerates samp.saa from filelist.txt\n"); + printf(" -v [filelist.txt] [samp.saa]\n \tVerifies samp.saa using filelist.txt\n"); + printf(" -sc file.dat file.saa\n \tGenerates single file SAA\n"); + printf(" -sv file.dat file.saa\n \tVerifies single file SAA\n"); + printf(" -gk\tGenerates a new set of TEA and RSA keys\n"); +} + +DWORD main(DWORD argc, CHAR** argv) +{ + + PrintBanner(); + + if (argc == 1) { + PrintUsage(); + } else { + if (strcmp(argv[1], "-c")==0) { + // generate file + PCHAR szFileList = "filelist.txt"; + PCHAR szArchive = "samp.saa"; + if (argc > 2) + szFileList = argv[2]; + if (argc > 3) + szArchive = argv[3]; + try + { + printf("Creating archive...\n"); + CreateArchive(szFileList, szArchive); + printf("Archive generated successfully.\n"); + } + catch (PCHAR error) + { + printf("Error: %s\n", error); + } + } else if (strcmp(argv[1], "-v")==0) { + // verify file + PCHAR szFileList = "filelist.txt"; + PCHAR szArchive = "samp.saa"; + if (argc > 2) + szFileList = argv[2]; + if (argc > 3) + szArchive = argv[3]; + try + { + printf("Verifying archive...\n"); + VerifyArchive(szFileList, szArchive); + printf("Archive verification complete.\n"); + } + catch (PCHAR error) + { + printf("Error: %s\n", error); + } + } else if (strcmp(argv[1], "-gk")==0) { + GenerateKeys(); + printf("Generated keys successfully.\n"); + } else if (argc > 3 && strcmp(argv[1], "-sc")==0) { + // generate file + PCHAR szFile = argv[2]; + PCHAR szArchive = argv[3]; + try + { + printf("Creating archive...\n"); + CreateArchiveSingle(szFile, szArchive); + printf("Archive generated successfully.\n"); + } + catch (PCHAR error) + { + printf("Error: %s\n", error); + } + } else if (argc > 3 && strcmp(argv[1], "-sv")==0) { + // generate file + PCHAR szFile = argv[2]; + PCHAR szArchive = argv[3]; + try + { + printf("Verifying archive...\n"); + VerifyArchiveSingle(szFile, szArchive); + printf("Archive verification complete.\n"); + } + catch (PCHAR error) + { + printf("Error: %s\n", error); + } + } else { + PrintUsage(); + } + } + +#ifdef _DEBUG + getchar(); +#endif + + +} diff --git a/arctool2/arctool2.sln b/arctool2/arctool2.sln new file mode 100644 index 0000000..c726378 --- /dev/null +++ b/arctool2/arctool2.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "arctool2", "arctool2.vcproj", "{15D9069D-0365-4493-A00F-9B2478884489}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {15D9069D-0365-4493-A00F-9B2478884489}.Debug.ActiveCfg = Debug|Win32 + {15D9069D-0365-4493-A00F-9B2478884489}.Debug.Build.0 = Debug|Win32 + {15D9069D-0365-4493-A00F-9B2478884489}.Release.ActiveCfg = Release|Win32 + {15D9069D-0365-4493-A00F-9B2478884489}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/arctool2/arctool2.vcproj b/arctool2/arctool2.vcproj new file mode 100644 index 0000000..4c9e445 --- /dev/null +++ b/arctool2/arctool2.vcproj @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/saco/archive/ArchiveCommon.h b/saco/archive/ArchiveCommon.h index 9349421..ccc688a 100644 --- a/saco/archive/ArchiveCommon.h +++ b/saco/archive/ArchiveCommon.h @@ -75,6 +75,28 @@ typedef struct _SAA_FILE_HEADER return(sizeof(DWORD)*2 + sizeof(WORD)*dwFakeDataSize + sizeof(VER2_HEADER)); } +#ifdef ARCTOOL + void InitializeDataV1() + { + headerV1.dwSAAV = 0x32414153; // "SAA2" + headerV1.dwFileCount = 0x16; + // All other data should be random, or predefined outside. + } + + void InitializeDataV2() + { + headerV2.dwSAMPID = SAA_FILE_ID; + headerV2.dwVersion = SAA_FILE_VERSION; + } + + void Write(FILE *f) + { + fseek(f, 0, SEEK_SET); + fwrite(&headerV1, 1, sizeof(DWORD)*2 + sizeof(WORD)*dwFakeDataSize, f); + fwrite(&headerV2, 1, sizeof(VER2_HEADER), f); + } +#endif + bool VerifyIdentifier() { return ((headerV2.dwSAMPID == SAA_FILE_ID) && diff --git a/saco/archive/ArchiveFS.h b/saco/archive/ArchiveFS.h index e0d2af5..19fe141 100644 --- a/saco/archive/ArchiveFS.h +++ b/saco/archive/ArchiveFS.h @@ -9,8 +9,13 @@ #include "../mod.h" +#ifndef ARCTOOL + +// Load the original CFileSystem class #include "../filesystem.h" +#endif + #define FS_INVALID_FILE 0xFFFFFFFF typedef struct _AFS_ENTRYBT_NODE @@ -86,8 +91,10 @@ typedef struct _AFS_ENTRYBT_NODE } AFS_ENTRYBT_NODE; -class CArchiveFS // size: 2357 +class CArchiveFS +#ifndef ARCTOOL : public CFileSystem +#endif { private: bool m_bLoaded; diff --git a/saco/archive/CryptoContext.cpp b/saco/archive/CryptoContext.cpp index 75afbd0..82a7038 100644 --- a/saco/archive/CryptoContext.cpp +++ b/saco/archive/CryptoContext.cpp @@ -60,3 +60,10 @@ HCRYPTPROV CCryptoContext::GetProvider() } //------------------------------------ + +#ifdef ARCTOOL +void CCryptoContext::GenerateRandom(DWORD dwLength, BYTE* pbBuffer) +{ + CryptGenRandom(m_hCryptProv, dwLength, pbBuffer); +} +#endif diff --git a/saco/archive/CryptoContext.h b/saco/archive/CryptoContext.h index d9c950d..81a83f7 100644 --- a/saco/archive/CryptoContext.h +++ b/saco/archive/CryptoContext.h @@ -20,4 +20,9 @@ public: ~CCryptoContext(void); HCRYPTPROV GetProvider(); + +#ifdef ARCTOOL + void GenerateRandom(DWORD dwLength, BYTE* pbBuffer); +#endif + }; diff --git a/saco/archive/KeyPair.cpp b/saco/archive/KeyPair.cpp index 7874f6e..871abdb 100644 --- a/saco/archive/KeyPair.cpp +++ b/saco/archive/KeyPair.cpp @@ -1,6 +1,7 @@ #include "KeyPair.h" #include "CryptoFns.h" +#include //------------------------------------ @@ -24,6 +25,17 @@ CKeyPair::~CKeyPair(void) //------------------------------------ +#ifdef ARCTOOL +void CKeyPair::GenerateKey() +{ + // Generate a key pair + HCRYPTPROV hCryptProv = m_pContext->GetProvider(); + CryptGenKey(hCryptProv, AT_SIGNATURE, (ms_dwRSAKeySize << 16) | CRYPT_EXPORTABLE, &m_hCryptKey); +} +#endif + +//------------------------------------ + void CKeyPair::ReleaseKey() { // Destroy the key pair @@ -33,6 +45,99 @@ void CKeyPair::ReleaseKey() //------------------------------------ +#ifdef ARCTOOL +void CKeyPair::LoadFromFile(PCHAR szFileName) +{ + DWORD dwKeySize; + BYTE *pbKeyBlob; + FILE *fiKey; + + // Load the private key from a file + fiKey = fopen((CHAR*)szFileName, "rb"); + fread(&dwKeySize, sizeof(dwKeySize), 1, fiKey); + pbKeyBlob = new BYTE[dwKeySize]; + fread(pbKeyBlob, 1, dwKeySize, fiKey); + fclose(fiKey); + + // Load the key pair + HCRYPTPROV hCryptProv = m_pContext->GetProvider(); + CryptImportKey(hCryptProv, pbKeyBlob, dwKeySize, NULL, CRYPT_EXPORTABLE, &m_hCryptKey); + + // Clean up memory + delete[] pbKeyBlob; +} +#endif + +//------------------------------------ + +#ifdef ARCTOOL +void CKeyPair::WriteToFile(PCHAR szFileName) +{ + DWORD dwKeySize; + BYTE *pbKeyBlob; + FILE *fiKey; + + // Export the private key + CryptExportKey(m_hCryptKey, NULL, PRIVATEKEYBLOB, 0, NULL, &dwKeySize); + pbKeyBlob = new BYTE[dwKeySize]; + CryptExportKey(m_hCryptKey, NULL, PRIVATEKEYBLOB, 0, pbKeyBlob, &dwKeySize); + + // Write the private key to a file + fiKey = fopen((CHAR*)szFileName, "wb"); + fwrite(&dwKeySize, sizeof(dwKeySize), 1, fiKey); + fwrite(pbKeyBlob, 1, dwKeySize, fiKey); + fclose(fiKey); + + // Clean up memory + delete[] pbKeyBlob; +} +#endif + +//------------------------------------ + +#ifdef ARCTOOL +void CKeyPair::WriteCHeaderFile(PSTR szFileName) +{ + const BYTE bytXORKey = 0xAA; + DWORD dwKeySize; + BYTE *pbKeyBlob; + FILE *fiHeader; + + // Export the public key + CryptExportKey(m_hCryptKey, NULL, PUBLICKEYBLOB, 0, NULL, &dwKeySize); + pbKeyBlob = new BYTE[dwKeySize]; + CryptExportKey(m_hCryptKey, NULL, PUBLICKEYBLOB, 0, pbKeyBlob, &dwKeySize); + + // Generate the header file + fiHeader = fopen(szFileName, "wt"); + fprintf(fiHeader, "//-----------------------------------------\n" ); + fprintf(fiHeader, "// SAMP Archive 2 Tool - Public Keys\n" ); + fprintf(fiHeader, "//\n" ); + fprintf(fiHeader, "// This file was automatically generated.\n" ); + fprintf(fiHeader, "// Do not modify this file!\n" ); + fprintf(fiHeader, "//-----------------------------------------\n" ); + fprintf(fiHeader, "\n" ); + fprintf(fiHeader, "#pragma once\n" ); + fprintf(fiHeader, "\n" ); + fprintf(fiHeader, "#define RSA_XOR_KEY %d\n", bytXORKey ); + fprintf(fiHeader, "\n" ); + fprintf(fiHeader, "#define RSA_PUB_KEY_SIZE %d\n", dwKeySize ); + fprintf(fiHeader, "const BYTE RSA_PUB_KEY[] = \n\t{" ); + for(DWORD i=0; iGetContainer(); + CryptSignHash(hCryptHash, AT_SIGNATURE, NULL, CRYPT_NOHASHOID, NULL, &m_dwLength); + m_pbSignature = new BYTE[m_dwLength]; + CryptSignHash(hCryptHash, AT_SIGNATURE, NULL, CRYPT_NOHASHOID, m_pbSignature, &m_dwLength); +} +#endif + +//------------------------------------ + +#ifdef ARCTOOL +BYTE* CSigner::GetSignature() +{ + return m_pbSignature; +} +#endif + +//------------------------------------ + +#ifdef ARCTOOL +DWORD CSigner::GetSignatureLength() +{ + return m_dwLength; +} +#endif + +//------------------------------------ + void CSigner::SetSignature(DWORD dwLength, BYTE *pbSignature) { if (m_pbSignature != NULL) diff --git a/saco/archive/Signer.h b/saco/archive/Signer.h index 43eb238..694142e 100644 --- a/saco/archive/Signer.h +++ b/saco/archive/Signer.h @@ -15,6 +15,12 @@ public: CSigner(void); ~CSigner(void); +#ifdef ARCTOOL + void SignHash(CHasher* pHasher); + BYTE* GetSignature(); + DWORD GetSignatureLength(); +#endif + void SetSignature(DWORD dwLength, BYTE* pbSignature); BOOL VerifySignature(CHasher* pHasher, CKeyPair* pKeyPair); diff --git a/saco/archive/TinyEncrypt.cpp b/saco/archive/TinyEncrypt.cpp index d4a3fb8..0f4ed5d 100644 --- a/saco/archive/TinyEncrypt.cpp +++ b/saco/archive/TinyEncrypt.cpp @@ -1,6 +1,7 @@ #include "TinyEncrypt.h" #include "Obfuscator.h" +#include //------------------------------------ @@ -42,6 +43,27 @@ void CTinyEncrypt::SetKey(BYTE *pbKey, BYTE bytXORKey) //------------------------------------ +#ifdef ARCTOOL + +void CTinyEncrypt::EncryptBlock(DWORD &dwV0, DWORD &dwV1) +{ + DWORD dwSum = 0; + + for(DWORD i=0; i> 5) + dwV1) ^ (dwSum + m_pdwKey[dwSum & 3]); + dwSum += ms_dwInitDelta; + dwV1 += ((dwV0 << 4 ^ dwV0 >> 5) + dwV0) ^ (dwSum + m_pdwKey[dwSum>>11 & 3]); + } + + m_pdwKey[0] ^= dwV0; + m_pdwKey[1] ^= dwV1; + m_pdwKey[2] ^= dwV0; + m_pdwKey[3] ^= dwV1; +} +#endif + +//------------------------------------ + void CTinyEncrypt::DecryptBlock(DWORD &dwV0, DWORD &dwV1) { DWORD dwSum = ms_dwInitSum; @@ -63,6 +85,19 @@ void CTinyEncrypt::DecryptBlock(DWORD &dwV0, DWORD &dwV1) //------------------------------------ +#ifdef ARCTOOL +void CTinyEncrypt::EncryptData(DWORD dwLength, BYTE *pbData) +{ + DWORD dwBlocks = dwLength / 4; + DWORD *pdwData = reinterpret_cast(pbData); + for(DWORD i=0; i(m_pdwKey); + FILE *fiHeader; + + // Generate the header file + fiHeader = fopen(szFileName, "wt"); + fprintf(fiHeader, "//-----------------------------------------\n" ); + fprintf(fiHeader, "// SAMP Archive 2 Tool - TEA Keys\n" ); + fprintf(fiHeader, "//\n" ); + fprintf(fiHeader, "// This file was automatically generated.\n" ); + fprintf(fiHeader, "// Do not modify this file!\n" ); + fprintf(fiHeader, "//-----------------------------------------\n" ); + fprintf(fiHeader, "\n" ); + fprintf(fiHeader, "#pragma once\n" ); + fprintf(fiHeader, "\n" ); + fprintf(fiHeader, "#define TEA_XOR_KEY %d\n", bytXORKey ); + fprintf(fiHeader, "\n" ); + fprintf(fiHeader, "#define TEA_KEY_SIZE %d\n", TEA_KEY_SIZE ); + fprintf(fiHeader, "const BYTE TEA_KEY[] = \n\t{" ); + for(DWORD i=0; i