From 99b3923ff4dba8d0be96cbd39278993602326c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Andr=C3=A9?= Date: Thu, 30 Mar 2023 12:01:28 -0300 Subject: [PATCH] Support for Modded Softpal Engine Leyline was released with a manual text hook that applies the wordwrap in the text, but since o SRL do wordwrap too, the leyline patch, who do the wordwrap after the SRL, didn't check if the line is already with wordwrap and tries again, breaking everything, this update fix that by detecting the wordwrap patch and hooking after the game wordwrap is done. --- StringReloads/AutoInstall/SoftPalMethodA.cs | 130 ++++++++++++++---- StringReloads/Engine/Config.cs | 5 +- StringReloads/Engine/SRL.cs | 10 +- StringReloads/Engine/Unmanaged/Alloc.cs | 85 +++++++++++- StringReloads/Hook/Base/Intercept.cs | 12 +- .../Hook/Others/ManagedInterceptor.cs | 16 +++ .../Others/SoftPal_PalSpriteCreateText.cs | 26 ++++ StringReloads/Hook/Types/Delegates.cs | 2 + StringReloads/SRL.ini | 1 + 9 files changed, 248 insertions(+), 39 deletions(-) create mode 100644 StringReloads/Hook/Others/ManagedInterceptor.cs create mode 100644 StringReloads/Hook/Others/SoftPal_PalSpriteCreateText.cs diff --git a/StringReloads/AutoInstall/SoftPalMethodA.cs b/StringReloads/AutoInstall/SoftPalMethodA.cs index f5e135f..9da6a6e 100644 --- a/StringReloads/AutoInstall/SoftPalMethodA.cs +++ b/StringReloads/AutoInstall/SoftPalMethodA.cs @@ -10,6 +10,7 @@ using StringReloads.Engine.String; using StringReloads.AutoInstall.Patcher; using StringReloads.Engine.Interface; +using Iced.Intel; namespace StringReloads.AutoInstall { @@ -18,7 +19,8 @@ unsafe class SoftPalMethodA : IAutoInstall bool SetupMode = false; CallerTracer Tracer; - Interceptor Intercepter; + Intercept Intercepter; + SoftPal_PalSpriteCreateText FontDrawTextHook; Config Config => Config.Default; Dictionary SoftPalConfig; @@ -39,9 +41,13 @@ public void Install() } StackOffset = SoftPalConfig["stackoffset"].ToUInt32(); + FromEBP = SoftPalConfig["fromebp"].ToBoolean(); void* hFunc = (void*)((int)Config.GameBaseAddress + SoftPalConfig["hookoffset"].ToInt32()); - Intercepter = new Interceptor(hFunc, new InterceptDelegate(DrawTextHook)); + Intercepter = new ManagedInterceptor(hFunc, new ManagedInterceptDelegate(DrawTextHook)); Intercepter.Install(); + + FontDrawTextHook = new SoftPal_PalSpriteCreateText(); + FontDrawTextHook.Install(); } public bool IsCompatible() @@ -96,10 +102,15 @@ public bool IsCompatible() public void Uninstall() { Intercepter.Uninstall(); + FontDrawTextHook.Uninstall(); } - void DrawTextHook(void* Stack) { - uint* Address = ((uint*)Stack) + StackOffset; + void DrawTextHook(ref ulong ESP, ref ulong EAX, ref ulong ECX, ref ulong EDX, ref ulong EBX, ref ulong EBP, ref ulong ESI, ref ulong EDI) + { + uint* BasePtr = (FromEBP ? ((uint*)EBP) : ((uint*)ESP)); + + uint* Address = BasePtr + StackOffset; + *Address = (uint)EntryPoint.Process((void*)*Address); } @@ -121,6 +132,48 @@ void SetupStepA(void* Caller) { Log.Debug($"PalFontDrawText Referenced by 0x{(uint)hFunc:X8}"); + + var MemReader = new MemoryCodeReader(hFunc); + var Diassembler = Decoder.Create(32, MemReader); + for (int i = 0, x = 0; i < 30; i++) + { + var CurrentOffset = Diassembler.IP; + var Instruction = Diassembler.Decode(); + switch (x) + { + case 0: + if (Instruction.IsCallNear && Instruction.IPRelativeMemoryAddress == Diassembler.IP) + x++; + break; + case 1: + if (Instruction.Code == Code.Pop_r32) + x++; + else + x = 0; + break; + case 2: + if (Instruction.Code == Code.Add_EAX_imm32) + x++; + else + x = 0; + break; + case 3: + if (Instruction.IsJmpNearIndirect) + x++; + else + x = 0; + break; + case 4: + if (Instruction.Code == Code.Nopd) + break; + + Log.Debug("SoftPAL Wordwrap Patched Engine Detected"); + hFunc = (byte*)hFunc + CurrentOffset; + x++; + break; + } + } + SoftPalConfig = new Dictionary(); SoftPalConfig["HookOffset"] = ((uint)hFunc - (uint)Config.GameBaseAddress).ToString(); SoftPalConfig["EngineSize"] = new FileInfo(Config.GameExePath).Length.ToString(); @@ -128,15 +181,17 @@ void SetupStepA(void* Caller) { Tracer.Uninstall(); - Intercepter = new Interceptor(hFunc, new InterceptDelegate(SetupStepB)); + Intercepter = new ManagedInterceptor(hFunc, new ManagedInterceptDelegate(SetupStepB)); Intercepter.Install(); ShowMessageBox($"Very well, looks like SRL can perform Auto-Install in this game!\nSRL now needs to gather more intricate information from the game.\nPlease press OK and continue to the next in-game dialogue.", "StringReloads Setup Wizard", MBButtons.Ok, MBIcon.Information); } + bool FromEBP; int LastOffset = 0; bool WaitingConfirmation = false; bool FirstTry = true; - void SetupStepB(void* ESP) { + void SetupStepB(ref ulong ESP, ref ulong EAX, ref ulong ECX, ref ulong EDX, ref ulong EBX, ref ulong EBP, ref ulong ESI, ref ulong EDI) + { if (FirstTry) ShowMessageBox("SRL will now need your help to confirm if the dialogue has been found.\nThe program will display the game dialogue; when the correct dialogue is shown, press YES. If nothing is shown or if you see corrupted text, press NO.\nTake note that if you don't set the proper game encoding inside SRL.ini, the correct dialogue will most likely never be shown.\nPress OK if you have read and understood the above.", "StringReloads Setup Wizard", MBButtons.Ok, MBIcon.Information); @@ -152,36 +207,33 @@ void SetupStepB(void* ESP) { } uint* Stack = (uint*)ESP; + uint* BaseStack = (uint*)EBP; int StackOffset = LastOffset; - while (StackOffset < 50) { + while (StackOffset < 80) + { var RStack = (Stack + StackOffset); - if (IsBadCodePtr((void*)*RStack)) { - Log.Debug($"Bad Pointer: 0x{*RStack:X8}"); - StackOffset++; - continue; + var BStack = (BaseStack + StackOffset); + + if (GuessOffset(RStack, StackOffset)) + { + FromEBP = false; + break; } - Log.Debug($"Guessing Offset: 0x{(uint)RStack:X8} (+{StackOffset}) (0x{*RStack:X8})"); - CString Str = (byte*)*RStack; - if (Str.Count() > 0 && Str.Count() < 500) + if (GuessOffset(BStack, StackOffset)) { - var Reply = ShowMessageBox(Str, "Is this the dialogue?", MBButtons.YesNo, MBIcon.Question); - if (Reply == MBResult.Yes) - { - Str = "Looks Like everything is working,
Continue to the next game dialogue!"; - *RStack = (uint)(void*)Str; - WaitingConfirmation = true; - break; - } + FromEBP = true; + break; } StackOffset++; continue; } + LastOffset = StackOffset; - if (StackOffset == 50) + if (StackOffset == 60) SetupFailed(); @@ -191,20 +243,46 @@ void SetupStepB(void* ESP) { } } + private bool GuessOffset(uint* Address, int Offset) + { + if (IsBadCodePtr((void*)*Address)) + { + return false; + } + + Log.Debug($"Guessing Offset: 0x{(uint)Address:X8} (+{StackOffset}) (0x{*Address:X8})"); + CString Str = (byte*)*Address; + if (Str.Count() > 0 && Str.Count() < 500) + { + var Reply = ShowMessageBox(Str, "Is this the dialogue?", MBButtons.YesNo, MBIcon.Question); + if (Reply == MBResult.Yes) + { + Str = "Looks Like everything is working,
Continue to the next game dialogue!"; + *Address = (uint)(void*)Str; + WaitingConfirmation = true; + return true; + } + } + + return false; + } + private void FinishSetup() { SoftPalConfig["StackOffset"] = LastOffset.ToString(); + SoftPalConfig["FromEBP"] = FromEBP ? "true" : "false"; + if (Config.BreakLine != "
") { var Rst = ShowMessageBox("Looks like you aren't using the tag
as breakline rigth now, You want use it?", "StringReloader Setup Wizard", MBButtons.YesNo, MBIcon.Question); if (Rst == MBResult.Yes) Config.SetValue("BreakLine", "
"); } - if (!Config.Overwrite) + if (!Config.SafeOverwrite) { - var Rst = ShowMessageBox("It looks like you are not in memory overwrite mode, which is probably needed for this game, do you want to enable memory overwrite mode?", "StringReloader Setup Wizard", MBButtons.YesNo, MBIcon.Question); + var Rst = ShowMessageBox("It looks like you are not in safe memory overwrite mode, which is probably needed for this game, do you want to enable safe memory overwrite mode?", "StringReloader Setup Wizard", MBButtons.YesNo, MBIcon.Question); if (Rst == MBResult.Yes) - Config.SetValue("Overwrite", "true"); + Config.SetValue("SafeOverwrite", "true"); } Config.SetValues("SoftPal", SoftPalConfig); diff --git a/StringReloads/Engine/Config.cs b/StringReloads/Engine/Config.cs index 5bb63ad..88408b2 100644 --- a/StringReloads/Engine/Config.cs +++ b/StringReloads/Engine/Config.cs @@ -82,7 +82,10 @@ public string[] IniLines { public bool HeapAlloc => ((bool?)(_HeapAlloc ??= GetValue("HeapAlloc").ToBoolean())).Value; bool? _Overwrite = null; - public bool Overwrite => ((bool?)(_Overwrite ??= GetValue("Overwrite").ToBoolean())).Value; + public bool Overwrite => ((bool?)(_Overwrite ??= GetValue("Overwrite").ToBoolean())).Value && !SafeOverwrite; + + bool? _SafeOverwrite = null; + public bool SafeOverwrite => ((bool?)(_SafeOverwrite ??= GetValue("SafeOverwrite").ToBoolean())).Value; bool? _ReloadRegexCaptures = null; public bool ReloadRegexCaptures => ((bool?)(_ReloadRegexCaptures ??= GetValue("ReloadRegexCaptures").ToBoolean())).Value; diff --git a/StringReloads/Engine/SRL.cs b/StringReloads/Engine/SRL.cs index f287ce5..1e4fce4 100644 --- a/StringReloads/Engine/SRL.cs +++ b/StringReloads/Engine/SRL.cs @@ -142,7 +142,10 @@ public Database CurrentDatabase { } if (Settings.Overwrite && Output != null && ((string)Output) != null) - return (CString)Alloc.Overwrite(Output.ToArray(), pString, pString.Count()); + return Alloc.Overwrite(Output, pString); + + if (Settings.SafeOverwrite && Output != null && ((string)Output) != null) + return Alloc.SafeOverwrite(Output, pString); if (Settings.HeapAlloc && Output != null && ((string)Output) != null) return (CString)Alloc.CreateHeap(Output.ToArray()); @@ -177,7 +180,10 @@ public Database CurrentDatabase { } if (Settings.Overwrite && Output != null && ((string)Output) != null) - return (WCString)Alloc.Overwrite(Output.ToArray(), pString, pString.Count()); + return Alloc.Overwrite(Output, pString); + + if (Settings.SafeOverwrite && Output != null && ((string)Output) != null) + return Alloc.SafeOverwrite(Output, pString); if (Settings.HeapAlloc && Output != null && ((string)Output) != null) return (WCString)Alloc.CreateHeap(Output.ToArray()); diff --git a/StringReloads/Engine/Unmanaged/Alloc.cs b/StringReloads/Engine/Unmanaged/Alloc.cs index bc0afc7..13ff891 100644 --- a/StringReloads/Engine/Unmanaged/Alloc.cs +++ b/StringReloads/Engine/Unmanaged/Alloc.cs @@ -1,4 +1,7 @@ -using System; +using Antlr.Runtime.Tree; +using StringReloads.Engine.String; +using System; +using System.Linq; using System.Runtime.InteropServices; namespace StringReloads.Engine.Unmanaged @@ -13,13 +16,87 @@ unsafe static class Alloc return hAlloc; } - public static void* Overwrite(byte[] Data, void* Address, int OriginalSize) { + public static CString Overwrite(CString String, CString OriString) + { + var Data = String.ToArray(); + + Array.Resize(ref Data, Data.Length + 1);//Null Termination + + var OriginalSize = OriString.Count(); + + return OveriteInternal(Data, OriString, OriginalSize); + } + + public static WCString Overwrite(WCString String, WCString OriString) + { + var Data = String.ToArray(); + + Array.Resize(ref Data, Data.Length + 2);//Null Termination + + var OriginalSize = OriString.Count(); + + return OveriteInternal(Data, OriString, OriginalSize); + } + + private static void* OveriteInternal(byte[] Data, void* OriString, int OriginalSize) + { + if (Data.Length < OriginalSize) + { + Array.Resize(ref Data, OriginalSize); + } + + Marshal.Copy(Data, 0, new IntPtr(OriString), Data.Length); + return OriString; + } + + public static CString SafeOverwrite(CString String, CString OriString) + { + var Data = String.ToArray(); + + Array.Resize(ref Data, Data.Length + 1);//Null Termination + + var OriginalSize = OriString.Count(); + + return SafeOveriteInternal(Data, String, OriString, OriginalSize); + } + + public static WCString SafeOverwrite(WCString String, WCString OriString) + { + var Data = String.ToArray(); + + Array.Resize(ref Data, Data.Length + 2);//Null Termination + + var OriginalSize = OriString.Count(); + + return SafeOveriteInternal(Data, String, OriString, OriginalSize); + } + + private static void* SafeOveriteInternal(byte[] Data, void* String, void* OriString, int OriginalSize) + { if (Data.Length < OriginalSize) { Array.Resize(ref Data, OriginalSize); } - Marshal.Copy(Data, 0, new IntPtr(Address), Data.Length); - return Address; + + if (Data.Length > OriginalSize) + { + byte* pOriEnd = (byte*)OriString + OriginalSize; + + int EmptyBufferSize = 0; + while (*pOriEnd == 0) + { + EmptyBufferSize++; + pOriEnd++; + } + + if (EmptyBufferSize + OriginalSize < Data.Length) + { + return String; + } + } + + Marshal.Copy(Data, 0, new IntPtr(OriString), Data.Length); + return OriString; } [DllImport("kernel32.dll", SetLastError = true)] diff --git a/StringReloads/Hook/Base/Intercept.cs b/StringReloads/Hook/Base/Intercept.cs index 52a7e0b..f2f9d51 100644 --- a/StringReloads/Hook/Base/Intercept.cs +++ b/StringReloads/Hook/Base/Intercept.cs @@ -2,8 +2,6 @@ using Iced.Intel; using System.Runtime.InteropServices; using static StringReloads.Hook.Base.Extensions; -using StringReloads.Engine; -using System.Runtime.Remoting.Contexts; namespace StringReloads.Hook.Base { @@ -31,7 +29,9 @@ public void Compile(void* Address) } - public virtual InterceptDelegate HookFunction { get => InterceptManager; } + private InterceptDelegate _HookDelegate; + + public virtual InterceptDelegate HookFunction { get => _HookDelegate ??= new InterceptDelegate(InterceptManager); } public virtual ManagedInterceptDelegate ManagedHookFunction { get => null; } void* Address; @@ -120,7 +120,7 @@ public void Uninstall() void InterceptManager(void* ESP) { uint* Stack = (uint*)ESP; - + ulong EDI = Stack[0]; ulong ESI = Stack[1]; ulong EBP = Stack[2]; @@ -129,7 +129,7 @@ void InterceptManager(void* ESP) ulong ECX = Stack[6]; ulong EAX = Stack[7]; - var OriESP = (void**)Stack[3]; + ulong OriESP = Stack[3]; ManagedHookFunction(ref OriESP, ref EAX, ref ECX, ref EDX, ref EBX, ref EBP, ref ESI, ref EDI); @@ -149,6 +149,6 @@ void InterceptManager(void* ESP) [UnmanagedFunctionPointer(CallingConvention.StdCall)] public unsafe delegate void InterceptDelegate(void* ESP); - public unsafe delegate void ManagedInterceptDelegate(ref void** ESP, ref ulong EAX, ref ulong ECX, ref ulong EDX, ref ulong EBX, ref ulong EBP, ref ulong ESI, ref ulong EDI); + public unsafe delegate void ManagedInterceptDelegate(ref ulong ESP, ref ulong EAX, ref ulong ECX, ref ulong EDX, ref ulong EBX, ref ulong EBP, ref ulong ESI, ref ulong EDI); } diff --git a/StringReloads/Hook/Others/ManagedInterceptor.cs b/StringReloads/Hook/Others/ManagedInterceptor.cs new file mode 100644 index 0000000..761d869 --- /dev/null +++ b/StringReloads/Hook/Others/ManagedInterceptor.cs @@ -0,0 +1,16 @@ +using StringReloads.Hook.Base; + +namespace StringReloads.Hook +{ + unsafe class ManagedInterceptor : Intercept + { + ManagedInterceptDelegate HookDelegate; + public ManagedInterceptor(void* Address, ManagedInterceptDelegate Action) + { + HookDelegate = Action; + Compile(Address); + } + + public override ManagedInterceptDelegate ManagedHookFunction => HookDelegate; + } +} diff --git a/StringReloads/Hook/Others/SoftPal_PalSpriteCreateText.cs b/StringReloads/Hook/Others/SoftPal_PalSpriteCreateText.cs new file mode 100644 index 0000000..cc8b935 --- /dev/null +++ b/StringReloads/Hook/Others/SoftPal_PalSpriteCreateText.cs @@ -0,0 +1,26 @@ +using StringReloads.Engine.String; + +namespace StringReloads.Hook +{ + unsafe class SoftPal_PalSpriteCreateText : Base.Hook + { + public override string Library => "pal.dll"; + + public override string Export => "PalSpriteCreateText"; + + public override void Initialize() + { + HookDelegate = new SoftPAL_PalSpriteCreateTextDelegate(PalSpriteCreateText); + Compile(); + } + + void* PalSpriteCreateText(int a1, byte* Text, int* a3, int* a4) + { + if (a1 != 0 && Text != null) + { + Text = (byte*)EntryPoint.Process(Text); + } + return Bypass(a1, Text, a3, a4); + } + } +} diff --git a/StringReloads/Hook/Types/Delegates.cs b/StringReloads/Hook/Types/Delegates.cs index cecb486..c6b5153 100644 --- a/StringReloads/Hook/Types/Delegates.cs +++ b/StringReloads/Hook/Types/Delegates.cs @@ -48,6 +48,8 @@ namespace StringReloads.Hook [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public unsafe delegate void SoftPAL_DrawTextDelegate(byte* Text, void* a2, void* a3, void* a4, void* a5, void* a6, void* a7, void* a8, void* a9, void* a10, void* a11, void* a12, void* a13, void* a14, void* a15, void* a16, void* a17, void* a18, void* a19, void* a20, void* a21, void* a22, void* a23); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void* SoftPAL_PalSpriteCreateTextDelegate(int a1, byte* Text, int* a3, int* a4); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public unsafe delegate void* CMVS_GetTextDelegate(void* hScript, int ID); diff --git a/StringReloads/SRL.ini b/StringReloads/SRL.ini index 57db18b..4d7ba45 100644 --- a/StringReloads/SRL.ini +++ b/StringReloads/SRL.ini @@ -16,6 +16,7 @@ Log=true LogFile=false LogLevel=INF Overwrite=false +SafeOverwrite=false ReloadRegexCaptures=true SanityCheck=true TrimMatch=false