# # Copyright (c) 2018-2020 Atmosphère-NX # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU General Public License, # version 2, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # erpt.py: Autogeneration tool for <stratosphere/erpt/erpt_ids.autogen.hpp> import nxo64 import sys, os, string from struct import unpack as up, pack as pk LOAD_BASE = 0x7100000000 HEADER = '''/* * Copyright (c) 2018-2020 Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #pragma once #include <vapours.hpp> /* NOTE: This file is auto-generated. */ /* Do not make edits to this file by hand. */ ''' if sys.version_info[0] == 3: iter_range = range int_types = (int,) ascii_string = lambda b: b.decode('ascii') bytes_to_list = lambda b: list(b) list_to_bytes = lambda l: bytes(l) else: iter_range = xrange int_types = (int, long) ascii_string = lambda b: str(b) bytes_to_list = lambda b: map(ord, b) list_to_bytes = lambda l: ''.join(map(chr, l)) (DT_NULL, DT_NEEDED, DT_PLTRELSZ, DT_PLTGOT, DT_HASH, DT_STRTAB, DT_SYMTAB, DT_RELA, DT_RELASZ, DT_RELAENT, DT_STRSZ, DT_SYMENT, DT_INIT, DT_FINI, DT_SONAME, DT_RPATH, DT_SYMBOLIC, DT_REL, DT_RELSZ, DT_RELENT, DT_PLTREL, DT_DEBUG, DT_TEXTREL, DT_JMPREL, DT_BIND_NOW, DT_INIT_ARRAY, DT_FINI_ARRAY, DT_INIT_ARRAYSZ, DT_FINI_ARRAYSZ, DT_RUNPATH, DT_FLAGS) = iter_range(31) DT_GNU_HASH = 0x6ffffef5 DT_VERSYM = 0x6ffffff0 DT_RELACOUNT = 0x6ffffff9 DT_RELCOUNT = 0x6ffffffa DT_FLAGS_1 = 0x6ffffffb DT_VERDEF = 0x6ffffffc DT_VERDEFNUM = 0x6ffffffd STT_NOTYPE = 0 STT_OBJECT = 1 STT_FUNC = 2 STT_SECTION = 3 STB_LOCAL = 0 STB_GLOBAL = 1 STB_WEAK = 2 R_ARM_ABS32 = 2 R_ARM_TLS_DESC = 13 R_ARM_GLOB_DAT = 21 R_ARM_JUMP_SLOT = 22 R_ARM_RELATIVE = 23 R_AARCH64_ABS64 = 257 R_AARCH64_GLOB_DAT = 1025 R_AARCH64_JUMP_SLOT = 1026 R_AARCH64_RELATIVE = 1027 R_AARCH64_TLSDESC = 1031 CATEGORIES = { 0 : 'Test', 1 : 'ErrorInfo', 2 : 'ConnectionStatusInfo', 3 : 'NetworkInfo', 4 : 'NXMacAddressInfo', 5 : 'StealthNetworkInfo', 6 : 'LimitHighCapacityInfo', 7 : 'NATTypeInfo', 8 : 'WirelessAPMacAddressInfo', 9 : 'GlobalIPAddressInfo', 10 : 'EnableWirelessInterfaceInfo', 11 : 'EnableWifiInfo', 12 : 'EnableBluetoothInfo', 13 : 'EnableNFCInfo', 14 : 'NintendoZoneSSIDListVersionInfo', 15 : 'LANAdapterMacAddressInfo', 16 : 'ApplicationInfo', 17 : 'OccurrenceInfo', 18 : 'ProductModelInfo', 19 : 'CurrentLanguageInfo', 20 : 'UseNetworkTimeProtocolInfo', 21 : 'TimeZoneInfo', 22 : 'ControllerFirmwareInfo', 23 : 'VideoOutputInfo', 24 : 'NANDFreeSpaceInfo', 25 : 'SDCardFreeSpaceInfo', 26 : 'ScreenBrightnessInfo', 27 : 'AudioFormatInfo', 28 : 'MuteOnHeadsetUnpluggedInfo', 29 : 'NumUserRegisteredInfo', 30 : 'DataDeletionInfo', 31 : 'ControllerVibrationInfo', 32 : 'LockScreenInfo', 33 : 'InternalBatteryLotNumberInfo', 34 : 'LeftControllerSerialNumberInfo', 35 : 'RightControllerSerialNumberInfo', 36 : 'NotificationInfo', 37 : 'TVInfo', 38 : 'SleepInfo', 39 : 'ConnectionInfo', 40 : 'NetworkErrorInfo', 41 : 'FileAccessPathInfo', 42 : 'GameCardCIDInfo', 43 : 'NANDCIDInfo', 44 : 'MicroSDCIDInfo', 45 : 'NANDSpeedModeInfo', 46 : 'MicroSDSpeedModeInfo', 47 : 'GameCardSpeedModeInfo', 48 : 'UserAccountInternalIDInfo', 49 : 'NetworkServiceAccountInternalIDInfo', 50 : 'NintendoAccountInternalIDInfo', 51 : 'USB3AvailableInfo', 52 : 'CallStackInfo', 53 : 'SystemStartupLogInfo', 54 : 'RegionSettingInfo', 55 : 'NintendoZoneConnectedInfo', 56 : 'ForceSleepInfo', 57 : 'ChargerInfo', 58 : 'RadioStrengthInfo', 59 : 'ErrorInfoAuto', 60 : 'AccessPointInfo', 61 : 'ErrorInfoDefaults', 62 : 'SystemPowerStateInfo', 63 : 'PerformanceInfo', 64 : 'ThrottlingInfo', 65 : 'GameCardErrorInfo', 66 : 'EdidInfo', 67 : 'ThermalInfo', 68 : 'CradleFirmwareInfo', 69 : 'RunningApplicationInfo', 70 : 'RunningAppletInfo', 71 : 'FocusedAppletHistoryInfo', 72 : 'CompositorInfo', 73 : 'BatteryChargeInfo', 74 : 'NANDExtendedCsd', 75 : 'NANDPatrolInfo', 76 : 'NANDErrorInfo', 77 : 'NANDDriverLog', 78 : 'SdCardSizeSpec', 79 : 'SdCardErrorInfo', 80 : 'SdCardDriverLog ', 81 : 'FsProxyErrorInfo', 82 : 'SystemAppletSceneInfo', 83 : 'VideoInfo', 84 : 'GpuErrorInfo', 85 : 'PowerClockInfo', 86 : 'AdspErrorInfo', 87 : 'NvDispDeviceInfo', 88 : 'NvDispDcWindowInfo', 89 : 'NvDispDpModeInfo', 90 : 'NvDispDpLinkSpec', 91 : 'NvDispDpLinkStatus', 92 : 'NvDispDpHdcpInfo', 93 : 'NvDispDpAuxCecInfo', 94 : 'NvDispDcInfo', 95 : 'NvDispDsiInfo', 96 : 'NvDispErrIDInfo', 97 : 'SdCardMountInfo', 98 : 'RetailInteractiveDisplayInfo', 99 : 'CompositorStateInfo', 100 : 'CompositorLayerInfo', 101 : 'CompositorDisplayInfo', 102 : 'CompositorHWCInfo', 103 : 'MonitorCapability', 104 : 'ErrorReportSharePermissionInfo', 105 : 'MultimediaInfo', 106 : 'ConnectedControllerInfo', 107 : 'FsMemoryInfo', 108 : 'UserClockContextInfo', 109 : 'NetworkClockContextInfo', 110 : 'AcpGeneralSettingsInfo', 111 : 'AcpPlayLogSettingsInfo', 112 : 'AcpAocSettingsInfo', 113 : 'AcpBcatSettingsInfo', 114 : 'AcpStorageSettingsInfo', 115 : 'AcpRatingSettingsInfo', 116 : 'MonitorSettings', 117 : 'RebootlessSystemUpdateVersionInfo', 118 : 'NifmConnectionTestInfo', 119 : 'PcieLoggedStateInfo', 120 : 'NetworkSecurityCertificateInfo', 121 : 'AcpNeighborDetectionInfo', 122 : 'GpuCrashInfo', 123 : 'UsbStateInfo', 124 : 'NvHostErrInfo', 125 : 'RunningUlaInfo', 126 : 'InternalPanelInfo', 127 : 'ResourceLimitLimitInfo', 128 : 'ResourceLimitPeakInfo', 129 : 'TouchScreenInfo', } FIELD_TYPES = { 0 : 'FieldType_NumericU64', 1 : 'FieldType_NumericU32', 2 : 'FieldType_NumericI64', 3 : 'FieldType_NumericI32', 4 : 'FieldType_String', 5 : 'FieldType_U8Array', 6 : 'FieldType_U32Array', 7 : 'FieldType_U64Array', 8 : 'FieldType_I32Array', 9 : 'FieldType_I64Array', 10 : 'FieldType_Bool', 11 : 'FieldType_NumericU16', 12 : 'FieldType_NumericU8', 13 : 'FieldType_NumericI16', 14 : 'FieldType_NumericI8', 15 : 'FieldType_I8Array', } FIELD_FLAGS = { 0 : 'FieldFlag_None', 1 : 'FieldFlag_Encrypt', } def get_full(nxo): full = nxo.text[0] if nxo.ro[2] >= len(full): full += b'\x00' * (nxo.ro[2] - len(full)) else: full = full[:nxo.ro[2]] full += nxo.ro[0] if nxo.data[2] > len(full): full += b'\x00' * (nxo.data[2] - len(full)) full += nxo.data[0] undef_count = 0 for s in nxo.symbols: if not s.shndx and s.name: undef_count += 1 last_ea = max(LOAD_BASE + end for start, end, name, kind in nxo.sections) undef_entry_size = 8 undef_ea = ((last_ea + 0xFFF) & ~0xFFF) + undef_entry_size # plus 8 so we don't end up on the "end" symbol for i,s in enumerate(nxo.symbols): if not s.shndx and s.name: #idaapi.create_data(undef_ea, idc.FF_QWORD, 8, idaapi.BADADDR) #idaapi.force_name(undef_ea, s.name) s.resolved = undef_ea undef_ea += undef_entry_size elif i != 0: assert s.shndx s.resolved = LOAD_BASE + s.value if s.name: if s.type == STT_FUNC: #print(hex(s.resolved), s.name) #idaapi.add_entry(s.resolved, s.resolved, s.name, 0) pass else: #idaapi.force_name(s.resolved, s.name) pass else: # NULL symbol s.resolved = 0 def put_dword(z, target, val): return z[:target] + pk('<I', val) + z[target+4:] def put_qword(z, target, val): return z[:target] + pk('<Q', val) + z[target+8:] def get_dword(z, target): return up('<I', z[target:target+4])[0] for offset, r_type, sym, addend in nxo.relocations: #print offset, r_type, sym, addend target = offset + LOAD_BASE if r_type in (R_ARM_GLOB_DAT, R_ARM_JUMP_SLOT, R_ARM_ABS32): if not sym: print('error: relocation at %X failed' % target) else: full = put_dword(full, target, sym.resolved) elif r_type == R_ARM_RELATIVE: full = put_dword(full, target, get_dword(full, target) + LOAD_BASE) elif r_type in (R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT, R_AARCH64_ABS64): full = put_qword(full, target, sym.resolved + addend) elif r_type == R_AARCH64_RELATIVE: full = put_qword(full, target, LOAD_BASE + addend) else: print('TODO r_type %d' % (r_type,)) return full def locate_fields(full): start = ['TestU64', 'TestU32', 'TestI64', 'TestI32'] inds = [LOAD_BASE + full.index('%s\x00' % s) for s in start] target = pk('<QQQQ', inds[0], inds[1], inds[2], inds[3]) return full.index(target) def read_string(full, ofs): s = '' if ofs >= len(full): return '' while full[ofs] != '\x00': s += full[ofs] ofs += 1 if ofs >= len(full): return '' return s def is_valid_field_name(s): ALLOWED = string.lowercase + string.uppercase + string.digits + '_' if not s: return False for c in s: if not c in ALLOWED: return False return True def parse_fields(full, table): fields = [] ofs = 0 while True: val = up('<Q', full[table + ofs:table + ofs + 8])[0] if (val & 0xFFFFFFFF00000000) != LOAD_BASE: break s = read_string(full, val - LOAD_BASE) if not is_valid_field_name(s): break fields.append(s) ofs += 8 return fields def find_categories(full, num_fields): KNOWN = [0] * 10 + [1] * 2 + [0x3B] * 2 ind = full.index(''.join(pk('<I', i) for i in KNOWN)) return list(up('<'+'I'*num_fields, full[ind:ind+4*num_fields])) def find_types(full, num_fields): KNOWN = range(10) + [4, 4, 2, 4] KNOWN_OLD = range(10) + [4, 4, 0, 4] try: ind = full.index(''.join(pk('<I', i) for i in KNOWN)) except ValueError: ind = full.index(''.join(pk('<I', i) for i in KNOWN_OLD)) return list(up('<'+'I'*num_fields, full[ind:ind+4*num_fields])) def find_flags(full, num_fields): KNOWN = '\x00' + ('\x01'*6) + '\x00\x01\x01\x00' if num_fields < 443 + len(KNOWN): return [0] * num_fields ind = full.index(KNOWN) - 443 return list(up('<'+'B'*num_fields, full[ind:ind+num_fields])) def cat_to_string(c): return CATEGORIES[c] if c in CATEGORIES else 'Category_Unknown%d' % c def typ_to_string(t): return FIELD_TYPES[t] if t in FIELD_TYPES else 'FieldType_Unknown%d' % t def flg_to_string(f): return FIELD_FLAGS[f] if f in FIELD_FLAGS else 'FieldFlag_Unknown%d' % f def main(argc, argv): if argc != 2 and not (argc == 3 and argv[1] == '-f'): print 'Usage: %s [-f] erpt_nso' % argv[0] return 1 f = open(argv[-1], 'rb') nxo = nxo64.load_nxo(f) full = get_full(nxo) field_table = locate_fields(full) fields = parse_fields(full, field_table) NUM_FIELDS = len(fields) cats = find_categories(full, NUM_FIELDS) types = find_types(full, NUM_FIELDS) flags = find_flags(full, NUM_FIELDS) print 'Identified %d fields.' % NUM_FIELDS mf = max(len(s) for s in fields) mc = max(len(cat_to_string(c)) for c in cats) mt = max(len(typ_to_string(t)) for t in types) ml = max(len(flg_to_string(f)) for f in flags) if argc == 3: mf, mc, mt, ml = (64, 48, 32, 32) format_string = '- %%-%ds %%-%ds %%-%ds %%-%ds' % (mf+1, mc+1, mt+1, ml) for i in xrange(NUM_FIELDS): f, c, t, l = fields[i], cat_to_string(cats[i]), typ_to_string(types[i]), flg_to_string(flags[i]) print format_string % (f+',', c+',', t+',', l) with open(argv[-1]+'.hpp', 'w') as out: out.write(HEADER) out.write('#define AMS_ERPT_FOREACH_FIELD_TYPE(HANDLER) \\\n') for tp in sorted(list(set(types + FIELD_TYPES.keys()))): out.write((' HANDLER(%%-%ds %%-2d) \\\n' % (mt+1)) % (typ_to_string(tp)+',', tp)) out.write('\n') out.write('#define AMS_ERPT_FOREACH_CATEGORY(HANDLER) \\\n') for ct in sorted(list(set(cats + CATEGORIES.keys()))): out.write((' HANDLER(%%-%ds %%-3d) \\\n' % (mc+1)) % (cat_to_string(ct)+',', ct)) out.write('\n') out.write('#define AMS_ERPT_FOREACH_FIELD(HANDLER) \\\n') for i in xrange(NUM_FIELDS): f, c, t, l = fields[i], cats[i], types[i], flags[i] out.write((' HANDLER(%%-%ds %%-4s %%-%ds %%-%ds %%-%ds) \\\n' % (mf+1, mc+1, mt+1, ml)) % (f+',', '%d,'%i, cat_to_string(c)+',', typ_to_string(t)+',', flg_to_string(l))) out.write('\n') return 0 if __name__ == '__main__': try: ret = main(len(sys.argv), sys.argv) except: ret = 1 print 'exception' sys.exit(ret)