diff --git a/Base64.hpp b/Base64.hpp new file mode 100755 index 0000000..aafa69d --- /dev/null +++ b/Base64.hpp @@ -0,0 +1,45 @@ +#pragma once +#include +#include + +typedef unsigned char uchar; + +namespace base64 { + const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string Encode(const std::string &in) + { + std::string out; + int val = 0, valb = -6; + for (uchar c : in) { + val = (val << 8) + c; + valb += 8; + while (valb >= 0) { + out.push_back(base64_chars[(val >> valb) & 0x3F]); + valb -= 6; + } + } + if (valb > -6) out.push_back(base64_chars[((val << 8) >> (valb + 8)) & 0x3F]); + while (out.size() % 4) out.push_back('='); + return out; + } + + std::string Decode(const std::string &in) + { + std::string out; + std::vector T(256, -1); + for (int i = 0; i < 64; i++) T[base64_chars[i]] = i; + + int val = 0, valb = -8; + for (uchar c : in) { + if (T[c] == -1) break; + val = (val << 6) + T[c]; + valb += 6; + if (valb >= 0) { + out.push_back(char((val >> valb) & 0xFF)); + valb -= 8; + } + } + return out; + } +} \ No newline at end of file diff --git a/GDGameSave.hpp b/GDGameSave.hpp new file mode 100644 index 0000000..6d1a230 --- /dev/null +++ b/GDGameSave.hpp @@ -0,0 +1,155 @@ +#pragma once + +#include +#include +#include "Base64.hpp" + +constexpr int CHUNK = 16384; +constexpr int WINDOW_BITS = 15; + +//Reads the contents of a given file into a string +std::string FileToStr(const std::string& filename) { + std::ifstream file(filename, std::ios::binary); + if (!file.good()) + return ""; + std::string ret(std::istreambuf_iterator(file), {}); + file.close(); + return ret; +} + +//Writes the contents of a string to the given file +void StrToFile(const std::string& filename, const std::string& data) { + std::ofstream ofile; + ofile.open(filename); + ofile << data; + ofile.close(); +} + +//XORs each byte of a string reference +void Xor_Str(std::string& str, unsigned char key) { + for (auto& c : str) { + c = c^key; + } +} + +std::string GZipDecompress(const std::string& in) +{ + //Initialize all members of strm to be 0 + z_stream strm = {}; + //Output buffer + char out[CHUNK]; + //Holds return value + std::string final_output; + //Initialize GZip decompression; except on failure + if(inflateInit2(&strm, WINDOW_BITS + 16) != Z_OK) + throw(std::exception()); + //Tell zlib where to find the data we want to decompress + strm.avail_in = in.size(); + strm.next_in = (Bytef*)in.data(); + + do { + //Tell zlib where to store the decompressed output data + strm.avail_out = CHUNK; + strm.next_out = (Bytef*)out; + //Decompress our input data; except on failure + switch (inflate(&strm, Z_FINISH)) { + case Z_NEED_DICT: + throw(std::exception()); + case Z_DATA_ERROR: + throw(std::exception()); + case Z_MEM_ERROR: + throw(std::exception()); + } + //How many bytes we received into our output buffer + unsigned int have = CHUNK - strm.avail_out; + //Append received data to our return string + final_output.append(reinterpret_cast(out), have); + } while (strm.avail_out == 0); + //Clean up and return + inflateEnd(&strm); + return final_output; +} + +std::string GZipCompress(const std::string& in) +{ + //Initialize all members of strm to be 0 + z_stream strm = {}; + //Output buffer + unsigned char out[CHUNK]; + //Return value + std::string final_output; + //Initialize GZip compression; except on failure + if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) + throw(std::exception()); + //Tell zlib where to find the data we want to compress + strm.avail_in = in.size(); + strm.next_in = (Bytef*)in.data(); + + do { + //Tell zlib where to store the compressed output data + strm.avail_out = CHUNK; + strm.next_out = out; + //Compress our input data; except on failure + if(deflate(&strm, Z_FINISH) == Z_STREAM_ERROR) + throw(std::exception()); + //How many bytes we received into our output buffer + unsigned int have = CHUNK - strm.avail_out; + //Append received data to our return string + final_output.append(reinterpret_cast(out), have); + } while (strm.avail_out == 0); + //Clean up and return + deflateEnd(&strm); + return final_output; +} + +std::string Decrypt(const std::string& filename) { + //load file + std::string ret = FileToStr(filename); + //Xor with value 11 + Xor_Str(ret, 11); + //replace - with + and _ with / + for (auto& i : ret) { + if (i == '-') + i = '+'; + if (i == '_') + i = '/'; + } + //Decrypt with Base64 + ret = base64::Decode(ret); + //Decompress with GZip + ret = GZipDecompress(ret); + //Return + return ret; +} + +std::string Encrypt(const std::string& filename) { + //load file + std::string data = FileToStr(filename); + //Calculate crc32 checksum + uLong crc = crc32(0L, Z_NULL, 0); + uint32_t crc32_sum = crc32(crc, (Bytef*)data.data(), data.size()); + uint32_t dataSize = data.size(); + //Compress with GZip + std::string ret = GZipCompress(data); + //Remove first 2 and last 4 chars of string + ret = ret.substr(2, ret.size() - 4 - 2); + //Add header, checksum and size + char header[10] = {'\x1f', '\x8b', '\x08', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x0b'}; + ret = std::string(header, sizeof(header)) + + ret + + std::string((const char*)&crc32_sum, sizeof(crc32_sum)) + //Use raw binary data + std::string((const char*)&dataSize, sizeof(dataSize)); //Use raw binary data + //Encrypt with Base64 + ret = base64::Encode(ret); + //replace + with - and / with _ + for (auto& i : ret) { + if (i == '+') + i = '-'; + if (i == '/') + i = '_'; + } + //Xor with value 11 + Xor_Str(ret, 11); + //Return + return ret; +} \ No newline at end of file diff --git a/Main.cpp b/Main.cpp new file mode 100755 index 0000000..7f6b236 --- /dev/null +++ b/Main.cpp @@ -0,0 +1,48 @@ +#include +#include "GDGameSave.hpp" + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + // Using Windows + const std::string gdLocalFolder = getenv("APPDATA") + std::string("\\..\\Local\\GeometryDash\\"); +#else + // Using GNU/Linux + const std::string gdLocalFolder = getenv("HOME") + std::string("/.local/share/Steam/steamapps/compatdata/322170/pfx/drive_c/users/steamuser/Local Settings/Application Data/GeometryDash/"); +#endif + +const std::string usage = R"(Usage: + -d: decrypt + -e: encrypt + -es: encrypt and save to GD folder)"; + +const std::string saves[2] = { + "CCGameManager.dat", + "CCLocalLevels.dat", +}; + +int main(int argc, const char** argv) { + std::vector args; + for(int i = 0; i < argc; i++) + args.push_back(argv[i]); + + if(argc < 2) + { + std::cout << usage << std::endl; + exit(1); + } + + if(args[1] == "-d") + { + const std::string decrypted = Decrypt(gdLocalFolder + saves[0]); + StrToFile(saves[0] + ".xml", decrypted); + } + else if(args[1] == "-e" || args[1] == "-es") + { + const std::string encrypted = Encrypt(saves[0] + ".xml"); + if(args[1] == "-e") + StrToFile(saves[0], encrypted); + else + StrToFile(gdLocalFolder + saves[0], encrypted); + } + + exit(0); +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c1f3241 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +CXX=g++ +CXXFLAGS=-lz + +gd_decr: Main.cpp Base64.hpp GDGameSave.hpp + $(CXX) -o $@ $^ $(CXXFLAGS) + +clean: + rm ./gd_decr