#!/usr/bin/env bash case $OSTYPE in darwin*) echo "" | gcut -f1 &>/dev/null || { echo "This script requires GNU coreutils, install it via homebrew (brew install coreutils)" >&2; exit 1; } CUT=gcut ;; *) CUT=cut ;; esac MYDIR=$(dirname "$0") mkdir -p "${MYDIR}"/.bins "${MYDIR}"/.files DEFAULT_BASEROM=baserom.nds DEFAULT_ARM9BUILDDIR=build/heartgold.us DEFAULT_ARM7BUILDDIR=sub/build DEFAULT_FSDIR=files difftype='-u' # getword FILE OFFSET getword() { od -j "$2" -N 4 -A n -t u "$1" | awk '{$1=$1};1' } # dump_autoload SBIN PROC VMA SIZE AUTOLOAD dump_autoload() { outfile="${1}_a${5}" # ARM9 has the start module params in the footer if [ "$proc" == armv5te ]; then _start_ModuleParams=$(($(getword "$1" $(($4-8)))-$3)) else _start_ModuleParams=0 # Have to be a bit clever for ARM7 for i in $(seq 0 4 $4); do if [ $(getword "$1" $i ) == $(($3 + $4)) ]; then _start_ModuleParams=$((i-4)) break fi done fi [[ $_start_ModuleParams == 0 ]] && { echo "Unable to find _start_ModuleParams" >&2; exit 1; } aload_list=$(($(getword "$1" $_start_ModuleParams)-$3)) aload_list_end=$(($(getword "$1" $((_start_ModuleParams+4)))-$3)) aload_start=$(($(getword "$1" $((_start_ModuleParams+8)))-$3)) [[ $aload_list == $aload_list_end ]] && { echo "error: autoload index exceeds list size" >&2; exit 1; } for i in $(seq 1 "$5"); do avma=$(getword "$1" $aload_list) asize=$(getword "$1" $((aload_list+4))) ((aload_start+=asize)) ((aload_list+=12)) [[ $aload_list == $aload_list_end ]] && { echo "error: autoload index exceeds list size" >&2; exit 1; } done avma=$(getword "$1" $aload_list) asize=$(getword "$1" $((aload_list+4))) dd if="$1" of="$outfile" bs=1 skip=$aload_start count=$asize 2>/dev/null echo $outfile $avma } [[ -n "$DEVKITARM" ]] && export PATH=${DEVKITARM}/bin:${PATH} POSITIONAL=() usage () { echo "Diff segments of a Nintendo DS ROM" echo "Usage: $0 [-h] [-7] [-m OVERLAY] [-r BASEROM] [-d BUILDDIR] [START [END]]" echo "" echo "Arguments:" echo " START, END Start and end virtual addresses to diff" echo "" echo "Options:" echo " -S SoulSilver diff. Requires baserom.soulsilver.nds (default: HeartGold)" echo " -7 Diff the ARM7 module (default: ARM9)" echo " -a AUTOLOAD Diff the indicated autoload module (default: static module)" echo " -m OVERLAY Diff the indicated overlay module (default: static module)" echo " -r BASEROM Use the indicated baserom (default: baserom.nds)" echo " -d BUILDDIR Look for compiled binaries in this directory (default: build/heartgold.us)" echo " -f FILE Dump the indicated file, and use hexdump instead" echo " -F FSDIR Filesystem path on the home system" echo " -t Force THUMB instructions (default: ARM)" echo " -h Show this message and exit" echo " -c Clear caches and exit" echo " -y Side-by-side diff" } mktgt=all while [[ $# -gt 0 ]]; do key="$1" case $key in -h|--help) usage exit 0 ;; -c|--clean) mktgt=clean shift ;; -7|--arm7) proc=armv4t builddir=${builddir:-$DEFAULT_ARM7BUILDDIR} shift ;; -a|--autoload) [[ -n $autoload ]] && { echo "can only do one autoload at a time" >&2; exit 1; } mode=autoload autoload="$2" shift 2 ;; -m|--overlay) [[ -n $overlay ]] && { echo "can only do one overlay at a time" >&2; exit 1; } mode=overlay overlay="$2" shift 2 ;; -r|--rom) baserom="$2" shift 2 ;; -t|--thumb) thumb=-Mforce-thumb shift ;; -f|--file) mode="file" filepath="$2" shift 2 ;; -F|--files-dir) fsdir="$2" shift 2 ;; -d|--build-dir) builddir="$2" shift 2 ;; -S|--soulsilver) baserom="${baserom:-baserom.soulsilver.nds}" builddir="${builddir:-build/soulsilver.us}" shift ;; -y) difftype='-y -W160' shift ;; -*) usage echo unrecognized option flag: "$1" exit 1 ;; *) POSITIONAL+=("$1") shift ;; esac done # Build C utils on demand make -C "${MYDIR}" -q --no-print-directory "$mktgt" if [ "$mktgt" = "clean" ]; then rm -rf "${MYDIR}"/.bins "${MYDIR}"/.files exit 0 fi set -- "${POSITIONAL[@]}" mode=${mode:-static} proc=${proc:-armv5te} builddir=${builddir:-$DEFAULT_ARM9BUILDDIR} baserom=${baserom:-$DEFAULT_BASEROM} fsdir=${fsdir:-$DEFAULT_FSDIR} [[ -f "$baserom" ]] || { echo $0: $baserom: no such file or directory >&2; exit 1; } basestem= [[ $proc == armv4t ]] && basestem=${basestem}.sub [[ $mode == overlay ]] && basestem=${basestem}.o${overlay} basefile=${MYDIR}/.bins/${baserom}${basestem}.sbin case "$mode" in overlay) case $proc in armv4t) ovt=88 ;; armv5te) ovt=80 ;; esac if [ -f "${builddir}/component.files" ]; then defsfile=$(${CUT} -d '' -f2 "${builddir}/component.files") else defsfile=$( cd "${builddir}"; ls *.lcf | sed 's/.lcf/_defs.sbin/' ) fi if [ -n "${overlay##*[!0-9]*}" 2>/dev/null ] ; then ovyfile=$(tail -c+16 "${builddir}/${defsfile}" | ${CUT} -d '' -f$((overlay+1)) ) [[ -n $ovyfile ]] || { echo "Overlay num out of range" >&2; exit 1; } else ovyfile=${overlay%.*}.sbin overlay=$(($(tail -c+16 "${builddir}/${defsfile}" | tr '\0' '\n' | grep -n $ovyfile | ${CUT} -d: -f1)-1)) [[ $overlay -eq -1 ]] && { echo "Invalid overlay name" >&2; exit 1; } fi ovtoff=$(getword "$baserom" "$ovt") vma=$(getword "$baserom" "$((ovtoff+32*overlay+4))") size=$(getword "$baserom" "$((ovtoff+32*overlay+8))") [[ $basefile -nt $baserom ]] || { fileid=$(getword "$baserom" "$((ovtoff+32*overlay+24))") param=$(getword "$baserom" "$((ovtoff+32*overlay+28))") fatoff=$(getword "$baserom" 72) fileoff=$(getword "$baserom" "$((fatoff+8*fileid))") filesize=$(($(getword "$baserom" "$((fatoff+8*fileid+4))")-fileoff)) dd if="$baserom" of="$basefile" bs=1 skip="$fileoff" count="$filesize" 2>/dev/null (( param & 16777216 )) && { compsize=$((param & 16777215)) "$MYDIR"/ntruncompbw "$basefile" "$vma" $((vma+compsize)) || { rm -f "$basefile"; exit 1; } } } buildfile=$builddir/$ovyfile ;; static|autoload) case $proc in armv4t) romtab=48 ;; armv5te) romtab=32 ;; esac fileoff=$(getword "$baserom" "$romtab") vma=$(getword "$baserom" "$((romtab+8))") size=$(getword "$baserom" "$((romtab+12))") [[ $basefile -nt "$baserom" ]] || { dd if="$baserom" of="$basefile" bs=1 skip="$fileoff" count="$size" 2>/dev/null [[ $proc == armv5te ]] && { _start_ModuleParams=$(getword "$baserom" $((fileoff+size+4))) compstatend=$(getword "$basefile" $((_start_ModuleParams+20))) [[ $compstatend != "0" ]] && { "$MYDIR"/ntruncompbw "$basefile" "$vma" "$compstatend" || { rm -f "$basefile"; exit 1; } dd if=/dev/zero of="$basefile" bs=1 seek="$((_start_ModuleParams+20))" count=4 conv=notrunc 2>/dev/null } } } if [ -f "${builddir}/component.files" ]; then compname=$(${CUT} -d '' -f1 "${builddir}/component.files") defsfile=$(${CUT} -d '' -f2 "${builddir}/component.files") else compname=$( cd "${builddir}"; ls *.lcf | sed 's/lcf/sbin/' ) defsfile=$( cd "${builddir}"; ls *.lcf | sed 's/.lcf/_defs.sbin/' ) fi buildfile=${builddir}/${compname} if [ "$mode" == "autoload" ]; then resp=$( dump_autoload "$basefile" "$proc" "$vma" "$size" "$autoload" ) basefile=$(echo $resp | cut -d' ' -f1) size=$( wc -c <${buildfile} ) resp=$( dump_autoload "$buildfile" "$proc" "$vma" "$size" "$autoload" ) buildfile=$(echo $resp | cut -d' ' -f1) vma=$(echo $resp | cut -d' ' -f2) fi ;; file) buildfile=${fsdir}/${filepath} [[ -f "${buildfile}" ]] || { echo file not found: "${buildfile}" >&2; exit 1; } basefile=${MYDIR}/.files/${filepath} [[ "${basefile}" -nt "$baserom" ]] || { mkdir -p $(dirname $basefile) "${MYDIR}"/ntrextractfile "${baserom}" "${filepath}" >"${basefile}" } cmp -s "$basefile" "$buildfile" || diff $difftype <(hexdump -Cv "$basefile") <(hexdump -Cv "$buildfile") exit 0 ;; esac [[ -n "$1" ]] && start=$(($1)) || start=$vma [[ -n "$2" ]] && size=$(($2)) || size=$(wc -c <"$basefile") do-objdump () { arm-none-eabi-objdump -Drz -bbinary -m$proc $thumb --adjust-vma="$vma" --start-address="$start" --stop-address=$((start+size)) "$1" } cmp -s "$basefile" "$buildfile" || diff $difftype <(do-objdump "$basefile") <(do-objdump "$buildfile")