Hello everyone,
I think nobody knows me, but I'm a member of the Underground here.
Since I will resign from hacking S4, I want to make my hacks public to
share with everyone. I stopped playing S4 League long ago, so I don't
care much about S4League anymore.
What I want to share with you is the decryption of the S4League
resource files. It is also used for decrypting the network traffic, but
you have to figure that out yourself.
Ok, lets start with the resource.s4hd file. It contains the internal
file names and the crc value of the decrypted/uncompressed file and
it's compressed and encrypted by a key.
The key is
Code:
0x82, 0x53, 0x43, 0x4C, 0x2B, 0x0D, 0x37, 0xD7,
0xD9, 0xD8, 0x1B, 0x6D, 0xA0, 0xC3, 0x2B, 0xEE,
0x45, 0x88, 0x1A, 0xA6, 0x18, 0x1D, 0x9D, 0x38,
0x2A, 0x55, 0x03, 0x1D, 0xCD, 0xA6, 0x73, 0x07,
0xED, 0x8D, 0xC5, 0xDB, 0xA3, 0xBD, 0xB6, 0xD5
Only the first 32 values are used. They use the LZO compression library.
The filename in the _resources folder is the crc as hex value. The files are compressed and encrypted with the same key.
Files that end with .x4 are special. They are compressed and encrypted
again, but with the whole 40 byte key above. X4 files contain many
parameters of the game, like height of jump, gravity, prices for shop
items and many more...
AFAIK, the CRC is not checked when reading the files from disk. The CRC
is the CRC32 of the internal name and the CRC32 of the
uncompressed/decrypted file. Both are encrypted with the key above.
They really know how to reuse code. [Resimleri görebilmek için üye olun veya giriş yapın.]
I add the source code of a program to this post that contains the
functions for decrypting the resource files. To prevent easy leaching,
you have to figure out how to use it yourself. But I'm sure, someone
will make a pretty filebrowser for this game. [Resimleri görebilmek için üye olun veya giriş yapın.] Oh, and you need the miniLZO files for compiling.
If you have an older installation of S4League that was patched several
times, the clean resource function is very useful. It deletes all
unused files from the _resources folder, which saved me 1.5 GB disk
space.
Code like this has been around for months, so we already had our fun, now they can fix it. [Resimleri görebilmek için üye olun veya giriş yapın.]
I bet they need around 2 months to fix it and everyone has to download
the full game again. I give no support for this code and I will not fix
it, when they change something. I'm out of the game.
Now the code:
Spoiler:
Code:
#include "windows.h"
#include "minilzo.203\minilzo.h"
class DecryptUtils {
private:
BYTE static const Key[];
DWORD *CRCTable;
protected:
DWORD* readDWORD(BYTE** pointer) {
DWORD* p = (DWORD*)*pointer;
(*pointer)+=4;
return p;
}
bool decryptX4(DWORD size, BYTE * data)
{
DWORD i = 0;
while ( i < size )
{
*data = ((signed int)*data >> 1) & 0x7F | (unsigned __int8)(((*data & 1) << 7) & 0x80);
*data++ ^= Key[i++ % 40];
}
return true;
}
bool encryptX4(DWORD size, BYTE* data) {
DWORD i = 0;
while ( i < size )
{
*(BYTE *)data ^= Key[i % 40];
*(BYTE *)data = (unsigned __int8)(2 * *(BYTE *)data & 0xFE) | ((*(BYTE *)data & 0x80) >> 7) & 1;
++data;
++i;
}
return true;
}
bool decryptCapped256(DWORD size, BYTE* data) {
int sizeCapped;
int i = 0;
if ( size >= 256 )
sizeCapped = 256;
else
sizeCapped = size;
while ( i < sizeCapped )
{
*data = ((signed int)*data >> 1) & 0x7F | (unsigned __int8)(((*data & 1) << 7) & 0x80);
*data++ ^= Key[i++ % 32];
}
return true;
}
bool encryptCapped256(DWORD size, BYTE* data) {
int sizeCapped;
int i = 0;
if ( size >= 256 )
sizeCapped = 256;
else
sizeCapped = size;
while ( i < sizeCapped )
{
*(BYTE *)data ^= Key[i % 32];
*(BYTE *)data = (unsigned __int8)(2 * *(BYTE *)data & 0xFE) | ((*(BYTE *)data & 0x80) >> 7) & 1;
++data;
++i;
}
return true;
}
int compress(DWORD sizeIN, BYTE* dataIN, DWORD* sizeOUT, BYTE* dataOUT) {
BYTE* dict = (BYTE*)malloc(0x10000);
return lzo1x_1_compress(dataIN, sizeIN, dataOUT, sizeOUT, dict);
}
int decompress(DWORD sizeIN, BYTE* dataIN, DWORD* sizeOUT, BYTE* dataOUT) {
return lzo1x_decompress_safe(dataIN, sizeIN, dataOUT, sizeOUT, NULL);
}
bool swapBytes(DWORD size, BYTE *data) {
int sizeCapped;
int i = 0;
BYTE swap;
int j;
if ( size >= 128 )
sizeCapped = 128;
else
sizeCapped = size;
while ( i < sizeCapped / 2 )
{
j = size - 1 - i;
swap = data[j];
data[j] = data[i];
data[i++] = swap;
}
return true;
}
DWORD crc32(BYTE *data, DWORD length)
{
register unsigned long crc;
unsigned long i;
crc = 0xFFFFFFFF;
for (i = 0; i < length; i++)
{
crc = ((crc >> & 0x00FFFFFF) ^ CRCTable[(crc ^ *data++) & 0xFF];
}
return (crc ^ 0xFFFFFFFF);
}
void crc32gentab ()
{
unsigned long crc, poly;
int i, j;
CRCTable = new DWORD[256];
poly = 0xEDB88320L;
for (i = 0; i < 256; i++) {
crc = i;
for (j = 8; j > 0; j--) {
if (crc & 1) {
crc = (crc >> 1) ^ poly;
} else {
crc >>= 1;
}
}
CRCTable[i] = crc;
}
}
};
struct FolderFileHeader {
char FileName[256];
unsigned __int64 CRC;
DWORD FileSize;
DWORD Unknown;
};
struct Buffer {
DWORD Size;
BYTE* Data;
};
class Folder : public DecryptUtils {
private:
static const char* Name;
FILE* Handle;
int Size;
BYTE* Data;
bool Initialized;
BYTE* Pointer;
BYTE* FileStart;
DWORD* Version;
DWORD* NumberOfFiles;
FolderFileHeader* FileHeader;
bool Decrypted;
void DecryptFolder();
Buffer OpenFileCRC(char* crc, DWORD realSize);
Buffer DecryptX4(Buffer data);
public:
Folder();
~Folder();
void List();
Buffer OpenFile(char * name);
__int64 GenerateCRC(Buffer data, char * name);
void CleanResources();
};
const BYTE DecryptUtils::Key[] = { // Only the first 40 keys copied from S4
0x82, 0x53, 0x43, 0x4C, 0x2B, 0x0D, 0x37, 0xD7,
0xD9, 0xD8, 0x1B, 0x6D, 0xA0, 0xC3, 0x2B, 0xEE,
0x45, 0x88, 0x1A, 0xA6, 0x18, 0x1D, 0x9D, 0x38,
0x2A, 0x55, 0x03, 0x1D, 0xCD, 0xA6, 0x73, 0x07,
0xED, 0x8D, 0xC5, 0xDB, 0xA3, 0xBD, 0xB6, 0xD5
};
const char* Folder::Name = "resource.s4hd";
Folder::Folder() {
this->Initialized = false;
this->Decrypted = false;
// Open Resource File
int errorNumber = fopen_s(&(this->Handle), this->Name, "rb");
if (errorNumber != 0 || this->Handle == NULL) {
printf("Error reading file %s. ErrorNr: %u\n", this->Name, errorNumber);
//return errorNumber;
return;
}
fseek(this->Handle, 0, SEEK_END);
this->Size = ftell(this->Handle);
rewind(this->Handle);
if (this->Size < {
printf("File %s must have at least 8 bytes in size.\n", this->Name);
fclose(this->Handle);
return;
}
// Read Resource File into memory
this->Data = (BYTE*) malloc(this->Size);
if (this->Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", this->Size);
}
int readBytes = fread_s(this->Data, this->Size, 1, this->Size, this->Handle);
if (readBytes != this->Size) {
printf("Expected to read %u bytes, but read %u bytes.\n", this->Size, readBytes);
fclose(this->Handle);
free(this->Data);
return;
}
// Read Version and number of Files;
this->Pointer = this->Data;
this->Version = readDWORD(&this->Pointer);
if (*(this->Version) != 1) {
printf("First DWORD of %s has to be 1, Pentavision changed the format.\n", this->Name);
fclose(this->Handle);
free(this->Data);
return;
}
this->NumberOfFiles = readDWORD(&this->Pointer);
this->FileStart = this->Pointer;
this->Initialized = true;
DecryptFolder();
}
Folder::~Folder() {
if (this->Initialized && this->Handle != NULL) {
fclose(this->Handle);
free(this->Data);
}
}
void Folder::DecryptFolder() {
if (!this->Initialized) return;
DWORD fileCounter = *this->NumberOfFiles;
BYTE* endOfFile = this->Data + this->Size;
this->FileHeader = new FolderFileHeader[fileCounter];
int i = 0;
while (fileCounter >= 0 && this->Pointer < endOfFile) {
DWORD * headerBlockSize = readDWORD(&this->Pointer);
if (*headerBlockSize + this->Pointer > endOfFile) {
printf("Length of header block size bigger than available memory. Size is %u at %u.\n", *headerBlockSize, this->Pointer - this->Data - sizeof(DWORD));
return;
}
decryptCapped256(*headerBlockSize, this->Pointer);
swapBytes(*headerBlockSize, this->Pointer);
DWORD headerSize = sizeof(FolderFileHeader);
decompress(*headerBlockSize, this->Pointer, &headerSize, (BYTE*)&this->FileHeader[i]);
decryptCapped256(headerSize, (BYTE*)&this->FileHeader[i]);
swapBytes(headerSize, (BYTE*)&this->FileHeader[i]);
this->Pointer+= *headerBlockSize;
fileCounter--;
i++;
}
this->Decrypted = true;
}
void Folder::List() {
if (!this->Initialized || !this->Decrypted) return;
for (DWORD i = 0; i < *this->NumberOfFiles; ++i) {
printf("Filename: %s \nFile-CRC: %I64X\nReal File-Size: %u\nUnknown: %X\n", this->FileHeader[i].FileName, this->FileHeader[i].CRC, this->FileHeader[i].FileSize, this->FileHeader[i].Unknown);
}
}
Buffer Folder::OpenFile(char *name) {
Buffer ret;
ret.Data = 0;
ret.Size = 0;
if (!this->Initialized || !this->Decrypted) return ret;
for (DWORD i = 0; i < *this->NumberOfFiles; ++i) {
if (strncmp(name, this->FileHeader[i].FileName, 256) == 0) {
char* fileName = new char[29];
sprintf_s(fileName, 29, "_resources\\%I64X", this->FileHeader[i].CRC);
Buffer data = OpenFileCRC(fileName, this->FileHeader[i].FileSize);
if (data.Data==0) return data;
Buffer ret = data;
if (strstr(this->FileHeader[i].FileName, ".x4")!=0) {
ret = DecryptX4(data);
free(data.Data);
}
delete fileName;
return ret;
}
}
return ret;
}
Buffer Folder::DecryptX4(Buffer data) {
Buffer x4;
x4.Size = *((DWORD*)data.Data);
data.Data+=4;
data.Size-=4;
x4.Data = (BYTE*) malloc(x4.Size);
if (x4.Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", x4.Size);
}
decryptX4(data.Size, data.Data);
decompress(data.Size, data.Data, &x4.Size, x4.Data);
data.Data -= 4;
return x4;
}
Buffer Folder::OpenFileCRC(char *crc, DWORD realSize) {
FILE* handle;
Buffer d;
d.Data = 0;
d.Size = 0;
int errNo = fopen_s(&handle, crc, "rb");
if (errNo != 0 || handle == NULL) {
printf("Error reading file %s. ErrorNr: %u\n", crc, errNo);
return d;
}
fseek(handle, 0, SEEK_END);
d.Size = ftell(handle);
rewind(handle);
d.Data = (BYTE*) malloc(d.Size);
if (d.Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", d.Size);
}
int readBytes = fread_s(d.Data, d.Size, 1, d.Size, handle);
if (readBytes != d.Size) {
printf("Expected to read %u bytes, but read %u bytes.\n", d.Size, readBytes);
fclose(handle);
free(d.Data);
return d;
}
swapBytes(d.Size, d.Data);
Buffer real;
real.Size = realSize;
real.Data = (BYTE*) malloc(real.Size);
if (real.Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", real.Size);
}
decompress(d.Size, d.Data, &real.Size, real.Data);
decryptCapped256(real.Size, real.Data);
free(d.Data);
fclose(handle);
return real;
}
__int64 Folder::GenerateCRC(Buffer data, char* name) {
crc32gentab();
__int64 dataCRC = crc32(data.Data, data.Size);
__int64 nameCRC = crc32((BYTE*)name, strlen(name));
__int64 crc = dataCRC | (nameCRC << 32);
encryptCapped256(8, (BYTE*)&crc);
return crc;
}
void Folder::CleanResources() {
if (!this->Initialized || !this->Decrypted) return;
int e = system("dir _resources >nul");
if (e != 0) return;
rename("_resources", "_resources.bkp");
system("mkdir _resources");
for (DWORD i = 0; i < *this->NumberOfFiles; ++i) {
char* oldName = new char[100];
sprintf_s(oldName, 100, "_resources.bkp\\%I64X", this->FileHeader[i].CRC);
char* newName = new char[100];
sprintf_s(newName, 100, "_resources\\%I64X", this->FileHeader[i].CRC);
rename(oldName, newName);
delete oldName;
delete newName;
}
printf("You can now remove the folder _resources.bkp from the S4League Folder.\n");
}
int main(int argc, char* argv[])
{
if (argc <= 1) {
printf("Usage:\n");
printf("program.exe list \t\t\t\t\tList files\n");
printf("program.exe clean \t\t\t\t\tClean resources\n");
printf("program.exe extract filename \t\t\t\tExtract file\n");
printf("program.exe crc external_file internal_filename \tGenerate CRC\n");
}
if (argc == 2) {
if (strcmp("list", argv[1]) == 0) {
Folder* folder = new Folder();
folder->List();
}
if (strcmp("clean", argv[1]) == 0) {
Folder* folder = new Folder();
folder->CleanResources();
}
}
if (argc == 3) {
if (strcmp("extract", argv[1])==0) {
Folder* folder = new Folder();
Buffer file = folder->OpenFile(argv[2]);
printf("%s",file.Data);
}
}
if (argc == 4) {
if (strcmp("crc", argv[1])==0) {
Folder* folder = new Folder();
FILE* handle;
Buffer d;
d.Data = 0;
d.Size = 0;
int errNo = fopen_s(&handle, argv[2], "rb");
if (errNo != 0 || handle == NULL) {
printf("Error reading file %s. ErrorNr: %u\n", argv[2], errNo);
return 1;
}
fseek(handle, 0, SEEK_END);
d.Size = ftell(handle);
rewind(handle);
d.Data = (BYTE*) malloc(d.Size);
if (d.Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", d.Size);
}
int readBytes = fread_s(d.Data, d.Size, 1, d.Size, handle);
if (readBytes != d.Size) {
printf("Expected to read %u bytes, but read %u bytes.\n", d.Size, readBytes);
fclose(handle);
free(d.Data);
return 1;
}
__int64 crc = folder->GenerateCRC(d, argv[3]);
printf("CRC: %I64X", crc);
}
}
return 0;
}
Edit: Let me add the code as C#:
Spoiler:
I think nobody knows me, but I'm a member of the Underground here.
Since I will resign from hacking S4, I want to make my hacks public to
share with everyone. I stopped playing S4 League long ago, so I don't
care much about S4League anymore.
What I want to share with you is the decryption of the S4League
resource files. It is also used for decrypting the network traffic, but
you have to figure that out yourself.
Ok, lets start with the resource.s4hd file. It contains the internal
file names and the crc value of the decrypted/uncompressed file and
it's compressed and encrypted by a key.
The key is
Code:
0x82, 0x53, 0x43, 0x4C, 0x2B, 0x0D, 0x37, 0xD7,
0xD9, 0xD8, 0x1B, 0x6D, 0xA0, 0xC3, 0x2B, 0xEE,
0x45, 0x88, 0x1A, 0xA6, 0x18, 0x1D, 0x9D, 0x38,
0x2A, 0x55, 0x03, 0x1D, 0xCD, 0xA6, 0x73, 0x07,
0xED, 0x8D, 0xC5, 0xDB, 0xA3, 0xBD, 0xB6, 0xD5
Only the first 32 values are used. They use the LZO compression library.
The filename in the _resources folder is the crc as hex value. The files are compressed and encrypted with the same key.
Files that end with .x4 are special. They are compressed and encrypted
again, but with the whole 40 byte key above. X4 files contain many
parameters of the game, like height of jump, gravity, prices for shop
items and many more...
AFAIK, the CRC is not checked when reading the files from disk. The CRC
is the CRC32 of the internal name and the CRC32 of the
uncompressed/decrypted file. Both are encrypted with the key above.
They really know how to reuse code. [Resimleri görebilmek için üye olun veya giriş yapın.]
I add the source code of a program to this post that contains the
functions for decrypting the resource files. To prevent easy leaching,
you have to figure out how to use it yourself. But I'm sure, someone
will make a pretty filebrowser for this game. [Resimleri görebilmek için üye olun veya giriş yapın.] Oh, and you need the miniLZO files for compiling.
If you have an older installation of S4League that was patched several
times, the clean resource function is very useful. It deletes all
unused files from the _resources folder, which saved me 1.5 GB disk
space.
Code like this has been around for months, so we already had our fun, now they can fix it. [Resimleri görebilmek için üye olun veya giriş yapın.]
I bet they need around 2 months to fix it and everyone has to download
the full game again. I give no support for this code and I will not fix
it, when they change something. I'm out of the game.
Now the code:
Spoiler:
Code:
#include "windows.h"
#include "minilzo.203\minilzo.h"
class DecryptUtils {
private:
BYTE static const Key[];
DWORD *CRCTable;
protected:
DWORD* readDWORD(BYTE** pointer) {
DWORD* p = (DWORD*)*pointer;
(*pointer)+=4;
return p;
}
bool decryptX4(DWORD size, BYTE * data)
{
DWORD i = 0;
while ( i < size )
{
*data = ((signed int)*data >> 1) & 0x7F | (unsigned __int8)(((*data & 1) << 7) & 0x80);
*data++ ^= Key[i++ % 40];
}
return true;
}
bool encryptX4(DWORD size, BYTE* data) {
DWORD i = 0;
while ( i < size )
{
*(BYTE *)data ^= Key[i % 40];
*(BYTE *)data = (unsigned __int8)(2 * *(BYTE *)data & 0xFE) | ((*(BYTE *)data & 0x80) >> 7) & 1;
++data;
++i;
}
return true;
}
bool decryptCapped256(DWORD size, BYTE* data) {
int sizeCapped;
int i = 0;
if ( size >= 256 )
sizeCapped = 256;
else
sizeCapped = size;
while ( i < sizeCapped )
{
*data = ((signed int)*data >> 1) & 0x7F | (unsigned __int8)(((*data & 1) << 7) & 0x80);
*data++ ^= Key[i++ % 32];
}
return true;
}
bool encryptCapped256(DWORD size, BYTE* data) {
int sizeCapped;
int i = 0;
if ( size >= 256 )
sizeCapped = 256;
else
sizeCapped = size;
while ( i < sizeCapped )
{
*(BYTE *)data ^= Key[i % 32];
*(BYTE *)data = (unsigned __int8)(2 * *(BYTE *)data & 0xFE) | ((*(BYTE *)data & 0x80) >> 7) & 1;
++data;
++i;
}
return true;
}
int compress(DWORD sizeIN, BYTE* dataIN, DWORD* sizeOUT, BYTE* dataOUT) {
BYTE* dict = (BYTE*)malloc(0x10000);
return lzo1x_1_compress(dataIN, sizeIN, dataOUT, sizeOUT, dict);
}
int decompress(DWORD sizeIN, BYTE* dataIN, DWORD* sizeOUT, BYTE* dataOUT) {
return lzo1x_decompress_safe(dataIN, sizeIN, dataOUT, sizeOUT, NULL);
}
bool swapBytes(DWORD size, BYTE *data) {
int sizeCapped;
int i = 0;
BYTE swap;
int j;
if ( size >= 128 )
sizeCapped = 128;
else
sizeCapped = size;
while ( i < sizeCapped / 2 )
{
j = size - 1 - i;
swap = data[j];
data[j] = data[i];
data[i++] = swap;
}
return true;
}
DWORD crc32(BYTE *data, DWORD length)
{
register unsigned long crc;
unsigned long i;
crc = 0xFFFFFFFF;
for (i = 0; i < length; i++)
{
crc = ((crc >> & 0x00FFFFFF) ^ CRCTable[(crc ^ *data++) & 0xFF];
}
return (crc ^ 0xFFFFFFFF);
}
void crc32gentab ()
{
unsigned long crc, poly;
int i, j;
CRCTable = new DWORD[256];
poly = 0xEDB88320L;
for (i = 0; i < 256; i++) {
crc = i;
for (j = 8; j > 0; j--) {
if (crc & 1) {
crc = (crc >> 1) ^ poly;
} else {
crc >>= 1;
}
}
CRCTable[i] = crc;
}
}
};
struct FolderFileHeader {
char FileName[256];
unsigned __int64 CRC;
DWORD FileSize;
DWORD Unknown;
};
struct Buffer {
DWORD Size;
BYTE* Data;
};
class Folder : public DecryptUtils {
private:
static const char* Name;
FILE* Handle;
int Size;
BYTE* Data;
bool Initialized;
BYTE* Pointer;
BYTE* FileStart;
DWORD* Version;
DWORD* NumberOfFiles;
FolderFileHeader* FileHeader;
bool Decrypted;
void DecryptFolder();
Buffer OpenFileCRC(char* crc, DWORD realSize);
Buffer DecryptX4(Buffer data);
public:
Folder();
~Folder();
void List();
Buffer OpenFile(char * name);
__int64 GenerateCRC(Buffer data, char * name);
void CleanResources();
};
const BYTE DecryptUtils::Key[] = { // Only the first 40 keys copied from S4
0x82, 0x53, 0x43, 0x4C, 0x2B, 0x0D, 0x37, 0xD7,
0xD9, 0xD8, 0x1B, 0x6D, 0xA0, 0xC3, 0x2B, 0xEE,
0x45, 0x88, 0x1A, 0xA6, 0x18, 0x1D, 0x9D, 0x38,
0x2A, 0x55, 0x03, 0x1D, 0xCD, 0xA6, 0x73, 0x07,
0xED, 0x8D, 0xC5, 0xDB, 0xA3, 0xBD, 0xB6, 0xD5
};
const char* Folder::Name = "resource.s4hd";
Folder::Folder() {
this->Initialized = false;
this->Decrypted = false;
// Open Resource File
int errorNumber = fopen_s(&(this->Handle), this->Name, "rb");
if (errorNumber != 0 || this->Handle == NULL) {
printf("Error reading file %s. ErrorNr: %u\n", this->Name, errorNumber);
//return errorNumber;
return;
}
fseek(this->Handle, 0, SEEK_END);
this->Size = ftell(this->Handle);
rewind(this->Handle);
if (this->Size < {
printf("File %s must have at least 8 bytes in size.\n", this->Name);
fclose(this->Handle);
return;
}
// Read Resource File into memory
this->Data = (BYTE*) malloc(this->Size);
if (this->Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", this->Size);
}
int readBytes = fread_s(this->Data, this->Size, 1, this->Size, this->Handle);
if (readBytes != this->Size) {
printf("Expected to read %u bytes, but read %u bytes.\n", this->Size, readBytes);
fclose(this->Handle);
free(this->Data);
return;
}
// Read Version and number of Files;
this->Pointer = this->Data;
this->Version = readDWORD(&this->Pointer);
if (*(this->Version) != 1) {
printf("First DWORD of %s has to be 1, Pentavision changed the format.\n", this->Name);
fclose(this->Handle);
free(this->Data);
return;
}
this->NumberOfFiles = readDWORD(&this->Pointer);
this->FileStart = this->Pointer;
this->Initialized = true;
DecryptFolder();
}
Folder::~Folder() {
if (this->Initialized && this->Handle != NULL) {
fclose(this->Handle);
free(this->Data);
}
}
void Folder::DecryptFolder() {
if (!this->Initialized) return;
DWORD fileCounter = *this->NumberOfFiles;
BYTE* endOfFile = this->Data + this->Size;
this->FileHeader = new FolderFileHeader[fileCounter];
int i = 0;
while (fileCounter >= 0 && this->Pointer < endOfFile) {
DWORD * headerBlockSize = readDWORD(&this->Pointer);
if (*headerBlockSize + this->Pointer > endOfFile) {
printf("Length of header block size bigger than available memory. Size is %u at %u.\n", *headerBlockSize, this->Pointer - this->Data - sizeof(DWORD));
return;
}
decryptCapped256(*headerBlockSize, this->Pointer);
swapBytes(*headerBlockSize, this->Pointer);
DWORD headerSize = sizeof(FolderFileHeader);
decompress(*headerBlockSize, this->Pointer, &headerSize, (BYTE*)&this->FileHeader[i]);
decryptCapped256(headerSize, (BYTE*)&this->FileHeader[i]);
swapBytes(headerSize, (BYTE*)&this->FileHeader[i]);
this->Pointer+= *headerBlockSize;
fileCounter--;
i++;
}
this->Decrypted = true;
}
void Folder::List() {
if (!this->Initialized || !this->Decrypted) return;
for (DWORD i = 0; i < *this->NumberOfFiles; ++i) {
printf("Filename: %s \nFile-CRC: %I64X\nReal File-Size: %u\nUnknown: %X\n", this->FileHeader[i].FileName, this->FileHeader[i].CRC, this->FileHeader[i].FileSize, this->FileHeader[i].Unknown);
}
}
Buffer Folder::OpenFile(char *name) {
Buffer ret;
ret.Data = 0;
ret.Size = 0;
if (!this->Initialized || !this->Decrypted) return ret;
for (DWORD i = 0; i < *this->NumberOfFiles; ++i) {
if (strncmp(name, this->FileHeader[i].FileName, 256) == 0) {
char* fileName = new char[29];
sprintf_s(fileName, 29, "_resources\\%I64X", this->FileHeader[i].CRC);
Buffer data = OpenFileCRC(fileName, this->FileHeader[i].FileSize);
if (data.Data==0) return data;
Buffer ret = data;
if (strstr(this->FileHeader[i].FileName, ".x4")!=0) {
ret = DecryptX4(data);
free(data.Data);
}
delete fileName;
return ret;
}
}
return ret;
}
Buffer Folder::DecryptX4(Buffer data) {
Buffer x4;
x4.Size = *((DWORD*)data.Data);
data.Data+=4;
data.Size-=4;
x4.Data = (BYTE*) malloc(x4.Size);
if (x4.Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", x4.Size);
}
decryptX4(data.Size, data.Data);
decompress(data.Size, data.Data, &x4.Size, x4.Data);
data.Data -= 4;
return x4;
}
Buffer Folder::OpenFileCRC(char *crc, DWORD realSize) {
FILE* handle;
Buffer d;
d.Data = 0;
d.Size = 0;
int errNo = fopen_s(&handle, crc, "rb");
if (errNo != 0 || handle == NULL) {
printf("Error reading file %s. ErrorNr: %u\n", crc, errNo);
return d;
}
fseek(handle, 0, SEEK_END);
d.Size = ftell(handle);
rewind(handle);
d.Data = (BYTE*) malloc(d.Size);
if (d.Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", d.Size);
}
int readBytes = fread_s(d.Data, d.Size, 1, d.Size, handle);
if (readBytes != d.Size) {
printf("Expected to read %u bytes, but read %u bytes.\n", d.Size, readBytes);
fclose(handle);
free(d.Data);
return d;
}
swapBytes(d.Size, d.Data);
Buffer real;
real.Size = realSize;
real.Data = (BYTE*) malloc(real.Size);
if (real.Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", real.Size);
}
decompress(d.Size, d.Data, &real.Size, real.Data);
decryptCapped256(real.Size, real.Data);
free(d.Data);
fclose(handle);
return real;
}
__int64 Folder::GenerateCRC(Buffer data, char* name) {
crc32gentab();
__int64 dataCRC = crc32(data.Data, data.Size);
__int64 nameCRC = crc32((BYTE*)name, strlen(name));
__int64 crc = dataCRC | (nameCRC << 32);
encryptCapped256(8, (BYTE*)&crc);
return crc;
}
void Folder::CleanResources() {
if (!this->Initialized || !this->Decrypted) return;
int e = system("dir _resources >nul");
if (e != 0) return;
rename("_resources", "_resources.bkp");
system("mkdir _resources");
for (DWORD i = 0; i < *this->NumberOfFiles; ++i) {
char* oldName = new char[100];
sprintf_s(oldName, 100, "_resources.bkp\\%I64X", this->FileHeader[i].CRC);
char* newName = new char[100];
sprintf_s(newName, 100, "_resources\\%I64X", this->FileHeader[i].CRC);
rename(oldName, newName);
delete oldName;
delete newName;
}
printf("You can now remove the folder _resources.bkp from the S4League Folder.\n");
}
int main(int argc, char* argv[])
{
if (argc <= 1) {
printf("Usage:\n");
printf("program.exe list \t\t\t\t\tList files\n");
printf("program.exe clean \t\t\t\t\tClean resources\n");
printf("program.exe extract filename \t\t\t\tExtract file\n");
printf("program.exe crc external_file internal_filename \tGenerate CRC\n");
}
if (argc == 2) {
if (strcmp("list", argv[1]) == 0) {
Folder* folder = new Folder();
folder->List();
}
if (strcmp("clean", argv[1]) == 0) {
Folder* folder = new Folder();
folder->CleanResources();
}
}
if (argc == 3) {
if (strcmp("extract", argv[1])==0) {
Folder* folder = new Folder();
Buffer file = folder->OpenFile(argv[2]);
printf("%s",file.Data);
}
}
if (argc == 4) {
if (strcmp("crc", argv[1])==0) {
Folder* folder = new Folder();
FILE* handle;
Buffer d;
d.Data = 0;
d.Size = 0;
int errNo = fopen_s(&handle, argv[2], "rb");
if (errNo != 0 || handle == NULL) {
printf("Error reading file %s. ErrorNr: %u\n", argv[2], errNo);
return 1;
}
fseek(handle, 0, SEEK_END);
d.Size = ftell(handle);
rewind(handle);
d.Data = (BYTE*) malloc(d.Size);
if (d.Data == NULL) {
printf("Could not alloc memory of size %u bytes.\n", d.Size);
}
int readBytes = fread_s(d.Data, d.Size, 1, d.Size, handle);
if (readBytes != d.Size) {
printf("Expected to read %u bytes, but read %u bytes.\n", d.Size, readBytes);
fclose(handle);
free(d.Data);
return 1;
}
__int64 crc = folder->GenerateCRC(d, argv[3]);
printf("CRC: %I64X", crc);
}
}
return 0;
}
Edit: Let me add the code as C#:
Spoiler: