Beispiel #1
0
        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 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);
                                                                                                                }
        }
Beispiel #3
0
        public static void PatchDisablePauseOnFocusLoss(Stream bin, Sen2ExecutablePatchState state)
        {
            bool jp = state.IsJp;

            // change dinput keyboard SetCooperativeLevel from DISCL_NONEXCLUSIVE | DISCL_FOREGROUND to DISCL_NONEXCLUSIVE | DISCL_BACKGROUND
            //bin.Position = state.Mapper.MapRamToRom(jp ? 0 : 0x7faf07);
            //bin.WriteUInt8(0x0a);

            // 0x413660 -> this branch is false when game is inactive, which skips most of the game update logic
            // 0x413A10 -> GameHasFocusCached() or something along those lines?
            // 0x412520 -> corresponding setter to the above
            // 0x4161BE -> called from here
            // 0x415cc0 -> function registered as the window procedure in SetWindowLongA()

            // don't silence audio output when unfocused
            using (var branch = new BranchHelper4Byte(bin, state.Mapper)) {
                bin.Position = state.Mapper.MapRamToRom(jp ? 0x4161b1 : 0x4161c1);
                branch.WriteJump5Byte(0xe9);
                branch.SetTarget(jp ? 0x4161f9u : 0x416209u);
            }

            // still run main game loop when unfocused
            bin.Position = state.Mapper.MapRamToRom(jp ? 0x413650 : 0x413660);
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop

            // avoid processing mouse clicks when unfocused
            // (this previously happened only implicitly because the game didn't run...)
            // carve out some now-unused code space
            uint codespaceStart = jp ? 0x4161b6u : 0x4161c6u;
            uint codespaceEnd   = jp ? 0x4161f9u : 0x416209u;
            var  codespace      = new RegionHelper(codespaceStart, codespaceEnd - codespaceStart, "Pause on Focus Loss: Codespace Region");

            bin.Position = state.Mapper.MapRamToRom(codespace.Address);
            for (uint i = 0; i < codespace.Remaining; ++i)
            {
                bin.WriteUInt8(0xcc);                 // int 3
            }

            // and assemble some logic to skip mouse button processing when unfocused
            using (var jump_to_codespace = new BranchHelper4Byte(bin, state.Mapper))
                using (var back_to_function = new BranchHelper4Byte(bin, state.Mapper))
                    using (var skip_processing = new BranchHelper1Byte(bin, state.Mapper)) {
                        var be = EndianUtils.Endianness.BigEndian;

                        back_to_function.SetTarget(jp ? 0x6ad622u : 0x6ae652u);
                        bin.Position = state.Mapper.MapRamToRom(jp ? 0x6ad61e : 0x6ae64e);
                        ulong GetKeyStateAddress = bin.ReadUInt32(be);
                        ulong GameStateAddress   = (jp ? 0x116f3c0u : 0x1173c40u).ToEndian(be);

                        // CS2 has this copy-pasted 5 times for the 5 supported mouse buttons so inject in all of them...
                        for (int i = 0; i < 5; ++i)
                        {
                            bin.Position = state.Mapper.MapRamToRom((jp ? 0x6ad61b : 0x6ae64b) + i * 0x11);
                            jump_to_codespace.WriteJump5Byte(0xe9);                // jmp jump_to_codespace
                            bin.WriteUInt8(0x90);                                  // nop
                            bin.WriteUInt8(0x90);                                  // nop
                        }

                        bin.Position = state.Mapper.MapRamToRom(codespace.Address);
                        jump_to_codespace.SetTarget(codespace.Address);
                        bin.WriteUInt48(0x8b0d00000000u | GameStateAddress, be);           // mov ecx,[static_address_that_holds_game_state]
                        bin.WriteUInt16(0x85c9, be);                                       // test ecx,ecx
                        skip_processing.WriteJump(0x74);                                   // jz skip_processing
                        bin.WriteUInt48(0x8a89c4090000, be);                               // mov cl,byte ptr[ecx+9c4h]   ; cl now holds 0 if unfocused, 1 if focused
                        bin.WriteUInt16(0x84c9, be);                                       // test cl,cl
                        skip_processing.WriteJump(0x74);                                   // jz skip_processing
                        bin.WriteUInt48(0x8b0d00000000u | GetKeyStateAddress, be);         // mov ecx,[USER32.DLL:GetKeyState]
                        bin.WriteUInt16(0x85c9, be);                                       // test ecx,ecx
                        skip_processing.WriteJump(0x74);                                   // jz skip_processing
                        bin.WriteUInt8(0x50);                                              // push eax
                        bin.WriteUInt16(0xffd1, be);                                       // call ecx
                        back_to_function.WriteJump5Byte(0xe9);                             // jmp back_to_function
                        skip_processing.SetTarget(state.Mapper.MapRomToRam((ulong)bin.Position));
                        bin.WriteUInt16(0x33c0, be);                                       // xor eax,eax
                        back_to_function.WriteJump5Byte(0xe9);                             // jmp back_to_function

                        codespace.TakeToAddress(state.Mapper.MapRomToRam(bin.Position), "Pause on Focus Loss: don't process mouse clicks");
                    }
        }
        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 PatchDisablePauseOnFocusLoss(Stream bin, Sen1ExecutablePatchState state)
        {
            bool jp = state.IsJp;

            // 0x444AA0 -> game active setter
            // 0x444AF0 -> game active getter

            // don't silence audio output when unfocused
            using (var branch = new BranchHelper4Byte(bin, state.Mapper)) {
                bin.Position = state.Mapper.MapRamToRom(jp ? 0x447d6a : 0x447eba);
                branch.WriteJump5Byte(0xe9);
                branch.SetTarget(jp ? 0x447da6u : 0x447ef6u);
            }

            // still run main game loop when unfocused
            bin.Position = state.Mapper.MapRamToRom(jp ? 0x441c00 : 0x441d50);
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop
            bin.WriteUInt8(0x90);             // nop

            // avoid processing mouse clicks when unfocused
            // (this previously happened only implicitly because the game didn't run...)
            // carve out some now-unused code space
            uint codespaceStart = jp ? 0x447d6fu : 0x447ebfu;
            uint codespaceEnd   = jp ? 0x447da4u : 0x447ef4u;
            var  codespace      = new RegionHelper(codespaceStart, codespaceEnd - codespaceStart, "Pause on Focus Loss: Codespace Region");

            bin.Position = state.Mapper.MapRamToRom(codespace.Address);
            for (uint i = 0; i < codespace.Remaining; ++i)
            {
                bin.WriteUInt8(0xcc);                 // int 3
            }

            // and assemble some logic to skip mouse button processing when unfocused
            using (var jump_to_codespace = new BranchHelper4Byte(bin, state.Mapper))
                using (var back_to_function = new BranchHelper4Byte(bin, state.Mapper))
                    using (var skip_processing = new BranchHelper1Byte(bin, state.Mapper)) {
                        var be = EndianUtils.Endianness.BigEndian;

                        back_to_function.SetTarget(jp ? 0x479ad8u : 0x47b348u);
                        bin.Position = state.Mapper.MapRamToRom(jp ? 0x479ad4u : 0x47b344);
                        ulong GetKeyStateAddress = bin.ReadUInt32(be);
                        ulong GameStateAddress   = (jp ? 0x1361c28u : 0x01363fc8u).ToEndian(be);

                        bin.Position = state.Mapper.MapRamToRom(jp ? 0x479ad1 : 0x47b341);
                        jump_to_codespace.WriteJump5Byte(0xe9);            // jmp jump_to_codespace
                        bin.WriteUInt8(0x90);                              // nop
                        bin.WriteUInt8(0x90);                              // nop

                        bin.Position = state.Mapper.MapRamToRom(codespace.Address);
                        jump_to_codespace.SetTarget(codespace.Address);
                        bin.WriteUInt48(0x8b0d00000000u | GameStateAddress, be);           // mov ecx,[static_address_that_holds_game_state]
                        bin.WriteUInt16(0x85c9, be);                                       // test ecx,ecx
                        skip_processing.WriteJump(0x74);                                   // jz skip_processing
                        bin.WriteUInt48(0x8a89b8070000, be);                               // mov cl,byte ptr[ecx+7b8h]   ; cl now holds 0 if unfocused, 1 if focused
                        bin.WriteUInt16(0x84c9, be);                                       // test cl,cl
                        skip_processing.WriteJump(0x74);                                   // jz skip_processing
                        bin.WriteUInt48(0x8b0d00000000u | GetKeyStateAddress, be);         // mov ecx,[USER32.DLL:GetKeyState]
                        bin.WriteUInt16(0x85c9, be);                                       // test ecx,ecx
                        skip_processing.WriteJump(0x74);                                   // jz skip_processing
                        bin.WriteUInt8(0x50);                                              // push eax
                        bin.WriteUInt16(0xffd1, be);                                       // call ecx
                        back_to_function.WriteJump5Byte(0xe9);                             // jmp back_to_function
                        skip_processing.SetTarget(state.Mapper.MapRomToRam((ulong)bin.Position));
                        bin.WriteUInt16(0x33c0, be);                                       // xor eax,eax
                        back_to_function.WriteJump5Byte(0xe9);                             // jmp back_to_function

                        codespace.TakeToAddress(state.Mapper.MapRomToRam(bin.Position), "Pause on Focus Loss: don't process mouse clicks");
                    }
        }
        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");
                                        }
                                    }
        }