1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| import sys import shutil import json from datetime import datetime from msgpack import Unpacker from lz4.block import decompress as lz4_decompress def decode_savedata(data): save_loader = Unpacker(None, max_buffer_size=0,strict_map_key=False) save_loader.feed(data) for _ in range(2): ext_code, data = save_loader.unpack() assert ext_code == 99 unpacker = Unpacker(None, max_buffer_size=0,strict_map_key=False) unpacker.feed(data) original_size = unpacker.unpack() start_pos = unpacker.tell() print("original_size",original_size,"start_pos",start_pos) data1 = data[start_pos:] raw = lz4_decompress(data1, uncompressed_size=original_size) assert len(raw) == original_size
unpacker = Unpacker(None, max_buffer_size=0,strict_map_key=False) unpacker.feed(raw) d = unpacker.unpack() if 'screenShotData' in d: d['screenShotData'] = '' yield d
def time_format(playedTime): millisec = playedTime % 10000000 ss = int(playedTime / 10000000 % 60) mm = int(playedTime / 600000000 % 60) hh = int(playedTime / 36000000000 % 24) days = int(playedTime / 36000000000) // 24 return (f"{days}." if days else "") + f"{hh:02}:{mm:02}:{ss:02}.{millisec:07}"
def generate_titlesave(header,data) -> bytes: s = '' s += f"TitleSave,{header['difficulty']}\r\n" s += time_format(header['playedTime']) + '\r\n' s += str(header['playedDays']) + '\r\n' s += f"{header['trackedQuestId']},{header['modName']},{header['modId']}\r\n" s += f"{int(data['ScreenShotSize'][0])}, {int(data['ScreenShotSize'][1])}, 0\r\n" s += data['ScreenShot'] + "\r\n" data['ScreenShot'] = ''
data['TotalTime'] = time_format(data['TotalTime']) data['GameWorldTime'] = datetime.fromtimestamp((data['GameWorldTime'] - 621355968000000000) / 10000000).strftime("%Y-%m-%dT%H:%M:%S") data['PlayerPostioion'] = [f"{round(i,5):.5f}" for i in data['PlayerPostioion']] data['PlayerDirection'] = round(data['PlayerDirection'],6) data['RelativeCameraPosition'] = [f"{round(i,5):.5f}" for i in data['RelativeCameraPosition']] data['TaggedPosition'] = [f"{round(i,5):.5f}" for i in data['TaggedPosition']] def recursive_modify(d: dict): for k in d: v = d[k] if k in ['position', 'forward'] and isinstance(v,list): d[k] = [f"{round(j,5):.5f}" for j in d[k]] elif k == 'Span': d[k] = time_format(d[k]) d[k] = d[k][:d[k].rindex(".")] if date_time_tick := d['Time']: d['Time'] = datetime.fromtimestamp((date_time_tick - 621355968000000000) / 10000000).strftime("%Y-%m-%dT%H:%M:%S") elif isinstance(v,dict): recursive_modify(d[k]) elif isinstance(v,list): for it in v: if isinstance(it,dict): recursive_modify(it) recursive_modify(data) data = json.dumps(data,separators=(",",":"),ensure_ascii=False) s += data s = s.encode("utf8") return s
if __name__ == '__main__': if len(sys.argv) < 2: print("usage: python heluo_save_converter.py savefile [output_name]") exit() elif len(sys.argv) == 2: save1 = save2 = sys.argv[1] else: save1, save2, *_ = sys.argv[1:] with open(save1,"rb") as f: f.read(9) binary = f.read() if save1 == save2: shutil.copy(save1,save1+".bak") header,gamedata = list(decode_savedata(binary)) save = generate_titlesave(header,gamedata) with open(save2,"wb") as f: f.write(save)
|