DSのROMのオーバーレイたちを展開するスクリプト
背景
DSはメインメモリが4MBしかなく、ゲームの全てのプログラムをメモリ上に載せることができません。そこで必要なときに必要なモジュールをメモリ上にロードしています(たとえば戦闘に入ったら戦闘用のプログラムモジュールをロードするなど)。
この各モジュールのことをオーバーレイと呼びます。
ROMファイルには各オーバーレイに対する
(ファイルID, 展開先のメモリアドレス)
の情報が入っています。
また、ファイルIDに対応する各ファイルのROM内のオフセットとサイズはROMのFAT領域に格納されています。
利点
ndsdis2 (http://i486.mods.jp/old/nds/ndshack.html)はメインメモリを全て逆アセンブルする仕組みでした。
これではプログラム以外が格納されているメモリも逆アセンブルするので無駄があります。
またメモリダンプしたそのときに載っているオーバーレイしか逆アセンブルできないという欠点があります。
今回のスクリプトはすべてのプログラムデータを無駄なく逆アセンブルできます。
必要なもの
- Ruby
- ARMアーキテクチャに対応したobjdump (devkitProに入っています)
- DSDecmp (https://github.com/barubary/dsdecmp) (圧縮を解凍するために必要)
スクリプト
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