Ragnarok OnlineにおけるDirectSoundの初期化、及びサウンドレート、そして高音質化へ
今回はサウンド関連というネタを提示しますが、ネタをもとに書きながら解析及び方法模索、コーディングを行っているので、長談になりがちですが、よろしくお付き合いください。
ROのexeやdllのインポートを参照してもdsound.dllは見つかりません。
サウンド関連の初期化はBinkw32.dll(Bink RAD Game Tools )というヘルパーライブラリによってdsound.dllが動的に読み込まれ初期化されます。
内部処理を詳しく知りたい方は、 デバッグやマルウェア解析等に関する有力ツール | Planetleaf.com Lab.で紹介したIDAで BinkOpenDirectSound というファンクションを見てみましょう。
BINK:300087C9 push offset aDsound_dll ; "DSOUND.DLL" BINK:300087CE mov edi, eax BINK:300087D0 call ds:LoadLibraryA BINK:300087D6 push edi ; uMode BINK:300087D7 mov ds:dword_30057AA8, eax BINK:300087DC call esi ; SetErrorMode BINK:300087DE mov eax, ds:dword_30057AA8 BINK:300087E3 pop edi BINK:300087E4 pop esi BINK:300087E5 BINK:300087E5 loc_300087E5: BINK:300087E5 cmp eax, 20h BINK:300087E8 jb short loc_300087FB BINK:300087EA push offset aDirectsoundcre ; "DirectSoundCreate" BINK:300087EF push eax ; hModule BINK:300087F0 call ds:GetProcAddress BINK:300087F6 mov ds:dword_30057AAC, eax BINK:300087FB BINK:300087FB loc_300087FB: BINK:300087FB mov eax, ds:dword_30057AAC BINK:30008800 test eax, eax BINK:30008802 jnz short loc_30008790
この通り、direct soundで音響関連が実装されていることが分かります。
さて、実は本題はサウンドレートを22kHzから44kHzに引き上げようという話になります。
現在のiROのクライアントはSetupからサウンドレートの設定が削除されていますが、サウンドレートの設定自体はレジストリのDIGITALRATETYPEという値を参照しています。
サウンドレートが弄れないため、レジストリの知識が無く旧クライアントでレートを11kや8kにしてしまうと変更するすべがないという罠もあったりなかったりで、相変わらずな適当ぶりだなと・・・。
DIGITALRATETYPEは0~2でそれぞれ22kHz、11kHz、8kHzの値へ内部で振り分けられます。
レジストリの値自体は以下のようなコードで読み込まれています。
.text:0076C100 mov ecx, [esp+1Ch+phkResult] .text:0076C104 lea edx, [esp+1Ch+cbData] .text:0076C108 push edx ; lpcbData .text:0076C109 push offset dword_8F2850 ; lpData .text:0076C10E lea eax, [esp+24h+Type] .text:0076C112 push eax ; lpType .text:0076C113 push 0 ; lpReserved .text:0076C115 push offset aDigitalratetyp ; "DIGITALRATETYPE" .text:0076C11A push ecx ; hKey .text:0076C11B call esi ; RegQueryValueExA
分からない人のために解説すると、C言語のファンクションの呼び出し引数はスタックに積まれます。つまりこの場合は、
RegQueryValueExA( ecx , “DIGITALRATETYPE" , null , eax , &dword_8F2850 ,edx
);という呼び出しが行われることになります。
このコードから、(DWORD)dword_8F2850
に、DIGITALRATETYPEの値が格納される事が分かります。
あとはこの、インデックスを参照している場所を捜索すると、実際のサウンドレートの値を設定しているコードが判明します。
.text:0065F956 loc_65F956 .text:0065F956 mov eax, dword_8F2850 .text:0065F95B sub eax, ebp .text:0065F95D jz short loc_65F97C .text:0065F95F sub eax, 1 .text:0065F962 jz short loc_65F970 .text:0065F964 mov dword_8E3594, 1F40h ; 8000 Hz .text:0065F96E jmp short loc_65F986 .text:0065F970 ; --------------------------------------------------------------------------- .text:0065F970 .text:0065F970 loc_65F970: .text:0065F970 mov dword_8E3594, 2B11h ; 11025 Hz .text:0065F97A jmp short loc_65F986 .text:0065F97C ; --------------------------------------------------------------------------- .text:0065F97C .text:0065F97C loc_65F97C: .text:0065F97C mov dword_8E3594, 5622h ; 22050 Hz .text:0065F986 .text:0065F986 loc_65F986: .text:0065F986 .text:0065F986 mov eax, dword_8F2858 .text:0065F98B sub eax, ebp .text:0065F98D mov dword_8E3598, 8 .text:0065F997 jnz short loc_65F99F .text:0065F999 mov dword_8E3598, esi .text:0065F99F .text:0065F99F loc_65F99F: .text:0065F99F mov eax, dword_8F285C .text:0065F9A4 push eax .text:0065F9A5 push 0Fh .text:0065F9A7 call ds:_AIL_set_preference@8 ; AIL_set_preference(x,x) .text:0065F9AD mov ecx, dword_8E359C .text:0065F9B3 mov edx, dword_8E3598 .text:0065F9B9 mov eax, dword_8E3594 ; soud rate .text:0065F9BE push 1 .text:0065F9C0 push ecx .text:0065F9C1 push edx .text:0065F9C2 push eax ; soud rate .text:0065F9C3 call ds:_AIL_open_digital_driver@16 ; AIL_open_digital_driver(x,x,x,x)
これでサンプルレートがdword_8E3594に格納されることが判明しました。
その後、AIL_open_digital_driverというMSS32.dll(Miles Sound System RAD Game Tools )内のファンクションをコールしていることが分かります。サンプルレートは1番目の引数です。ROのメモリ上のコードを書き換えるのはリスクが大きいため、ここではMSS32.dllのAIL_open_digital_driverファンクションをフックすることを考えます。
では実際MSS32.dllの該当ファンクションを見ていきましょう。
.text:21103640 loc_21103640: .text:21103640 dec word ptr dword_21150E64 .text:21103647 retn .text:21103647 _AIL_release_all_timers@0 endp .text:21103647 .text:21103647 ; --------------------------------------------------------------------------- .text:21103648 align 10h .text:21103650 ; Exported entry 120. _AIL_open_digital_driver@16 .text:21103650 .text:21103650 ; =============== S U B R O U T I N E ======================================= .text:21103650 .text:21103650 .text:21103650 ; __stdcall AIL_open_digital_driver(x, x, x, x) .text:21103650 public _AIL_open_digital_driver@16 .text:21103650 _AIL_open_digital_driver@16 proc near .text:21103650 ; DATA XREF: .rdata:off_21147E28o .text:21103650 .text:21103650 pMem = word ptr -10h .text:21103650 var_E = dword ptr -0Eh .text:21103650 var_8 = dword ptr -8 .text:21103650 var_4 = dword ptr -4 .text:21103650 arg_0 = dword ptr 4 .text:21103650 arg_4 = dword ptr 8 .text:21103650 arg_8 = dword ptr 0Ch .text:21103650 arg_C = dword ptr 10h .text:21103650 .text:21103650 sub esp, 10h .text:21103653 inc word ptr dword_21150E64 .text:2110365A cmp word_21150E60, 0
都合がいいことに、ファンクションの下位アドレスに8バイトのパディングが存在するので、これを利用して処理を挟み込みます。今どき、超低レートでの再生を考える必要はないと思えるので単純にレートを2倍化するコードを挟み込むことにします。
以下は可読性を上げるため、成形したコードを提示します。
21103648 90 nop 21103649 90 nop 2110364a 90 nop 2110364b 90 nop 2110364c 90 nop 2110364d 90 nop 2110364e 90 nop 2110364f 90 nop Export__AIL_open_digital_driver: 21103650 83ec10 sub esp,byte +010h 21103653 66ff05640e1521 inc word [L21150e64]
スタックフレームの都合上通常のプロクシ関数呼び出しというわけにはいかないので、プロクシファンクションをスタックフレームなしの純粋なアセンブラコードとして記述することにします。
ProxyAIL_open_digital_driver: 処理 . . sub esp,byte +010h jmp ProxyResume ProxyFuncSub: 2110364c e9******** jmp ProxyAIL_open_digital_driver Export__AIL_open_digital_driver: 21103650 ebf9 jmp short ProxyFuncSub 21103652 90 nop ProxyResume: 21103653 66ff05640e1521 inc word [L21150e64]
そして、肝心の今回書き起こしたコードはこちら、
void *pResumeAIL_open_digital_driverFunction; void __declspec(naked) ProxyAIL_open_digital_driver(void) { __asm mov eax,[esp+0x04] // eax = soundrate __asm add [esp+0x04],eax // soundrate + soundrate ( soundrate x 2 ) __asm sub esp,0x010 __asm jmp pResumeAIL_open_digital_driverFunction } BOOL RagexeSoundRateFixer(void) { BOOL result = FALSE; CString fullpath; TCHAR currentdir[MAX_PATH]; HINSTANCE hDll; ::GetCurrentDirectoryA( MAX_PATH,currentdir ); fullpath.Format(_T("%s¥¥Mss32.dll"), currentdir); hDll = ::LoadLibrary(fullpath); if( !hDll ){ return result; } BYTE *p = (BYTE*)::GetProcAddress( hDll, "_AIL_open_digital_driver@16"); if( p ) { if( p[ 0] == 0x83 && p[ 1] == 0xec && p[ 2] == 0x10 ) { // find hotpatch structure. DWORD flOldProtect, flDontCare; if ( ::VirtualProtect( (LPVOID)&p[-5], 7 , PAGE_READWRITE, &flOldProtect) ) { p[-5] = 0xe9; // jmp p[ 0] = 0xeb; p[ 1] = 0xf9;// jmp short [pc-7] p[ 2] = 0x90; // nop pResumeAIL_open_digital_driverFunction = &p[3]; *((DWORD*)&p[-4]) = (DWORD)ProxyAIL_open_digital_driver - (DWORD)&p[-5] -5; ::VirtualProtect( (LPVOID)&p[-5], 7 , flOldProtect, &flDontCare); result = TRUE; } } } ::FreeLibrary(hDll); return result; }
今回のコードは、CBTフックにより、ウインドウ作成時に実行することで、簡単に導入が出来る物です。
ただし、同じような事をするアプリケーションとの協調性は無いので、注意したいところです。
ディスカッション
コメント一覧
まだ、コメントがありません