DSのROMのオーバーレイたちを展開するスクリプト

背景

DSはメインメモリが4MBしかなく、ゲームの全てのプログラムをメモリ上に載せることができません。そこで必要なときに必要なモジュールをメモリ上にロードしています(たとえば戦闘に入ったら戦闘用のプログラムモジュールをロードするなど)。
この各モジュールのことをオーバーレイと呼びます。

ROMファイルには各オーバーレイに対する
(ファイルID, 展開先のメモリアドレス)
の情報が入っています。

また、ファイルIDに対応する各ファイルのROM内のオフセットとサイズはROMのFAT領域に格納されています。

そこで各オーバーレイをファイルとして取り出し、圧縮を解凍し、逆アセンブルするスクリプトを書きました。

利点

ndsdis2 (http://i486.mods.jp/old/nds/ndshack.html)はメインメモリを全て逆アセンブルする仕組みでした。
これではプログラム以外が格納されているメモリも逆アセンブルするので無駄があります。
またメモリダンプしたそのときに載っているオーバーレイしか逆アセンブルできないという欠点があります。
今回のスクリプトはすべてのプログラムデータを無駄なく逆アセンブルできます。

必要なもの

スクリプト

nitrodis.rb

# NDSファイルの中のoverlayたちを展開して逆アセンブルする

# Usage: ruby nitrodis.rb <filename>.nds


require "fileutils"

OBJDUMP = 'C:/devkitPro/devkitARM/bin/arm-none-eabi-objdump.exe'
SIZEOF_OVR = 32

Fat = Struct.new(:start, :end)
Ovr = Struct.new(:id, :ramaddr, :ramsize, :bsssize, :start, :end, :file_id, :reserved)

def read_u32(f, ofs)
  f.seek ofs
  f.read(4).unpack("V")[0]
end

def read_u32_list(f, ofs, len)
  f.seek ofs
  f.read(4 * len).unpack("V*")
end

def read_cstr(f, ofs, len)
  f.seek ofs
  f.read(len).unpack("Z*")[0]
end

def read(f, ofs, bytes)
  f.seek ofs
  f.read(bytes)
end

def do_file(fname, bin, addr)
  File.binwrite(fname+".orig", bin)
  system "./DSDecmp", "-d", "-f", "lzovl", fname+".orig", fname
  destfname = File.exist?(fname) ? fname : fname + ".orig"
  system OBJDUMP, "-b", "binary", "-m", "arm",
         "-Mforce-thumb", "--adjust-vma=#{addr}",
         "-D", destfname, {1 => destfname + ".dis"}
end

fname = ARGV[0]
open(fname, "rb") do |f|
  romname = read_cstr(f, 0, 12).gsub(" ", "")
  arm9exe_start = read_u32(f, 0x20)
  arm9exe_size = read_u32(f, 0x2c)

  fat_off = read_u32(f, 0x48)
  fat_size = read_u32(f, 0x4c)
  num_files = fat_size / 8
  arm9_overlay_off = read_u32(f, 0x50)
  arm9_overlay_size = read_u32(f, 0x54)
  num_overlay_9 = arm9_overlay_size / SIZEOF_OVR
  fats = []
  ovrs = []
  num_files.times do |i|
    s, e = read_u32_list(f, fat_off + i * 8, 2)
    fats << Fat.new(s, e)
  end
  dirname = "overlay-#{romname}"
  #FileUtils.rm_r dirname
  FileUtils.mkdir_p dirname

  bin = read(f, arm9exe_start, arm9exe_size)
  do_file "#{dirname}/arm9exe", bin, 0x02000000

  num_overlay_9.times do |i|
    ovr = Ovr.new(*read_u32_list(f, arm9_overlay_off + i * SIZEOF_OVR, 8))
    ovrs << ovr
    fat = fats[ovr.file_id]
    #puts "%d %08x %d %d %08x %08x %d %d %08x" % [ovr.file_id, ovr.ramaddr, ovr.ramsize, ovr.bsssize, ovr.start, ovr.end, fat.start, fat.end - fat.start, ovr.reserved]
    #puts "%d %08x %d" % [ovr.file_id, ovr.ramaddr, ovr.ramsize]
    bin = read(f, fat.start, fat.end - fat.start)
    ovrfname = "#{dirname}/#{ovr.file_id}"
    do_file ovrfname, bin, ovr.ramaddr
  end
end