UeSaveSerializer.py
February 27, 2021 ยท View on GitHub
import argparse import json import os import struct import sys from io import BytesIO
HEADER = b'GVAS' HEADER_LEN = 1073
def read(filename, output=sys.stdout): data = None with open(filename, 'rb') as stream: data = stream.read()
if data[:4] != HEADER:
print("Bad Header")
save = deserialize_save(BytesIO(data[HEADER_LEN:]))
print(json.dumps(save, indent=2), file=output)
def deserialize_save(stream): save = {} save['script'] = read_str(stream) save['properties'] = read_properties(stream)
return save
def read_properties(stream): try: data = [] while True: next = read_str(stream) if next == 'None': break data.append(read_property(stream, next)) return data
except Exception as e:
print(e)
print(data)
def read_str(stream): length = read_int(stream) ret = '' for i in range(length - 1): char = stream.read(1).decode() ret += char
if stream.read(1).decode() != '\x00':
raise Exception(f"Bad String ({length}): '{ret}'")
return ret
def read_property(stream, name): #name = read_str(stream) type = read_str(stream)
if type not in deserializers:
raise Exception(f"No deserializer for {type}")
if type == 'StructProperty':
length = read_int(stream)
read_padding(stream, 4)
struct_type = read_str(stream)
read_padding(stream, 17)
data = stream.read(length)
value = read_struct(BytesIO(data), struct_type)
elif type == 'ArrayProperty':
length = read_int(stream)
read_padding(stream, 4)
data = stream.read(length + 20)
value = deserializers[type](BytesIO(data))
elif type == 'BoolProperty':
read_padding(stream, 8)
data = stream.read(2)
value = deserializers[type](BytesIO(data))
else:
length = read_int(stream)
stream.read(1)
read_padding(stream, 4)
data = stream.read(length)
value = deserializers[type](BytesIO(data))
return { 'name': name, 'type': type, 'value': value }
def read_padding(stream, count): ret = '' for i in range(count): byte = stream.read(1) if byte != b'\x00': print(f"Bad Padding: {byte}") ret += '.'
return ret
def read_int(stream): return int.from_bytes(stream.read(4), byteorder='little')
def read_float(stream): return struct.unpack('f', stream.read(4))[0]
def read_bool(stream): return struct.unpack('?', stream.read(1))[0]
def read_struct(stream, type): return { 'type': type, 'properties': read_properties(stream) }
def read_array(stream): read_str(stream) read_int(stream) read_padding(stream, 1)
read_str(stream)
read_str(stream)
length = read_int(stream)
read_padding(stream, 4)
type = read_str(stream)
read_padding(stream, 17)
data_stream = BytesIO(stream.read())
data = []
while data_stream.tell() < length:
props = read_properties(data_stream)
if props:
data.append(props)
else:
break
return { 'type': type, 'data': data }
deserializers = {} deserializers['FloatProperty'] = read_float deserializers['IntProperty'] = read_int deserializers['StrProperty'] = read_str deserializers['ObjectProperty'] = read_str deserializers['SoftObjectProperty'] = read_str deserializers['StructProperty'] = read_struct deserializers['ArrayProperty'] = read_array deserializers['BoolProperty'] = read_bool
if name == "main": parser = argparse.ArgumentParser(description="UE4 Save file serializer") parser.add_argument(metavar='mode', dest='mode', choices=['sav_to_json'], help='Conversion to perform') parser.add_argument(metavar='input', dest='input', help='File to read') parser.add_argument('-o', '--output', dest='output', default=None, help='File to write to') args = parser.parse_args()
output = sys.stdout
if args.output:
output = open(args.output, 'w')
if args.mode == 'sav_to_json':
read(args.input, output=output)