Last active
June 7, 2025 08:17
-
-
Save UserUnknownFactor/74698a2ec1df3643b164f301981ea961 to your computer and use it in GitHub Desktop.
Tool to decrypt and re-encrypt Bruns engine game files (images and scripts)
This file contains hidden or 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
import os | |
import struct | |
import argparse | |
import numpy as np | |
import zlib | |
def generate_xor_key(size): | |
"""Generate the XOR key based on the size value""" | |
xor_key = np.array([ | |
size & 0xFF, | |
(size >> 8) & 0xFF, | |
(size >> 16) & 0xFF, | |
(size >> 24) & 0xFF | |
], dtype=np.uint8) | |
# Apply the transformation from the original code | |
xor_key[0] ^= (0xBEEF & 0xFF) | |
xor_key[1] ^= ((0xBEEF >> 8) & 0xFF) | |
xor_key[2] ^= ((0xDEADBEEF >> 16) & 0xFF) | |
xor_key[3] ^= ((0xDEADBEEF >> 24) & 0xFF) | |
return xor_key | |
def decrypt_file(input_file, output_file): | |
with open(input_file, 'rb') as f: | |
data = f.read() | |
if not (data[0:3] == b'EEN' and (data[3] == ord('C') or data[3] == ord('Z'))): | |
print(f"Invalid file format: {input_file}") | |
return False | |
is_compressed = (data[3] == ord('Z')) | |
size = struct.unpack('<I', data[4:8])[0] | |
xor_key = generate_xor_key(size) | |
encrypted_data = np.frombuffer(data[8:], dtype=np.uint8) | |
repeated_key = np.tile(xor_key, (len(encrypted_data) + 3) // 4)[:len(encrypted_data)] | |
decrypted_data = encrypted_data ^ repeated_key | |
if is_compressed: | |
try: | |
decompressed_data = zlib.decompress(decrypted_data.tobytes()) | |
if len(decompressed_data) != size: | |
print(f"Warning: Decompressed size ({len(decompressed_data)}) doesn't match expected size ({size})") | |
final_data = decompressed_data | |
except zlib.error as e: | |
print(f"Decompression error: {e}") | |
return False | |
else: | |
# For EENC files, just use the decrypted data | |
if len(decrypted_data) != size: | |
print(f"Warning: Decrypted size ({len(decrypted_data)}) doesn't match expected size ({size})") | |
final_data = decrypted_data.tobytes() | |
# Write the final data to the output file | |
with open(output_file, 'wb') as f: | |
f.write(final_data) | |
print(f"Successfully decrypted: {input_file} -> {output_file}") | |
return True | |
def encrypt_file(input_file, output_file, force_format=None): | |
with open(input_file, 'rb') as f: | |
data = f.read() | |
if force_format: | |
is_compressed = force_format.upper() == 'EENZ' | |
else: | |
ext = os.path.splitext(input_file)[1].lower() | |
is_compressed = (ext != '.png') | |
header = bytearray() | |
header.extend(b'EEN') | |
header.extend(b'Z' if is_compressed else b'C') | |
if is_compressed: | |
compressed_data = zlib.compress(data) | |
processed_data = compressed_data | |
original_size = len(data) | |
else: | |
processed_data = data | |
original_size = len(data) | |
# Add size to header | |
header.extend(struct.pack('<I', original_size)) | |
xor_key = generate_xor_key(original_size) | |
data_array = np.frombuffer(processed_data, dtype=np.uint8) | |
repeated_key = np.tile(xor_key, (len(data_array) + 3) // 4)[:len(data_array)] | |
encrypted_data = data_array ^ repeated_key | |
with open(output_file, 'wb') as f: | |
f.write(header) | |
f.write(encrypted_data.tobytes()) | |
print(f"Successfully encrypted: {input_file} -> {output_file}") | |
return True | |
def batch_process(input_dir, output_dir, mode): | |
if not os.path.exists(output_dir): | |
os.makedirs(output_dir) | |
success_count = 0 | |
fail_count = 0 | |
for file in os.listdir(input_dir): | |
input_path = os.path.join(input_dir, file) | |
if os.path.isfile(input_path): | |
if mode == 'decrypt': | |
if file.endswith('.bso') or file.endswith('.png'): | |
if decrypt_file(input_path, os.path.join(output_dir, file)): | |
success_count += 1 | |
else: | |
fail_count += 1 | |
else: # encrypt | |
output_path = os.path.join(output_dir, file) | |
force_format = 'EENC' if file.lower().endswith('.png') else 'EENZ' | |
if encrypt_file(input_path, output_path, force_format): | |
success_count += 1 | |
else: | |
fail_count += 1 | |
print(f"{mode.capitalize()} complete. Success: {success_count}, Failed: {fail_count}") | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description='Encrypt/Decrypt Bruns game resource files') | |
parser.add_argument('--mode', choices=['encrypt', 'decrypt'], required=True, help='Operation mode') | |
parser.add_argument('--file', help='Single file to process') | |
parser.add_argument('--output', help='Output file for single file processing') | |
parser.add_argument('--format', choices=['EENC', 'EENZ'], help='Force format for encryption (EENC for uncompressed, EENZ for compressed)') | |
parser.add_argument('--input-dir', help='Directory containing files to process') | |
parser.add_argument('--output-dir', help='Directory to save processed files') | |
args = parser.parse_args() | |
if args.file and args.output: | |
if args.mode == 'decrypt': | |
decrypt_file(args.file, args.output) | |
else: | |
encrypt_file(args.file, args.output, args.format) | |
elif args.input_dir and args.output_dir: | |
batch_process(args.input_dir, args.output_dir, args.mode) | |
else: | |
parser.print_help() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment