Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Avery3R committed Nov 25, 2023
0 parents commit e5cf3e2
Show file tree
Hide file tree
Showing 8 changed files with 581 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.vs/
x64/
Release/
Debug/
enc_temp_folder/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lz4"]
path = lz4
url = https://github.com/lz4/lz4
31 changes: 31 additions & 0 deletions WRCGModTools.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33815.320
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WRCGUnpackager", "WRCGUnpackager\WRCGUnpackager.vcxproj", "{25A14CDE-B32F-41AF-A224-89C52C073C76}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{25A14CDE-B32F-41AF-A224-89C52C073C76}.Debug|x64.ActiveCfg = Debug|x64
{25A14CDE-B32F-41AF-A224-89C52C073C76}.Debug|x64.Build.0 = Debug|x64
{25A14CDE-B32F-41AF-A224-89C52C073C76}.Debug|x86.ActiveCfg = Debug|Win32
{25A14CDE-B32F-41AF-A224-89C52C073C76}.Debug|x86.Build.0 = Debug|Win32
{25A14CDE-B32F-41AF-A224-89C52C073C76}.Release|x64.ActiveCfg = Release|x64
{25A14CDE-B32F-41AF-A224-89C52C073C76}.Release|x64.Build.0 = Release|x64
{25A14CDE-B32F-41AF-A224-89C52C073C76}.Release|x86.ActiveCfg = Release|Win32
{25A14CDE-B32F-41AF-A224-89C52C073C76}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D23B8B3B-088A-48C4-8A77-D620A45D0950}
EndGlobalSection
EndGlobal
324 changes: 324 additions & 0 deletions WRCGUnpackager/WRCGUnpackager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
#include <iostream>
#include <fstream>
#include <filesystem>
#include <span>
#include <vector>
#define OPENSSL_API_COMPAT 1110
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <lz4frame.h>

#pragma pack(push, 1)
struct PPKGHeader
{
char magic[4];
uint32_t version;
uint32_t unk;
uint32_t fileHeadersSize;
uint8_t reserved[0x20];
};
static_assert(sizeof(PPKGHeader) == 0x30, "sizeof(PPKGHeader) == 0x30");
#pragma pack(pop)

enum FileCompressionType : uint32_t
{
FCT_None = 0,
FCT_Unk1 = 1,
FCT_LZ4F = 2,
};

struct FileMetadata
{
std::string fileName;
uint32_t checksum;
uint64_t filetime;
uint64_t dataOffset;
uint64_t rawDataSize;
uint64_t uncompressedDataSize;
FileCompressionType compressionType;
};

const std::string DEFAULT_XOR_KEY = "14F5FsyDFFUC4NVANPpYggyakreWkJfy";

inline void XorData(const std::string_view xorKey, std::span<uint8_t> data, size_t keyOffset = 0)
{
for (size_t i = 0; i < data.size(); ++i)
{
data[i] ^= xorKey[(i+keyOffset)%xorKey.size()];
}
}

int main(int argc, char** argv)
{
const auto basePath = std::filesystem::current_path();

if(!std::filesystem::exists(basePath / "WRCG.exe"))
{
std::cerr << "Unpackager must be run from within the same folder as WRCG.exe\n" << "Press enter to exit..." << std::endl;
std::cin.ignore(99999, '\n');
return 1;
}

const auto pkgsFolder = basePath / "WIN32" / "PKG";

std::vector<std::filesystem::path> pkgFiles;

for(const auto &pkgChunkFolder : std::filesystem::directory_iterator(pkgsFolder))
{
if(std::filesystem::is_directory(pkgChunkFolder))
{
for(const auto &pkgFile : std::filesystem::directory_iterator(pkgChunkFolder))
{
if(pkgFile.path().extension() == ".PKG")
{
pkgFiles.push_back(pkgFile.path());
}
}
}
}

for(size_t pkgIndex = 0; pkgIndex < pkgFiles.size(); ++pkgIndex)
{
std::fstream infile(pkgFiles[pkgIndex], std::ios::in | std::ios::binary);

if(!infile.is_open())
{
std::cerr << "Could not open file " << pkgFiles[pkgIndex] << " skipping..." << std::endl;
continue;
}

std::vector<uint8_t> dataBuf(0x30);

infile.read((char*)&dataBuf[0], 0x30);

std::string xorKey = DEFAULT_XOR_KEY;
size_t headerOffset = 0;

for(size_t i = 0; i < dataBuf.size(); ++i)
{
dataBuf[i] ^= xorKey[i%xorKey.size()];
}

if(*(uint32_t*)&dataBuf[0] != 'GKPP')
{
dataBuf.resize(0x100);

infile.read((char*)&dataBuf[0x30], 0x100-0x30);

for(size_t i = 0x30; i < dataBuf.size(); ++i)
{
dataBuf[i] ^= xorKey[i%xorKey.size()];
}

std::string publicKey =
"-----BEGIN PUBLIC KEY-----\r\n"
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAohx1scPAXQSAF4E7wuEz\r\n"
"ehIAiCiU9OMFGvCLoSmtQUOCQDPqY3bykOKBMqGJKQ7yaf55jiJHaW3lCZkWLOBO\r\n"
"46pAUOtoxVeQ9+4M4BmCUalTwWq/SsCc/JuEl6j+7DK1sGBAcjz/uyxvmVa85TtO\r\n"
"zDXEc2oDBhoNdg1AcMnwU7PQsdON/qiI7UIZ4JZ7QzoAklvA3GBdT93ln6UVy5U2\r\n"
"KWj8pCwMcVEJ5UOxdGWCebTvF7yxvPo+6AhkFUyrZ1lOWA6kgu8z3xzdnBet/fzf\r\n"
"+nHQZ5eT09ackoWjuGe6rFxAcEqVb80KqkPjqTIoKprkegl78yXeewraegjEXzZj\r\n"
"ywIDAQAB\r\n"
"-----END PUBLIC KEY-----";

BIO *bio = BIO_new_mem_buf((void*)publicKey.c_str(), publicKey.size());
RSA *rsa = PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr);
if(!rsa)
{
std::cerr << "Could not decode public key, skipping this package..." << std::endl;
continue;
}

std::vector<uint8_t> decryptedData(0x100);
int decryptedSize = RSA_public_decrypt(dataBuf.size(), &dataBuf[0], &decryptedData[0], rsa, RSA_PKCS1_PADDING);

if(decryptedSize < 0)
{
std::cerr << "Could not decrypt data, skipping this package..." << std::endl;
continue;
}

RSA_free(rsa);
BIO_free(bio);

headerOffset = 0x180; //encrypted xor key + sig

xorKey = std::string((char*)&decryptedData[0], decryptedSize);

dataBuf.resize(0x30);

infile.seekg(headerOffset, std::ios::beg);
infile.read((char*)&dataBuf[0], 0x30);

XorData(xorKey, dataBuf);
}

PPKGHeader *header = (PPKGHeader*)&dataBuf[0];

std::vector<uint8_t> fileHeadersBuf(header->fileHeadersSize);
infile.read((char*)&fileHeadersBuf[0], header->fileHeadersSize);
XorData(xorKey, fileHeadersBuf, headerOffset+0x30);

std::span<const uint8_t> remainingFileHeadersData = fileHeadersBuf;

std::vector<FileMetadata> pkgFileMetadata;

pkgFileMetadata.reserve(header->fileHeadersSize/sizeof(FileMetadata));

while(remainingFileHeadersData.size() > 0)
{
FileMetadata meta = {};

uint32_t fileNameSize = *(uint32_t*)&remainingFileHeadersData[0];
remainingFileHeadersData = remainingFileHeadersData.subspan(sizeof(uint32_t));

meta.fileName = std::string((char*)&remainingFileHeadersData[0], fileNameSize);
remainingFileHeadersData = remainingFileHeadersData.subspan(fileNameSize);

//some sort of checksum
remainingFileHeadersData = remainingFileHeadersData.subspan(4);

meta.dataOffset = *(uint64_t*)&remainingFileHeadersData[0];
remainingFileHeadersData = remainingFileHeadersData.subspan(sizeof(uint64_t));

meta.filetime = *(uint64_t*)&remainingFileHeadersData[0];
remainingFileHeadersData = remainingFileHeadersData.subspan(sizeof(uint64_t));

meta.rawDataSize = *(uint64_t*)&remainingFileHeadersData[0];
remainingFileHeadersData = remainingFileHeadersData.subspan(sizeof(uint64_t));

meta.uncompressedDataSize = *(uint64_t*)&remainingFileHeadersData[0];
remainingFileHeadersData = remainingFileHeadersData.subspan(sizeof(uint64_t));

meta.compressionType = *(FileCompressionType*)&remainingFileHeadersData[0];
remainingFileHeadersData = remainingFileHeadersData.subspan(sizeof(uint32_t));

//some sort of checksum and or signature
remainingFileHeadersData = remainingFileHeadersData.subspan(0x30);

pkgFileMetadata.push_back(std::move(meta));
}

for(size_t fileIndex = 0; fileIndex < pkgFileMetadata.size(); ++fileIndex)
{
FileMetadata &fileMeta = pkgFileMetadata[fileIndex];
std::cout << pkgIndex << "/" << pkgFiles.size() << " " << fileIndex << "/" << pkgFileMetadata.size() << " " << fileMeta.fileName << std::endl;

std::filesystem::path outputPath = basePath / fileMeta.fileName;
std::filesystem::path outpurDir = outputPath.parent_path();

std::filesystem::create_directories(outpurDir);

if(fileMeta.compressionType == FCT_Unk1)
{
std::cerr << "File is compressed with unimplemented compression type, skipping..." << std::endl;
continue;
}

char sanityMagic[4];

infile.seekg(fileMeta.dataOffset, std::ios::beg);

infile.read(sanityMagic, sizeof(sanityMagic));

if(memcmp(sanityMagic, "PKGB", 4) != 0)
{
std::cerr << "File begin magic sanity check failed, skipping..." << std::endl;
continue;
}

uint8_t *rawDataBuf = new uint8_t[fileMeta.rawDataSize];
infile.read((char*)rawDataBuf, fileMeta.rawDataSize);

infile.read(sanityMagic, sizeof(sanityMagic));

if(memcmp(sanityMagic, "PKGE", 4) != 0)
{
std::cerr << "File end magic sanity check failed, skipping..." << std::endl;
continue;
}

uint8_t *decompressedDataBuf = new uint8_t[fileMeta.uncompressedDataSize];

switch(fileMeta.compressionType)
{
case FCT_None:
{
if(fileMeta.rawDataSize != fileMeta.uncompressedDataSize)
{
__debugbreak();
}

memcpy(decompressedDataBuf, rawDataBuf, fileMeta.uncompressedDataSize);
}
break;
case FCT_LZ4F:
{
LZ4F_dctx *dctxPtr = nullptr;

LZ4F_createDecompressionContext(&dctxPtr, LZ4F_VERSION);

size_t decompressedSize = fileMeta.uncompressedDataSize;
size_t compressedSize = fileMeta.rawDataSize;

LZ4F_decompress(dctxPtr, decompressedDataBuf, &decompressedSize, rawDataBuf, &compressedSize, nullptr);

LZ4F_freeDecompressionContext(dctxPtr);

if(decompressedSize != fileMeta.uncompressedDataSize)
{
__debugbreak();
}
}
break;
}

delete [] rawDataBuf;

std::fstream outfile(outputPath, std::ios::out | std::ios::binary);
outfile.write((char*)decompressedDataBuf, fileMeta.uncompressedDataSize);
outfile.close();

delete [] decompressedDataBuf;
}
}

const auto settingsFilePath = basePath / "COMMON" / "SETTINGS" / "SETTINGS.CFG";

if(!std::filesystem::exists(settingsFilePath))
{
std::cout << "Decrypting settings file..." << std::endl;

const auto encryptedSettingsFilePath = basePath / "COMMON" / "SETTINGS" / "DUMMYS.DAT";

if(std::filesystem::exists(encryptedSettingsFilePath))
{
std::fstream infile(encryptedSettingsFilePath, std::ios::in | std::ios::binary);
if(infile.is_open())
{
infile.seekg(0, std::ios::end);
const size_t fileSize = infile.tellg();
infile.seekg(0, std::ios::beg);

uint8_t *fileData = new uint8_t[fileSize];
infile.read((char*)fileData, fileSize);
infile.close();

XorData(DEFAULT_XOR_KEY, std::span<uint8_t>(fileData, fileSize));

std::fstream outfile(settingsFilePath, std::ios::out | std::ios::binary);
outfile.write((char*)fileData, fileSize);

delete [] fileData;
}
else
{
std::cerr << "Failed to open encrypted settings file, skipping..." << std::endl;
}
}
else
{
std::cerr << "Encrypted settings file was missing, skipping..." << std::endl;
}
}
}
Loading

0 comments on commit e5cf3e2

Please sign in to comment.