├── LuaExtractor.py ├── README.md ├── amongus.py ├── descpb_set_to_proto.py ├── enc_dispatch_decryptor.py ├── map_merger.py ├── ooooooooooooooo.py ├── protogenerator.py ├── typedumper.py └── typedumperaarch64.py /LuaExtractor.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import os, sys 3 | import re 4 | from typing import List 5 | 6 | r = re.compile(rb'\x1bLuaS\x00', re.DOTALL) 7 | 8 | def main(lua_path: str) -> None: 9 | f = open(lua_path, 'rb+') 10 | data = f.read() 11 | 12 | matches = [i for i in r.finditer(data)] 13 | 14 | asd = lambda matches, x: [matches[i:i+x] for i in range(0, len(matches), 2)] 15 | #final_list: List[List[re.Match[bytes]]] = asd(matches, 2) 16 | print(len(matches)) 17 | 18 | 19 | for idx, i in enumerate(matches): 20 | beg_offset = i.start() 21 | if idx == len(matches) - 1: 22 | f.seek(0, os.SEEK_END) 23 | end_offset = f.tell() 24 | else: 25 | end_offset = matches[idx+1].start() 26 | 27 | """ 28 | chunk_1 = i[0].group() 29 | beg_offset = i[0].start() 30 | if len(i) > 1: 31 | chunk_2 = i[1].group() 32 | end_offset = i[1].start() - 1 33 | else: 34 | f.seek(0, os.SEEK_END) 35 | end_offset = f.tell() 36 | """ 37 | 38 | f.seek(beg_offset) 39 | luachunk = f.read(end_offset - beg_offset) 40 | 41 | #Make a folder 42 | if not os.path.exists(f'luas/{lua_path.replace(".bytes", "")}_chunks'): 43 | os.makedirs(f'luas/{lua_path.replace(".bytes", "")}_chunks') 44 | 45 | with open(f'luas/{lua_path.replace(".bytes", "")}_chunks/{idx}.luac', 'wb') as luafile: 46 | luafile.write(luachunk) 47 | print(f'{idx}.luac saved!') 48 | 49 | #print(f'{idx} - LuaS at {i.start()}') 50 | #print(i.group()) 51 | #print('\n') 52 | #print(chunk_1, chunk_2) 53 | 54 | 55 | if __name__ == '__main__': 56 | 57 | if len(sys.argv) != 2: 58 | print('HSR Lua Extractor') 59 | print('Usage: {} '.format(sys.argv[0])) 60 | sys.exit(1) 61 | 62 | lua = sys.argv[1] 63 | if os.path.isfile(lua): 64 | main(lua) 65 | else: 66 | print(f'Cannot find {lua}, please check your path.') 67 | sys.exit(1) 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sus-scripts 2 | some sus scripts 3 | -------------------------------------------------------------------------------- /amongus.py: -------------------------------------------------------------------------------- 1 | import os, sys, re 2 | 3 | if __name__ == "__main__": 4 | # Path to the binary from argv 5 | binary = sys.argv[1] 6 | 7 | # Open the binary 8 | with open(binary, "rb") as f: 9 | pattern = b"\x62\x06\x70\x72\x6F\x74\x6F\x33\x00" 10 | data = f.read() 11 | match: list[re.Match] = list(re.finditer(pattern, data)) 12 | 13 | if match: 14 | for idx, m in enumerate(match): 15 | endoffset = m.start() 16 | startoffset = endoffset 17 | # Travel backwards until we find 0x000A 18 | while True: 19 | if data[startoffset] == 0x00 and data[startoffset + 1] == 0x0A: 20 | break 21 | startoffset -= 1 22 | 23 | if data[startoffset] == 0x00 and data[startoffset + 1] == 0x0a: 24 | binary_data = data[startoffset+1:endoffset+len(pattern)-1] 25 | 26 | with open(f"out/{idx}.bin", "wb") as outf: 27 | outf.write(binary_data) 28 | 29 | -------------------------------------------------------------------------------- /descpb_set_to_proto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | #-*- encoding: Utf-8 -*- 3 | import os 4 | import sys 5 | from google.protobuf.descriptor_pb2 import DescriptorProto, FieldDescriptorProto, FileDescriptorProto, FileDescriptorSet 6 | from collections import OrderedDict 7 | from itertools import groupby 8 | 9 | """ 10 | This script converts back a FileDescriptor structure to a readable .proto file. 11 | 12 | There is already a function in the standard C++ library that does this [1], but 13 | - It is not accessible through the Python binding 14 | - This implementation has a few output readability improvements, i.e 15 | -- Declaring enums/messages after first use rather than at top of block 16 | -- Not always using full names when referencing messages types 17 | -- Smaller aesthetic differences (number of tabs, line jumps) 18 | 19 | For reference of the FileDescriptor structure, see [2]. 20 | Other (less complete) implementations of this are [3] or [4]. 21 | 22 | [1] https://github.com/google/protobuf/blob/5a76e/src/google/protobuf/descriptor.cc#L2242 23 | [2] https://github.com/google/protobuf/blob/bb77c/src/google/protobuf/descriptor.proto#L59 24 | 25 | [3] https://github.com/fry/d3/blob/master/decompile_protobins.py 26 | [4] https://github.com/sarum9in/bunsan_binlogs_python/blob/master/src/python/source.py 27 | """ 28 | 29 | INDENT = ' ' * 4 30 | 31 | def descpb_to_proto(desc): 32 | out = 'syntax = "%s";\n\n' % (desc.syntax or 'proto2') 33 | 34 | scopes = [''] 35 | if desc.package: 36 | out += 'package %s;\n\n' % desc.package 37 | scopes[0] += '.' + desc.package 38 | 39 | for index, dep in enumerate(desc.dependency): 40 | prefix = ' public' * (index in desc.public_dependency) 41 | prefix += ' weak' * (index in desc.weak_dependency) 42 | out += 'import%s "%s";\n' % (prefix, dep) 43 | scopes.append('.' + ('/' + dep.rsplit('/', 1)[0])[1:].replace('/', '.')) 44 | 45 | out += '\n' * (out[-2] != '\n') 46 | 47 | out += parse_msg(desc, scopes, desc.syntax).strip('\n') 48 | name = desc.name.replace('..', '').strip('.\\/') 49 | 50 | return name, out + '\n' 51 | 52 | def parse_msg(desc, scopes, syntax): 53 | out = '' 54 | is_msg = isinstance(desc, DescriptorProto) 55 | 56 | if is_msg: 57 | scopes = list(scopes) 58 | scopes[0] += '.' + desc.name 59 | 60 | blocks = OrderedDict() 61 | for nested_msg in (desc.nested_type if is_msg else desc.message_type): 62 | blocks[nested_msg.name] = parse_msg(nested_msg, scopes, syntax) 63 | 64 | for enum in desc.enum_type: 65 | out2 = '' 66 | for val in enum.value: 67 | out2 += '%s = %s;\n' % (val.name, fmt_value(val.number, val.options)) 68 | 69 | if len(set(i.number for i in enum.value)) == len(enum.value): 70 | enum.options.ClearField('allow_alias') 71 | 72 | blocks[enum.name] = wrap_block('enum', out2, enum) 73 | 74 | if is_msg and desc.options.map_entry: 75 | return ' map<%s>' % ', '.join(min_name(i.type_name, scopes) \ 76 | if i.type_name else types[i.type] \ 77 | for i in desc.field) 78 | 79 | if is_msg: 80 | for field in desc.field: 81 | out += fmt_field(field, scopes, blocks, syntax) 82 | 83 | for index, oneof in enumerate(desc.oneof_decl): 84 | out += wrap_block('oneof', blocks.pop('_oneof_%d' % index), oneof) 85 | 86 | out += fmt_ranges('extensions', desc.extension_range) 87 | out += fmt_ranges('reserved', [*desc.reserved_range, *desc.reserved_name]) 88 | 89 | else: 90 | for service in desc.service: 91 | out2 = '' 92 | for method in service.method: 93 | out2 += 'rpc %s(%s%s) returns (%s%s);\n' % (method.name, 94 | 'stream ' * method.client_streaming, 95 | min_name(method.input_type, scopes), 96 | 'stream ' * method.server_streaming, 97 | min_name(method.output_type, scopes)) 98 | 99 | out += wrap_block('service', out2, service) 100 | 101 | extendees = OrderedDict() 102 | for ext in desc.extension: 103 | extendees.setdefault(ext.extendee, '') 104 | extendees[ext.extendee] += fmt_field(ext, scopes, blocks, syntax, True) 105 | 106 | for name, value in blocks.items(): 107 | out += value[:-1] 108 | 109 | for name, fields in extendees.items(): 110 | out += wrap_block('extend', fields, name=min_name(name, scopes)) 111 | 112 | out = wrap_block('message' * is_msg, out, desc) 113 | return out 114 | 115 | def fmt_value(val, options=None, desc=None, optarr=[]): 116 | if type(val) != str: 117 | if type(val) == bool: 118 | val = str(val).lower() 119 | elif desc and desc.enum_type: 120 | val = desc.enum_type.values_by_number[val].name 121 | val = str(val) 122 | else: 123 | val = '"%s"' % val.encode('unicode_escape').decode('utf8') 124 | 125 | if options: 126 | opts = [*optarr] 127 | for (option, value) in options.ListFields(): 128 | opts.append('%s = %s' % (option.name, fmt_value(value, desc=option))) 129 | if opts: 130 | val += ' [%s]' % ', '.join(opts) 131 | return val 132 | 133 | types = {v: k.split('_')[1].lower() for k, v in FieldDescriptorProto.Type.items()} 134 | labels = {v: k.split('_')[1].lower() for k, v in FieldDescriptorProto.Label.items()} 135 | 136 | def fmt_field(field, scopes, blocks, syntax, extend=False): 137 | type_ = types[field.type] 138 | 139 | default = '' 140 | if field.default_value: 141 | if field.type == field.TYPE_STRING: 142 | default = ['default = %s' % fmt_value(field.default_value)] 143 | elif field.type == field.TYPE_BYTES: 144 | default = ['default = "%s"' % field.default_value] 145 | else: 146 | # Guess whether it ought to be more readable as base 10 or 16, 147 | # based on the presence of repeated digits: 148 | 149 | if ('int' in type_ or 'fixed' in type_) and \ 150 | int(field.default_value) >= 0x10000 and \ 151 | not any(len(list(i)) > 3 for _, i in groupby(str(field.default_value))): 152 | 153 | field.default_value = hex(int(field.default_value)) 154 | 155 | default = ['default = %s' % field.default_value] 156 | 157 | out = '' 158 | if field.type_name: 159 | type_ = min_name(field.type_name, scopes) 160 | short_type = type_.split('.')[-1] 161 | 162 | if short_type in blocks and ((not extend and not field.HasField('oneof_index')) or \ 163 | blocks[short_type].startswith(' map<')): 164 | out += blocks.pop(short_type)[1:] 165 | 166 | if out.startswith('map<'): 167 | line = out + ' %s = %s;\n' % (field.name, fmt_value(field.number, field.options, optarr=default)) 168 | out = '' 169 | elif field.type != field.TYPE_GROUP: 170 | line = '%s %s %s = %s;\n' % (labels[field.label], type_, field.name, fmt_value(field.number, field.options, optarr=default)) 171 | else: 172 | line = '%s group %s = %d ' % (labels[field.label], type_, field.number) 173 | out = out.split(' ', 2)[-1] 174 | 175 | if field.HasField('oneof_index') or (syntax == 'proto3' and line.startswith('optional')): 176 | line = line.split(' ', 1)[-1] 177 | if out: 178 | line = '\n' + line 179 | 180 | if field.HasField('oneof_index'): 181 | blocks.setdefault('_oneof_%d' % field.oneof_index, '') 182 | blocks['_oneof_%d' % field.oneof_index] += line + out 183 | return '' 184 | else: 185 | return line + out 186 | 187 | """ 188 | Find the smallest name to refer to another message from our scopes. 189 | 190 | For this, we take the final part of its name, and expand it until 191 | the path both scopes don't have in common (if any) is specified; and 192 | expand it again if there are multiple outer packages/messages in the 193 | scopes sharing the same name, and that the first part of the obtained 194 | partial name is one of them, leading to ambiguity. 195 | """ 196 | 197 | def min_name(name, scopes): 198 | name, cur_scope = name.split('.'), scopes[0].split('.') 199 | short_name = [name.pop()] 200 | 201 | while name and (cur_scope[:len(name)] != name or \ 202 | any(list_rfind(scope.split('.'), short_name[0]) > len(name) \ 203 | for scope in scopes)): 204 | short_name.insert(0, name.pop()) 205 | 206 | return '.'.join(short_name) 207 | 208 | def wrap_block(type_, value, desc=None, name=None): 209 | out = '' 210 | if type_: 211 | out = '\n%s %s {\n' % (type_, name or desc.name) 212 | 213 | if desc: 214 | for (option, optval) in desc.options.ListFields(): 215 | value = 'option %s = %s;\n' % (option.name, fmt_value(optval, desc=option)) + value 216 | 217 | value = value.replace('\n\n\n', '\n\n') 218 | if type_: 219 | out += '\n'.join(INDENT + line for line in value.strip('\n').split('\n')) 220 | out += '\n}\n\n' 221 | else: 222 | out += value 223 | return out 224 | 225 | def fmt_ranges(name, ranges): 226 | text = [] 227 | for range_ in ranges: 228 | if type(range_) != str and range_.end - 1 > range_.start: 229 | if range_.end < 0x20000000: 230 | text.append('%d to %d' % (range_.start, range_.end - 1)) 231 | else: 232 | text.append('%d to max' % range_.start) 233 | elif type(range_) != str: 234 | text.append(fmt_value(range_.start)) 235 | else: 236 | text.append(fmt_value(range_)) 237 | if text: 238 | return '\n%s %s;\n' % (name, ', '.join(text)) 239 | return '' 240 | 241 | 242 | # Fulfilling a blatant lack of the Python language. 243 | list_rfind = lambda x, i: len(x) - 1 - x[::-1].index(i) if i in x else -1 244 | 245 | def main(): 246 | if len(sys.argv) != 3: 247 | print('Protobuf File Descriptor Set Converter') 248 | print(f'Usage: {sys.argv[0]} ') 249 | sys.exit(1) 250 | 251 | filename = sys.argv[1] 252 | if not os.path.isfile(filename): 253 | print(f'Cannot find {filename}, please check your path.') 254 | sys.exit(1) 255 | 256 | output_folder = sys.argv[2] 257 | 258 | if not os.path.exists(output_folder): 259 | os.makedirs(output_folder) 260 | 261 | fd = FileDescriptorSet() 262 | 263 | with open(filename, 'rb') as f: 264 | fd.ParseFromString(f.read()) 265 | 266 | for i in fd.file: 267 | filename, content = descpb_to_proto(i) 268 | with open(f"{output_folder}/{filename}", 'w') as f: 269 | f.write(content) 270 | 271 | if __name__ == '__main__': 272 | main() -------------------------------------------------------------------------------- /enc_dispatch_decryptor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Copyright (C) 2022 Nobody 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License as 8 | published by the Free Software Foundation, either version 3 of the 9 | License, or (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU Affero General Public License for more details. 15 | 16 | You should have received a copy of the GNU Affero General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import sys 21 | import base64 22 | 23 | import json 24 | import traceback 25 | from xml.etree import ElementTree as etree 26 | 27 | import Crypto 28 | from Crypto.PublicKey import RSA 29 | from Crypto.Cipher import PKCS1_v1_5 30 | from Crypto.Signature import pkcs1_15 31 | from Crypto.Hash import SHA256 32 | 33 | def conv_num(num): 34 | num = base64.b64decode(num) 35 | num = ''.join(['{:02x}'.format(x) for x in num]) 36 | num = int(num, 16) 37 | return num 38 | 39 | def get_params_from_xml(xml, params): 40 | return tuple([conv_num(xml.find(p).text) for p in params]) 41 | 42 | def get_key_from_xml(s, params): 43 | xml = etree.fromstring(s) 44 | params = get_params_from_xml(xml, params) 45 | return RSA.construct( params ) 46 | 47 | def get_pub_key_from_xml(s): 48 | return get_key_from_xml(s, ['Modulus', 'Exponent']) 49 | 50 | def get_priv_key_from_xml(s): 51 | return get_key_from_xml(s, ['Modulus', 'Exponent', 'D', 'P', 'Q']) 52 | 53 | def verify(message, sign, key): 54 | signer = pkcs1_15.new(key) 55 | digest = SHA256.new(message) 56 | signer.verify(digest, sign) # Throws an exception in the case of invalid signature 57 | 58 | def do_sign(message, key): 59 | signer = pkcs1_15.new(key) 60 | digest = SHA256.new(message) 61 | return signer.sign(digest) 62 | 63 | if __name__ == '__main__': 64 | 65 | server_response = json.loads(input("Please enter the server response: ")) 66 | #server_response = json.loads('{"content":"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","sign":"duEsPIG6/J3UGJS6Mc0HFJLDqcdUrixs74igfP5KN3RTGkTHNJWQz0yxIQLdztQRI0mnE1LxMKbQBgUwEs8RmIqMnBelQrIXRXc0vL/Ge0/PQ2zfhFwylsgQ6ulOD79/UfEX0+oxN1HTKH0eHkVgiayplHAy8qMZVRnKBMBuDeqUmK2S4V/dZAsMAtwuwJhalWTJVr2JyboaZNLRhElnInysEE8ZaVNYtXQS1TJdzDQRmh6xhRKHP4n+qaRVb3E6PTtaOGCpSRajf6rP2RJ4rDRDw0U7sIUMG+0IYSyTHDsJzkM6YOcBnyKZMRWRAqDNUNdw0Iztp7FCxQHgEpQt4w=="}') 67 | key_type = input("Please enter the encryption key type (2 for CN, 3 for Global, 5 for Global Beta???, 'sign' for GC Signing Key ): ") 68 | sign_key_type = input("Please enter the sign key type (2 for CN, 3 for Global, 5 for Global Beta???, 'sign' for GC Signing Key): ") 69 | 70 | content = None 71 | sign = None 72 | 73 | keys = { 74 | "1": { 75 | "publicRSAKey": "4dwQDDcitJ97u1yMTCSddaMk1tZEkFBmh5cjfz0PndE+BJp0aayuIPMtkFuG2gPL0GBpIO77BNXTgt/bbvPY/3SgI8wf8lClqSOHErmekbpvD8c/D8mrH6NP1adcC/Jciz7v0ogeqJnw2azAyXRki4vvEpIBsXWq+rt1xCLAGyr5j9j/CpoO2IuUb5XKIN94Jiz+h4nlw0mSFMPCUv/V3EmhmOD/g8ya40XBNDY5g7HkBkHy55kjFnf6e12gxyI88i5vgrtcEFyWsGdtydQezQdgVp5qwGeW5BL9QbIVJ/p8AgnS1gEgwPJOg9WY9AZcHVGghOZ4Vei+RXQajzcnWw==AQAB", 76 | "privateRSAKey": "xAde3Gos++LVlw2vvW5OiziD97fVvA3Y6GLY0xMd4d7LpnHrfwuO7+60IZA9zR6QOFkrMrxNMTf2UbEJsnfbnUyhsQJr8bHR6PfUj6d/YUceM8YWcjpCyZGM2mKuVBXYqWO8gU81bwQOKgHSytvIrXtsk+3+g+pfkSKZOakiPzosSXCtGSPrVRJlumCmiXjTiKiwKqR+BBk8tZSuINl+r/aTfgu5xkfcB6YOYIIsNfkhpyZTcO0TNk7A3gRhikySyfxZX/T7lBDqUUKMMoCPy8MPGHvc66YyW1xsf02zdkAHXDzchj6TrzApvzrUzEcl9rwxzCEtGWh5wjIV1a/sJQ==AQAB

/HcDx3kyHFcCtOXefbE10IoNxJm7gwbkELGZzvTKsLj4xReEyizzW/JS3P2zgdFwmWqgj7a2vQmm1VoRaM375UUphVbiL6df5hNuqd1DW2Bepdca4tODp996ZC3CYqg6Y1+rkVcy4LPJIbT5gyn6VG6EdpMINPma+EWeCmRSNZ8=

xsYOFP4pxu8kAGfU/n95LZycN5ttwFQRUTRRMXyNaysY1B0+PqhXZemN+OU9HnJtElt4kjy4N09RQB7KvhM07Ccm/IztPWzGSbzOYshSjq+w1dBP8djJoUVMp+1gAD1XcAHft61IAsofiDlRUbqV9m4mWnTzA+UarzjO/f+Ln7s=LFvQ+yxtRJN5M0WsWRNY7EJFdwS38Ka2TcSWzMkwD+sAMskWGNvbCo3CR3gAIVAmY55bhcTJyN84RAZmRq7ikn8bc4U3ir3y2J8Tc58f5Z9CIgtweuhFGqrme1Ga9PCwCaPWplvW4apVLan5qTUn+cvNVHQzHfO5aeP5h8Pmues=M3FUeahhpYOEfLpijE7vTJeocle+arUXGj4A+V6zttWbgmHjFxI4ND7iqFSjobqZcdPMe4RNZLsZWw/dBp4v5yIm29uZFnmNQ84iV7xiaV0c1ekol320iRFHSnqLiuRVOb6yaHXnGhm3WWkEG7O/Vdyh/m0f65Uid9Cq+V9mgjs=gfQo6fET7ixHs/CEm7Kmmq/7NZel1WzwF++13lbpCY1yl390jeEWrQYvzgC0pHfU8XCK6n0PEybQ4nM/AiQt+/f4YX8fZ8Tf2UsgH+tKmeCkXO6hLU0yLRsaXWAs+6QFF0e+A+cNU+jm+U2CMjItlnn3cw8fiKNMmalIxG6rnS8=sx0w9iEl3TnUg+MDkwj5R4hqPKyC5QOaECxRhfvPsBSM7BbnYPxErMLlYE2VypiEmpgPOpqHxdMmhFpJTALsbfwZOc3Gp98ct8vLMz+OymnbFN+InvLRF7CgjOLh+v7DK3NwSI8BeeCwuH1WB/lukeVWvowVpJ9AlzKP4vnByxZWFfGEnG5Tvp5oY8SODvbMvG+oK0NfxDB4vIqDovuMNFWSw+dhlbEOerQH++RDhQLGYM5XTB0zQnbwwMtTXjV4J4jeqhb+766gn0dQ1oeIy8s3i3ZOnOM2pKM0ohJZ2Pjzkbsz1rvhgAWeqmDA6Q5RgK4kRtWPJRtDuJySvk+CeQ==
" 77 | }, 78 | "2": { 79 | "publicRSAKey": "wt/Z98o8gbw94la07B1/ApVCuHWHGI7Pd8FPF3PvNYf1oTYwgRczQBfPqHfXyttRRP44mqG4tfrz2zO8gXENRSyDXtzu7dQGh3hu1t87TpPbiYcQ+ZHK58v6dy1jo30TTK64sRnjxJfWrKYDxSBxBzDbKClzqlY0J/4mVjKFxk7qS0HvoYydlRnhvJVOMdjt/SV6wyHRY66FvOvdk6BVLom3K0WBHNcFE6ChA3GQcR+xyX1Z058AviFrx6KS45mqRujUC5vZXuwbvgrICgEVlfOScHFnrTlFX8ysM4C1bSb8Icy3V8XSb7LjCmXBeB7TUpW2vjhKlzgZeWwNu1DaEw==AQAB", 80 | "privateRSAKey": "z/fyfozlDIDWG9e3Lb29+7j3c66wvUJBaBWP10rB9HTE6prjfcGMqC9imr6zAdD9q+Gr1j7egvqgi3Da+VBAMFH92/5wD5PsD7dX8Z2f4o65Vk2nVOY8Dl75Z/uRhg0Euwnfrved69z9LG6utmlyv6YUPAflXh/JFw7Dq6c4EGeR+KejFTwmVhEdzPGHjXhFmsVt9HdXRYSf4NxHPzOwj8tiSaOQA0jC4E4mM7rvGSH5GX6hma+7pJnl/5+rEVM0mSQvm0m1XefmuFy040bEZ/6O7ZenOGBsvvwuG3TT4FNDNzW8Dw9ExH1l6NoRGaVkDdtrl/nFu5+a09Pm/E0Elw==AQAB

9hdURxe6DnOqSpe6nh2nVLTmxrPXNY+FSFCb4KtuGB5OqmOeAkkQHv2oysabKSLQ/9wa1tNysd/z6LuAOUgZbQ4xvj+Ofh/kAJUPTSLK+QdIY+fQCKYyg04xuQai3tKRKedzDFd1rDAPJO7Z2h9e4Gvvb4ZiqBEAbnYi4DQLSlE=

2Fen9TJb+G0Hbt+spyH+tMpAqbXaQFXbQCSRFRBSJuKJDJa55Yqz7ltVpblHmgMiFbGp+0m2cQVZS9ZpMekewH9umNLcInpaSeo1ulrdAhJylXW7DxX4S3P8rb9+2PJnMWiepz4m53nfrjEV0iU6xGP2BmcrzdZy6LoQXEB6vmc=nNPPNKMtQep6OqEpH3ycV4IVk8mmO47kDGq6e9okBiDCVxm255PyPx2+BMO+u99hO7zkKcWE0VB8WvOqylZlRbeHAcv1HfFq1ugnYSvsF/mJK4nebLSlekJJs7VD9CZStla2XcYayomyDQJeOQBG8VQ3uWX1109GbB7DKQhhrZE=cmKuWFNfE1O6WWIELH4p6LcDR3fyRI/gk+KBnyx48zxVkAVllrsmdYFvIGd9Ny4u6F9+a3HG960HULS1/ACxFMCL3lumrsgYUvp1m+mM7xqH4QRVeh14oZRa5hbY36YS76nMMMsI0Ny8aqJjUjADCXF81FfabkPTj79JBS3GeEM=F5hSE9O/UKJB4ya1s/1GqBFG6zZFwf/LyogOGRkszLZd41D0HV61X3tKH3ioqgkLOH+EtHWBIwr+/ziIo1eS9uJo/2dUOKvvkuTpLCizzwHd4F+AGG0XID0uK1CpdaA5P3mDdAjWAvw2FfbAL+uZV/G9+R2Ib1yElWLcMELv/mI=rR9ewnJPiiUGF49vcahuKspDVA2sGyC4igjJARO+ed1qv1HI5rrkeG1ZzC/LnEt5oEfwYB1d5fL1Cp8b6kcf6BmZFjWs24rsC/k4QG5S1qqxJmLmVQqEHAJ75E/LSKg1s+34QxLmZ55DM2XAEyGc4GVEmuSHz97t6z/jK1W8mgncyRHiNGK79V0/jOXXZCkK2IKguZEYmIvy4zXCyYaklbfKd+wnScdTxhxYyim+DGaQDZTUYHk7VqRlX0tDyS82oiNTcj0ib+8VmYFYWyvfsEMakhuipmeL6RL0SNcyoqL+QbABTfhn7g+ZqZ9V6PQqc034/7Dtd1aRx/jLfNPsgQ==
" 81 | }, 82 | "3": { 83 | "publicRSAKey": "yYlF2xKeHRDZUSsXlLoSk/YAb2oIOwpbUeO4I+5GfWoybSpde4UnOlZgpKIDgltF3e9ST8bqIwuBxMoJTpOAnEKLNuBDdSeefHwhFqdczgeETxySwFKScmti1QRwgacrlgWglmaYCaeQrqbBceF9JbF4npi6S3+eFpw0j4rPjlE3vjh1AopaZQWAHGZI8Ixr7LDebe/uF8i7OCWXpkPKUTJnCEpyqM5H+pLN3MWRiL7mBR4XFqwKQr8J27Y3LN1iX9927hMsvAnh9PWoHzqpDTqIBF7w1ifYs3XQ3EMbf0zqc26UZXUaI5pD6qXNm3STz94SrfYqYY1R3Npz/Syaww==AQAB", 84 | "privateRSAKey": "02M1I1V/YvxANOvLFX8R7D8At40IlT7HDWpAW3t+tAgQ7sqjCeYOxiXqOaaw2kJhM3HT5nZll48UmykVq45Q05J57nhdSsGXLJshtLcTg9liMEoW61BjVZi9EPPRSnE05tBJc57iqZw+aEcaSU0awfzBc8IkRd6+pJ5iIgEVfuTluanizhHWvRli3EkAF4VNhaTfP3EkYfr4NE899aUeScbbdLFI6u1XQudlJCPTxaISx5ZcwM+nP3v242ABcjgUcfCbz0AY547WazK4bWP3qicyxo4MoLOoe9WBq6EuG4CuZQrzKnq8ltSxud/6chdg8Mqp/IasEQ2TpvY78tEXDQ==AQAB

9ci4i5gUVSGo3PkIpTTXiNm7pCXTPlkDTxXzhxMlx8sgrh7auoLwMVOV0DQW1V84a3RXTwf/HalEKEY69TAYbmef0OqqHoGMHJStbjPaGdfNPdm5IOHp5qmIIHWOX2Z4nSyeEXY+z+GpYYvZvdKQIJ73SpVPM5U54s7phQIg6r0=

3Cx9CQCr/THDyd5EY1OudeKa9tL5Vc8gXfzCJ2WO5s03sNjlwgVNAmudMFYpu7P+31onxBfpMUvRyL/2+E8mhOF8vXa8vaRYZiBaRZE+apoFbfLPsezmu37G4ff/sDnDm+aQSDU1kmCewnSsxRO7VDo8zkIGDo6nIdjhOEFvypE=ML8ciuMgtTm1yg3CPzHZxZSZeJbf7K+uzlKmOBX+GkAZPS91ZiRuCvpu7hpGpQ77m6Q5ZL1LRdC6adpz+wkM72ix87d3AhHjfg+mzgKOsS1x0WCLLRBhWZQqIXXvRNCH/3RH7WKsVoKFG4mnJ9TJLQ8aMLqoOKzSDD/JZM3lRWk=PIZ+WNs2bIQhrnzLkAKRExcYQoH8yPoHi87QEMR6ZDhF9vepMY0DfobWz1LgZhk1F3FRPTbVhBezs9wRqHEZxa22/N6HRBrJsklyh21GG0f79h2put/FDgXr5nKmd2tpupHHWBJIh9THz+0DEao69QyNaqX7xESy7TsRrsVOVgE=mlWr8mOkpY92UUO4ipPXx5IHv2xZfs4QDcUX1lTmDAvJg9oBw7KvQiHQqdTINLSaVi2hoMgzNZIAoWWLH3+I0cRwuHM7wLaD0pcVlxdpy99aid75Nmc83GuBkhwCJ6HVwayrLWr+UiCqLFik9mMrMYB5QPUptn+J9PRoxW7JRB4=uLj7GJOALEnu+dALug8+5EnyIHQ4SeOAIrL05ny2rjBWS7X8X4wQ4QsE8bg+15wmQMR5ve08vgKkqSpv62kELL7VmpTIQamGp84w2DEb9p4idbxo5t1q0MQWhBfsjrb62bCuX0E7JaiJyKpJyEB+34I2sye2dvA9fLGDY9+6nxVkkspoBaPkqEvwShK9tNJaUQP6Ghl4h3MiDoyYnT+m+1BnrO7oTF1Ly636M5grEqrJcVzuVJOVzf31peC8Qhl+5qTXz2SE+WAUox5YhZDZcSI8iYPDkSxovNjNnLssad/a/dxermgoy7W/q3cJRrq+56YF1JCn1kCX/VhO7mq+gQ==
" 85 | }, 86 | "4": { 87 | "publicRSAKey": "lCwdYrveozYYcKOSz4cjBfORvd6POZSxsM9JybWvTb9rr1qGhulgoNcMB0sUA4XnfNlt/aaT+JKSTEgynyX8of74Nmu70MRO2Nemi0YnI56gK2f0tIdmpFKnojgDTlLslQnKBzcK/elbcX2XE3FMK/hA2rkJBIMkIsXJ23nfWy/6KFB/nhXft+wzDahYmzaoLKsgq4xQInB6n0dUSkFNSMV+98CRjh+Y7pXlyEglDXxj+IhBVsl8s41c9vmgLHWS7feMufbeqko83fLv2GlI/aU0pvmYr9Lyf4kgPMp5aTqeyCm/ztb3bp5QoW7S2hlGP6gtxGr4s/lMpZN5YgTZbQ==AQAB", 88 | "privateRSAKey": "yaxqjPJP5+Innfv5IdfQqY/ftS++lnDRe3EczNkIjESWXhHSOljEw9b9C+/BtF+fO9QZL7Z742y06eIdvsMPQKdGflB26+9OZ8AF4SpXDn3aVWGr8+9qpB7BELRZI/Ph2FlFL4cobCzMHunncW8zTfMId48+fgHkAzCjRl5rC6XT0Yge6+eKpXmF+hr0vGYWiTzqPzTABl44WZo3rw0yurZTzkrmRE4kR2VzkjY/rBnQAbFKKFUKsUozjCXvSag4l461wDkhmmyivpNkK5cAxuDbsmC39iqagMt9438fajLVvYOvpVs9ci5tiLcbBtfB4Rf/QVAkqtTm86Z0O3e7Dw==AQAB

/auFx84D7UlrfuFQcp5t+n2sex7Hj6kbK3cp27tZ2o6fix7GbJoG6IdBxRyE8NWVr+u5BnbT7wseDMEOjSbyxjuCl/vXlRX01JUhEPTC7bpIpGSU4XMngcE7BT2EEYtKdFQnPK9WW3k7sT2EC/rVIKu9YERyjDZico1AvC+MxUk=

y4ahJvcD+6Wq2nbOnFUByVh79tIi1llM5RY/pVviE6IfEgnSfUf1qnqCs5iQn9ifiCDJjMqb+egXXBc/tGP/E5qGe8yTOEZ2Y5pu8T0sfkfBBNbEEFZORnOAFti1uD4nkxNwqolrJyFJGMmP7Ff533Su2VK79zbtyGVJEoAddZc=FTcIHDq9l1XBmL3tRXi8h+uExlM/q2MgM5VmucrEbAPrke4D+Ec1drMBLCQDdkTWnPzg34qGlQJgA/8NYX61ZSDK/j0AvaY1cKX8OvfNaaZftuf2j5ha4H4xmnGXnwQAORRkp62eUk4kUOFtLrdOpcnXL7rpvZI6z4vCszpi0ok=p3lZEl8g/+oK9UneKfYpSi1tlGTGFevVwozUQpWhKta1CnraogycsnOtKWvZVi9C1xljwF7YioPY9QaMfTvroY3+K9DjM+OHd96UfB4Chsc0pW60V10te/t+403f+oPqvLO6ehop+kEBjUwPCkQ6cQ3q8xmJYpvofoYZ4wdZNnE=cBvFa7+2fpF/WbodRb3EaGOe22C1NHFlvdkgNzb4vKWTiBGix60Mmab72iyInEdZvfirDgJoou67tMy+yrKxlvuZooELGg4uIM2oSkKWnf0ezCyovy+d62JqNGmSgESx1vNhm6JkNM8XUaKPb2qnxjaV5Mcsrd5Nxhg7p5q7JGM=spmttur01t+SxDec11rgIPoYXMZOm76H1jFDFyrxhf9Lxz0zF5b7kpA3gzWuLwYr53kbYQTTzIG96g7k1sa6IEDDjiPGXYWNwxXsXw73EA9mpwybkqkpoPTXd+qvssZN8SKFweSJaNt3Xb05yVx4bATaL7+80Sztd+HABxag6Cs7eRBB63tLJFHJ+h4xznpOnOd476Sq+S0q64sMeYDLmP+2UiFA6PVhmO9Km0BRmOmzpV/cfLjY3BRfu0s7RFUPr4Sf/uxL8Kmia8rMHqNJfdUyjPVmjLsKLnCnnHlVrspxMOhhk8PFEy7ZbXpCxnum0vGMWPH1cJypE0cCWMACUQ==
" 89 | }, 90 | "5": { 91 | "publicRSAKey": "15RBm/vARY0axYksImhsTicpv09OYfS4+wCvmE7psOvZhW2URZ2Rlf5DsEtuRG/7v5W/2obqqVkf+1dorPcR2iqrYZ4VVPf7KU3Cgqh0kzLGxWOpGxzwJULEyFVaiMDWbk7gr8rik/jYyhLiLc52zz3E3whTUPleKhOhXnxx1iOKY+TPVI8jJfDNiQoh0UvgjnkigJ/saPzjogeig/4McBc4l5cDkvttkKQKq7oXe9OCBClgKlYjcc1CNalwMlTz7NvLEko+ZLTgpA+kElZumyBXT67mmW7t7IDXorscAI7auwusKWmq797alFkQ/6sUqs8KKGnqQ2fwHfa/RYDhEw==AQAB", 92 | "privateRSAKey": "sJbFp3WcsiojjdQtVnTuvtawL2m4XxK93F6lCnFwcZqUP39txFGGlrogHMqreyawIUN7E5shtwGzigzjW8Ly5CryBJpXP3ehNTqJS7emb+9LlC19Oxa1eQuUQnatgcsd16DPH7kJ5JzN3vXnhvUyk4Qficdmm0uk7FRaNYFi7EJs4xyqFTrp3rDZ0dzBHumlIeK1om7FNt6Nyivgp+UybO7kl0NLFEeSlV4S+7ofitWQsO5xYqKAzSzz+KIRQcxJidGBlZ1JN/g5DPDpx/ztvOWYUlM7TYk6xN3focZpU0kBzAw/rn94yW9z8jpXfzk+MvWzVL/HAcPy4ySwkay0Nw==AQAB

19wQUISXtpnmCrEZfbyZ6IwOy8ZCVaVUtbTjVa8UyfNglzzJG3yzcXU3X35v5/HNCHaXbG2qcbQLThnHBA+obW3RDo+Q49V84Zh1fUNH0ONHHuC09kB//gHqzn/4nLf1aJ2O0NrMyrZNsZ0ZKUKQuVCqWjBOmTNUitcc8RpXZ8s=

0W09POM/It7RoVGI+cfbbgSRmzFo9kzSp5lP7iZ81bnvUMabu2nv3OeGc3Pmdh1ZJFRw6iDM6VVbG0uz8g+f8+JT32XdqM7MJAmgfcYfTVBMiVnh330WNkeRrGWqQzB2f2Wr+0vJjU8CAAcOWDh0oNguJ1l1TSyKxqdL8FsA38U=udt1AJ7psgOYmqQZ+rUlH6FYLAQsoWmVIk75XpE9KRUwmYdw8QXRy2LNpp9K4z7C9wKFJorWMsh+42Q2gzyoHHBtjEf4zPLIb8XBg3UmpKjMV73Kkiy/B4nHDr4I5YdO+iCPEy0RH4kQJFnLjEcQLT9TLgxh4G7d4B2PgdjYYTk=rdgiV2LETCvulBzcuYufqOn9/He9i4cl7p4jbathQQFBmSnkqGQ+Cn/eagQxsKaYEsJNoOxtbNu/7x6eVzeFLawYt38Vy0UuzFN5eC54WXNotTN5fk2VnKU4VYVnGrMmCobZhpbYzoZhQKiazby/g60wUtW9u7xXzqOdM/428Yk=cGxDsdUW6B/B/nz9QgIhfnKrauCa8/SEVjzoHA6bdlLJNaw8Hlq2cW00ZcCGlXOXLCBBNl9Nn7rf00169TKFx2urNnEK52WKuOOPPDbDuEwAtuoarP8fx21TnF9d4E9ukmJ4ABx3oe8Y1ia/yoCCML3L4L6FbOpbu2vGi1L6zmo=PMpalrBtVgQdoziUtvugKMA9fMT3PHt2MsO+Kx8sJ1+gg0952Sh7na3LWj4G1GlYHstdNj2kWJzUUsTnC/LLrPJ/yEfdmzKyo2FYXGGHgWcubH9QaiQCKv5qdormZhUnW9C3HOOVXUcBtCyRHKuSUqgcN1EWqIVc7CKJv3ugM1aEP5HF/IbDAmfKdllJd0tstKLP9AdA2v/5R+QpEFrG3QJ9TuY4tnGjLp80DEd0FwEk8cLKH5oO8RuLHudKdxJTwm7/jxgnwOuCVtmxcJigDlTPw0wO5oQyCg1YIVBWgRxGQRShofsGVZ3dRQVE+cNnUHgGaStWhETxrnzc6pLBqQ==
", 93 | }, 94 | "sign": { 95 | "publicRSAKey": "xbbx2m1feHyrQ7jP+8mtDF/pyYLrJWKWAdEv3wZrOtjOZzeLGPzsmkcgncgoRhX4dT+1itSMR9j9m0/OwsH2UoF6U32LxCOQWQD1AMgIZjAkJeJvFTrtn8fMQ1701CkbaLTVIjRMlTw8kNXvNA/A9UatoiDmi4TFG6mrxTKZpIcTInvPEpkK2A7Qsp1E4skFK8jmysy7uRhMaYHtPTsBvxP0zn3lhKB3W+HTqpneewXWHjCDfL7Nbby91jbz5EKPZXWLuhXIvR1Cu4tiruorwXJxmXaP1HQZonytECNU/UOzP6GNLdq0eFDE4b04Wjp396551G99YiFP2nqHVJ5OMQ==AQAB", 96 | "privateRSAKey": "xbbx2m1feHyrQ7jP+8mtDF/pyYLrJWKWAdEv3wZrOtjOZzeLGPzsmkcgncgoRhX4dT+1itSMR9j9m0/OwsH2UoF6U32LxCOQWQD1AMgIZjAkJeJvFTrtn8fMQ1701CkbaLTVIjRMlTw8kNXvNA/A9UatoiDmi4TFG6mrxTKZpIcTInvPEpkK2A7Qsp1E4skFK8jmysy7uRhMaYHtPTsBvxP0zn3lhKB3W+HTqpneewXWHjCDfL7Nbby91jbz5EKPZXWLuhXIvR1Cu4tiruorwXJxmXaP1HQZonytECNU/UOzP6GNLdq0eFDE4b04Wjp396551G99YiFP2nqHVJ5OMQ==AQAB

8tHjikSSZN18ggXxm3MGJV8Nnb1tP3onQJZcZXOnzHptK7knmOWzuw/wMRyMZnq8ewsY6+Rw3HNydHeX/kc7PpMi69W5SbfpvWMeW2rXFlK2MZ4pmzWKGElK7aUgD5OsrwUJGcoBEnS6CFcY1kUi2B4zbfRKCOnZEvghJcnvbhc=

0HJLZHA2lRi+QJJkdIWdAz+OrWOV3HD7SniMAalYuKURoD/zFZSdmucKs8UX+32WWlt1NH90Ijye0gwDLZ0fghQfJgpRqHIdLMIBQ0qlLSzjfeSfmHL20a+fuPK44nh2T0WjU8hkzup/OaR0IFtfc0XZManM69tgYkccLeyxWvc=0ckOik32INjOklNqS0BURgNaczbOZTI3KXD+wNPsXBhFq6nbERkbb/k0LmoYzw0pPDD5Rgxmib/gWcldct29zLE4UYKkA5G2it5QwvCKhYnOSQ35qlPWTGc+KhUonuyaG9gA5dwFkxlwBHajSbQPh6KIEm4lbJAE8IOZt9lAV98=qlyvh7A6vBLT87xyA9XsJOp+NvIMWnWwvAXYD8eTrp2i0UFS8FFdmmu4kILGfhH/n2veWADPLugyueN9eXtQdCTz7EhEwxI5DAqns5K/ezOT3qHLWnKjjW8ncKZYOyhPMazttx0yXvbC8p6ZFpT3ZyQwRmnMBPxwQwJxYotvzLM=MibG8IyHSo7CJz82+7UHm98jNOlg6s73CEjp0W/+FL45Ka7MF/lpxtR3eSmxltvwvjQoti3V4Qboqtc2IPCt+EtapTM7Wo41wlLCWCNx4u25pZPH/c8g1yQ+OvH+xOYG+SeO98Phw/8d3IRfR83aqisQHv5upo2Rozzo0Kh3OsE=xHmGYY8qvmr1LnkrhYTmiFOP2YZV8nLDqs6cCb8xM+tbQUr62TwOS0m/acwL6YnPu4Qx/eI1/PfvHTXzu6pQA7FTRECQcbr9qNTAo6QkZJgWc+dOiARlOtCrdY+ZMHQhHq4E1tat++c+MJfH+y5ki9lOlrynHaI01caIQZCFCe7IbZprpA4tmJzH3uk/9iblwwy/K7yHJ36+RDAoD0LPsS3ixBqyCXaVMtYiGGWK8766ScH/RCS9w9Hu45KW7wEGfBBfWIRIsyYTpnc06luD4FtslGh2Hd6uUI4iC8uwAvqDmKE2ZZ90X4zzsZfm2I3jDlpapILaT0JABOCOuMPEWQ==
" 97 | } 98 | } 99 | 100 | public_key_xml = keys[sign_key_type]['publicRSAKey'] 101 | private_key_xml = keys[key_type]['privateRSAKey'] 102 | 103 | pub_rsa_key = get_pub_key_from_xml(public_key_xml) 104 | priv_rsa_key = get_priv_key_from_xml(private_key_xml) 105 | 106 | try: 107 | content = base64.b64decode(server_response['content']) 108 | sign = base64.b64decode(server_response['sign']) 109 | 110 | except Exception: 111 | print(f'\nAn error occured while parsing the input data: \n\n{traceback.format_exc()}') 112 | 113 | if content: 114 | dec = PKCS1_v1_5.new(priv_rsa_key) 115 | 116 | chunk_size = 256 117 | 118 | out = b'' 119 | 120 | for i in range(0, len(content), chunk_size): 121 | chunk = content[i:i + chunk_size] 122 | out += dec.decrypt(chunk, None) 123 | 124 | 125 | output_filename = input("Enter the output file name (leave it blank if you don't want to save to a file): ") 126 | 127 | if output_filename: 128 | with open(output_filename, 'wb') as out_fp: 129 | out_fp.write(out) 130 | 131 | #calc_sign = do_sign(out, signing_priv_key)# 132 | #print("\nReceived sign: {}\n".format(base64.b64encode(sign).decode())) 133 | #print("Calculated sign: {}\n".format(base64.b64encode(calc_sign).decode())) 134 | 135 | print("\nContent: \n\n{}\n".format(base64.b64encode(out).decode())) 136 | 137 | if input("Do you want to skip the signature check (y or n): ") == "n": 138 | verify(out, sign, pub_rsa_key) 139 | -------------------------------------------------------------------------------- /map_merger.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PIL import Image 3 | 4 | def main(): 5 | list_of_images = [] 6 | for i in os.listdir("./"): 7 | if i.startswith("UI_MapBack"): 8 | list_of_images.append(i) 9 | 10 | images = [] 11 | for i in list_of_images: 12 | img = Image.open(i) 13 | if img.width != 2048 and img.height != 2048: 14 | img = img.resize((2048, 2048)) 15 | x, y = int(i.split("_")[2]), int(i.split("_")[3].split(".")[0]) 16 | images.append((x, y, img)) 17 | 18 | s = sorted(images, key = lambda x: (x[0], x[1])) 19 | s.reverse() 20 | 21 | min_x = max(s, key = lambda x: x[1])[1] + (-min(s, key = lambda x: x[1])[1]) + 1 22 | min_y = max(s, key = lambda x: x[0])[0] + (-min(s, key = lambda x: x[0])[0]) + 1 23 | 24 | final_image = Image.new("RGB", (2048 * min_x, 2048 * min_y)) 25 | 26 | b = max(s, key = lambda x: x[1])[1] 27 | 28 | x_offset = 0 29 | current_x = 0 30 | 31 | pos_x = 0 32 | pos_y = -2048 33 | 34 | for x, y, img in s: 35 | if current_x == x: 36 | final_image.paste(img, (x_offset + pos_x, pos_y)) 37 | pos_x = pos_x + 2048 38 | else: 39 | current_x = x 40 | pos_x = 0 41 | pos_y = pos_y + 2048 42 | x_offset = 0 43 | 44 | if b > y and x_offset == 0: 45 | diff = b - y 46 | x_offset = diff * 2048 47 | 48 | final_image.paste(img, (x_offset + pos_x, pos_y)) 49 | pos_x = pos_x + 2048 50 | 51 | final_image.save("final_image_2.png") 52 | 53 | if __name__ == "__main__": 54 | main() 55 | -------------------------------------------------------------------------------- /ooooooooooooooo.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | import re 3 | 4 | r = re.compile(rb'(.*?)', re.DOTALL) 5 | 6 | def main(metadata_path: str) -> None: 7 | f = open(metadata_path, 'rb+') 8 | data = f.read() 9 | matches = [i for i in r.finditer(data)] 10 | 11 | if len(matches) < 1: 12 | print('No RSA key found') 13 | sys.exit(1) 14 | 15 | print(f'Found {len(matches)} RSA key(s)') 16 | for i, match in enumerate(matches): 17 | key = match.group().decode().replace("\n", "").replace("\r", "").replace(" ", "") 18 | print(f'\n{i} - RSAKey at {match.start()}: {key}') 19 | 20 | #Let the user select the key that they want to replace 21 | key_to_replace = input(f'Which key do you want to replace? (0-{len(matches)-1}) ') 22 | 23 | k = matches[int(key_to_replace)] 24 | k_str = k.group().decode() 25 | k_max_len = len(k_str) 26 | 27 | print(f'Replacing: {k_str}') 28 | replacement = b'' 29 | 30 | if "\r\n" in matches[int(key_to_replace)].group().decode(): 31 | print('Selected key is a multiline key. Replacement key needs to be read from a file.') 32 | answer = input('Do you want to save the key for editing? (y/n): ') 33 | if answer == 'y': 34 | with open("key.txt", "wb") as ky: 35 | ky.write(k.group()) 36 | print('Key saved to key.txt') 37 | 38 | rep_key_name = input('Please enter the path for the replacement key (syntax of the key needs to be same): ') 39 | with open(rep_key_name, 'rb') as rep_key_file: 40 | replacement = rep_key_file.read() 41 | else: 42 | #Let the user enter the replacement key 43 | new_key = input(f'Enter the new key (must be {k_max_len} characters!): ') 44 | replacement = new_key.encode() 45 | 46 | if (len(replacement) != k_max_len): 47 | print(f'Key must be {k_max_len} characters long!') 48 | sys.exit(1) 49 | 50 | #Replace the key 51 | f.seek(k.start()) 52 | f.write(replacement) 53 | f.close() 54 | 55 | print('Key replaced! Please recrypt the metadata before using it.') 56 | 57 | if __name__ == '__main__': 58 | 59 | if len(sys.argv) != 2: 60 | print('Genshin RSA Key Extractor & Modifier') 61 | print('Usage: {} '.format(sys.argv[0])) 62 | sys.exit(1) 63 | 64 | metadata = sys.argv[1] 65 | if os.path.isfile(metadata): 66 | main(metadata) 67 | else: 68 | print(f'Cannot find {metadata}, please check your path.') 69 | sys.exit(1) 70 | -------------------------------------------------------------------------------- /protogenerator.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | import json 3 | 4 | type_dict = { 5 | "int": "int32", 6 | "uint": "uint32", 7 | "long": "int64", 8 | "ulong": "uint64", 9 | "bool": "bool", 10 | "string": "string", 11 | "byte[]": "bytes", 12 | "double": "double", 13 | "float": "float" 14 | } 15 | 16 | def format_type(type: dict, skip_dot_split: bool = False): 17 | code = "" 18 | if "is_optional" in type and type["is_optional"] == True: 19 | code += "optional " 20 | elif "List<" not in type["type"]: 21 | code += "required " 22 | 23 | if type["type"] in type_dict: 24 | code += type_dict[type["type"]] 25 | else: 26 | if "List<" in type["type"]: 27 | code += "repeated " 28 | typename = type["type"].split("<")[1].split(">")[0] 29 | 30 | if not skip_dot_split: 31 | if "." in typename: 32 | typename = typename.split(".")[-1] 33 | 34 | if typename in type_dict: 35 | code += type_dict[typename] 36 | else: 37 | code += typename 38 | 39 | elif "." in type["type"] and not skip_dot_split: 40 | code += type["type"].split(".")[1] 41 | else: 42 | code += type["type"] 43 | 44 | return code 45 | 46 | def resolve_generic_type(type: str): 47 | if "<" in type: 48 | return type.split("<")[1].split(">")[0] 49 | else: 50 | return type 51 | 52 | def insert_str(string, str_to_insert, index): 53 | return string[:index] + str_to_insert + string[index:] 54 | 55 | def try_get_dict(obj, key): 56 | if key in obj: 57 | return obj[key] 58 | else: 59 | return {} 60 | 61 | if __name__ == "__main__": 62 | if len(sys.argv) != 3: 63 | print("Usage: python protogenerator.py ") 64 | sys.exit(1) 65 | 66 | if not os.path.exists(sys.argv[1]): 67 | print("protos.json does not exist") 68 | sys.exit(1) 69 | 70 | if not os.path.exists(sys.argv[2]): 71 | os.makedirs(sys.argv[2]) 72 | 73 | with open(sys.argv[1], "r") as f: 74 | protos: dict = json.load(f) 75 | 76 | for name, proto in protos.items(): 77 | code = "syntax = \"proto2\";\n\n" 78 | 79 | if proto["type"] == "message": 80 | code += f"message {name} {{\n" 81 | 82 | if "fields" in proto: 83 | for field in proto["fields"]: 84 | ball = False 85 | 86 | if resolve_generic_type(field["type"]) not in type_dict: 87 | if resolve_generic_type(field["type"]) not in try_get_dict(proto, "enums") and resolve_generic_type(field["type"]) not in try_get_dict(proto, "messages"): 88 | import_line = f"import \"{resolve_generic_type(field['type']).split('.')[0]}.proto\";\n" 89 | 90 | if import_line not in code: 91 | code = insert_str(code, import_line, len("syntax = \"proto3\";\n\n")) 92 | 93 | ball = True 94 | 95 | field_type = format_type(field, ball) 96 | code += f' {field_type} {field["name"]} = {field["field_number"]};\n' 97 | 98 | if "enums" in proto: 99 | code += "\n" 100 | for enum_name, enum in proto["enums"].items(): 101 | enum_name_2 = enum_name.split(".")[1] 102 | code += f' enum {enum_name_2} {{\n' 103 | 104 | #if enum_name_2 == "Retcode" or enum_name_2 == "CmdId": 105 | #code += f' option allow_alias = true;\n' 106 | 107 | for value in enum["values"]: 108 | code += f' {value["name"]} = {value["value"]};\n' 109 | code += " }\n" 110 | 111 | if "messages" in proto: 112 | for message_name, message in proto["messages"].items(): 113 | code += f' message {message_name.split(".")[1]} {{\n' 114 | 115 | for field in message["fields"]: 116 | ball = False 117 | 118 | if resolve_generic_type(field["type"]) not in type_dict: 119 | if resolve_generic_type(field["type"]) not in try_get_dict(proto, "enums") and resolve_generic_type(field["type"]) not in try_get_dict(proto, "messages"): 120 | import_line = f"import \"{resolve_generic_type(field['type']).split('.')[0]}.proto\";\n" 121 | 122 | if import_line not in code: 123 | code = insert_str(code, import_line, len("syntax = \"proto3\";\n\n")) 124 | 125 | ball = True 126 | 127 | field_type = format_type(field, ball) 128 | code += f' {field_type} {field["name"]} = {field["field_number"]};\n' 129 | 130 | code += " }\n" 131 | 132 | code += "}" 133 | 134 | elif proto["type"] == "enum": 135 | code += f"enum {name} {{\n" 136 | for value in proto["values"]: 137 | code += f' {value["name"]} = {value["value"]};\n' 138 | code += "}" 139 | 140 | with open(os.path.join(sys.argv[2], f"{name}.proto"), "w") as f: 141 | f.write(code) -------------------------------------------------------------------------------- /typedumper.py: -------------------------------------------------------------------------------- 1 | import os, io 2 | import sys 3 | import re 4 | import json 5 | 6 | ua_file_handle: io.BufferedReader = None 7 | 8 | def get_field_number(ua_path: str, cag_offset: int) -> int: 9 | ua_file_handle.seek(cag_offset) 10 | 11 | search_window = f.read(100) 12 | 13 | # lea edx, [r8+?] 14 | # or "41 8D 50 ?" 15 | 16 | pattern = b'\x41\x8D\x50' 17 | 18 | match = re.search(pattern, search_window) 19 | 20 | if match is None: 21 | return -1 22 | 23 | offset = match.start() 24 | 25 | # Get the field number 26 | field_number = search_window[offset + 3] 27 | 28 | return field_number 29 | 30 | 31 | def dump_message_class(class_contents: str, protos: dict, base_type: str = "") -> None: 32 | class_name = class_contents.split("public class ")[1].split(" :")[0] 33 | 34 | property_start_offset = class_contents.find("\t// Properties") 35 | property_end_offset = class_contents.find("\t// Methods") 36 | 37 | if not base_type: 38 | if class_name not in protos: 39 | protos[class_name] = {"type": "message", "fields": []} 40 | else: 41 | if "messages" not in protos[base_type]: 42 | protos[base_type]["messages"] = {} 43 | 44 | if class_name not in protos[base_type]["messages"]: 45 | protos[base_type]["messages"][class_name] = {"fields": []} 46 | 47 | if property_start_offset == -1: 48 | print(f"Class {class_name} does not have properties") 49 | return 50 | 51 | # Get the properties 52 | property_content = class_contents[property_start_offset:property_end_offset].split("\n") 53 | property_content.pop(0) 54 | property_content.pop(len(property_content) - 1) 55 | property_content.pop(len(property_content) - 1) 56 | 57 | current_field_attribute = "" 58 | is_next_prop_browsable = False 59 | for propline in property_content: 60 | if propline.startswith("\t["): 61 | # Get the attribute name 62 | attribute_name = propline.split("[")[1].split("]")[0] 63 | if attribute_name == "ProtoMemberAttribute": 64 | current_field_attribute = propline 65 | elif attribute_name == "BrowsableAttribute": 66 | is_next_prop_browsable = True 67 | else: 68 | if "Specified { get; set; }" in propline or is_next_prop_browsable: 69 | is_next_prop_browsable = False 70 | continue 71 | 72 | #\t(\[([a-zA-Z]+)\]) \/\/ RVA: (0x[A-Z0-9]+) Offset: (0x[A-Z0-9]+) VA: (0x[A-Z0-9]+)\n 73 | prop_field_attribute = current_field_attribute 74 | current_field_attribute = "" 75 | 76 | prop_attribute_offset_re = re.match(r"\t(\[([a-zA-Z]+)\]) \/\/ RVA: (0x[A-Z0-9]+) Offset: (0x[A-Z0-9]+) VA: (0x[A-Z0-9]+)", prop_field_attribute) 77 | prop_field_offset_dict = {"rva": prop_attribute_offset_re.group(3), "offset": prop_attribute_offset_re.group(4), "va": prop_attribute_offset_re.group(5)} 78 | 79 | items = propline.split(" ") 80 | 81 | # Get property type & name 82 | prop_type, prop_name = items[1], items[2] 83 | 84 | # Get the field number 85 | field_number = get_field_number(sys.argv[2], int(prop_field_offset_dict["offset"], 16)) 86 | 87 | # Print the result 88 | print(f"{class_name}.{prop_name} ({prop_type}) = {field_number}") 89 | 90 | is_nullable = f"{prop_name}Specified {{ get; set; }}" in class_contents 91 | 92 | field_obj = {"name": prop_name, "type": prop_type, "field_number": field_number} 93 | 94 | if is_nullable: 95 | field_obj["is_optional"] = True 96 | 97 | # Add the field to the proto 98 | if not base_type: 99 | protos[class_name]["fields"].append(field_obj) 100 | else: 101 | protos[base_type]["messages"][class_name]["fields"].append(field_obj) 102 | 103 | def dump_enum_class(class_contents: str, protos: dict, base_type: str = "") -> None: 104 | enum_name = class_contents.split("public enum ")[1].split(" //")[0] 105 | lines = class_contents.split("\n") 106 | for line in lines: 107 | if line.startswith("\tpublic const"): 108 | enum_value = line.split(" = ")[0].strip().split(" ")[-1] 109 | enum_value_number = line.split(" = ")[1].strip().replace(";", "") 110 | print(f"{enum_name}.{enum_value} = {enum_value_number}") 111 | 112 | if not base_type: 113 | if enum_name not in protos: 114 | protos[enum_name] = {"type": "enum", "values": []} 115 | 116 | protos[enum_name]["values"].append({"name": enum_value, "value": enum_value_number}) 117 | else: 118 | if "enums" not in protos[base_type]: 119 | protos[base_type]["enums"] = {} 120 | 121 | if enum_name not in protos[base_type]["enums"]: 122 | protos[base_type]["enums"][enum_name] = {"values": []} 123 | 124 | protos[base_type]["enums"][enum_name]["values"].append({"name": enum_value, "value": enum_value_number}) 125 | 126 | if __name__ == "__main__": 127 | if len(sys.argv) != 3: 128 | print("Usage: python main.py ") 129 | sys.exit(1) 130 | 131 | if not os.path.exists(sys.argv[1]): 132 | print("dump.cs does not exist") 133 | sys.exit(1) 134 | 135 | if not os.path.exists(sys.argv[2]): 136 | print("(User/Game)Assembly.dll does not exist") 137 | sys.exit(1) 138 | 139 | protos = {} 140 | 141 | try: 142 | ua_file_handle = open(sys.argv[2], "rb") 143 | 144 | # Open the file and read the contents 145 | with open(sys.argv[1], "r", encoding="utf-8") as f: 146 | contents = f.read() 147 | 148 | # Match all lines that contain // Namespace: proto and make it into a list with offset 149 | proto_types = [(m.start(0), m.group(0)) for m in re.finditer(r"// Namespace: proto", contents)] 150 | 151 | # Parse the C# class 152 | for offset, line in proto_types: 153 | # Example class: 154 | # // Namespace: proto 155 | # [ProtoContractAttribute] // RVA: 0x509D40 Offset: 0x509140 VA: 0x180509D40 156 | # [Serializable] 157 | # public class AvatarSubSkill : IExtensible // TypeDefIndex: 3041 158 | 159 | # Get the class contents 160 | class_contents = contents[offset:contents.find("}\n\n//", offset) + 2] 161 | 162 | # Get the class name 163 | if "public class" not in class_contents: 164 | if "public enum" in class_contents: 165 | dump_enum_class(class_contents, protos) 166 | else: 167 | ... 168 | else: 169 | dump_message_class(class_contents, protos) 170 | 171 | #Get class nested types 172 | if "public class" in class_contents: 173 | class_name = class_contents.split("public class ")[1].split(" :")[0] 174 | 175 | #Enums 176 | class_nested_enums = [(m.start(0), m.group(0)) for m in re.finditer(rf"public enum {class_name}\.([a-zA-Z]+)", contents)] 177 | 178 | for enum_offset, enum_line in class_nested_enums: 179 | enum_contents = contents[enum_offset:contents.find("}\n\n//", enum_offset) + 2] 180 | dump_enum_class(enum_contents, protos, class_name) 181 | 182 | #Nested classes/protos/types 183 | class_nested_classes = [(m.start(0), m.group(0)) for m in re.finditer(rf"public class {class_name}\.([a-zA-Z]+)", contents)] 184 | 185 | for class_offset, class_line in class_nested_classes: 186 | subclass_contents = contents[class_offset:contents.find("}\n\n//", class_offset) + 2] 187 | dump_message_class(subclass_contents, protos, class_name) 188 | 189 | # Write the protos to a json file 190 | with open("protos.json", "w") as f: 191 | print("Dumping completed, writing...") 192 | json.dump(protos, f, indent=4) 193 | 194 | finally: 195 | ua_file_handle.close() 196 | 197 | print("Done!") 198 | -------------------------------------------------------------------------------- /typedumperaarch64.py: -------------------------------------------------------------------------------- 1 | import os, io 2 | import sys 3 | import re 4 | import json 5 | 6 | from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM, CsInsn 7 | 8 | ua_file_handle: io.BufferedReader = None 9 | proto_member_attribute_ctor_rva = 0 10 | 11 | def get_field_number(ua_path: str, cag_offset: int) -> int: 12 | ua_file_handle.seek(cag_offset) 13 | 14 | search_window = f.read(100) 15 | 16 | # lea edx, [r8+?] 17 | # or "41 8D 50 ?" 18 | 19 | pattern = b'\x41\x8D\x50' 20 | 21 | match = re.search(pattern, search_window) 22 | 23 | if match is None: 24 | return -1 25 | 26 | offset = match.start() 27 | 28 | # Get the field number 29 | field_number = search_window[offset + 3] 30 | 31 | return field_number 32 | 33 | def get_field_number_capstone_aarch64(ua_path: str, cag_offset: int) -> int: 34 | md = Cs(CS_ARCH_ARM64, CS_MODE_ARM) 35 | md.detail = True 36 | ua_file_handle.seek(cag_offset) 37 | data = ua_file_handle.read(100 * 4) # 100 instructions 38 | 39 | insts_until_ctor: list[CsInsn] = [] 40 | for inst in md.disasm(data, cag_offset): 41 | if inst.mnemonic == "bl" and inst.op_str == f"#0x{proto_member_attribute_ctor_rva:x}": 42 | insts_until_ctor.append(inst) 43 | break 44 | elif inst.mnemonic == "bl": 45 | insts_until_ctor.clear() 46 | insts_until_ctor.append(inst) 47 | 48 | for inst in insts_until_ctor: 49 | inst_str = f"{inst.mnemonic} {inst.op_str}" 50 | if inst_str.startswith("orr w1, wzr, #") or inst_str.startswith("movz w1, #"): 51 | #return int(inst_str.split("#")[1]) 52 | return inst.operands[-1].value.imm 53 | 54 | raise Exception("Failed to find field number") 55 | 56 | 57 | def dump_message_class(class_contents: str, protos: dict, base_type: str = "") -> None: 58 | class_name = class_contents.split("public class ")[1].split(" // TypeDefIndex:")[0] 59 | 60 | property_start_offset = class_contents.find("\t// Properties") 61 | property_end_offset = class_contents.find("\t// Methods") 62 | 63 | if not base_type: 64 | if class_name not in protos: 65 | protos[class_name] = {"type": "message", "fields": []} 66 | else: 67 | if "messages" not in protos[base_type]: 68 | protos[base_type]["messages"] = {} 69 | 70 | if class_name not in protos[base_type]["messages"]: 71 | protos[base_type]["messages"][class_name] = {"fields": []} 72 | 73 | if property_start_offset == -1: 74 | print(f"Class {class_name} does not have properties") 75 | return 76 | 77 | # Get the properties 78 | property_content = class_contents[property_start_offset:property_end_offset].split("\n") 79 | property_content.pop(0) 80 | property_content.pop(len(property_content) - 1) 81 | property_content.pop(len(property_content) - 1) 82 | 83 | current_field_attribute = "" 84 | is_next_prop_browsable = False 85 | for propline in property_content: 86 | if propline.startswith("\t["): 87 | # Get the attribute name 88 | attribute_name = propline.split("[")[1].split("]")[0] 89 | if attribute_name == "ProtoMemberAttribute": 90 | current_field_attribute = propline 91 | elif attribute_name == "BrowsableAttribute": 92 | is_next_prop_browsable = True 93 | else: 94 | if "Specified { get; set; }" in propline or is_next_prop_browsable: 95 | is_next_prop_browsable = False 96 | continue 97 | 98 | #\t(\[([a-zA-Z]+)\]) \/\/ RVA: (0x[A-Z0-9]+) Offset: (0x[A-Z0-9]+) VA: (0x[A-Z0-9]+)\n 99 | prop_field_attribute = current_field_attribute 100 | current_field_attribute = "" 101 | 102 | prop_attribute_offset_re = re.match(r"\t(\[([a-zA-Z]+)\]) \/\/ RVA: (0x[A-Z0-9]+) Offset: (0x[A-Z0-9]+) VA: (0x[A-Z0-9]+)", prop_field_attribute) 103 | prop_field_offset_dict = {"rva": prop_attribute_offset_re.group(3), "offset": prop_attribute_offset_re.group(4), "va": prop_attribute_offset_re.group(5)} 104 | 105 | items = propline.split(" ") 106 | 107 | # Get property type & name 108 | prop_type, prop_name = items[1], items[2] 109 | 110 | # Get the field number 111 | field_number = get_field_number_capstone_aarch64(sys.argv[2], int(prop_field_offset_dict["offset"], 16)) 112 | 113 | # Print the result 114 | # print(f"{class_name}.{prop_name} ({prop_type}) = {field_number}") 115 | 116 | is_nullable = f"{prop_name}Specified {{ get; set; }}" in class_contents 117 | 118 | field_obj = {"name": prop_name, "type": prop_type, "field_number": field_number} 119 | 120 | if is_nullable: 121 | field_obj["is_optional"] = True 122 | 123 | # Add the field to the proto 124 | if not base_type: 125 | protos[class_name]["fields"].append(field_obj) 126 | else: 127 | protos[base_type]["messages"][class_name]["fields"].append(field_obj) 128 | 129 | def dump_enum_class(class_contents: str, protos: dict, base_type: str = "") -> None: 130 | enum_name = class_contents.split("public enum ")[1].split(" //")[0] 131 | lines = class_contents.split("\n") 132 | for line in lines: 133 | if line.startswith("\tpublic const"): 134 | enum_value = line.split(" = ")[0].strip().split(" ")[-1] 135 | enum_value_number = line.split(" = ")[1].strip().replace(";", "") 136 | # print(f"{enum_name}.{enum_value} = {enum_value_number}") 137 | 138 | if not base_type: 139 | if enum_name not in protos: 140 | protos[enum_name] = {"type": "enum", "values": []} 141 | 142 | protos[enum_name]["values"].append({"name": enum_value, "value": enum_value_number}) 143 | else: 144 | if "enums" not in protos[base_type]: 145 | protos[base_type]["enums"] = {} 146 | 147 | if enum_name not in protos[base_type]["enums"]: 148 | protos[base_type]["enums"][enum_name] = {"values": []} 149 | 150 | protos[base_type]["enums"][enum_name]["values"].append({"name": enum_value, "value": enum_value_number}) 151 | 152 | if __name__ == "__main__": 153 | if len(sys.argv) != 3: 154 | print("Usage: python main.py ") 155 | sys.exit(1) 156 | 157 | if not os.path.exists(sys.argv[1]): 158 | print("dump.cs does not exist") 159 | sys.exit(1) 160 | 161 | if not os.path.exists(sys.argv[2]): 162 | print("(User/Game)Assembly.dll does not exist") 163 | sys.exit(1) 164 | 165 | protos = {} 166 | 167 | try: 168 | ua_file_handle = open(sys.argv[2], "rb") 169 | 170 | # Open the file and read the contents 171 | with open(sys.argv[1], "r", encoding="utf-8") as f: 172 | contents = f.read() 173 | 174 | proto_member_attribute = [(m.start(0), m.group(0)) for m in re.finditer(r"public class ProtoMemberAttribute : Attribute", contents)] 175 | for offset, line in proto_member_attribute: 176 | class_contents = contents[offset:contents.find("}\n\n//", offset) + 2] 177 | previous_line = "" 178 | for line in class_contents.split("\n"): 179 | if line.startswith("\tpublic void .ctor(int tag) { }"): 180 | proto_member_attribute_ctor_rva = int(re.search(r"RVA: (0x[a-zA-Z0-9]+)", previous_line).group(1), 16) 181 | break 182 | previous_line = line 183 | 184 | # Match all lines that contain // Namespace: proto and make it into a list with offset 185 | proto_types = [(m.start(0), m.group(0)) for m in re.finditer(r"// Namespace: ", contents)] 186 | 187 | # Parse the C# class 188 | for offset, line in proto_types: 189 | # Example class: 190 | # // Namespace: proto 191 | # [ProtoContractAttribute] // RVA: 0x509D40 Offset: 0x509140 VA: 0x180509D40 192 | # [Serializable] 193 | # public class AvatarSubSkill : IExtensible // TypeDefIndex: 3041 194 | 195 | # Get the class contents 196 | class_contents = contents[offset:contents.find("}\n\n//", offset) + 2] 197 | """ 198 | // Image 37: MessageCS.dll - 2969 199 | // Image 38: UnityEngine.UI.dll - 4606 200 | """ 201 | type_def_index = int(re.search(r"\/\/ TypeDefIndex: (\d+)\n", class_contents).group(1)) 202 | if type_def_index < 2969: 203 | continue 204 | elif not 2969 <= type_def_index <= 4606: 205 | break 206 | 207 | if "public class" not in class_contents: 208 | if "public enum" in class_contents: 209 | dump_enum_class(class_contents, protos) 210 | else: 211 | ... 212 | else: 213 | dump_message_class(class_contents, protos) 214 | 215 | #Get class nested types 216 | if "public class" in class_contents: 217 | class_name = class_contents.split("public class ")[1].split(" :")[0] 218 | 219 | #Enums 220 | class_nested_enums = [(m.start(0), m.group(0)) for m in re.finditer(rf"public enum {re.escape(class_name)}\.([a-zA-Z]+)", contents)] 221 | 222 | for enum_offset, enum_line in class_nested_enums: 223 | enum_contents = contents[enum_offset:contents.find("}\n\n//", enum_offset) + 2] 224 | dump_enum_class(enum_contents, protos, class_name) 225 | 226 | #Nested classes/protos/types 227 | class_nested_classes = [(m.start(0), m.group(0)) for m in re.finditer(rf"public class {re.escape(class_name)}\.([a-zA-Z]+)", contents)] 228 | 229 | for class_offset, class_line in class_nested_classes: 230 | subclass_contents = contents[class_offset:contents.find("}\n\n//", class_offset) + 2] 231 | dump_message_class(subclass_contents, protos, class_name) 232 | 233 | protos_copy = protos.copy() 234 | # Fix the nested types 235 | for proto_name, proto in protos.items(): 236 | dot_count = proto_name.count(".") 237 | if dot_count == 1: 238 | base_type, nested_type = proto_name.split(".") 239 | if base_type in protos: 240 | if protos[proto_name]["type"] == "enum": 241 | if "enums" not in protos[base_type]: 242 | protos_copy[base_type]["enums"] = {} 243 | protos_copy[base_type]["enums"][nested_type] = protos[proto_name] 244 | 245 | elif protos[proto_name]["type"] == "message": 246 | if "messages" not in protos[base_type]: 247 | protos_copy[base_type]["messages"] = {} 248 | protos_copy[base_type]["messages"][nested_type] = protos[proto_name] 249 | 250 | del protos_copy[proto_name] 251 | for i, field in enumerate(protos[base_type]["fields"]): 252 | if field["type"] == proto_name: 253 | #field["type"] = f"{nested_type}" 254 | protos_copy[base_type]["fields"][i]["type"] = f"{nested_type}" 255 | 256 | elif dot_count > 2: 257 | print("well fuck me i guess:", proto_name) 258 | 259 | for proto_name, proto in protos.items(): 260 | dot_count = proto_name.count(".") 261 | if dot_count == 2: 262 | #these guys are a bit special 263 | base_type, nested_type, nested_nested_type = proto_name.split(".") 264 | if base_type in protos: 265 | if protos[proto_name]["type"] == "enum": 266 | if "enums" not in protos[base_type]: 267 | protos_copy[base_type]["enums"] = {} 268 | if nested_type not in protos_copy[base_type]["enums"]: 269 | protos_copy[base_type]["enums"][nested_type] = {} 270 | protos_copy[base_type]["enums"][nested_type][nested_nested_type] = protos[proto_name] 271 | 272 | elif protos[proto_name]["type"] == "message": 273 | if "messages" not in protos[base_type]: 274 | protos_copy[base_type]["messages"] = {} 275 | 276 | if nested_type not in protos_copy[base_type]["messages"]: 277 | protos_copy[base_type]["messages"][nested_type] = {} 278 | 279 | if "messages" not in protos_copy[base_type]["messages"][nested_type]: 280 | protos_copy[base_type]["messages"][nested_type]["messages"] = {} 281 | 282 | if nested_nested_type not in protos_copy[base_type]["messages"][nested_type]["messages"]: 283 | protos_copy[base_type]["messages"][nested_type]["messages"][nested_nested_type] = {} 284 | 285 | protos_copy[base_type]["messages"][nested_type]["messages"][nested_nested_type] = protos[proto_name] 286 | 287 | del protos_copy[proto_name] 288 | 289 | for i, field in enumerate(protos[base_type]["fields"]): 290 | if field["type"] == proto_name: 291 | protos_copy[base_type]["fields"][i]["type"] = f"{nested_type}.{nested_nested_type}" 292 | for i, field in enumerate(protos[f"{base_type}.{nested_type}"]["fields"]): 293 | if field["type"] == proto_name: 294 | protos_copy[base_type]["messages"][nested_type]["fields"][i]["type"] = f"{nested_nested_type}" 295 | 296 | # Write the protos to a json file 297 | with open("protos.json", "w") as f: 298 | print("Dumping completed, writing...") 299 | json.dump(protos_copy, f, indent=4) 300 | 301 | finally: 302 | ua_file_handle.close() 303 | 304 | print("Done!") 305 | --------------------------------------------------------------------------------