DeSmuME用デバッガと霊界の布ときのみスクラッチ

Pokémon RNG Advent Calendar 2017の12日目の記事です。

DeSmuME用デバッガ


DeSmuME用デバッガを作りました。

DeSmuMEはGDB Remote Stub Protocolを実装しています。これを使ってDeSmuMEとやり取りしデバッガの動作を行っています。
ARMとGDB StubであればDeSmuMEに限らずほかのエミュレータでも動くかもしれません。(未チェック)

というわけで、DS用ゲームを解析したい方、どうぞご利用ください。

TODOは以下の通り

  • メモリビュー
  • アセンブルリストにコメントをつけられるように
  • Follow Jump / Undo Follow
  • Jump命令に関して矢印をつける
  • Read, Writeのブレークポイント

と、これだけではRNGともPokémonとも関係ない記事になってしまうのでポケモンの乱数の話をします。

霊界の布

ダイヤモンドパールで霊界の布をゲットする乱数調整を行いました。


きのみスクラッチ

プラチナ、HGSSバトルフロンティアにおけるきのみスクラッチを解析しました。

どういう仕組みできのみが決まっているのか日本語で解説するのは面倒なのでseedを入力したらそのときの結果を出力するプログラムを貼ります。Rubyで書いています。

# Pt, HGSSでのフロンティアのきのみスクラッチ

class LCG
  def initialize(seed)
    @seed = seed
  end

  def clone
    LCG.new(@seed)
  end

  def rand()
    @seed = (@seed * 0x41c64e6d + 0x6073) % 2**32
    @seed >> 16
  end

  def rand_n(n)
    rand() % n
  end
end

ITEM_LIST = [0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8]

ITEM_NAMES = {
  0x005C => "きんのたま",
  0x00A9 => "ザロクのみ",
  0x00AA => "ネコブのみ",
  0x00AB => "タボルのみ",
  0x00AC => "ロメのみ",
  0x00AD => "ウブのみ",
  0x00AE => "マトマのみ",
  0x00B8 => "オッカのみ",
  0x00B9 => "イトケのみ",
  0x00BA => "ソクノのみ",
  0x00BB => "ソンドのみ",
  0x00BC => "ヤチェのみ",
  0x00BD => "ヨブのみ",
  0x00BE => "ビアーのみ",
  0x00BF => "シュカのみ",
  0x00C0 => "バコウのみ",
  0x00C1 => "ウタンのみ",
  0x00C2 => "タンガのみ",
  0x00C3 => "ヨロギのみ",
  0x00C4 => "カシブのみ",
  0x00C5 => "ハバンのみ",
  0x00C6 => "ナモのみ",
  0x00C7 => "リリバのみ",
  0x00C8 => "ホズのみ",
}

def make_scratch(lcg)
  # メタモンの位置決め
  arrange = Array.new(9, nil)
  2.times do |i|
    while true
      i = lcg.rand_n(9)
      if not arrange[i]
        arrange[i] = 4
        break
      end
    end
  end

  #きのみの配置を決定
  item = lcg.rand_n(4)
  count = 0
  9.times do |i|
    x = lcg.rand_n(9)
    if not arrange[x]
      count = 0
      arrange[x] = item
      if i == 2 or i == 4 or i == 6
        item = (item + 1) % 4
      end
    else
      count += 1
      redo if count < 30
      count = 0
      9.times do |j|
        next if arrange[j]
        arrange[j] = item
        if i == 2 or i == 4 or i == 6
          item = (item + 1) % 4
        end
        break
      end
    end
  end

  # アイテムを決定
  items = Array.new(4, nil)
  i0 = lcg.rand_n(4)
  4.times do |i|
    if i == i0
      items[i] = 0x5c
    else
      item = ITEM_LIST[lcg.rand_n(23)]
      redo if items.include?(item)
      items[i] = item
    end
  end
  
  [arrange, items.map{|x| ITEM_NAMES[x] }]
end

def main()
  print "seedを入力> 0x"
  seed = gets.to_i(16)
  print "最大消費数を入力> "
  max_f = gets.to_i
  lcg = LCG.new(seed)
  max_f.times do |f|
    puts "消費#{f}:"
    l = lcg.clone()
    3.times { p make_scratch(l) }
    lcg.rand()
  end
end

main() if $0 == __FILE__