#
# 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)