Skip to content

Instantly share code, notes, and snippets.

@Rob7045713
Created February 27, 2021 20:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rob7045713/2f838ad66237f87c86d5396af573b71c to your computer and use it in GitHub Desktop.
Save Rob7045713/2f838ad66237f87c86d5396af573b71c to your computer and use it in GitHub Desktop.
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment