Full Code of Adubbz/Ghidra-Switch-Loader for AI

master 0dd9b1db595f cached
46 files
213.8 KB
51.0k tokens
262 symbols
1 requests
Download .txt
Showing preview only (229K chars total). Download the full file or copy to clipboard to get everything.
Repository: Adubbz/Ghidra-Switch-Loader
Branch: master
Commit: 0dd9b1db595f
Files: 46
Total size: 213.8 KB

Directory structure:
gitextract_z2gawarw/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── dep-updates-am.yml
│       └── publish.yml
├── .gitignore
├── LICENSE.txt
├── Module.manifest
├── README.md
├── build.gradle
├── extension.properties
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src/
    └── main/
        └── java/
            └── adubbz/
                └── nx/
                    ├── analyzer/
                    │   ├── IPCAnalyzer.java
                    │   └── ipc/
                    │       ├── IPCEmulator.java
                    │       └── IPCTrace.java
                    ├── common/
                    │   ├── ElfCompatibilityProvider.java
                    │   ├── InvalidMagicException.java
                    │   └── NXRelocation.java
                    ├── loader/
                    │   ├── SwitchLoader.java
                    │   ├── common/
                    │   │   ├── MemoryBlockHelper.java
                    │   │   └── NXProgramBuilder.java
                    │   ├── kip1/
                    │   │   ├── KIP1Adapter.java
                    │   │   ├── KIP1Header.java
                    │   │   └── KIP1SectionHeader.java
                    │   ├── knx/
                    │   │   ├── KNXAdapter.java
                    │   │   └── KNXMapHeader.java
                    │   ├── nro0/
                    │   │   ├── NRO0Adapter.java
                    │   │   ├── NRO0Header.java
                    │   │   └── NRO0SectionHeader.java
                    │   ├── nso0/
                    │   │   ├── NSO0Adapter.java
                    │   │   ├── NSO0Header.java
                    │   │   └── NSO0SectionHeader.java
                    │   └── nxo/
                    │       ├── MOD0Adapter.java
                    │       ├── MOD0Header.java
                    │       ├── NXO.java
                    │       ├── NXOAdapter.java
                    │       ├── NXOSection.java
                    │       └── NXOSectionType.java
                    └── util/
                        ├── ByteUtil.java
                        ├── FullMemoryByteProvider.java
                        ├── LegacyBinaryReader.java
                        ├── LegacyByteProviderWrapper.java
                        └── UIUtil.java

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - directory: /
    open-pull-requests-limit: 5
    package-ecosystem: github-actions
    schedule:
      interval: weekly


================================================
FILE: .github/workflows/dep-updates-am.yml
================================================
name: Dependency updates auto-merge
on: pull_request

permissions:
  pull-requests: write
  contents: write
  checks: read

jobs:
  auto_merge:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:
      - name: Enable auto-merge for dependency update PRs
        run: gh pr merge --auto -s "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}


================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish release artifacts

on:
  workflow_dispatch:
    inputs:
      tag:
        description: Release git tag
        type: string
        required: true
  push:
  pull_request:

permissions:
  contents: write

jobs:
  build_and_publish:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        ghidra: [ "12.0.1" ]
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Setup Java
        uses: actions/setup-java@v5
        with:
          distribution: "temurin"
          java-version: "21"

      - name: Setup Ghidra ${{ matrix.ghidra }}
        uses: antoniovazquezblanco/setup-ghidra@v2.1.0
        with:
          version: ${{ matrix.ghidra }}

      - name: Build extension
        run: ./gradlew

      - uses: svenstaro/upload-release-action@v2
        if: github.event_name == 'push' && contains(github.ref, 'refs/tags/v')
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: dist/SwitchLoader-*.zip
          tag: ${{ github.ref }}
          overwrite: true
          file_glob: true

      - uses: svenstaro/upload-release-action@v2
        if: github.event_name == 'workflow_dispatch'
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: dist/SwitchLoader-*.zip
          tag: ${{ inputs.tag }}
          overwrite: true
          file_glob: true


================================================
FILE: .gitignore
================================================
eclipse/
.gradle/
dist/
build/


================================================
FILE: LICENSE.txt
================================================
Copyright 2019 Adubbz

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.

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.


================================================
FILE: Module.manifest
================================================


================================================
FILE: README.md
================================================
# Ghidra Switch Loader

A loader for Ghidra supporting a variety of Nintendo Switch file formats.

## Building

- Ensure you have ``JAVA_HOME`` set to the path of your JDK 21 installation.
- Set ``GHIDRA_INSTALL_DIR`` to your Ghidra install directory. This can be done in one of the following ways:
  - **Windows**: Running ``set GHIDRA_INSTALL_DIR=<Absolute path to Ghidra without quotations>``
  - **macos/Linux**: Running ``export GHIDRA_INSTALL_DIR=<Absolute path to Ghidra>``
  - Using ``-PGHIDRA_INSTALL_DIR=<Absolute path to Ghidra>`` when running ``./gradlew``
  - Adding ``GHIDRA_INSTALL_DIR`` to your Windows environment variables.
- Run ``./gradlew``
- You'll find the output zip file inside `/dist`

## Installation

- Start Ghidra and use the "Install Extensions" dialog (``File -> Install Extensions...``).
- Press the ``+`` button in the upper right corner.
- Select the zip file in the file browser, then restart Ghidra.


================================================
FILE: build.gradle
================================================
// Builds a Ghidra Extension for a given Ghidra installation.
//
// An absolute path to the Ghidra installation directory must be supplied either by setting the 
// GHIDRA_INSTALL_DIR environment variable or Gradle project property:
//
//     > export GHIDRA_INSTALL_DIR=<Absolute path to Ghidra> 
//     > gradle
//
//         or
//
//     > gradle -PGHIDRA_INSTALL_DIR=<Absolute path to Ghidra>
//
// Gradle should be invoked from the directory of the project to build.  Please see the
// application.gradle.version property in <GHIDRA_INSTALL_DIR>/Ghidra/application.properties
// for the correction version of Gradle to use for the Ghidra installation you specify.

//----------------------START "DO NOT MODIFY" SECTION------------------------------
apply plugin: 'java'
apply plugin: 'eclipse'
def ghidraInstallDir

if (System.env.GHIDRA_INSTALL_DIR) {
    ghidraInstallDir = System.env.GHIDRA_INSTALL_DIR
}
else if (project.hasProperty("GHIDRA_INSTALL_DIR")) {
    ghidraInstallDir = project.getProperty("GHIDRA_INSTALL_DIR")
}

if (ghidraInstallDir) {
    apply from: new File(ghidraInstallDir).getCanonicalPath() + "/support/buildExtension.gradle"
}
else {
    throw new GradleException("GHIDRA_INSTALL_DIR is not defined!")
}
//----------------------END "DO NOT MODIFY" SECTION-------------------------------

def getGitHash = {
    def stdout = new ByteArrayOutputStream()
    exec {
        if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
            commandLine 'cmd', '/c', 'git', 'rev-parse', '--short', 'HEAD'
        } else {
            commandLine 'git', 'rev-parse', '--short', 'HEAD'
        }
        standardOutput = stdout
    }
    return stdout.toString().trim()
}

repositories {
    mavenCentral()
}

configurations {
    localDeps
}

def docs = file(ghidraInstallDir+'/docs/GhidraAPI_javadoc.zip')
def ghidraUserDir = System.getProperty("user.home") + "/.ghidra/.${DISTRO_PREFIX}_${RELEASE_NAME}"

dependencies {
    implementation 'commons-primitives:commons-primitives:1.0'
    implementation group: 'at.yawk.lz4', name: 'lz4-java', version: '1.10.2'
    localDeps group: 'at.yawk.lz4', name: 'lz4-java', version: '1.10.2'

    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/patch', include: "**/*.jar")
    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Configurations', include: "**/*.jar")
    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Features', include: "**/*.jar")
    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Framework', include: "**/*.jar")
    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Processors', include: "**/*.jar")
    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Debug', include: "**/*.jar")
    runtimeOnly fileTree(dir: ghidraInstallDir + '/Ghidra/Extensions',
            include: "**/*.jar", exclude: project.name)
    runtimeOnly fileTree(dir: ghidraUserDir + "/Extensions",
            include: "**/*.jar", exclude: project.name)
}

eclipse {
    classpath {
        downloadJavadoc = true
        downloadSources = true
        file {
            whenMerged {
                for (entry in entries) {
                    if (entry.path.contains('jar')) {
                        File folder = new File(entry.getPath()).getParentFile();
                        for (File file : folder.listFiles()) {
                            if (file.getName().endsWith(".zip")) {
                                if (file.getName().contains("-src")) {
                                    entry.setSourcePath(it.fileReference(file));
                                }
                                entry.setJavadocPath(it.fileReference(docs));
                            }
                        }
                    }
                }
                entries.add(
                        new org.gradle.plugins.ide.eclipse.model.Library(
                                it.fileReference(new File(projectDir, '/data'))
                        )
                )
            }
        }
    }
}

buildExtension {
    exclude 'gradle*'
    exclude '.github/**'

    archiveBaseName = "${project.name}-${project.version}-${getGitHash()}-Ghidra_${ghidra_version}".replace(' ', '_')
}


================================================
FILE: extension.properties
================================================
name=@extname@
description=A loader for Nintendo Switch file formats.
author=Adubbz
createdOn=9/3/2019
version=@extversion@


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https://services.gradle.org/distributions/gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


================================================
FILE: gradle.properties
================================================
version=1.6.0


================================================
FILE: gradlew
================================================
#!/bin/sh

#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

##############################################################################
#
#   Gradle start up script for POSIX generated by Gradle.
#
#   Important for running:
#
#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
#       noncompliant, but you have some other compliant shell such as ksh or
#       bash, then to run this script, type that shell name before the whole
#       command line, like:
#
#           ksh Gradle
#
#       Busybox and similar reduced shells will NOT work, because this script
#       requires all of these POSIX shell features:
#         * functions;
#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
#         * compound commands having a testable exit status, especially «case»;
#         * various built-in commands including «command», «set», and «ulimit».
#
#   Important for patching:
#
#   (2) This script targets any POSIX shell, so it avoids extensions provided
#       by Bash, Ksh, etc; in particular arrays are avoided.
#
#       The "traditional" practice of packing multiple parameters into a
#       space-separated string is a well documented source of bugs and security
#       problems, so this is (mostly) avoided, by progressively accumulating
#       options in "$@", and eventually passing that to Java.
#
#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
#       see the in-line comments for details.
#
#       There are tweaks for specific operating systems such as AIX, CygWin,
#       Darwin, MinGW, and NonStop.
#
#   (3) This script is generated from the Groovy template
#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
#       within the Gradle project.
#
#       You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: $0 may be a link
app_path=$0

# Need this for daisy-chained symlinks.
while
    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
    [ -h "$app_path" ]
do
    ls=$( ls -ld "$app_path" )
    link=${ls#*' -> '}
    case $link in             #(
      /*)   app_path=$link ;; #(
      *)    app_path=$APP_HOME$link ;;
    esac
done

APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
    echo "$*"
} >&2

die () {
    echo
    echo "$*"
    echo
    exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in                #(
  CYGWIN* )         cygwin=true  ;; #(
  Darwin* )         darwin=true  ;; #(
  MSYS* | MINGW* )  msys=true    ;; #(
  NONSTOP* )        nonstop=true ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar


# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD=$JAVA_HOME/jre/sh/java
    else
        JAVACMD=$JAVA_HOME/bin/java
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD=java
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
    case $MAX_FD in #(
      max*)
        MAX_FD=$( ulimit -H -n ) ||
            warn "Could not query maximum file descriptor limit"
    esac
    case $MAX_FD in  #(
      '' | soft) :;; #(
      *)
        ulimit -n "$MAX_FD" ||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
    esac
fi

# Collect all arguments for the java command, stacking in reverse order:
#   * args from the command line
#   * the main class name
#   * -classpath
#   * -D...appname settings
#   * --module-path (only if needed)
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )

    JAVACMD=$( cygpath --unix "$JAVACMD" )

    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    for arg do
        if
            case $arg in                                #(
              -*)   false ;;                            # don't mess with options #(
              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
                    [ -e "$t" ] ;;                      #(
              *)    false ;;
            esac
        then
            arg=$( cygpath --path --ignore --mixed "$arg" )
        fi
        # Roll the args list around exactly as many times as the number of
        # args, so each arg winds up back in the position where it started, but
        # possibly modified.
        #
        # NB: a `for` loop captures its iteration list before it begins, so
        # changing the positional parameters here affects neither the number of
        # iterations, nor the values presented in `arg`.
        shift                   # remove old arg
        set -- "$@" "$arg"      # push replacement arg
    done
fi

# Collect all arguments for the java command;
#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
#     shell script including quotes and variable substitutions, so put them in
#     double quotes to make sure that they get re-expanded; and
#   * put everything else in single quotes, so that it's not re-expanded.

set -- \
        "-Dorg.gradle.appname=$APP_BASE_NAME" \
        -classpath "$CLASSPATH" \
        org.gradle.wrapper.GradleWrapperMain \
        "$@"

# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
    die "xargs is not available"
fi

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
#   set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- $(
        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
        xargs -n1 |
        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
        tr '\n' ' '
    )" '"$@"'

exec "$JAVACMD" "$@"


================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem      https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem

@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: settings.gradle
================================================
rootProject.name = "SwitchLoader"

================================================
FILE: src/main/java/adubbz/nx/analyzer/IPCAnalyzer.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.analyzer;

import adubbz.nx.analyzer.ipc.IPCEmulator;
import adubbz.nx.analyzer.ipc.IPCTrace;
import adubbz.nx.common.ElfCompatibilityProvider;
import adubbz.nx.common.NXRelocation;
import adubbz.nx.loader.SwitchLoader;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import generic.stl.Pair;
import ghidra.app.services.AbstractAnalyzer;
import ghidra.app.services.AnalyzerType;
import ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants;
import ghidra.app.util.demangler.DemangledObject;
import ghidra.app.util.demangler.DemanglerUtil;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOutOfBoundsException;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.listing.CommentType;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
import org.apache.commons.compress.utils.Lists;
import org.python.google.common.collect.HashBiMap;
import org.python.google.common.collect.Sets;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static adubbz.nx.common.ElfCompatibilityProvider.R_FAKE_RELR;

public class IPCAnalyzer extends AbstractAnalyzer 
{
    public IPCAnalyzer() 
    {
        super("(Switch) IPC Analyzer", "Locates and labels IPC vtables, s_Tables and implementation functions.", AnalyzerType.INSTRUCTION_ANALYZER);
    
        this.setSupportsOneTimeAnalysis();
    }

    @Override
    public boolean getDefaultEnablement(Program program) 
    {
        return false;
    }

    @Override
    public boolean canAnalyze(Program program) 
    {
        return program.getExecutableFormat().equals(SwitchLoader.SWITCH_NAME);
    }

    @Override
    public void registerOptions(Options options, Program program) 
    {
        // TODO: Symbol options
    }

    @Override
    public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
    {
        Memory memory = program.getMemory();
        MemoryBlock text = memory.getBlock(".text");
        MemoryBlock rodata = memory.getBlock(".rodata");
        MemoryBlock data = memory.getBlock(".data");
        ElfCompatibilityProvider elfCompatProvider = new ElfCompatibilityProvider(program, false);
        
        Msg.info(this, "Beginning IPC analysis...");
        
        if (text == null || rodata == null || data == null)
            return true;
        
        try
        {
            List<Address> vtAddrs = this.locateIpcVtables(program, elfCompatProvider);
            List<IPCVTableEntry> vtEntries = this.createVTableEntries(program, elfCompatProvider, vtAddrs);
            HashBiMap<Address, Address> sTableProcessFuncMap = this.locateSTables(program, elfCompatProvider);
            Multimap<Address, IPCTrace> processFuncTraces = this.emulateProcessFunctions(program, monitor, sTableProcessFuncMap.values());
            HashBiMap<Address, IPCVTableEntry> procFuncVtMap = this.matchVtables(vtEntries, sTableProcessFuncMap.values(), processFuncTraces);
            this.markupIpc(program, monitor, vtEntries, sTableProcessFuncMap, processFuncTraces, procFuncVtMap);
        }
        catch (Exception e)
        {
            Msg.error(this, "Failed to analyze binary IPC.", e);
            return false;
        }
        
        return true;
    }
    
    private List<Address> locateIpcVtables(Program program, ElfCompatibilityProvider elfProvider) throws MemoryAccessException, AddressOutOfBoundsException, IOException
    {
        List<Address> out = Lists.newArrayList();
        Address baseAddr = program.getImageBase();
        AddressSpace aSpace = program.getAddressFactory().getDefaultAddressSpace();
        Memory mem = program.getMemory();
        SymbolTable symbolTable = program.getSymbolTable();
        
        Map<String, Address> knownVTabAddrs = new HashMap<>();
        Map<Address, Address> gotDataSyms = this.getGotDataSyms(program, elfProvider);
        
        if (gotDataSyms.isEmpty())
        {
            Msg.warn(this, "Failed to locate vtables - No got data symbols found!");
            return out;
        }
        
        Msg.info(this, "Locating IPC vtables...");
        
        // NOTE: We can't get the .<bla> block and check if it contains an address, as there may be multiple
        // blocks with the same name, which Ghidra doesn't account for.
        
        // Locate some initial vtables based on RTTI
        for (Address vtAddr : gotDataSyms.values()) 
        {
            MemoryBlock vtBlock = mem.getBlock(vtAddr);
            
            // vtables are only found in the data block
            if (vtBlock == null || !vtBlock.getName().equals(".data"))
                continue;
            
            try
            {
                Address rttiAddr = aSpace.getAddress(mem.getLong(vtAddr.add(8)));
                MemoryBlock rttiBlock = mem.getBlock(rttiAddr);
                
                // RTTI is only found in the data block
                if (rttiBlock == null || !rttiBlock.getName().equals(".data"))
                    continue;

                Address thisAddr = aSpace.getAddress(mem.getLong(rttiAddr.add(0x8)));
                MemoryBlock thisBlock = mem.getBlock(thisAddr);
                
                if (thisBlock == null || thisBlock.getName().equals(".rodata"))
                    continue;

                String symbol = elfProvider.getReader().readAsciiString(thisAddr.getOffset());

                if (symbol.isEmpty() || symbol.length() > 512)
                    continue;

                if (symbol.contains("UnmanagedServiceObject") || symbol.equals("N2nn2sf4cmif6server23CmifServerDomainManager6DomainE"))
                {
                    knownVTabAddrs.put(symbol, vtAddr);
                }
            }
            catch (MemoryAccessException e) // Skip entries with out of bounds offsets
            {
                continue;
            }
        }
        
        if (knownVTabAddrs.isEmpty())
        {
            Msg.warn(this, "Failed to locate vtables - No known addresses found!");
            return out;
        }
            
        // All IServiceObjects share a common non-overridable virtual function at vt + 0x20
        // and thus that value can be used to distinguish a virtual table vs a non-virtual table.
        // Here we locate the address of that function.
        long knownAddress = 0;
        
        for (Address addr : knownVTabAddrs.values())
        {
            long curKnownAddr = mem.getLong(addr.add(0x20));
            
            if (knownAddress == 0)
            {
                knownAddress = curKnownAddr; 
            }
            else if (knownAddress != curKnownAddr) 
                return out;
        }
        
        Msg.info(this, String.format("Known service address: 0x%x", knownAddress));
        
        // Use the known function to find all IPC vtables
        for (Address vtAddr : gotDataSyms.values()) 
        {
            MemoryBlock vtBlock = mem.getBlock(vtAddr);
            
            try
            {
                if (vtBlock != null && vtBlock.getName().equals(".data"))
                {
                    if (knownAddress == mem.getLong(vtAddr.add(0x20)))
                    {
                        out.add(vtAddr);
                    }
                }
            }
            catch (MemoryAccessException e) // Skip entries with out of bounds offsets
            {
                continue;
            }
        }
        
        return out;
    }
    
    protected List<IPCVTableEntry> createVTableEntries(Program program, ElfCompatibilityProvider elfProvider, List<Address> vtAddrs) throws MemoryAccessException, AddressOutOfBoundsException, IOException
    {
        List<IPCVTableEntry> out = Lists.newArrayList();
        Memory mem = program.getMemory();
        AddressSpace aSpace = program.getAddressFactory().getDefaultAddressSpace();
        
        for (Address vtAddr : vtAddrs)
        {
            long vtOff = vtAddr.getOffset();
            long rttiBase = mem.getLong(vtAddr.add(0x8));
            String name = String.format("SRV_%X::vtable", vtOff);
            
            // Attempt to find the name if the vtable has RTTI
            if (rttiBase != 0)
            {
                Address rttiBaseAddr = aSpace.getAddress(rttiBase);
                MemoryBlock rttiBaseBlock = mem.getBlock(rttiBaseAddr);
                
                // RTTI must be within the data block
                if (rttiBaseBlock != null && rttiBaseBlock.getName().equals(".data"))
                {
                    Address thisAddr = aSpace.getAddress(mem.getLong(rttiBaseAddr.add(0x8)));
                    MemoryBlock thisBlock = mem.getBlock(thisAddr);
                    
                    if (thisBlock != null && thisBlock.getName().equals(".rodata"))
                    {
                        String symbol = elfProvider.getReader().readAsciiString(thisAddr.getOffset());
                        
                        if (!symbol.isEmpty() && symbol.length() <= 512)
                        {
                            if (!symbol.startsWith("_Z"))
                                symbol = "_ZTV" + symbol;
                            
                            name = demangleIpcSymbol(symbol);
                        }
                    }
                }
            }
            
            Map<Address, Address> gotDataSyms = this.getGotDataSyms(program, elfProvider);
            List<Address> implAddrs = new ArrayList<>();
            long funcVtOff = 0x30;
            long funcOff;
            
            // Find all ipc impl functions in the vtable
            while ((funcOff = mem.getLong(vtAddr.add(funcVtOff))) != 0)
            {
                Address funcAddr = aSpace.getAddress(funcOff);
                MemoryBlock funcAddrBlock = mem.getBlock(funcAddr);
                
                if (funcAddrBlock != null && funcAddrBlock.getName().equals(".text"))
                {
                    implAddrs.add(funcAddr);
                    funcVtOff += 0x8;
                }
                else break;
            
                if (gotDataSyms.containsValue(vtAddr.add(funcVtOff)))
                {
                    break;
                }
            }
            
            Set<Address> uniqueAddrs = new HashSet<>(implAddrs);
            
            // There must be either 1 unique function without repeats, or more than one unique function with repeats allowed
            if (uniqueAddrs.size() <= 1 && implAddrs.size() != 1)
            {
                Msg.warn(this, String.format("Insufficient unique addresses for vtable at 0x%X", vtAddr.getOffset()));
                
                for (Address addr : uniqueAddrs)
                {
                    Msg.info(this, String.format("    Found: 0x%X", addr.getOffset()));
                }
                
                implAddrs.clear();
            }
            
            // Some IPC symbols are very long and Ghidra crops them off far too early by default.
            // Let's shorten these.
            String shortName = shortenIpcSymbol(name);
            
            var entry = new IPCVTableEntry(name, shortName, vtAddr, implAddrs);
            Msg.info(this, String.format("VTable Entry: %s @ 0x%X", entry.abvName, entry.addr.getOffset()));
            out.add(entry);
        }
        
        return out;
    }
    
    protected HashBiMap<Address, Address> locateSTables(Program program, ElfCompatibilityProvider elfProvider) throws MemoryAccessException {
        HashBiMap<Address, Address> out = HashBiMap.create();
        List<Pair<Long, Long>> candidates = new ArrayList<>();
        AddressSpace aSpace = program.getAddressFactory().getDefaultAddressSpace();
        Address baseAddr = program.getImageBase();
        Memory mem = program.getMemory();
        
        for (NXRelocation reloc : elfProvider.getRelocations()) 
        {
            if (reloc.addend > 0) {
                candidates.add(new Pair<>(baseAddr.getOffset() + reloc.addend, baseAddr.getOffset() + reloc.offset));
            }
            else if (reloc.r_type == R_FAKE_RELR) {
                reloc.addend = mem.getLong(baseAddr.add(reloc.offset)) - baseAddr.getOffset();
                candidates.add(new Pair<>(baseAddr.getOffset() + reloc.addend, baseAddr.getOffset() + reloc.offset));
            }
        }
        
        candidates.sort(Comparator.comparing(a -> a.first));
        
        
        // 5.x: match on the "SFCI" constant used in the template of s_Table
        //   MOV  W?, #0x4653
        //   MOVK W?, #0x4943, LSL#16
        long movMask  = 0x5288CAL;
        long movkMask = 0x72A928L;
        
        MemoryBlock text = mem.getBlock(".text"); // Text is one of the few blocks that isn't split
        
        try
        {
            for (long off = text.getStart().getOffset(); off < text.getEnd().getOffset() - 0x4; off += 0x4)
            {
                long val1 = (elfProvider.getReader().readUnsignedInt(off) & 0xFFFFFF00L) >> 8;
                long val2 = (elfProvider.getReader().readUnsignedInt(off + 0x4) & 0xFFFFFF00L) >> 8;
                
                // Match on a sequence of MOV, MOVK
                if (val1 == movMask && val2 == movkMask)
                {
                    long processFuncOffset = 0;
                    long sTableOffset = 0;
                    
                    // Find the candidate after our offset, then pick the one before that
                    for (Pair<Long, Long> candidate : candidates)
                    {
                        if (candidate.first > off)
                            break;
                        
                        processFuncOffset = candidate.first;
                        sTableOffset = candidate.second;
                    }
                    
                    long pRetOff;
                    
                    // Make sure our SFCI offset is within the process function by matching on the
                    // RET instruction
                    for (pRetOff = processFuncOffset; pRetOff < text.getEnd().getOffset(); pRetOff += 0x4)
                    {
                        long rval = elfProvider.getReader().readUnsignedInt(pRetOff);
                        
                        // RET
                        if (rval == 0xD65F03C0L)
                            break;
                    }
                    
                    if (pRetOff > off)
                    {
                        Address stAddr = aSpace.getAddress(sTableOffset);
                        Address pFuncAddr = aSpace.getAddress(processFuncOffset);
                        out.put(stAddr, pFuncAddr);
                    }
                }
            }
        }
        catch (IOException e)
        {
            Msg.error(this, "Failed to locate s_Tables", e);
        }
        
        return out;
    }
    
    protected Multimap<Address, IPCTrace> emulateProcessFunctions(Program program, TaskMonitor monitor, Set<Address> procFuncAddrs)
    {
        Multimap<Address, IPCTrace> out = HashMultimap.create();
        IPCEmulator ipcEmu = new IPCEmulator(program);
        Set<Integer> cmdsToTry = Sets.newHashSet();
        
        // Bruteforce 0-1000
        //for (int i = 0; i <= 1000; i++)
            //cmdsToTry.add(i);
        
        // The rest we add ourselves. From SwIPC. Duplicates are avoided by using a set
        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 };
        
        for (int preset : presets)
            cmdsToTry.add(preset);
        
        Multimap<Address, IPCTrace> map = HashMultimap.create();
        
        int maxProgress = procFuncAddrs.size() * cmdsToTry.size();
        int progress = 0;
        
        monitor.setMessage("Emulating IPC process functions...");
        monitor.initialize(maxProgress);
        
        for (Address procFuncAddr : procFuncAddrs)
        {
            for (int cmd : cmdsToTry)
            {
                IPCTrace trace = ipcEmu.emulateCommand(procFuncAddr, cmd);
                
                if (trace.hasDescription())
                    map.put(procFuncAddr, trace);
            
                progress++;
                monitor.setProgress(progress);
            }
        }
        
        // Recreate the map as we can't sort the original
        for (Address procFuncAddr : map.keySet())
        {
            List<IPCTrace> traces = Lists.newArrayList(map.get(procFuncAddr).iterator());
            
            traces.sort(Comparator.comparingLong(a -> a.cmdId));
            
            for (IPCTrace trace : traces)
            {
                out.put(procFuncAddr, trace);
            }
        }
        
        return out;
    }
    
    protected HashBiMap<Address, IPCVTableEntry> matchVtables(List<IPCVTableEntry> vtEntries, Set<Address> procFuncAddrs, Multimap<Address, IPCTrace> processFuncTraces)
    {
        // Map process func addrs to vtable addrs
        HashBiMap<Address, IPCVTableEntry> out = HashBiMap.create();
        List<IPCVTableEntry> possibilities = Lists.newArrayList(vtEntries.iterator());
        
        for (Address procFuncAddr : procFuncAddrs)
        {
            // We've already found this address. No need to do it again
            if (out.containsKey(procFuncAddr))
                continue;
            
            List<IPCVTableEntry> filteredPossibilities = possibilities.stream().filter(vtEntry -> vtEntry.ipcFuncs.size() == getProcFuncVTableSize(processFuncTraces, procFuncAddr)).collect(Collectors.toList());
            
            // See if there is a single entry that *exactly* matches the vtable size
            if (filteredPossibilities.size() == 1)
            {
                IPCVTableEntry vtEntry = filteredPossibilities.get(0);
                out.put(procFuncAddr, vtEntry);
                possibilities.remove(vtEntry);
                continue;
            }
            
            filteredPossibilities = possibilities.stream().filter(vtEntry -> vtEntry.ipcFuncs.size() >= getProcFuncVTableSize(processFuncTraces, procFuncAddr)).collect(Collectors.toList());

            // See if there is a single entry that is equal to or greater than the vtable size
            if (filteredPossibilities.size() == 1)
            {
                IPCVTableEntry vtEntry = filteredPossibilities.get(0);
                out.put(procFuncAddr, vtEntry);
                possibilities.remove(vtEntry);
                continue;
            }
            
            // Iterate over all the possible vtables with a size greater than our current process function
            for (IPCVTableEntry filteredPossibility : filteredPossibilities)
            {
                List<Address> unlocatedProcFuncAddrs = procFuncAddrs.stream().filter(pFAddr -> !out.containsKey(pFAddr)).toList();
                
                // See if there is only a single trace set of size <= this vtable
                // 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:
                // 0x10, 0x20, 0x60, 0x110, 0x230
                // We will run this loop for both 0x110 and 0x230. 
                // In the case of 0x110, we will then filter for sizes <= 0x110. These are 0x10, 0x20, 0x60 and 0x110
                // As there are four of these, the check will fail.
                if (unlocatedProcFuncAddrs.stream().filter(unlocatedProcFuncAddr -> getProcFuncVTableSize(processFuncTraces, unlocatedProcFuncAddr) <= filteredPossibility.ipcFuncs.size()).count() == 1)
                {
                    out.put(procFuncAddr, filteredPossibility);
                    possibilities.remove(filteredPossibility);
                    break;
                }
            }
        }
        
        List<Address> unlocatedProcFuncAddrs = procFuncAddrs.stream().filter(pFAddr -> !out.containsKey(pFAddr)).toList();
        
        for (Address addr : unlocatedProcFuncAddrs)
        {
            Msg.info(this, String.format("Unmatched process func at 0x%X. Calculated VTable Size: 0x%X", addr.getOffset(), getProcFuncVTableSize(processFuncTraces, addr)));
        }
        
        for (IPCVTableEntry entry : possibilities)
        {
            Msg.info(this, String.format("Unmatched IPC VTable entry at 0x%X. VTable Size: 0x%X", entry.addr.getOffset(), entry.ipcFuncs.size()));
        }
        
        return out;
    }
    
    protected void markupIpc(Program program, TaskMonitor monitor, List<IPCVTableEntry> vtEntries, HashBiMap<Address, Address> sTableProcessFuncMap, Multimap<Address, IPCTrace> processFuncTraces, HashBiMap<Address, IPCVTableEntry> procFuncVtMap)
    {
        AddressSpace aSpace = program.getAddressFactory().getDefaultAddressSpace();
        
        try
        {
            // Analyze and label any IPC info found
            for (IPCVTableEntry entry : vtEntries)
            {
                List<IPCTrace> ipcTraces = Lists.newArrayList();
                Address processFuncAddr = procFuncVtMap.inverse().get(entry);
                
                if (processFuncAddr != null)
                {
                    Address sTableAddr = sTableProcessFuncMap.inverse().get(processFuncAddr);
                    String ipcComment = ""                +
                            "IPC INFORMATION\n"           +
                            "s_Table Address:       0x%X";
                    
                    if (sTableAddr != null)
                    {
                        ipcComment = String.format(ipcComment, sTableAddr.getOffset());
                        program.getListing().setComment(entry.addr, CommentType.PLATE, ipcComment);
                    }
                    
                    ipcTraces = Lists.newArrayList(processFuncTraces.get(processFuncAddr).iterator());
                }
                    
                String entryNameNoSuffix = entry.abvName.replace("::vtable", "");
                
                // Set the vtable name
                if (!this.hasImportedSymbol(program, entry.addr))
                {
                    // For shortened names, leave a comment so the user knows what the original name is
                    if (!entry.fullName.equals(entry.abvName))
                        program.getListing().setComment(entry.addr, CommentType.REPEATABLE, entry.fullName);
                    
                    Msg.info(this, String.format("Creating label for %s @ 0x%X", entry.abvName, entry.addr.getOffset()));
                    program.getSymbolTable().createLabel(entry.addr, entry.abvName, null, SourceType.IMPORTED);
                }
                
                // Label the four functions that exist for all ipc vtables
                for (int i = 0; i < 4; i++)
                {
                    Address vtAddr = entry.addr.add(0x10 + i * 0x8);
                    String name = "";
                    
                    // Set vtable func data types to pointers
                    this.createPointer(program, vtAddr);

                    switch (i) {
                        case 0 -> name = "AddReference";
                        case 1 -> name = "Release";
                        case 2 -> name = "GetProxyInfo";
                        // Shared by everything
                        case 3 -> name = "nn::sf::IServiceObject::GetInterfaceTypeInfo";
                    }
                             
                    if (i == 3) // For now, only label GetInterfaceTypeInfo. We need better heuristics for the others as they may be shared.
                    {
                        Address funcAddr = aSpace.getAddress(program.getMemory().getLong(vtAddr));
                        
                        if (!this.hasImportedSymbol(program, funcAddr))
                            program.getSymbolTable().createLabel(funcAddr, name, null, SourceType.IMPORTED);
                    }
                    else
                    {
                        program.getListing().setComment(vtAddr, CommentType.REPEATABLE, name);
                    }
                }
                
                for (int i = 0; i < entry.ipcFuncs.size(); i++)
                {
                    Address func = entry.ipcFuncs.get(i);
                    String name = null;
    
                    // Set vtable func data types to pointers
                    this.createPointer(program, entry.addr.add(0x30 + i * 0x8L));
                }
                
                for (IPCTrace trace : ipcTraces)
                {
                    // Safety precaution. I *think* these should've been filtered out earlier though.
                    if (trace.vtOffset == -1 || !trace.hasDescription())
                        continue;
                    
                    Address vtOffsetAddr = entry.addr.add(0x10 + trace.vtOffset);
                    Address ipcCmdImplAddr = aSpace.getAddress(program.getMemory().getLong(vtOffsetAddr));
                    
                    if (!this.hasImportedSymbol(program, ipcCmdImplAddr))
                        program.getSymbolTable().createLabel(ipcCmdImplAddr, String.format("%s::Cmd%d", entryNameNoSuffix, trace.cmdId), null, SourceType.IMPORTED);
                    
                    String implComment = """
                            IPC INFORMATION
                            Bytes In:       0x%X
                            Bytes Out:      0x%X
                            Buffer Count:   0x%X
                            In Interfaces:  0x%X
                            Out Interfaces: 0x%X
                            In Handles:     0x%X
                            Out Handles:    0x%X
                            """;
                    
                    implComment = String.format(implComment, trace.bytesIn, trace.bytesOut, trace.bufferCount, trace.inInterfaces, trace.outInterfaces, trace.inHandles, trace.outHandles);
                    program.getListing().setComment(ipcCmdImplAddr, CommentType.PLATE, implComment);
                }
            }
            
            // Annotate s_Tables
            for (Address stAddr : sTableProcessFuncMap.keySet())
            {
                this.createPointer(program, stAddr);
                
                if (!this.hasImportedSymbol(program, stAddr))
                {
                    Address procFuncAddr = sTableProcessFuncMap.get(stAddr);
                    String sTableName = String.format("SRV_S_TAB_%X", stAddr.getOffset());
                    
                    if (procFuncAddr != null)
                    {
                        IPCVTableEntry entry = procFuncVtMap.get(procFuncAddr);
                        
                        if (entry != null)
                        {
                            String entryNameNoSuffix = entry.abvName.replace("::vtable", "");
                            sTableName = entryNameNoSuffix + "::s_Table";
                        }
                    }
                    
                    program.getSymbolTable().createLabel(stAddr, sTableName, null, SourceType.IMPORTED);
                }
            }
        }
        catch (InvalidInputException | AddressOutOfBoundsException | MemoryAccessException e)
        {
            Msg.error(this, "Failed to markup IPC", e);
        }
    }
    
    protected int getProcFuncVTableSize(Multimap<Address, IPCTrace> processFuncTraces, Address procFuncAddr)
    {
        if (!processFuncTraces.containsKey(procFuncAddr) || processFuncTraces.get(procFuncAddr).isEmpty())
            return 0;
        
        IPCTrace maxTrace = null;
        
        for (IPCTrace trace : processFuncTraces.get(procFuncAddr))
        {
            if (trace.vtOffset == -1)
                continue;
            
            if (maxTrace == null || trace.vtOffset > maxTrace.vtOffset)
                maxTrace = trace;
        }
        
        return (int)Math.max(processFuncTraces.get(procFuncAddr).size(), (maxTrace.vtOffset + 8 - 0x20) / 8);
    }
    
    private Map<Address, Address> gotDataSyms = null;
    
    /**
     * A map of relocated entries in the global offset table to their new values.
     */
    protected Map<Address, Address> getGotDataSyms(Program program, ElfCompatibilityProvider elfProvider) throws MemoryAccessException {
        if (gotDataSyms != null)
            return this.gotDataSyms;
        
        Address baseAddr = program.getImageBase();
        gotDataSyms = new HashMap<>();
        MemoryBlock gotBlock = program.getMemory().getBlock(".got");
        
        for (NXRelocation reloc : elfProvider.getRelocations()) 
        {
            if (baseAddr.add(reloc.offset).getOffset() < gotBlock.getStart().getOffset() || baseAddr.add(reloc.offset).getOffset() > gotBlock.getEnd().getOffset() + 1)
            {
                continue;
            }

            long off;

            if (reloc.r_type == R_FAKE_RELR) {
                reloc.addend = program.getMemory().getLong(baseAddr.add(reloc.offset)) - baseAddr.getOffset();
            }
            
            if (reloc.sym != null && reloc.sym.getSectionHeaderIndex() != ElfSectionHeaderConstants.SHN_UNDEF && reloc.sym.getValue() == 0)
            {
                off = reloc.sym.getValue();
            }
            else if (reloc.addend != 0)
            {
                off = reloc.addend;
            }
            else continue;
            
            // Target -> Value
           this.gotDataSyms.put(baseAddr.add(reloc.offset), baseAddr.add(off));
        }
        
        return gotDataSyms;
    }
    
public static String demangleIpcSymbol(String mangled)
{
    // Needed by the demangler
    if (!mangled.startsWith("_Z"))
        mangled = "_Z" + mangled;
 
    String out = mangled;
    
    try {
        // Use the new API: demangle(Program, String, Address)
        // Pass null for Program and Address since we're in a static method
        // This returns a List<DemangledObject>
        List<DemangledObject> demangledObjects = DemanglerUtil.demangle(null, mangled, null);
        
        // Use the first result if available
        if (demangledObjects != null && !demangledObjects.isEmpty())
        {
            DemangledObject demangledObj = demangledObjects.get(0);
            StringBuilder builder = new StringBuilder(demangledObj.toString());
            int templateLevel = 0;
            
            //De-Ghidrify-template colons
            for (int i = 0; i < builder.length(); ++i) 
            {
                char ch = builder.charAt(i);
                
                if (ch == '<') 
                {
                    ++templateLevel;
                }
                else if (ch == '>' && templateLevel != 0) 
                {
                    --templateLevel;
                }

                if (templateLevel > 0 && ch == '-') 
                    builder.setCharAt(i, ':');
            }
            
            out = builder.toString();
        }
    } catch (Exception e) {
        // If demangling fails, just return the mangled name
        // This prevents crashes if the demangler encounters unexpected input
    }
    
    return out;
}
    
    public static String shortenIpcSymbol(String longSym)
    {
        String out = longSym;
        String suffix = out.substring(out.lastIndexOf(':') + 1);
        
        if (out.startsWith("nn::sf::detail::ObjectImplFactoryWithStatelessAllocator<") || out.startsWith("nn::sf::detail::ObjectImplFactoryWithStatefulAllocator<"))
        {
            String abvNamePrefixOld = "nn::sf::detail::EmplacedImplHolder<";
            String abvNamePrefixNew = "_tO2N<";
            
            int abvNamePrefixOldIndex = out.indexOf(abvNamePrefixOld);
            int abvNamePrefixNewIndex = out.indexOf(abvNamePrefixNew);
            
            if (abvNamePrefixOldIndex != -1)
            {
                int abvNameStart = abvNamePrefixOldIndex + abvNamePrefixOld.length();
                out = out.substring(abvNameStart, out.indexOf(',', abvNameStart));
            }
            else if (abvNamePrefixNewIndex != -1)
            {
                int abvNameStart = abvNamePrefixNewIndex + abvNamePrefixNew.length();
                out = out.substring(abvNameStart, out.indexOf('>', abvNameStart));
            }
            
            out += "::" + suffix;
        }

        return out;
    }
    
    public boolean hasImportedSymbol(Program program, Address addr)
    {
        for (Symbol sym : program.getSymbolTable().getSymbols(addr))
        {
            if (sym.getSource() == SourceType.IMPORTED)
                return true;
        }
        
        return false;
    }
    
    protected int createPointer(Program program, Address address)
    {
        Data d = program.getListing().getDataAt(address);
        
        if (d == null) 
        {
            try 
            {
                d = program.getListing().createData(address, PointerDataType.dataType, 8);
            } 
            catch (CodeUnitInsertionException e)
            {
                Msg.error(this, String.format("Failed to create pointer at 0x%X", address.getOffset()), e);
            }
        }
        
        return d.getLength();
    }
    
    public static class IPCVTableEntry
    {
        public final String fullName;
        public final String abvName;
        public final Address addr;
        public final ImmutableList<Address> ipcFuncs;
        
        private IPCVTableEntry(String fullName, String abvName, Address addr, List<Address> ipcFuncs)
        {
            this.fullName = fullName;
            this.abvName = abvName;
            this.addr = addr;
            this.ipcFuncs = ImmutableList.copyOf(ipcFuncs);
        }
    }
}


================================================
FILE: src/main/java/adubbz/nx/analyzer/ipc/IPCEmulator.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.analyzer.ipc;

import adubbz.nx.util.ByteUtil;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteArrayProvider;
import ghidra.pcode.emulate.BreakCallBack;
import ghidra.pcode.emulate.BreakTableCallBack;
import ghidra.pcode.emulate.Emulate;
import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.memstate.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.code.CodeManager;
import ghidra.program.disassemble.Disassembler;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.InstructionPrototype;
import ghidra.program.model.lang.OperandType;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
import org.apache.commons.compress.utils.Lists;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class IPCEmulator 
{
    private Program program;
    public boolean hasSetup;
    
    private SleighLanguage sLang;
    private MemoryState state;
    private MemoryBank ramBank;
    private MemoryBank registerBank;
    private BreakTableCallBack bTable;
    private Emulate emu;
    private Disassembler disassembler;
    
    private List<Consumer<Long>> instructionHandlers = Lists.newArrayList();
    
    private long messageSize;
    private long messagePtr;
    
    private long messageStructPtr;
    private long targetObjectPtr;
    private long ipcObjectPtr;
    
    private long retInstructionPtr;
    private long inObjectVtablePtr;
    private long inObjectPtr;
    
    private long bufferSize;
    private long bufferMemory;
    private long outputMemory;
    
    private IPCTrace currentTrace;
    
    public IPCEmulator(Program program)
    {
        this.program = program;
    
        try 
        {
            this.setup();
            this.hasSetup = true;
        }
        catch (MemoryAccessException e) 
        {
            Msg.error(this, "Failed to setup IPC emulator");
        }
    }
    
    public void setup() throws MemoryAccessException
    {
        MemoryFaultHandler faultHandler = new MemoryFaultHandler()
        {

            @Override
            public boolean uninitializedRead(Address address, int size, byte[] buf, int bufOffset) 
            {
                Emulate emu = IPCEmulator.this.emu;
                long pc = IPCEmulator.this.state.getValue("pc");
                
                if (address.isRegisterAddress())
                {
                    for (Register reg : sLang.getRegisters(address))
                    {
                        Msg.info(this, String.format("Uninitialized read from register %s at 0x%X", reg.getName(), address.getOffset()));
                    }
                }
                
                return false;
            }

            @Override
            public boolean unknownAddress(Address address, boolean write) 
            {
                Msg.info(this, String.format("Unknwon address 0x%X", address.getOffset()));
                return false;
            }
        };
        
        this.sLang = (SleighLanguage)this.program.getLanguage();
        this.state = new DefaultMemoryState(this.sLang);
        
        // Create banks for ram and registers and add them to our state
        this.ramBank = new MemoryPageBank(this.sLang.getAddressFactory().getDefaultAddressSpace(), false, 4096, faultHandler);
        this.registerBank = new MemoryPageBank(this.sLang.getAddressFactory().getRegisterSpace(), false, 4096, faultHandler);
        this.state.setMemoryBank(this.ramBank);
        this.state.setMemoryBank(this.registerBank);
        
        this.bTable = new BreakTableCallBack(this.sLang);
        this.emu = new Emulate(this.sLang, this.state, this.bTable);
        this.disassembler = Disassembler.getDisassembler(this.program, TaskMonitorAdapter.DUMMY, null);
        
        // Copy over our binary to the emulator's memory, typically 7100000000
        Memory programMemory = this.program.getMemory();
        byte[] programBytes = new byte[(int)programMemory.getMaxAddress().getOffset()+1];
        
        // Copy memory blocks manually. The entire thing can't be copied at once because there are gaps between segments
        for (MemoryBlock block : programMemory.getBlocks())
        {
            if (block.isInitialized())
            {
                byte[] blockBytes = new byte[(int)block.getSize()];
                int copiedBytes = programMemory.getBytes(block.getStart(), blockBytes);
                
                if (copiedBytes != blockBytes.length)
                    throw new RuntimeException(String.format("Failed to copy bytes from 0x%X of size 0x%x!", block.getStart().getOffset(), block.getSize()));
                
                // Copy the block bytes to the program bytes
                long blockOff = block.getStart().getOffset() - this.program.getImageBase().getOffset();
                System.arraycopy(blockBytes, 0, programBytes, (int)blockOff, (int)block.getSize());
            }
        }
            
        state.setChunk(programBytes, sLang.getAddressFactory().getDefaultAddressSpace(), this.program.getImageBase().getOffset(), programBytes.length);
        
        // Initialize GPRs
        for (int i = 0; i <= 30; i++)
            state.setValue("x" + i, 0);
        
        // Stack is from 0x100000000-0x100002000
        state.setValue("sp", 0x100002000L);
        
        // Allocate IPC message data.
        // We set it later
        this.messageSize = 0x1010;
        this.messagePtr = this.calloc(messageSize);
        
        this.messageStructPtr = this.calloc(0x10);
        this.setLong(messageStructPtr, messagePtr); // Message ptr
        this.setLong(messageStructPtr + 0x8, messageSize); // Message length
        
        // Create the ipc vtable and object
        long ipcVtableSize = 0x8 * 11;
        long ipcVtablePtr = this.calloc(ipcVtableSize);
        this.setLong(ipcVtablePtr,        this.createFunctionPointer(this::PrepareForProcess));
        this.setLong(ipcVtablePtr + 0x8,  this.createFunctionPointer(this::OverwriteClientProcessId));
        this.setLong(ipcVtablePtr + 0x10, this.createFunctionPointer(this::GetBuffers));
        this.setLong(ipcVtablePtr + 0x18, this.createFunctionPointer(this::GetInNativeHandles));
        this.setLong(ipcVtablePtr + 0x20, this.createFunctionPointer(this::GetInObjects));
        this.setLong(ipcVtablePtr + 0x28, this.createFunctionPointer(this::BeginPreparingForReply));
        this.setLong(ipcVtablePtr + 0x30, this.createFunctionPointer(this::SetBuffers));
        this.setLong(ipcVtablePtr + 0x38, this.createFunctionPointer(this::SetOutObjects));
        this.setLong(ipcVtablePtr + 0x40, this.createFunctionPointer(this::SetOutNativeHandles));
        this.setLong(ipcVtablePtr + 0x48, this.createFunctionPointer(this::BeginPreparingForErrorReply));
        this.setLong(ipcVtablePtr + 0x50, this.createFunctionPointer(this::EndPreparingForReply));
        this.ipcObjectPtr = this.calloc(0x10);
        this.setLong(this.ipcObjectPtr, ipcVtablePtr);
        
        // Create the target function vtable
        long targetFuncVtableSize = 0x8 * 512;
        long targetFuncVtablePtr = this.calloc(targetFuncVtableSize);
        
        // Create 512 function pointers, each with a different offset within the target function vtable.
        // This allows us to figure out the vtable offsets for each command id.
        for (long off = 0; off < targetFuncVtableSize; off += 8)
        {
            final long off2 = off; // Lambdas require this to be final
            long targetFuncPtr = this.createFunctionPointer(() -> this.targetFunction(off2));
            
            long targetFuncVtableOff = targetFuncVtablePtr + off; // Where to put the function pointer within the target func vtable
            this.setLong(targetFuncVtableOff, targetFuncPtr);
        }
        
        this.targetObjectPtr = this.calloc(0x10);
        this.setLong(this.targetObjectPtr, targetFuncVtablePtr);
        
        this.retInstructionPtr = this.calloc(0x4);
        this.setInt(this.retInstructionPtr, 0xd65f03c0);
        this.inObjectVtablePtr = this.calloc(0x8 * 16);
        
        // Set up the in object vtable
        for (int i = 0; i < 16; i++)
        {
            this.setLong(this.inObjectVtablePtr + i * 0x8, this.retInstructionPtr);
        }
        
        this.inObjectPtr = this.calloc(0x8 + 8 * 16);
        this.setLong(this.inObjectPtr, this.inObjectVtablePtr);
        
        this.bufferSize = 0x1000;
        this.bufferMemory = this.calloc(this.bufferSize);
        this.outputMemory = this.calloc(0x1000);
    }
    
    public IPCTrace emulateCommand(Address procFuncAddr, int cmd)
    {
        if (!this.hasSetup)
            return null;
        
        // Some commands have in-dispatcher validation. If we fail this
        // validation we miss some information about vtable offsets and
        // returned objects. This tries to brute-force until we find an
        // input that passes that validation.

        int[] bufferSizes = new int[] { 128, 33, 1 };
        ByteBuffer nonZeroBuf = ByteBuffer.allocate(0x8 * 6);
        
        for (int i = 0; i < 6; i++) nonZeroBuf.putLong(1);
        
        // All-zeros, standard buffer size
        IPCTrace trace = this.emulateCommand(procFuncAddr, cmd, null, 0x1000);
        
        if (trace.isCorrect())
            return trace;
            
        // Pass checks for non-zero inline data
        trace = this.emulateCommand(procFuncAddr, cmd, nonZeroBuf.array(), 0x1000);

        if (trace.isCorrect())
            return trace;
        
        // All-zeros, Pass buffer size checks
        for (int bufSize : bufferSizes)
        {
            trace = this.emulateCommand(procFuncAddr, cmd, null, bufSize);
            
            if (trace.isCorrect())
                return trace;
        }
        
        nonZeroBuf = ByteBuffer.allocate(0x8 * 4);
        for (int i = 0; i < 4; i++) nonZeroBuf.putLong(1);

        // Pass checks for buffer size, and non-zero inline data
        for (int bufSize : bufferSizes)
        {
            trace = this.emulateCommand(procFuncAddr, cmd, nonZeroBuf.array(), bufSize);
            
            if (trace.isCorrect())
                return trace;
        }
        
        Msg.warn(this, String.format("Warning: unable to brute-force validation on dispatch_func %X command %d", procFuncAddr.getOffset(), cmd));
        return trace;
    }
    
    public IPCTrace emulateCommand(Address procFuncAddr, int cmd, byte[] data, int bufferSize)
    {
        if (!this.hasSetup)
            return null;
        
        // We allocate a fixed 0x1000 for the buffer. Therefore we only allow adjustments to less than or equal
        // to that.
        if (bufferSize < 0 || bufferSize > 0x1000)
            throw new RuntimeException("Invalid buffer size provided");
        
        this.bufferSize = bufferSize;
        
        this.instructionHandlers.clear(); // Clear any existing instruction handlers
        this.currentTrace = new IPCTrace(cmd, procFuncAddr.getOffset());
        
        // Clear out any existing IPC message data
        byte[] zeros = new byte[(int)this.messageSize];
        this.state.setChunk(zeros, this.sLang.getDefaultSpace(), this.messagePtr, zeros.length);
        
        this.setLong(messagePtr, 0x49434653); // IPC Magic
        this.setLong(messagePtr + 0x8, cmd); // Cmd ID
        
        if (data != null && data.length > 0)
            this.state.setChunk(data, this.sLang.getDefaultSpace(), this.messagePtr + 0x10, data.length);
        
        // Set registers to point to our objects.
        // We need to do this each time to reset the state
        this.state.setValue("x0", this.targetObjectPtr);
        this.state.setValue("x1", this.ipcObjectPtr);
        this.state.setValue("x2", this.messageStructPtr);
        
        // Set to the start of the process function
        emu.setExecuteAddress(procFuncAddr);
        
        // Disassemble the proc function so we can get instructions from it
        disassembler.disassemble(procFuncAddr, null);
        
        while (true)
        {
            long pc = state.getValue("pc");
            Address pcAddr = this.sLang.getDefaultSpace().getAddress(pc);
            
            // Pre-process instructions
            for (Consumer<Long> instructionHandler : this.instructionHandlers)
            {
                instructionHandler.accept(pc);
            }
            
            if (emu.getExecuteAddress().getOffset() == 0)
            {
                break;
            }
                
            try 
            {
                emu.executeInstruction(true, TaskMonitor.DUMMY);
            } 
            catch (CancelledException | LowlevelError e) 
            {
                e.printStackTrace();
            }
        }
        
        return this.currentTrace;
    }
    
    /**
     * Memory utils 
     */
    
    private static final long HEAP_START = 0x200000000L;
    private static final long HEAP_SIZE = 0x100000L;
    private long nextHeapPtr = HEAP_START;
    
    private static final long FUNC_PTR_START = 0x400000000L;
    private long nextFuncPtr = FUNC_PTR_START;
    
    private long allocate(long size)
    {
        long available = (HEAP_START + HEAP_SIZE) - nextHeapPtr;
        // Align to 0x10
        long allocationSize = (size + 0xF) & ~0xF;
        
        if (allocationSize > available)
            throw new RuntimeException(String.format("Could not allocate 0x%X bytes", allocationSize));
        
        long start = nextHeapPtr;
        nextHeapPtr += allocationSize;
        return start;
    }
    
    private long calloc(long size)
    {
        long start = allocate(size);
        byte[] vals = new byte[(int)size];
        this.state.setChunk(vals, this.sLang.getDefaultSpace(), start, vals.length);
        return start;
    }
    
    private long createFunctionPointer(Supplier<Boolean> func)
    {
        long start = nextFuncPtr;
        nextFuncPtr += 0x8;
        
        BreakCallBack callBack = new BreakCallBack()
        {
            @Override
            public boolean addressCallback(Address addr) 
            {
                if (addr.getOffset() == start)
                {
                    if (!func.get())
                    {
                        // Failed, halt execution.
                        IPCEmulator.this.emu.setExecuteAddress(addr.getNewAddress(0));
                        //Msg.info(this, "HLE function returned false, halting further execution...");
                    }
                    
                    // Don't execute the instruction. We've handled it
                    return true;
                }
                
                // Execute the instruction
                return false;
            }
        };
        
        this.bTable.registerAddressCallback(this.sLang.getDefaultSpace().getAddress(start), callBack);
        return start;
    }
    
    private void setLong(long off, long val)
    {
        this.state.setChunk(Utils.longToBytes(val, 0x8, this.sLang.isBigEndian()), this.sLang.getDefaultSpace(), off, 0x8);
    }
    
    private void setInt(long off, long val)
    {
        this.state.setChunk(Utils.longToBytes(val, 0x4, this.sLang.isBigEndian()), this.sLang.getDefaultSpace(), off, 0x4);
    }
    
    private void printMemory(long off, long size)
    {
        Msg.info(this, String.format("0x%X (0x%X bytes):", off, size));
        byte[] out = new byte[(int)size];
        this.state.getChunk(out, this.sLang.getDefaultSpace(), off, out.length, true);
        ByteUtil.logBytes(out);
    }
    
    /**
     * HLE function utils 
     */
    
    private void returnFromFunc(long value)
    {
        this.state.setValue("x0", value);
        // x30 = lr
        this.emu.setExecuteAddress(this.sLang.getDefaultSpace().getAddress(this.state.getValue("x30")));
    }
    
    /**
     * HLE-ed functions.
     * If these return false, the emulation will halt.
     */
    
    private boolean targetFunction(long offset)
    {
        this.currentTrace.vtOffset = offset;
        this.returnFromFunc(0);
        return true;
    }
    
    private boolean PrepareForProcess()
    {
        long metaInfoPtr = this.state.getValue("x1");
        long metaInfoSize = 0x90;
        byte[] metaInfo = new byte[(int)metaInfoSize];
        
        if (metaInfoSize != this.state.getChunk(metaInfo, this.sLang.getDefaultSpace(), metaInfoPtr, metaInfo.length, true))
            throw new RuntimeException("Failed to read meta info");
        
        BinaryReader reader = new BinaryReader(new ByteArrayProvider(metaInfo), true);
        
        try
        {
            this.currentTrace.bytesIn = reader.readInt(0x8) - 0x10;
            this.currentTrace.bytesOut = reader.readInt(0x10) - 0x10;
            this.currentTrace.bufferCount = reader.readInt(0x18);
            this.currentTrace.inInterfaces = reader.readInt(0x1C);
            this.currentTrace.outInterfaces = reader.readInt(0x20);
            this.currentTrace.inHandles = reader.readInt(0x24);
            this.currentTrace.outHandles = reader.readInt(0x28);
            
            if (this.currentTrace.bytesIn < 0 || this.currentTrace.bytesIn > 0x1000L)
                throw new RuntimeException("Invalid value for bytesIn!");
            
            if (this.currentTrace.bytesOut < 0 || this.currentTrace.bytesOut > 0x1000L)
                throw new RuntimeException("Invalid value for bytesOut!");
            
            if (this.currentTrace.bufferCount > 20)
                throw new RuntimeException("Too many buffers!");
            
            if (this.currentTrace.inInterfaces > 20)
                throw new RuntimeException("Too many in interfaces!");
            
            if (this.currentTrace.outInterfaces > 20)
                throw new RuntimeException("Too many out interfaces!");
            
            if (this.currentTrace.inHandles > 20)
                throw new RuntimeException("Too many in handles!");
            
            if (this.currentTrace.outHandles > 20)
                throw new RuntimeException("Too many out handles!");
            
            this.currentTrace.lr = this.state.getValue("x30");
            
            if (this.currentTrace.inInterfaces > 0)
            {
                // Add a handler to cheat the cmp x8, x9 that usually happens
                this.instructionHandlers.add((off) -> 
                {
                    Address pcAddr = this.sLang.getDefaultSpace().getAddress(off);
                    CodeManager codeManager = ((ProgramDB)this.program).getCodeManager();
                    Instruction currentInstruction = codeManager.getInstructionAt(pcAddr);
                    
                    // Attempt to disassemble the instruction so we can try again
                    if (currentInstruction == null)
                    {
                        disassembler.disassemble(pcAddr, null);
                        currentInstruction = codeManager.getInstructionAt(pcAddr);
                    }
                    
                    if (currentInstruction != null)
                    {
                        InstructionPrototype prototype = currentInstruction.getPrototype();
                        String mnemonic = prototype.getMnemonic(currentInstruction.getInstructionContext());
                        
                        if (mnemonic.equals("cmp") && currentInstruction.getOperandType(0) == OperandType.REGISTER && currentInstruction.getOperandType(1) == OperandType.REGISTER)
                        {
                            Register r0 = currentInstruction.getRegister(0);
                            Register r1 = currentInstruction.getRegister(1);
                            
                            // Cheat time!
                            if (r0.getName().equals("x8") && r1.getName().equals("x9"))
                            {
                                long x9 = this.state.getValue("x9");
                                this.state.setValue("x8", x9);
                                this.state.setValue("NZCV", 0b0100);
                            }
                        }
                    }
                });
            }
        }
        catch (Exception e)
        {
            return false;
        }
        
        this.returnFromFunc(0);
        return true;
    }
    
    private boolean OverwriteClientProcessId()
    {
        long out = this.state.getValue("x1");
        this.setLong(out, 0);
        this.returnFromFunc(0);
        return true;
    }

    private boolean GetBuffers()
    {
        long out = this.state.getValue("x1");
        
        for (long i = out; i < out + this.currentTrace.bufferCount * 0x10; i += 0x10)
        {
            this.setLong(i, this.bufferMemory);
            this.setLong(i + 0x8, this.bufferSize);
        }
        
        this.returnFromFunc(0);
        return true;
    }
    
    private boolean GetInNativeHandles()
    {
        this.returnFromFunc(0);
        return true;
    }
    
    private boolean GetInObjects()
    {
        long out = this.state.getValue("x1");
        
        if (this.currentTrace.inInterfaces != 1)
            throw new RuntimeException("Invalid number of in interfaces!");
            
        this.setLong(out, this.inObjectPtr);
        
        this.returnFromFunc(0);
        return true;
    }
    
    private boolean BeginPreparingForReply()
    {
        long off = this.state.getValue("x1");
        this.setLong(off, this.outputMemory);
        this.setLong(off + 0x8, 0x1000);
        this.returnFromFunc(0);
        return true;
    }
    
    private boolean SetBuffers()
    {
        this.returnFromFunc(0);
        return true;
    }
    
    private boolean SetOutObjects()
    {
        // Stubbed
        return false;
    }
    
    private boolean SetOutNativeHandles()
    {
        this.returnFromFunc(0);
        return true;
    }
    
    private boolean BeginPreparingForErrorReply()
    {
        // Stubbed
        return false;
    }
    
    private boolean EndPreparingForReply()
    {
        // Stubbed
        this.returnFromFunc(0);
        return false;
    }
}


================================================
FILE: src/main/java/adubbz/nx/analyzer/ipc/IPCTrace.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.analyzer.ipc;

import ghidra.util.Msg;

public class IPCTrace 
{
    public final long cmdId;
    public final long procFuncAddr;
    
    public long bytesIn = -1;
    public long bytesOut = -1;
    public long bufferCount = -1;
    public long inInterfaces = -1;
    public long outInterfaces = -1;
    public long inHandles = -1;
    public long outHandles = -1;
    public long lr = -1;
    
    public long vtOffset = -1;
    
    public IPCTrace(int cmdId, long procFuncAddr)
    {
        this.cmdId = cmdId;
        this.procFuncAddr = procFuncAddr;
    }
    
    public boolean hasDescription()
    {
        return bytesIn != -1 || bytesOut != -1 || bufferCount != -1 || inInterfaces != -1 ||
                outInterfaces != -1 || inHandles != -1 || outHandles != -1 || lr != -1;
    }
    
    public boolean isCorrect()
    {
        if (!this.hasDescription())
        {
            return true;
        }

        return vtOffset != -1;
    }
    
    public void printTrace()
    {
        String out = """

                --------------------
                0x%X, Cmd 0x%X     \s
                --------------------
                Lr:             0x%X
                Vt:             0x%X
                Bytes In:       0x%X
                Bytes Out:      0x%X
                Buffer Count:   0x%X
                In Interfaces:  0x%X
                Out Interfaces: 0x%X
                In Handles:     0x%X
                Out Handles:    0x%X
                --------------------
                """;
        
        out = String.format(out, procFuncAddr, cmdId, lr, vtOffset, bytesIn, bytesOut, bufferCount, inInterfaces,
                            outInterfaces, inHandles, outHandles);
        Msg.info(this, out);
    }
}


================================================
FILE: src/main/java/adubbz/nx/common/ElfCompatibilityProvider.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.common;

import adubbz.nx.util.FullMemoryByteProvider;
import adubbz.nx.util.LegacyBinaryReader;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteArrayProvider;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.elf.*;
import ghidra.app.util.bin.format.elf.extend.ElfExtensionFactory;
import ghidra.app.util.bin.format.elf.extend.ElfLoadAdapter;
import ghidra.app.util.bin.format.elf.relocation.AARCH64_ElfRelocationType;
import ghidra.app.util.bin.format.elf.relocation.ARM_ElfRelocationType;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.Msg;
import ghidra.util.exception.NotFoundException;

import java.io.IOException;
import java.util.*;

public class ElfCompatibilityProvider 
{
    public static final int R_FAKE_RELR = -1;

    private Program program;
    private ByteProvider provider;
    private BinaryReader binaryReader;
    boolean isAarch32;
    
    private ElfHeader dummyElfHeader;
    
    protected ElfDynamicTable dynamicTable;
    protected ElfStringTable stringTable;
    protected ElfSymbolTable symbolTable;
    
    protected String[] dynamicLibraryNames;
    protected List<NXRelocation> relocs = new ArrayList<>();
    protected List<NXRelocation> pltRelocs = new ArrayList<>();
    
    public ElfCompatibilityProvider(Program program, ByteProvider provider, boolean isAarch32)
    {
        this.program = program;
        this.provider = provider;
        this.binaryReader = new LegacyBinaryReader(this.provider, true);
        this.isAarch32 = isAarch32;
        try {
            this.dummyElfHeader = new DummyElfHeader(isAarch32);
        } catch (ElfException e) {
            Msg.error(this, "Couldn't construct DummyElfHeader", e);
        }
    }
    
    public ElfCompatibilityProvider(Program program, boolean isAarch32)
    {
        this(program, new FullMemoryByteProvider(program), isAarch32);
    }
    
    public ElfDynamicTable getDynamicTable()
    {
        if (this.dynamicTable != null)
            return this.dynamicTable;

        MemoryBlock dynamic = this.getDynamicBlock();
        
        if (dynamic == null) return null;
        
        try
        {
            this.dynamicTable = new ElfDynamicTable(this.binaryReader, this.dummyElfHeader, dynamic.getStart().getOffset(), dynamic.getStart().getOffset());
        }
        catch (IOException e)
        {
            Msg.error(this, "Failed to create dynamic table", e);
        }
        
        return this.dynamicTable;
    }
    
    public ElfStringTable getStringTable()
    {
        if (this.stringTable != null)
            return this.stringTable;
        
        ElfDynamicTable dynamicTable = this.getDynamicTable();
        
        if (dynamicTable == null || !dynamicTable.containsDynamicValue(ElfDynamicType.DT_STRTAB)) 
            return null;
        
        try
        {
            long dynamicStringTableAddr = this.program.getImageBase().getOffset() + dynamicTable.getDynamicValue(ElfDynamicType.DT_STRTAB);
            long dynamicStringTableSize = dynamicTable.getDynamicValue(ElfDynamicType.DT_STRSZ);
    
            this.stringTable = new ElfStringTable(this.dummyElfHeader,
                    null, dynamicStringTableAddr, dynamicStringTableAddr, dynamicStringTableSize);
        }
        catch (NotFoundException e)
        {
            Msg.error(this, "Failed to create string table", e);
        }
        
        return this.stringTable;
    }
    
    public String[] getDynamicLibraryNames()
    {
        if (this.dynamicLibraryNames != null)
            return this.dynamicLibraryNames;
        
        ElfDynamicTable dynamicTable = this.getDynamicTable();
        ElfStringTable stringTable = this.getStringTable();
        
        if (dynamicTable == null) return new String[0];
        
        ElfDynamic[] needed = dynamicTable.getDynamics(ElfDynamicType.DT_NEEDED);
        this.dynamicLibraryNames = new String[needed.length];
        for (int i = 0; i < needed.length; i++) 
        {
            if (stringTable != null) 
            {
                try 
                {
                    this.dynamicLibraryNames[i] = stringTable.readString(this.binaryReader, needed[i].getValue());
                }
                catch (Exception e) 
                {
                    // ignore
                }
            }
            if (this.dynamicLibraryNames[i] == null) {
                this.dynamicLibraryNames[i] = "UNK_LIB_NAME_" + i;
            }
        }
        
        return this.dynamicLibraryNames;
    }
    
    public ElfSymbolTable getSymbolTable()
    {
        if (this.symbolTable != null)
            return this.symbolTable;
        
        ElfDynamicTable dynamicTable = this.getDynamicTable();
        ElfStringTable stringTable = this.getStringTable();
        
        if (dynamicTable == null || stringTable == null) 
            return null;
        
        try
        {
            long symbolTableOff = dynamicTable.getDynamicValue(ElfDynamicType.DT_SYMTAB) + this.program.getImageBase().getOffset();
            long symbolEntrySize = dynamicTable.getDynamicValue(ElfDynamicType.DT_SYMENT);
            long symbolTableSize;
            
            if (dynamicTable.containsDynamicValue(ElfDynamicType.DT_HASH)) {
                long dtHashOff = dynamicTable.getDynamicValue(ElfDynamicType.DT_HASH);
                long nchain = this.binaryReader.readUnsignedInt(this.program.getImageBase().getOffset() + dtHashOff + 4);
                symbolTableSize = nchain * symbolEntrySize;
            }
            else if (symbolTableOff < stringTable.getAddressOffset()) {
                symbolTableSize = stringTable.getAddressOffset() - symbolTableOff;
            }
            else {
                Msg.error(this, "Unable to determine symbol table size.");
                return null;
            }
            
            this.symbolTable = new ElfSymbolTable(this.binaryReader, this.dummyElfHeader, null,
                    symbolTableOff,
                    symbolTableOff,
                    symbolTableSize,
                    symbolEntrySize,
                    stringTable, null, true);
        }
        catch (IllegalArgumentException | NotFoundException | IOException e)
        {
            Msg.error(this, "Failed to create symbol table", e);
        }
        
        return this.symbolTable;
    }
    
    public List<NXRelocation> getPltRelocations()
    {
        if (!this.pltRelocs.isEmpty())
            return this.pltRelocs;
        
        ElfDynamicTable dynamicTable = this.getDynamicTable();
        ElfSymbolTable symbolTable = this.getSymbolTable();
        
        if (dynamicTable == null || symbolTable == null)
            return this.pltRelocs;
        
        try
        {
            if (this.dynamicTable.containsDynamicValue(ElfDynamicType.DT_JMPREL)) 
            {
                Msg.info(this, "Processing JMPREL relocations...");
                this.processRelocations(this.pltRelocs, symbolTable,
                        dynamicTable.getDynamicValue(ElfDynamicType.DT_JMPREL),
                        dynamicTable.getDynamicValue(ElfDynamicType.DT_PLTRELSZ));
    
                this.pltRelocs.sort(Comparator.comparing(reloc -> reloc.offset));
            }
        }
        catch (NotFoundException | IOException e)
        {
            Msg.error(this, "Failed to get plt relocations", e);
        }
        
        return this.pltRelocs;
    }
    
    public List<NXRelocation> getRelocations()
    {
        if (!this.relocs.isEmpty())
            return this.relocs;
        
        ElfDynamicTable dynamicTable = this.getDynamicTable();
        ElfSymbolTable symbolTable = this.getSymbolTable();

        if(dynamicTable == null)
            return this.relocs;

        try {
            if (dynamicTable.containsDynamicValue(ElfDynamicType.DT_RELR)) {
                Msg.info(this, "Processing DT_RELR relocations...");
                processReadOnlyRelocations(this.relocs,
                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELR),
                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELRSZ));
            }
        }
        catch (NotFoundException | IOException e)
        {
            Msg.error(this, "Failed to get relocations", e);
        }
        
        if (symbolTable == null)
            return this.relocs;
        
        try
        {
            if (dynamicTable.containsDynamicValue(ElfDynamicType.DT_REL))
            {
                Msg.info(this, "Processing DT_REL relocations...");
                processRelocations(this.relocs, this.symbolTable,
                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_REL),
                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELSZ));
            }
            
            if (dynamicTable.containsDynamicValue(ElfDynamicType.DT_RELA)) 
            {
                Msg.info(this, "Processing DT_RELA relocations...");
                processRelocations(this.relocs, this.symbolTable,
                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELA),
                        this.dynamicTable.getDynamicValue(ElfDynamicType.DT_RELASZ));
            }
        }
        catch (NotFoundException | IOException e)
        {
            Msg.error(this, "Failed to get relocations", e);
        }
        
        this.relocs.addAll(this.getPltRelocations());
        return this.relocs;
    }
    
    private Set<Long> processRelocations(List<NXRelocation> relocs, ElfSymbolTable symtab, long rel, long relsz) throws IOException 
    {
        long base = this.program.getImageBase().getOffset();
        Set<Long> locations = new HashSet<>();
        int relocSize = this.isAarch32 ? 0x8 : 0x18;

        for (long i = 0; i < relsz / relocSize; i++) 
        {
            long offset;
            long info;
            long addend;
            
            long r_type;
            long r_sym;
        
            // Assumes all aarch32 relocs have no addends,
            // and all 64-bit ones do.
            if (this.isAarch32)
            {
                offset = this.binaryReader.readInt(base + rel + i * 0x8);
                info = this.binaryReader.readInt(base + rel + i * 0x8 + 4);
                addend = 0;
                r_type = info & 0xff;
                r_sym = info >> 8;
            }
            else
            {
                offset = this.binaryReader.readLong(base + rel + i * 0x18);
                info = this.binaryReader.readLong(base + rel + i * 0x18 + 8);
                addend = this.binaryReader.readLong(base + rel + i * 0x18 + 0x10);
                r_type = info & 0xffffffffL;
                r_sym = info >> 32;
            }
        
            ElfSymbol sym;
            if (r_sym != 0) {
                // Note: getSymbolAt doesn't work as it relies on getValue() being the address, which is 0 for imports.
                // We manually correct the value later to point to the fake external block.
                sym = symtab.getSymbols()[(int)r_sym];
            } else {
                sym = null;
            }
            
            if (r_type != AARCH64_ElfRelocationType.R_AARCH64_TLSDESC.typeId() && r_type != ARM_ElfRelocationType.R_ARM_TLS_DESC.typeId())
            {
                locations.add(offset);
            }
            relocs.add(new NXRelocation(offset, r_sym, r_type, sym, addend));
        }
        return locations;
    }

    private Set<Long> processReadOnlyRelocations(List<NXRelocation> relocs, long relr, long relrsz) throws IOException
    {
        long base = this.program.getImageBase().getOffset();
        Set<Long> locations = new HashSet<>();
        int relocSize = 0x8;

        long where = 0;
        for (long entryNumber = 0; entryNumber < relrsz / relocSize; entryNumber++)
        {
            long entry = this.binaryReader.readLong(base + relr + entryNumber * relocSize);

            if ((entry & 1) != 0) {
                entry >>= 1;
                long i = 0;
                while (i < (relocSize * 8) - 1) {
                    if ((entry & (1L << i)) != 0) {
                        locations.add(where + i * relocSize);
                        relocs.add(new NXRelocation(where + i * relocSize, 0, R_FAKE_RELR, null, 0));
                    }
                    i++;
                }
                where += relocSize * ((relocSize * 8) - 1);
            }
            else {
                where = entry;
                locations.add(where);
                relocs.add(new NXRelocation(where, 0, R_FAKE_RELR, null, 0));
                where += relocSize;
            }
        }
        return locations;
    }
    
    protected MemoryBlock getDynamicBlock()
    {
        return this.program.getMemory().getBlock(".dynamic");
    }
    
    public BinaryReader getReader()
    {
        return this.binaryReader;
    }
    
    // Fake only what is needed for an elf dynamic table
    public static class DummyElfHeader extends ElfHeader
    {
        boolean isAarch32;
        private HashMap<Integer, ElfDynamicType> dynamicTypeMap;
        
        public DummyElfHeader(boolean isAarch32) throws ElfException {
            super(new ByteArrayProvider(Arrays.copyOf(ElfConstants.MAGIC_BYTES, ElfConstants.EI_NIDENT + 18)), s -> {});

            this.isAarch32 = isAarch32;
            dynamicTypeMap = new HashMap<>();
            ElfDynamicType.addDefaultTypes(this.dynamicTypeMap);

            ElfLoadAdapter extensionAdapter = ElfExtensionFactory.getLoadAdapter(this);
            if (extensionAdapter != null) 
            {
                extensionAdapter.addDynamicTypes(this.dynamicTypeMap);
            }
        }


        @Override
        protected HashMap<Integer, ElfDynamicType> getDynamicTypeMap()
        {
            return this.dynamicTypeMap;
        }

        @Override
        public ElfDynamicType getDynamicType(int type) 
        {
            if (this.dynamicTypeMap != null) 
            {
                return this.dynamicTypeMap.get(type);
            }
            return null; // not found
        }
        
        @Override
        public long adjustAddressForPrelink(long address) 
        {
            return address;
        }
        
        @Override
        public long unadjustAddressForPrelink(long address) 
        {
            return address;
        }
        
        @Override
        public boolean is32Bit() 
        {
            return this.isAarch32;
        }
        
        @Override
        public boolean is64Bit()
        {
            return !this.isAarch32;
        }
    }
}


================================================
FILE: src/main/java/adubbz/nx/common/InvalidMagicException.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.common;

public class InvalidMagicException extends RuntimeException
{
    public InvalidMagicException(String magic)
    {
        super(String.format("Invalid %s magic!", magic));
    }
}


================================================
FILE: src/main/java/adubbz/nx/common/NXRelocation.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.common;

import ghidra.app.util.bin.format.elf.ElfSymbol;

public class NXRelocation 
{
    public NXRelocation(long offset, long r_sym, long r_type, ElfSymbol sym, long addend) 
    {
        this.offset = offset;
        this.r_sym = r_sym;
        this.r_type = r_type;
        this.sym = sym;
        this.addend = addend;
    }
    
    public long offset;
    public long r_sym;
    public long r_type;
    public ElfSymbol sym;
    public long addend;
}

================================================
FILE: src/main/java/adubbz/nx/loader/SwitchLoader.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */

package adubbz.nx.loader;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;

import adubbz.nx.loader.common.NXProgramBuilder;
import adubbz.nx.loader.kip1.KIP1Adapter;
import adubbz.nx.loader.knx.KNXAdapter;
import adubbz.nx.loader.nro0.NRO0Adapter;
import adubbz.nx.loader.nso0.NSO0Adapter;
import adubbz.nx.loader.nxo.NXOAdapter;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.ByteProviderWrapper;
import ghidra.app.util.opinion.*;
import ghidra.framework.store.LockException;
import ghidra.program.model.address.AddressOutOfBoundsException;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.program.model.lang.LanguageID;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;

public class SwitchLoader extends BinaryLoader 
{
    public static final String SWITCH_NAME = "Nintendo Switch Binary";
    public static final LanguageID AARCH64_LANGUAGE_ID = new LanguageID("AARCH64:LE:64:v8A");
    public static final LanguageID AARCH32_LANGUAGE_ID = new LanguageID("ARM:LE:32:v8");
    private BinaryType binaryType;

    @Override
    public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException 
    {
        List<LoadSpec> loadSpecs = new ArrayList<>();
        BinaryReader reader = new BinaryReader(provider, true);
        String magic_0x0 = reader.readAsciiString(0, 4);
        String magic_0x10 = reader.readAsciiString(0x10, 4);
        
        reader.setPointerIndex(0);

        if (magic_0x0.equals("KIP1")) 
        {
            this.binaryType = BinaryType.KIP1;
        }
        else if (magic_0x0.equals("NSO0"))
        {
            this.binaryType = BinaryType.NSO0;
        }
        else if (magic_0x0.equals("\u00DF\u004F\u0003\u00D5"))
        {
            this.binaryType = BinaryType.KERNEL_800;
        }
        else if (magic_0x10.equals("NRO0"))
        {
            this.binaryType = BinaryType.NRO0;
        }
        else if (magic_0x10.equals("KIP1"))
        {
            // Note: This is kinda a bad way of determining this, but for now it gets the job done
            // and I don't believe there are any clashes.
            this.binaryType = BinaryType.SX_KIP1; 
        }
        else
            return loadSpecs;

        var adapter = this.binaryType.createAdapter(null, provider);
        
        if (adapter.isAarch32())
        {
            loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair(AARCH32_LANGUAGE_ID, new CompilerSpecID("default")), true));
        }
        else
        {
            loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair(AARCH64_LANGUAGE_ID, new CompilerSpecID("default")), true));
        }

        return loadSpecs;
    }

    @Override
    protected void loadProgramInto(Program program, Loader.ImporterSettings settings) throws IOException, CancelledException
    {
        var space = program.getAddressFactory().getDefaultAddressSpace();
        var provider = settings.provider();
        
        if (this.binaryType == BinaryType.SX_KIP1)
        {
            provider = new ByteProviderWrapper(settings.provider(), 0x10, settings.provider().length() - 0x10);
        }

        var adapter = this.binaryType.createAdapter(program, provider);
        
        // Set the base address
        try 
        {
            long baseAddress = adapter.isAarch32() ? 0x60000000L : 0x7100000000L;
            
            if (this.binaryType == BinaryType.KERNEL_800)
            {
                baseAddress = 0x80060000L;
            }

            program.setImageBase(space.getAddress(baseAddress), true);
        } 
        catch (AddressOverflowException | LockException | IllegalStateException | AddressOutOfBoundsException e) 
        {
            Msg.error(this, "Failed to set image base", e);
        }

        var loader = new NXProgramBuilder(program, provider, adapter);
        loader.load(settings.monitor());
        
        if (this.binaryType == BinaryType.KIP1)
        {
            // KIP1s always start with a branch instruction at the start of their text
            loader.createEntryFunction("entry", program.getImageBase().getOffset(), settings.monitor());
        }
    }

    @Override
    public LoaderTier getTier() 
    {
        return LoaderTier.SPECIALIZED_TARGET_LOADER;
    }

    @Override
    public int getTierPriority() 
    {
        return 0;
    }

    @Override
    public String getName() 
    {
        return SWITCH_NAME;
    }

    private enum BinaryType
    {
        KIP1("Kernel Initial Process", KIP1Adapter::new),
        NSO0("Nintendo Shared Object", NSO0Adapter::new), 
        NRO0("Nintendo Relocatable Object", NRO0Adapter::new),
        SX_KIP1("Gateway Kernel Initial Process", KIP1Adapter::new),
        KERNEL_800("Nintendo Switch Kernel 8.0.0+", KNXAdapter::new);
        
        public final String name;
        private final BiFunction<Program, ByteProvider, NXOAdapter> adapterFunc;
        
        BinaryType(String name, BiFunction<Program, ByteProvider, NXOAdapter> adapterFunc)
        {
            this.name = name;
            this.adapterFunc = adapterFunc;
        }
        
        public NXOAdapter createAdapter(Program program, ByteProvider provider)
        {
            return adapterFunc.apply(program, provider);
        }
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/common/MemoryBlockHelper.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.common;

import java.util.List;

import org.apache.commons.compress.utils.Lists;

import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;

public class MemoryBlockHelper 
{
    private TaskMonitor monitor;
    private Program program;
    private ByteProvider byteProvider;
    private MessageLog log;

    public MemoryBlockHelper(TaskMonitor monitor, Program program, ByteProvider byteProvider)
    {
        this.monitor = monitor;
        this.program = program;
        this.byteProvider = byteProvider;
        this.log = new MessageLog();
    }
    
    public void addSection(String name, long addressOffset, long offset, long length, boolean read, boolean write, boolean execute)
    {
        try
        {
            FileBytes fileBytes = MemoryBlockUtils.createFileBytes(this.program, this.byteProvider, offset, length, this.monitor);
            MemoryBlockUtils.createInitializedBlock(this.program, false, name, this.program.getImageBase().add(addressOffset), fileBytes, 0, length, "", null, read, write, execute, this.log);
        }
        catch (Exception e)
        {
            Msg.error(this, "Failed to add section " + name, e);
        }
        
        this.flushLog();
    }
    
    private void addUniqueSection(String name, long addressOffset, long offset, long length, boolean read, boolean write, boolean execute)
    {
        Memory memory = this.program.getMemory();
        Address startAddr = this.program.getImageBase().add(addressOffset);
        Address endAddr = startAddr.add(length);
        String newBlockName = name;
        int nameCounter = 0;
        
        while (memory.getBlock(newBlockName) != null)
        {
            nameCounter++;
            newBlockName = name + "." + nameCounter; 
        }
        
        Msg.info(this, "Adding unique section " + newBlockName + " from " + startAddr + " to " + endAddr);
        this.addSection(newBlockName, offset, offset, length, read, write, execute);
    }
    
    public void addFillerSection(String name, long addressOffset, long length, boolean read, boolean write, boolean execute)
    {
        Memory memory = this.program.getMemory();
        Address startAddr = this.program.getImageBase().add(addressOffset);
        Address endAddr = startAddr.add(length);
        AddressRange range = new AddressRangeImpl(startAddr, endAddr);
        
        List<MemoryBlock> blocksInRange = Lists.newArrayList();
        
        for (MemoryBlock block : memory.getBlocks())
        {
            AddressRange blockRange = new AddressRangeImpl(block.getStart(), block.getEnd());
            
            if (range.intersects(blockRange))
            {
                blocksInRange.add(block);
            }
        }
        
        if (blocksInRange.isEmpty())
        {
            Msg.info(this, "Adding filler section " + name + " from " + startAddr + " to " + endAddr);
            this.addSection(name, addressOffset, addressOffset, range.getLength(), read, write, execute);
            return;
        }
        
        Address fillerBlockStart = startAddr;
        AddressRange fillerBlockRange;
        
        for (MemoryBlock block : blocksInRange)
        {
            fillerBlockRange = new AddressRangeImpl(fillerBlockStart, block.getStart());
            
            if (fillerBlockRange.getLength() > 2)
            {
                long offset = fillerBlockRange.getMinAddress().subtract(this.program.getImageBase());
                this.addUniqueSection(name, offset, offset, fillerBlockRange.getLength() - 1, read, write, execute);
            }
            
            fillerBlockStart = block.getEnd().add(1);
        }
        
        fillerBlockRange = new AddressRangeImpl(fillerBlockStart, endAddr);
        
        if (fillerBlockRange.getLength() > 2)
        {
            long offset = fillerBlockRange.getMinAddress().subtract(this.program.getImageBase());
            this.addUniqueSection(name, offset, offset, fillerBlockRange.getLength() - 1, read, write, execute);
        }
    }
    
    public void flushLog()
    {
        if (this.log.hasMessages())
        {
            Msg.info(this, this.log.toString());
            this.log.clear();
        }
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/common/NXProgramBuilder.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.common;

import adubbz.nx.common.NXRelocation;
import adubbz.nx.loader.nxo.NXO;
import adubbz.nx.loader.nxo.NXOAdapter;
import adubbz.nx.loader.nxo.NXOSection;
import adubbz.nx.loader.nxo.NXOSectionType;
import adubbz.nx.util.UIUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Longs;
import ghidra.app.cmd.label.SetLabelPrimaryCmd;
import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.elf.ElfDynamicType;
import ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants;
import ghidra.app.util.bin.format.elf.ElfStringTable;
import ghidra.app.util.bin.format.elf.ElfSymbol;
import ghidra.app.util.bin.format.elf.relocation.AARCH64_ElfRelocationType;
import ghidra.app.util.bin.format.elf.relocation.ARM_ElfRelocationType;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.disassemble.Disassembler;
import ghidra.program.model.address.*;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.data.TerminatedStringDataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.reloc.Relocation;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.exception.NotFoundException;
import ghidra.util.task.TaskMonitor;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static adubbz.nx.common.ElfCompatibilityProvider.R_FAKE_RELR;

public class NXProgramBuilder 
{
    protected ByteProvider fileByteProvider;
    protected Program program;
    protected NXO nxo;
    
    protected AddressSpace aSpace;
    protected MemoryBlockHelper memBlockHelper;
    
    protected List<PltEntry> pltEntries = new ArrayList<>();
    
    protected int undefSymbolCount;
    
    public NXProgramBuilder(Program program, ByteProvider provider, NXOAdapter adapter)
    {
        this.program = program;
        this.fileByteProvider = provider;
        this.nxo = new NXO(program, adapter, program.getImageBase().getOffset());
    }
    
    public void load(TaskMonitor monitor) throws CancelledException
    {
        NXOAdapter adapter = this.nxo.getAdapter();
        ByteProvider memoryProvider = adapter.getMemoryProvider();
        this.aSpace = program.getAddressFactory().getDefaultAddressSpace();
        
        try 
        {
            this.memBlockHelper = new MemoryBlockHelper(monitor, this.program, memoryProvider);
            
            NXOSection text = adapter.getSection(NXOSectionType.TEXT);
            NXOSection rodata = adapter.getSection(NXOSectionType.RODATA);
            NXOSection data = adapter.getSection(NXOSectionType.DATA);
            
            if (adapter.getDynamicSize() == 0)
            {
                // We can't create .dynamic, so work with what we've got.
                return;
            }
            
            this.memBlockHelper.addSection(".dynamic", adapter.getDynamicOffset(), adapter.getDynamicOffset(), adapter.getDynamicSize(), true, true, false);

            // Create dynamic sections
            this.tryCreateDynBlock(".dynstr", ElfDynamicType.DT_STRTAB, ElfDynamicType.DT_STRSZ);
            this.tryCreateDynBlock(".init_array", ElfDynamicType.DT_INIT_ARRAY, ElfDynamicType.DT_INIT_ARRAYSZ);
            this.tryCreateDynBlock(".fini_array", ElfDynamicType.DT_FINI_ARRAY, ElfDynamicType.DT_FINI_ARRAYSZ);
            this.tryCreateDynBlock(".rela.dyn", ElfDynamicType.DT_RELA, ElfDynamicType.DT_RELASZ);
            this.tryCreateDynBlock(".rel.dyn", ElfDynamicType.DT_REL, ElfDynamicType.DT_RELSZ);
            this.tryCreateDynBlock(".relr.dyn", ElfDynamicType.DT_RELR, ElfDynamicType.DT_RELRSZ);
            
            if (adapter.isAarch32())
            {
                this.tryCreateDynBlock(".rel.plt", ElfDynamicType.DT_JMPREL, ElfDynamicType.DT_PLTRELSZ);
            }
            else
            {
                this.tryCreateDynBlock(".rela.plt", ElfDynamicType.DT_JMPREL, ElfDynamicType.DT_PLTRELSZ);
            }

            this.tryCreateDynBlockWithRange(".hash", ElfDynamicType.DT_HASH, ElfDynamicType.DT_GNU_HASH);
            this.tryCreateDynBlockWithRange(".gnu.hash", ElfDynamicType.DT_GNU_HASH, ElfDynamicType.DT_SYMTAB);
            
            if (adapter.getSymbolTable(this.program) != null)
            {
                Msg.info(this, String.format("String table offset %X, base addr %X", adapter.getSymbolTable(this.program).getFileOffset(), this.nxo.getBaseAddress()));
                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);
            }
            
            this.setupRelocations(monitor);
            this.createGlobalOffsetTable();
            
            this.memBlockHelper.addFillerSection(".text", text.getOffset(), text.getSize(), true, false, true);
            this.memBlockHelper.addFillerSection(".rodata", rodata.getOffset(), rodata.getSize(), true, false, false);
            this.memBlockHelper.addFillerSection(".data", data.getOffset(), data.getSize(), true, true, false);
            
            this.setupStringTable();
            this.setupSymbolTable();
            
            // Create BSS. This needs to be done before the EXTERNAL block is created in setupImports
            Address bssStartAddr = aSpace.getAddress(this.nxo.getBaseAddress() + adapter.getBssOffset());
            Msg.info(this, String.format("Created bss from 0x%X to 0x%X", bssStartAddr.getOffset(), bssStartAddr.getOffset() + adapter.getBssSize()));
            MemoryBlockUtils.createUninitializedBlock(this.program, false, ".bss", bssStartAddr, adapter.getBssSize(), "", null, true, true, false, new MessageLog());
            
            this.setupImports(monitor);
            this.performRelocations();
            
            // Set all data in the GOT to the pointer data type
            // NOTE: Currently the got range may be null in e.g. old libnx nros
            // We may want to manually figure this out ourselves in future.
            if (adapter.getGotSize() > 0)
            {
                for (Address addr = this.aSpace.getAddress(adapter.getGotOffset()); addr.compareTo(this.aSpace.getAddress(adapter.getGotOffset() + adapter.getGotSize())) < 0; addr = addr.add(adapter.getOffsetSize()))
                {
                    this.createPointer(addr);
                }
            }
        }
        catch (IOException | NotFoundException | AddressOverflowException | AddressOutOfBoundsException | CodeUnitInsertionException | MemoryAccessException | InvalidInputException e)
        {
            e.printStackTrace();
        }
        
        // Ensure memory blocks are ordered from first to last.
        // Normally they are ordered by the order they are added.
        UIUtil.sortProgramTree(this.program);
    }
    
    public NXO getNxo()
    {
        return this.nxo;
    }
    
    protected void setupStringTable() throws AddressOverflowException, CodeUnitInsertionException
    {
       NXOAdapter adapter = this.nxo.getAdapter();
       ElfStringTable stringTable = adapter.getStringTable(this.program);
       
       if (stringTable == null)
           return;
       
       long stringTableAddrOffset = stringTable.getAddressOffset();
        
        Address address = this.aSpace.getAddress(stringTableAddrOffset);
        Address end = address.addNoWrap(stringTable.getLength() - 1);
        
        while (address.compareTo(end) < 0) 
        {
            int length = this.createString(address);
            address = address.addNoWrap(length);
        }
    }
    
    protected void setupSymbolTable()
    {
        NXOAdapter adapter = this.nxo.getAdapter();
        
        if (adapter.getSymbolTable(this.program) != null)
        {
            for (ElfSymbol elfSymbol : adapter.getSymbolTable(this.program).getSymbols()) 
            {
                String symName = elfSymbol.getNameAsString();
    
                if (elfSymbol.getSectionHeaderIndex() == ElfSectionHeaderConstants.SHN_UNDEF && symName != null && !symName.isEmpty())
                {
                    // NOTE: We handle adding these symbols later
                    this.undefSymbolCount++;
                }
                else
                {
                    Address address = this.aSpace.getAddress(this.nxo.getBaseAddress() + elfSymbol.getValue());
                    this.evaluateElfSymbol(elfSymbol, address, false);
                }
            }
        }
    }
    
    protected void setupRelocations(TaskMonitor monitor) throws AddressOutOfBoundsException, NotFoundException, IOException, CancelledException {
        NXOAdapter adapter = this.nxo.getAdapter();
        ByteProvider memoryProvider = adapter.getMemoryProvider();
        BinaryReader memoryReader = adapter.getMemoryReader();
        ImmutableList<NXRelocation> pltRelocs = adapter.getPltRelocations(this.program);
        
        if (pltRelocs.isEmpty())
        {
            Msg.info(this, "No plt relocations found.");
            return;
        }
            
        long pltGotStart = pltRelocs.get(0).offset;
        long pltGotEnd = pltRelocs.get(pltRelocs.size() - 1).offset + adapter.getOffsetSize();
        
        if (adapter.getDynamicTable(this.program).containsDynamicValue(ElfDynamicType.DT_PLTGOT))
        {
            long pltGotOff = adapter.getDynamicTable(this.program).getDynamicValue(ElfDynamicType.DT_PLTGOT);
            this.memBlockHelper.addSection(".got.plt", pltGotOff, pltGotOff, pltGotEnd - pltGotOff, true, false, false);
        }
        
        // Only add .plt on aarch64
        if (adapter.isAarch32())
        {
            return;
        }
        
        int last = 12;
        
        while (true)
        {
            int pos = -1;
            
            for (int i = last; i < adapter.getSection(NXOSectionType.TEXT).getSize(); i++)
            {
                if (memoryReader.readInt(i) == 0xD61F0220)
                {
                    pos = i;
                    break;
                }
            }
            
            if (pos == -1) break;
            last = pos + 1;
            if ((pos % 4) != 0) continue;
            
            int off = pos - 12;
            long a = Integer.toUnsignedLong(memoryReader.readInt(off));
            long b = Integer.toUnsignedLong(memoryReader.readInt(off + 4));
            long c = Integer.toUnsignedLong(memoryReader.readInt(off + 8));
            long d = Integer.toUnsignedLong(memoryReader.readInt(off + 12));

            if (d == 0xD61F0220L && (a & 0x9f00001fL) == 0x90000010L && (b & 0xffe003ffL) == 0xf9400211L)
            {
                long base = off & ~0xFFFL;
                long immhi = (a >> 5) & 0x7ffffL;
                long immlo = (a >> 29) & 3;
                long paddr = base + ((immlo << 12) | (immhi << 14));
                long poff = ((b >> 10) & 0xfffL) << 3;
                long target = paddr + poff;
                if (pltGotStart <= target && target < pltGotEnd)
                    this.pltEntries.add(new PltEntry(off, target));
            }
        }

        if (!this.pltEntries.isEmpty()) {
            long pltStart = this.pltEntries.get(0).off;
            long pltEnd = this.pltEntries.get(this.pltEntries.size() - 1).off + 0x10;
            this.memBlockHelper.addSection(".plt", pltStart, pltStart, pltEnd - pltStart, true, false, false);
            // Disassemble the entire section, so AARCH64PltThunkAnalyzer works for functions within this binary.
            disassembleRange(program.getImageBase().add(pltStart), program.getImageBase().add(pltEnd), program, monitor);
        }
        else {
            // TODO: Find a way to locate the plt in CFI-enabled binaries.
            Msg.error(this, "No PLT entries found, does this binary have CFI enabled? This loader currently can't locate the plt in them.");
        }
    }
    
    protected void createGlobalOffsetTable() throws AddressOutOfBoundsException
    {
        NXOAdapter adapter = this.nxo.getAdapter();
        ByteProvider memoryProvider = adapter.getMemoryProvider();
        
        // .got.plt needs to have been created first
        long gotStartOff = adapter.getGotOffset() - this.nxo.getBaseAddress();
        long gotSize = adapter.getGotSize();
        
        if (gotSize > 0)
        {
            Msg.info(this, String.format("Created got from 0x%X to 0x%X", gotStartOff, gotStartOff + gotSize));
            this.memBlockHelper.addSection(".got", gotStartOff, gotStartOff, gotSize, true, false, false);
        }
    }
    
    protected void performRelocations() throws MemoryAccessException, InvalidInputException, AddressOutOfBoundsException
    {
        NXOAdapter adapter = this.nxo.getAdapter();
        Map<Long, String> gotNameLookup = new HashMap<>(); 
        
        // Relocations again
        for (NXRelocation reloc : adapter.getRelocations(this.program)) 
        {
            Address target = this.aSpace.getAddress(reloc.offset + this.nxo.getBaseAddress());
            long originalValue = adapter.isAarch32() ? this.program.getMemory().getInt(target) : this.program.getMemory().getLong(target);
            
            if (reloc.r_type == ARM_ElfRelocationType.R_ARM_GLOB_DAT.typeId() ||
                    reloc.r_type == ARM_ElfRelocationType.R_ARM_JUMP_SLOT.typeId() ||
                    reloc.r_type == ARM_ElfRelocationType.R_ARM_ABS32.typeId()) 
                {
                    if (reloc.sym == null) 
                    {
                        Msg.error(this, String.format("Error: Relocation at %x failed", target.getOffset()));
                    } 
                    else 
                    {
                        program.getMemory().setInt(target, (int)(reloc.sym.getValue() + this.nxo.getBaseAddress()));
                    }
                } 
            else if (reloc.r_type == ARM_ElfRelocationType.R_ARM_RELATIVE.typeId())
            {
                program.getMemory().setInt(target, (int)(program.getMemory().getInt(target) + this.nxo.getBaseAddress()));
            }
            else if (reloc.r_type == AARCH64_ElfRelocationType.R_AARCH64_GLOB_DAT.typeId() ||
                reloc.r_type == AARCH64_ElfRelocationType.R_AARCH64_JUMP_SLOT.typeId() ||
                reloc.r_type == AARCH64_ElfRelocationType.R_AARCH64_ABS64.typeId()) 
            {
                if (reloc.sym == null) 
                {
                    // Ignore these sorts of errors, the IDA loader fails on some relocations too.
                    // It doesn't appear to be a Ghidra specific issue.
                    //Msg.error(this, String.format("Error: Relocation at %x failed", target.getOffset()));
                } 
                else 
                {
                    program.getMemory().setLong(target, reloc.sym.getValue() + this.nxo.getBaseAddress() + reloc.addend);
                    
                    if (reloc.addend == 0)
                        gotNameLookup.put(reloc.offset, reloc.sym.getNameAsString());
                }
            } 
            else if (reloc.r_type == AARCH64_ElfRelocationType.R_AARCH64_RELATIVE.typeId()) 
            {
                program.getMemory().setLong(target, this.nxo.getBaseAddress() + reloc.addend);
            }
            else if (reloc.r_type == R_FAKE_RELR) {
                if (this.nxo.getAdapter().isAarch32()) {
                    // TODO: Add RELRO support for 32-bit
                    Msg.error(this, "TODO: RELRO support for 32-bit");
                    continue;
                }

                program.getMemory().setLong(target, this.nxo.getBaseAddress() + originalValue);
            }
            else 
            {
                Msg.info(this, String.format("TODO: r_type 0x%x", reloc.r_type));
            }
            
            long newValue = adapter.isAarch32() ? this.program.getMemory().getInt(target) : this.program.getMemory().getLong(target);
            
            // Store relocations for Ghidra's relocation table view
            if (newValue != originalValue)
            {
                String symbolName = null;
                
                if (reloc.sym != null) 
                {
                    symbolName = reloc.sym.getNameAsString();
                }

                // Status APPLIED: "Relocation was applied successfully and resulted in the modification of memory bytes."
                program.getRelocationTable().add(target, Relocation.Status.APPLIED,(int)reloc.r_type, new long[] { reloc.r_sym }, Longs.toByteArray(originalValue), symbolName);
            }
        }
        
        for (PltEntry entry : this.pltEntries)
        {
            if (gotNameLookup.containsKey(entry.target))
            {
                Address addr = this.aSpace.getAddress(this.nxo.getBaseAddress() + entry.off);
                String name = gotNameLookup.get(entry.target);
                if (name != null && !name.isEmpty())
                {
                    ExternalLocation extLoc = program.getExternalManager().getUniqueExternalLocation(Library.UNKNOWN, name);
                    // AARCH64PltThunkAnalyzer won't be able to find a valid destination address for entries referencing external functions.
                    if (extLoc != null) {
                        Address lastAddr = addr.add(0x10 - 0x4);
                        Instruction lastInstruction = program.getListing().getInstructionAt(lastAddr);

                        // Make sure the last instruction of this function is a branch.
                        // This check is also performed by AARCH64PltThunkAnalyzer.
                        if (lastInstruction != null && lastInstruction.getMnemonicString().equals("br")) {
                            try {
                                program.getFunctionManager().createThunkFunction(name, null, addr, new AddressSet(addr, lastAddr), extLoc.getFunction(), SourceType.IMPORTED);
                                lastInstruction.setFlowOverride(FlowOverride.CALL_RETURN);
                            } catch (OverlappingFunctionException e) {
                                Msg.error(this, String.format("Couldn't create thunk function at %s: %s", addr, e));
                            }
                        }
                        else {
                            // This shouldn't happen.
                            Msg.warn(this, String.format("Couldn't find a valid last instruction for thunk function %s at: %s", addr, lastAddr));
                            createOneByteFunction(name, addr, false).setThunkedFunction(extLoc.getFunction());
                        }
                    }
                    else {
                        // Already defined functions are skipped by AARCH64PltThunkAnalyzer, so we only create a symbol.
                        this.createSymbol(addr, name, false, false, null);
                    }
                }
            }
        }
    }
    
    protected void setupImports(TaskMonitor monitor)
    {
        NXOAdapter adapter = this.nxo.getAdapter();
        this.processImports(monitor);
        
        if (this.undefSymbolCount == 0)
            return;
        
        // Create the fake EXTERNAL block after everything else
        long lastAddrOff = this.nxo.getBaseAddress(); 
        
        for (MemoryBlock block : this.program.getMemory().getBlocks())
        {
            if (block.getEnd().getOffset() > lastAddrOff)
                lastAddrOff = block.getEnd().getOffset();
        }
        
        int undefEntrySize = 0x1000; // We create fake 0x1000 regions for each import
        long externalBlockAddrOffset = ((lastAddrOff + 0xFFF) & ~0xFFF) + undefEntrySize; // plus 1 so we don't end up on the "end" symbol
        
        // Create the block where imports will be located
        this.createExternalBlock(this.aSpace.getAddress(externalBlockAddrOffset), (long) this.undefSymbolCount * undefEntrySize);

        // Handle imported symbols
        if (adapter.getSymbolTable(this.program) != null)
        {
            for (ElfSymbol elfSymbol : adapter.getSymbolTable(this.program).getSymbols())
            {
                String symName = elfSymbol.getNameAsString();

                if (elfSymbol.getSectionHeaderIndex() == ElfSectionHeaderConstants.SHN_UNDEF && symName != null && !symName.isEmpty())
                {
                    Address address = this.aSpace.getAddress(externalBlockAddrOffset);
                    try {
                        Field elfSymbolValue = elfSymbol.getClass().getDeclaredField("st_value");
                        elfSymbolValue.setAccessible(true);
                        // Fix the value to be non-zero, instead pointing to our fake EXTERNAL block
                        // make the symbol value relative to the image base, as that's how all other symbols are stored
                        elfSymbolValue.set(elfSymbol, externalBlockAddrOffset - this.nxo.getBaseAddress());
                    } catch (NoSuchFieldException | IllegalAccessException e) {
                        Msg.error(this, "Couldn't find or set st_value field in ElfSymbol.", e);
                    }
                    this.evaluateElfSymbol(elfSymbol, address, true);
                    externalBlockAddrOffset += undefEntrySize;
                }
            }
        }
    }
    
    private void createExternalBlock(Address addr, long size) 
    {
        try 
        {
            MemoryBlock block = this.program.getMemory().createUninitializedBlock("EXTERNAL", addr, size, false);

            // assume any value in external is writable.
            block.setWrite(true);
            block.setSourceName("Switch Loader");
            block.setComment("NOTE: This block is artificial and is used to make relocations work correctly");
        }
        catch (Exception e) 
        {
            Msg.error(this, "Error creating external memory block: " + " - " + e.getMessage());
        }
    }
    
    private void processImports(TaskMonitor monitor) 
    {
        NXOAdapter adapter = this.nxo.getAdapter();
        
        if (monitor.isCancelled())
            return;

        monitor.setMessage("Processing imports...");

        ExternalManager extManager = program.getExternalManager();
        String[] neededLibs = adapter.getDynamicLibraryNames(this.program);
        
        for (String neededLib : neededLibs) 
        {
            try {
                extManager.setExternalPath(neededLib, null, false);
            }
            catch (InvalidInputException e) {
                Msg.error(this, "Bad library name: " + neededLib);
            }
        }
    }
    
    public Address createEntryFunction(String name, long entryAddr, TaskMonitor monitor) 
    {
        Address entryAddress = this.aSpace.getAddress(entryAddr);

        // TODO: Entry may refer to a pointer - make sure we have execute permission
        MemoryBlock block = this.program.getMemory().getBlock(entryAddress);
        
        if (block == null || !block.isExecute()) 
        {
            return entryAddress;
        }

        Function function = program.getFunctionManager().getFunctionAt(entryAddress);
        
        if (function != null) 
        {
            program.getSymbolTable().addExternalEntryPoint(entryAddress);
            return entryAddress; // symbol-based function already created
        }

        try 
        {
            this.createOneByteFunction(name, entryAddress, true);
        }
        catch (Exception e) 
        {
            Msg.error(this, "Could not create symbol at entry point: " + e);
        }

        return entryAddress;
    }
    
    protected int createString(Address address) throws CodeUnitInsertionException
    {
        Data d = this.program.getListing().getDataAt(address);
        
        if (d == null || !TerminatedStringDataType.dataType.isEquivalent(d.getDataType())) 
        {
            d = this.program.getListing().createData(address, TerminatedStringDataType.dataType, -1);
        }
        
        return d.getLength();
    }
    
    protected int createPointer(Address address) throws CodeUnitInsertionException
    {
        NXOAdapter adapter = this.nxo.getAdapter();
        Data d = this.program.getListing().getDataAt(address);
        
        if (d == null || !PointerDataType.dataType.isEquivalent(d.getDataType())) 
        {
            d = this.program.getListing().createData(address, PointerDataType.dataType, adapter.getOffsetSize());
        }
        
        return d.getLength();
    }
    
    private void evaluateElfSymbol(ElfSymbol elfSymbol, Address address, boolean isFakeExternal)
    {
        try
        {
            if (elfSymbol.isSection()) {
                // Do not add section symbols to program symbol table
                return;
            }
    
            String name = elfSymbol.getNameAsString();
            if (name == null || name.isEmpty()) {
                return;
            }
    
            boolean isPrimary = (elfSymbol.getType() == ElfSymbol.STT_FUNC) ||
                (elfSymbol.getType() == ElfSymbol.STT_OBJECT) || (elfSymbol.getSize() != 0);
            // don't displace existing primary unless symbol is a function or object symbol
            if (name.contains("@")) {
                isPrimary = false; // do not make version symbol primary
            }
            else if (!isPrimary && (elfSymbol.isGlobal() || elfSymbol.isWeak())) {
                Symbol existingSym = program.getSymbolTable().getPrimarySymbol(address);
                isPrimary = (existingSym == null);
            }
    
            this.createSymbol(address, name, isPrimary, elfSymbol.isAbsolute(), null);
    
            // NOTE: treat weak symbols as global so that other programs may link to them.
            // In the future, we may want additional symbol flags to denote the distinction
            if ((elfSymbol.isGlobal() || elfSymbol.isWeak()) && !isFakeExternal) 
            {
                program.getSymbolTable().addExternalEntryPoint(address);
            }
            
            if (elfSymbol.getType() == ElfSymbol.STT_FUNC) 
            {
                Function existingFunction = program.getFunctionManager().getFunctionAt(address);
                if (existingFunction == null) {
                    Function f = createOneByteFunction(null, address, false);
                    if (f != null) {
                        if (isFakeExternal && !f.isThunk()) {
                            ExternalLocation extLoc = program.getExternalManager().addExtFunction(Library.UNKNOWN, name, null, SourceType.IMPORTED);
                            f.setThunkedFunction(extLoc.getFunction());
                            // revert thunk function symbol to default source
                            Symbol s = f.getSymbol();
                            if (s.getSource() != SourceType.DEFAULT) {
                                program.getSymbolTable().removeSymbolSpecial(f.getSymbol());
                            }
                        }
                    }
                }
            }
        }
        catch (DuplicateNameException | InvalidInputException e)
        {
            e.printStackTrace();
        }
    }
    
    public Function createOneByteFunction(String name, Address address, boolean isEntry) 
    {
        Function function = null;
        try 
        {
            FunctionManager functionMgr = program.getFunctionManager();
            function = functionMgr.getFunctionAt(address);
            if (function == null) {
                function = functionMgr.createFunction(null, address, new AddressSet(address), SourceType.IMPORTED);
            }
        }
        catch (Exception e) 
        {
            Msg.error(this, "Error while creating function at " + address + ": " + e.getMessage());
        }

        try 
        {
            if (name != null) 
            {
                createSymbol(address, name, true, false, null);
            }
            if (isEntry) {
                program.getSymbolTable().addExternalEntryPoint(address);
            }
        }
        catch (Exception e) {
            Msg.error(this, "Error while creating symbol " + name + " at " + address + ": " + e.getMessage());
        }
        return function;
    }
    
    public Symbol createSymbol(Address addr, String name, boolean isPrimary, boolean pinAbsolute, Namespace namespace) throws InvalidInputException 
    {
        // TODO: At this point, we should be marking as data or code
        SymbolTable symbolTable = program.getSymbolTable();
        Symbol sym = symbolTable.createLabel(addr, name, namespace, SourceType.IMPORTED);
        if (isPrimary) {
            checkPrimary(sym);
        }
        if (pinAbsolute && !sym.isPinned()) {
            sym.setPinned(true);
        }
        return sym;
    }

    // Source: https://github.com/NationalSecurityAgency/ghidra/blob/de7c3eaee2a4bc993a402e371b039c2bb2d6c545/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfDefaultGotPltMarkup.java#L545
    private void disassembleRange(Address start, Address end, Program program, TaskMonitor monitor)
            throws CancelledException {
        AddressSet set = new AddressSet(start, end);
        Disassembler disassembler = Disassembler.getDisassembler(program, monitor, m -> {
            /* silent */});
        while (!set.isEmpty()) {
            monitor.checkCancelled();
            AddressSet disset = disassembler.disassemble(set.getMinAddress(), null, true);
            if (disset.isEmpty()) {
                // Stop on first error but discard error bookmark since
                // some plt sections are partly empty and must rely
                // on normal flow disassembly during analysis
                program.getBookmarkManager()
                        .removeBookmarks(set, BookmarkType.ERROR,
                                Disassembler.ERROR_BOOKMARK_CATEGORY, monitor);
                break;//we did not disassemble anything...
            }
            set.delete(disset);
        }
    }
    
    private Symbol checkPrimary(Symbol sym) 
    {
        if (sym == null || sym.isPrimary()) 
        {
            return sym;
        }

        String name = sym.getName();
        Address addr = sym.getAddress();

        if (name.indexOf("@") > 0) { // <sym>@<version> or <sym>@@<version>
            return sym; // do not make versioned symbols primary
        }

        // if starts with a $, probably a markup symbol, like $t,$a,$d
        if (name.startsWith("$")) {
            return sym;
        }

        // if sym starts with a non-letter give preference to an existing symbol which does
        if (!Character.isAlphabetic(name.codePointAt(0))) {
            Symbol primarySymbol = program.getSymbolTable().getPrimarySymbol(addr);
            if (primarySymbol != null && primarySymbol.getSource() != SourceType.DEFAULT &&
                Character.isAlphabetic(primarySymbol.getName().codePointAt(0))) {
                return sym;
            }
        }

        SetLabelPrimaryCmd cmd = new SetLabelPrimaryCmd(addr, name, sym.getParentNamespace());
        if (cmd.applyTo(program)) {
            return program.getSymbolTable().getSymbol(name, addr, sym.getParentNamespace());
        }

        Msg.error(this, cmd.getStatusMsg());

        return sym;
    }
    
    public boolean hasImportedSymbol(Address addr)
    {
        for (Symbol sym : program.getSymbolTable().getSymbols(addr))
        {
            if (sym.getSource() == SourceType.IMPORTED)
                return true;
        }
        
        return false;
    }
    
    protected void tryCreateDynBlock(String name, ElfDynamicType offsetType, ElfDynamicType sizeType)
    {
        NXOAdapter adapter = this.nxo.getAdapter();
        
        try
        {
            if (adapter.getDynamicTable(this.program).containsDynamicValue(offsetType) && adapter.getDynamicTable(this.program).containsDynamicValue(sizeType))
            {
                long offset = adapter.getDynamicTable(this.program).getDynamicValue(offsetType);
                long size = adapter.getDynamicTable(this.program).getDynamicValue(sizeType);
                
                if (size > 0)
                {
                    Msg.info(this, String.format("Created dyn block %s at 0x%X of size 0x%X", name, offset, size));
                    this.memBlockHelper.addSection(name, offset, offset, size, true, false, false);
                }
            }
        }
        catch (NotFoundException | AddressOutOfBoundsException e)
        {
            Msg.warn(this, String.format("Couldn't create dyn block %s. It may be absent.", name), e);
        }
    }
    
    protected void tryCreateDynBlockWithRange(String name, ElfDynamicType start, ElfDynamicType end)
    {
        NXOAdapter adapter = this.nxo.getAdapter();
        
        try
        {
            if (adapter.getDynamicTable(this.program).containsDynamicValue(start) && adapter.getDynamicTable(this.program).containsDynamicValue(end))
            {
                long offset = adapter.getDynamicTable(this.program).getDynamicValue(start);
                long size = adapter.getDynamicTable(this.program).getDynamicValue(end) - offset;
                
                if (size > 0)
                {
                    Msg.info(this, String.format("Created dyn block %s at 0x%X of size 0x%X", name, offset, size));
                    this.memBlockHelper.addSection(name, offset, offset, size, true, false, false);
                }
            }
        }
        catch (NotFoundException | AddressOutOfBoundsException e)
        {
            Msg.warn(this, String.format("Couldn't create dyn block %s. It may be absent.", name), e);
        }
    }
    
    private static class PltEntry
    {
        long off;
        long target;
        
        public PltEntry(long offset, long target)
        {
            this.off = offset;
            this.target = target;
        }
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/kip1/KIP1Adapter.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.kip1;

import java.io.IOException;

import adubbz.nx.loader.nxo.MOD0Adapter;
import adubbz.nx.loader.nxo.NXOSection;
import adubbz.nx.loader.nxo.NXOSectionType;
import adubbz.nx.util.ByteUtil;
import ghidra.app.util.bin.ByteArrayProvider;
import ghidra.app.util.bin.ByteProvider;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;

public class KIP1Adapter extends MOD0Adapter
{
    protected KIP1Header kip1;
    
    protected ByteProvider memoryProvider;
    protected NXOSection[] sections;
    
    public KIP1Adapter(Program program, ByteProvider fileProvider)
    {
        super(program, fileProvider);
        
        try
        {
            this.read();
        }
        catch (IOException e)
        {
            Msg.error(this, "Failed to read KIP1");
            e.printStackTrace();
        }
    }
    
    private void read() throws IOException
    {
        this.kip1 = new KIP1Header(this.fileReader, 0x0);
        
        KIP1SectionHeader textHeader = this.kip1.getSectionHeader(NXOSectionType.TEXT);
        KIP1SectionHeader rodataHeader = this.kip1.getSectionHeader(NXOSectionType.RODATA);
        KIP1SectionHeader dataHeader = this.kip1.getSectionHeader(NXOSectionType.DATA);
        
        long textOffset = textHeader.getOutOffset();
        long rodataOffset = rodataHeader.getOutOffset();
        long dataOffset = dataHeader.getOutOffset();
        long textSize = textHeader.getDecompressedSize();
        long rodataSize = rodataHeader.getDecompressedSize();
        long dataSize = dataHeader.getDecompressedSize();
        
        // The data section is last, so we use its offset + decompressed size
        byte[] full = new byte[Math.toIntExact(dataOffset + dataSize)];
        byte[] decompressedText;
        byte[] decompressedRodata;
        byte[] decompressedData;
        
        if (this.kip1.isSectionCompressed(NXOSectionType.TEXT))
        {
            byte[] compressedText = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.TEXT), this.kip1.getCompressedSectionSize(NXOSectionType.TEXT));
            decompressedText = ByteUtil.kip1BlzDecompress(compressedText, Math.toIntExact(textSize));
        }
        else
        {
            decompressedText = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.TEXT), textSize);
        }
        
        System.arraycopy(decompressedText, 0, full, Math.toIntExact(textOffset), Math.toIntExact(textSize));
        
        if (this.kip1.isSectionCompressed(NXOSectionType.RODATA))
        {
            byte[] compressedRodata = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.RODATA), this.kip1.getCompressedSectionSize(NXOSectionType.RODATA));
            decompressedRodata = ByteUtil.kip1BlzDecompress(compressedRodata, Math.toIntExact(rodataSize));
        }
        else
        {
            decompressedRodata = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.RODATA), rodataSize);
        }
        
        System.arraycopy(decompressedRodata, 0, full, Math.toIntExact(rodataOffset), Math.toIntExact(rodataSize));
        
        if (this.kip1.isSectionCompressed(NXOSectionType.DATA))
        {
            byte[] compressedData = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.DATA), this.kip1.getCompressedSectionSize(NXOSectionType.DATA));
            decompressedData = ByteUtil.kip1BlzDecompress(compressedData, Math.toIntExact(dataSize));
        }
        else
        {
            decompressedData = this.fileProvider.readBytes(this.kip1.getSectionFileOffset(NXOSectionType.DATA), dataSize);
        }
        
        System.arraycopy(decompressedData, 0, full, Math.toIntExact(dataOffset), Math.toIntExact(dataSize));
        this.memoryProvider = new ByteArrayProvider(full);
        
        this.sections = new NXOSection[3];
        this.sections[NXOSectionType.TEXT.ordinal()] = new NXOSection(NXOSectionType.TEXT, textOffset, textSize);
        this.sections[NXOSectionType.RODATA.ordinal()] = new NXOSection(NXOSectionType.RODATA, rodataOffset, rodataSize);
        this.sections[NXOSectionType.DATA.ordinal()] = new NXOSection(NXOSectionType.DATA, dataOffset, dataSize);
    }

    @Override
    public ByteProvider getMemoryProvider() 
    {
        return this.memoryProvider;
    }

    @Override
    public NXOSection[] getSections() 
    {
        return this.sections;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/kip1/KIP1Header.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.kip1;

import java.io.IOException;

import adubbz.nx.common.InvalidMagicException;
import adubbz.nx.loader.nxo.NXOSectionType;
import ghidra.app.util.bin.BinaryReader;
import ghidra.util.Msg;

public class KIP1Header
{
    private String magic;
    private String name;
    private long tid;
    private int processCategory;
    private byte mainThreadPriority;
    private byte defaultCpuCore;
    private byte flags;
    
    private KIP1SectionHeader[] sectionHeaders = new KIP1SectionHeader[6];
    
    public KIP1Header(BinaryReader reader, int readerOffset)
    {
        long prevPointerIndex = reader.getPointerIndex();
        
        reader.setPointerIndex(readerOffset);
        this.readHeader(reader);
        
        // Restore the previous pointer index
        reader.setPointerIndex(prevPointerIndex);
    }
    
    private void readHeader(BinaryReader reader)
    {
        try 
        {
            this.magic = reader.readNextAsciiString(4);
            
            if (!this.magic.equals("KIP1"))
                throw new InvalidMagicException("KIP1");
            
            this.name = reader.readNextAsciiString(12);
            this.tid = reader.readNextLong();
            this.processCategory = reader.readNextInt();
            this.mainThreadPriority = reader.readNextByte();
            this.defaultCpuCore = reader.readNextByte();
            reader.readNextByte(); // Unused
            this.flags = reader.readNextByte();
            
            for (int i = 0; i < this.sectionHeaders.length; i++)
            {
                this.sectionHeaders[i] = new KIP1SectionHeader(reader);
            }
        } 
        catch (IOException e) 
        {
            Msg.error(this, "Failed to read KIP1 header");
        }
    }
    
    public KIP1SectionHeader getSectionHeader(NXOSectionType type)
    {
        return this.sectionHeaders[type.ordinal()];
    }
    
    public long getSectionFileOffset(NXOSectionType type)
    {
        if (type == NXOSectionType.TEXT)
            return 0x100;
        else
        {
            NXOSectionType prevType = NXOSectionType.values()[type.ordinal() - 1];
            KIP1SectionHeader prevHeader = this.getSectionHeader(prevType);
            return this.getSectionFileOffset(prevType) + prevHeader.getCompressedSize();
        }
    }
    
    public long getCompressedSectionSize(NXOSectionType type)
    {
        return this.sectionHeaders[type.ordinal()].getCompressedSize();
    }
    
    public boolean isSectionCompressed(NXOSectionType type)
    {
        int index = type.ordinal();
        
        if (index > 2)
            return false;
        
        int flagMask = 1 << index;
        return (this.flags & flagMask) > 0;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/kip1/KIP1SectionHeader.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */

package adubbz.nx.loader.kip1;

import java.io.IOException;

import ghidra.app.util.bin.BinaryReader;
import ghidra.util.Msg;

public class KIP1SectionHeader 
{
    private long outOffset;
    private long decompressedSize;
    private long compressedSize;
    private long attributes;
    
    public KIP1SectionHeader(BinaryReader reader)
    {
        this.readHeader(reader);
    }
    
    private void readHeader(BinaryReader reader)
    {
        try
        {
            this.outOffset = reader.readNextUnsignedInt();
            this.decompressedSize = reader.readNextUnsignedInt();
            this.compressedSize = reader.readNextUnsignedInt();
            this.attributes = reader.readNextUnsignedInt();
        } 
        catch (IOException e) 
        {
            Msg.error(this, "Failed to read KIP1 section header");
        }
    }
    
    public long getOutOffset()
    {
        return this.outOffset;
    }
    
    public long getDecompressedSize()
    {
        return this.decompressedSize;
    }
    
    /* 
     * NOTE: This will be the same as the decompressed size when
     * no compression is applied.
     */
    public long getCompressedSize()
    {
        return this.compressedSize;
    }
    
    public long getAttributes()
    {
        return this.attributes;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/knx/KNXAdapter.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.knx;

import java.io.IOException;

import adubbz.nx.loader.nxo.MOD0Adapter;
import adubbz.nx.loader.nxo.NXOSection;
import adubbz.nx.loader.nxo.NXOSectionType;
import ghidra.app.util.bin.ByteArrayProvider;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.elf.ElfDynamicTable;
import ghidra.app.util.bin.format.elf.ElfDynamicType;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.exception.NotFoundException;

// We don't have a MOD0, but inherit from the adapter anyway to reduce redundancy
public class KNXAdapter extends MOD0Adapter
{
    protected KNXMapHeader map;
    
    protected ByteProvider memoryProvider;
    protected NXOSection[] sections;
    
    public KNXAdapter(Program program, ByteProvider fileProvider)
    {
        super(program, fileProvider);
        
        try
        {
            this.read();
        }
        catch (IOException e)
        {
            Msg.error(this, "Failed to read KNX");
            e.printStackTrace();
        }
    }
    
    private void read() throws IOException
    {
        Msg.info(this, "Reading...");
        
        this.fileReader.setPointerIndex(0);
        
        while (this.fileReader.getPointerIndex() < 0x2000)
        {
            long candidate = this.fileReader.readNextInt();
            
            if (candidate == 0xD51C403E)
            {
                break;
            }
        }
        
        if (this.fileReader.getPointerIndex() >= 0x2000)
            throw new RuntimeException("Failed to find map offset");
        
        long mapOffset = this.fileReader.getPointerIndex() - 0x34;
        this.map = new KNXMapHeader(this.fileReader, (int)mapOffset);
        
        long textOffset = this.map.getTextFileOffset();
        long rodataOffset = this.map.getRodataFileOffset();
        long dataOffset = this.map.getDataFileOffset();
        long textSize = this.map.getTextSize();
        long rodataSize = this.map.getRodataSize();
        long dataSize = this.map.getDataSize();

        Msg.info(this, String.format("Text size: 0x%X", textSize));
        
        // The data section is last, so we use its offset + decompressed size
        byte[] full = new byte[Math.toIntExact(dataOffset + dataSize)];

        byte[] text = this.fileProvider.readBytes(textOffset, textSize);
        System.arraycopy(text, 0, full, Math.toIntExact(textOffset), Math.toIntExact(textSize));

        byte[] rodata = this.fileProvider.readBytes(rodataOffset, rodataSize);
        System.arraycopy(rodata, 0, full, Math.toIntExact(rodataOffset), Math.toIntExact(rodataSize));

        byte[] data = this.fileProvider.readBytes(dataOffset, dataSize);
        System.arraycopy(data, 0, full, Math.toIntExact(dataOffset), Math.toIntExact(dataSize));
        this.memoryProvider = new ByteArrayProvider(full);
        
        this.sections = new NXOSection[3];
        this.sections[NXOSectionType.TEXT.ordinal()] = new NXOSection(NXOSectionType.TEXT, textOffset, textSize);
        this.sections[NXOSectionType.RODATA.ordinal()] = new NXOSection(NXOSectionType.RODATA, rodataOffset, rodataSize);
        this.sections[NXOSectionType.DATA.ordinal()] = new NXOSection(NXOSectionType.DATA, dataOffset, dataSize);
    }

    @Override
    public ByteProvider getMemoryProvider() 
    {
        return this.memoryProvider;
    }

    @Override
    public NXOSection[] getSections() 
    {
        return this.sections;
    }

    @Override
    public long getDynamicOffset() 
    {
        return this.map.getDynamicOffset();
    }

    @Override
    public long getBssOffset()
    {
        return this.map.getBssFileOffset();
    }
    
    @Override
    public long getBssSize()
    {
        return this.map.getBssSize();
    }
    
    @Override
    public long getGotOffset()
    {
        ElfDynamicTable dt = this.getDynamicTable(this.program);
        
        if (dt == null)
            return 0;
        
        return dt.getAddressOffset() + dt.getLength();
    }
    
    @Override
    public long getGotSize()
    {
        ElfDynamicTable dt = this.getDynamicTable(this.program);
        
        if (dt == null || !dt.containsDynamicValue(ElfDynamicType.DT_INIT_ARRAY))
            return 0;
        
        try 
        {
            return this.program.getImageBase().getOffset() + dt.getDynamicValue(ElfDynamicType.DT_INIT_ARRAY) - this.getGotOffset();
        } 
        catch (NotFoundException e) 
        {
            return 0;
        }
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/knx/KNXMapHeader.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.knx;

import java.io.IOException;

import ghidra.app.util.bin.BinaryReader;
import ghidra.util.Msg;

public class KNXMapHeader 
{
    private long textOffset;
    private long textEndOffset;
    private long rodataOffset;
    private long rodataEndOffset;
    private long dataOffset;
    private long dataEndOffset;
    private long bssOffset;
    private long bssEndOffset;
    private long ini1Offset;
    private long dynamicOffset;
    private long initArrayOffset;
    private long initArrayEndOffset;
    
    public KNXMapHeader(BinaryReader reader, int readerOffset)
    {
        long prevPointerIndex = reader.getPointerIndex();
        
        reader.setPointerIndex(readerOffset);
        this.readHeader(reader);
        
        // Restore the previous pointer index
        reader.setPointerIndex(prevPointerIndex);
    }

    private void readHeader(BinaryReader reader)
    {
        try 
        {
            this.textOffset = reader.readNextUnsignedInt();
            this.textEndOffset = reader.readNextUnsignedInt();
            this.rodataOffset = reader.readNextUnsignedInt();
            this.rodataEndOffset = reader.readNextUnsignedInt();
            this.dataOffset = reader.readNextUnsignedInt();
            this.dataEndOffset = reader.readNextUnsignedInt();
            this.bssOffset = reader.readNextUnsignedInt();
            this.bssEndOffset = reader.readNextUnsignedInt();
            this.ini1Offset = reader.readNextUnsignedInt();
            this.dynamicOffset = reader.readNextUnsignedInt();
            this.initArrayOffset = reader.readNextUnsignedInt();
            this.initArrayEndOffset = reader.readNextUnsignedInt();
        } 
        catch (IOException e) 
        {
            Msg.error(this, "Failed to read KNX Map header");
        }
    }
    
    public long getTextFileOffset()
    {
        return this.textOffset;
    }
    
    public long getTextSize()
    {
        return this.textEndOffset - this.textOffset;
    }
    
    public long getRodataFileOffset()
    {
        return this.rodataOffset;
    }
    
    public long getRodataSize()
    {
        return this.rodataEndOffset - this.rodataOffset;
    }
    
    public long getDataFileOffset()
    {
        return this.dataOffset;
    }
    
    public long getDataSize()
    {
        return this.dataEndOffset - this.dataOffset;
    }
    
    public long getBssFileOffset()
    {
        return this.bssOffset;
    }
    
    public long getBssSize()
    {
        return this.bssEndOffset - this.bssOffset;
    }
    
    public long getDynamicOffset()
    {
        return this.dynamicOffset;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/nro0/NRO0Adapter.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.nro0;

import java.io.IOException;

import adubbz.nx.loader.nxo.MOD0Adapter;
import adubbz.nx.loader.nxo.NXOSection;
import adubbz.nx.loader.nxo.NXOSectionType;
import ghidra.app.util.bin.ByteArrayProvider;
import ghidra.app.util.bin.ByteProvider;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;

public class NRO0Adapter extends MOD0Adapter
{
    protected NRO0Header nro0;
    
    protected ByteProvider memoryProvider;
    protected NXOSection[] sections;
    
    public NRO0Adapter(Program program, ByteProvider fileProvider)
    {
        super(program, fileProvider);
        
        try
        {
            this.read();
        }
        catch (IOException e)
        {
            Msg.error(this, "Failed to read NRO0");
            e.printStackTrace();
        }
    }
    
    private void read() throws IOException
    {
        this.nro0 = new NRO0Header(this.fileReader, 0x0);
        
        NRO0SectionHeader textHeader = this.nro0.getSectionHeader(NXOSectionType.TEXT);
        NRO0SectionHeader rodataHeader = this.nro0.getSectionHeader(NXOSectionType.RODATA);
        NRO0SectionHeader dataHeader = this.nro0.getSectionHeader(NXOSectionType.DATA);

        long textOffset = textHeader.getFileOffset();
        long rodataOffset = rodataHeader.getFileOffset();
        long dataOffset = dataHeader.getFileOffset();
        long textSize = textHeader.getSize();
        long rodataSize = rodataHeader.getSize();
        long dataSize = dataHeader.getSize();

        // The data section is last, so we use its offset + decompressed size
        byte[] full = new byte[Math.toIntExact(dataOffset + dataSize)];

        byte[] text = this.fileProvider.readBytes(textHeader.getFileOffset(), textSize);
        System.arraycopy(text, 0, full, Math.toIntExact(textOffset), Math.toIntExact(textSize));

        byte[] rodata = this.fileProvider.readBytes(rodataHeader.getFileOffset(), rodataSize);
        System.arraycopy(rodata, 0, full, Math.toIntExact(rodataOffset), Math.toIntExact(rodataSize));

        byte[] data = this.fileProvider.readBytes(dataHeader.getFileOffset(), dataSize);
        System.arraycopy(data, 0, full, Math.toIntExact(dataOffset), Math.toIntExact(dataSize));
        this.memoryProvider = new ByteArrayProvider(full);
        
        this.sections = new NXOSection[3];
        this.sections[NXOSectionType.TEXT.ordinal()] = new NXOSection(NXOSectionType.TEXT, textOffset, textSize);
        this.sections[NXOSectionType.RODATA.ordinal()] = new NXOSection(NXOSectionType.RODATA, rodataOffset, rodataSize);
        this.sections[NXOSectionType.DATA.ordinal()] = new NXOSection(NXOSectionType.DATA, dataOffset, dataSize);
    }

    @Override
    public ByteProvider getMemoryProvider() 
    {
        return this.memoryProvider;
    }

    @Override
    public NXOSection[] getSections() 
    {
        return this.sections;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/nro0/NRO0Header.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.nro0;

import java.io.IOException;

import adubbz.nx.common.InvalidMagicException;
import adubbz.nx.loader.nxo.NXOSectionType;
import ghidra.app.util.bin.BinaryReader;
import ghidra.util.Msg;

public class NRO0Header 
{
    private long mod0Offset;
    private String magic;
    private long version;
    private long size;
    private long flags;
    private NRO0SectionHeader textHeader;
    private NRO0SectionHeader rodataHeader;
    private NRO0SectionHeader dataHeader;
    private long bssSize;
    private byte[] buildId;
    private NRO0SectionHeader apiInfo;
    private NRO0SectionHeader dynstr;
    private NRO0SectionHeader dynsym;

    public NRO0Header(BinaryReader reader, int readerOffset)
    {
        long prevPointerIndex = reader.getPointerIndex();
        
        reader.setPointerIndex(readerOffset);
        this.readHeader(reader);
        
        // Restore the previous pointer index
        reader.setPointerIndex(prevPointerIndex);
    }

    private void readHeader(BinaryReader reader)
    {
        try 
        {
            reader.readNextUnsignedInt(); // Reserved
            this.mod0Offset = reader.readNextUnsignedInt();
            reader.readNextLong(); // Padding
            this.magic = reader.readNextAsciiString(4);
            
            if (!this.magic.equals("NRO0"))
                throw new InvalidMagicException("NRO0");
            
            this.version = reader.readNextUnsignedInt();
            this.size = reader.readNextUnsignedInt();
            this.flags = reader.readNextUnsignedInt();
            this.textHeader = new NRO0SectionHeader(reader);
            this.rodataHeader = new NRO0SectionHeader(reader);
            this.dataHeader = new NRO0SectionHeader(reader);
            this.bssSize = reader.readNextUnsignedInt();
            reader.readNextUnsignedInt(); // Reserved
            this.buildId = reader.readNextByteArray(0x20);
            reader.readNextUnsignedInt(); // Reserved
            this.apiInfo = new NRO0SectionHeader(reader);
            this.dynstr = new NRO0SectionHeader(reader);
            this.dynsym = new NRO0SectionHeader(reader);
        } 
        catch (IOException e) 
        {
            Msg.error(this, "Failed to read NRO0 header");
        }
    }

    public NRO0SectionHeader getSectionHeader(NXOSectionType type)
    {
        return switch (type) {
            case TEXT -> this.textHeader;
            case RODATA -> this.rodataHeader;
            case DATA -> this.dataHeader;
            default -> null;
        };
    }

    public long getBssSize()
    {
        return this.bssSize;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/nro0/NRO0SectionHeader.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.nro0;

import java.io.IOException;

import ghidra.app.util.bin.BinaryReader;
import ghidra.util.Msg;

public class NRO0SectionHeader 
{
    private long fileOffset;
    private long size;

    public NRO0SectionHeader(BinaryReader reader)
    {
        this.readHeader(reader);
    }

    private void readHeader(BinaryReader reader)
    {
        try
        {
            this.fileOffset = reader.readNextUnsignedInt();
            this.size = reader.readNextUnsignedInt();
        } 
        catch (IOException e) 
        {
            Msg.error(this, "Failed to read NRO0 section header");
        }
    }

    public long getFileOffset()
    {
        return this.fileOffset;
    }

    public long getSize()
    {
        return this.size;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/nso0/NSO0Adapter.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.nso0;

import java.io.IOException;

import adubbz.nx.loader.nxo.MOD0Adapter;
import adubbz.nx.loader.nxo.NXOSection;
import adubbz.nx.loader.nxo.NXOSectionType;
import ghidra.app.util.bin.ByteArrayProvider;
import ghidra.app.util.bin.ByteProvider;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FastDecompressor;

public class NSO0Adapter extends MOD0Adapter
{
    protected NSO0Header nso0;
    
    protected ByteProvider memoryProvider;
    protected NXOSection[] sections;
    
    public NSO0Adapter(Program program, ByteProvider fileProvider)
    {
        super(program, fileProvider);
        
        try
        {
            this.read();
        }
        catch (IOException e)
        {
            Msg.error(this, "Failed to read NSO0");
            e.printStackTrace();
        }
    }
    
    private void read() throws IOException
    {
        this.nso0 = new NSO0Header(this.fileReader, 0x0);
        
        LZ4Factory factory = LZ4Factory.fastestInstance();
        LZ4FastDecompressor decompressor = factory.fastDecompressor();
        
        NSO0SectionHeader textHeader = this.nso0.getSectionHeader(NXOSectionType.TEXT);
        NSO0SectionHeader rodataHeader = this.nso0.getSectionHeader(NXOSectionType.RODATA);
        NSO0SectionHeader dataHeader = this.nso0.getSectionHeader(NXOSectionType.DATA);

        long textOffset = textHeader.getMemoryOffset();
        long rodataOffset = rodataHeader.getMemoryOffset();
        long dataOffset = dataHeader.getMemoryOffset();
        long textSize = textHeader.getDecompressedSize();
        long rodataSize = rodataHeader.getDecompressedSize();
        long dataSize = dataHeader.getDecompressedSize();
        
        // The data section is last, so we use its offset + decompressed size
        byte[] full = new byte[Math.toIntExact(dataOffset + dataSize)];
        byte[] decompressedText;
        byte[] decompressedRodata;
        byte[] decompressedData;
        
        if (this.nso0.isSectionCompressed(NXOSectionType.TEXT))
        {
            byte[] compressedText = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.TEXT), this.nso0.getCompressedSectionSize(NXOSectionType.TEXT));
            decompressedText = new byte[Math.toIntExact(textSize)];
            decompressor.decompress(compressedText, decompressedText);
        }
        else
        {
            decompressedText = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.TEXT), textSize);
        }
        
        System.arraycopy(decompressedText, 0, full, Math.toIntExact(textOffset), Math.toIntExact(textSize));
        
        if (this.nso0.isSectionCompressed(NXOSectionType.RODATA))
        {
            byte[] compressedRodata = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.RODATA), this.nso0.getCompressedSectionSize(NXOSectionType.RODATA));
            decompressedRodata = new byte[Math.toIntExact(rodataSize)];
            decompressor.decompress(compressedRodata, decompressedRodata);
        }
        else
        {
            decompressedRodata = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.RODATA), rodataSize);
        }
        
        System.arraycopy(decompressedRodata, 0, full, Math.toIntExact(rodataOffset), Math.toIntExact(rodataSize));
        
        if (this.nso0.isSectionCompressed(NXOSectionType.DATA))
        {
            byte[] compressedData = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.DATA), this.nso0.getCompressedSectionSize(NXOSectionType.DATA));
            decompressedData = new byte[Math.toIntExact(dataSize)];
            decompressor.decompress(compressedData, decompressedData);
        }
        else
        {
            decompressedData = this.fileProvider.readBytes(this.nso0.getSectionFileOffset(NXOSectionType.DATA), dataSize);
        }
        
        System.arraycopy(decompressedData, 0, full, Math.toIntExact(dataOffset), Math.toIntExact(dataSize));
        this.memoryProvider = new ByteArrayProvider(full);
        
        this.sections = new NXOSection[3];
        this.sections[NXOSectionType.TEXT.ordinal()] = new NXOSection(NXOSectionType.TEXT, textOffset, textSize);
        this.sections[NXOSectionType.RODATA.ordinal()] = new NXOSection(NXOSectionType.RODATA, rodataOffset, rodataSize);
        this.sections[NXOSectionType.DATA.ordinal()] = new NXOSection(NXOSectionType.DATA, dataOffset, dataSize);
    }

    @Override
    public ByteProvider getMemoryProvider() 
    {
        return this.memoryProvider;
    }

    @Override
    public NXOSection[] getSections() 
    {
        return this.sections;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/nso0/NSO0Header.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.nso0;

import java.io.IOException;

import adubbz.nx.common.InvalidMagicException;
import adubbz.nx.loader.nxo.NXOSectionType;
import ghidra.app.util.bin.BinaryReader;
import ghidra.util.Msg;

public class NSO0Header 
{
    private String magic;
    private long version;
    private long flags;
    private NSO0SectionHeader textHeader;
    private long moduleOffset;
    private NSO0SectionHeader rodataHeader;
    private long moduleFileSize;
    private NSO0SectionHeader dataHeader;
    private long bssSize;
    private byte[] buildId;
    private long compressedTextSize;
    private long compressedRodataSize;
    private long compressedDataSize;
    
    public NSO0Header(BinaryReader reader, int readerOffset)
    {
        long prevPointerIndex = reader.getPointerIndex();
        
        reader.setPointerIndex(readerOffset);
        this.readHeader(reader);
        
        // Restore the previous pointer index
        reader.setPointerIndex(prevPointerIndex);
    }
    
    private void readHeader(BinaryReader reader)
    {
        try 
        {
            this.magic = reader.readNextAsciiString(4);
            
            if (!this.magic.equals("NSO0"))
                throw new InvalidMagicException("NSO0");
            
            this.version = reader.readNextUnsignedInt();
            reader.readNextUnsignedInt(); // Reserved
            this.flags = reader.readNextUnsignedInt();
            this.textHeader = new NSO0SectionHeader(reader);
            this.moduleOffset = reader.readNextUnsignedInt();
            this.rodataHeader = new NSO0SectionHeader(reader);
            this.moduleFileSize = reader.readNextUnsignedInt();
            this.dataHeader = new NSO0SectionHeader(reader);
            this.bssSize = reader.readNextUnsignedInt();
            this.buildId = reader.readNextByteArray(0x20);
            this.compressedTextSize = reader.readNextUnsignedInt();
            this.compressedRodataSize = reader.readNextUnsignedInt();
            this.compressedDataSize = reader.readNextUnsignedInt();
        } 
        catch (IOException e) 
        {
            Msg.error(this, "Failed to read NSO0 header");
        }
    }
    
    public NSO0SectionHeader getSectionHeader(NXOSectionType type)
    {
        return switch (type) {
            case TEXT -> this.textHeader;
            case RODATA -> this.rodataHeader;
            case DATA -> this.dataHeader;
            default -> null;
        };
    }
    
    public long getSectionFileOffset(NXOSectionType type)
    {
        return switch (type) {
            case TEXT -> this.textHeader.getFileOffset();
            case RODATA -> this.rodataHeader.getFileOffset();
            case DATA -> this.dataHeader.getFileOffset();
            default -> 0;
        };
    }
    
    public long getCompressedSectionSize(NXOSectionType type)
    {
        return switch (type) {
            case TEXT -> this.compressedTextSize;
            case RODATA -> this.compressedRodataSize;
            case DATA -> this.compressedDataSize;
            case BSS -> this.bssSize;
        };
    }
    
    public boolean isSectionCompressed(NXOSectionType type)
    {
        int index = type.ordinal();
        
        if (index > 2)
            return false;
        
        int flagMask = 1 << index;
        return (this.flags & flagMask) > 0;
    }
    
    public long getBssSize()
    {
        return this.bssSize;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/nso0/NSO0SectionHeader.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.nso0;

import java.io.IOException;

import ghidra.app.util.bin.BinaryReader;
import ghidra.util.Msg;

public class NSO0SectionHeader 
{
    private long fileOffset;
    private long memoryOffset;
    private long decompressedSize;
    
    public NSO0SectionHeader(BinaryReader reader)
    {
        this.readHeader(reader);
    }
    
    private void readHeader(BinaryReader reader)
    {
        try
        {
            this.fileOffset = reader.readNextUnsignedInt();
            this.memoryOffset = reader.readNextUnsignedInt();
            this.decompressedSize = reader.readNextUnsignedInt();
        } 
        catch (IOException e) 
        {
            Msg.error(this, "Failed to read NSO0 section header");
        }
    }
    
    public long getFileOffset()
    {
        return this.fileOffset;
    }
    
    public long getMemoryOffset()
    {
        return this.memoryOffset;
    }
    
    public long getDecompressedSize()
    {
        return this.decompressedSize;
    }
}


================================================
FILE: src/main/java/adubbz/nx/loader/nxo/MOD0Adapter.java
================================================
/**
 * Copyright 2019 Adubbz
 * 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.
 *
 * 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.
 */
package adubbz.nx.loader.nxo;

import adubbz.nx.common.ElfCompatibilityProvider;
import adubbz.nx.common.InvalidMagicException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.elf.ElfDynamic;
import ghidra.app.util.bin.format.elf.ElfDynamicType;
import ghidra.app.util.bin.format.elf.ElfException;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.Msg;
import ghidra.util.exception.NotFoundException;

import java.io.IOException;
import java.util.List;

/*
 * An adapter implementation for binaries with a MOD0 section.
 */
public abstract class MOD0Adapter extends NXOAdapter
{
    protected Program program;
    protected MOD0Header mod0;
    
    public MOD0Adapter(Program program, ByteProvider fileProvider)
    {
        super(fileProvider);
        this.program = program;
    }
    
    @Override
    public long getDynamicOffset()
    {
        MOD0Header mod0 = this.getMOD0();
        
        if (mod0 == null)
            return 0;
        
        return mod0.getDynamicOffset();
    }
    
    @Override
    public long getDynamicSize()
    {
        assert this.program != null;
        
        if (this.getElfProvider(this.program).getDynamicTable() != null)
            return this.getElfProvider(this.program).getDynamicTable().getLength();
        
        long dtSize = 0;
        var reader = new BinaryReader(this.getMemoryProvider(), true);
        reader.setPointerIndex(this.getDynamicOffset());
        
        try
        {
            while (true) 
            {
                ElfDynamic dyn = new ElfDynamic(reader, new ElfCompatibilityProvider.DummyElfHeader(this.isAarch32()));
                dtSize += dyn.sizeof();
                if (dyn.getTag() == ElfDynamicType.DT_NULL.value) 
                {
                    break;
                }
            }
        }
        catch (IOException e)
        {
            Msg.error(this, "Failed to get dynamic size", e);
        } catch (ElfException e) {
            Msg.error(this, "Can't construct DummyElfHeader", e);
        }

        return dtSize;
    }
    
    @Override
    public long getBssOffset()
    {
        MOD0Header mod0 = this.getMOD0();
        
        if (mod0 == null)
            return 0;
        
        return mod0.getBssStartOffset();
    }
    
    @Override
    public long getBssSize()
    {
        MOD0Header mod0 = this.getMOD0();
        
        if (mod0 == null)
            return 0;
        
        return mod0.getBssSize();
    }

    private long gotOffset = 0;
    private long gotSize = 0;

    private boolean findGot() {
        assert this.program != null;

        if (this.gotOffset > 0 && this.gotSize > 0) {
            return true;
        }

        MOD0Header mod0 = this.getMOD0();

        if (mod0 == null) {
            return false;
        }

        if (mod0.hasLibnxExtension()) {
            this.gotOffset = mod0.getLibnxGotStart() + this.program.getImageBase().getOffset();
            this.gotSize = mod0.getLibnxGotEnd() - mod0.getLibnxGotStart();
            return true;
        }

        boolean good = false;
        List<Long> relocationOffsets = this.getRelocations(program).stream().map(reloc -> reloc.offset).toList();
        MemoryBlock gotPlt = this.program.getMemory().getBlock(".got.plt");
        long gotStart = gotPlt != null ? gotPlt.getEnd().getOffset() + 1 - this.program.getImageBase().getOffset() : this.getDynamicOffset() + this.getDynamicSize();
        long gotEnd = gotStart + this.getOffsetSize();
        long initArrayValue;

        try {
            initArrayValue = this.getDynamicTable(program).getDynamicValue(ElfDynamicType.DT_INIT_ARRAY);
        } catch (NotFoundException ignored) {
            initArrayValue = -1;
        }

        while ((relocationOffsets.contains(gotEnd) || (gotPlt == null && initArrayValue != -1 && gotEnd < initArrayValue))
                && (initArrayValue == -1 || gotEnd < initArrayValue || gotStart > initArrayValue)) {
            good = true;
            gotEnd += this.getOffsetSize();
        }

        if (good) {
            this.gotOffset = this.program.getImageBase().getOffset() + gotStart;
            this.gotSize = gotEnd - gotStart;
            return true;
        }

        Msg.error(this, "Failed to find .got section.");
        return false;
    }
    
    @Override
    public long getGotOffset()
    {
        if (this.findGot()) {
            return this.gotOffset;
        }

        return 0;
    }
    
    @Override
    public long getGotSize()
    {
        if (this.findGot()) {
            return this.gotSize;
        }

        return 0;
    }
    
    public MOD0Header getMOD0()
    {
        if (this.mod0 != null)
            return this.mod0;
        
        try 
        {
            long mod0Offset = this.getMemoryReader().readUnsignedInt(this.getSection(NXOSectionType.TEXT).getOffset() + 4);
        
            if (mod0Offset >= this.getMemoryProvider().length())
                throw new IllegalArgumentException("Mod0 offset is outside the binary!");
            
            this.mod0 = new MOD0Header(this.getMemoryReader(), mod0Offset, mod0Offset);
            return this.mod0;
        }
        catch (InvalidMagicException e)
        {
            Msg.error(this, "Invalid MOD0 magic.", e);
        }
        catch (IOException e) 
        {
     
Download .txt
gitextract_z2gawarw/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── dep-updates-am.yml
│       └── publish.yml
├── .gitignore
├── LICENSE.txt
├── Module.manifest
├── README.md
├── build.gradle
├── extension.properties
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src/
    └── main/
        └── java/
            └── adubbz/
                └── nx/
                    ├── analyzer/
                    │   ├── IPCAnalyzer.java
                    │   └── ipc/
                    │       ├── IPCEmulator.java
                    │       └── IPCTrace.java
                    ├── common/
                    │   ├── ElfCompatibilityProvider.java
                    │   ├── InvalidMagicException.java
                    │   └── NXRelocation.java
                    ├── loader/
                    │   ├── SwitchLoader.java
                    │   ├── common/
                    │   │   ├── MemoryBlockHelper.java
                    │   │   └── NXProgramBuilder.java
                    │   ├── kip1/
                    │   │   ├── KIP1Adapter.java
                    │   │   ├── KIP1Header.java
                    │   │   └── KIP1SectionHeader.java
                    │   ├── knx/
                    │   │   ├── KNXAdapter.java
                    │   │   └── KNXMapHeader.java
                    │   ├── nro0/
                    │   │   ├── NRO0Adapter.java
                    │   │   ├── NRO0Header.java
                    │   │   └── NRO0SectionHeader.java
                    │   ├── nso0/
                    │   │   ├── NSO0Adapter.java
                    │   │   ├── NSO0Header.java
                    │   │   └── NSO0SectionHeader.java
                    │   └── nxo/
                    │       ├── MOD0Adapter.java
                    │       ├── MOD0Header.java
                    │       ├── NXO.java
                    │       ├── NXOAdapter.java
                    │       ├── NXOSection.java
                    │       └── NXOSectionType.java
                    └── util/
                        ├── ByteUtil.java
                        ├── FullMemoryByteProvider.java
                        ├── LegacyBinaryReader.java
                        ├── LegacyByteProviderWrapper.java
                        └── UIUtil.java
Download .txt
SYMBOL INDEX (262 symbols across 31 files)

FILE: src/main/java/adubbz/nx/analyzer/IPCAnalyzer.java
  class IPCAnalyzer (line 53) | public class IPCAnalyzer extends AbstractAnalyzer
    method IPCAnalyzer (line 55) | public IPCAnalyzer()
    method getDefaultEnablement (line 62) | @Override
    method canAnalyze (line 68) | @Override
    method registerOptions (line 74) | @Override
    method added (line 80) | @Override
    method locateIpcVtables (line 112) | private List<Address> locateIpcVtables(Program program, ElfCompatibili...
    method createVTableEntries (line 223) | protected List<IPCVTableEntry> createVTableEntries(Program program, El...
    method locateSTables (line 313) | protected HashBiMap<Address, Address> locateSTables(Program program, E...
    method emulateProcessFunctions (line 395) | protected Multimap<Address, IPCTrace> emulateProcessFunctions(Program ...
    method matchVtables (line 449) | protected HashBiMap<Address, IPCVTableEntry> matchVtables(List<IPCVTab...
    method markupIpc (line 518) | protected void markupIpc(Program program, TaskMonitor monitor, List<IP...
    method getProcFuncVTableSize (line 657) | protected int getProcFuncVTableSize(Multimap<Address, IPCTrace> proces...
    method getGotDataSyms (line 681) | protected Map<Address, Address> getGotDataSyms(Program program, ElfCom...
    method demangleIpcSymbol (line 719) | public static String demangleIpcSymbol(String mangled)
    method shortenIpcSymbol (line 768) | public static String shortenIpcSymbol(String longSym)
    method hasImportedSymbol (line 798) | public boolean hasImportedSymbol(Program program, Address addr)
    method createPointer (line 809) | protected int createPointer(Program program, Address address)
    class IPCVTableEntry (line 828) | public static class IPCVTableEntry
      method IPCVTableEntry (line 835) | private IPCVTableEntry(String fullName, String abvName, Address addr...

FILE: src/main/java/adubbz/nx/analyzer/ipc/IPCEmulator.java
  class IPCEmulator (line 42) | public class IPCEmulator
    method IPCEmulator (line 74) | public IPCEmulator(Program program)
    method setup (line 89) | public void setup() throws MemoryAccessException
    method emulateCommand (line 224) | public IPCTrace emulateCommand(Address procFuncAddr, int cmd)
    method emulateCommand (line 276) | public IPCTrace emulateCommand(Address procFuncAddr, int cmd, byte[] d...
    method allocate (line 353) | private long allocate(long size)
    method calloc (line 367) | private long calloc(long size)
    method createFunctionPointer (line 375) | private long createFunctionPointer(Supplier<Boolean> func)
    method setLong (line 407) | private void setLong(long off, long val)
    method setInt (line 412) | private void setInt(long off, long val)
    method printMemory (line 417) | private void printMemory(long off, long size)
    method returnFromFunc (line 429) | private void returnFromFunc(long value)
    method targetFunction (line 441) | private boolean targetFunction(long offset)
    method PrepareForProcess (line 448) | private boolean PrepareForProcess()
    method OverwriteClientProcessId (line 539) | private boolean OverwriteClientProcessId()
    method GetBuffers (line 547) | private boolean GetBuffers()
    method GetInNativeHandles (line 561) | private boolean GetInNativeHandles()
    method GetInObjects (line 567) | private boolean GetInObjects()
    method BeginPreparingForReply (line 580) | private boolean BeginPreparingForReply()
    method SetBuffers (line 589) | private boolean SetBuffers()
    method SetOutObjects (line 595) | private boolean SetOutObjects()
    method SetOutNativeHandles (line 601) | private boolean SetOutNativeHandles()
    method BeginPreparingForErrorReply (line 607) | private boolean BeginPreparingForErrorReply()
    method EndPreparingForReply (line 613) | private boolean EndPreparingForReply()

FILE: src/main/java/adubbz/nx/analyzer/ipc/IPCTrace.java
  class IPCTrace (line 11) | public class IPCTrace
    method IPCTrace (line 27) | public IPCTrace(int cmdId, long procFuncAddr)
    method hasDescription (line 33) | public boolean hasDescription()
    method isCorrect (line 39) | public boolean isCorrect()
    method printTrace (line 49) | public void printTrace()

FILE: src/main/java/adubbz/nx/common/ElfCompatibilityProvider.java
  class ElfCompatibilityProvider (line 27) | public class ElfCompatibilityProvider
    method ElfCompatibilityProvider (line 46) | public ElfCompatibilityProvider(Program program, ByteProvider provider...
    method ElfCompatibilityProvider (line 59) | public ElfCompatibilityProvider(Program program, boolean isAarch32)
    method getDynamicTable (line 64) | public ElfDynamicTable getDynamicTable()
    method getStringTable (line 85) | public ElfStringTable getStringTable()
    method getDynamicLibraryNames (line 111) | public String[] getDynamicLibraryNames()
    method getSymbolTable (line 144) | public ElfSymbolTable getSymbolTable()
    method getPltRelocations (line 189) | public List<NXRelocation> getPltRelocations()
    method getRelocations (line 220) | public List<NXRelocation> getRelocations()
    method processRelocations (line 274) | private Set<Long> processRelocations(List<NXRelocation> relocs, ElfSym...
    method processReadOnlyRelocations (line 326) | private Set<Long> processReadOnlyRelocations(List<NXRelocation> relocs...
    method getDynamicBlock (line 359) | protected MemoryBlock getDynamicBlock()
    method getReader (line 364) | public BinaryReader getReader()
    class DummyElfHeader (line 370) | public static class DummyElfHeader extends ElfHeader
      method DummyElfHeader (line 375) | public DummyElfHeader(boolean isAarch32) throws ElfException {
      method getDynamicTypeMap (line 390) | @Override
      method getDynamicType (line 396) | @Override
      method adjustAddressForPrelink (line 406) | @Override
      method unadjustAddressForPrelink (line 412) | @Override
      method is32Bit (line 418) | @Override
      method is64Bit (line 424) | @Override

FILE: src/main/java/adubbz/nx/common/InvalidMagicException.java
  class InvalidMagicException (line 9) | public class InvalidMagicException extends RuntimeException
    method InvalidMagicException (line 11) | public InvalidMagicException(String magic)

FILE: src/main/java/adubbz/nx/common/NXRelocation.java
  class NXRelocation (line 11) | public class NXRelocation
    method NXRelocation (line 13) | public NXRelocation(long offset, long r_sym, long r_type, ElfSymbol sy...

FILE: src/main/java/adubbz/nx/loader/SwitchLoader.java
  class SwitchLoader (line 36) | public class SwitchLoader extends BinaryLoader
    method findSupportedLoadSpecs (line 43) | @Override
    method loadProgramInto (line 92) | @Override
    method getTier (line 132) | @Override
    method getTierPriority (line 138) | @Override
    method getName (line 144) | @Override
    type BinaryType (line 150) | private enum BinaryType
      method BinaryType (line 161) | BinaryType(String name, BiFunction<Program, ByteProvider, NXOAdapter...
      method createAdapter (line 167) | public NXOAdapter createAdapter(Program program, ByteProvider provider)

FILE: src/main/java/adubbz/nx/loader/common/MemoryBlockHelper.java
  class MemoryBlockHelper (line 26) | public class MemoryBlockHelper
    method MemoryBlockHelper (line 33) | public MemoryBlockHelper(TaskMonitor monitor, Program program, BytePro...
    method addSection (line 41) | public void addSection(String name, long addressOffset, long offset, l...
    method addUniqueSection (line 56) | private void addUniqueSection(String name, long addressOffset, long of...
    method addFillerSection (line 74) | public void addFillerSection(String name, long addressOffset, long len...
    method flushLog (line 125) | public void flushLog()

FILE: src/main/java/adubbz/nx/loader/common/NXProgramBuilder.java
  class NXProgramBuilder (line 55) | public class NXProgramBuilder
    method NXProgramBuilder (line 68) | public NXProgramBuilder(Program program, ByteProvider provider, NXOAda...
    method load (line 75) | public void load(TaskMonitor monitor) throws CancelledException
    method getNxo (line 162) | public NXO getNxo()
    method setupStringTable (line 167) | protected void setupStringTable() throws AddressOverflowException, Cod...
    method setupSymbolTable (line 187) | protected void setupSymbolTable()
    method setupRelocations (line 211) | protected void setupRelocations(TaskMonitor monitor) throws AddressOut...
    method createGlobalOffsetTable (line 289) | protected void createGlobalOffsetTable() throws AddressOutOfBoundsExce...
    method performRelocations (line 305) | protected void performRelocations() throws MemoryAccessException, Inva...
    method setupImports (line 425) | protected void setupImports(TaskMonitor monitor)
    method createExternalBlock (line 474) | private void createExternalBlock(Address addr, long size)
    method processImports (line 491) | private void processImports(TaskMonitor monitor)
    method createEntryFunction (line 514) | public Address createEntryFunction(String name, long entryAddr, TaskMo...
    method createString (line 546) | protected int createString(Address address) throws CodeUnitInsertionEx...
    method createPointer (line 558) | protected int createPointer(Address address) throws CodeUnitInsertionE...
    method evaluateElfSymbol (line 571) | private void evaluateElfSymbol(ElfSymbol elfSymbol, Address address, b...
    method createOneByteFunction (line 630) | public Function createOneByteFunction(String name, Address address, bo...
    method createSymbol (line 662) | public Symbol createSymbol(Address addr, String name, boolean isPrimar...
    method disassembleRange (line 677) | private void disassembleRange(Address start, Address end, Program prog...
    method checkPrimary (line 698) | private Symbol checkPrimary(Symbol sym)
    method hasImportedSymbol (line 736) | public boolean hasImportedSymbol(Address addr)
    method tryCreateDynBlock (line 747) | protected void tryCreateDynBlock(String name, ElfDynamicType offsetTyp...
    method tryCreateDynBlockWithRange (line 771) | protected void tryCreateDynBlockWithRange(String name, ElfDynamicType ...
    class PltEntry (line 795) | private static class PltEntry
      method PltEntry (line 800) | public PltEntry(long offset, long target)

FILE: src/main/java/adubbz/nx/loader/kip1/KIP1Adapter.java
  class KIP1Adapter (line 20) | public class KIP1Adapter extends MOD0Adapter
    method KIP1Adapter (line 27) | public KIP1Adapter(Program program, ByteProvider fileProvider)
    method read (line 42) | private void read() throws IOException
    method getMemoryProvider (line 106) | @Override
    method getSections (line 112) | @Override

FILE: src/main/java/adubbz/nx/loader/kip1/KIP1Header.java
  class KIP1Header (line 16) | public class KIP1Header
    method KIP1Header (line 28) | public KIP1Header(BinaryReader reader, int readerOffset)
    method readHeader (line 39) | private void readHeader(BinaryReader reader)
    method getSectionHeader (line 67) | public KIP1SectionHeader getSectionHeader(NXOSectionType type)
    method getSectionFileOffset (line 72) | public long getSectionFileOffset(NXOSectionType type)
    method getCompressedSectionSize (line 84) | public long getCompressedSectionSize(NXOSectionType type)
    method isSectionCompressed (line 89) | public boolean isSectionCompressed(NXOSectionType type)

FILE: src/main/java/adubbz/nx/loader/kip1/KIP1SectionHeader.java
  class KIP1SectionHeader (line 15) | public class KIP1SectionHeader
    method KIP1SectionHeader (line 22) | public KIP1SectionHeader(BinaryReader reader)
    method readHeader (line 27) | private void readHeader(BinaryReader reader)
    method getOutOffset (line 42) | public long getOutOffset()
    method getDecompressedSize (line 47) | public long getDecompressedSize()
    method getCompressedSize (line 56) | public long getCompressedSize()
    method getAttributes (line 61) | public long getAttributes()

FILE: src/main/java/adubbz/nx/loader/knx/KNXAdapter.java
  class KNXAdapter (line 23) | public class KNXAdapter extends MOD0Adapter
    method KNXAdapter (line 30) | public KNXAdapter(Program program, ByteProvider fileProvider)
    method read (line 45) | private void read() throws IOException
    method getMemoryProvider (line 95) | @Override
    method getSections (line 101) | @Override
    method getDynamicOffset (line 107) | @Override
    method getBssOffset (line 113) | @Override
    method getBssSize (line 119) | @Override
    method getGotOffset (line 125) | @Override
    method getGotSize (line 136) | @Override

FILE: src/main/java/adubbz/nx/loader/knx/KNXMapHeader.java
  class KNXMapHeader (line 14) | public class KNXMapHeader
    method KNXMapHeader (line 29) | public KNXMapHeader(BinaryReader reader, int readerOffset)
    method readHeader (line 40) | private void readHeader(BinaryReader reader)
    method getTextFileOffset (line 63) | public long getTextFileOffset()
    method getTextSize (line 68) | public long getTextSize()
    method getRodataFileOffset (line 73) | public long getRodataFileOffset()
    method getRodataSize (line 78) | public long getRodataSize()
    method getDataFileOffset (line 83) | public long getDataFileOffset()
    method getDataSize (line 88) | public long getDataSize()
    method getBssFileOffset (line 93) | public long getBssFileOffset()
    method getBssSize (line 98) | public long getBssSize()
    method getDynamicOffset (line 103) | public long getDynamicOffset()

FILE: src/main/java/adubbz/nx/loader/nro0/NRO0Adapter.java
  class NRO0Adapter (line 19) | public class NRO0Adapter extends MOD0Adapter
    method NRO0Adapter (line 26) | public NRO0Adapter(Program program, ByteProvider fileProvider)
    method read (line 41) | private void read() throws IOException
    method getMemoryProvider (line 75) | @Override
    method getSections (line 81) | @Override

FILE: src/main/java/adubbz/nx/loader/nro0/NRO0Header.java
  class NRO0Header (line 16) | public class NRO0Header
    method NRO0Header (line 32) | public NRO0Header(BinaryReader reader, int readerOffset)
    method readHeader (line 43) | private void readHeader(BinaryReader reader)
    method getSectionHeader (line 75) | public NRO0SectionHeader getSectionHeader(NXOSectionType type)
    method getBssSize (line 85) | public long getBssSize()

FILE: src/main/java/adubbz/nx/loader/nro0/NRO0SectionHeader.java
  class NRO0SectionHeader (line 14) | public class NRO0SectionHeader
    method NRO0SectionHeader (line 19) | public NRO0SectionHeader(BinaryReader reader)
    method readHeader (line 24) | private void readHeader(BinaryReader reader)
    method getFileOffset (line 37) | public long getFileOffset()
    method getSize (line 42) | public long getSize()

FILE: src/main/java/adubbz/nx/loader/nso0/NSO0Adapter.java
  class NSO0Adapter (line 21) | public class NSO0Adapter extends MOD0Adapter
    method NSO0Adapter (line 28) | public NSO0Adapter(Program program, ByteProvider fileProvider)
    method read (line 43) | private void read() throws IOException
    method getMemoryProvider (line 113) | @Override
    method getSections (line 119) | @Override

FILE: src/main/java/adubbz/nx/loader/nso0/NSO0Header.java
  class NSO0Header (line 16) | public class NSO0Header
    method NSO0Header (line 32) | public NSO0Header(BinaryReader reader, int readerOffset)
    method readHeader (line 43) | private void readHeader(BinaryReader reader)
    method getSectionHeader (line 72) | public NSO0SectionHeader getSectionHeader(NXOSectionType type)
    method getSectionFileOffset (line 82) | public long getSectionFileOffset(NXOSectionType type)
    method getCompressedSectionSize (line 92) | public long getCompressedSectionSize(NXOSectionType type)
    method isSectionCompressed (line 102) | public boolean isSectionCompressed(NXOSectionType type)
    method getBssSize (line 113) | public long getBssSize()

FILE: src/main/java/adubbz/nx/loader/nso0/NSO0SectionHeader.java
  class NSO0SectionHeader (line 14) | public class NSO0SectionHeader
    method NSO0SectionHeader (line 20) | public NSO0SectionHeader(BinaryReader reader)
    method readHeader (line 25) | private void readHeader(BinaryReader reader)
    method getFileOffset (line 39) | public long getFileOffset()
    method getMemoryOffset (line 44) | public long getMemoryOffset()
    method getDecompressedSize (line 49) | public long getDecompressedSize()

FILE: src/main/java/adubbz/nx/loader/nxo/MOD0Adapter.java
  class MOD0Adapter (line 27) | public abstract class MOD0Adapter extends NXOAdapter
    method MOD0Adapter (line 32) | public MOD0Adapter(Program program, ByteProvider fileProvider)
    method getDynamicOffset (line 38) | @Override
    method getDynamicSize (line 49) | @Override
    method getBssOffset (line 83) | @Override
    method getBssSize (line 94) | @Override
    method findGot (line 108) | private boolean findGot() {
    method getGotOffset (line 156) | @Override
    method getGotSize (line 166) | @Override
    method getMOD0 (line 176) | public MOD0Header getMOD0()

FILE: src/main/java/adubbz/nx/loader/nxo/MOD0Header.java
  class MOD0Header (line 15) | public class MOD0Header
    method MOD0Header (line 30) | public MOD0Header(BinaryReader reader, long readerOffset, long mod0Sta...
    method readHeader (line 41) | private void readHeader(BinaryReader reader, long mod0StartOffset) thr...
    method getDynamicOffset (line 65) | public long getDynamicOffset()
    method getBssStartOffset (line 70) | public long getBssStartOffset()
    method getBssEndOffset (line 75) | public long getBssEndOffset()
    method getBssSize (line 80) | public long getBssSize()
    method getEhFrameHdrStartOffset (line 85) | public long getEhFrameHdrStartOffset()
    method getEhFrameHdrEndOffset (line 90) | public long getEhFrameHdrEndOffset()
    method getRuntimeModuleOffset (line 95) | public long getRuntimeModuleOffset()
    method hasLibnxExtension (line 101) | public boolean hasLibnxExtension()
    method getLibnxGotStart (line 106) | public long getLibnxGotStart()
    method getLibnxGotEnd (line 111) | public long getLibnxGotEnd()

FILE: src/main/java/adubbz/nx/loader/nxo/NXO.java
  class NXO (line 11) | public class NXO
    method NXO (line 16) | public NXO(Program program, NXOAdapter adapter, long baseAddress)
    method getAdapter (line 22) | public NXOAdapter getAdapter()
    method getBaseAddress (line 27) | public long getBaseAddress()

FILE: src/main/java/adubbz/nx/loader/nxo/NXOAdapter.java
  class NXOAdapter (line 27) | public abstract class NXOAdapter
    method NXOAdapter (line 34) | public NXOAdapter(ByteProvider fileProvider)
    method getMemoryProvider (line 40) | public abstract ByteProvider getMemoryProvider();
    method getMemoryReader (line 42) | public BinaryReader getMemoryReader()
    method getDynamicOffset (line 51) | public abstract long getDynamicOffset();
    method getDynamicSize (line 52) | public abstract long getDynamicSize();
    method getBssOffset (line 54) | public abstract long getBssOffset();
    method getBssSize (line 55) | public abstract long getBssSize();
    method getGotOffset (line 57) | public abstract long getGotOffset();
    method getGotSize (line 58) | public abstract long getGotSize();
    method isAarch32 (line 60) | public boolean isAarch32()
    method getOffsetSize (line 76) | public int getOffsetSize()
    method getSection (line 81) | public NXOSection getSection(NXOSectionType type)
    method getSections (line 86) | public abstract NXOSection[] getSections();
    method getDynamicTable (line 88) | public ElfDynamicTable getDynamicTable(Program program)
    method getStringTable (line 93) | public ElfStringTable getStringTable(Program program)
    method getSymbolTable (line 98) | public ElfSymbolTable getSymbolTable(Program program)
    method getDynamicLibraryNames (line 103) | public String[] getDynamicLibraryNames(Program program)
    method getRelocations (line 108) | public ImmutableList<NXRelocation> getRelocations(Program program)
    method getPltRelocations (line 113) | public ImmutableList<NXRelocation> getPltRelocations(Program program)
    method getElfProvider (line 118) | public ElfCompatibilityProvider getElfProvider(Program program)

FILE: src/main/java/adubbz/nx/loader/nxo/NXOSection.java
  class NXOSection (line 9) | public class NXOSection
    method NXOSection (line 14) | public NXOSection(NXOSectionType type, long offset, long size)
    method getOffset (line 20) | public long getOffset()
    method getSize (line 25) | public long getSize()

FILE: src/main/java/adubbz/nx/loader/nxo/NXOSectionType.java
  type NXOSectionType (line 10) | public enum NXOSectionType

FILE: src/main/java/adubbz/nx/util/ByteUtil.java
  class ByteUtil (line 15) | public class ByteUtil
    method kip1BlzDecompress (line 17) | public static byte[] kip1BlzDecompress(byte[] compressed, int decompre...
    method logBytes (line 80) | public static void logBytes(byte[] data)

FILE: src/main/java/adubbz/nx/util/FullMemoryByteProvider.java
  class FullMemoryByteProvider (line 12) | public class FullMemoryByteProvider extends MemoryByteProvider
    method FullMemoryByteProvider (line 14) | public FullMemoryByteProvider(Program program)
    method length (line 19) | @Override

FILE: src/main/java/adubbz/nx/util/LegacyBinaryReader.java
  class LegacyBinaryReader (line 14) | public class LegacyBinaryReader extends BinaryReader
    method LegacyBinaryReader (line 16) | public LegacyBinaryReader(ByteProvider provider, boolean isLittleEndian)
    method readAsciiString (line 22) | @Override

FILE: src/main/java/adubbz/nx/util/LegacyByteProviderWrapper.java
  class LegacyByteProviderWrapper (line 8) | public class LegacyByteProviderWrapper extends ByteProviderWrapper {
    method LegacyByteProviderWrapper (line 10) | public LegacyByteProviderWrapper(ByteProvider provider, long subOffset...
    method isValidIndex (line 14) | @Override
    method readByte (line 19) | @Override
    method readBytes (line 27) | @Override

FILE: src/main/java/adubbz/nx/util/UIUtil.java
  class UIUtil (line 22) | public class UIUtil
    method sortProgramTree (line 29) | public static void sortProgramTree(Program program)
    method sortModule (line 46) | private static void sortModule(ProgramModule parent) throws NotFoundEx...
    class GroupComparator (line 67) | private static class GroupComparator implements Comparator<Group>
      method GroupComparator (line 71) | GroupComparator(int sortType)
      method compare (line 76) | @Override
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (228K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 144,
    "preview": "version: 2\nupdates:\n  - directory: /\n    open-pull-requests-limit: 5\n    package-ecosystem: github-actions\n    schedule:"
  },
  {
    "path": ".github/workflows/dep-updates-am.yml",
    "chars": 455,
    "preview": "name: Dependency updates auto-merge\non: pull_request\n\npermissions:\n  pull-requests: write\n  contents: write\n  checks: re"
  },
  {
    "path": ".github/workflows/publish.yml",
    "chars": 1359,
    "preview": "name: Publish release artifacts\n\non:\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: Release git tag\n  "
  },
  {
    "path": ".gitignore",
    "chars": 31,
    "preview": "eclipse/\n.gradle/\ndist/\nbuild/\n"
  },
  {
    "path": "LICENSE.txt",
    "chars": 720,
    "preview": "Copyright 2019 Adubbz\n\nPermission to use, copy, modify, and/or distribute this software for any purpose with or without "
  },
  {
    "path": "Module.manifest",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "README.md",
    "chars": 937,
    "preview": "# Ghidra Switch Loader\n\nA loader for Ghidra supporting a variety of Nintendo Switch file formats.\n\n## Building\n\n- Ensure"
  },
  {
    "path": "build.gradle",
    "chars": 4217,
    "preview": "// Builds a Ghidra Extension for a given Ghidra installation.\n//\n// An absolute path to the Ghidra installation director"
  },
  {
    "path": "extension.properties",
    "chars": 124,
    "preview": "name=@extname@\ndescription=A loader for Nintendo Switch file formats.\nauthor=Adubbz\ncreatedOn=9/3/2019\nversion=@extversi"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 202,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https://services.gradle.org/distributio"
  },
  {
    "path": "gradle.properties",
    "chars": 14,
    "preview": "version=1.6.0\n"
  },
  {
    "path": "gradlew",
    "chars": 8165,
    "preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "gradlew.bat",
    "chars": 2747,
    "preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "settings.gradle",
    "chars": 33,
    "preview": "rootProject.name = \"SwitchLoader\""
  },
  {
    "path": "src/main/java/adubbz/nx/analyzer/IPCAnalyzer.java",
    "chars": 38875,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/analyzer/ipc/IPCEmulator.java",
    "chars": 23605,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/analyzer/ipc/IPCTrace.java",
    "chars": 2511,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/common/ElfCompatibilityProvider.java",
    "chars": 15526,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/common/InvalidMagicException.java",
    "chars": 946,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/common/NXRelocation.java",
    "chars": 1216,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/SwitchLoader.java",
    "chars": 6371,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/common/MemoryBlockHelper.java",
    "chars": 5433,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/common/NXProgramBuilder.java",
    "chars": 35550,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/kip1/KIP1Adapter.java",
    "chars": 5229,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/kip1/KIP1Header.java",
    "chars": 3503,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/kip1/KIP1SectionHeader.java",
    "chars": 2050,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/knx/KNXAdapter.java",
    "chars": 5283,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/knx/KNXMapHeader.java",
    "chars": 3395,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nro0/NRO0Adapter.java",
    "chars": 3662,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nro0/NRO0Header.java",
    "chars": 3383,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nro0/NRO0SectionHeader.java",
    "chars": 1518,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nso0/NSO0Adapter.java",
    "chars": 5529,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nso0/NSO0Header.java",
    "chars": 4194,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nso0/NSO0SectionHeader.java",
    "chars": 1760,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/MOD0Adapter.java",
    "chars": 6295,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/MOD0Header.java",
    "chars": 3784,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/NXO.java",
    "chars": 1230,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/NXOAdapter.java",
    "chars": 4690,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/NXOSection.java",
    "chars": 1128,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/loader/nxo/NXOSectionType.java",
    "chars": 830,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/util/ByteUtil.java",
    "chars": 4203,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/util/FullMemoryByteProvider.java",
    "chars": 1198,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/util/LegacyBinaryReader.java",
    "chars": 1780,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  },
  {
    "path": "src/main/java/adubbz/nx/util/LegacyByteProviderWrapper.java",
    "chars": 1239,
    "preview": "package adubbz.nx.util;\n\nimport ghidra.app.util.bin.ByteProvider;\nimport ghidra.app.util.bin.ByteProviderWrapper;\n\nimpor"
  },
  {
    "path": "src/main/java/adubbz/nx/util/UIUtil.java",
    "chars": 3824,
    "preview": "/**\n * Copyright 2019 Adubbz\n * Permission to use, copy, modify, and/or distribute this software for any purpose with or"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the Adubbz/Ghidra-Switch-Loader GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (213.8 KB), approximately 51.0k tokens, and a symbol index with 262 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!