Luaスクリプト
ツールによっては、Luaスクリプトにより、内部情報を表示させたり自動操作させたりといったカスタマイズができるようになっているものがあります。
そちらで使えるスクリプトを参考として挙げておきます。
そちらで使えるスクリプトを参考として挙げておきます。
体力表示
ゲーム内時間、救助者(最大7人、ロスト状態含む)および主人公の体力をゲーム画面内に表示させるスクリプトです。
※次図は動作時のサンプル

※次図は動作時のサンプル

- --機能: セプテントリオン同行者体力表示
- --対象メモリ:
- -- 時刻: 経過分・秒・フレーム 0x7e03e2, 0x7e03e0, 0x7e03de
- -- 体力: 0x7e15?d, 0x7e15?c ?=0-6 ( 先頭が0 )
- vit = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
- while true do
- time_m = memory.readbyte(0x7e03e2)
- time_s = memory.readbyte(0x7e03e0)
- time_f = memory.readbyte(0x7e03de)
- for i=1,8 do
- vu=memory.readbyte(0x7e14ed+i*16)
- vl=memory.readbyte(0x7e14ec+i*16)
- if vu==0 and vl==0 then
- vit[i*2-1] = 0
- vit[i*2 ] = 0
- else
- if i==1 then
- vit[i*2-1]=(vu+8)%256
- else
- vit[i*2-1]=(vu+64)%256
- vit[i*2 ]= (vl*1000+128)/256
- gui.text(
- 180,184,
- string.format(
- "%3dm %02ds %02dbf\n1:%3d.%03d 5:%3d.%03d\n2:%3d.%03d 6:%3d.%03d\n3:%3d.%03d 7:%3d.%03d\n4:%3d.%03d H:%3d.%03d",
- time_m, time_s, time_f,
- vit[ 3], vit[ 4], vit[11], vit[12],
- vit[ 5], vit[ 6], vit[13], vit[14],
- vit[ 7], vit[ 8], vit[15], vit[16],
- vit[ 9], vit[10], vit[ 1], vit[ 2]
- ),
- 0xffff00ff
- )
- snes9x.frameadvance()
-
位置・イベント情報表示
位置情報(x,y座標を1/16してピクセル単位にしたもの)、部屋ID(表エリア以外)あるいはブロックID(表エリア)、イベントID(プロローグ時は主人公共通イベントかどうか)とイベント参加キャラ(イベント有効時)一覧を表示するスクリプトです。
イベントの調査のために実装したものです。
※次図は動作時のサンプル

イベントの調査のために実装したものです。
※次図は動作時のサンプル

- --機能: セプテントリオン現在地・イベント情報表示
- --対象メモリ:
- -- 部屋ID: 0x7e0230
- -- 主人公X,Y座標: 0x7e04e2,6
- -- イベントID: 0x7e040c
- -- プロローグ共通フラグ: 0x7e02ec
- -- イベント参加キャラID: 0x7e0658 + 0x20*0~3
- while true do
- x = bit.rshift(memory.readword(0x7e04e2),4)
- y = bit.rshift(memory.readword(0x7e04e6),4)
- roomid = memory.readword(0x7e0230)
- blkid = bit.band(bit.rshift(x,9),7)
- if y >= 0x600 then
- blkid = -1
- else
- roomid = -1
- if y >= 0x400 then
- -- 底層
- blkid = blkid+16
- elseif y >= 0x200 then
- -- 中層
- blkid = blkid+8
- eid = memory.readbyte(0x7e040c)
- if eid==255 then
- gui.text(
- 180,188,
- string.format(
- "x: %04d, y: %04d\nblk: %2d, room: %3d\nevid: -1\nchars:",
- x, y, blkid, roomid
- ),
- 0xffff00ff
- )
- else
- if memory.readword(0x7e02ec)==0 then
- pc = "-"
- else
- pc = "p"
- ids = { -1, -1, -1, -1 }
- for i=1,4 do
- cid = memory.readbyte(0x7e0638+0x20*i)
- if cid==255 then
- break
- ids[i] = cid
- gui.text(
- 180,188,
- string.format(
- "x: %04d, y: %04d\nblk: %2d, room: %3d\nevid: %3d (%s)\nchars: %s",
- x, y, blkid, roomid, eid, pc, table.concat(ids,",")
- ),
- 0xffff00ff
- )
- snes9x.frameadvance()
-
rubyスクリプト
もともとエミュレータ用のLuaスクリプトだけ出すつもりでしたが、解析のために使ったちょっとしたスクリプトもこちらに載せることにします。使い方は細かく書きませんので感じてください。
イベントデータ伸長
圧縮形式のイベントデータ(16進ダンプ、非英数文字はスキップ)を標準入力(あるいはARGVに指定したファイル)から読み取り、伸長して16進ダンプするスクリプトです。
途中の処理状況が分かるように色々標準エラーに出力してます。
※途中で伸長に失敗しても、できたところまで出力します。
途中の処理状況が分かるように色々標準エラーに出力してます。
※途中で伸長に失敗しても、できたところまで出力します。
- uncompress=->bytes,verbose=false{
- #p bytes
- rbytes=[]
- i=j=0
- getb=->{
- b=bytes[i] or raise "#{i},#{j}"
- i+=1
- b
- }
- setb=->b{
- rbytes[j]=b
- j+=1
- }
- copy=->a,off{
- (a+1).times{
- rbytes[j]=rbytes[j-off]
- j+=1
- }
- }
- buf=0
- sbits=0
- fill=->{
- ibak=i
- bl=getb[]
- bh=getb[]
- w=(bh<<8)|bl
- buf|=w<<(16-sbits)
- sbits+=16
- warn "fill @#{ibak}" if verbose
- }
- getbw=->{
- buf>>16
- }
- discard=->d{
- return if d==0
- buf=(buf<<d)&0xffffffff
- sbits-=d
- if sbits<16
- fill[]
- end
- }
- judge1=->{
- w=getbw[]
- #p "j1: %04x" % w
- case w
- when 0...0x200
- [(w>>2)|0x80, 13]
- when 0x200...0x400
- [w>>3, 12]
- when 0x400...0x800
- [w>>5, 10]
- when 0x800...0x1000
- [w>>7, 8]
- when 0x1000...0x2000
- [w>>9, 6]
- when 0x2000...0x4000
- [w>>11, 4]
- when 0x4000...0x8000
- [w>>13, 2]
- else
- [1, 0]
- end
- }
- judge2=->{
- w=getbw[] & 0x7fff
- #p "j2: %04x" % w
- w &= 0x7fff
- case w
- when 0...0x800
- [w>>9, 7]
- when 0x800...0xc00
- [(w>>8)-4, 8]
- when 0xc00...0x1800
- [(w>>7)-0x10, 9]
- when 0x1800...0x3000
- [(w>>6)-0x40, 10]
- else
- sw=8-(w>>12)
- [(w&0xfff|0x1000)>>sw, 16-sw]
- end
- }
- fill[]
- begin
- loop{
- warn "getf @#{i}" if verbose
- cb=getb[]
- warn "flag byte: #{'%08b'%cb}" if verbose
- 7.downto(0){|k|
- if cb[k]>0
- warn "copyraw @#{i} -> @#{j}" if verbose
- setb[getb[]]
- else
- p1,d1=judge1[]
- if p1==0xff
- warn "detect end mark (p1=0xff)"
- break
- end discard[d1]
- p2,d2=judge2[]
- discard[d2]
- if verbose
- warn "repeat @#{j} from -#{p2} by #{p1+1}, consume #{d1+d2}"
- bufs=("%032b"%buf)[0...sbits]
- warn "pooled #{bufs} ( #{sbits} bits )"
- end
- copy[p1,p2+1]
- end
- } or break
- }
- rescue => e
- warn " * error #{e} in uncompression *"
- end
- rbytes
- }
- cbytes = $<.flat_map{|line| line.scan(/\w\w/).map{|t| t.to_i(16) } }
- rbytes=uncompress[cbytes,true]
-
- 0.step{|off|
- bytes=[]
- dobreak=false
- 16.times{|i|
- b=rbytes[off*16+i] or break
- bytes[i]=b
- } or dobreak=true
- break if bytes.size==0
- bstrs=[" "]*16
- bytes.each_with_index{|b,i| bstrs[i]="%02x" % b}
- puts (" %02x0: %s" % [off, bstrs.each_slice(2).map{|(s1,s2)| s1+s2 }*" "])
- break if dobreak
- }
-
実行例については、https://ideone.com/MlLkkk をご覧ください。
イベントデータ圧縮
上記イベントデータ伸長の逆の効果を持ちます。
なお、圧縮形式の性質上、一意に圧縮結果は定まりません。このスクリプトでは次のポリシーで圧縮処理を行います。
なお、圧縮形式の性質上、一意に圧縮結果は定まりません。このスクリプトでは次のポリシーで圧縮処理を行います。
- 既出パターンの検索
その時点で最長マッチとなる既出パターンを優先します。
※一旦既出パターンを短く見積もって、後で長めの既出パターンを切り出せる可能性があったとしても考慮しません。 - 余りのプール
プールに残るビット列は伸長結果に影響がないため、その分圧縮結果の最後の2バイトで、値を選択できる余地があります。
そのビット数はmargin bitとして、圧縮処理の最後に標準エラーに出力します。
本スクリプトではビットパターン0をプールするものとします。 - 最後のフラグバイト
フラグバイトを設ける関係上、元のデータを8か所ずつに区切って処理するわけですが、区切った箇所が8の倍数になるとは限りません。
そのため、最後のフラグバイトの末尾の数ビットは任意の値を採ることができます。
本スクリプトではビット0を設定するものとします。
- compress=->bytes,verbose=false{
- wmax=4095
- dmax=255
- search=->pos{
- bestl,bestp=1,0
- (0..[wmax,pos-1].min).each{|i|
- matched = 0.step{|j|
- break j if j>=dmax || pos+j>=bytes.size || bytes[pos+j]!=bytes[pos+j-i-1]
- }
- next if matched<bestl||matched<2
- bestl=matched
- bestp=i
- }
- [bestl-1,bestp]
- }
- pools=[]
- pool=0
- pooledbit=0
- addpool=->len,pos{
- raise 'unexpected' unless len<=dmax && pos<=wmax
- addlen=[14,len.bit_length*2-1].min
- pool=(pool<<addlen)|(len&0x7f)
- pooledbit+=addlen
- ret=addlen
- addlen,bias=case pos
- when 0..3; [6,0]
- when 4..7; [7,4]
- when 8..31; [8,16]
- when 32..127; [9,64]
- else
- blen=pos.bit_length
- [blen+2, (blen-6) << blen]
- end
- pool=(pool<<addlen)|(pos+bias)
- pooledbit+=addlen
- ret+=addlen
- while pooledbit>=16
- sword=pool>>(pooledbit-16)
- pools.push(sword&0x7f,sword>>8)
- pooledbit-=16
- pool&=(1<<pooledbit)-1
- end
- ret
- }
- i=0
- elems=[]
- while i<bytes.size
- len,pos=search[i]
- warn "search @#{i} -> len,pos=#{len},#{pos}" if verbose
- if len==0
- elems.push([1,bytes[i]])
- i+=1
- else
- pbit=addpool[len,pos]
- elems.push([0,pbit])
- i+=len+1
- end
- end
- addpool[255,0]
- elems.push([0,14])
- ((-elems.size)%8).times{
- elems.push([0,0])
- }
- margin=0
- if pooledbit>0
- raise 'unexpected' unless pooledbit<16
- margin=16-pooledbit
- pool<<=margin
- pools.push(pool&0xff,pool>>8)
- end
- cbytes=[]
- pooledbit=0
- consume=->bit{
- pooledbit -= bit
- while pooledbit<16
- cbytes.push(*pools.shift(2))
- pooledbit+=16
- end
- }
- consume[0]
- elems.each_slice(8){|chunk|
- cbytes.push((chunk.map{|e| e[0] }*'').to_i(2))
- chunk.each{|(dtype,val)|
- if dtype==1
- cbytes.push(val)
- else
- consume[val]
- end
- }
- }
- warn "margin bit: #{margin}" if verbose
- cbytes
- }
-
- rbytes = $<.flat_map{|line| line.scan(/\w\w/).map{|t| t.to_i(16) } }
- cbytes=compress[rbytes,true]
-
- 0.step{|off|
- bytes=[]
- dobreak=false
- 16.times{|i|
- b=cbytes[off*16+i] or break
- bytes[i]=b
- } or dobreak=true
- break if bytes.size==0
- bstrs=[" "]*16
- bytes.each_with_index{|b,i| bstrs[i]="%02x" % b}
- puts (" %02x0: %s" % [off, bstrs.each_slice(2).map{|(s1,s2)| s1+s2 }*" "])
- break if dobreak
- }
-
実行例については、https://ideone.com/u9yczd をご覧ください。