Thats not supposed to be there

This commit is contained in:
coah
2026-03-04 11:28:19 -06:00
parent 8a71ed970a
commit 2479a63bca
9 changed files with 0 additions and 446 deletions

View File

@@ -1,4 +0,0 @@
@echo off
call "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat" x64
cd /d "C:\Users\cole\Documents\GitHub\client\tools\save_converter"
cl /LD /O2 kernelx_shim.c /link /DEF:kernelx.def /OUT:kernelx.dll

View File

@@ -1,403 +0,0 @@
"""
Xbox 360 Minecraft LCE savegame.dat -> Windows .mcs converter
Usage: python convert_x360_save.py <savegame.dat> [output.mcs]
This tool:
1. Strips the 4-byte STFS size prefix from the Xbox 360 save
2. Decompresses LZXRLE data (LZX via xcompress.dll, then custom RLE)
3. Byte-swaps the FileHeader and FileEntry structures (big-endian -> little-endian)
4. Byte-swaps region file data (chunk offsets and timestamps)
5. Re-compresses as ZLIBRLE (zlib + RLE) for the Windows build
6. Writes a .mcs file loadable by the LCE Windows build
"""
import ctypes
import struct
import zlib
import os
import sys
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# --- LZX Decompression via xcompress.dll ---
def load_xcompress():
"""Load xcompress.dll with the kernelx.dll shim."""
os.add_dll_directory(SCRIPT_DIR)
ctypes.WinDLL(os.path.join(SCRIPT_DIR, "kernelx.dll"))
return ctypes.WinDLL(os.path.join(SCRIPT_DIR, "xcompress.dll"))
def lzx_decompress(xcomp, compressed_data, decompressed_size):
"""Decompress LZX data using XMemDecompress."""
XMEMCODEC_LZX = 1
class XMEMCODEC_PARAMETERS_LZX(ctypes.Structure):
_fields_ = [
("Flags", ctypes.c_uint32),
("WindowSize", ctypes.c_uint32),
("CompressionPartitionSize", ctypes.c_uint32),
]
params = XMEMCODEC_PARAMETERS_LZX()
params.Flags = 0
params.WindowSize = 128 * 1024
params.CompressionPartitionSize = 128 * 1024
context = ctypes.c_void_p()
hr = xcomp.XMemCreateDecompressionContext(
XMEMCODEC_LZX,
ctypes.byref(params),
0,
ctypes.byref(context),
)
if hr != 0:
raise RuntimeError(f"XMemCreateDecompressionContext failed: 0x{hr:08X}")
src_buf = (ctypes.c_ubyte * len(compressed_data))(*compressed_data)
dst_buf = (ctypes.c_ubyte * decompressed_size)()
dst_size = ctypes.c_size_t(decompressed_size)
hr = xcomp.XMemDecompress(
context,
dst_buf,
ctypes.byref(dst_size),
src_buf,
ctypes.c_size_t(len(compressed_data)),
)
xcomp.XMemDestroyDecompressionContext(context)
if hr != 0:
raise RuntimeError(f"XMemDecompress failed: 0x{hr:08X}")
return bytes(dst_buf[: dst_size.value])
# --- RLE Decompression (from compression.cpp DecompressLZXRLE) ---
def rle_decompress(data):
"""
Decompress the custom RLE format used by LCE.
Format:
0-254: literal byte
255 followed by 0,1,2: 1,2,3 literal 255s
255 followed by 3-255 followed by byte: run of (count+1) copies of byte
"""
result = bytearray()
i = 0
length = len(data)
while i < length:
b = data[i]
i += 1
if b == 255:
if i >= length:
break
count = data[i]
i += 1
if count < 3:
result.extend(b"\xff" * (count + 1))
else:
if i >= length:
break
val = data[i]
i += 1
result.extend(bytes([val]) * (count + 1))
else:
result.append(b)
return bytes(result)
# --- RLE Compression (from compression.cpp CompressRLE / CompressLZXRLE) ---
def rle_compress(data):
"""
Compress with the custom RLE format used by LCE.
"""
result = bytearray()
i = 0
length = len(data)
while i < length:
b = data[i]
i += 1
count = 1
while i < length and data[i] == b and count < 256:
i += 1
count += 1
if count <= 3:
if b == 255:
result.append(255)
result.append(count - 1)
else:
result.extend(bytes([b]) * count)
else:
result.append(255)
result.append(count - 1)
result.append(b)
return bytes(result)
# --- Endianness Conversion ---
def swap16(data, offset):
"""Swap a 16-bit value in-place."""
data[offset], data[offset + 1] = data[offset + 1], data[offset]
def swap32(data, offset):
"""Swap a 32-bit value in-place."""
data[offset : offset + 4] = data[offset : offset + 4][::-1]
def swap64(data, offset):
"""Swap a 64-bit value in-place."""
data[offset : offset + 8] = data[offset : offset + 8][::-1]
def swap_wchar_array(data, offset, count):
"""Swap an array of wchar_t (2 bytes each)."""
for i in range(count):
swap16(data, offset + i * 2)
def convert_file_header(data):
"""
Convert the FileHeader from big-endian (Xbox 360) to little-endian (Windows).
Layout of decompressed save data:
Bytes 0-3: headerOffset (uint32)
Bytes 4-7: headerSize (uint32) = number of file entries
Bytes 8-9: originalSaveVersion (int16)
Bytes 10-11: saveVersion (int16)
Bytes 12+: file data
At headerOffset: file table (headerSize entries, each 144 bytes)
"""
buf = bytearray(data)
# Read header values in big-endian
header_offset = struct.unpack(">I", buf[0:4])[0]
header_size = struct.unpack(">I", buf[4:8])[0]
orig_version = struct.unpack(">h", buf[8:10])[0]
save_version = struct.unpack(">h", buf[10:12])[0]
print(f" Header offset: {header_offset}")
print(f" File count: {header_size}")
print(f" Original version: {orig_version}")
print(f" Save version: {save_version}")
# Swap the header fields to little-endian
swap32(buf, 0) # headerOffset
swap32(buf, 4) # headerSize
swap16(buf, 8) # originalSaveVersion
swap16(buf, 10) # saveVersion
# Swap each FileEntry in the file table
# FileEntrySaveDataV2 = 144 bytes:
# wchar_t filename[64] = 128 bytes
# uint32 length = 4 bytes
# uint32 startOffset = 4 bytes
# int64 lastModified = 8 bytes
ENTRY_SIZE = 144
print(f"\n Files in save:")
for i in range(header_size):
entry_offset = header_offset + i * ENTRY_SIZE
# Read filename before swapping (big-endian wchar)
raw_name = buf[entry_offset : entry_offset + 128]
name_chars = []
for j in range(64):
c = struct.unpack(">H", raw_name[j * 2 : j * 2 + 2])[0]
if c == 0:
break
name_chars.append(chr(c))
filename = "".join(name_chars)
length = struct.unpack(">I", buf[entry_offset + 128 : entry_offset + 132])[0]
start = struct.unpack(">I", buf[entry_offset + 132 : entry_offset + 136])[0]
print(f" {filename!r}: offset={start}, length={length}")
# Swap the fields
swap_wchar_array(buf, entry_offset, 64) # filename
swap32(buf, entry_offset + 128) # length
swap32(buf, entry_offset + 132) # startOffset
swap64(buf, entry_offset + 136) # lastModifiedTime
return bytes(buf), header_size, header_offset, save_version
def convert_region_files(data, header_offset, header_size):
"""
Convert region file data from big-endian to little-endian.
Region files (.mcr) contain:
- Sector 0: offset table (1024 uint32 entries)
- Sector 1: timestamp table (1024 uint32 entries)
- Sectors 2+: chunk data with per-chunk headers
This converts the offset/timestamp tables and chunk headers.
"""
buf = bytearray(data)
ENTRY_SIZE = 144
SECTOR_SIZE = 4096
SECTOR_INTS = 1024
for i in range(header_size):
entry_offset = header_offset + i * ENTRY_SIZE
# Read filename (now in little-endian after header conversion)
name_chars = []
for j in range(64):
c = struct.unpack("<H", buf[entry_offset + j * 2 : entry_offset + j * 2 + 2])[0]
if c == 0:
break
name_chars.append(chr(c))
filename = "".join(name_chars)
if not filename.endswith(".mcr"):
continue
file_start = struct.unpack("<I", buf[entry_offset + 132 : entry_offset + 136])[0]
file_length = struct.unpack("<I", buf[entry_offset + 128 : entry_offset + 132])[0]
print(f" Converting region file: {filename} (offset={file_start}, size={file_length})")
if file_length < 2 * SECTOR_SIZE:
print(f" Region file too small, skipping")
continue
# Swap offset table (sector 0: 1024 uint32 values)
for j in range(SECTOR_INTS):
swap32(buf, file_start + j * 4)
# Swap timestamp table (sector 1: 1024 uint32 values)
for j in range(SECTOR_INTS):
swap32(buf, file_start + SECTOR_SIZE + j * 4)
# Swap chunk data headers
# Each chunk entry in the offset table encodes: (sector_count & 0xFF) | (sector_offset << 8)
# The chunk data at each sector has an 8-byte header:
# uint32 compressed_length (MSB = RLE flag)
# uint32 decompressed_length
for j in range(SECTOR_INTS):
offset_entry = struct.unpack("<I", buf[file_start + j * 4 : file_start + j * 4 + 4])[0]
if offset_entry == 0:
continue
sector_count = offset_entry & 0xFF
sector_offset = offset_entry >> 8
if sector_offset < 2:
continue
chunk_data_offset = file_start + sector_offset * SECTOR_SIZE
if chunk_data_offset + 8 > len(buf):
continue
# Swap the chunk header (compressed_length and decompressed_length)
swap32(buf, chunk_data_offset)
swap32(buf, chunk_data_offset + 4)
return bytes(buf)
# --- Main Conversion ---
def convert_save(input_path, output_path):
print(f"Loading {input_path}...")
with open(input_path, "rb") as f:
raw_data = f.read()
file_size = len(raw_data)
print(f"File size: {file_size} bytes")
# Check for 4-byte STFS size prefix
# The first 4 bytes (BE) should match approximately (file_size - padding - 4)
prefix_size = struct.unpack(">I", raw_data[0:4])[0]
comp_flag = struct.unpack(">I", raw_data[4:8])[0]
if comp_flag == 0 and prefix_size > 0 and prefix_size < file_size:
print(f"Detected 4-byte STFS prefix (size={prefix_size})")
save_data = raw_data[4:]
else:
save_data = raw_data
# Parse compression header
compressed_flag = struct.unpack(">I", save_data[0:4])[0]
if compressed_flag != 0:
print("Save data does not appear to be compressed (flag != 0)")
print("Treating as raw uncompressed save data")
decompressed = save_data
else:
decomp_size = struct.unpack(">I", save_data[4:8])[0]
compressed_data = save_data[8:]
print(f"Compressed save: decompressed size = {decomp_size} ({decomp_size/1024/1024:.1f} MB)")
print(f"Compressed data size: {len(compressed_data)} bytes")
# LZX decompress (whole-file level is pure LZX, not LZXRLE)
# The RLE layer is only used per-chunk inside region files
print("\nStep 1: LZX decompression...")
xcomp = load_xcompress()
decompressed = lzx_decompress(xcomp, compressed_data, decomp_size)
print(f" LZX decompressed: {len(decompressed)} bytes")
# Step 2: Inspect the raw save structure (big-endian)
print("\nStep 2: Inspecting raw save data (big-endian Xbox 360 format)...")
header_offset = struct.unpack(">I", decompressed[0:4])[0]
file_count = struct.unpack(">I", decompressed[4:8])[0]
orig_version = struct.unpack(">h", decompressed[8:10])[0]
save_version = struct.unpack(">h", decompressed[10:12])[0]
print(f" Header offset: {header_offset}")
print(f" File count: {file_count}")
print(f" Original version: {orig_version}, Save version: {save_version}")
# List files in the save
ENTRY_SIZE = 144
print(f"\n Files in save:")
for i in range(file_count):
entry_off = header_offset + i * ENTRY_SIZE
name_chars = []
for j in range(64):
c = struct.unpack(">H", decompressed[entry_off + j * 2 : entry_off + j * 2 + 2])[0]
if c == 0:
break
name_chars.append(chr(c))
filename = "".join(name_chars)
length = struct.unpack(">I", decompressed[entry_off + 128 : entry_off + 132])[0]
start = struct.unpack(">I", decompressed[entry_off + 132 : entry_off + 136])[0]
print(f" {filename}: offset={start}, size={length}")
# Step 3: Write raw decompressed data
# The game code will handle endianness conversion when loaded with plat=X360
# Write as uncompressed (first 4 bytes != 0 tells the game it's uncompressed)
print(f"\nStep 3: Writing {output_path}...")
with open(output_path, "wb") as f:
f.write(decompressed)
print(f"\nDone! Output: {output_path} ({len(decompressed)} bytes)")
print(f"\nTo use this save with the LCE source code:")
print(f" 1. Ensure xcompress.dll is available to the Windows build")
print(f" 2. Replace XMemDecompress stubs with real implementation")
print(f" 3. Load with: ConsoleSaveFileOriginal(name, data, size, false, SAVE_FILE_PLATFORM_X360)")
print(f" 4. Call pSave->ConvertToLocalPlatform() after loading")
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <savegame.dat> [output.mcs]")
sys.exit(1)
input_file = sys.argv[1]
if len(sys.argv) >= 3:
output_file = sys.argv[2]
else:
base = os.path.splitext(input_file)[0]
output_file = base + ".mcs"
convert_save(input_file, output_file)

View File

@@ -1,14 +0,0 @@
LIBRARY kernelx
EXPORTS
XMemAlloc
XMemFree
SetUnhandledExceptionFilter = kernel32.SetUnhandledExceptionFilter
UnhandledExceptionFilter = kernel32.UnhandledExceptionFilter
GetTickCount = kernel32.GetTickCount
GetSystemTimeAsFileTime = kernel32.GetSystemTimeAsFileTime
GetCurrentThreadId = kernel32.GetCurrentThreadId
GetCurrentProcessId = kernel32.GetCurrentProcessId
QueryPerformanceCounter = kernel32.QueryPerformanceCounter
Sleep = kernel32.Sleep
TerminateProcess = kernel32.TerminateProcess
GetCurrentProcess = kernel32.GetCurrentProcess

Binary file not shown.

View File

@@ -1,25 +0,0 @@
// kernelx.dll shim - forwards standard Win32 functions to kernel32.dll
// and provides stub implementations for Xbox-specific XMemAlloc/XMemFree
#include <windows.h>
#include <stdlib.h>
// Forward declarations for exports
__declspec(dllexport) void* __stdcall XMemAlloc(SIZE_T dwSize, DWORD dwAllocAttributes);
__declspec(dllexport) void __stdcall XMemFree(void* pAddress, DWORD dwAllocAttributes);
// Xbox-specific memory functions - map to standard heap
__declspec(dllexport) void* __stdcall XMemAlloc(SIZE_T dwSize, DWORD dwAllocAttributes) {
(void)dwAllocAttributes;
return malloc(dwSize);
}
__declspec(dllexport) void __stdcall XMemFree(void* pAddress, DWORD dwAllocAttributes) {
(void)dwAllocAttributes;
free(pAddress);
}
// All other functions are forwarded to kernel32.dll via the .def file
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
(void)hinstDLL; (void)fdwReason; (void)lpReserved;
return TRUE;
}

Binary file not shown.