[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - directory: /\n    open-pull-requests-limit: 5\n    package-ecosystem: github-actions\n    schedule:\n      interval: weekly\n"
  },
  {
    "path": ".github/workflows/dep-updates-am.yml",
    "content": "name: Dependency updates auto-merge\non: pull_request\n\npermissions:\n  pull-requests: write\n  contents: write\n  checks: read\n\njobs:\n  auto_merge:\n    runs-on: ubuntu-latest\n    if: ${{ github.actor == 'dependabot[bot]' }}\n    steps:\n      - name: Enable auto-merge for dependency update PRs\n        run: gh pr merge --auto -s \"$PR_URL\"\n        env:\n          PR_URL: ${{github.event.pull_request.html_url}}\n          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish release artifacts\n\non:\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: Release git tag\n        type: string\n        required: true\n  push:\n  pull_request:\n\npermissions:\n  contents: write\n\njobs:\n  build_and_publish:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        ghidra: [ \"12.0.1\" ]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Java\n        uses: actions/setup-java@v5\n        with:\n          distribution: \"temurin\"\n          java-version: \"21\"\n\n      - name: Setup Ghidra ${{ matrix.ghidra }}\n        uses: antoniovazquezblanco/setup-ghidra@v2.1.0\n        with:\n          version: ${{ matrix.ghidra }}\n\n      - name: Build extension\n        run: ./gradlew\n\n      - uses: svenstaro/upload-release-action@v2\n        if: github.event_name == 'push' && contains(github.ref, 'refs/tags/v')\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: dist/SwitchLoader-*.zip\n          tag: ${{ github.ref }}\n          overwrite: true\n          file_glob: true\n\n      - uses: svenstaro/upload-release-action@v2\n        if: github.event_name == 'workflow_dispatch'\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: dist/SwitchLoader-*.zip\n          tag: ${{ inputs.tag }}\n          overwrite: true\n          file_glob: true\n"
  },
  {
    "path": ".gitignore",
    "content": "eclipse/\n.gradle/\ndist/\nbuild/\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright 2019 Adubbz\n\nPermission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "Module.manifest",
    "content": ""
  },
  {
    "path": "README.md",
    "content": "# Ghidra Switch Loader\n\nA loader for Ghidra supporting a variety of Nintendo Switch file formats.\n\n## Building\n\n- Ensure you have ``JAVA_HOME`` set to the path of your JDK 21 installation.\n- Set ``GHIDRA_INSTALL_DIR`` to your Ghidra install directory. This can be done in one of the following ways:\n  - **Windows**: Running ``set GHIDRA_INSTALL_DIR=<Absolute path to Ghidra without quotations>``\n  - **macos/Linux**: Running ``export GHIDRA_INSTALL_DIR=<Absolute path to Ghidra>``\n  - Using ``-PGHIDRA_INSTALL_DIR=<Absolute path to Ghidra>`` when running ``./gradlew``\n  - Adding ``GHIDRA_INSTALL_DIR`` to your Windows environment variables.\n- Run ``./gradlew``\n- You'll find the output zip file inside `/dist`\n\n## Installation\n\n- Start Ghidra and use the \"Install Extensions\" dialog (``File -> Install Extensions...``).\n- Press the ``+`` button in the upper right corner.\n- Select the zip file in the file browser, then restart Ghidra.\n"
  },
  {
    "path": "build.gradle",
    "content": "// Builds a Ghidra Extension for a given Ghidra installation.\n//\n// An absolute path to the Ghidra installation directory must be supplied either by setting the \n// GHIDRA_INSTALL_DIR environment variable or Gradle project property:\n//\n//     > export GHIDRA_INSTALL_DIR=<Absolute path to Ghidra> \n//     > gradle\n//\n//         or\n//\n//     > gradle -PGHIDRA_INSTALL_DIR=<Absolute path to Ghidra>\n//\n// Gradle should be invoked from the directory of the project to build.  Please see the\n// application.gradle.version property in <GHIDRA_INSTALL_DIR>/Ghidra/application.properties\n// for the correction version of Gradle to use for the Ghidra installation you specify.\n\n//----------------------START \"DO NOT MODIFY\" SECTION------------------------------\napply plugin: 'java'\napply plugin: 'eclipse'\ndef ghidraInstallDir\n\nif (System.env.GHIDRA_INSTALL_DIR) {\n    ghidraInstallDir = System.env.GHIDRA_INSTALL_DIR\n}\nelse if (project.hasProperty(\"GHIDRA_INSTALL_DIR\")) {\n    ghidraInstallDir = project.getProperty(\"GHIDRA_INSTALL_DIR\")\n}\n\nif (ghidraInstallDir) {\n    apply from: new File(ghidraInstallDir).getCanonicalPath() + \"/support/buildExtension.gradle\"\n}\nelse {\n    throw new GradleException(\"GHIDRA_INSTALL_DIR is not defined!\")\n}\n//----------------------END \"DO NOT MODIFY\" SECTION-------------------------------\n\ndef getGitHash = {\n    def stdout = new ByteArrayOutputStream()\n    exec {\n        if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {\n            commandLine 'cmd', '/c', 'git', 'rev-parse', '--short', 'HEAD'\n        } else {\n            commandLine 'git', 'rev-parse', '--short', 'HEAD'\n        }\n        standardOutput = stdout\n    }\n    return stdout.toString().trim()\n}\n\nrepositories {\n    mavenCentral()\n}\n\nconfigurations {\n    localDeps\n}\n\ndef docs = file(ghidraInstallDir+'/docs/GhidraAPI_javadoc.zip')\ndef ghidraUserDir = System.getProperty(\"user.home\") + \"/.ghidra/.${DISTRO_PREFIX}_${RELEASE_NAME}\"\n\ndependencies {\n    implementation 'commons-primitives:commons-primitives:1.0'\n    implementation group: 'at.yawk.lz4', name: 'lz4-java', version: '1.10.2'\n    localDeps group: 'at.yawk.lz4', name: 'lz4-java', version: '1.10.2'\n\n    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/patch', include: \"**/*.jar\")\n    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Configurations', include: \"**/*.jar\")\n    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Features', include: \"**/*.jar\")\n    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Framework', include: \"**/*.jar\")\n    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Processors', include: \"**/*.jar\")\n    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Debug', include: \"**/*.jar\")\n    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Extensions',\n            include: \"**/*.jar\", exclude: project.name)\n    runtimeOnly fileTree(dir: ghidraUserDir + \"/Extensions\",\n            include: \"**/*.jar\", exclude: project.name)\n}\n\neclipse {\n    classpath {\n        downloadJavadoc = true\n        downloadSources = true\n        file {\n            whenMerged {\n                for (entry in entries) {\n                    if (entry.path.contains('jar')) {\n                        File folder = new File(entry.getPath()).getParentFile();\n                        for (File file : folder.listFiles()) {\n                            if (file.getName().endsWith(\".zip\")) {\n                                if (file.getName().contains(\"-src\")) {\n                                    entry.setSourcePath(it.fileReference(file));\n                                }\n                                entry.setJavadocPath(it.fileReference(docs));\n                            }\n                        }\n                    }\n                }\n                entries.add(\n                        new org.gradle.plugins.ide.eclipse.model.Library(\n                                it.fileReference(new File(projectDir, '/data'))\n                        )\n                )\n            }\n        }\n    }\n}\n\nbuildExtension {\n    exclude 'gradle*'\n    exclude '.github/**'\n\n    archiveBaseName = \"${project.name}-${project.version}-${getGitHash()}-Ghidra_${ghidra_version}\".replace(' ', '_')\n}\n"
  },
  {
    "path": "extension.properties",
    "content": "name=@extname@\ndescription=A loader for Nintendo Switch file formats.\nauthor=Adubbz\ncreatedOn=9/3/2019\nversion=@extversion@\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https://services.gradle.org/distributions/gradle-8.10.2-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "version=1.6.0\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=${0##*/}\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "settings.gradle",
    "content": "rootProject.name = \"SwitchLoader\""
  },
  {
    "path": "src/main/java/adubbz/nx/analyzer/IPCAnalyzer.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.analyzer;\n\nimport adubbz.nx.analyzer.ipc.IPCEmulator;\nimport adubbz.nx.analyzer.ipc.IPCTrace;\nimport adubbz.nx.common.ElfCompatibilityProvider;\nimport adubbz.nx.common.NXRelocation;\nimport adubbz.nx.loader.SwitchLoader;\nimport com.google.common.collect.HashMultimap;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Multimap;\nimport generic.stl.Pair;\nimport ghidra.app.services.AbstractAnalyzer;\nimport ghidra.app.services.AnalyzerType;\nimport ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants;\nimport ghidra.app.util.demangler.DemangledObject;\nimport ghidra.app.util.demangler.DemanglerUtil;\nimport ghidra.app.util.importer.MessageLog;\nimport ghidra.framework.options.Options;\nimport ghidra.program.model.address.Address;\nimport ghidra.program.model.address.AddressOutOfBoundsException;\nimport ghidra.program.model.address.AddressSetView;\nimport ghidra.program.model.address.AddressSpace;\nimport ghidra.program.model.data.PointerDataType;\nimport ghidra.program.model.listing.CommentType;\nimport ghidra.program.model.listing.Data;\nimport ghidra.program.model.listing.Program;\nimport ghidra.program.model.mem.Memory;\nimport ghidra.program.model.mem.MemoryAccessException;\nimport ghidra.program.model.mem.MemoryBlock;\nimport ghidra.program.model.symbol.SourceType;\nimport ghidra.program.model.symbol.Symbol;\nimport ghidra.program.model.symbol.SymbolTable;\nimport ghidra.program.model.util.CodeUnitInsertionException;\nimport ghidra.util.Msg;\nimport ghidra.util.exception.InvalidInputException;\nimport ghidra.util.task.TaskMonitor;\nimport org.apache.commons.compress.utils.Lists;\nimport org.python.google.common.collect.HashBiMap;\nimport org.python.google.common.collect.Sets;\n\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static adubbz.nx.common.ElfCompatibilityProvider.R_FAKE_RELR;\n\npublic class IPCAnalyzer extends AbstractAnalyzer \n{\n    public IPCAnalyzer() \n    {\n        super(\"(Switch) IPC Analyzer\", \"Locates and labels IPC vtables, s_Tables and implementation functions.\", AnalyzerType.INSTRUCTION_ANALYZER);\n    \n        this.setSupportsOneTimeAnalysis();\n    }\n\n    @Override\n    public boolean getDefaultEnablement(Program program) \n    {\n        return false;\n    }\n\n    @Override\n    public boolean canAnalyze(Program program) \n    {\n        return program.getExecutableFormat().equals(SwitchLoader.SWITCH_NAME);\n    }\n\n    @Override\n    public void registerOptions(Options options, Program program) \n    {\n        // TODO: Symbol options\n    }\n\n    @Override\n    public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)\n    {\n        Memory memory = program.getMemory();\n        MemoryBlock text = memory.getBlock(\".text\");\n        MemoryBlock rodata = memory.getBlock(\".rodata\");\n        MemoryBlock data = memory.getBlock(\".data\");\n        ElfCompatibilityProvider elfCompatProvider = new ElfCompatibilityProvider(program, false);\n        \n        Msg.info(this, \"Beginning IPC analysis...\");\n        \n        if (text == null || rodata == null || data == null)\n            return true;\n        \n        try\n        {\n            List<Address> vtAddrs = this.locateIpcVtables(program, elfCompatProvider);\n            List<IPCVTableEntry> vtEntries = this.createVTableEntries(program, elfCompatProvider, vtAddrs);\n            HashBiMap<Address, Address> sTableProcessFuncMap = this.locateSTables(program, elfCompatProvider);\n            Multimap<Address, IPCTrace> processFuncTraces = this.emulateProcessFunctions(program, monitor, sTableProcessFuncMap.values());\n            HashBiMap<Address, IPCVTableEntry> procFuncVtMap = this.matchVtables(vtEntries, sTableProcessFuncMap.values(), processFuncTraces);\n            this.markupIpc(program, monitor, vtEntries, sTableProcessFuncMap, processFuncTraces, procFuncVtMap);\n        }\n        catch (Exception e)\n        {\n            Msg.error(this, \"Failed to analyze binary IPC.\", e);\n            return false;\n        }\n        \n        return true;\n    }\n    \n    private List<Address> locateIpcVtables(Program program, ElfCompatibilityProvider elfProvider) throws MemoryAccessException, AddressOutOfBoundsException, IOException\n    {\n        List<Address> out = Lists.newArrayList();\n        Address baseAddr = program.getImageBase();\n        AddressSpace aSpace = program.getAddressFactory().getDefaultAddressSpace();\n        Memory mem = program.getMemory();\n        SymbolTable symbolTable = program.getSymbolTable();\n        \n        Map<String, Address> knownVTabAddrs = new HashMap<>();\n        Map<Address, Address> gotDataSyms = this.getGotDataSyms(program, elfProvider);\n        \n        if (gotDataSyms.isEmpty())\n        {\n            Msg.warn(this, \"Failed to locate vtables - No got data symbols found!\");\n            return out;\n        }\n        \n        Msg.info(this, \"Locating IPC vtables...\");\n        \n        // NOTE: We can't get the .<bla> block and check if it contains an address, as there may be multiple\n        // blocks with the same name, which Ghidra doesn't account for.\n        \n        // Locate some initial vtables based on RTTI\n        for (Address vtAddr : gotDataSyms.values()) \n        {\n            MemoryBlock vtBlock = mem.getBlock(vtAddr);\n            \n            // vtables are only found in the data block\n            if (vtBlock == null || !vtBlock.getName().equals(\".data\"))\n                continue;\n            \n            try\n            {\n                Address rttiAddr = aSpace.getAddress(mem.getLong(vtAddr.add(8)));\n                MemoryBlock rttiBlock = mem.getBlock(rttiAddr);\n                \n                // RTTI is only found in the data block\n                if (rttiBlock == null || !rttiBlock.getName().equals(\".data\"))\n                    continue;\n\n                Address thisAddr = aSpace.getAddress(mem.getLong(rttiAddr.add(0x8)));\n                MemoryBlock thisBlock = mem.getBlock(thisAddr);\n                \n                if (thisBlock == null || thisBlock.getName().equals(\".rodata\"))\n                    continue;\n\n                String symbol = elfProvider.getReader().readAsciiString(thisAddr.getOffset());\n\n                if (symbol.isEmpty() || symbol.length() > 512)\n                    continue;\n\n                if (symbol.contains(\"UnmanagedServiceObject\") || symbol.equals(\"N2nn2sf4cmif6server23CmifServerDomainManager6DomainE\"))\n                {\n                    knownVTabAddrs.put(symbol, vtAddr);\n                }\n            }\n            catch (MemoryAccessException e) // Skip entries with out of bounds offsets\n            {\n                continue;\n            }\n        }\n        \n        if (knownVTabAddrs.isEmpty())\n        {\n            Msg.warn(this, \"Failed to locate vtables - No known addresses found!\");\n            return out;\n        }\n            \n        // All IServiceObjects share a common non-overridable virtual function at vt + 0x20\n        // and thus that value can be used to distinguish a virtual table vs a non-virtual table.\n        // Here we locate the address of that function.\n        long knownAddress = 0;\n        \n        for (Address addr : knownVTabAddrs.values())\n        {\n            long curKnownAddr = mem.getLong(addr.add(0x20));\n            \n            if (knownAddress == 0)\n            {\n                knownAddress = curKnownAddr; \n            }\n            else if (knownAddress != curKnownAddr) \n                return out;\n        }\n        \n        Msg.info(this, String.format(\"Known service address: 0x%x\", knownAddress));\n        \n        // Use the known function to find all IPC vtables\n        for (Address vtAddr : gotDataSyms.values()) \n        {\n            MemoryBlock vtBlock = mem.getBlock(vtAddr);\n            \n            try\n            {\n                if (vtBlock != null && vtBlock.getName().equals(\".data\"))\n                {\n                    if (knownAddress == mem.getLong(vtAddr.add(0x20)))\n                    {\n                        out.add(vtAddr);\n                    }\n                }\n            }\n            catch (MemoryAccessException e) // Skip entries with out of bounds offsets\n            {\n                continue;\n            }\n        }\n        \n        return out;\n    }\n    \n    protected List<IPCVTableEntry> createVTableEntries(Program program, ElfCompatibilityProvider elfProvider, List<Address> vtAddrs) throws MemoryAccessException, AddressOutOfBoundsException, IOException\n    {\n        List<IPCVTableEntry> out = Lists.newArrayList();\n        Memory mem = program.getMemory();\n        AddressSpace aSpace = program.getAddressFactory().getDefaultAddressSpace();\n        \n        for (Address vtAddr : vtAddrs)\n        {\n            long vtOff = vtAddr.getOffset();\n            long rttiBase = mem.getLong(vtAddr.add(0x8));\n            String name = String.format(\"SRV_%X::vtable\", vtOff);\n            \n            // Attempt to find the name if the vtable has RTTI\n            if (rttiBase != 0)\n            {\n                Address rttiBaseAddr = aSpace.getAddress(rttiBase);\n                MemoryBlock rttiBaseBlock = mem.getBlock(rttiBaseAddr);\n                \n                // RTTI must be within the data block\n                if (rttiBaseBlock != null && rttiBaseBlock.getName().equals(\".data\"))\n                {\n                    Address thisAddr = aSpace.getAddress(mem.getLong(rttiBaseAddr.add(0x8)));\n                    MemoryBlock thisBlock = mem.getBlock(thisAddr);\n                    \n                    if (thisBlock != null && thisBlock.getName().equals(\".rodata\"))\n                    {\n                        String symbol = elfProvider.getReader().readAsciiString(thisAddr.getOffset());\n                        \n                        if (!symbol.isEmpty() && symbol.length() <= 512)\n                        {\n                            if (!symbol.startsWith(\"_Z\"))\n                                symbol = \"_ZTV\" + symbol;\n                            \n                            name = demangleIpcSymbol(symbol);\n                        }\n                    }\n                }\n            }\n            \n            Map<Address, Address> gotDataSyms = this.getGotDataSyms(program, elfProvider);\n            List<Address> implAddrs = new ArrayList<>();\n            long funcVtOff = 0x30;\n            long funcOff;\n            \n            // Find all ipc impl functions in the vtable\n            while ((funcOff = mem.getLong(vtAddr.add(funcVtOff))) != 0)\n            {\n                Address funcAddr = aSpace.getAddress(funcOff);\n                MemoryBlock funcAddrBlock = mem.getBlock(funcAddr);\n                \n                if (funcAddrBlock != null && funcAddrBlock.getName().equals(\".text\"))\n                {\n                    implAddrs.add(funcAddr);\n                    funcVtOff += 0x8;\n                }\n                else break;\n            \n                if (gotDataSyms.containsValue(vtAddr.add(funcVtOff)))\n                {\n                    break;\n                }\n            }\n            \n            Set<Address> uniqueAddrs = new HashSet<>(implAddrs);\n            \n            // There must be either 1 unique function without repeats, or more than one unique function with repeats allowed\n            if (uniqueAddrs.size() <= 1 && implAddrs.size() != 1)\n            {\n                Msg.warn(this, String.format(\"Insufficient unique addresses for vtable at 0x%X\", vtAddr.getOffset()));\n                \n                for (Address addr : uniqueAddrs)\n                {\n                    Msg.info(this, String.format(\"    Found: 0x%X\", addr.getOffset()));\n                }\n                \n                implAddrs.clear();\n            }\n            \n            // Some IPC symbols are very long and Ghidra crops them off far too early by default.\n            // Let's shorten these.\n            String shortName = shortenIpcSymbol(name);\n            \n            var entry = new IPCVTableEntry(name, shortName, vtAddr, implAddrs);\n            Msg.info(this, String.format(\"VTable Entry: %s @ 0x%X\", entry.abvName, entry.addr.getOffset()));\n            out.add(entry);\n        }\n        \n        return out;\n    }\n    \n    protected HashBiMap<Address, Address> locateSTables(Program program, ElfCompatibilityProvider elfProvider) throws MemoryAccessException {\n        HashBiMap<Address, Address> out = HashBiMap.create();\n        List<Pair<Long, Long>> candidates = new ArrayList<>();\n        AddressSpace aSpace = program.getAddressFactory().getDefaultAddressSpace();\n        Address baseAddr = program.getImageBase();\n        Memory mem = program.getMemory();\n        \n        for (NXRelocation reloc : elfProvider.getRelocations()) \n        {\n            if (reloc.addend > 0) {\n                candidates.add(new Pair<>(baseAddr.getOffset() + reloc.addend, baseAddr.getOffset() + reloc.offset));\n            }\n            else if (reloc.r_type == R_FAKE_RELR) {\n                reloc.addend = mem.getLong(baseAddr.add(reloc.offset)) - baseAddr.getOffset();\n                candidates.add(new Pair<>(baseAddr.getOffset() + reloc.addend, baseAddr.getOffset() + reloc.offset));\n            }\n        }\n        \n        candidates.sort(Comparator.comparing(a -> a.first));\n        \n        \n        // 5.x: match on the \"SFCI\" constant used in the template of s_Table\n        //   MOV  W?, #0x4653\n        //   MOVK W?, #0x4943, LSL#16\n        long movMask  = 0x5288CAL;\n        long movkMask = 0x72A928L;\n        \n        MemoryBlock text = mem.getBlock(\".text\"); // Text is one of the few blocks that isn't split\n        \n        try\n        {\n            for (long off = text.getStart().getOffset(); off < text.getEnd().getOffset() - 0x4; off += 0x4)\n            {\n                long val1 = (elfProvider.getReader().readUnsignedInt(off) & 0xFFFFFF00L) >> 8;\n                long val2 = (elfProvider.getReader().readUnsignedInt(off + 0x4) & 0xFFFFFF00L) >> 8;\n                \n                // Match on a sequence of MOV, MOVK\n                if (val1 == movMask && val2 == movkMask)\n                {\n                    long processFuncOffset = 0;\n                    long sTableOffset = 0;\n                    \n                    // Find the candidate after our offset, then pick the one before that\n                    for (Pair<Long, Long> candidate : candidates)\n                    {\n                        if (candidate.first > off)\n                            break;\n                        \n                        processFuncOffset = candidate.first;\n                        sTableOffset = candidate.second;\n                    }\n                    \n                    long pRetOff;\n                    \n                    // Make sure our SFCI offset is within the process function by matching on the\n                    // RET instruction\n                    for (pRetOff = processFuncOffset; pRetOff < text.getEnd().getOffset(); pRetOff += 0x4)\n                    {\n                        long rval = elfProvider.getReader().readUnsignedInt(pRetOff);\n                        \n                        // RET\n                        if (rval == 0xD65F03C0L)\n                            break;\n                    }\n                    \n                    if (pRetOff > off)\n                    {\n                        Address stAddr = aSpace.getAddress(sTableOffset);\n                        Address pFuncAddr = aSpace.getAddress(processFuncOffset);\n                        out.put(stAddr, pFuncAddr);\n                    }\n                }\n            }\n        }\n        catch (IOException e)\n        {\n            Msg.error(this, \"Failed to locate s_Tables\", e);\n        }\n        \n        return out;\n    }\n    \n    protected Multimap<Address, IPCTrace> emulateProcessFunctions(Program program, TaskMonitor monitor, Set<Address> procFuncAddrs)\n    {\n        Multimap<Address, IPCTrace> out = HashMultimap.create();\n        IPCEmulator ipcEmu = new IPCEmulator(program);\n        Set<Integer> cmdsToTry = Sets.newHashSet();\n        \n        // Bruteforce 0-1000\n        //for (int i = 0; i <= 1000; i++)\n            //cmdsToTry.add(i);\n        \n        // The rest we add ourselves. From SwIPC. Duplicates are avoided by using a set\n        int[] presets = new int[] { 0, 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, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 4201, 106, 107, 108, 4205, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 20501, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 2413, 8216, 150, 151, 2201, 2202, 2203, 2204, 2205, 2207, 10400, 2209, 8219, 8220, 8221, 30900, 30901, 30902, 8223, 90300, 190, 8224, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 220, 20701, 222, 223, 230, 231, 250, 251, 252, 2301, 2302, 255, 256, 10500, 261, 2312, 280, 290, 291, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 2101, 20800, 20801, 322, 323, 2102, 8250, 350, 2400, 2401, 2402, 2403, 2404, 2405, 10600, 10601, 2411, 2412, 2450, 2414, 8253, 10610, 2451, 2421, 2422, 2424, 8255, 2431, 8254, 2433, 2434, 406, 8257, 400, 401, 402, 403, 404, 405, 10300, 407, 408, 409, 410, 411, 2460, 20900, 8252, 412, 2501, 10700, 10701, 10702, 8200, 1106, 1107, 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511, 512, 513, 520, 521, 90200, 8201, 90201, 540, 30810, 542, 543, 544, 545, 546, 30811, 30812, 8202, 8203, 8291, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 8295, 620, 8204, 8296, 630, 105, 640, 4203, 8225, 2050, 109, 30830, 2052, 8256, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 8207, 20600, 8208, 49900, 751, 11000, 127, 8209, 800, 801, 802, 803, 804, 805, 806, 821, 822, 823, 824, 8211, 850, 851, 852, 7000, 2055, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 3000, 3001, 3002, 160, 8012, 8217, 8013, 320, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1020, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1061, 1062, 1063, 21000, 1100, 1101, 1102, 2053, 5202, 5203, 8218, 3200, 3201, 3202, 3203, 3204, 3205, 3206, 3207, 3208, 3209, 3210, 3211, 3214, 3215, 3216, 3217, 40100, 40101, 541, 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 8292, 547, 20500, 8293, 2054, 2601, 8294, 40200, 40201, 1300, 1301, 1302, 1303, 1304, 8227, 20700, 221, 8228, 8297, 8229, 4206, 1400, 1401, 1402, 1403, 1404, 1405, 1406, 1411, 1421, 1422, 1423, 1424, 30100, 30101, 30102, 1431, 1432, 30110, 30120, 30121, 1451, 1452, 1453, 1454, 1455, 1456, 1457, 1458, 1471, 1472, 1473, 1474, 1500, 1501, 1502, 1503, 1504, 1505, 2300, 30200, 30201, 30202, 30203, 30204, 30205, 30210, 30211, 30212, 30213, 30214, 30215, 30216, 30217, 260, 1600, 1601, 1602, 1603, 60001, 60002, 30300, 2051, 20100, 20101, 20102, 20103, 20104, 20110, 1700, 1701, 1702, 1703, 8222, 30400, 30401, 30402, 631, 20200, 20201, 1800, 1801, 1802, 1803, 2008, 10011, 30500, 7992, 7993, 7994, 7995, 7996, 7997, 7998, 7999, 8000, 8001, 8002, 8011, 20300, 20301, 8021, 1900, 1901, 1902, 6000, 6001, 6002, 10100, 10101, 10102, 10110, 30820, 321, 1941, 1951, 1952, 1953, 8100, 20400, 20401, 8210, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 10200, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 10211, 2020, 2021, 30700, 2030, 2031, 8251, 90100, 90101, 90102 };\n        \n        for (int preset : presets)\n            cmdsToTry.add(preset);\n        \n        Multimap<Address, IPCTrace> map = HashMultimap.create();\n        \n        int maxProgress = procFuncAddrs.size() * cmdsToTry.size();\n        int progress = 0;\n        \n        monitor.setMessage(\"Emulating IPC process functions...\");\n        monitor.initialize(maxProgress);\n        \n        for (Address procFuncAddr : procFuncAddrs)\n        {\n            for (int cmd : cmdsToTry)\n            {\n                IPCTrace trace = ipcEmu.emulateCommand(procFuncAddr, cmd);\n                \n                if (trace.hasDescription())\n                    map.put(procFuncAddr, trace);\n            \n                progress++;\n                monitor.setProgress(progress);\n            }\n        }\n        \n        // Recreate the map as we can't sort the original\n        for (Address procFuncAddr : map.keySet())\n        {\n            List<IPCTrace> traces = Lists.newArrayList(map.get(procFuncAddr).iterator());\n            \n            traces.sort(Comparator.comparingLong(a -> a.cmdId));\n            \n            for (IPCTrace trace : traces)\n            {\n                out.put(procFuncAddr, trace);\n            }\n        }\n        \n        return out;\n    }\n    \n    protected HashBiMap<Address, IPCVTableEntry> matchVtables(List<IPCVTableEntry> vtEntries, Set<Address> procFuncAddrs, Multimap<Address, IPCTrace> processFuncTraces)\n    {\n        // Map process func addrs to vtable addrs\n        HashBiMap<Address, IPCVTableEntry> out = HashBiMap.create();\n        List<IPCVTableEntry> possibilities = Lists.newArrayList(vtEntries.iterator());\n        \n        for (Address procFuncAddr : procFuncAddrs)\n        {\n            // We've already found this address. No need to do it again\n            if (out.containsKey(procFuncAddr))\n                continue;\n            \n            List<IPCVTableEntry> filteredPossibilities = possibilities.stream().filter(vtEntry -> vtEntry.ipcFuncs.size() == getProcFuncVTableSize(processFuncTraces, procFuncAddr)).collect(Collectors.toList());\n            \n            // See if there is a single entry that *exactly* matches the vtable size\n            if (filteredPossibilities.size() == 1)\n            {\n                IPCVTableEntry vtEntry = filteredPossibilities.get(0);\n                out.put(procFuncAddr, vtEntry);\n                possibilities.remove(vtEntry);\n                continue;\n            }\n            \n            filteredPossibilities = possibilities.stream().filter(vtEntry -> vtEntry.ipcFuncs.size() >= getProcFuncVTableSize(processFuncTraces, procFuncAddr)).collect(Collectors.toList());\n\n            // See if there is a single entry that is equal to or greater than the vtable size\n            if (filteredPossibilities.size() == 1)\n            {\n                IPCVTableEntry vtEntry = filteredPossibilities.get(0);\n                out.put(procFuncAddr, vtEntry);\n                possibilities.remove(vtEntry);\n                continue;\n            }\n            \n            // Iterate over all the possible vtables with a size greater than our current process function\n            for (IPCVTableEntry filteredPossibility : filteredPossibilities)\n            {\n                List<Address> unlocatedProcFuncAddrs = procFuncAddrs.stream().filter(pFAddr -> !out.containsKey(pFAddr)).toList();\n                \n                // See if there is only a single trace set of size <= this vtable\n                // For example, if the process func vtable size is found by emulation to be 0x100, and we have previously found vtables of the following sizes, which have yet to be located:\n                // 0x10, 0x20, 0x60, 0x110, 0x230\n                // We will run this loop for both 0x110 and 0x230. \n                // In the case of 0x110, we will then filter for sizes <= 0x110. These are 0x10, 0x20, 0x60 and 0x110\n                // As there are four of these, the check will fail.\n                if (unlocatedProcFuncAddrs.stream().filter(unlocatedProcFuncAddr -> getProcFuncVTableSize(processFuncTraces, unlocatedProcFuncAddr) <= filteredPossibility.ipcFuncs.size()).count() == 1)\n                {\n                    out.put(procFuncAddr, filteredPossibility);\n                    possibilities.remove(filteredPossibility);\n                    break;\n                }\n            }\n        }\n        \n        List<Address> unlocatedProcFuncAddrs = procFuncAddrs.stream().filter(pFAddr -> !out.containsKey(pFAddr)).toList();\n        \n        for (Address addr : unlocatedProcFuncAddrs)\n        {\n            Msg.info(this, String.format(\"Unmatched process func at 0x%X. Calculated VTable Size: 0x%X\", addr.getOffset(), getProcFuncVTableSize(processFuncTraces, addr)));\n        }\n        \n        for (IPCVTableEntry entry : possibilities)\n        {\n            Msg.info(this, String.format(\"Unmatched IPC VTable entry at 0x%X. VTable Size: 0x%X\", entry.addr.getOffset(), entry.ipcFuncs.size()));\n        }\n        \n        return out;\n    }\n    \n    protected void markupIpc(Program program, TaskMonitor monitor, List<IPCVTableEntry> vtEntries, HashBiMap<Address, Address> sTableProcessFuncMap, Multimap<Address, IPCTrace> processFuncTraces, HashBiMap<Address, IPCVTableEntry> procFuncVtMap)\n    {\n        AddressSpace aSpace = program.getAddressFactory().getDefaultAddressSpace();\n        \n        try\n        {\n            // Analyze and label any IPC info found\n            for (IPCVTableEntry entry : vtEntries)\n            {\n                List<IPCTrace> ipcTraces = Lists.newArrayList();\n                Address processFuncAddr = procFuncVtMap.inverse().get(entry);\n                \n                if (processFuncAddr != null)\n                {\n                    Address sTableAddr = sTableProcessFuncMap.inverse().get(processFuncAddr);\n                    String ipcComment = \"\"                +\n                            \"IPC INFORMATION\\n\"           +\n                            \"s_Table Address:       0x%X\";\n                    \n                    if (sTableAddr != null)\n                    {\n                        ipcComment = String.format(ipcComment, sTableAddr.getOffset());\n                        program.getListing().setComment(entry.addr, CommentType.PLATE, ipcComment);\n                    }\n                    \n                    ipcTraces = Lists.newArrayList(processFuncTraces.get(processFuncAddr).iterator());\n                }\n                    \n                String entryNameNoSuffix = entry.abvName.replace(\"::vtable\", \"\");\n                \n                // Set the vtable name\n                if (!this.hasImportedSymbol(program, entry.addr))\n                {\n                    // For shortened names, leave a comment so the user knows what the original name is\n                    if (!entry.fullName.equals(entry.abvName))\n                        program.getListing().setComment(entry.addr, CommentType.REPEATABLE, entry.fullName);\n                    \n                    Msg.info(this, String.format(\"Creating label for %s @ 0x%X\", entry.abvName, entry.addr.getOffset()));\n                    program.getSymbolTable().createLabel(entry.addr, entry.abvName, null, SourceType.IMPORTED);\n                }\n                \n                // Label the four functions that exist for all ipc vtables\n                for (int i = 0; i < 4; i++)\n                {\n                    Address vtAddr = entry.addr.add(0x10 + i * 0x8);\n                    String name = \"\";\n                    \n                    // Set vtable func data types to pointers\n                    this.createPointer(program, vtAddr);\n\n                    switch (i) {\n                        case 0 -> name = \"AddReference\";\n                        case 1 -> name = \"Release\";\n                        case 2 -> name = \"GetProxyInfo\";\n                        // Shared by everything\n                        case 3 -> name = \"nn::sf::IServiceObject::GetInterfaceTypeInfo\";\n                    }\n                             \n                    if (i == 3) // For now, only label GetInterfaceTypeInfo. We need better heuristics for the others as they may be shared.\n                    {\n                        Address funcAddr = aSpace.getAddress(program.getMemory().getLong(vtAddr));\n                        \n                        if (!this.hasImportedSymbol(program, funcAddr))\n                            program.getSymbolTable().createLabel(funcAddr, name, null, SourceType.IMPORTED);\n                    }\n                    else\n                    {\n                        program.getListing().setComment(vtAddr, CommentType.REPEATABLE, name);\n                    }\n                }\n                \n                for (int i = 0; i < entry.ipcFuncs.size(); i++)\n                {\n                    Address func = entry.ipcFuncs.get(i);\n                    String name = null;\n    \n                    // Set vtable func data types to pointers\n                    this.createPointer(program, entry.addr.add(0x30 + i * 0x8L));\n                }\n                \n                for (IPCTrace trace : ipcTraces)\n                {\n                    // Safety precaution. I *think* these should've been filtered out earlier though.\n                    if (trace.vtOffset == -1 || !trace.hasDescription())\n                        continue;\n                    \n                    Address vtOffsetAddr = entry.addr.add(0x10 + trace.vtOffset);\n                    Address ipcCmdImplAddr = aSpace.getAddress(program.getMemory().getLong(vtOffsetAddr));\n                    \n                    if (!this.hasImportedSymbol(program, ipcCmdImplAddr))\n                        program.getSymbolTable().createLabel(ipcCmdImplAddr, String.format(\"%s::Cmd%d\", entryNameNoSuffix, trace.cmdId), null, SourceType.IMPORTED);\n                    \n                    String implComment = \"\"\"\n                            IPC INFORMATION\n                            Bytes In:       0x%X\n                            Bytes Out:      0x%X\n                            Buffer Count:   0x%X\n                            In Interfaces:  0x%X\n                            Out Interfaces: 0x%X\n                            In Handles:     0x%X\n                            Out Handles:    0x%X\n                            \"\"\";\n                    \n                    implComment = String.format(implComment, trace.bytesIn, trace.bytesOut, trace.bufferCount, trace.inInterfaces, trace.outInterfaces, trace.inHandles, trace.outHandles);\n                    program.getListing().setComment(ipcCmdImplAddr, CommentType.PLATE, implComment);\n                }\n            }\n            \n            // Annotate s_Tables\n            for (Address stAddr : sTableProcessFuncMap.keySet())\n            {\n                this.createPointer(program, stAddr);\n                \n                if (!this.hasImportedSymbol(program, stAddr))\n                {\n                    Address procFuncAddr = sTableProcessFuncMap.get(stAddr);\n                    String sTableName = String.format(\"SRV_S_TAB_%X\", stAddr.getOffset());\n                    \n                    if (procFuncAddr != null)\n                    {\n                        IPCVTableEntry entry = procFuncVtMap.get(procFuncAddr);\n                        \n                        if (entry != null)\n                        {\n                            String entryNameNoSuffix = entry.abvName.replace(\"::vtable\", \"\");\n                            sTableName = entryNameNoSuffix + \"::s_Table\";\n                        }\n                    }\n                    \n                    program.getSymbolTable().createLabel(stAddr, sTableName, null, SourceType.IMPORTED);\n                }\n            }\n        }\n        catch (InvalidInputException | AddressOutOfBoundsException | MemoryAccessException e)\n        {\n            Msg.error(this, \"Failed to markup IPC\", e);\n        }\n    }\n    \n    protected int getProcFuncVTableSize(Multimap<Address, IPCTrace> processFuncTraces, Address procFuncAddr)\n    {\n        if (!processFuncTraces.containsKey(procFuncAddr) || processFuncTraces.get(procFuncAddr).isEmpty())\n            return 0;\n        \n        IPCTrace maxTrace = null;\n        \n        for (IPCTrace trace : processFuncTraces.get(procFuncAddr))\n        {\n            if (trace.vtOffset == -1)\n                continue;\n            \n            if (maxTrace == null || trace.vtOffset > maxTrace.vtOffset)\n                maxTrace = trace;\n        }\n        \n        return (int)Math.max(processFuncTraces.get(procFuncAddr).size(), (maxTrace.vtOffset + 8 - 0x20) / 8);\n    }\n    \n    private Map<Address, Address> gotDataSyms = null;\n    \n    /**\n     * A map of relocated entries in the global offset table to their new values.\n     */\n    protected Map<Address, Address> getGotDataSyms(Program program, ElfCompatibilityProvider elfProvider) throws MemoryAccessException {\n        if (gotDataSyms != null)\n            return this.gotDataSyms;\n        \n        Address baseAddr = program.getImageBase();\n        gotDataSyms = new HashMap<>();\n        MemoryBlock gotBlock = program.getMemory().getBlock(\".got\");\n        \n        for (NXRelocation reloc : elfProvider.getRelocations()) \n        {\n            if (baseAddr.add(reloc.offset).getOffset() < gotBlock.getStart().getOffset() || baseAddr.add(reloc.offset).getOffset() > gotBlock.getEnd().getOffset() + 1)\n            {\n                continue;\n            }\n\n            long off;\n\n            if (reloc.r_type == R_FAKE_RELR) {\n                reloc.addend = program.getMemory().getLong(baseAddr.add(reloc.offset)) - baseAddr.getOffset();\n            }\n            \n            if (reloc.sym != null && reloc.sym.getSectionHeaderIndex() != ElfSectionHeaderConstants.SHN_UNDEF && reloc.sym.getValue() == 0)\n            {\n                off = reloc.sym.getValue();\n            }\n            else if (reloc.addend != 0)\n            {\n                off = reloc.addend;\n            }\n            else continue;\n            \n            // Target -> Value\n           this.gotDataSyms.put(baseAddr.add(reloc.offset), baseAddr.add(off));\n        }\n        \n        return gotDataSyms;\n    }\n    \npublic static String demangleIpcSymbol(String mangled)\n{\n    // Needed by the demangler\n    if (!mangled.startsWith(\"_Z\"))\n        mangled = \"_Z\" + mangled;\n \n    String out = mangled;\n    \n    try {\n        // Use the new API: demangle(Program, String, Address)\n        // Pass null for Program and Address since we're in a static method\n        // This returns a List<DemangledObject>\n        List<DemangledObject> demangledObjects = DemanglerUtil.demangle(null, mangled, null);\n        \n        // Use the first result if available\n        if (demangledObjects != null && !demangledObjects.isEmpty())\n        {\n            DemangledObject demangledObj = demangledObjects.get(0);\n            StringBuilder builder = new StringBuilder(demangledObj.toString());\n            int templateLevel = 0;\n            \n            //De-Ghidrify-template colons\n            for (int i = 0; i < builder.length(); ++i) \n            {\n                char ch = builder.charAt(i);\n                \n                if (ch == '<') \n                {\n                    ++templateLevel;\n                }\n                else if (ch == '>' && templateLevel != 0) \n                {\n                    --templateLevel;\n                }\n\n                if (templateLevel > 0 && ch == '-') \n                    builder.setCharAt(i, ':');\n            }\n            \n            out = builder.toString();\n        }\n    } catch (Exception e) {\n        // If demangling fails, just return the mangled name\n        // This prevents crashes if the demangler encounters unexpected input\n    }\n    \n    return out;\n}\n    \n    public static String shortenIpcSymbol(String longSym)\n    {\n        String out = longSym;\n        String suffix = out.substring(out.lastIndexOf(':') + 1);\n        \n        if (out.startsWith(\"nn::sf::detail::ObjectImplFactoryWithStatelessAllocator<\") || out.startsWith(\"nn::sf::detail::ObjectImplFactoryWithStatefulAllocator<\"))\n        {\n            String abvNamePrefixOld = \"nn::sf::detail::EmplacedImplHolder<\";\n            String abvNamePrefixNew = \"_tO2N<\";\n            \n            int abvNamePrefixOldIndex = out.indexOf(abvNamePrefixOld);\n            int abvNamePrefixNewIndex = out.indexOf(abvNamePrefixNew);\n            \n            if (abvNamePrefixOldIndex != -1)\n            {\n                int abvNameStart = abvNamePrefixOldIndex + abvNamePrefixOld.length();\n                out = out.substring(abvNameStart, out.indexOf(',', abvNameStart));\n            }\n            else if (abvNamePrefixNewIndex != -1)\n            {\n                int abvNameStart = abvNamePrefixNewIndex + abvNamePrefixNew.length();\n                out = out.substring(abvNameStart, out.indexOf('>', abvNameStart));\n            }\n            \n            out += \"::\" + suffix;\n        }\n\n        return out;\n    }\n    \n    public boolean hasImportedSymbol(Program program, Address addr)\n    {\n        for (Symbol sym : program.getSymbolTable().getSymbols(addr))\n        {\n            if (sym.getSource() == SourceType.IMPORTED)\n                return true;\n        }\n        \n        return false;\n    }\n    \n    protected int createPointer(Program program, Address address)\n    {\n        Data d = program.getListing().getDataAt(address);\n        \n        if (d == null) \n        {\n            try \n            {\n                d = program.getListing().createData(address, PointerDataType.dataType, 8);\n            } \n            catch (CodeUnitInsertionException e)\n            {\n                Msg.error(this, String.format(\"Failed to create pointer at 0x%X\", address.getOffset()), e);\n            }\n        }\n        \n        return d.getLength();\n    }\n    \n    public static class IPCVTableEntry\n    {\n        public final String fullName;\n        public final String abvName;\n        public final Address addr;\n        public final ImmutableList<Address> ipcFuncs;\n        \n        private IPCVTableEntry(String fullName, String abvName, Address addr, List<Address> ipcFuncs)\n        {\n            this.fullName = fullName;\n            this.abvName = abvName;\n            this.addr = addr;\n            this.ipcFuncs = ImmutableList.copyOf(ipcFuncs);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/analyzer/ipc/IPCEmulator.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.analyzer.ipc;\n\nimport adubbz.nx.util.ByteUtil;\nimport ghidra.app.plugin.processors.sleigh.SleighLanguage;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.app.util.bin.ByteArrayProvider;\nimport ghidra.pcode.emulate.BreakCallBack;\nimport ghidra.pcode.emulate.BreakTableCallBack;\nimport ghidra.pcode.emulate.Emulate;\nimport ghidra.pcode.error.LowlevelError;\nimport ghidra.pcode.memstate.*;\nimport ghidra.pcode.utils.Utils;\nimport ghidra.program.database.ProgramDB;\nimport ghidra.program.database.code.CodeManager;\nimport ghidra.program.disassemble.Disassembler;\nimport ghidra.program.model.address.Address;\nimport ghidra.program.model.lang.InstructionPrototype;\nimport ghidra.program.model.lang.OperandType;\nimport ghidra.program.model.lang.Register;\nimport ghidra.program.model.listing.Instruction;\nimport ghidra.program.model.listing.Program;\nimport ghidra.program.model.mem.Memory;\nimport ghidra.program.model.mem.MemoryAccessException;\nimport ghidra.program.model.mem.MemoryBlock;\nimport ghidra.util.Msg;\nimport ghidra.util.exception.CancelledException;\nimport ghidra.util.task.TaskMonitor;\nimport ghidra.util.task.TaskMonitorAdapter;\nimport org.apache.commons.compress.utils.Lists;\n\nimport java.nio.ByteBuffer;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\npublic class IPCEmulator \n{\n    private Program program;\n    public boolean hasSetup;\n    \n    private SleighLanguage sLang;\n    private MemoryState state;\n    private MemoryBank ramBank;\n    private MemoryBank registerBank;\n    private BreakTableCallBack bTable;\n    private Emulate emu;\n    private Disassembler disassembler;\n    \n    private List<Consumer<Long>> instructionHandlers = Lists.newArrayList();\n    \n    private long messageSize;\n    private long messagePtr;\n    \n    private long messageStructPtr;\n    private long targetObjectPtr;\n    private long ipcObjectPtr;\n    \n    private long retInstructionPtr;\n    private long inObjectVtablePtr;\n    private long inObjectPtr;\n    \n    private long bufferSize;\n    private long bufferMemory;\n    private long outputMemory;\n    \n    private IPCTrace currentTrace;\n    \n    public IPCEmulator(Program program)\n    {\n        this.program = program;\n    \n        try \n        {\n            this.setup();\n            this.hasSetup = true;\n        }\n        catch (MemoryAccessException e) \n        {\n            Msg.error(this, \"Failed to setup IPC emulator\");\n        }\n    }\n    \n    public void setup() throws MemoryAccessException\n    {\n        MemoryFaultHandler faultHandler = new MemoryFaultHandler()\n        {\n\n            @Override\n            public boolean uninitializedRead(Address address, int size, byte[] buf, int bufOffset) \n            {\n                Emulate emu = IPCEmulator.this.emu;\n                long pc = IPCEmulator.this.state.getValue(\"pc\");\n                \n                if (address.isRegisterAddress())\n                {\n                    for (Register reg : sLang.getRegisters(address))\n                    {\n                        Msg.info(this, String.format(\"Uninitialized read from register %s at 0x%X\", reg.getName(), address.getOffset()));\n                    }\n                }\n                \n                return false;\n            }\n\n            @Override\n            public boolean unknownAddress(Address address, boolean write) \n            {\n                Msg.info(this, String.format(\"Unknwon address 0x%X\", address.getOffset()));\n                return false;\n            }\n        };\n        \n        this.sLang = (SleighLanguage)this.program.getLanguage();\n        this.state = new DefaultMemoryState(this.sLang);\n        \n        // Create banks for ram and registers and add them to our state\n        this.ramBank = new MemoryPageBank(this.sLang.getAddressFactory().getDefaultAddressSpace(), false, 4096, faultHandler);\n        this.registerBank = new MemoryPageBank(this.sLang.getAddressFactory().getRegisterSpace(), false, 4096, faultHandler);\n        this.state.setMemoryBank(this.ramBank);\n        this.state.setMemoryBank(this.registerBank);\n        \n        this.bTable = new BreakTableCallBack(this.sLang);\n        this.emu = new Emulate(this.sLang, this.state, this.bTable);\n        this.disassembler = Disassembler.getDisassembler(this.program, TaskMonitorAdapter.DUMMY, null);\n        \n        // Copy over our binary to the emulator's memory, typically 7100000000\n        Memory programMemory = this.program.getMemory();\n        byte[] programBytes = new byte[(int)programMemory.getMaxAddress().getOffset()+1];\n        \n        // Copy memory blocks manually. The entire thing can't be copied at once because there are gaps between segments\n        for (MemoryBlock block : programMemory.getBlocks())\n        {\n            if (block.isInitialized())\n            {\n                byte[] blockBytes = new byte[(int)block.getSize()];\n                int copiedBytes = programMemory.getBytes(block.getStart(), blockBytes);\n                \n                if (copiedBytes != blockBytes.length)\n                    throw new RuntimeException(String.format(\"Failed to copy bytes from 0x%X of size 0x%x!\", block.getStart().getOffset(), block.getSize()));\n                \n                // Copy the block bytes to the program bytes\n                long blockOff = block.getStart().getOffset() - this.program.getImageBase().getOffset();\n                System.arraycopy(blockBytes, 0, programBytes, (int)blockOff, (int)block.getSize());\n            }\n        }\n            \n        state.setChunk(programBytes, sLang.getAddressFactory().getDefaultAddressSpace(), this.program.getImageBase().getOffset(), programBytes.length);\n        \n        // Initialize GPRs\n        for (int i = 0; i <= 30; i++)\n            state.setValue(\"x\" + i, 0);\n        \n        // Stack is from 0x100000000-0x100002000\n        state.setValue(\"sp\", 0x100002000L);\n        \n        // Allocate IPC message data.\n        // We set it later\n        this.messageSize = 0x1010;\n        this.messagePtr = this.calloc(messageSize);\n        \n        this.messageStructPtr = this.calloc(0x10);\n        this.setLong(messageStructPtr, messagePtr); // Message ptr\n        this.setLong(messageStructPtr + 0x8, messageSize); // Message length\n        \n        // Create the ipc vtable and object\n        long ipcVtableSize = 0x8 * 11;\n        long ipcVtablePtr = this.calloc(ipcVtableSize);\n        this.setLong(ipcVtablePtr,        this.createFunctionPointer(this::PrepareForProcess));\n        this.setLong(ipcVtablePtr + 0x8,  this.createFunctionPointer(this::OverwriteClientProcessId));\n        this.setLong(ipcVtablePtr + 0x10, this.createFunctionPointer(this::GetBuffers));\n        this.setLong(ipcVtablePtr + 0x18, this.createFunctionPointer(this::GetInNativeHandles));\n        this.setLong(ipcVtablePtr + 0x20, this.createFunctionPointer(this::GetInObjects));\n        this.setLong(ipcVtablePtr + 0x28, this.createFunctionPointer(this::BeginPreparingForReply));\n        this.setLong(ipcVtablePtr + 0x30, this.createFunctionPointer(this::SetBuffers));\n        this.setLong(ipcVtablePtr + 0x38, this.createFunctionPointer(this::SetOutObjects));\n        this.setLong(ipcVtablePtr + 0x40, this.createFunctionPointer(this::SetOutNativeHandles));\n        this.setLong(ipcVtablePtr + 0x48, this.createFunctionPointer(this::BeginPreparingForErrorReply));\n        this.setLong(ipcVtablePtr + 0x50, this.createFunctionPointer(this::EndPreparingForReply));\n        this.ipcObjectPtr = this.calloc(0x10);\n        this.setLong(this.ipcObjectPtr, ipcVtablePtr);\n        \n        // Create the target function vtable\n        long targetFuncVtableSize = 0x8 * 512;\n        long targetFuncVtablePtr = this.calloc(targetFuncVtableSize);\n        \n        // Create 512 function pointers, each with a different offset within the target function vtable.\n        // This allows us to figure out the vtable offsets for each command id.\n        for (long off = 0; off < targetFuncVtableSize; off += 8)\n        {\n            final long off2 = off; // Lambdas require this to be final\n            long targetFuncPtr = this.createFunctionPointer(() -> this.targetFunction(off2));\n            \n            long targetFuncVtableOff = targetFuncVtablePtr + off; // Where to put the function pointer within the target func vtable\n            this.setLong(targetFuncVtableOff, targetFuncPtr);\n        }\n        \n        this.targetObjectPtr = this.calloc(0x10);\n        this.setLong(this.targetObjectPtr, targetFuncVtablePtr);\n        \n        this.retInstructionPtr = this.calloc(0x4);\n        this.setInt(this.retInstructionPtr, 0xd65f03c0);\n        this.inObjectVtablePtr = this.calloc(0x8 * 16);\n        \n        // Set up the in object vtable\n        for (int i = 0; i < 16; i++)\n        {\n            this.setLong(this.inObjectVtablePtr + i * 0x8, this.retInstructionPtr);\n        }\n        \n        this.inObjectPtr = this.calloc(0x8 + 8 * 16);\n        this.setLong(this.inObjectPtr, this.inObjectVtablePtr);\n        \n        this.bufferSize = 0x1000;\n        this.bufferMemory = this.calloc(this.bufferSize);\n        this.outputMemory = this.calloc(0x1000);\n    }\n    \n    public IPCTrace emulateCommand(Address procFuncAddr, int cmd)\n    {\n        if (!this.hasSetup)\n            return null;\n        \n        // Some commands have in-dispatcher validation. If we fail this\n        // validation we miss some information about vtable offsets and\n        // returned objects. This tries to brute-force until we find an\n        // input that passes that validation.\n\n        int[] bufferSizes = new int[] { 128, 33, 1 };\n        ByteBuffer nonZeroBuf = ByteBuffer.allocate(0x8 * 6);\n        \n        for (int i = 0; i < 6; i++) nonZeroBuf.putLong(1);\n        \n        // All-zeros, standard buffer size\n        IPCTrace trace = this.emulateCommand(procFuncAddr, cmd, null, 0x1000);\n        \n        if (trace.isCorrect())\n            return trace;\n            \n        // Pass checks for non-zero inline data\n        trace = this.emulateCommand(procFuncAddr, cmd, nonZeroBuf.array(), 0x1000);\n\n        if (trace.isCorrect())\n            return trace;\n        \n        // All-zeros, Pass buffer size checks\n        for (int bufSize : bufferSizes)\n        {\n            trace = this.emulateCommand(procFuncAddr, cmd, null, bufSize);\n            \n            if (trace.isCorrect())\n                return trace;\n        }\n        \n        nonZeroBuf = ByteBuffer.allocate(0x8 * 4);\n        for (int i = 0; i < 4; i++) nonZeroBuf.putLong(1);\n\n        // Pass checks for buffer size, and non-zero inline data\n        for (int bufSize : bufferSizes)\n        {\n            trace = this.emulateCommand(procFuncAddr, cmd, nonZeroBuf.array(), bufSize);\n            \n            if (trace.isCorrect())\n                return trace;\n        }\n        \n        Msg.warn(this, String.format(\"Warning: unable to brute-force validation on dispatch_func %X command %d\", procFuncAddr.getOffset(), cmd));\n        return trace;\n    }\n    \n    public IPCTrace emulateCommand(Address procFuncAddr, int cmd, byte[] data, int bufferSize)\n    {\n        if (!this.hasSetup)\n            return null;\n        \n        // We allocate a fixed 0x1000 for the buffer. Therefore we only allow adjustments to less than or equal\n        // to that.\n        if (bufferSize < 0 || bufferSize > 0x1000)\n            throw new RuntimeException(\"Invalid buffer size provided\");\n        \n        this.bufferSize = bufferSize;\n        \n        this.instructionHandlers.clear(); // Clear any existing instruction handlers\n        this.currentTrace = new IPCTrace(cmd, procFuncAddr.getOffset());\n        \n        // Clear out any existing IPC message data\n        byte[] zeros = new byte[(int)this.messageSize];\n        this.state.setChunk(zeros, this.sLang.getDefaultSpace(), this.messagePtr, zeros.length);\n        \n        this.setLong(messagePtr, 0x49434653); // IPC Magic\n        this.setLong(messagePtr + 0x8, cmd); // Cmd ID\n        \n        if (data != null && data.length > 0)\n            this.state.setChunk(data, this.sLang.getDefaultSpace(), this.messagePtr + 0x10, data.length);\n        \n        // Set registers to point to our objects.\n        // We need to do this each time to reset the state\n        this.state.setValue(\"x0\", this.targetObjectPtr);\n        this.state.setValue(\"x1\", this.ipcObjectPtr);\n        this.state.setValue(\"x2\", this.messageStructPtr);\n        \n        // Set to the start of the process function\n        emu.setExecuteAddress(procFuncAddr);\n        \n        // Disassemble the proc function so we can get instructions from it\n        disassembler.disassemble(procFuncAddr, null);\n        \n        while (true)\n        {\n            long pc = state.getValue(\"pc\");\n            Address pcAddr = this.sLang.getDefaultSpace().getAddress(pc);\n            \n            // Pre-process instructions\n            for (Consumer<Long> instructionHandler : this.instructionHandlers)\n            {\n                instructionHandler.accept(pc);\n            }\n            \n            if (emu.getExecuteAddress().getOffset() == 0)\n            {\n                break;\n            }\n                \n            try \n            {\n                emu.executeInstruction(true, TaskMonitor.DUMMY);\n            } \n            catch (CancelledException | LowlevelError e) \n            {\n                e.printStackTrace();\n            }\n        }\n        \n        return this.currentTrace;\n    }\n    \n    /**\n     * Memory utils \n     */\n    \n    private static final long HEAP_START = 0x200000000L;\n    private static final long HEAP_SIZE = 0x100000L;\n    private long nextHeapPtr = HEAP_START;\n    \n    private static final long FUNC_PTR_START = 0x400000000L;\n    private long nextFuncPtr = FUNC_PTR_START;\n    \n    private long allocate(long size)\n    {\n        long available = (HEAP_START + HEAP_SIZE) - nextHeapPtr;\n        // Align to 0x10\n        long allocationSize = (size + 0xF) & ~0xF;\n        \n        if (allocationSize > available)\n            throw new RuntimeException(String.format(\"Could not allocate 0x%X bytes\", allocationSize));\n        \n        long start = nextHeapPtr;\n        nextHeapPtr += allocationSize;\n        return start;\n    }\n    \n    private long calloc(long size)\n    {\n        long start = allocate(size);\n        byte[] vals = new byte[(int)size];\n        this.state.setChunk(vals, this.sLang.getDefaultSpace(), start, vals.length);\n        return start;\n    }\n    \n    private long createFunctionPointer(Supplier<Boolean> func)\n    {\n        long start = nextFuncPtr;\n        nextFuncPtr += 0x8;\n        \n        BreakCallBack callBack = new BreakCallBack()\n        {\n            @Override\n            public boolean addressCallback(Address addr) \n            {\n                if (addr.getOffset() == start)\n                {\n                    if (!func.get())\n                    {\n                        // Failed, halt execution.\n                        IPCEmulator.this.emu.setExecuteAddress(addr.getNewAddress(0));\n                        //Msg.info(this, \"HLE function returned false, halting further execution...\");\n                    }\n                    \n                    // Don't execute the instruction. We've handled it\n                    return true;\n                }\n                \n                // Execute the instruction\n                return false;\n            }\n        };\n        \n        this.bTable.registerAddressCallback(this.sLang.getDefaultSpace().getAddress(start), callBack);\n        return start;\n    }\n    \n    private void setLong(long off, long val)\n    {\n        this.state.setChunk(Utils.longToBytes(val, 0x8, this.sLang.isBigEndian()), this.sLang.getDefaultSpace(), off, 0x8);\n    }\n    \n    private void setInt(long off, long val)\n    {\n        this.state.setChunk(Utils.longToBytes(val, 0x4, this.sLang.isBigEndian()), this.sLang.getDefaultSpace(), off, 0x4);\n    }\n    \n    private void printMemory(long off, long size)\n    {\n        Msg.info(this, String.format(\"0x%X (0x%X bytes):\", off, size));\n        byte[] out = new byte[(int)size];\n        this.state.getChunk(out, this.sLang.getDefaultSpace(), off, out.length, true);\n        ByteUtil.logBytes(out);\n    }\n    \n    /**\n     * HLE function utils \n     */\n    \n    private void returnFromFunc(long value)\n    {\n        this.state.setValue(\"x0\", value);\n        // x30 = lr\n        this.emu.setExecuteAddress(this.sLang.getDefaultSpace().getAddress(this.state.getValue(\"x30\")));\n    }\n    \n    /**\n     * HLE-ed functions.\n     * If these return false, the emulation will halt.\n     */\n    \n    private boolean targetFunction(long offset)\n    {\n        this.currentTrace.vtOffset = offset;\n        this.returnFromFunc(0);\n        return true;\n    }\n    \n    private boolean PrepareForProcess()\n    {\n        long metaInfoPtr = this.state.getValue(\"x1\");\n        long metaInfoSize = 0x90;\n        byte[] metaInfo = new byte[(int)metaInfoSize];\n        \n        if (metaInfoSize != this.state.getChunk(metaInfo, this.sLang.getDefaultSpace(), metaInfoPtr, metaInfo.length, true))\n            throw new RuntimeException(\"Failed to read meta info\");\n        \n        BinaryReader reader = new BinaryReader(new ByteArrayProvider(metaInfo), true);\n        \n        try\n        {\n            this.currentTrace.bytesIn = reader.readInt(0x8) - 0x10;\n            this.currentTrace.bytesOut = reader.readInt(0x10) - 0x10;\n            this.currentTrace.bufferCount = reader.readInt(0x18);\n            this.currentTrace.inInterfaces = reader.readInt(0x1C);\n            this.currentTrace.outInterfaces = reader.readInt(0x20);\n            this.currentTrace.inHandles = reader.readInt(0x24);\n            this.currentTrace.outHandles = reader.readInt(0x28);\n            \n            if (this.currentTrace.bytesIn < 0 || this.currentTrace.bytesIn > 0x1000L)\n                throw new RuntimeException(\"Invalid value for bytesIn!\");\n            \n            if (this.currentTrace.bytesOut < 0 || this.currentTrace.bytesOut > 0x1000L)\n                throw new RuntimeException(\"Invalid value for bytesOut!\");\n            \n            if (this.currentTrace.bufferCount > 20)\n                throw new RuntimeException(\"Too many buffers!\");\n            \n            if (this.currentTrace.inInterfaces > 20)\n                throw new RuntimeException(\"Too many in interfaces!\");\n            \n            if (this.currentTrace.outInterfaces > 20)\n                throw new RuntimeException(\"Too many out interfaces!\");\n            \n            if (this.currentTrace.inHandles > 20)\n                throw new RuntimeException(\"Too many in handles!\");\n            \n            if (this.currentTrace.outHandles > 20)\n                throw new RuntimeException(\"Too many out handles!\");\n            \n            this.currentTrace.lr = this.state.getValue(\"x30\");\n            \n            if (this.currentTrace.inInterfaces > 0)\n            {\n                // Add a handler to cheat the cmp x8, x9 that usually happens\n                this.instructionHandlers.add((off) -> \n                {\n                    Address pcAddr = this.sLang.getDefaultSpace().getAddress(off);\n                    CodeManager codeManager = ((ProgramDB)this.program).getCodeManager();\n                    Instruction currentInstruction = codeManager.getInstructionAt(pcAddr);\n                    \n                    // Attempt to disassemble the instruction so we can try again\n                    if (currentInstruction == null)\n                    {\n                        disassembler.disassemble(pcAddr, null);\n                        currentInstruction = codeManager.getInstructionAt(pcAddr);\n                    }\n                    \n                    if (currentInstruction != null)\n                    {\n                        InstructionPrototype prototype = currentInstruction.getPrototype();\n                        String mnemonic = prototype.getMnemonic(currentInstruction.getInstructionContext());\n                        \n                        if (mnemonic.equals(\"cmp\") && currentInstruction.getOperandType(0) == OperandType.REGISTER && currentInstruction.getOperandType(1) == OperandType.REGISTER)\n                        {\n                            Register r0 = currentInstruction.getRegister(0);\n                            Register r1 = currentInstruction.getRegister(1);\n                            \n                            // Cheat time!\n                            if (r0.getName().equals(\"x8\") && r1.getName().equals(\"x9\"))\n                            {\n                                long x9 = this.state.getValue(\"x9\");\n                                this.state.setValue(\"x8\", x9);\n                                this.state.setValue(\"NZCV\", 0b0100);\n                            }\n                        }\n                    }\n                });\n            }\n        }\n        catch (Exception e)\n        {\n            return false;\n        }\n        \n        this.returnFromFunc(0);\n        return true;\n    }\n    \n    private boolean OverwriteClientProcessId()\n    {\n        long out = this.state.getValue(\"x1\");\n        this.setLong(out, 0);\n        this.returnFromFunc(0);\n        return true;\n    }\n\n    private boolean GetBuffers()\n    {\n        long out = this.state.getValue(\"x1\");\n        \n        for (long i = out; i < out + this.currentTrace.bufferCount * 0x10; i += 0x10)\n        {\n            this.setLong(i, this.bufferMemory);\n            this.setLong(i + 0x8, this.bufferSize);\n        }\n        \n        this.returnFromFunc(0);\n        return true;\n    }\n    \n    private boolean GetInNativeHandles()\n    {\n        this.returnFromFunc(0);\n        return true;\n    }\n    \n    private boolean GetInObjects()\n    {\n        long out = this.state.getValue(\"x1\");\n        \n        if (this.currentTrace.inInterfaces != 1)\n            throw new RuntimeException(\"Invalid number of in interfaces!\");\n            \n        this.setLong(out, this.inObjectPtr);\n        \n        this.returnFromFunc(0);\n        return true;\n    }\n    \n    private boolean BeginPreparingForReply()\n    {\n        long off = this.state.getValue(\"x1\");\n        this.setLong(off, this.outputMemory);\n        this.setLong(off + 0x8, 0x1000);\n        this.returnFromFunc(0);\n        return true;\n    }\n    \n    private boolean SetBuffers()\n    {\n        this.returnFromFunc(0);\n        return true;\n    }\n    \n    private boolean SetOutObjects()\n    {\n        // Stubbed\n        return false;\n    }\n    \n    private boolean SetOutNativeHandles()\n    {\n        this.returnFromFunc(0);\n        return true;\n    }\n    \n    private boolean BeginPreparingForErrorReply()\n    {\n        // Stubbed\n        return false;\n    }\n    \n    private boolean EndPreparingForReply()\n    {\n        // Stubbed\n        this.returnFromFunc(0);\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/analyzer/ipc/IPCTrace.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.analyzer.ipc;\n\nimport ghidra.util.Msg;\n\npublic class IPCTrace \n{\n    public final long cmdId;\n    public final long procFuncAddr;\n    \n    public long bytesIn = -1;\n    public long bytesOut = -1;\n    public long bufferCount = -1;\n    public long inInterfaces = -1;\n    public long outInterfaces = -1;\n    public long inHandles = -1;\n    public long outHandles = -1;\n    public long lr = -1;\n    \n    public long vtOffset = -1;\n    \n    public IPCTrace(int cmdId, long procFuncAddr)\n    {\n        this.cmdId = cmdId;\n        this.procFuncAddr = procFuncAddr;\n    }\n    \n    public boolean hasDescription()\n    {\n        return bytesIn != -1 || bytesOut != -1 || bufferCount != -1 || inInterfaces != -1 ||\n                outInterfaces != -1 || inHandles != -1 || outHandles != -1 || lr != -1;\n    }\n    \n    public boolean isCorrect()\n    {\n        if (!this.hasDescription())\n        {\n            return true;\n        }\n\n        return vtOffset != -1;\n    }\n    \n    public void printTrace()\n    {\n        String out = \"\"\"\n\n                --------------------\n                0x%X, Cmd 0x%X     \\s\n                --------------------\n                Lr:             0x%X\n                Vt:             0x%X\n                Bytes In:       0x%X\n                Bytes Out:      0x%X\n                Buffer Count:   0x%X\n                In Interfaces:  0x%X\n                Out Interfaces: 0x%X\n                In Handles:     0x%X\n                Out Handles:    0x%X\n                --------------------\n                \"\"\";\n        \n        out = String.format(out, procFuncAddr, cmdId, lr, vtOffset, bytesIn, bytesOut, bufferCount, inInterfaces,\n                            outInterfaces, inHandles, outHandles);\n        Msg.info(this, out);\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/common/ElfCompatibilityProvider.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.common;\n\nimport adubbz.nx.util.FullMemoryByteProvider;\nimport adubbz.nx.util.LegacyBinaryReader;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.app.util.bin.ByteArrayProvider;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.app.util.bin.format.elf.*;\nimport ghidra.app.util.bin.format.elf.extend.ElfExtensionFactory;\nimport ghidra.app.util.bin.format.elf.extend.ElfLoadAdapter;\nimport ghidra.app.util.bin.format.elf.relocation.AARCH64_ElfRelocationType;\nimport ghidra.app.util.bin.format.elf.relocation.ARM_ElfRelocationType;\nimport ghidra.program.model.listing.Program;\nimport ghidra.program.model.mem.MemoryBlock;\nimport ghidra.util.Msg;\nimport ghidra.util.exception.NotFoundException;\n\nimport java.io.IOException;\nimport java.util.*;\n\npublic class ElfCompatibilityProvider \n{\n    public static final int R_FAKE_RELR = -1;\n\n    private Program program;\n    private ByteProvider provider;\n    private BinaryReader binaryReader;\n    boolean isAarch32;\n    \n    private ElfHeader dummyElfHeader;\n    \n    protected ElfDynamicTable dynamicTable;\n    protected ElfStringTable stringTable;\n    protected ElfSymbolTable symbolTable;\n    \n    protected String[] dynamicLibraryNames;\n    protected List<NXRelocation> relocs = new ArrayList<>();\n    protected List<NXRelocation> pltRelocs = new ArrayList<>();\n    \n    public ElfCompatibilityProvider(Program program, ByteProvider provider, boolean isAarch32)\n    {\n        this.program = program;\n        this.provider = provider;\n        this.binaryReader = new LegacyBinaryReader(this.provider, true);\n        this.isAarch32 = isAarch32;\n        try {\n            this.dummyElfHeader = new DummyElfHeader(isAarch32);\n        } catch (ElfException e) {\n            Msg.error(this, \"Couldn't construct DummyElfHeader\", e);\n        }\n    }\n    \n    public ElfCompatibilityProvider(Program program, boolean isAarch32)\n    {\n        this(program, new FullMemoryByteProvider(program), isAarch32);\n    }\n    \n    public ElfDynamicTable getDynamicTable()\n    {\n        if (this.dynamicTable != null)\n            return this.dynamicTable;\n\n        MemoryBlock dynamic = this.getDynamicBlock();\n        \n        if (dynamic == null) return null;\n        \n        try\n        {\n            this.dynamicTable = new ElfDynamicTable(this.binaryReader, this.dummyElfHeader, dynamic.getStart().getOffset(), dynamic.getStart().getOffset());\n        }\n        catch (IOException e)\n        {\n            Msg.error(this, \"Failed to create dynamic table\", e);\n        }\n        \n        return this.dynamicTable;\n    }\n    \n    public ElfStringTable getStringTable()\n    {\n        if (this.stringTable != null)\n            return this.stringTable;\n        \n        ElfDynamicTable dynamicTable = this.getDynamicTable();\n        \n        if (dynamicTable == null || !dynamicTable.containsDynamicValue(ElfDynamicType.DT_STRTAB)) \n            return null;\n        \n        try\n        {\n            long dynamicStringTableAddr = this.program.getImageBase().getOffset() + dynamicTable.getDynamicValue(ElfDynamicType.DT_STRTAB);\n            long dynamicStringTableSize = dynamicTable.getDynamicValue(ElfDynamicType.DT_STRSZ);\n    \n            this.stringTable = new ElfStringTable(this.dummyElfHeader,\n                    null, dynamicStringTableAddr, dynamicStringTableAddr, dynamicStringTableSize);\n        }\n        catch (NotFoundException e)\n        {\n            Msg.error(this, \"Failed to create string table\", e);\n        }\n        \n        return this.stringTable;\n    }\n    \n    public String[] getDynamicLibraryNames()\n    {\n        if (this.dynamicLibraryNames != null)\n            return this.dynamicLibraryNames;\n        \n        ElfDynamicTable dynamicTable = this.getDynamicTable();\n        ElfStringTable stringTable = this.getStringTable();\n        \n        if (dynamicTable == null) return new String[0];\n        \n        ElfDynamic[] needed = dynamicTable.getDynamics(ElfDynamicType.DT_NEEDED);\n        this.dynamicLibraryNames = new String[needed.length];\n        for (int i = 0; i < needed.length; i++) \n        {\n            if (stringTable != null) \n            {\n                try \n                {\n                    this.dynamicLibraryNames[i] = stringTable.readString(this.binaryReader, needed[i].getValue());\n                }\n                catch (Exception e) \n                {\n                    // ignore\n                }\n            }\n            if (this.dynamicLibraryNames[i] == null) {\n                this.dynamicLibraryNames[i] = \"UNK_LIB_NAME_\" + i;\n            }\n        }\n        \n        return this.dynamicLibraryNames;\n    }\n    \n    public ElfSymbolTable getSymbolTable()\n    {\n        if (this.symbolTable != null)\n            return this.symbolTable;\n        \n        ElfDynamicTable dynamicTable = this.getDynamicTable();\n        ElfStringTable stringTable = this.getStringTable();\n        \n        if (dynamicTable == null || stringTable == null) \n            return null;\n        \n        try\n        {\n            long symbolTableOff = dynamicTable.getDynamicValue(ElfDynamicType.DT_SYMTAB) + this.program.getImageBase().getOffset();\n            long symbolEntrySize = dynamicTable.getDynamicValue(ElfDynamicType.DT_SYMENT);\n            long symbolTableSize;\n            \n            if (dynamicTable.containsDynamicValue(ElfDynamicType.DT_HASH)) {\n                long dtHashOff = dynamicTable.getDynamicValue(ElfDynamicType.DT_HASH);\n                long nchain = this.binaryReader.readUnsignedInt(this.program.getImageBase().getOffset() + dtHashOff + 4);\n                symbolTableSize = nchain * symbolEntrySize;\n            }\n            else if (symbolTableOff < stringTable.getAddressOffset()) {\n                symbolTableSize = stringTable.getAddressOffset() - symbolTableOff;\n            }\n            else {\n                Msg.error(this, \"Unable to determine symbol table size.\");\n                return null;\n            }\n            \n            this.symbolTable = new ElfSymbolTable(this.binaryReader, this.dummyElfHeader, null,\n                    symbolTableOff,\n                    symbolTableOff,\n                    symbolTableSize,\n                    symbolEntrySize,\n                    stringTable, null, true);\n        }\n        catch (IllegalArgumentException | NotFoundException | IOException e)\n        {\n            Msg.error(this, \"Failed to create symbol table\", e);\n        }\n        \n        return this.symbolTable;\n    }\n    \n    public List<NXRelocation> getPltRelocations()\n    {\n        if (!this.pltRelocs.isEmpty())\n            return this.pltRelocs;\n        \n        ElfDynamicTable dynamicTable = this.getDynamicTable();\n        ElfSymbolTable symbolTable = this.getSymbolTable();\n        \n        if (dynamicTable == null || symbolTable == null)\n            return this.pltRelocs;\n        \n        try\n        {\n            if (this.dynamicTable.containsDynamicValue(ElfDynamicType.DT_JMPREL)) \n            {\n                Msg.info(this, \"Processing JMPREL relocations...\");\n                this.processRelocations(this.pltRelocs, symbolTable,\n                        dynamicTable.getDynamicValue(ElfDynamicType.DT_JMPREL),\n                        dynamicTable.getDynamicValue(ElfDynamicType.DT_PLTRELSZ));\n    \n                this.pltRelocs.sort(Comparator.comparing(reloc -> reloc.offset));\n            }\n        }\n        catch (NotFoundException | IOException e)\n        {\n            Msg.error(this, \"Failed to get plt relocations\", e);\n        }\n        \n        return this.pltRelocs;\n    }\n    \n    public List<NXRelocation> getRelocations()\n    {\n        if (!this.relocs.isEmpty())\n            return this.relocs;\n        \n        ElfDynamicTable dynamicTable = this.getDynamicTable();\n        ElfSymbolTable symbolTable = this.getSymbolTable();\n\n        if(dynamicTable == null)\n            return this.relocs;\n\n        try {\n            if (dynamicTable.containsDynamicValue(ElfDynamicType.DT_RELR)) {\n                Msg.info(this, \"Processing DT_RELR relocations...\");\n                processReadOnlyRelocations(this.relocs,\n                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELR),\n                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELRSZ));\n            }\n        }\n        catch (NotFoundException | IOException e)\n        {\n            Msg.error(this, \"Failed to get relocations\", e);\n        }\n        \n        if (symbolTable == null)\n            return this.relocs;\n        \n        try\n        {\n            if (dynamicTable.containsDynamicValue(ElfDynamicType.DT_REL))\n            {\n                Msg.info(this, \"Processing DT_REL relocations...\");\n                processRelocations(this.relocs, this.symbolTable,\n                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_REL),\n                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELSZ));\n            }\n            \n            if (dynamicTable.containsDynamicValue(ElfDynamicType.DT_RELA)) \n            {\n                Msg.info(this, \"Processing DT_RELA relocations...\");\n                processRelocations(this.relocs, this.symbolTable,\n                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELA),\n                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELASZ));\n            }\n        }\n        catch (NotFoundException | IOException e)\n        {\n            Msg.error(this, \"Failed to get relocations\", e);\n        }\n        \n        this.relocs.addAll(this.getPltRelocations());\n        return this.relocs;\n    }\n    \n    private Set<Long> processRelocations(List<NXRelocation> relocs, ElfSymbolTable symtab, long rel, long relsz) throws IOException \n    {\n        long base = this.program.getImageBase().getOffset();\n        Set<Long> locations = new HashSet<>();\n        int relocSize = this.isAarch32 ? 0x8 : 0x18;\n\n        for (long i = 0; i < relsz / relocSize; i++) \n        {\n            long offset;\n            long info;\n            long addend;\n            \n            long r_type;\n            long r_sym;\n        \n            // Assumes all aarch32 relocs have no addends,\n            // and all 64-bit ones do.\n            if (this.isAarch32)\n            {\n                offset = this.binaryReader.readInt(base + rel + i * 0x8);\n                info = this.binaryReader.readInt(base + rel + i * 0x8 + 4);\n                addend = 0;\n                r_type = info & 0xff;\n                r_sym = info >> 8;\n            }\n            else\n            {\n                offset = this.binaryReader.readLong(base + rel + i * 0x18);\n                info = this.binaryReader.readLong(base + rel + i * 0x18 + 8);\n                addend = this.binaryReader.readLong(base + rel + i * 0x18 + 0x10);\n                r_type = info & 0xffffffffL;\n                r_sym = info >> 32;\n            }\n        \n            ElfSymbol sym;\n            if (r_sym != 0) {\n                // Note: getSymbolAt doesn't work as it relies on getValue() being the address, which is 0 for imports.\n                // We manually correct the value later to point to the fake external block.\n                sym = symtab.getSymbols()[(int)r_sym];\n            } else {\n                sym = null;\n            }\n            \n            if (r_type != AARCH64_ElfRelocationType.R_AARCH64_TLSDESC.typeId() && r_type != ARM_ElfRelocationType.R_ARM_TLS_DESC.typeId())\n            {\n                locations.add(offset);\n            }\n            relocs.add(new NXRelocation(offset, r_sym, r_type, sym, addend));\n        }\n        return locations;\n    }\n\n    private Set<Long> processReadOnlyRelocations(List<NXRelocation> relocs, long relr, long relrsz) throws IOException\n    {\n        long base = this.program.getImageBase().getOffset();\n        Set<Long> locations = new HashSet<>();\n        int relocSize = 0x8;\n\n        long where = 0;\n        for (long entryNumber = 0; entryNumber < relrsz / relocSize; entryNumber++)\n        {\n            long entry = this.binaryReader.readLong(base + relr + entryNumber * relocSize);\n\n            if ((entry & 1) != 0) {\n                entry >>= 1;\n                long i = 0;\n                while (i < (relocSize * 8) - 1) {\n                    if ((entry & (1L << i)) != 0) {\n                        locations.add(where + i * relocSize);\n                        relocs.add(new NXRelocation(where + i * relocSize, 0, R_FAKE_RELR, null, 0));\n                    }\n                    i++;\n                }\n                where += relocSize * ((relocSize * 8) - 1);\n            }\n            else {\n                where = entry;\n                locations.add(where);\n                relocs.add(new NXRelocation(where, 0, R_FAKE_RELR, null, 0));\n                where += relocSize;\n            }\n        }\n        return locations;\n    }\n    \n    protected MemoryBlock getDynamicBlock()\n    {\n        return this.program.getMemory().getBlock(\".dynamic\");\n    }\n    \n    public BinaryReader getReader()\n    {\n        return this.binaryReader;\n    }\n    \n    // Fake only what is needed for an elf dynamic table\n    public static class DummyElfHeader extends ElfHeader\n    {\n        boolean isAarch32;\n        private HashMap<Integer, ElfDynamicType> dynamicTypeMap;\n        \n        public DummyElfHeader(boolean isAarch32) throws ElfException {\n            super(new ByteArrayProvider(Arrays.copyOf(ElfConstants.MAGIC_BYTES, ElfConstants.EI_NIDENT + 18)), s -> {});\n\n            this.isAarch32 = isAarch32;\n            dynamicTypeMap = new HashMap<>();\n            ElfDynamicType.addDefaultTypes(this.dynamicTypeMap);\n\n            ElfLoadAdapter extensionAdapter = ElfExtensionFactory.getLoadAdapter(this);\n            if (extensionAdapter != null) \n            {\n                extensionAdapter.addDynamicTypes(this.dynamicTypeMap);\n            }\n        }\n\n\n        @Override\n        protected HashMap<Integer, ElfDynamicType> getDynamicTypeMap()\n        {\n            return this.dynamicTypeMap;\n        }\n\n        @Override\n        public ElfDynamicType getDynamicType(int type) \n        {\n            if (this.dynamicTypeMap != null) \n            {\n                return this.dynamicTypeMap.get(type);\n            }\n            return null; // not found\n        }\n        \n        @Override\n        public long adjustAddressForPrelink(long address) \n        {\n            return address;\n        }\n        \n        @Override\n        public long unadjustAddressForPrelink(long address) \n        {\n            return address;\n        }\n        \n        @Override\n        public boolean is32Bit() \n        {\n            return this.isAarch32;\n        }\n        \n        @Override\n        public boolean is64Bit()\n        {\n            return !this.isAarch32;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/common/InvalidMagicException.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.common;\n\npublic class InvalidMagicException extends RuntimeException\n{\n    public InvalidMagicException(String magic)\n    {\n        super(String.format(\"Invalid %s magic!\", magic));\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/common/NXRelocation.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.common;\n\nimport ghidra.app.util.bin.format.elf.ElfSymbol;\n\npublic class NXRelocation \n{\n    public NXRelocation(long offset, long r_sym, long r_type, ElfSymbol sym, long addend) \n    {\n        this.offset = offset;\n        this.r_sym = r_sym;\n        this.r_type = r_type;\n        this.sym = sym;\n        this.addend = addend;\n    }\n    \n    public long offset;\n    public long r_sym;\n    public long r_type;\n    public ElfSymbol sym;\n    public long addend;\n}"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/SwitchLoader.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\npackage adubbz.nx.loader;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.function.BiFunction;\n\nimport adubbz.nx.loader.common.NXProgramBuilder;\nimport adubbz.nx.loader.kip1.KIP1Adapter;\nimport adubbz.nx.loader.knx.KNXAdapter;\nimport adubbz.nx.loader.nro0.NRO0Adapter;\nimport adubbz.nx.loader.nso0.NSO0Adapter;\nimport adubbz.nx.loader.nxo.NXOAdapter;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.app.util.bin.ByteProviderWrapper;\nimport ghidra.app.util.opinion.*;\nimport ghidra.framework.store.LockException;\nimport ghidra.program.model.address.AddressOutOfBoundsException;\nimport ghidra.program.model.address.AddressOverflowException;\nimport ghidra.program.model.lang.CompilerSpecID;\nimport ghidra.program.model.lang.LanguageCompilerSpecPair;\nimport ghidra.program.model.lang.LanguageID;\nimport ghidra.program.model.listing.Program;\nimport ghidra.util.Msg;\nimport ghidra.util.exception.CancelledException;\n\npublic class SwitchLoader extends BinaryLoader \n{\n    public static final String SWITCH_NAME = \"Nintendo Switch Binary\";\n    public static final LanguageID AARCH64_LANGUAGE_ID = new LanguageID(\"AARCH64:LE:64:v8A\");\n    public static final LanguageID AARCH32_LANGUAGE_ID = new LanguageID(\"ARM:LE:32:v8\");\n    private BinaryType binaryType;\n\n    @Override\n    public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException \n    {\n        List<LoadSpec> loadSpecs = new ArrayList<>();\n        BinaryReader reader = new BinaryReader(provider, true);\n        String magic_0x0 = reader.readAsciiString(0, 4);\n        String magic_0x10 = reader.readAsciiString(0x10, 4);\n        \n        reader.setPointerIndex(0);\n\n        if (magic_0x0.equals(\"KIP1\")) \n        {\n            this.binaryType = BinaryType.KIP1;\n        }\n        else if (magic_0x0.equals(\"NSO0\"))\n        {\n            this.binaryType = BinaryType.NSO0;\n        }\n        else if (magic_0x0.equals(\"\\u00DF\\u004F\\u0003\\u00D5\"))\n        {\n            this.binaryType = BinaryType.KERNEL_800;\n        }\n        else if (magic_0x10.equals(\"NRO0\"))\n        {\n            this.binaryType = BinaryType.NRO0;\n        }\n        else if (magic_0x10.equals(\"KIP1\"))\n        {\n            // Note: This is kinda a bad way of determining this, but for now it gets the job done\n            // and I don't believe there are any clashes.\n            this.binaryType = BinaryType.SX_KIP1; \n        }\n        else\n            return loadSpecs;\n\n        var adapter = this.binaryType.createAdapter(null, provider);\n        \n        if (adapter.isAarch32())\n        {\n            loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair(AARCH32_LANGUAGE_ID, new CompilerSpecID(\"default\")), true));\n        }\n        else\n        {\n            loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair(AARCH64_LANGUAGE_ID, new CompilerSpecID(\"default\")), true));\n        }\n\n        return loadSpecs;\n    }\n\n    @Override\n    protected void loadProgramInto(Program program, Loader.ImporterSettings settings) throws IOException, CancelledException\n    {\n        var space = program.getAddressFactory().getDefaultAddressSpace();\n        var provider = settings.provider();\n        \n        if (this.binaryType == BinaryType.SX_KIP1)\n        {\n            provider = new ByteProviderWrapper(settings.provider(), 0x10, settings.provider().length() - 0x10);\n        }\n\n        var adapter = this.binaryType.createAdapter(program, provider);\n        \n        // Set the base address\n        try \n        {\n            long baseAddress = adapter.isAarch32() ? 0x60000000L : 0x7100000000L;\n            \n            if (this.binaryType == BinaryType.KERNEL_800)\n            {\n                baseAddress = 0x80060000L;\n            }\n\n            program.setImageBase(space.getAddress(baseAddress), true);\n        } \n        catch (AddressOverflowException | LockException | IllegalStateException | AddressOutOfBoundsException e) \n        {\n            Msg.error(this, \"Failed to set image base\", e);\n        }\n\n        var loader = new NXProgramBuilder(program, provider, adapter);\n        loader.load(settings.monitor());\n        \n        if (this.binaryType == BinaryType.KIP1)\n        {\n            // KIP1s always start with a branch instruction at the start of their text\n            loader.createEntryFunction(\"entry\", program.getImageBase().getOffset(), settings.monitor());\n        }\n    }\n\n    @Override\n    public LoaderTier getTier() \n    {\n        return LoaderTier.SPECIALIZED_TARGET_LOADER;\n    }\n\n    @Override\n    public int getTierPriority() \n    {\n        return 0;\n    }\n\n    @Override\n    public String getName() \n    {\n        return SWITCH_NAME;\n    }\n\n    private enum BinaryType\n    {\n        KIP1(\"Kernel Initial Process\", KIP1Adapter::new),\n        NSO0(\"Nintendo Shared Object\", NSO0Adapter::new), \n        NRO0(\"Nintendo Relocatable Object\", NRO0Adapter::new),\n        SX_KIP1(\"Gateway Kernel Initial Process\", KIP1Adapter::new),\n        KERNEL_800(\"Nintendo Switch Kernel 8.0.0+\", KNXAdapter::new);\n        \n        public final String name;\n        private final BiFunction<Program, ByteProvider, NXOAdapter> adapterFunc;\n        \n        BinaryType(String name, BiFunction<Program, ByteProvider, NXOAdapter> adapterFunc)\n        {\n            this.name = name;\n            this.adapterFunc = adapterFunc;\n        }\n        \n        public NXOAdapter createAdapter(Program program, ByteProvider provider)\n        {\n            return adapterFunc.apply(program, provider);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/common/MemoryBlockHelper.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.common;\n\nimport java.util.List;\n\nimport org.apache.commons.compress.utils.Lists;\n\nimport ghidra.app.util.MemoryBlockUtils;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.app.util.importer.MessageLog;\nimport ghidra.program.database.mem.FileBytes;\nimport ghidra.program.model.address.Address;\nimport ghidra.program.model.address.AddressRange;\nimport ghidra.program.model.address.AddressRangeImpl;\nimport ghidra.program.model.listing.Program;\nimport ghidra.program.model.mem.Memory;\nimport ghidra.program.model.mem.MemoryBlock;\nimport ghidra.util.Msg;\nimport ghidra.util.task.TaskMonitor;\n\npublic class MemoryBlockHelper \n{\n    private TaskMonitor monitor;\n    private Program program;\n    private ByteProvider byteProvider;\n    private MessageLog log;\n\n    public MemoryBlockHelper(TaskMonitor monitor, Program program, ByteProvider byteProvider)\n    {\n        this.monitor = monitor;\n        this.program = program;\n        this.byteProvider = byteProvider;\n        this.log = new MessageLog();\n    }\n    \n    public void addSection(String name, long addressOffset, long offset, long length, boolean read, boolean write, boolean execute)\n    {\n        try\n        {\n            FileBytes fileBytes = MemoryBlockUtils.createFileBytes(this.program, this.byteProvider, offset, length, this.monitor);\n            MemoryBlockUtils.createInitializedBlock(this.program, false, name, this.program.getImageBase().add(addressOffset), fileBytes, 0, length, \"\", null, read, write, execute, this.log);\n        }\n        catch (Exception e)\n        {\n            Msg.error(this, \"Failed to add section \" + name, e);\n        }\n        \n        this.flushLog();\n    }\n    \n    private void addUniqueSection(String name, long addressOffset, long offset, long length, boolean read, boolean write, boolean execute)\n    {\n        Memory memory = this.program.getMemory();\n        Address startAddr = this.program.getImageBase().add(addressOffset);\n        Address endAddr = startAddr.add(length);\n        String newBlockName = name;\n        int nameCounter = 0;\n        \n        while (memory.getBlock(newBlockName) != null)\n        {\n            nameCounter++;\n            newBlockName = name + \".\" + nameCounter; \n        }\n        \n        Msg.info(this, \"Adding unique section \" + newBlockName + \" from \" + startAddr + \" to \" + endAddr);\n        this.addSection(newBlockName, offset, offset, length, read, write, execute);\n    }\n    \n    public void addFillerSection(String name, long addressOffset, long length, boolean read, boolean write, boolean execute)\n    {\n        Memory memory = this.program.getMemory();\n        Address startAddr = this.program.getImageBase().add(addressOffset);\n        Address endAddr = startAddr.add(length);\n        AddressRange range = new AddressRangeImpl(startAddr, endAddr);\n        \n        List<MemoryBlock> blocksInRange = Lists.newArrayList();\n        \n        for (MemoryBlock block : memory.getBlocks())\n        {\n            AddressRange blockRange = new AddressRangeImpl(block.getStart(), block.getEnd());\n            \n            if (range.intersects(blockRange))\n            {\n                blocksInRange.add(block);\n            }\n        }\n        \n        if (blocksInRange.isEmpty())\n        {\n            Msg.info(this, \"Adding filler section \" + name + \" from \" + startAddr + \" to \" + endAddr);\n            this.addSection(name, addressOffset, addressOffset, range.getLength(), read, write, execute);\n            return;\n        }\n        \n        Address fillerBlockStart = startAddr;\n        AddressRange fillerBlockRange;\n        \n        for (MemoryBlock block : blocksInRange)\n        {\n            fillerBlockRange = new AddressRangeImpl(fillerBlockStart, block.getStart());\n            \n            if (fillerBlockRange.getLength() > 2)\n            {\n                long offset = fillerBlockRange.getMinAddress().subtract(this.program.getImageBase());\n                this.addUniqueSection(name, offset, offset, fillerBlockRange.getLength() - 1, read, write, execute);\n            }\n            \n            fillerBlockStart = block.getEnd().add(1);\n        }\n        \n        fillerBlockRange = new AddressRangeImpl(fillerBlockStart, endAddr);\n        \n        if (fillerBlockRange.getLength() > 2)\n        {\n            long offset = fillerBlockRange.getMinAddress().subtract(this.program.getImageBase());\n            this.addUniqueSection(name, offset, offset, fillerBlockRange.getLength() - 1, read, write, execute);\n        }\n    }\n    \n    public void flushLog()\n    {\n        if (this.log.hasMessages())\n        {\n            Msg.info(this, this.log.toString());\n            this.log.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/common/NXProgramBuilder.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.common;\n\nimport adubbz.nx.common.NXRelocation;\nimport adubbz.nx.loader.nxo.NXO;\nimport adubbz.nx.loader.nxo.NXOAdapter;\nimport adubbz.nx.loader.nxo.NXOSection;\nimport adubbz.nx.loader.nxo.NXOSectionType;\nimport adubbz.nx.util.UIUtil;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.primitives.Longs;\nimport ghidra.app.cmd.label.SetLabelPrimaryCmd;\nimport ghidra.app.util.MemoryBlockUtils;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.app.util.bin.format.elf.ElfDynamicType;\nimport ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants;\nimport ghidra.app.util.bin.format.elf.ElfStringTable;\nimport ghidra.app.util.bin.format.elf.ElfSymbol;\nimport ghidra.app.util.bin.format.elf.relocation.AARCH64_ElfRelocationType;\nimport ghidra.app.util.bin.format.elf.relocation.ARM_ElfRelocationType;\nimport ghidra.app.util.importer.MessageLog;\nimport ghidra.program.database.function.OverlappingFunctionException;\nimport ghidra.program.disassemble.Disassembler;\nimport ghidra.program.model.address.*;\nimport ghidra.program.model.data.PointerDataType;\nimport ghidra.program.model.data.TerminatedStringDataType;\nimport ghidra.program.model.listing.*;\nimport ghidra.program.model.mem.MemoryAccessException;\nimport ghidra.program.model.mem.MemoryBlock;\nimport ghidra.program.model.reloc.Relocation;\nimport ghidra.program.model.symbol.*;\nimport ghidra.program.model.util.CodeUnitInsertionException;\nimport ghidra.util.Msg;\nimport ghidra.util.exception.CancelledException;\nimport ghidra.util.exception.DuplicateNameException;\nimport ghidra.util.exception.InvalidInputException;\nimport ghidra.util.exception.NotFoundException;\nimport ghidra.util.task.TaskMonitor;\n\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static adubbz.nx.common.ElfCompatibilityProvider.R_FAKE_RELR;\n\npublic class NXProgramBuilder \n{\n    protected ByteProvider fileByteProvider;\n    protected Program program;\n    protected NXO nxo;\n    \n    protected AddressSpace aSpace;\n    protected MemoryBlockHelper memBlockHelper;\n    \n    protected List<PltEntry> pltEntries = new ArrayList<>();\n    \n    protected int undefSymbolCount;\n    \n    public NXProgramBuilder(Program program, ByteProvider provider, NXOAdapter adapter)\n    {\n        this.program = program;\n        this.fileByteProvider = provider;\n        this.nxo = new NXO(program, adapter, program.getImageBase().getOffset());\n    }\n    \n    public void load(TaskMonitor monitor) throws CancelledException\n    {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        ByteProvider memoryProvider = adapter.getMemoryProvider();\n        this.aSpace = program.getAddressFactory().getDefaultAddressSpace();\n        \n        try \n        {\n            this.memBlockHelper = new MemoryBlockHelper(monitor, this.program, memoryProvider);\n            \n            NXOSection text = adapter.getSection(NXOSectionType.TEXT);\n            NXOSection rodata = adapter.getSection(NXOSectionType.RODATA);\n            NXOSection data = adapter.getSection(NXOSectionType.DATA);\n            \n            if (adapter.getDynamicSize() == 0)\n            {\n                // We can't create .dynamic, so work with what we've got.\n                return;\n            }\n            \n            this.memBlockHelper.addSection(\".dynamic\", adapter.getDynamicOffset(), adapter.getDynamicOffset(), adapter.getDynamicSize(), true, true, false);\n\n            // Create dynamic sections\n            this.tryCreateDynBlock(\".dynstr\", ElfDynamicType.DT_STRTAB, ElfDynamicType.DT_STRSZ);\n            this.tryCreateDynBlock(\".init_array\", ElfDynamicType.DT_INIT_ARRAY, ElfDynamicType.DT_INIT_ARRAYSZ);\n            this.tryCreateDynBlock(\".fini_array\", ElfDynamicType.DT_FINI_ARRAY, ElfDynamicType.DT_FINI_ARRAYSZ);\n            this.tryCreateDynBlock(\".rela.dyn\", ElfDynamicType.DT_RELA, ElfDynamicType.DT_RELASZ);\n            this.tryCreateDynBlock(\".rel.dyn\", ElfDynamicType.DT_REL, ElfDynamicType.DT_RELSZ);\n            this.tryCreateDynBlock(\".relr.dyn\", ElfDynamicType.DT_RELR, ElfDynamicType.DT_RELRSZ);\n            \n            if (adapter.isAarch32())\n            {\n                this.tryCreateDynBlock(\".rel.plt\", ElfDynamicType.DT_JMPREL, ElfDynamicType.DT_PLTRELSZ);\n            }\n            else\n            {\n                this.tryCreateDynBlock(\".rela.plt\", ElfDynamicType.DT_JMPREL, ElfDynamicType.DT_PLTRELSZ);\n            }\n\n            this.tryCreateDynBlockWithRange(\".hash\", ElfDynamicType.DT_HASH, ElfDynamicType.DT_GNU_HASH);\n            this.tryCreateDynBlockWithRange(\".gnu.hash\", ElfDynamicType.DT_GNU_HASH, ElfDynamicType.DT_SYMTAB);\n            \n            if (adapter.getSymbolTable(this.program) != null)\n            {\n                Msg.info(this, String.format(\"String table offset %X, base addr %X\", adapter.getSymbolTable(this.program).getFileOffset(), this.nxo.getBaseAddress()));\n                this.memBlockHelper.addSection(\".dynsym\", adapter.getSymbolTable(this.program).getFileOffset() - this.nxo.getBaseAddress(), adapter.getSymbolTable(this.program).getFileOffset() - this.nxo.getBaseAddress(), adapter.getSymbolTable(this.program).getLength(), true, false, false);\n            }\n            \n            this.setupRelocations(monitor);\n            this.createGlobalOffsetTable();\n            \n            this.memBlockHelper.addFillerSection(\".text\", text.getOffset(), text.getSize(), true, false, true);\n            this.memBlockHelper.addFillerSection(\".rodata\", rodata.getOffset(), rodata.getSize(), true, false, false);\n            this.memBlockHelper.addFillerSection(\".data\", data.getOffset(), data.getSize(), true, true, false);\n            \n            this.setupStringTable();\n            this.setupSymbolTable();\n            \n            // Create BSS. This needs to be done before the EXTERNAL block is created in setupImports\n            Address bssStartAddr = aSpace.getAddress(this.nxo.getBaseAddress() + adapter.getBssOffset());\n            Msg.info(this, String.format(\"Created bss from 0x%X to 0x%X\", bssStartAddr.getOffset(), bssStartAddr.getOffset() + adapter.getBssSize()));\n            MemoryBlockUtils.createUninitializedBlock(this.program, false, \".bss\", bssStartAddr, adapter.getBssSize(), \"\", null, true, true, false, new MessageLog());\n            \n            this.setupImports(monitor);\n            this.performRelocations();\n            \n            // Set all data in the GOT to the pointer data type\n            // NOTE: Currently the got range may be null in e.g. old libnx nros\n            // We may want to manually figure this out ourselves in future.\n            if (adapter.getGotSize() > 0)\n            {\n                for (Address addr = this.aSpace.getAddress(adapter.getGotOffset()); addr.compareTo(this.aSpace.getAddress(adapter.getGotOffset() + adapter.getGotSize())) < 0; addr = addr.add(adapter.getOffsetSize()))\n                {\n                    this.createPointer(addr);\n                }\n            }\n        }\n        catch (IOException | NotFoundException | AddressOverflowException | AddressOutOfBoundsException | CodeUnitInsertionException | MemoryAccessException | InvalidInputException e)\n        {\n            e.printStackTrace();\n        }\n        \n        // Ensure memory blocks are ordered from first to last.\n        // Normally they are ordered by the order they are added.\n        UIUtil.sortProgramTree(this.program);\n    }\n    \n    public NXO getNxo()\n    {\n        return this.nxo;\n    }\n    \n    protected void setupStringTable() throws AddressOverflowException, CodeUnitInsertionException\n    {\n       NXOAdapter adapter = this.nxo.getAdapter();\n       ElfStringTable stringTable = adapter.getStringTable(this.program);\n       \n       if (stringTable == null)\n           return;\n       \n       long stringTableAddrOffset = stringTable.getAddressOffset();\n        \n        Address address = this.aSpace.getAddress(stringTableAddrOffset);\n        Address end = address.addNoWrap(stringTable.getLength() - 1);\n        \n        while (address.compareTo(end) < 0) \n        {\n            int length = this.createString(address);\n            address = address.addNoWrap(length);\n        }\n    }\n    \n    protected void setupSymbolTable()\n    {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        \n        if (adapter.getSymbolTable(this.program) != null)\n        {\n            for (ElfSymbol elfSymbol : adapter.getSymbolTable(this.program).getSymbols()) \n            {\n                String symName = elfSymbol.getNameAsString();\n    \n                if (elfSymbol.getSectionHeaderIndex() == ElfSectionHeaderConstants.SHN_UNDEF && symName != null && !symName.isEmpty())\n                {\n                    // NOTE: We handle adding these symbols later\n                    this.undefSymbolCount++;\n                }\n                else\n                {\n                    Address address = this.aSpace.getAddress(this.nxo.getBaseAddress() + elfSymbol.getValue());\n                    this.evaluateElfSymbol(elfSymbol, address, false);\n                }\n            }\n        }\n    }\n    \n    protected void setupRelocations(TaskMonitor monitor) throws AddressOutOfBoundsException, NotFoundException, IOException, CancelledException {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        ByteProvider memoryProvider = adapter.getMemoryProvider();\n        BinaryReader memoryReader = adapter.getMemoryReader();\n        ImmutableList<NXRelocation> pltRelocs = adapter.getPltRelocations(this.program);\n        \n        if (pltRelocs.isEmpty())\n        {\n            Msg.info(this, \"No plt relocations found.\");\n            return;\n        }\n            \n        long pltGotStart = pltRelocs.get(0).offset;\n        long pltGotEnd = pltRelocs.get(pltRelocs.size() - 1).offset + adapter.getOffsetSize();\n        \n        if (adapter.getDynamicTable(this.program).containsDynamicValue(ElfDynamicType.DT_PLTGOT))\n        {\n            long pltGotOff = adapter.getDynamicTable(this.program).getDynamicValue(ElfDynamicType.DT_PLTGOT);\n            this.memBlockHelper.addSection(\".got.plt\", pltGotOff, pltGotOff, pltGotEnd - pltGotOff, true, false, false);\n        }\n        \n        // Only add .plt on aarch64\n        if (adapter.isAarch32())\n        {\n            return;\n        }\n        \n        int last = 12;\n        \n        while (true)\n        {\n            int pos = -1;\n            \n            for (int i = last; i < adapter.getSection(NXOSectionType.TEXT).getSize(); i++)\n            {\n                if (memoryReader.readInt(i) == 0xD61F0220)\n                {\n                    pos = i;\n                    break;\n                }\n            }\n            \n            if (pos == -1) break;\n            last = pos + 1;\n            if ((pos % 4) != 0) continue;\n            \n            int off = pos - 12;\n            long a = Integer.toUnsignedLong(memoryReader.readInt(off));\n            long b = Integer.toUnsignedLong(memoryReader.readInt(off + 4));\n            long c = Integer.toUnsignedLong(memoryReader.readInt(off + 8));\n            long d = Integer.toUnsignedLong(memoryReader.readInt(off + 12));\n\n            if (d == 0xD61F0220L && (a & 0x9f00001fL) == 0x90000010L && (b & 0xffe003ffL) == 0xf9400211L)\n            {\n                long base = off & ~0xFFFL;\n                long immhi = (a >> 5) & 0x7ffffL;\n                long immlo = (a >> 29) & 3;\n                long paddr = base + ((immlo << 12) | (immhi << 14));\n                long poff = ((b >> 10) & 0xfffL) << 3;\n                long target = paddr + poff;\n                if (pltGotStart <= target && target < pltGotEnd)\n                    this.pltEntries.add(new PltEntry(off, target));\n            }\n        }\n\n        if (!this.pltEntries.isEmpty()) {\n            long pltStart = this.pltEntries.get(0).off;\n            long pltEnd = this.pltEntries.get(this.pltEntries.size() - 1).off + 0x10;\n            this.memBlockHelper.addSection(\".plt\", pltStart, pltStart, pltEnd - pltStart, true, false, false);\n            // Disassemble the entire section, so AARCH64PltThunkAnalyzer works for functions within this binary.\n            disassembleRange(program.getImageBase().add(pltStart), program.getImageBase().add(pltEnd), program, monitor);\n        }\n        else {\n            // TODO: Find a way to locate the plt in CFI-enabled binaries.\n            Msg.error(this, \"No PLT entries found, does this binary have CFI enabled? This loader currently can't locate the plt in them.\");\n        }\n    }\n    \n    protected void createGlobalOffsetTable() throws AddressOutOfBoundsException\n    {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        ByteProvider memoryProvider = adapter.getMemoryProvider();\n        \n        // .got.plt needs to have been created first\n        long gotStartOff = adapter.getGotOffset() - this.nxo.getBaseAddress();\n        long gotSize = adapter.getGotSize();\n        \n        if (gotSize > 0)\n        {\n            Msg.info(this, String.format(\"Created got from 0x%X to 0x%X\", gotStartOff, gotStartOff + gotSize));\n            this.memBlockHelper.addSection(\".got\", gotStartOff, gotStartOff, gotSize, true, false, false);\n        }\n    }\n    \n    protected void performRelocations() throws MemoryAccessException, InvalidInputException, AddressOutOfBoundsException\n    {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        Map<Long, String> gotNameLookup = new HashMap<>(); \n        \n        // Relocations again\n        for (NXRelocation reloc : adapter.getRelocations(this.program)) \n        {\n            Address target = this.aSpace.getAddress(reloc.offset + this.nxo.getBaseAddress());\n            long originalValue = adapter.isAarch32() ? this.program.getMemory().getInt(target) : this.program.getMemory().getLong(target);\n            \n            if (reloc.r_type == ARM_ElfRelocationType.R_ARM_GLOB_DAT.typeId() ||\n                    reloc.r_type == ARM_ElfRelocationType.R_ARM_JUMP_SLOT.typeId() ||\n                    reloc.r_type == ARM_ElfRelocationType.R_ARM_ABS32.typeId()) \n                {\n                    if (reloc.sym == null) \n                    {\n                        Msg.error(this, String.format(\"Error: Relocation at %x failed\", target.getOffset()));\n                    } \n                    else \n                    {\n                        program.getMemory().setInt(target, (int)(reloc.sym.getValue() + this.nxo.getBaseAddress()));\n                    }\n                } \n            else if (reloc.r_type == ARM_ElfRelocationType.R_ARM_RELATIVE.typeId())\n            {\n                program.getMemory().setInt(target, (int)(program.getMemory().getInt(target) + this.nxo.getBaseAddress()));\n            }\n            else if (reloc.r_type == AARCH64_ElfRelocationType.R_AARCH64_GLOB_DAT.typeId() ||\n                reloc.r_type == AARCH64_ElfRelocationType.R_AARCH64_JUMP_SLOT.typeId() ||\n                reloc.r_type == AARCH64_ElfRelocationType.R_AARCH64_ABS64.typeId()) \n            {\n                if (reloc.sym == null) \n                {\n                    // Ignore these sorts of errors, the IDA loader fails on some relocations too.\n                    // It doesn't appear to be a Ghidra specific issue.\n                    //Msg.error(this, String.format(\"Error: Relocation at %x failed\", target.getOffset()));\n                } \n                else \n                {\n                    program.getMemory().setLong(target, reloc.sym.getValue() + this.nxo.getBaseAddress() + reloc.addend);\n                    \n                    if (reloc.addend == 0)\n                        gotNameLookup.put(reloc.offset, reloc.sym.getNameAsString());\n                }\n            } \n            else if (reloc.r_type == AARCH64_ElfRelocationType.R_AARCH64_RELATIVE.typeId()) \n            {\n                program.getMemory().setLong(target, this.nxo.getBaseAddress() + reloc.addend);\n            }\n            else if (reloc.r_type == R_FAKE_RELR) {\n                if (this.nxo.getAdapter().isAarch32()) {\n                    // TODO: Add RELRO support for 32-bit\n                    Msg.error(this, \"TODO: RELRO support for 32-bit\");\n                    continue;\n                }\n\n                program.getMemory().setLong(target, this.nxo.getBaseAddress() + originalValue);\n            }\n            else \n            {\n                Msg.info(this, String.format(\"TODO: r_type 0x%x\", reloc.r_type));\n            }\n            \n            long newValue = adapter.isAarch32() ? this.program.getMemory().getInt(target) : this.program.getMemory().getLong(target);\n            \n            // Store relocations for Ghidra's relocation table view\n            if (newValue != originalValue)\n            {\n                String symbolName = null;\n                \n                if (reloc.sym != null) \n                {\n                    symbolName = reloc.sym.getNameAsString();\n                }\n\n                // Status APPLIED: \"Relocation was applied successfully and resulted in the modification of memory bytes.\"\n                program.getRelocationTable().add(target, Relocation.Status.APPLIED,(int)reloc.r_type, new long[] { reloc.r_sym }, Longs.toByteArray(originalValue), symbolName);\n            }\n        }\n        \n        for (PltEntry entry : this.pltEntries)\n        {\n            if (gotNameLookup.containsKey(entry.target))\n            {\n                Address addr = this.aSpace.getAddress(this.nxo.getBaseAddress() + entry.off);\n                String name = gotNameLookup.get(entry.target);\n                if (name != null && !name.isEmpty())\n                {\n                    ExternalLocation extLoc = program.getExternalManager().getUniqueExternalLocation(Library.UNKNOWN, name);\n                    // AARCH64PltThunkAnalyzer won't be able to find a valid destination address for entries referencing external functions.\n                    if (extLoc != null) {\n                        Address lastAddr = addr.add(0x10 - 0x4);\n                        Instruction lastInstruction = program.getListing().getInstructionAt(lastAddr);\n\n                        // Make sure the last instruction of this function is a branch.\n                        // This check is also performed by AARCH64PltThunkAnalyzer.\n                        if (lastInstruction != null && lastInstruction.getMnemonicString().equals(\"br\")) {\n                            try {\n                                program.getFunctionManager().createThunkFunction(name, null, addr, new AddressSet(addr, lastAddr), extLoc.getFunction(), SourceType.IMPORTED);\n                                lastInstruction.setFlowOverride(FlowOverride.CALL_RETURN);\n                            } catch (OverlappingFunctionException e) {\n                                Msg.error(this, String.format(\"Couldn't create thunk function at %s: %s\", addr, e));\n                            }\n                        }\n                        else {\n                            // This shouldn't happen.\n                            Msg.warn(this, String.format(\"Couldn't find a valid last instruction for thunk function %s at: %s\", addr, lastAddr));\n                            createOneByteFunction(name, addr, false).setThunkedFunction(extLoc.getFunction());\n                        }\n                    }\n                    else {\n                        // Already defined functions are skipped by AARCH64PltThunkAnalyzer, so we only create a symbol.\n                        this.createSymbol(addr, name, false, false, null);\n                    }\n                }\n            }\n        }\n    }\n    \n    protected void setupImports(TaskMonitor monitor)\n    {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        this.processImports(monitor);\n        \n        if (this.undefSymbolCount == 0)\n            return;\n        \n        // Create the fake EXTERNAL block after everything else\n        long lastAddrOff = this.nxo.getBaseAddress(); \n        \n        for (MemoryBlock block : this.program.getMemory().getBlocks())\n        {\n            if (block.getEnd().getOffset() > lastAddrOff)\n                lastAddrOff = block.getEnd().getOffset();\n        }\n        \n        int undefEntrySize = 0x1000; // We create fake 0x1000 regions for each import\n        long externalBlockAddrOffset = ((lastAddrOff + 0xFFF) & ~0xFFF) + undefEntrySize; // plus 1 so we don't end up on the \"end\" symbol\n        \n        // Create the block where imports will be located\n        this.createExternalBlock(this.aSpace.getAddress(externalBlockAddrOffset), (long) this.undefSymbolCount * undefEntrySize);\n\n        // Handle imported symbols\n        if (adapter.getSymbolTable(this.program) != null)\n        {\n            for (ElfSymbol elfSymbol : adapter.getSymbolTable(this.program).getSymbols())\n            {\n                String symName = elfSymbol.getNameAsString();\n\n                if (elfSymbol.getSectionHeaderIndex() == ElfSectionHeaderConstants.SHN_UNDEF && symName != null && !symName.isEmpty())\n                {\n                    Address address = this.aSpace.getAddress(externalBlockAddrOffset);\n                    try {\n                        Field elfSymbolValue = elfSymbol.getClass().getDeclaredField(\"st_value\");\n                        elfSymbolValue.setAccessible(true);\n                        // Fix the value to be non-zero, instead pointing to our fake EXTERNAL block\n                        // make the symbol value relative to the image base, as that's how all other symbols are stored\n                        elfSymbolValue.set(elfSymbol, externalBlockAddrOffset - this.nxo.getBaseAddress());\n                    } catch (NoSuchFieldException | IllegalAccessException e) {\n                        Msg.error(this, \"Couldn't find or set st_value field in ElfSymbol.\", e);\n                    }\n                    this.evaluateElfSymbol(elfSymbol, address, true);\n                    externalBlockAddrOffset += undefEntrySize;\n                }\n            }\n        }\n    }\n    \n    private void createExternalBlock(Address addr, long size) \n    {\n        try \n        {\n            MemoryBlock block = this.program.getMemory().createUninitializedBlock(\"EXTERNAL\", addr, size, false);\n\n            // assume any value in external is writable.\n            block.setWrite(true);\n            block.setSourceName(\"Switch Loader\");\n            block.setComment(\"NOTE: This block is artificial and is used to make relocations work correctly\");\n        }\n        catch (Exception e) \n        {\n            Msg.error(this, \"Error creating external memory block: \" + \" - \" + e.getMessage());\n        }\n    }\n    \n    private void processImports(TaskMonitor monitor) \n    {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        \n        if (monitor.isCancelled())\n            return;\n\n        monitor.setMessage(\"Processing imports...\");\n\n        ExternalManager extManager = program.getExternalManager();\n        String[] neededLibs = adapter.getDynamicLibraryNames(this.program);\n        \n        for (String neededLib : neededLibs) \n        {\n            try {\n                extManager.setExternalPath(neededLib, null, false);\n            }\n            catch (InvalidInputException e) {\n                Msg.error(this, \"Bad library name: \" + neededLib);\n            }\n        }\n    }\n    \n    public Address createEntryFunction(String name, long entryAddr, TaskMonitor monitor) \n    {\n        Address entryAddress = this.aSpace.getAddress(entryAddr);\n\n        // TODO: Entry may refer to a pointer - make sure we have execute permission\n        MemoryBlock block = this.program.getMemory().getBlock(entryAddress);\n        \n        if (block == null || !block.isExecute()) \n        {\n            return entryAddress;\n        }\n\n        Function function = program.getFunctionManager().getFunctionAt(entryAddress);\n        \n        if (function != null) \n        {\n            program.getSymbolTable().addExternalEntryPoint(entryAddress);\n            return entryAddress; // symbol-based function already created\n        }\n\n        try \n        {\n            this.createOneByteFunction(name, entryAddress, true);\n        }\n        catch (Exception e) \n        {\n            Msg.error(this, \"Could not create symbol at entry point: \" + e);\n        }\n\n        return entryAddress;\n    }\n    \n    protected int createString(Address address) throws CodeUnitInsertionException\n    {\n        Data d = this.program.getListing().getDataAt(address);\n        \n        if (d == null || !TerminatedStringDataType.dataType.isEquivalent(d.getDataType())) \n        {\n            d = this.program.getListing().createData(address, TerminatedStringDataType.dataType, -1);\n        }\n        \n        return d.getLength();\n    }\n    \n    protected int createPointer(Address address) throws CodeUnitInsertionException\n    {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        Data d = this.program.getListing().getDataAt(address);\n        \n        if (d == null || !PointerDataType.dataType.isEquivalent(d.getDataType())) \n        {\n            d = this.program.getListing().createData(address, PointerDataType.dataType, adapter.getOffsetSize());\n        }\n        \n        return d.getLength();\n    }\n    \n    private void evaluateElfSymbol(ElfSymbol elfSymbol, Address address, boolean isFakeExternal)\n    {\n        try\n        {\n            if (elfSymbol.isSection()) {\n                // Do not add section symbols to program symbol table\n                return;\n            }\n    \n            String name = elfSymbol.getNameAsString();\n            if (name == null || name.isEmpty()) {\n                return;\n            }\n    \n            boolean isPrimary = (elfSymbol.getType() == ElfSymbol.STT_FUNC) ||\n                (elfSymbol.getType() == ElfSymbol.STT_OBJECT) || (elfSymbol.getSize() != 0);\n            // don't displace existing primary unless symbol is a function or object symbol\n            if (name.contains(\"@\")) {\n                isPrimary = false; // do not make version symbol primary\n            }\n            else if (!isPrimary && (elfSymbol.isGlobal() || elfSymbol.isWeak())) {\n                Symbol existingSym = program.getSymbolTable().getPrimarySymbol(address);\n                isPrimary = (existingSym == null);\n            }\n    \n            this.createSymbol(address, name, isPrimary, elfSymbol.isAbsolute(), null);\n    \n            // NOTE: treat weak symbols as global so that other programs may link to them.\n            // In the future, we may want additional symbol flags to denote the distinction\n            if ((elfSymbol.isGlobal() || elfSymbol.isWeak()) && !isFakeExternal) \n            {\n                program.getSymbolTable().addExternalEntryPoint(address);\n            }\n            \n            if (elfSymbol.getType() == ElfSymbol.STT_FUNC) \n            {\n                Function existingFunction = program.getFunctionManager().getFunctionAt(address);\n                if (existingFunction == null) {\n                    Function f = createOneByteFunction(null, address, false);\n                    if (f != null) {\n                        if (isFakeExternal && !f.isThunk()) {\n                            ExternalLocation extLoc = program.getExternalManager().addExtFunction(Library.UNKNOWN, name, null, SourceType.IMPORTED);\n                            f.setThunkedFunction(extLoc.getFunction());\n                            // revert thunk function symbol to default source\n                            Symbol s = f.getSymbol();\n                            if (s.getSource() != SourceType.DEFAULT) {\n                                program.getSymbolTable().removeSymbolSpecial(f.getSymbol());\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        catch (DuplicateNameException | InvalidInputException e)\n        {\n            e.printStackTrace();\n        }\n    }\n    \n    public Function createOneByteFunction(String name, Address address, boolean isEntry) \n    {\n        Function function = null;\n        try \n        {\n            FunctionManager functionMgr = program.getFunctionManager();\n            function = functionMgr.getFunctionAt(address);\n            if (function == null) {\n                function = functionMgr.createFunction(null, address, new AddressSet(address), SourceType.IMPORTED);\n            }\n        }\n        catch (Exception e) \n        {\n            Msg.error(this, \"Error while creating function at \" + address + \": \" + e.getMessage());\n        }\n\n        try \n        {\n            if (name != null) \n            {\n                createSymbol(address, name, true, false, null);\n            }\n            if (isEntry) {\n                program.getSymbolTable().addExternalEntryPoint(address);\n            }\n        }\n        catch (Exception e) {\n            Msg.error(this, \"Error while creating symbol \" + name + \" at \" + address + \": \" + e.getMessage());\n        }\n        return function;\n    }\n    \n    public Symbol createSymbol(Address addr, String name, boolean isPrimary, boolean pinAbsolute, Namespace namespace) throws InvalidInputException \n    {\n        // TODO: At this point, we should be marking as data or code\n        SymbolTable symbolTable = program.getSymbolTable();\n        Symbol sym = symbolTable.createLabel(addr, name, namespace, SourceType.IMPORTED);\n        if (isPrimary) {\n            checkPrimary(sym);\n        }\n        if (pinAbsolute && !sym.isPinned()) {\n            sym.setPinned(true);\n        }\n        return sym;\n    }\n\n    // Source: https://github.com/NationalSecurityAgency/ghidra/blob/de7c3eaee2a4bc993a402e371b039c2bb2d6c545/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfDefaultGotPltMarkup.java#L545\n    private void disassembleRange(Address start, Address end, Program program, TaskMonitor monitor)\n            throws CancelledException {\n        AddressSet set = new AddressSet(start, end);\n        Disassembler disassembler = Disassembler.getDisassembler(program, monitor, m -> {\n            /* silent */});\n        while (!set.isEmpty()) {\n            monitor.checkCancelled();\n            AddressSet disset = disassembler.disassemble(set.getMinAddress(), null, true);\n            if (disset.isEmpty()) {\n                // Stop on first error but discard error bookmark since\n                // some plt sections are partly empty and must rely\n                // on normal flow disassembly during analysis\n                program.getBookmarkManager()\n                        .removeBookmarks(set, BookmarkType.ERROR,\n                                Disassembler.ERROR_BOOKMARK_CATEGORY, monitor);\n                break;//we did not disassemble anything...\n            }\n            set.delete(disset);\n        }\n    }\n    \n    private Symbol checkPrimary(Symbol sym) \n    {\n        if (sym == null || sym.isPrimary()) \n        {\n            return sym;\n        }\n\n        String name = sym.getName();\n        Address addr = sym.getAddress();\n\n        if (name.indexOf(\"@\") > 0) { // <sym>@<version> or <sym>@@<version>\n            return sym; // do not make versioned symbols primary\n        }\n\n        // if starts with a $, probably a markup symbol, like $t,$a,$d\n        if (name.startsWith(\"$\")) {\n            return sym;\n        }\n\n        // if sym starts with a non-letter give preference to an existing symbol which does\n        if (!Character.isAlphabetic(name.codePointAt(0))) {\n            Symbol primarySymbol = program.getSymbolTable().getPrimarySymbol(addr);\n            if (primarySymbol != null && primarySymbol.getSource() != SourceType.DEFAULT &&\n                Character.isAlphabetic(primarySymbol.getName().codePointAt(0))) {\n                return sym;\n            }\n        }\n\n        SetLabelPrimaryCmd cmd = new SetLabelPrimaryCmd(addr, name, sym.getParentNamespace());\n        if (cmd.applyTo(program)) {\n            return program.getSymbolTable().getSymbol(name, addr, sym.getParentNamespace());\n        }\n\n        Msg.error(this, cmd.getStatusMsg());\n\n        return sym;\n    }\n    \n    public boolean hasImportedSymbol(Address addr)\n    {\n        for (Symbol sym : program.getSymbolTable().getSymbols(addr))\n        {\n            if (sym.getSource() == SourceType.IMPORTED)\n                return true;\n        }\n        \n        return false;\n    }\n    \n    protected void tryCreateDynBlock(String name, ElfDynamicType offsetType, ElfDynamicType sizeType)\n    {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        \n        try\n        {\n            if (adapter.getDynamicTable(this.program).containsDynamicValue(offsetType) && adapter.getDynamicTable(this.program).containsDynamicValue(sizeType))\n            {\n                long offset = adapter.getDynamicTable(this.program).getDynamicValue(offsetType);\n                long size = adapter.getDynamicTable(this.program).getDynamicValue(sizeType);\n                \n                if (size > 0)\n                {\n                    Msg.info(this, String.format(\"Created dyn block %s at 0x%X of size 0x%X\", name, offset, size));\n                    this.memBlockHelper.addSection(name, offset, offset, size, true, false, false);\n                }\n            }\n        }\n        catch (NotFoundException | AddressOutOfBoundsException e)\n        {\n            Msg.warn(this, String.format(\"Couldn't create dyn block %s. It may be absent.\", name), e);\n        }\n    }\n    \n    protected void tryCreateDynBlockWithRange(String name, ElfDynamicType start, ElfDynamicType end)\n    {\n        NXOAdapter adapter = this.nxo.getAdapter();\n        \n        try\n        {\n            if (adapter.getDynamicTable(this.program).containsDynamicValue(start) && adapter.getDynamicTable(this.program).containsDynamicValue(end))\n            {\n                long offset = adapter.getDynamicTable(this.program).getDynamicValue(start);\n                long size = adapter.getDynamicTable(this.program).getDynamicValue(end) - offset;\n                \n                if (size > 0)\n                {\n                    Msg.info(this, String.format(\"Created dyn block %s at 0x%X of size 0x%X\", name, offset, size));\n                    this.memBlockHelper.addSection(name, offset, offset, size, true, false, false);\n                }\n            }\n        }\n        catch (NotFoundException | AddressOutOfBoundsException e)\n        {\n            Msg.warn(this, String.format(\"Couldn't create dyn block %s. It may be absent.\", name), e);\n        }\n    }\n    \n    private static class PltEntry\n    {\n        long off;\n        long target;\n        \n        public PltEntry(long offset, long target)\n        {\n            this.off = offset;\n            this.target = target;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/kip1/KIP1Adapter.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.kip1;\n\nimport java.io.IOException;\n\nimport adubbz.nx.loader.nxo.MOD0Adapter;\nimport adubbz.nx.loader.nxo.NXOSection;\nimport adubbz.nx.loader.nxo.NXOSectionType;\nimport adubbz.nx.util.ByteUtil;\nimport ghidra.app.util.bin.ByteArrayProvider;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.program.model.listing.Program;\nimport ghidra.util.Msg;\n\npublic class KIP1Adapter extends MOD0Adapter\n{\n    protected KIP1Header kip1;\n    \n    protected ByteProvider memoryProvider;\n    protected NXOSection[] sections;\n    \n    public KIP1Adapter(Program program, ByteProvider fileProvider)\n    {\n        super(program, fileProvider);\n        \n        try\n        {\n            this.read();\n        }\n        catch (IOException e)\n        {\n            Msg.error(this, \"Failed to read KIP1\");\n            e.printStackTrace();\n        }\n    }\n    \n    private void read() throws IOException\n    {\n        this.kip1 = new KIP1Header(this.fileReader, 0x0);\n        \n        KIP1SectionHeader textHeader = this.kip1.getSectionHeader(NXOSectionType.TEXT);\n        KIP1SectionHeader rodataHeader = this.kip1.getSectionHeader(NXOSectionType.RODATA);\n        KIP1SectionHeader dataHeader = this.kip1.getSectionHeader(NXOSectionType.DATA);\n        \n        long textOffset = textHeader.getOutOffset();\n        long rodataOffset = rodataHeader.getOutOffset();\n        long dataOffset = dataHeader.getOutOffset();\n        long textSize = textHeader.getDecompressedSize();\n        long rodataSize = rodataHeader.getDecompressedSize();\n        long dataSize = dataHeader.getDecompressedSize();\n        \n        // The data section is last, so we use its offset + decompressed size\n        byte[] full = new byte[Math.toIntExact(dataOffset + dataSize)];\n        byte[] decompressedText;\n        byte[] decompressedRodata;\n        byte[] decompressedData;\n        \n        if (this.kip1.isSectionCompressed(NXOSectionType.TEXT))\n        {\n            byte[] compressedText = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.TEXT), this.kip1.getCompressedSectionSize(NXOSectionType.TEXT));\n            decompressedText = ByteUtil.kip1BlzDecompress(compressedText, Math.toIntExact(textSize));\n        }\n        else\n        {\n            decompressedText = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.TEXT), textSize);\n        }\n        \n        System.arraycopy(decompressedText, 0, full, Math.toIntExact(textOffset), Math.toIntExact(textSize));\n        \n        if (this.kip1.isSectionCompressed(NXOSectionType.RODATA))\n        {\n            byte[] compressedRodata = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.RODATA), this.kip1.getCompressedSectionSize(NXOSectionType.RODATA));\n            decompressedRodata = ByteUtil.kip1BlzDecompress(compressedRodata, Math.toIntExact(rodataSize));\n        }\n        else\n        {\n            decompressedRodata = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.RODATA), rodataSize);\n        }\n        \n        System.arraycopy(decompressedRodata, 0, full, Math.toIntExact(rodataOffset), Math.toIntExact(rodataSize));\n        \n        if (this.kip1.isSectionCompressed(NXOSectionType.DATA))\n        {\n            byte[] compressedData = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.DATA), this.kip1.getCompressedSectionSize(NXOSectionType.DATA));\n            decompressedData = ByteUtil.kip1BlzDecompress(compressedData, Math.toIntExact(dataSize));\n        }\n        else\n        {\n            decompressedData = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.DATA), dataSize);\n        }\n        \n        System.arraycopy(decompressedData, 0, full, Math.toIntExact(dataOffset), Math.toIntExact(dataSize));\n        this.memoryProvider = new ByteArrayProvider(full);\n        \n        this.sections = new NXOSection[3];\n        this.sections[NXOSectionType.TEXT.ordinal()] = new NXOSection(NXOSectionType.TEXT, textOffset, textSize);\n        this.sections[NXOSectionType.RODATA.ordinal()] = new NXOSection(NXOSectionType.RODATA, rodataOffset, rodataSize);\n        this.sections[NXOSectionType.DATA.ordinal()] = new NXOSection(NXOSectionType.DATA, dataOffset, dataSize);\n    }\n\n    @Override\n    public ByteProvider getMemoryProvider() \n    {\n        return this.memoryProvider;\n    }\n\n    @Override\n    public NXOSection[] getSections() \n    {\n        return this.sections;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/kip1/KIP1Header.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.kip1;\n\nimport java.io.IOException;\n\nimport adubbz.nx.common.InvalidMagicException;\nimport adubbz.nx.loader.nxo.NXOSectionType;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.util.Msg;\n\npublic class KIP1Header\n{\n    private String magic;\n    private String name;\n    private long tid;\n    private int processCategory;\n    private byte mainThreadPriority;\n    private byte defaultCpuCore;\n    private byte flags;\n    \n    private KIP1SectionHeader[] sectionHeaders = new KIP1SectionHeader[6];\n    \n    public KIP1Header(BinaryReader reader, int readerOffset)\n    {\n        long prevPointerIndex = reader.getPointerIndex();\n        \n        reader.setPointerIndex(readerOffset);\n        this.readHeader(reader);\n        \n        // Restore the previous pointer index\n        reader.setPointerIndex(prevPointerIndex);\n    }\n    \n    private void readHeader(BinaryReader reader)\n    {\n        try \n        {\n            this.magic = reader.readNextAsciiString(4);\n            \n            if (!this.magic.equals(\"KIP1\"))\n                throw new InvalidMagicException(\"KIP1\");\n            \n            this.name = reader.readNextAsciiString(12);\n            this.tid = reader.readNextLong();\n            this.processCategory = reader.readNextInt();\n            this.mainThreadPriority = reader.readNextByte();\n            this.defaultCpuCore = reader.readNextByte();\n            reader.readNextByte(); // Unused\n            this.flags = reader.readNextByte();\n            \n            for (int i = 0; i < this.sectionHeaders.length; i++)\n            {\n                this.sectionHeaders[i] = new KIP1SectionHeader(reader);\n            }\n        } \n        catch (IOException e) \n        {\n            Msg.error(this, \"Failed to read KIP1 header\");\n        }\n    }\n    \n    public KIP1SectionHeader getSectionHeader(NXOSectionType type)\n    {\n        return this.sectionHeaders[type.ordinal()];\n    }\n    \n    public long getSectionFileOffset(NXOSectionType type)\n    {\n        if (type == NXOSectionType.TEXT)\n            return 0x100;\n        else\n        {\n            NXOSectionType prevType = NXOSectionType.values()[type.ordinal() - 1];\n            KIP1SectionHeader prevHeader = this.getSectionHeader(prevType);\n            return this.getSectionFileOffset(prevType) + prevHeader.getCompressedSize();\n        }\n    }\n    \n    public long getCompressedSectionSize(NXOSectionType type)\n    {\n        return this.sectionHeaders[type.ordinal()].getCompressedSize();\n    }\n    \n    public boolean isSectionCompressed(NXOSectionType type)\n    {\n        int index = type.ordinal();\n        \n        if (index > 2)\n            return false;\n        \n        int flagMask = 1 << index;\n        return (this.flags & flagMask) > 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/kip1/KIP1SectionHeader.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\npackage adubbz.nx.loader.kip1;\n\nimport java.io.IOException;\n\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.util.Msg;\n\npublic class KIP1SectionHeader \n{\n    private long outOffset;\n    private long decompressedSize;\n    private long compressedSize;\n    private long attributes;\n    \n    public KIP1SectionHeader(BinaryReader reader)\n    {\n        this.readHeader(reader);\n    }\n    \n    private void readHeader(BinaryReader reader)\n    {\n        try\n        {\n            this.outOffset = reader.readNextUnsignedInt();\n            this.decompressedSize = reader.readNextUnsignedInt();\n            this.compressedSize = reader.readNextUnsignedInt();\n            this.attributes = reader.readNextUnsignedInt();\n        } \n        catch (IOException e) \n        {\n            Msg.error(this, \"Failed to read KIP1 section header\");\n        }\n    }\n    \n    public long getOutOffset()\n    {\n        return this.outOffset;\n    }\n    \n    public long getDecompressedSize()\n    {\n        return this.decompressedSize;\n    }\n    \n    /* \n     * NOTE: This will be the same as the decompressed size when\n     * no compression is applied.\n     */\n    public long getCompressedSize()\n    {\n        return this.compressedSize;\n    }\n    \n    public long getAttributes()\n    {\n        return this.attributes;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/knx/KNXAdapter.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.knx;\n\nimport java.io.IOException;\n\nimport adubbz.nx.loader.nxo.MOD0Adapter;\nimport adubbz.nx.loader.nxo.NXOSection;\nimport adubbz.nx.loader.nxo.NXOSectionType;\nimport ghidra.app.util.bin.ByteArrayProvider;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.app.util.bin.format.elf.ElfDynamicTable;\nimport ghidra.app.util.bin.format.elf.ElfDynamicType;\nimport ghidra.program.model.listing.Program;\nimport ghidra.util.Msg;\nimport ghidra.util.exception.NotFoundException;\n\n// We don't have a MOD0, but inherit from the adapter anyway to reduce redundancy\npublic class KNXAdapter extends MOD0Adapter\n{\n    protected KNXMapHeader map;\n    \n    protected ByteProvider memoryProvider;\n    protected NXOSection[] sections;\n    \n    public KNXAdapter(Program program, ByteProvider fileProvider)\n    {\n        super(program, fileProvider);\n        \n        try\n        {\n            this.read();\n        }\n        catch (IOException e)\n        {\n            Msg.error(this, \"Failed to read KNX\");\n            e.printStackTrace();\n        }\n    }\n    \n    private void read() throws IOException\n    {\n        Msg.info(this, \"Reading...\");\n        \n        this.fileReader.setPointerIndex(0);\n        \n        while (this.fileReader.getPointerIndex() < 0x2000)\n        {\n            long candidate = this.fileReader.readNextInt();\n            \n            if (candidate == 0xD51C403E)\n            {\n                break;\n            }\n        }\n        \n        if (this.fileReader.getPointerIndex() >= 0x2000)\n            throw new RuntimeException(\"Failed to find map offset\");\n        \n        long mapOffset = this.fileReader.getPointerIndex() - 0x34;\n        this.map = new KNXMapHeader(this.fileReader, (int)mapOffset);\n        \n        long textOffset = this.map.getTextFileOffset();\n        long rodataOffset = this.map.getRodataFileOffset();\n        long dataOffset = this.map.getDataFileOffset();\n        long textSize = this.map.getTextSize();\n        long rodataSize = this.map.getRodataSize();\n        long dataSize = this.map.getDataSize();\n\n        Msg.info(this, String.format(\"Text size: 0x%X\", textSize));\n        \n        // The data section is last, so we use its offset + decompressed size\n        byte[] full = new byte[Math.toIntExact(dataOffset + dataSize)];\n\n        byte[] text = this.fileProvider.readBytes(textOffset, textSize);\n        System.arraycopy(text, 0, full, Math.toIntExact(textOffset), Math.toIntExact(textSize));\n\n        byte[] rodata = this.fileProvider.readBytes(rodataOffset, rodataSize);\n        System.arraycopy(rodata, 0, full, Math.toIntExact(rodataOffset), Math.toIntExact(rodataSize));\n\n        byte[] data = this.fileProvider.readBytes(dataOffset, dataSize);\n        System.arraycopy(data, 0, full, Math.toIntExact(dataOffset), Math.toIntExact(dataSize));\n        this.memoryProvider = new ByteArrayProvider(full);\n        \n        this.sections = new NXOSection[3];\n        this.sections[NXOSectionType.TEXT.ordinal()] = new NXOSection(NXOSectionType.TEXT, textOffset, textSize);\n        this.sections[NXOSectionType.RODATA.ordinal()] = new NXOSection(NXOSectionType.RODATA, rodataOffset, rodataSize);\n        this.sections[NXOSectionType.DATA.ordinal()] = new NXOSection(NXOSectionType.DATA, dataOffset, dataSize);\n    }\n\n    @Override\n    public ByteProvider getMemoryProvider() \n    {\n        return this.memoryProvider;\n    }\n\n    @Override\n    public NXOSection[] getSections() \n    {\n        return this.sections;\n    }\n\n    @Override\n    public long getDynamicOffset() \n    {\n        return this.map.getDynamicOffset();\n    }\n\n    @Override\n    public long getBssOffset()\n    {\n        return this.map.getBssFileOffset();\n    }\n    \n    @Override\n    public long getBssSize()\n    {\n        return this.map.getBssSize();\n    }\n    \n    @Override\n    public long getGotOffset()\n    {\n        ElfDynamicTable dt = this.getDynamicTable(this.program);\n        \n        if (dt == null)\n            return 0;\n        \n        return dt.getAddressOffset() + dt.getLength();\n    }\n    \n    @Override\n    public long getGotSize()\n    {\n        ElfDynamicTable dt = this.getDynamicTable(this.program);\n        \n        if (dt == null || !dt.containsDynamicValue(ElfDynamicType.DT_INIT_ARRAY))\n            return 0;\n        \n        try \n        {\n            return this.program.getImageBase().getOffset() + dt.getDynamicValue(ElfDynamicType.DT_INIT_ARRAY) - this.getGotOffset();\n        } \n        catch (NotFoundException e) \n        {\n            return 0;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/knx/KNXMapHeader.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.knx;\n\nimport java.io.IOException;\n\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.util.Msg;\n\npublic class KNXMapHeader \n{\n    private long textOffset;\n    private long textEndOffset;\n    private long rodataOffset;\n    private long rodataEndOffset;\n    private long dataOffset;\n    private long dataEndOffset;\n    private long bssOffset;\n    private long bssEndOffset;\n    private long ini1Offset;\n    private long dynamicOffset;\n    private long initArrayOffset;\n    private long initArrayEndOffset;\n    \n    public KNXMapHeader(BinaryReader reader, int readerOffset)\n    {\n        long prevPointerIndex = reader.getPointerIndex();\n        \n        reader.setPointerIndex(readerOffset);\n        this.readHeader(reader);\n        \n        // Restore the previous pointer index\n        reader.setPointerIndex(prevPointerIndex);\n    }\n\n    private void readHeader(BinaryReader reader)\n    {\n        try \n        {\n            this.textOffset = reader.readNextUnsignedInt();\n            this.textEndOffset = reader.readNextUnsignedInt();\n            this.rodataOffset = reader.readNextUnsignedInt();\n            this.rodataEndOffset = reader.readNextUnsignedInt();\n            this.dataOffset = reader.readNextUnsignedInt();\n            this.dataEndOffset = reader.readNextUnsignedInt();\n            this.bssOffset = reader.readNextUnsignedInt();\n            this.bssEndOffset = reader.readNextUnsignedInt();\n            this.ini1Offset = reader.readNextUnsignedInt();\n            this.dynamicOffset = reader.readNextUnsignedInt();\n            this.initArrayOffset = reader.readNextUnsignedInt();\n            this.initArrayEndOffset = reader.readNextUnsignedInt();\n        } \n        catch (IOException e) \n        {\n            Msg.error(this, \"Failed to read KNX Map header\");\n        }\n    }\n    \n    public long getTextFileOffset()\n    {\n        return this.textOffset;\n    }\n    \n    public long getTextSize()\n    {\n        return this.textEndOffset - this.textOffset;\n    }\n    \n    public long getRodataFileOffset()\n    {\n        return this.rodataOffset;\n    }\n    \n    public long getRodataSize()\n    {\n        return this.rodataEndOffset - this.rodataOffset;\n    }\n    \n    public long getDataFileOffset()\n    {\n        return this.dataOffset;\n    }\n    \n    public long getDataSize()\n    {\n        return this.dataEndOffset - this.dataOffset;\n    }\n    \n    public long getBssFileOffset()\n    {\n        return this.bssOffset;\n    }\n    \n    public long getBssSize()\n    {\n        return this.bssEndOffset - this.bssOffset;\n    }\n    \n    public long getDynamicOffset()\n    {\n        return this.dynamicOffset;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nro0/NRO0Adapter.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nro0;\n\nimport java.io.IOException;\n\nimport adubbz.nx.loader.nxo.MOD0Adapter;\nimport adubbz.nx.loader.nxo.NXOSection;\nimport adubbz.nx.loader.nxo.NXOSectionType;\nimport ghidra.app.util.bin.ByteArrayProvider;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.program.model.listing.Program;\nimport ghidra.util.Msg;\n\npublic class NRO0Adapter extends MOD0Adapter\n{\n    protected NRO0Header nro0;\n    \n    protected ByteProvider memoryProvider;\n    protected NXOSection[] sections;\n    \n    public NRO0Adapter(Program program, ByteProvider fileProvider)\n    {\n        super(program, fileProvider);\n        \n        try\n        {\n            this.read();\n        }\n        catch (IOException e)\n        {\n            Msg.error(this, \"Failed to read NRO0\");\n            e.printStackTrace();\n        }\n    }\n    \n    private void read() throws IOException\n    {\n        this.nro0 = new NRO0Header(this.fileReader, 0x0);\n        \n        NRO0SectionHeader textHeader = this.nro0.getSectionHeader(NXOSectionType.TEXT);\n        NRO0SectionHeader rodataHeader = this.nro0.getSectionHeader(NXOSectionType.RODATA);\n        NRO0SectionHeader dataHeader = this.nro0.getSectionHeader(NXOSectionType.DATA);\n\n        long textOffset = textHeader.getFileOffset();\n        long rodataOffset = rodataHeader.getFileOffset();\n        long dataOffset = dataHeader.getFileOffset();\n        long textSize = textHeader.getSize();\n        long rodataSize = rodataHeader.getSize();\n        long dataSize = dataHeader.getSize();\n\n        // The data section is last, so we use its offset + decompressed size\n        byte[] full = new byte[Math.toIntExact(dataOffset + dataSize)];\n\n        byte[] text = this.fileProvider.readBytes(textHeader.getFileOffset(), textSize);\n        System.arraycopy(text, 0, full, Math.toIntExact(textOffset), Math.toIntExact(textSize));\n\n        byte[] rodata = this.fileProvider.readBytes(rodataHeader.getFileOffset(), rodataSize);\n        System.arraycopy(rodata, 0, full, Math.toIntExact(rodataOffset), Math.toIntExact(rodataSize));\n\n        byte[] data = this.fileProvider.readBytes(dataHeader.getFileOffset(), dataSize);\n        System.arraycopy(data, 0, full, Math.toIntExact(dataOffset), Math.toIntExact(dataSize));\n        this.memoryProvider = new ByteArrayProvider(full);\n        \n        this.sections = new NXOSection[3];\n        this.sections[NXOSectionType.TEXT.ordinal()] = new NXOSection(NXOSectionType.TEXT, textOffset, textSize);\n        this.sections[NXOSectionType.RODATA.ordinal()] = new NXOSection(NXOSectionType.RODATA, rodataOffset, rodataSize);\n        this.sections[NXOSectionType.DATA.ordinal()] = new NXOSection(NXOSectionType.DATA, dataOffset, dataSize);\n    }\n\n    @Override\n    public ByteProvider getMemoryProvider() \n    {\n        return this.memoryProvider;\n    }\n\n    @Override\n    public NXOSection[] getSections() \n    {\n        return this.sections;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nro0/NRO0Header.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nro0;\n\nimport java.io.IOException;\n\nimport adubbz.nx.common.InvalidMagicException;\nimport adubbz.nx.loader.nxo.NXOSectionType;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.util.Msg;\n\npublic class NRO0Header \n{\n    private long mod0Offset;\n    private String magic;\n    private long version;\n    private long size;\n    private long flags;\n    private NRO0SectionHeader textHeader;\n    private NRO0SectionHeader rodataHeader;\n    private NRO0SectionHeader dataHeader;\n    private long bssSize;\n    private byte[] buildId;\n    private NRO0SectionHeader apiInfo;\n    private NRO0SectionHeader dynstr;\n    private NRO0SectionHeader dynsym;\n\n    public NRO0Header(BinaryReader reader, int readerOffset)\n    {\n        long prevPointerIndex = reader.getPointerIndex();\n        \n        reader.setPointerIndex(readerOffset);\n        this.readHeader(reader);\n        \n        // Restore the previous pointer index\n        reader.setPointerIndex(prevPointerIndex);\n    }\n\n    private void readHeader(BinaryReader reader)\n    {\n        try \n        {\n            reader.readNextUnsignedInt(); // Reserved\n            this.mod0Offset = reader.readNextUnsignedInt();\n            reader.readNextLong(); // Padding\n            this.magic = reader.readNextAsciiString(4);\n            \n            if (!this.magic.equals(\"NRO0\"))\n                throw new InvalidMagicException(\"NRO0\");\n            \n            this.version = reader.readNextUnsignedInt();\n            this.size = reader.readNextUnsignedInt();\n            this.flags = reader.readNextUnsignedInt();\n            this.textHeader = new NRO0SectionHeader(reader);\n            this.rodataHeader = new NRO0SectionHeader(reader);\n            this.dataHeader = new NRO0SectionHeader(reader);\n            this.bssSize = reader.readNextUnsignedInt();\n            reader.readNextUnsignedInt(); // Reserved\n            this.buildId = reader.readNextByteArray(0x20);\n            reader.readNextUnsignedInt(); // Reserved\n            this.apiInfo = new NRO0SectionHeader(reader);\n            this.dynstr = new NRO0SectionHeader(reader);\n            this.dynsym = new NRO0SectionHeader(reader);\n        } \n        catch (IOException e) \n        {\n            Msg.error(this, \"Failed to read NRO0 header\");\n        }\n    }\n\n    public NRO0SectionHeader getSectionHeader(NXOSectionType type)\n    {\n        return switch (type) {\n            case TEXT -> this.textHeader;\n            case RODATA -> this.rodataHeader;\n            case DATA -> this.dataHeader;\n            default -> null;\n        };\n    }\n\n    public long getBssSize()\n    {\n        return this.bssSize;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nro0/NRO0SectionHeader.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nro0;\n\nimport java.io.IOException;\n\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.util.Msg;\n\npublic class NRO0SectionHeader \n{\n    private long fileOffset;\n    private long size;\n\n    public NRO0SectionHeader(BinaryReader reader)\n    {\n        this.readHeader(reader);\n    }\n\n    private void readHeader(BinaryReader reader)\n    {\n        try\n        {\n            this.fileOffset = reader.readNextUnsignedInt();\n            this.size = reader.readNextUnsignedInt();\n        } \n        catch (IOException e) \n        {\n            Msg.error(this, \"Failed to read NRO0 section header\");\n        }\n    }\n\n    public long getFileOffset()\n    {\n        return this.fileOffset;\n    }\n\n    public long getSize()\n    {\n        return this.size;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nso0/NSO0Adapter.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nso0;\n\nimport java.io.IOException;\n\nimport adubbz.nx.loader.nxo.MOD0Adapter;\nimport adubbz.nx.loader.nxo.NXOSection;\nimport adubbz.nx.loader.nxo.NXOSectionType;\nimport ghidra.app.util.bin.ByteArrayProvider;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.program.model.listing.Program;\nimport ghidra.util.Msg;\nimport net.jpountz.lz4.LZ4Factory;\nimport net.jpountz.lz4.LZ4FastDecompressor;\n\npublic class NSO0Adapter extends MOD0Adapter\n{\n    protected NSO0Header nso0;\n    \n    protected ByteProvider memoryProvider;\n    protected NXOSection[] sections;\n    \n    public NSO0Adapter(Program program, ByteProvider fileProvider)\n    {\n        super(program, fileProvider);\n        \n        try\n        {\n            this.read();\n        }\n        catch (IOException e)\n        {\n            Msg.error(this, \"Failed to read NSO0\");\n            e.printStackTrace();\n        }\n    }\n    \n    private void read() throws IOException\n    {\n        this.nso0 = new NSO0Header(this.fileReader, 0x0);\n        \n        LZ4Factory factory = LZ4Factory.fastestInstance();\n        LZ4FastDecompressor decompressor = factory.fastDecompressor();\n        \n        NSO0SectionHeader textHeader = this.nso0.getSectionHeader(NXOSectionType.TEXT);\n        NSO0SectionHeader rodataHeader = this.nso0.getSectionHeader(NXOSectionType.RODATA);\n        NSO0SectionHeader dataHeader = this.nso0.getSectionHeader(NXOSectionType.DATA);\n\n        long textOffset = textHeader.getMemoryOffset();\n        long rodataOffset = rodataHeader.getMemoryOffset();\n        long dataOffset = dataHeader.getMemoryOffset();\n        long textSize = textHeader.getDecompressedSize();\n        long rodataSize = rodataHeader.getDecompressedSize();\n        long dataSize = dataHeader.getDecompressedSize();\n        \n        // The data section is last, so we use its offset + decompressed size\n        byte[] full = new byte[Math.toIntExact(dataOffset + dataSize)];\n        byte[] decompressedText;\n        byte[] decompressedRodata;\n        byte[] decompressedData;\n        \n        if (this.nso0.isSectionCompressed(NXOSectionType.TEXT))\n        {\n            byte[] compressedText = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.TEXT), this.nso0.getCompressedSectionSize(NXOSectionType.TEXT));\n            decompressedText = new byte[Math.toIntExact(textSize)];\n            decompressor.decompress(compressedText, decompressedText);\n        }\n        else\n        {\n            decompressedText = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.TEXT), textSize);\n        }\n        \n        System.arraycopy(decompressedText, 0, full, Math.toIntExact(textOffset), Math.toIntExact(textSize));\n        \n        if (this.nso0.isSectionCompressed(NXOSectionType.RODATA))\n        {\n            byte[] compressedRodata = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.RODATA), this.nso0.getCompressedSectionSize(NXOSectionType.RODATA));\n            decompressedRodata = new byte[Math.toIntExact(rodataSize)];\n            decompressor.decompress(compressedRodata, decompressedRodata);\n        }\n        else\n        {\n            decompressedRodata = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.RODATA), rodataSize);\n        }\n        \n        System.arraycopy(decompressedRodata, 0, full, Math.toIntExact(rodataOffset), Math.toIntExact(rodataSize));\n        \n        if (this.nso0.isSectionCompressed(NXOSectionType.DATA))\n        {\n            byte[] compressedData = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.DATA), this.nso0.getCompressedSectionSize(NXOSectionType.DATA));\n            decompressedData = new byte[Math.toIntExact(dataSize)];\n            decompressor.decompress(compressedData, decompressedData);\n        }\n        else\n        {\n            decompressedData = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.DATA), dataSize);\n        }\n        \n        System.arraycopy(decompressedData, 0, full, Math.toIntExact(dataOffset), Math.toIntExact(dataSize));\n        this.memoryProvider = new ByteArrayProvider(full);\n        \n        this.sections = new NXOSection[3];\n        this.sections[NXOSectionType.TEXT.ordinal()] = new NXOSection(NXOSectionType.TEXT, textOffset, textSize);\n        this.sections[NXOSectionType.RODATA.ordinal()] = new NXOSection(NXOSectionType.RODATA, rodataOffset, rodataSize);\n        this.sections[NXOSectionType.DATA.ordinal()] = new NXOSection(NXOSectionType.DATA, dataOffset, dataSize);\n    }\n\n    @Override\n    public ByteProvider getMemoryProvider() \n    {\n        return this.memoryProvider;\n    }\n\n    @Override\n    public NXOSection[] getSections() \n    {\n        return this.sections;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nso0/NSO0Header.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nso0;\n\nimport java.io.IOException;\n\nimport adubbz.nx.common.InvalidMagicException;\nimport adubbz.nx.loader.nxo.NXOSectionType;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.util.Msg;\n\npublic class NSO0Header \n{\n    private String magic;\n    private long version;\n    private long flags;\n    private NSO0SectionHeader textHeader;\n    private long moduleOffset;\n    private NSO0SectionHeader rodataHeader;\n    private long moduleFileSize;\n    private NSO0SectionHeader dataHeader;\n    private long bssSize;\n    private byte[] buildId;\n    private long compressedTextSize;\n    private long compressedRodataSize;\n    private long compressedDataSize;\n    \n    public NSO0Header(BinaryReader reader, int readerOffset)\n    {\n        long prevPointerIndex = reader.getPointerIndex();\n        \n        reader.setPointerIndex(readerOffset);\n        this.readHeader(reader);\n        \n        // Restore the previous pointer index\n        reader.setPointerIndex(prevPointerIndex);\n    }\n    \n    private void readHeader(BinaryReader reader)\n    {\n        try \n        {\n            this.magic = reader.readNextAsciiString(4);\n            \n            if (!this.magic.equals(\"NSO0\"))\n                throw new InvalidMagicException(\"NSO0\");\n            \n            this.version = reader.readNextUnsignedInt();\n            reader.readNextUnsignedInt(); // Reserved\n            this.flags = reader.readNextUnsignedInt();\n            this.textHeader = new NSO0SectionHeader(reader);\n            this.moduleOffset = reader.readNextUnsignedInt();\n            this.rodataHeader = new NSO0SectionHeader(reader);\n            this.moduleFileSize = reader.readNextUnsignedInt();\n            this.dataHeader = new NSO0SectionHeader(reader);\n            this.bssSize = reader.readNextUnsignedInt();\n            this.buildId = reader.readNextByteArray(0x20);\n            this.compressedTextSize = reader.readNextUnsignedInt();\n            this.compressedRodataSize = reader.readNextUnsignedInt();\n            this.compressedDataSize = reader.readNextUnsignedInt();\n        } \n        catch (IOException e) \n        {\n            Msg.error(this, \"Failed to read NSO0 header\");\n        }\n    }\n    \n    public NSO0SectionHeader getSectionHeader(NXOSectionType type)\n    {\n        return switch (type) {\n            case TEXT -> this.textHeader;\n            case RODATA -> this.rodataHeader;\n            case DATA -> this.dataHeader;\n            default -> null;\n        };\n    }\n    \n    public long getSectionFileOffset(NXOSectionType type)\n    {\n        return switch (type) {\n            case TEXT -> this.textHeader.getFileOffset();\n            case RODATA -> this.rodataHeader.getFileOffset();\n            case DATA -> this.dataHeader.getFileOffset();\n            default -> 0;\n        };\n    }\n    \n    public long getCompressedSectionSize(NXOSectionType type)\n    {\n        return switch (type) {\n            case TEXT -> this.compressedTextSize;\n            case RODATA -> this.compressedRodataSize;\n            case DATA -> this.compressedDataSize;\n            case BSS -> this.bssSize;\n        };\n    }\n    \n    public boolean isSectionCompressed(NXOSectionType type)\n    {\n        int index = type.ordinal();\n        \n        if (index > 2)\n            return false;\n        \n        int flagMask = 1 << index;\n        return (this.flags & flagMask) > 0;\n    }\n    \n    public long getBssSize()\n    {\n        return this.bssSize;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nso0/NSO0SectionHeader.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nso0;\n\nimport java.io.IOException;\n\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.util.Msg;\n\npublic class NSO0SectionHeader \n{\n    private long fileOffset;\n    private long memoryOffset;\n    private long decompressedSize;\n    \n    public NSO0SectionHeader(BinaryReader reader)\n    {\n        this.readHeader(reader);\n    }\n    \n    private void readHeader(BinaryReader reader)\n    {\n        try\n        {\n            this.fileOffset = reader.readNextUnsignedInt();\n            this.memoryOffset = reader.readNextUnsignedInt();\n            this.decompressedSize = reader.readNextUnsignedInt();\n        } \n        catch (IOException e) \n        {\n            Msg.error(this, \"Failed to read NSO0 section header\");\n        }\n    }\n    \n    public long getFileOffset()\n    {\n        return this.fileOffset;\n    }\n    \n    public long getMemoryOffset()\n    {\n        return this.memoryOffset;\n    }\n    \n    public long getDecompressedSize()\n    {\n        return this.decompressedSize;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/MOD0Adapter.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nxo;\n\nimport adubbz.nx.common.ElfCompatibilityProvider;\nimport adubbz.nx.common.InvalidMagicException;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.app.util.bin.format.elf.ElfDynamic;\nimport ghidra.app.util.bin.format.elf.ElfDynamicType;\nimport ghidra.app.util.bin.format.elf.ElfException;\nimport ghidra.program.model.listing.Program;\nimport ghidra.program.model.mem.MemoryBlock;\nimport ghidra.util.Msg;\nimport ghidra.util.exception.NotFoundException;\n\nimport java.io.IOException;\nimport java.util.List;\n\n/*\n * An adapter implementation for binaries with a MOD0 section.\n */\npublic abstract class MOD0Adapter extends NXOAdapter\n{\n    protected Program program;\n    protected MOD0Header mod0;\n    \n    public MOD0Adapter(Program program, ByteProvider fileProvider)\n    {\n        super(fileProvider);\n        this.program = program;\n    }\n    \n    @Override\n    public long getDynamicOffset()\n    {\n        MOD0Header mod0 = this.getMOD0();\n        \n        if (mod0 == null)\n            return 0;\n        \n        return mod0.getDynamicOffset();\n    }\n    \n    @Override\n    public long getDynamicSize()\n    {\n        assert this.program != null;\n        \n        if (this.getElfProvider(this.program).getDynamicTable() != null)\n            return this.getElfProvider(this.program).getDynamicTable().getLength();\n        \n        long dtSize = 0;\n        var reader = new BinaryReader(this.getMemoryProvider(), true);\n        reader.setPointerIndex(this.getDynamicOffset());\n        \n        try\n        {\n            while (true) \n            {\n                ElfDynamic dyn = new ElfDynamic(reader, new ElfCompatibilityProvider.DummyElfHeader(this.isAarch32()));\n                dtSize += dyn.sizeof();\n                if (dyn.getTag() == ElfDynamicType.DT_NULL.value) \n                {\n                    break;\n                }\n            }\n        }\n        catch (IOException e)\n        {\n            Msg.error(this, \"Failed to get dynamic size\", e);\n        } catch (ElfException e) {\n            Msg.error(this, \"Can't construct DummyElfHeader\", e);\n        }\n\n        return dtSize;\n    }\n    \n    @Override\n    public long getBssOffset()\n    {\n        MOD0Header mod0 = this.getMOD0();\n        \n        if (mod0 == null)\n            return 0;\n        \n        return mod0.getBssStartOffset();\n    }\n    \n    @Override\n    public long getBssSize()\n    {\n        MOD0Header mod0 = this.getMOD0();\n        \n        if (mod0 == null)\n            return 0;\n        \n        return mod0.getBssSize();\n    }\n\n    private long gotOffset = 0;\n    private long gotSize = 0;\n\n    private boolean findGot() {\n        assert this.program != null;\n\n        if (this.gotOffset > 0 && this.gotSize > 0) {\n            return true;\n        }\n\n        MOD0Header mod0 = this.getMOD0();\n\n        if (mod0 == null) {\n            return false;\n        }\n\n        if (mod0.hasLibnxExtension()) {\n            this.gotOffset = mod0.getLibnxGotStart() + this.program.getImageBase().getOffset();\n            this.gotSize = mod0.getLibnxGotEnd() - mod0.getLibnxGotStart();\n            return true;\n        }\n\n        boolean good = false;\n        List<Long> relocationOffsets = this.getRelocations(program).stream().map(reloc -> reloc.offset).toList();\n        MemoryBlock gotPlt = this.program.getMemory().getBlock(\".got.plt\");\n        long gotStart = gotPlt != null ? gotPlt.getEnd().getOffset() + 1 - this.program.getImageBase().getOffset() : this.getDynamicOffset() + this.getDynamicSize();\n        long gotEnd = gotStart + this.getOffsetSize();\n        long initArrayValue;\n\n        try {\n            initArrayValue = this.getDynamicTable(program).getDynamicValue(ElfDynamicType.DT_INIT_ARRAY);\n        } catch (NotFoundException ignored) {\n            initArrayValue = -1;\n        }\n\n        while ((relocationOffsets.contains(gotEnd) || (gotPlt == null && initArrayValue != -1 && gotEnd < initArrayValue))\n                && (initArrayValue == -1 || gotEnd < initArrayValue || gotStart > initArrayValue)) {\n            good = true;\n            gotEnd += this.getOffsetSize();\n        }\n\n        if (good) {\n            this.gotOffset = this.program.getImageBase().getOffset() + gotStart;\n            this.gotSize = gotEnd - gotStart;\n            return true;\n        }\n\n        Msg.error(this, \"Failed to find .got section.\");\n        return false;\n    }\n    \n    @Override\n    public long getGotOffset()\n    {\n        if (this.findGot()) {\n            return this.gotOffset;\n        }\n\n        return 0;\n    }\n    \n    @Override\n    public long getGotSize()\n    {\n        if (this.findGot()) {\n            return this.gotSize;\n        }\n\n        return 0;\n    }\n    \n    public MOD0Header getMOD0()\n    {\n        if (this.mod0 != null)\n            return this.mod0;\n        \n        try \n        {\n            long mod0Offset = this.getMemoryReader().readUnsignedInt(this.getSection(NXOSectionType.TEXT).getOffset() + 4);\n        \n            if (mod0Offset >= this.getMemoryProvider().length())\n                throw new IllegalArgumentException(\"Mod0 offset is outside the binary!\");\n            \n            this.mod0 = new MOD0Header(this.getMemoryReader(), mod0Offset, mod0Offset);\n            return this.mod0;\n        }\n        catch (InvalidMagicException e)\n        {\n            Msg.error(this, \"Invalid MOD0 magic.\", e);\n        }\n        catch (IOException e) \n        {\n            Msg.error(this, \"Failed to read MOD0.\", e);\n        }\n        \n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/MOD0Header.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nxo;\n\nimport java.io.IOException;\n\nimport adubbz.nx.common.InvalidMagicException;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.util.Msg;\n\npublic class MOD0Header \n{\n    private String magic;\n    private long dynamicOffset;\n    private long bssStartOffset;\n    private long bssEndOffset;\n    private long ehFrameHdrStartOffset;\n    private long ehFrameHdrEndOffset;\n    private long runtimeModuleOffset;\n    \n    // libnx extensions\n    private String lnxMagic;\n    private long lnxGotStart;\n    private long lnxGotEnd;\n    \n    public MOD0Header(BinaryReader reader, long readerOffset, long mod0StartOffset) throws InvalidMagicException, IOException\n    {\n        long prevPointerIndex = reader.getPointerIndex();\n        \n        reader.setPointerIndex(readerOffset);\n        this.readHeader(reader, mod0StartOffset);\n        \n        // Restore the previous pointer index\n        reader.setPointerIndex(prevPointerIndex);\n    }\n    \n    private void readHeader(BinaryReader reader, long mod0StartOffset) throws InvalidMagicException, IOException\n    {\n        this.magic = reader.readNextAsciiString(4);\n        \n        if (!this.magic.equals(\"MOD0\"))\n            throw new InvalidMagicException(\"MOD0\");\n        \n        this.dynamicOffset = mod0StartOffset + reader.readNextUnsignedInt();\n        this.bssStartOffset = mod0StartOffset + reader.readNextUnsignedInt();\n        this.bssEndOffset = mod0StartOffset + reader.readNextUnsignedInt();\n        this.ehFrameHdrStartOffset = mod0StartOffset + reader.readNextUnsignedInt();\n        this.ehFrameHdrEndOffset = mod0StartOffset + reader.readNextUnsignedInt();\n        this.runtimeModuleOffset = mod0StartOffset + reader.readNextUnsignedInt();\n        \n        this.lnxMagic = reader.readNextAsciiString(4);\n        \n        if (this.lnxMagic.equals(\"LNY0\"))\n        {\n            Msg.info(this, \"Detected Libnx MOD0 extension\");\n            this.lnxGotStart = mod0StartOffset + reader.readNextUnsignedInt();\n            this.lnxGotEnd = mod0StartOffset + reader.readNextUnsignedInt();\n        }\n    }\n    \n    public long getDynamicOffset() \n    {\n        return this.dynamicOffset;\n    }\n\n    public long getBssStartOffset()\n    {\n        return this.bssStartOffset;\n    }\n    \n    public long getBssEndOffset()\n    {\n        return this.bssEndOffset;\n    }\n    \n    public long getBssSize()\n    {\n        return this.bssEndOffset - this.bssStartOffset;\n    }\n    \n    public long getEhFrameHdrStartOffset()\n    {\n        return this.ehFrameHdrStartOffset;\n    }\n    \n    public long getEhFrameHdrEndOffset()\n    {\n        return this.ehFrameHdrEndOffset;\n    }\n    \n    public long getRuntimeModuleOffset()\n    {\n        return this.runtimeModuleOffset;\n    }\n    \n    // libnx extensions\n    public boolean hasLibnxExtension()\n    {\n        return this.lnxMagic.equals(\"LNY0\");\n    }\n    \n    public long getLibnxGotStart()\n    {\n        return this.lnxGotStart;\n    }\n    \n    public long getLibnxGotEnd()\n    {\n        return this.lnxGotEnd;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/NXO.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nxo;\n\nimport ghidra.program.model.listing.Program;\n\npublic class NXO \n{\n    private NXOAdapter adapter;\n    \n    protected final long baseAddress;\n    public NXO(Program program, NXOAdapter adapter, long baseAddress)\n    {\n        this.adapter = adapter;\n        this.baseAddress = baseAddress;\n    }\n    \n    public NXOAdapter getAdapter()\n    {\n        return this.adapter;\n    }\n    \n    public long getBaseAddress()\n    {\n        return this.baseAddress;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/NXOAdapter.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nxo;\n\nimport java.io.IOException;\n\nimport adubbz.nx.util.LegacyByteProviderWrapper;\nimport com.google.common.collect.ImmutableList;\n\nimport adubbz.nx.common.ElfCompatibilityProvider;\nimport adubbz.nx.common.NXRelocation;\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.app.util.bin.format.elf.ElfDynamicTable;\nimport ghidra.app.util.bin.format.elf.ElfStringTable;\nimport ghidra.app.util.bin.format.elf.ElfSymbolTable;\nimport ghidra.program.model.listing.Program;\nimport ghidra.util.Msg;\n\n/**\n * Abstracts away the differences between different Switch file formats.\n */\npublic abstract class NXOAdapter \n{\n    protected ByteProvider fileProvider;\n    protected BinaryReader fileReader;\n    protected BinaryReader memoryReader;\n    protected ElfCompatibilityProvider elfProvider;\n    \n    public NXOAdapter(ByteProvider fileProvider)\n    {\n        this.fileProvider = fileProvider;\n        this.fileReader = new BinaryReader(this.fileProvider, true);\n    }\n    \n    public abstract ByteProvider getMemoryProvider();\n    \n    public BinaryReader getMemoryReader()\n    {\n        if (this.memoryReader != null)\n            return this.memoryReader;\n        \n        this.memoryReader = new BinaryReader(this.getMemoryProvider(), true);\n        return this.memoryReader;\n    }\n    \n    public abstract long getDynamicOffset();\n    public abstract long getDynamicSize();\n    \n    public abstract long getBssOffset();\n    public abstract long getBssSize();\n    \n    public abstract long getGotOffset();\n    public abstract long getGotSize();\n    \n    public boolean isAarch32()\n    {\n        long dynamicOffset = this.getDynamicOffset();\n        \n        try\n        {\n            return this.getMemoryReader().readLong(dynamicOffset) > 0xFFFFFFFFL || this.getMemoryReader().readLong(dynamicOffset + 0x10) > 0xFFFFFFFFL;\n        }\n        catch (IOException e)\n        {\n            Msg.error(this, \"Failed to check if aarch32\", e);\n        }\n        \n        return false;\n    }\n    \n    public int getOffsetSize()\n    {\n        return this.isAarch32() ? 4 : 8;\n    }\n    \n    public NXOSection getSection(NXOSectionType type)\n    {\n        return this.getSections()[type.ordinal()];\n    }\n    \n    public abstract NXOSection[] getSections();\n    \n    public ElfDynamicTable getDynamicTable(Program program)\n    {\n        return this.getElfProvider(program).getDynamicTable();\n    }\n    \n    public ElfStringTable getStringTable(Program program)\n    {\n        return this.getElfProvider(program).getStringTable();\n    }\n    \n    public ElfSymbolTable getSymbolTable(Program program)\n    {\n        return this.getElfProvider(program).getSymbolTable();\n    }\n    \n    public String[] getDynamicLibraryNames(Program program) \n    {\n        return this.getElfProvider(program).getDynamicLibraryNames();\n    }\n    \n    public ImmutableList<NXRelocation> getRelocations(Program program)\n    {\n        return ImmutableList.copyOf(this.getElfProvider(program).getRelocations());\n    }\n    \n    public ImmutableList<NXRelocation> getPltRelocations(Program program)\n    {\n        return ImmutableList.copyOf(this.getElfProvider(program).getPltRelocations());\n    }\n    \n    public ElfCompatibilityProvider getElfProvider(Program program)\n    {\n        if (this.elfProvider != null)\n            return this.elfProvider;\n        \n        long baseAddress = program.getImageBase().getOffset();\n        long memoryProviderLength = 0x0;\n        \n        try \n        {\n            memoryProviderLength = this.getMemoryProvider().length();\n        } \n        catch (IOException e) \n        {\n            Msg.error(this, \"Failed to get memory provider length\", e);\n        }\n\n        this.elfProvider = new ElfCompatibilityProvider(program, new LegacyByteProviderWrapper(this.getMemoryProvider(), -baseAddress, memoryProviderLength), this.isAarch32());\n        \n        return this.elfProvider;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/NXOSection.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.loader.nxo;\n\npublic class NXOSection \n{\n    private long offset;\n    private long size;\n    \n    public NXOSection(NXOSectionType type, long offset, long size)\n    {\n        this.offset = offset;\n        this.size = size;\n    }\n    \n    public long getOffset()\n    {\n        return this.offset;\n    }\n    \n    public long getSize()\n    {\n        return this.size;\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/NXOSectionType.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\npackage adubbz.nx.loader.nxo;\n\npublic enum NXOSectionType \n{\n    TEXT, RODATA, DATA, BSS\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/util/ByteUtil.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\npackage adubbz.nx.util;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\n\nimport ghidra.util.Msg;\n\npublic class ByteUtil \n{\n    public static byte[] kip1BlzDecompress(byte[] compressed, int decompressedSize)\n    {\n        int uncompressedAdditionalSize = ByteBuffer.wrap(compressed, compressed.length - 4, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();\n        int headerSize = ByteBuffer.wrap(compressed, compressed.length - 8, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();\n        int compressedAndHeaderSize = ByteBuffer.wrap(compressed, compressed.length - 12, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();\n        \n        int compressedStart = compressed.length - compressedAndHeaderSize;\n        int compressedOffset = compressedAndHeaderSize - headerSize;\n        int outOffset = compressedAndHeaderSize + uncompressedAdditionalSize;\n        \n        byte[] out = new byte[decompressedSize];\n        System.arraycopy(compressed, 0, out, 0, compressed.length);\n        \n        while (outOffset > 0)\n        {\n            byte control = out[compressedStart + --compressedOffset];\n            \n            for (int i = 0; i < 8; i++)\n            {\n                if ((control & 0x80) > 0)\n                {\n                    if (compressedOffset < 2)\n                        throw new IndexOutOfBoundsException(\"Compression out of bounds!\");\n                    \n                    compressedOffset -= 2;\n                    \n                    // Java has no concept of unsigned bytes, so when converting them to ints it'll think they're sometimes\n                    // negative. We obviously don't want this.\n                    int segmentValue = (Byte.toUnsignedInt(out[compressedStart + compressedOffset + 1]) << 8) | Byte.toUnsignedInt(out[compressedStart + compressedOffset]);\n                    int segmentSize = ((segmentValue >> 12) & 0xF) + 3;\n                    int segmentOffset = (segmentValue & 0x0FFF) + 3;\n                    \n                    if (outOffset < segmentSize)\n                    {\n                        /* Kernel restricts segment copy to stay in bounds. */\n                        segmentSize = outOffset;\n                    }\n                    \n                    outOffset -= segmentSize;\n                    \n                    for (int j = 0; j < segmentSize; j++)\n                    {\n                        out[compressedStart + outOffset + j] = out[compressedStart + outOffset + j + segmentOffset];\n                    }\n                }\n                else\n                {\n                    if (compressedOffset < 1)\n                        throw new IndexOutOfBoundsException(\"Compression out of bounds!\");\n                    \n                    out[compressedStart + --outOffset] = out[compressedStart + --compressedOffset];\n                }\n                \n                control <<= 1;\n                \n                if (outOffset == 0)\n                    break;\n            }\n        }\n        \n        return out;\n    }\n    \n    public static void logBytes(byte[] data)\n    {\n        StringBuilder sb = new StringBuilder();\n        int lineWidth = 0;\n        \n        for (byte b : data) \n        {\n            sb.append(String.format(\"%02X \", b));\n            lineWidth++;\n            \n            if (lineWidth >= 16)\n            {\n                sb.append(\"\\n\");\n                lineWidth = 0;\n            }\n        }\n        \n        Msg.info(null, sb.toString());\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/util/FullMemoryByteProvider.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.util;\n\nimport ghidra.app.util.bin.MemoryByteProvider;\nimport ghidra.program.model.listing.Program;\n\npublic class FullMemoryByteProvider extends MemoryByteProvider\n{\n    public FullMemoryByteProvider(Program program) \n    {\n        super(program.getMemory(), program.getAddressFactory().getDefaultAddressSpace().getAddress(0));\n    }\n    \n    @Override\n    public long length()\n    {\n        return memory.getMaxAddress().getOffset();\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/util/LegacyBinaryReader.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\npackage adubbz.nx.util;\n\nimport java.io.IOException;\n\nimport ghidra.app.util.bin.BinaryReader;\nimport ghidra.app.util.bin.ByteProvider;\n\npublic class LegacyBinaryReader extends BinaryReader\n{\n    public LegacyBinaryReader(ByteProvider provider, boolean isLittleEndian)\n    {\n        super(provider, isLittleEndian);\n    }\n\n    // readAsciiString no longer works correctly as of Ghidra 9.1. Here we revert back to the old version\n    @Override\n    public String readAsciiString(long index) throws IOException \n    {\n        StringBuilder builder = new StringBuilder();\n        while (true) {\n            if (index == this.getByteProvider().length()) {\n                // reached the end of the bytes and found no non-ascii data\n                break;\n            }\n            byte b = this.getByteProvider().readByte(index++);\n            if ((b >= 32) && (b <= 126)) {\n                builder.append((char) b);\n            }\n            else {\n                break;\n            }\n        }\n        return builder.toString().trim();\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/util/LegacyByteProviderWrapper.java",
    "content": "package adubbz.nx.util;\n\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.app.util.bin.ByteProviderWrapper;\n\nimport java.io.IOException;\n\npublic class LegacyByteProviderWrapper extends ByteProviderWrapper {\n\n    public LegacyByteProviderWrapper(ByteProvider provider, long subOffset, long subLength) {\n        super(provider, subOffset, subLength);\n    }\n\n    @Override\n    public boolean isValidIndex(long index) {\n        return (0 <= index && subOffset + index < subLength) && provider.isValidIndex(subOffset + index);\n    }\n\n    @Override\n    public byte readByte(long index) throws IOException {\n        if (index < 0 || subOffset + index >= subLength) {\n            throw new IOException(\"Invalid index: \" + index);\n        }\n        return provider.readByte(subOffset + index);\n    }\n\n    @Override\n    public byte[] readBytes(long index, long length) throws IOException {\n        if (index < 0 || subOffset + index >= subLength) {\n            throw new IOException(\"Invalid index: \" + index);\n        }\n        if (subOffset + index + length > subLength) {\n            throw new IOException(\"Unable to read past EOF: \" + index + \", \" + length);\n        }\n        return provider.readBytes(subOffset + index, length);\n    }\n}\n"
  },
  {
    "path": "src/main/java/adubbz/nx/util/UIUtil.java",
    "content": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\npackage adubbz.nx.util;\n\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport ghidra.program.database.ProgramDB;\nimport ghidra.program.model.address.Address;\nimport ghidra.program.model.listing.Group;\nimport ghidra.program.model.listing.Program;\nimport ghidra.program.model.listing.ProgramFragment;\nimport ghidra.program.model.listing.ProgramModule;\nimport ghidra.util.exception.NotFoundException;\n\npublic class UIUtil \n{\n    private static final int SORT_BY_NAME = 1;\n    private static final int SORT_BY_ADDRESS = 2;\n    \n    private static final GroupComparator ADDR_COMPARATOR = new GroupComparator(SORT_BY_ADDRESS);\n    \n    public static void sortProgramTree(Program program)\n    {\n        ProgramDB db = (ProgramDB)program;\n        ProgramModule programTreeModule = db.getTreeManager().getRootModule(\"Program Tree\");\n        \n        if (programTreeModule == null)\n            return;\n        \n        try \n        {\n            sortModule(programTreeModule);\n        } \n        catch (NotFoundException ignored)\n        {\n        }\n    }\n    \n    private static void sortModule(ProgramModule parent) throws NotFoundException\n    {\n        List<Group> list = new ArrayList<>();\n        Group[] kids = parent.getChildren();\n\n        for (Group kid : kids) {\n            list.add(kid);\n            if (kid instanceof ProgramModule) {\n                sortModule((ProgramModule) kid);\n            }\n        }\n\n        list.sort(ADDR_COMPARATOR);\n\n        for (int i = 0; i < list.size(); i++) \n        {\n            Group group = list.get(i);\n            parent.moveChild(group.getName(), i);\n        }\n    }\n    \n    private static class GroupComparator implements Comparator<Group> \n    {\n        private int sortType;\n\n        GroupComparator(int sortType) \n        {\n            this.sortType = sortType;\n        }\n\n        @Override\n        public int compare(Group g1, Group g2) \n        {\n            if (sortType == SORT_BY_ADDRESS) \n            {\n                Address addr1;\n                Address addr2;\n                if (g1 instanceof ProgramFragment) {\n                    addr1 = ((ProgramFragment) g1).getMinAddress();\n                }\n                else {\n                    ProgramModule m = (ProgramModule) g1;\n                    addr1 = m.getAddressSet().getMinAddress();\n                }\n                if (g2 instanceof ProgramFragment) {\n                    addr2 = ((ProgramFragment) g2).getMinAddress();\n                }\n                else {\n                    ProgramModule m = (ProgramModule) g2;\n                    addr2 = m.getAddressSet().getMinAddress();\n                }\n                if (addr1 == null && addr2 == null) {\n                    return 0;\n                }\n                if (addr1 != null && addr2 == null) {\n                    return -1;\n                }\n                if (addr1 == null) {\n                    return 1;\n                }\n                return addr1.compareTo(addr2);\n            }\n            return g1.getName().compareTo(g2.getName());\n        }\n\n    }\n}\n"
  }
]