雑用担当の備忘録

技術的な話のみになります。翻訳データそのものはありません。 カテゴリ>その他>注意事項を必ず参照してください

2015年12月

消すかもと書きましたが、よく考えると良い題材だなと思うのでその補足

アセンブラの話になりますが実行ファイルへのパッチのあて方が題材です。
  1. パッチサイズが変わらない。あるいは減少する。(CoL)
  2. パッチサイズが増加する。(F◇4)
1. サイズが変わらない、もしくは減少する場合
CoLが該当します。

db 04 ⇒ db 08

なのでサイズが変わりません。この場合は何も問題はありません。

減少するパターンも考えてみます。
よくあるのが条件ジャンプで
CoL でも traditional chinese の部分

cmp ecx, 10
ja childoflight.1699588

これは if (ecx > 0x10) ならばジャンプするということなんですが、これをジャンプしないようにパッチをあてる場合が減少するパターンです。(命令を削除するから)
ジャンプしないようにするのは ja 命令を削除すると良いので、ここを何もしない命令(何も変化しない命令) NOP に置き換えます。つまり

cmp ecx, 10
nop
nop

とします。なぜ nop が2個必要なのか?
これは元の ja childoflight.1699588 をアセンブルすると 77 6B という2バイトの機械語になっているので
その2バイトを NOP(1バイトの90)で置き換えるには2個必要になる。
77 6B ⇒ 90 90
とする必要があります。

減少する場合も NOP で埋めれば良いだけなので問題はありません。

2. 増加する場合
問題になるのが増加する(命令を追加する)場合で F◇4 が該当します。

00007FF705CBF944: or [rdx],4000
00007FF705CBF94A: mov [rsp+30],ecx
00007FF705CBF94E: lea rdx,[rsp+30]

これを

00007FF705CBF944: or [rdx],4000
                                    mov ecx, 2                        ; << 命令を追加したい
00007FF705CBF94A: mov [rsp+30],ecx
00007FF705CBF94E: lea rdx,[rsp+30]

命令を追加したいのですが追加するスペースがありません。無理矢理
or [rdx],4000 の後に書いたとすると 00007FF705CBF94A 以降にある命令を全部後ろにずらして追加する形になるので(ジャンプ等のアドレスも変更になる)現実的ではありません

なので
空いている場所あるいは新しく確保した場所に追加の命令を書き、そちらにジャンプして追加分の命令が終わったら元の流れに戻るようにリターンするということをします。

ジャンプにも命令を書く必要があるのでまず命令1個つぶしてジャンプに置き換えます

or [rdx],4000
jmp NewMemory
lea rdx,[rsp+30]

NewMemory:                               ; 空いている場所もしくは確保した場所
mov ecx, 2                                   ; << 追加したい命令
mov [rsp+30],ecx                         ; << jmp でつぶされた元の命令
jmp (lea rdx,[rsp+30])のアドレス   ; 元の流れにリターン

イメージは上記なんですが
現実には jmp の命令サイズと元の命令サイズが違いjmp の方が多いので命令2個をつぶす必要があります
したがって

or [rdx],4000
jmp NewMemory                       ; ここから
nop                                            ;
nop                                            ;
nop                                            ;
nop                                            ; ここまでが2個の命令と同じサイズになる
mov ecx,1

NewMemory:
mov ecx, 2                                   ; << 追加したい命令
mov [rsp+30],ecx                         ; << jmp でつぶされた1個目の命令
lea rdx,[rsp+30]                           ; << jmp でつぶされた2個目の命令
jmp (mov ecx,1)のアドレス           ; 元の流れにリターン

とすると良いです。

CheatEngine スクリプトの
alloc(MyCode,$1000,trackNo)
の部分はメモリを確保している部分になります。
他の部分は上記で説明したことをやっているだけです。

F某4 ムービー音声について

有名どころなので、あまり関わりたくないので消す可能性が高いですが一応書いておきます。

音声をツールで英語トラックに移すやり方が広まりつつありますが
英語トラックを選択している部分を変えるやり方です。CoL の時と同じように
BinkSetSoundTrack を見ます。

00007FF705CBF940 | 48 83 EC 28              | sub rsp,28
00007FF705CBF944 | 81 0A 00 40 00 00    | or dword ptr ds:[rdx],4000
00007FF705CBF94A | 89 4C 24 30              | mov dword ptr ss:[rsp+30],ecx
00007FF705CBF94E | 48 8D 54 24 30         | lea rdx,qword ptr ss:[rsp+30]
00007FF705CBF953 | B9 01 00 00 00          | mov ecx,1
00007FF705CBF958 | FF 15 BA A0 36 05    | call qword ptr ds:[<&BinkSetSoundTrack>]
00007FF705CBF95E | 48 83 C4 28              | add rsp,28
00007FF705CBF962 | C3                              | ret


mov dword ptr ss:[rsp+30],ecx
ここの ecx が track no で english だと 1 が入っています。
ここを
mov ecx, 2    // あえて french 察してください…
mov dword ptr ss:[rsp+30],ecx
とすると french 音声になります。

これを CheatEngine で実装すると
<?xml version="1.0" encoding="utf-8"?>
<CheatTable CheatEngineTableVersion="18">
  <CheatEntries>
    <CheatEntry>
      <ID>10000</ID>
      <Description>"french movie voice"</Description>
      <LastState Activated="0"/>
      <Color>80000008</Color>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript>[ENABLE]
aobscanmodule(trackNo,Fallout4.exe,89 4C 24 30 48 8D 54 24 30 B9 01 00 00 00)
alloc(MyCode,$1000,trackNo)

label(code)
label(return)

MyCode:

code:
  mov ecx, 2
  mov [rsp+30],ecx
  lea rdx,[rsp+30]
  jmp return

trackNo:
  jmp code
  nop
  nop
  nop
  nop
return:
registersymbol(trackNo)

[DISABLE]
trackNo:
  db 89 4C 24 30 48 8D 54 24 30
unregistersymbol(trackNo)
dealloc(MyCode)

</AssemblerScript>
    </CheatEntry>
  </CheatEntries>
</CheatTable>
こんな感じです。これで仏語音声になるはずです。

CheatEngine による実装

CheatEngine を新規にインストールされる方への注意、インストール時に色々とアドオンがインストールされてしまうのでチェックを外してインストールされた方が無難です。

スクリプトここから
<?xml version="1.0" encoding="utf-8"?>
<CheatTable CheatEngineTableVersion="18">
  <CheatEntries>
    <CheatEntry>
      <ID>45</ID>
      <Description>"english voice"</Description>
      <LastState Activated="0"/>
      <Color>FF0000</Color>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript>[ENABLE]
aobscanmodule(voice_lng,ChildofLight.exe,00 08 08 08 08 01 02 03 04 08 05 08 08 08 06 08 07)
voice_lng+8:
  db 08
registersymbol(voice_lng)

[DISABLE]
voice_lng+8:
  db 04
unregistersymbol(voice_lng)
</AssemblerScript>
    </CheatEntry>
  </CheatEntries>
  <Comments>===============================
 Game Title    : Child of Light
 Game Version  : 1.0.31711
 Proces Name   : ChildofLight.exe
 Script Version: 1.0
 CE Version    : 6.4
 Release date  : 2015/12/07
 Author        : 雑用担当
 ------------------------------------------
 History:
 2015/12/07: Release for version 1.0.31711
 Features:
 - change the language of movie voice
===========================================
[NOTE]
===========================================
- brazilian = 0
- english = 8
- french = 1
- german = 2
- italian = 3
- japanese = 4
- latamspanish = 5
- russian = 6
- spanish = 7
===========================================
</Comments>
</CheatTable>
ここまで
を ゲーム名.ct で保存、文字コードは UTF-8N
ゲーム名で無くても可、拡張子をCTにすれば良い。

CheatEngine を起動する
CoL_CE00
左上の緑枠で囲まれたアイコンをクリック
CoL_CE01
プロセスリストが表示されるのでゲームの実行ファイル名を選択して Open
CoL_CE02
フォルダアイコンをクリックして先ほどセーブしておいたスクリプトファイル(ゲーム名.ct)を読み込む
CoL_CE03
このように下の欄に english voice という項目が表示されるはず
この状態では左端のチェックボックスがオフになっているので現在は japanese voice
CoL_CE04
オンにすると以降のムービーは english voice となる。

ここから余談
スクリプトは長々書いていますが重要なのは

aobscanmodule(voice_lng,ChildofLight.exe,00 08 08 08 08 01 02 03 04 08 05 08 08 08 06 08 07)
voice_lng+8:
  db 08

この部分でチェックボックスがチェックされたら 00 08 ..... と jmp_tbl のアドレスを検索して、そのアドレス+8 が japanese のアドレスになる。そのアドレスの値は 04 なのでそこを 08 に置き換える
チェックボックスのチェックが外されたら元の値 04 に戻す。
ということをやっているだけ。

つまり応用として french 音声 japanese 字幕や japanese 音声 italian 字幕など色々な組み合わせが可能である。各 language の値はスクリプトの [NOTE] 部分を参照してください。

ムービー Voice 選択部分の解析

016994F0 | BA 03 00 00 00            | mov edx,3
016994F5 | C7 00 00 00 00 00       | mov dword ptr ds:[eax],0
016994FB | C7 40 04 01 00 00 00  | mov dword ptr ds:[eax+4],1
01699502 | C7 40 08 02 00 00 00  | mov dword ptr ds:[eax+8],2
01699509 | 89 50 0C                      | mov dword ptr ds:[eax+C],edx
0169950C | 8B 0D 48 FA CB 01     | mov ecx,dword ptr ds:[1CBFA48]
01699512 | 8B 89 F0 00 00 00       | mov ecx,dword ptr ds:[ecx+F0]
01699518 | 83 F9 10                      | cmp ecx,10
0169951B | 77 6B                           | ja childoflight.1699588 ; traditional chinese 12
0169951D | 0F B6 89 D0 95 69 01 | movzx ecx,byte ptr ds:[ecx+16995D0]
01699524 | FF 24 8D AC 95 69 01 | jmp dword ptr ds:[ecx*4+16995AC]
0169952B | C7 00 14 00 00 00       | mov dword ptr ds:[eax],14 ; french
01699531 | 50                                 | push eax
01699532 | 6A 04                           | push 4
01699534 | C7 40 04 0B 00 00 00 | mov dword ptr ds:[eax+4],B
0169953B | C7 40 08 0C 00 00 00 | mov dword ptr ds:[eax+8],C
01699542 | C7 40 0C 0D 00 00 00 | mov dword ptr ds:[eax+C],D
01699549 | FF 15 94 56 B2 01       | call dword ptr ds:[<&_BinkSetSoundTrack@8>]
0169954F | C3                                | ret
01699550 | C7 00 15 00 00 00       | mov dword ptr ds:[eax],15 ; japanese
01699556 | EB D9                          | jmp childoflight.1699531
01699558 | C7 00 16 00 00 00       | mov dword ptr ds:[eax],16 ; german
0169955E | EB D1                          | jmp childoflight.1699531
01699560 | C7 00 17 00 00 00       | mov dword ptr ds:[eax],17 ; spanish
01699566 | EB C9                          | jmp childoflight.1699531
01699568 | C7 00 18 00 00 00      | mov dword ptr ds:[eax],18 ; italian
0169956E | EB C1                         | jmp childoflight.1699531
01699570 | C7 00 19 00 00 00      | mov dword ptr ds:[eax],19 ; russian
01699576 | EB B9                         | jmp childoflight.1699531
01699578 | C7 00 1A 00 00 00     | mov dword ptr ds:[eax],1A ; brazilian
0169957E | EB B1                         | jmp childoflight.1699531
01699580 | C7 00 1B 00 00 00     | mov dword ptr ds:[eax],1B ; latamspanish
01699586 | EB A9                         | jmp childoflight.1699531
01699588 | 50                               | push eax                  ; english & traditional chinese
01699589 | 6A 04                          | push 4
0169958B | C7 00 00 00 00 00      | mov dword ptr ds:[eax],0  ;
01699591 | C7 40 04 01 00 00 00 | mov dword ptr ds:[eax+4],1
01699598 | C7 40 08 02 00 00 00 | mov dword ptr ds:[eax+8],2
0169959F | 89 50 0C                    | mov dword ptr ds:[eax+C],edx
016995A2 | FF 15 94 56 B2 01     | call dword ptr ds:[<&_BinkSetSoundTrack@8>]
016995A8 | C3                              | ret

016995AC | 78 95 69 01 | DD 01699578 ; brazilian
016995B0 | 2B 95 69 01 | DD 0169952B ; french
016995B4 | 58 95 69 01  | DD 01699558 ; german
016995B8 | 68 95 69 01  | DD 01699568 ; italian
016995BC | 50 95 69 01 | DD 01699550 ; japanese
016995C0 | 80 95 69 01 | DD 01699580 ; latamspanish
016995C4 | 70 95 69 01 | DD 01699570 ; russian
016995C8 | 60 95 69 01 | DD 01699560 ; spanish
016995CC | 88 95 69 01 | DD 01699588 ; english

016995D0 | 00                | DB 00       ; brazilian 0,0
016995D1 | 08                | DB 08
016995D2 | 08                | DB 08
016995D3 | 08                | DB 08       ; english 3,8
016995D4 | 08                | DB 08
016995D5 | 01                | DB 01       ; french 5,1
016995D6 | 02                | DB 02       ; german 6,2
016995D7 | 03                | DB 03       ; italian 7,3
016995D8 | 04                | DB 04       ; japanese 8,4
016995D9 | 08                | DB 08
016995DA | 05                | DB 05       ; latamspanish A,5
016995DB | 08                | DB 08
016995DC | 08                | DB 08
016995DD | 08                | DB 08
016995DE | 06                | DB 06       ; russian E,6
016995DF | 08                | DB 08
016995E0 | 07                | DB 07       ; spanish 10,7

この部分が相当するのですが
もうちょっと分かりやすく記述すると

if language_cd > 0x10 {
    goto (switch分のenglishの部分へ);  // traditional chinese = 12
} else {
    switch (language_cd) {
        case 0x0:    // brazilian
            jmp_tbl = 0; break;
        case 0x3:    // english
            jmp_tbl = 8; break;
        case 0x5:    // french
            jmp_tbl = 1; break;
        case 0x6:    // german
            jmp_tbl = 2; break;
        case 0x7:    // italian
            jmp_tbl = 3; break;
        case 0x8:    // japanese
            jmp_tbl = 4; break;
        case 0xA:    // latamspanish
            jmp_tbl = 5; break;
        case 0xE:    // russian
            jmp_tbl = 6; break;
        case 0x10:  // spanish
            jmp_tbl = 7; break;
    }
}

switch (jmp_tbl) {
    case 0:  // brazilian
        track_no = 0x1A; break;
    case 1:  //  french
        track_no = 0x14; break;
    case 2:  // german
        track_no = 0x16; break;
    case 3:  // italian
        track_no = 0x18; break;
    case 4:  // japanese
        track_no = 0x15; break;
    case 5:  // latamspanish
        track_no = 0x1B; break;
    case 6:  // russian
        track_no = 0x19; break;
    case 7:  // spanish
        track_no = 0x17; break;
    case 8:  // english
        track_no = 0x0; break;
}

となっています。japanese 字幕で english 音声にするには language_cd = 8(japanese) の時に jmp_tbl = 4 としていますがこれを english と同じ jmp_tbl = 8 とすると良い。

やり方はプロセスエディタで switch (language_cd) に該当する部分 00 08 08 08 08 01 02 03 04 08 05 08 08 08 06 08 07 を探して 04 の部分を 08 に置き換えると良い。

CheatEngine を使った実装例は次回

リクエストがあったので、ムービー音声を english にすることがメインです。
CoL00
概要:
  • UbiArt Framework
  • アーカイブ ipk
  • フォント ビットマップ
ムービーは bink で音声は BinkSetSoundTrack() で各 language を切り替えているのでここをフックすると良い。
Track No は実行ファイルに依存しているようなので、プロセスエディタでそこを書き換えるようにする。

プロセスエディタは何でもいいんだが、とりあえず CheatEngine のスクリプトを書いたので記事中に記述する。
動作は CheatEngine の項目をオンにした状態でムービーが始まると英語、オフの状態で始まると japanese になる。

この状態だと japanese の音声に字幕表示時間が調整されているので、english に替えたときに若干表示が短くなったような気もする。このあたり気になる人は srt ファイルで字幕表示時間の調整が出来るのでそれをすると良い。ただしアンパックが必要。

↑このページのトップヘ