Skip to content

Instantly share code, notes, and snippets.

@modeco80
Created February 15, 2023 21:24
Show Gist options
  • Save modeco80/3b5c20839997030d9359c7bc53353bd6 to your computer and use it in GitHub Desktop.
Save modeco80/3b5c20839997030d9359c7bc53353bd6 to your computer and use it in GitHub Desktop.
SSX OG save checksum
//
// 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