public static void PatchMusicQueueing(Stream binary, Sen2ExecutablePatchState state) { var mapper = state.Mapper; var a = state.BgmTimingPatchLocations; state.InitCodeSpaceIfNeeded(binary); using (BranchHelper4Byte lock_mutex = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte unlock_mutex = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte set_dirty_and_write_sound_queue_4bytes = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte clear_dirty_if_queue_empty = new BranchHelper4Byte(binary, mapper)) using (BranchHelper1Byte queue_not_empty = new BranchHelper1Byte(binary, mapper)) using (BranchHelper4Byte queue_empty_jump_back = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte check_if_should_enqueue = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte back_to_function = new BranchHelper4Byte(binary, mapper)) using (BranchHelper1Byte check_done_return_0 = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte check_done = new BranchHelper1Byte(binary, mapper)) using (BranchHelper4Byte write_sound_queue_4bytes = new BranchHelper4Byte(binary, mapper)) { EndianUtils.Endianness be = EndianUtils.Endianness.BigEndian; EndianUtils.Endianness le = EndianUtils.Endianness.LittleEndian; Stream _ = binary; // end of the uninitialized .data section, right before .rsrc, should be unused // TODO: where is the actual information about the location/length of that section stored...? uint address_of_dirty_flag = a.AddressOfDirtyFlag; uint address_of_overwritable_write_sound_queue_4bytes_0x5 = a.AddressOfOverwritableWriteSoundQueue4bytes_0x5; uint address_of_overwritable_write_sound_queue_4bytes_0x6 = a.AddressOfOverwritableWriteSoundQueue4bytes_0x6; uint address_write_sound_queue_4bytes = a.AddressWriteSoundQueue4bytes; uint end_of_sound_queue_processing = a.EndOfSoundQueueProcessing; uint address_of_is_playing_check_injection = a.AddressOfIsPlayingCheckInjection; write_sound_queue_4bytes.SetTarget(address_write_sound_queue_4bytes); lock_mutex.SetTarget(a.LockMutex); unlock_mutex.SetTarget(a.UnlockMutex); // set dirty flag when a BGM switch or stop is enqueued into the sound queue { _.Position = (long)mapper.MapRamToRom(state.RegionD.Address); set_dirty_and_write_sound_queue_4bytes.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0xb8); // mov eax,(address of dirty flag) _.WriteUInt32(address_of_dirty_flag, le); _.WriteUInt24(0xc60001, be); // mov byte ptr[eax],1 write_sound_queue_4bytes.WriteJump5Byte(0xe9); // jmp write_sound_queue_4bytes state.RegionD.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Queueing: Set dirty flag"); } { _.Position = (long)mapper.MapRamToRom(address_of_overwritable_write_sound_queue_4bytes_0x5); set_dirty_and_write_sound_queue_4bytes.WriteJump5Byte(0xe8); } { _.Position = (long)mapper.MapRamToRom(address_of_overwritable_write_sound_queue_4bytes_0x6); set_dirty_and_write_sound_queue_4bytes.WriteJump5Byte(0xe8); } // clear dirty flag after sound queue is processed if queue is still empty // this is... very much a heuristic, but should be *good enough* hopefully { _.Position = (long)mapper.MapRamToRom(end_of_sound_queue_processing); ulong tmp = _.PeekUInt48(le); // remember instruction here so we can reinsert it later clear_dirty_if_queue_empty.WriteJump5Byte(0xe9); _.WriteUInt8(0x90); // nop rest of instruction for safety, though shouldn't matter queue_empty_jump_back.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.Position = (long)mapper.MapRamToRom(state.Region50a.Address); clear_dirty_if_queue_empty.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt48(0x8bb56cffffff, be); // mov esi,dword ptr [ebp-94h] _.WriteUInt24(0x8d4e44, be); // lea ecx,[esi+44h] lock_mutex.WriteJump5Byte(0xe8); // call lock_mutex _.WriteUInt32(0x837e6c00, be); // cmp dword ptr [esi+6Ch],0 queue_not_empty.WriteJump(0x75); // jne queue_not_empty _.WriteUInt8(0xb9); // mov ecx,(address of dirty flag) _.WriteUInt32(address_of_dirty_flag, le); _.WriteUInt24(0xc60100, be); // mov byte ptr[ecx],0 queue_not_empty.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt24(0x8d4e44, be); // lea ecx,[esi+44h] unlock_mutex.WriteJump5Byte(0xe8); // call unlock_mutex _.WriteUInt48(tmp, le); // reinsert instruction we overwrote queue_empty_jump_back.WriteJump5Byte(0xe9); // jmp queue_empty_jump_back state.Region50a.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Queueing: Clear dirty flag"); } // patch the logic for when to skip enqueueing a bgm { _.Position = (long)mapper.MapRamToRom(address_of_is_playing_check_injection); check_if_should_enqueue.WriteJump5Byte(0xe9); for (int i = 0; i < 5; ++i) { _.WriteUInt8(0x90); // clear instructions we no longer want here } back_to_function.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.Position = (long)mapper.MapRamToRom(state.Region60.Address); check_if_should_enqueue.SetTarget(mapper.MapRomToRam((ulong)_.Position)); // load dirty flag _.WriteUInt24(0x8d4e44, be); // lea ecx,[esi+44h] lock_mutex.WriteJump5Byte(0xe8); // call lock_mutex _.WriteUInt8(0xb8); // mov eax,(address of dirty flag) _.WriteUInt32(address_of_dirty_flag, le); _.WriteUInt16(0x8a00, be); // mov al,byte ptr[eax] _.WriteUInt16(0x8bf8, be); // mov edi,eax _.WriteUInt24(0x8d4e44, be); // lea ecx,[esi+44h] unlock_mutex.WriteJump5Byte(0xe8); // call unlock_mutex // lock on bgm state _.WriteUInt24(0x8d4e40, be); // lea ecx,[esi+40h] lock_mutex.WriteJump5Byte(0xe8); // call lock_mutex // check is_playing_bgm() _.WriteUInt16(0x8b06, be); // mov eax,dword ptr[esi] _.WriteUInt48(0x8b8094000000, be); // mov eax,dword ptr[eax+94h] _.WriteUInt16(0x8bce, be); // mov ecx,esi _.WriteUInt16(0xffd0, be); // call eax ; al is now 1 when bgm is playing, 0 if not _.WriteUInt16(0x84c0, be); // test al,al check_done.WriteJump(0x74); // je check_done ; if !is_playing_bgm() we're already done; this returns 0 // check dirty flag _.WriteUInt16(0x8bc7, be); // mov eax,edi _.WriteUInt16(0x84c0, be); // test al,al ; al is now 1 when dirty, 0 if not check_done_return_0.WriteJump(0x75); // jne check_done_return_0 ; if dirty return 0 // check bgm_is_fading() _.WriteUInt24(0x8b4614, be); // mov eax,dword ptr[esi+14h] _.WriteUInt16(0x8b00, be); // mov eax,dword ptr[eax] ; eax is now pointing at the bgm sound channel _.WriteUInt24(0x8a4038, be); // mov al,byte ptr[eax+38h] ; al is now 1 when fade is active, 0 if not _.WriteUInt16(0x84c0, be); // test al,al check_done_return_0.WriteJump(0x75); // jne check_done_return_0 ; if it's fading out return 0 _.WriteUInt8(0x40); // inc eax check_done.WriteJump(0xeb); // jmp check_done ; otherwise return 1 check_done_return_0.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt16(0x33c0, be); // xor eax,eax check_done.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt16(0x8bf8, be); // mov edi,eax _.WriteUInt24(0x8d4e40, be); // lea ecx,[esi+40h] unlock_mutex.WriteJump5Byte(0xe8); // call unlock_mutex _.WriteUInt16(0x8bc7, be); // mov eax,edi _.WriteUInt16(0x8bce, be); // mov ecx,esi ; restore ecx (probably unnecessary) _.WriteUInt16(0x8b3e, be); // mov edi,dword ptr[esi] ; restore edi back_to_function.WriteJump5Byte(0xe9); // jmp back_to_function state.Region60.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Queueing: Check if want to enqueue"); } // some old notes: // 0x8eda84 -> FSound vftable // 0x8ed0b4 -> FSoundBase vftable // 0x8eda10 -> FSoundChannelController vftable // 0x8ed268 -> FSoundBank vftable // FSound data structure: // +0x00 vftableptr // +0x04 ? // +0x08 length of 1st FSoundChannelController* array // +0x0C pointer to 1st FSoundChannelController* array // +0x10 length of 2nd FSoundChannelController* array // +0x14 pointer to 2nd FSoundChannelController* array // +0x40 mutex handle for the bgm FSoundChannelController (?) // +0x44 mutex handle for locking the sound queue (?) // +0x5C base address of sound queue (ringbuffer?) // +0x60 size of sound queue // +0x64 write offset in sound queue (?) // +0x68 read offset in sound queue (?) // +0x6C bytes used in sound queue // always jump the disallow-enqueue-while-same-track-playing branch // if we need a safer test, *(int*)(*(((int*)edi)+5)) in function at 0x41F846 (which is the currently-playing-bgm check, called at 0x57c803) // seems to give us the pointer to the data structure containing the current volume/fade info of the bgm // which seems to be: // +0x00 ptr: FSoundChannelController::vftable // +0x04 ptr: FSound // +0x08 ptr: FSoundData // +0x0c ? // +0x10 ptr: FSoundStreamParamsWin32 // +0x14 ? // +0x18 ? // +0x1C float: ? // +0x20 float: current volume // +0x24 float: fade start factor // +0x28 float: fade end factor // +0x2C float: target fade time in seconds // +0x30 float: current fade time in seconds // +0x34 float?: ? // +0x38 byte: fade out is active? // +0x4D byte: channel is active? // (compare the fade adjustment function at 0x421da0, which is pretty clear) // so we could inject a test for this at 0x57c80f to allow fades if and only if bgm is currently fading out // by checking (current fade time < target fade time) && fade end factor == 0.0f // or just the flag at 0x38, which seems to indicate that the track should be stopped (either after a fade or immediately) // but this doesn't actually seem to be necessary, as far as I can tell? // still, figured I'd note this here in case it ends up being useful // _.Position = (long)mapper.MapRamToRom(a.BgmAlreadyPlayingJump); // _.WriteUInt8(0xeb); } }
public static void PatchLanguageAppropriateVoiceTables(Stream binary, Sen2ExecutablePatchState state) { state.InitCodeSpaceIfNeeded(binary); var mapper = state.Mapper; var regionStrings = state.RegionScriptCompilerFunctionStrings; var regionCode = state.RegionScriptCompilerFunction23; bool jp = state.IsJp; using (BranchHelper4Byte jump_from_function = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte back_to_function = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte get_pc_config = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte pc_config__get_voice_language = new BranchHelper4Byte(binary, mapper)) using (BranchHelper1Byte depends_on_voice_lang = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte exit_inject = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte use_english_string = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte use_text_lang_string = new BranchHelper1Byte(binary, mapper)) { EndianUtils.Endianness be = EndianUtils.Endianness.BigEndian; EndianUtils.Endianness le = EndianUtils.Endianness.LittleEndian; Stream _ = binary; get_pc_config.SetTarget(jp ? 0x6cf5b0u : 0x6d0520u); pc_config__get_voice_language.SetTarget(jp ? 0x6cfbd0u : 0x6d0b90u); // inject into the asset-from-text-subfolder loader function long address_of_inject = jp ? 0x51d1e7 : 0x51ce87; _.Position = mapper.MapRamToRom(address_of_inject); ulong push_local_formatting_string = _.PeekUInt40(be); uint address_local_formatting_string = ((uint)(push_local_formatting_string & 0xffffffffu)).SwapEndian(); jump_from_function.SetTarget(regionCode.Address); jump_from_function.WriteJump5Byte(0xe9); back_to_function.SetTarget(mapper.MapRomToRam((ulong)_.Position)); ulong push_en_formatting_string; ulong push_jp_formatting_string; uint t_vctiming_address; uint t_voice_address; if (jp) { push_jp_formatting_string = push_local_formatting_string; t_vctiming_address = 0x8f4a88; t_voice_address = 0x8f4a10; // english formatting string doesn't exist in the japanese executable, generate and inject it byte[] formatting_string_jp = _.ReadBytesFromLocationAndReset(mapper.MapRamToRom(address_local_formatting_string), 0x13); byte[] extra = _.ReadBytesFromLocationAndReset(0x4f1c4e, 0x3); byte[] formatting_string_en = new byte[formatting_string_jp.Length + 3]; ArrayUtils.CopyByteArrayPart(formatting_string_jp, 0, formatting_string_en, 0, 0xb); ArrayUtils.CopyByteArrayPart(extra, 0, formatting_string_en, 0xb, 3); ArrayUtils.CopyByteArrayPart(formatting_string_jp, 0xb, formatting_string_en, 0xe, 0x8); uint nonlocal_string_address = regionStrings.Address; _.Position = mapper.MapRamToRom(regionStrings.Address); _.Write(formatting_string_en); regionStrings.TakeToAddress(mapper.MapRomToRam(_.Position), "Voice Tables: EN Formatting String"); push_en_formatting_string = 0x6800000000 | (ulong)(nonlocal_string_address.SwapEndian()); } else { push_en_formatting_string = push_local_formatting_string; t_vctiming_address = 0x8f7cd4; t_voice_address = 0x8f7c5c; // japanese formatting string doesn't exist in the english executable, generate and inject it byte[] formatting_string_en = _.ReadBytesFromLocationAndReset(mapper.MapRamToRom(address_local_formatting_string), 0x16); byte[] formatting_string_jp = new byte[formatting_string_en.Length - 3]; ArrayUtils.CopyByteArrayPart(formatting_string_en, 0, formatting_string_jp, 0, 0xb); ArrayUtils.CopyByteArrayPart(formatting_string_en, 0xe, formatting_string_jp, 0xb, 0x8); uint nonlocal_string_address = regionStrings.Address; _.Position = mapper.MapRamToRom(regionStrings.Address); _.Write(formatting_string_jp); regionStrings.TakeToAddress(mapper.MapRomToRam(_.Position), "Voice Tables: JP Formatting String"); push_jp_formatting_string = 0x6800000000 | (ulong)(nonlocal_string_address.SwapEndian()); } // assemble logic to select the voice-language-matching voice tables _.Position = mapper.MapRamToRom(regionCode.Address); _.WriteUInt16(0x81ff, be); // cmp edi,(t_vctiming) _.WriteUInt32(t_vctiming_address, le); depends_on_voice_lang.WriteJump(0x74); // je depends_on_voice_lang _.WriteUInt16(0x81ff, be); // cmp edi,(t_voice) _.WriteUInt32(t_voice_address, le); depends_on_voice_lang.WriteJump(0x74); // je depends_on_voice_lang use_text_lang_string.WriteJump(0xeb); // jmp use_text_lang_string depends_on_voice_lang.SetTarget(mapper.MapRomToRam((ulong)_.Position)); get_pc_config.WriteJump5Byte(0xe8); // call get_pc_config _.WriteUInt16(0x8bc8, be); // mov ecx,eax pc_config__get_voice_language.WriteJump5Byte(0xe8); // call pc_config__get_voice_language _.WriteUInt16(0x84c0, be); // test al,al use_english_string.WriteJump(0x74); // jz use_english_string if (jp) { use_text_lang_string.SetTarget(mapper.MapRomToRam((ulong)_.Position)); } _.WriteUInt40(push_jp_formatting_string, be); // push (jp_formatting_string) exit_inject.WriteJump(0xeb); // jmp exit_inject use_english_string.SetTarget(mapper.MapRomToRam((ulong)_.Position)); if (!jp) { use_text_lang_string.SetTarget(mapper.MapRomToRam((ulong)_.Position)); } _.WriteUInt40(push_en_formatting_string, be); // push (en_formatting_string) exit_inject.SetTarget(mapper.MapRomToRam((ulong)_.Position)); back_to_function.WriteJump5Byte(0xe9); // jmp back_to_function regionCode.TakeToAddress(mapper.MapRomToRam(_.Position), "Voice Tables: Formatting String Select Code"); } }
public static void PatchMusicFadeTiming(Stream binary, Sen2ExecutablePatchState state, uint divisor) { var mapper = state.Mapper; var a = state.BgmTimingPatchLocations; // divisor of 1000 seems to be console-accurate, but making fades a little faster actually feels nicer with the fast PC loading times state.InitCodeSpaceIfNeeded(binary); RegionHelper region50b = state.Region50b; RegionHelper region51 = state.Region51; RegionHelper regionEntryPoint = null; RegionHelper region80 = null; using (BranchHelper4Byte alldvrm = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte allmul = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte do_compare = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte invoke_sleep_milliseconds = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte lock_mutex = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte invoke_query_performance_counter = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte invoke_query_performance_frequency = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte process_sound_queue = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte unlock_mutex = new BranchHelper4Byte(binary, mapper)) using (BranchHelper1Byte do_compare_end = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte fail = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte success = new BranchHelper1Byte(binary, mapper)) using (BranchHelper4Byte early_exit = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte exit_inner_loop = new BranchHelper4Byte(binary, mapper)) using (BranchHelper1Byte go_to_sleep_maybe = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte go_to_next_iteration = new BranchHelper1Byte(binary, mapper)) using (BranchHelper4Byte inner_loop = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte outer_loop_init = new BranchHelper4Byte(binary, mapper)) using (BranchHelper1Byte outer_loop = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte post_every_33_iterations = new BranchHelper1Byte(binary, mapper)) using (BranchHelper4Byte remainder_increment = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte thread_mainloop_continue = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte time_pass_loop_4byte = new BranchHelper4Byte(binary, mapper)) using (BranchHelper1Byte time_pass_loop_1byte = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte exit_remainder_increment = new BranchHelper1Byte(binary, mapper)) using (BranchHelper4Byte thread_mainloop = new BranchHelper4Byte(binary, mapper)) { { // this is the entry point of the function we're replacing, clear it out binary.Position = (long)mapper.MapRamToRom((ulong)a.ThreadEntryPointAddress); for (uint i = 0; i < a.ThreadEntryPointLength; ++i) { binary.WriteUInt8(0xcc); } regionEntryPoint = new RegionHelper(a.ThreadEntryPointAddress, a.ThreadEntryPointLength, "Timing Thread Entry Point"); } { // this is the body of the function we're replacing, clear it out // don't ask me why this is split up like this binary.Position = (long)mapper.MapRamToRom((ulong)a.ThreadFunctionBodyAddress); for (uint i = 0; i < a.ThreadFunctionBodyLength; ++i) { binary.WriteUInt8(0xcc); } region80 = new RegionHelper(a.ThreadFunctionBodyAddress, a.ThreadFunctionBodyLength, "Timing Thread Body"); } EndianUtils.Endianness be = EndianUtils.Endianness.BigEndian; EndianUtils.Endianness le = EndianUtils.Endianness.LittleEndian; // and assemble! Stream _ = binary; { _.Position = (long)mapper.MapRamToRom(regionEntryPoint.Address); _.WriteUInt8(0x55); // push ebp _.WriteUInt16(0x8bec, be); // mov ebp,esp _.WriteUInt24(0x83ec7c, be); // sub esp,7Ch _.WriteUInt24(0x8b4d08, be); // mov ecx,dword ptr [ebp+8] _.WriteUInt8(0x57); // push edi _.WriteUInt8(0x56); // push esi thread_mainloop.WriteJump5Byte(0xe9); // jmp thread_mainloop regionEntryPoint.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Timing: Thread Entry"); } { _.Position = (long)mapper.MapRamToRom(state.Region41.Address); remainder_increment.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt24(0x8b45bc, be); // mov eax,dword ptr [ebp-44h] ; load counter into eax _.WriteUInt16(0x03c2, be); // add eax,edx ; counter += ticks_per_loop_remainder _.WriteUInt24(0x8b55c8, be); // mov edx,dword ptr [ebp-38h] ; edx = original_divisor _.WriteUInt16(0x3bc2, be); // cmp eax,edx ; if counter >= original_divisor exit_remainder_increment.WriteJump(0x72); // jb exit_remainder_increment _.WriteUInt32(0x8345f001, be); // add dword ptr [ebp-10h],1 ; ++ticks_last _.WriteUInt32(0x8355f400, be); // adc dword ptr [ebp-0Ch],0 _.WriteUInt16(0x2bc2, be); // sub eax,edx ; counter -= original_divisor exit_remainder_increment.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt24(0x8945bc, be); // mov dword ptr [ebp-44h],eax ; write counter back to stack time_pass_loop_4byte.WriteJump5Byte(0xe9); // jmp time_pass_loop state.Region41.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Timing: Remainder Increment"); } { _.Position = (long)mapper.MapRamToRom(state.Region41.Address); thread_mainloop.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0x53); // push ebx _.WriteUInt16(0x8bf9, be); // mov edi,ecx _.WriteUInt16(0x33f6, be); // xor esi,esi _.WriteUInt56(0xf74710ffffff7f, be); // test dword ptr [edi+10h],7FFFFFFFh early_exit.WriteJump6Byte(0x0f84); // je early_exit _.WriteUInt24(0x8d45d8, be); // lea eax,[ebp-28h] invoke_query_performance_frequency.WriteJump5Byte(0xe8); // call invoke_query_performance_frequency thread_mainloop_continue.WriteJump5Byte(0xe9); // jmp thread_mainloop_continue state.Region41.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Timing: Thread Init 1"); } { _.Position = (long)mapper.MapRamToRom(region51.Address); thread_mainloop_continue.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0xb8); // mov eax,(divisor) _.WriteUInt32(divisor, le); _.WriteUInt24(0x8945c8, be); // mov dword ptr [ebp-38h],eax _.WriteUInt16(0x6a00, be); // push 0 _.WriteUInt8(0x50); // push eax _.WriteUInt24(0x8b45dc, be); // mov eax,dword ptr [ebp-24h] _.WriteUInt24(0x8b55d8, be); // mov edx,dword ptr [ebp-28h] _.WriteUInt8(0x50); // push eax _.WriteUInt8(0x52); // push edx alldvrm.WriteJump5Byte(0xe8); // call _alldvrm _.WriteUInt24(0x8945e0, be); // mov dword ptr [ebp-20h],eax _.WriteUInt24(0x8955e4, be); // mov dword ptr [ebp-1Ch],edx _.WriteUInt24(0x894dc0, be); // mov dword ptr [ebp-40h],ecx _.WriteUInt24(0x8b45dc, be); // mov eax,dword ptr [ebp-24h] _.WriteUInt24(0x8b55d8, be); // mov edx,dword ptr [ebp-28h] _.WriteUInt16(0x6a00, be); // push 0 _.WriteUInt16(0x6a05, be); // push 5 _.WriteUInt8(0x50); // push eax _.WriteUInt8(0x52); // push edx allmul.WriteJump5Byte(0xe8); // call _allmul _.WriteUInt24(0x8945d0, be); // mov dword ptr [ebp-30h],eax _.WriteUInt24(0x8955d4, be); // mov dword ptr [ebp-2Ch],edx _.WriteUInt24(0x8d45f0, be); // lea eax,[ebp-10h] invoke_query_performance_counter.WriteJump5Byte(0xe8); // call invoke_query_performance_counter _.WriteUInt32(0x807f5400, be); // cmp byte ptr [edi+54h],0 early_exit.WriteJump6Byte(0x0f85); // jne early_exit outer_loop_init.WriteJump5Byte(0xe9); // jmp outer_loop_init region51.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Timing: Thread Init 2"); } { _.Position = (long)mapper.MapRamToRom(region80.Address); outer_loop_init.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt56(0xc745bc00000000, be); // mov dword ptr [ebp-44h],0 outer_loop.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt16(0x33db, be); // xor ebx,ebx _.WriteUInt24(0x8d45e8, be); // lea eax,[ebp-18h] invoke_query_performance_counter.WriteJump5Byte(0xe8); // call invoke_query_performance_counter _.WriteUInt24(0x8d45e8, be); // lea eax,[ebp-18h] ; ticks_now _.WriteUInt24(0x8d55d0, be); // lea edx,[ebp-30h] ; ticks_for_reset _.WriteUInt24(0x8d4df0, be); // lea ecx,[ebp-10h] ; ticks_last do_compare.WriteJump5Byte(0xe8); // call do_compare _.WriteUInt16(0x85c0, be); // test eax,eax time_pass_loop_1byte.WriteJump(0x74); // je time_pass_loop _.WriteUInt24(0x8b45e8, be); // mov eax,dword ptr [ebp-18h] _.WriteUInt24(0x8945f0, be); // mov dword ptr [ebp-10h],eax _.WriteUInt24(0x8b45ec, be); // mov eax,dword ptr [ebp-14h] _.WriteUInt24(0x8945f4, be); // mov dword ptr [ebp-0Ch],eax time_pass_loop_1byte.SetTarget(mapper.MapRomToRam((ulong)_.Position)); time_pass_loop_4byte.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt24(0x8d45e8, be); // lea eax,[ebp-18h] ; ticks_now _.WriteUInt24(0x8d55e0, be); // lea edx,[ebp-20h] ; ticks_per_loop _.WriteUInt24(0x8d4df0, be); // lea ecx,[ebp-10h] ; ticks_last do_compare.WriteJump5Byte(0xe8); // call do_compare _.WriteUInt16(0x85c0, be); // test eax,eax go_to_sleep_maybe.WriteJump(0x74); // je go_to_sleep_maybe inner_loop.WriteJump5Byte(0xe9); // jmp inner_loop exit_inner_loop.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt24(0x8b45e0, be); // mov eax,dword ptr [ebp-20h] ; ticks_per_loop _.WriteUInt24(0x8b4de4, be); // mov ecx,dword ptr [ebp-1Ch] _.WriteUInt24(0x0145f0, be); // add dword ptr [ebp-10h],eax ; ticks_last _.WriteUInt24(0x114df4, be); // adc dword ptr [ebp-0Ch],ecx _.WriteUInt24(0x8b55c0, be); // mov edx,dword ptr [ebp-40h] ; ticks_per_loop_remainder _.WriteUInt16(0x85d2, be); // test edx,edx time_pass_loop_1byte.WriteJump(0x74); // je time_pass_loop ; no remainder, just go back to loop remainder_increment.WriteJump5Byte(0xe9); // jmp remainder_increment go_to_sleep_maybe.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt16(0x85db, be); // test ebx,ebx go_to_next_iteration.WriteJump(0x75); // jne go_to_next_iteration _.WriteUInt16(0x6a00, be); // push 0 invoke_sleep_milliseconds.WriteJump5Byte(0xe8); // call invoke_sleep_milliseconds _.WriteUInt24(0x83c404, be); // add esp,4 go_to_next_iteration.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt32(0x807f5400, be); // cmp byte ptr [edi+54h],0 outer_loop.WriteJump(0x74); // je outer_loop early_exit.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0x5b); // pop ebx _.WriteUInt8(0x5e); // pop esi _.WriteUInt16(0x33c0, be); // xor eax,eax _.WriteUInt8(0x5f); // pop edi _.WriteUInt16(0x8be5, be); // mov esp,ebp _.WriteUInt8(0x5d); // pop ebp _.WriteUInt8(0xc3); // ret region80.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Timing: Outer Loop"); } { _.Position = (long)mapper.MapRamToRom(region50b.Address); inner_loop.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt40(0xbb01000000, be); // mov ebx,1 ; remember that we've executed an inner_loop _.WriteUInt24(0x8d4f38, be); // lea ecx,[edi+38h] lock_mutex.WriteJump5Byte(0xe8); // call lock_mutex _.WriteUInt16(0x8bcf, be); // mov ecx,edi process_sound_queue.WriteJump5Byte(0xe8); // call unknown_func _.WriteUInt24(0x83fe21, be); // cmp esi,21h post_every_33_iterations.WriteJump(0x72); // jb post_every_33_iterations _.WriteUInt32(0x660f6ec6, be); // movd xmm0,esi _.WriteUInt24(0x0f5bc0, be); // cvtdq2ps xmm0,xmm0 _.WriteUInt16(0x8b17, be); // mov edx,dword ptr [edi] _.WriteUInt8(0x51); // push ecx _.WriteUInt16(0x8bcf, be); // mov ecx,edi _.WriteUInt32(0xf30f5e05, be); // divss xmm0,dword ptr ds:[8ED254h] _.WriteUInt32(a.InnerLoopDivss, le); _.WriteUInt40(0xf30f594758, be); // mulss xmm0,dword ptr [edi+58h] _.WriteUInt40(0xf30f110424, be); // movss dword ptr [esp],xmm0 _.WriteUInt24(0xff5268, be); // call dword ptr [edx+68h] _.WriteUInt24(0x83ee21, be); // sub esi,21h post_every_33_iterations.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt24(0x8d4f38, be); // lea ecx,[edi+38h] unlock_mutex.WriteJump5Byte(0xe8); // call unlock_mutex _.WriteUInt8(0x46); // inc esi exit_inner_loop.WriteJump5Byte(0xe9); // jmp exit_inner_loop region50b.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Timing: Inner Loop"); } { _.Position = (long)mapper.MapRamToRom(state.RegionD.Address); invoke_query_performance_frequency.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0x50); // push eax _.WriteUInt16(0xff15, be); // call dword ptr[QueryPerformanceFrequency] _.WriteUInt32(a.QueryPerformanceFrequency); _.WriteUInt8(0xc3); // ret state.RegionD.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Timing: QueryPerformanceFrequency"); } { _.Position = (long)mapper.MapRamToRom(state.Region32.Address); invoke_query_performance_counter.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0x50); // push eax _.WriteUInt16(0xff15, be); // call dword ptr[QueryPerformanceCounter] _.WriteUInt32(a.QueryPerformanceCounter); _.WriteUInt8(0xc3); // ret state.Region32.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Timing: QueryPerformanceCounter"); } { _.Position = (long)mapper.MapRamToRom(state.Region32.Address); do_compare.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0x53); // push ebx _.WriteUInt16(0x8bd8, be); // mov ebx,eax _.WriteUInt24(0x8b4104, be); // mov eax,dword ptr [ecx+4] _.WriteUInt8(0x56); // push esi _.WriteUInt16(0x8b31, be); // mov esi,dword ptr [ecx] _.WriteUInt16(0x0332, be); // add esi,dword ptr [edx] _.WriteUInt16(0x8bcb, be); // mov ecx,ebx _.WriteUInt24(0x134204, be); // adc eax,dword ptr [edx+4] _.WriteUInt24(0x3b4104, be); // cmp eax,dword ptr [ecx+4] success.WriteJump(0x7f); // jg success fail.WriteJump(0x7c); // jl fail _.WriteUInt16(0x3b31, be); // cmp esi,dword ptr [ecx] success.WriteJump(0x73); // jae success fail.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt40(0xb801000000, be); // mov eax,1 do_compare_end.WriteJump(0xeb); // jmp do_compare_end success.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt16(0x33c0, be); // xor eax,eax do_compare_end.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0x5e); // pop esi _.WriteUInt8(0x5b); // pop ebx _.WriteUInt8(0xc3); // ret state.Region32.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Timing: do_compare"); } lock_mutex.SetTarget(a.LockMutex); unlock_mutex.SetTarget(a.UnlockMutex); invoke_sleep_milliseconds.SetTarget(a.InvokeSleepMilliseconds); process_sound_queue.SetTarget(a.ProcessSoundQueue); allmul.SetTarget(a.AllMul); alldvrm.SetTarget(a.AllDvRm); // patch the bizarre and apparently intentional behavior that messes with the music timing when left shift or left ctrl are held _.Position = (long)mapper.MapRamToRom(a.MultiplierWhenLCrtlHeld); _.WriteUInt32(0x3f800000, le); _.Position = (long)mapper.MapRamToRom(a.MultiplierWhenLShiftHeld); _.WriteUInt32(0x3f800000, le); } }
public static void PatchMusicQueueingOnSoundThreadSide(Stream binary, Sen2ExecutablePatchState state) { var mapper = state.Mapper; var a = state.BgmTimingPatchLocations; state.InitCodeSpaceIfNeeded(binary); using (BranchHelper4Byte lock_mutex = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte unlock_mutex = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte inject_entry_point = new BranchHelper4Byte(binary, mapper)) using (BranchHelper4Byte enqueue_check = new BranchHelper4Byte(binary, mapper)) using (BranchHelper1Byte exit_continue = new BranchHelper1Byte(binary, mapper)) using (BranchHelper1Byte exit_skip = new BranchHelper1Byte(binary, mapper)) using (BranchHelper4Byte back_to_function = new BranchHelper4Byte(binary, mapper)) { EndianUtils.Endianness be = EndianUtils.Endianness.BigEndian; EndianUtils.Endianness le = EndianUtils.Endianness.LittleEndian; Stream _ = binary; lock_mutex.SetTarget(a.LockMutex); unlock_mutex.SetTarget(a.UnlockMutex); // ignore the check if the track is already playing on main thread side { _.Position = (long)mapper.MapRamToRom(a.BgmAlreadyPlayingJump); _.WriteUInt8(0xeb); // for testing, write command even if the tracked track is the same //_.Position = (long)mapper.MapRamToRom(0x57c836); //_.WriteUInt8(0x90); //_.WriteUInt8(0x90); } // on audio thread side, inject after the bgm play command is extracted from the ringbuffer // but before it is actually used to switch to the BGM { _.Position = (long)mapper.MapRamToRom(a.AddressOfSkipEnqueueOnSoundThreadSideInjection); inject_entry_point.WriteJump5Byte(0xe9); back_to_function.SetTarget(mapper.MapRomToRam((ulong)_.Position)); } { _.Position = (long)mapper.MapRamToRom(state.Region50a.Address); inject_entry_point.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt56(0xf74610ffffff7f, be); // test dword ptr[esi+10h],7FFFFFFFh enqueue_check.WriteJump6Byte(0x0f85); // jne enqueue_check _.WriteUInt16(0x8bce, be); // mov ecx,esi _.WriteUInt24(0xff5034, be); // call dword ptr[eax+34h] back_to_function.WriteJump5Byte(0xe9); // jmp back_to_function state.Region50a.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Queueing Audio Thread Side: Inject Entry"); } { _.Position = (long)mapper.MapRamToRom(state.Region60.Address); enqueue_check.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0x57); // push edi _.WriteUInt24(0x8d4e40, be); // lea ecx,[esi+40h] lock_mutex.WriteJump5Byte(0xe8); // call lock_mutex _.WriteUInt24(0x8b4614, be); // mov eax,dword ptr[esi+14h] _.WriteUInt32(0x8b4c241c, be); // mov ecx,dword ptr[esp+1Ch] _.WriteUInt24(0x8b3c88, be); // mov edi,dword ptr[eax+ecx*4h] _.WriteUInt16(0x8b07, be); // mov eax,dword ptr[edi] _.WriteUInt16(0x8bcf, be); // mov ecx,edi _.WriteUInt24(0x8b4010, be); // mov eax,dword ptr[eax+10h] _.WriteUInt16(0xffd0, be); // call eax _.WriteUInt16(0x84c0, be); // test al,al exit_continue.WriteJump(0x74); // je exit_continue _.WriteUInt24(0x8b4708, be); // mov eax,dword ptr[edi+8h] _.WriteUInt16(0x85c0, be); // test eax,eax exit_continue.WriteJump(0x74); // je exit_continue _.WriteUInt24(0x8b404c, be); // mov eax,dword ptr[eax+4Ch] _.WriteUInt48(0x8b8dccfeffff, be); // mov ecx,dword ptr[ebp-134h] _.WriteUInt16(0x3bc1, be); // cmp eax,ecx exit_continue.WriteJump(0x75); // jne exit_continue _.WriteUInt24(0x8a4738, be); // mov al,byte ptr[edi+38h] _.WriteUInt16(0x84c0, be); // test al,al exit_continue.WriteJump(0x75); // jne exit_continue _.WriteUInt8(0x5f); // pop edi _.WriteUInt24(0x83c41c, be); // add esp,1Ch exit_skip.WriteJump(0xeb); // jmp exit_skip exit_continue.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt8(0x5f); // pop edi _.WriteUInt16(0x8b06, be); // mov eax,dword ptr[esi] _.WriteUInt16(0x8bce, be); // mov ecx,esi _.WriteUInt24(0xff5034, be); // call dword ptr[eax+34h] exit_skip.SetTarget(mapper.MapRomToRam((ulong)_.Position)); _.WriteUInt24(0x8d4e40, be); // lea ecx,[esi+40h] unlock_mutex.WriteJump5Byte(0xe8); // call unlock_mutex back_to_function.WriteJump5Byte(0xe9); // jmp back_to_function state.Region60.TakeToAddress((long)mapper.MapRomToRam((ulong)_.Position), "BGM Queueing Audio Thread Side: Inject Main"); } } }