From 6ead0ee4d37cb64ab36cb1727d3edf90d11adf64 Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Sat, 9 Mar 2024 19:38:11 -0500 Subject: initial commit --- srb_lib.py | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 srb_lib.py (limited to 'srb_lib.py') diff --git a/srb_lib.py b/srb_lib.py new file mode 100644 index 0000000..f3a1deb --- /dev/null +++ b/srb_lib.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# Startdate: 2024-03-08-6 15:28 +# File: srb_lib.py +# Purpose: library for srb.exe savegame hacking +# SPDX-License-Identifier: GPL-3.0-only +# History: +# Usage: +# from srb.py +# Reference: +# blog posts 2024-03 +# +# crc width=32 poly=0x4c11db7 init=0x0 xorout=0x235b4b9c refin=false refout=false out_endian=little +# Incorrect functions: https://gist.github.com/djsweet/3477115595efab31905be7000bb013bc FAILED +# Fuctions that worked: https://gist.github.com/Lauszus/6c787a3bc26fea6e842dfb8296ebd630 Author: Lauszus +# https://stackoverflow.com/questions/46109815/reversing-the-byte-order-of-a-string-containing-hexadecimal-characters +# https://gamefaqs.gamespot.com/pc/930591-snoopy-vs-the-red-baron/faqs/46161 +# Documentation: +# winetricks vd=1024x768 +# winetricks vd=off +# Dependencies: +# python3-crcmod + +import sys, struct +srb_lib_version = "20240309a" + +# Table of byte positions of values in the savegame file, minus the first four bytes which are the checksum. Due to zero-indexing of python lists, but for ease of usage, we will always put a zero as the value of index 0. That is, profile 1 will use index 1 of the list. +# money is 0x270 bytes after the "ZL',checksum)) + #f.write(struct.pack('>I',int(checksum))) + f.write(data) + +######## start copy-paste 2 +# https://gist.github.com/Lauszus/6c787a3bc26fea6e842dfb8296ebd630 + +def reflect_data(x, width): + # See: https://stackoverflow.com/a/20918545 + if width == 8: + x = ((x & 0x55) << 1) | ((x & 0xAA) >> 1) + x = ((x & 0x33) << 2) | ((x & 0xCC) >> 2) + x = ((x & 0x0F) << 4) | ((x & 0xF0) >> 4) + elif width == 16: + x = ((x & 0x5555) << 1) | ((x & 0xAAAA) >> 1) + x = ((x & 0x3333) << 2) | ((x & 0xCCCC) >> 2) + x = ((x & 0x0F0F) << 4) | ((x & 0xF0F0) >> 4) + x = ((x & 0x00FF) << 8) | ((x & 0xFF00) >> 8) + elif width == 32: + x = ((x & 0x55555555) << 1) | ((x & 0xAAAAAAAA) >> 1) + x = ((x & 0x33333333) << 2) | ((x & 0xCCCCCCCC) >> 2) + x = ((x & 0x0F0F0F0F) << 4) | ((x & 0xF0F0F0F0) >> 4) + x = ((x & 0x00FF00FF) << 8) | ((x & 0xFF00FF00) >> 8) + x = ((x & 0x0000FFFF) << 16) | ((x & 0xFFFF0000) >> 16) + else: + raise ValueError('Unsupported width') + return x + +def crc_poly(data, n, poly, crc=0, ref_in=False, ref_out=False, xor_out=0): + g = 1 << n | poly # Generator polynomial + # Loop over the data + for d in data: + # Reverse the input byte if the flag is true + if ref_in: + d = reflect_data(d, 8) + # XOR the top byte in the CRC with the input byte + crc ^= d << (n - 8) + # Loop over all the bits in the byte + for _ in range(8): + # Start by shifting the CRC, so we can check for the top bit + crc <<= 1 + # XOR the CRC if the top bit is 1 + if crc & (1 << n): + crc ^= g + # Reverse the output if the flag is true + if ref_out: + crc = reflect_data(crc, n) + # Return the CRC value + return crc ^ xor_out +##### end copy-paste 2 + +def _get_data_from_data_object(data_object): + """ Helper function to either open file, or pass bytes through. """ + if type(data_object) == str: + _, data = read_file(data_object) + elif type(data_object) == bytes: + data = data_object + return data + +def _convert_dec_to_reverse_bytes(num_dec): + """ Helper function to turn a given decimal value into the little-endian values for insertion into the data. """ + # Here is how to do this manually. + #num_str = str(hex(num_dec))[2:] + #num_str = ("0" if len(num_str) % 2 else "") + num_str + #num_bytes = bytearray.fromhex(num_str) + #num_bytes.reverse() + #return bytes(num_bytes) + # I learned how to do this the pythonic way. + return struct.pack('15 + else: + return weapon, WEAPONS[weapon] + else: + return -1, f"invalid way to reference weapon: [{type(weapon)}]. Use index or name." + +def calculate_checksum(data): + """ Return the 4-byte checksum used by the game for the provided data. """ + # aka # CRC-32/BZIP2 + # The polynomial appears to be a standard one like used for Ethernet, but I am uncertain of the xor is standard or not. + # I learned all this only with + # crc width=32 poly=0x4c11db7 init=0x0 xorout=0x235b4b9c refin=false refout=false out_endian=little + csum = hex(crc_poly(data, 32, 0x4C11DB7, crc=0x0, ref_in=False, ref_out=False, xor_out=0x235b4b9c)) + # comes in as ceb434d4, but needs to be d434b4ce, and bytearray is convenient way to swap it like that + # trim off the '0x' from the string + #print(f"Got csum={csum}") + csum = bytearray.fromhex(str(csum)[2:]) + csum.reverse() + # and back to bytes because that is how we will want to use it. + return bytes(csum) + +def correct_file(filename, debuglevel = 0): + """ Given a savegame file, calculate the correct checksum and store that sum instead of whatever is there. """ + cs, data = read_file(filename) + csum = calculate_checksum(data) + ran = False + if csum != cs: + if debuglev(5,debuglevel): + ferror(f"Stored checksum is {cs} and will update it to {csum}") + write_file(filename,csum,data) + ran = True + else: + if debuglev(2,debuglevel): + ferror(f"Stored checksum is still correct, {csum}. Skipping {filename}") + return ran -- cgit