Created
February 15, 2023 21:24
-
-
Save modeco80/3b5c20839997030d9359c7bc53353bd6 to your computer and use it in GitHub Desktop.
SSX OG save checksum
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// A tool to checksum SSX OG save data. Also provides a | |
// cleaned-up C++20 version of the checksum implementation. | |
// | |
// Copyright (C) 2023 Lily/modeco80 <lily.modeco80@protonmail.ch> | |
// | |
// This software is provided 'as-is', without any express or implied | |
// warranty. In no event will the authors be held liable for any damages | |
// arising from the use of this software. | |
// | |
// Permission is granted to anyone to use this software for any purpose, | |
// including commercial applications, and to alter it and redistribute it | |
// freely, subject to the following restrictions: | |
// | |
// 1. The origin of this software must not be misrepresented; you must not | |
// claim that you wrote the original software. If you use this software | |
// in a product, an acknowledgment in the product documentation would be | |
// appreciated but is not required. | |
// 2. Altered source versions must be plainly marked as such, and must not be | |
// misrepresented as being the original software. | |
// 3. This notice may not be removed or altered from any source distribution. | |
// | |
// Usage: | |
// - Compile with `g++ -std=c++20 -O3 ssx_save_csum.cpp -o ssx_save_csum` | |
// - Drop modified save data as "new_save.bin" in the current directory | |
// - Run `./ssx_save_csum` | |
// - "summed_save.bin" in the current directory will be a valid save file | |
// (I reccomend using PCSX2 folder memory cards for testing.) | |
#include <cstdint> | |
#include <bit> | |
#include <cstdio> | |
#include <string_view> | |
// boilerplate type definitions | |
#define T(bitsize) \ | |
using u##bitsize = std::uint##bitsize##_t; \ | |
using s##bitsize = std::int##bitsize##_t | |
T(8); | |
T(16); | |
T(32); | |
T(64); | |
#undef T | |
using ull = unsigned long long; | |
using sll = signed long long; | |
using usize = std::size_t; | |
using ssize = std::intptr_t; | |
// A RAII wrapper over a file. | |
// For portability's sake, and to avoid a soup of #ifdef and includes | |
// here, I've chosen to just use libc file I/O for simplicity. | |
struct File { | |
constexpr explicit File(FILE* fp) | |
: fp(fp) { | |
} | |
File(File&&) noexcept = delete; | |
File(const File&) = delete; | |
~File() { | |
Close(); | |
} | |
void Close() { | |
if(Good()) { | |
std::fclose(fp); | |
fp = nullptr; | |
} | |
} | |
void Flush() { | |
if(Good()) | |
std::fflush(fp); | |
} | |
constexpr bool Good() const { | |
return fp != nullptr; | |
} | |
bool Eof() const { | |
return Good() | |
? std::feof(fp) | |
: true; | |
} | |
usize Seek(usize where, int whence) { | |
if(Good()) | |
return std::fseek(fp, where, whence); | |
return 0; | |
} | |
usize Tell() const { | |
if(Good()) | |
return std::ftell(fp); | |
return 0; | |
} | |
usize Read(u8* buffer, usize count) { | |
if(!Good()) | |
return 0; | |
return std::fread(buffer, count, 1, fp); | |
} | |
usize Write(const u8* buffer, usize count) { | |
if(!Good()) | |
return 0; | |
return std::fwrite(buffer, count, 1, fp); | |
} | |
private: | |
FILE* fp{}; | |
}; | |
// A fairly simple class which reads in an entire file, | |
// and provides a (non-UB, in most cases) accessor to data inside it. | |
struct FileBuffer { | |
explicit FileBuffer(std::string_view filename) { | |
static_cast<void>(Load(filename)); | |
} | |
FileBuffer(const FileBuffer&) = delete; | |
FileBuffer(FileBuffer&&) noexcept = delete; | |
~FileBuffer() { | |
delete[] ptr; | |
} | |
bool Load(std::string_view filename) { | |
auto file = File(std::fopen(filename.data(), "rb")); | |
if(!file.Good()) | |
return false; | |
file.Seek(0, SEEK_END); | |
size = file.Tell(); | |
file.Seek(0, SEEK_SET); | |
std::printf("Reading file \"%s\", %d bytes...\n", filename.data(), size); | |
ptr = new u8[size]; | |
file.Read(ptr, size); | |
return true; | |
} | |
bool Write(std::string_view path) { | |
auto file = File(std::fopen(path.data(), "wb")); | |
if(file.Good()) { | |
file.Write(ptr, size); | |
file.Flush(); | |
return true; | |
} | |
return false; | |
} | |
// Data accessors | |
constexpr bool Valid() const { | |
return ptr != nullptr; | |
} | |
// Not UB:tm: | |
template<class T> | |
constexpr T& Peek(usize offset) const { | |
return *(std::bit_cast<T*>(ptr + offset)); | |
} | |
void RewriteString(usize offset, std::string_view newString) const { | |
auto counter = offset; | |
for(auto c : newString) { | |
Peek<char>(counter) = c; | |
counter++; | |
} | |
// C null termination | |
Peek<char>(counter) = '\0'; | |
} | |
u8* Raw() const { | |
return ptr; | |
} | |
auto Size() const { | |
return size; | |
} | |
private: | |
u8* ptr{nullptr}; | |
usize size{0}; | |
}; | |
// Modified EA CRC-16 algorithm. The tables | |
// seem to be standard CRC-16 tables, though. | |
u16 bxCRC16(u8* data, usize length) { | |
u8 crcLo = 0xfb; | |
u8 crcHi = 0xea; // Don't think I didn't notice.. - lily | |
constexpr static u8 crcHiTable[] = { | |
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, | |
0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, | |
0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, | |
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, | |
0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, | |
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, | |
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, | |
0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40 | |
}; | |
constexpr static u8 crcLoTable[] = { | |
0x00, 0xc0, 0xc1, 0x01, 0xc3, 0x03, 0x02, 0xc2, 0xc6, 0x06, 0x07, 0xc7, 0x05, 0xc5, 0xc4, 0x04, 0xcc, 0x0c, 0x0d, 0xcd, 0x0f, 0xcf, 0xce, 0x0e, 0x0a, 0xca, | |
0xcb, 0x0b, 0xc9, 0x09, 0x08, 0xc8, 0xd8, 0x18, 0x19, 0xd9, 0x1b, 0xdb, 0xda, 0x1a, 0x1e, 0xde, 0xdf, 0x1f, 0xdd, 0x1d, 0x1c, 0xdc, 0x14, 0xd4, 0xd5, 0x15, | |
0xd7, 0x17, 0x16, 0xd6, 0xd2, 0x12, 0x13, 0xd3, 0x11, 0xd1, 0xd0, 0x10, 0xf0, 0x30, 0x31, 0xf1, 0x33, 0xf3, 0xf2, 0x32, 0x36, 0xf6, 0xf7, 0x37, 0xf5, 0x35, | |
0x34, 0xf4, 0x3c, 0xfc, 0xfd, 0x3d, 0xff, 0x3f, 0x3e, 0xfe, 0xfa, 0x3a, 0x3b, 0xfb, 0x39, 0xf9, 0xf8, 0x38, 0x28, 0xe8, 0xe9, 0x29, 0xeb, 0x2b, 0x2a, 0xea, | |
0xee, 0x2e, 0x2f, 0xef, 0x2d, 0xed, 0xec, 0x2c, 0xe4, 0x24, 0x25, 0xe5, 0x27, 0xe7, 0xe6, 0x26, 0x22, 0xe2, 0xe3, 0x23, 0xe1, 0x21, 0x20, 0xe0, 0xa0, 0x60, | |
0x61, 0xa1, 0x63, 0xa3, 0xa2, 0x62, 0x66, 0xa6, 0xa7, 0x67, 0xa5, 0x65, 0x64, 0xa4, 0x6c, 0xac, 0xad, 0x6d, 0xaf, 0x6f, 0x6e, 0xae, 0xaa, 0x6a, 0x6b, 0xab, | |
0x69, 0xa9, 0xa8, 0x68, 0x78, 0xb8, 0xb9, 0x79, 0xbb, 0x7b, 0x7a, 0xba, 0xbe, 0x7e, 0x7f, 0xbf, 0x7d, 0xbd, 0xbc, 0x7c, 0xb4, 0x74, 0x75, 0xb5, 0x77, 0xb7, | |
0xb6, 0x76, 0x72, 0xb2, 0xb3, 0x73, 0xb1, 0x71, 0x70, 0xb0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, | |
0x9c, 0x5c, 0x5d, 0x9d, 0x5f, 0x9f, 0x9e, 0x5e, 0x5a, 0x9a, 0x9b, 0x5b, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4b, 0x8b, 0x8a, 0x4a, 0x4e, 0x8e, | |
0x8f, 0x4f, 0x8d, 0x4d, 0x4c, 0x8c, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 | |
}; | |
for (usize i = length - sizeof(u32); i > 0; i -= sizeof(u32)) { | |
// TODO: Slightly more meaningful variable names. | |
u8 tmp = data[1] ^ crcHiTable[*data ^ crcHi] ^ crcLo; | |
u8 tmp2 = data[2] ^ crcHiTable[tmp] ^ crcLoTable[*data ^ crcHi]; | |
u8 tmp3 = data[3] ^ crcHiTable[tmp2] ^ crcLoTable[tmp]; | |
crcLo = crcLoTable[tmp3]; | |
crcHi = (crcHiTable[tmp3] ^ crcLoTable[tmp2]); | |
data += sizeof(u32); | |
} | |
// do a more conventional CRC-16 table hash on specifically | |
// the last 4 bytes.... I dunno the purpose of this... | |
for (usize i = sizeof(u32); i > 0; i--) { | |
u8 tmp = *data ^ crcHi; | |
crcHi = crcHiTable[tmp] ^ crcLo; | |
crcLo = crcLoTable[tmp]; | |
data++; | |
} | |
return crcHi | (crcLo << 8); | |
} | |
int main() { | |
FileBuffer buffer("new_save.bin"); | |
if(buffer.Raw() == nullptr) { | |
std::printf(".. why's it null?\n"); | |
return 1; | |
} | |
// Wipe out the previous checksum, as including it | |
// will cause problems for us later. | |
buffer.Peek<u16>(0x126c) = 0x0; | |
// Calculate the new checksum, and place it inside the buffer. | |
buffer.Peek<u16>(0x126c) = bxCRC16(buffer.Raw(), buffer.Size()); | |
// Display the new checksum, and write the checksummed | |
// save data to a new file. | |
std::printf("Wrote new checksum: 0x%04x\n", buffer.Peek<u16>(0x126c)); | |
buffer.Write("./summed_save.bin"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment