Closed t0mtee closed 2 months ago
First off, thanks for maintaining the fork so people can actually use it!
Let's start with the problem. The game receives a WM_DEVICECHANGE message from Windows, and decides to scan your hardware to see if there's a new controller by calling IDirectInput8::EnumDevices (steam hijacks this call, but that doesn't really matter). This call takes 100ms+ and blocks rendering which causes the stutter, and it's also unnecessary unless you plugged in a second controller. So we need to stop that somehow.
There's a bunch of ways but the cleanest, lowest-crash-risk option is to use a flag that's already there in all of the games. To get that you need a pointer to the structure (or class) and the offset within the structure.
The pointer moves every patch, while the offset changes more rarely. The pointer is accessed in a bunch of places; I picked one that doesn't change too much between patches and that's what the scan looks for.
For example:
eldenring.exe+1329D5 - 48 89 05 ACB17204 - mov [eldenring.exe+485DB88],rax <---- +485DB88 is our pointer
eldenring.exe+1329DC - 48 8B 05 A5B17204 - mov rax,[eldenring.exe+485DB88]
eldenring.exe+1329E3 - E8 08A6D901 - call eldenring.exe+1ECCFF0
eldenring.exe+1329E8 - 4C 8B 08 - mov r9,[rax]
eldenring.exe+1329EB - 41 B8 01000000 - mov r8d,00000001
eldenring.exe+1329F1 - 48 8D 15 E8010000 - lea rdx,[eldenring.exe+132BE0]
eldenring.exe+1329F8 - 48 8B C8 - mov rcx,rax
eldenring.exe+1329FB - 41 FF 51 08 - call qword ptr [r9+08]
eldenring.exe+1329FF - 48 8B 1D 82B17204 - mov rbx,[eldenring.exe+485DB88]
It takes a bit of math to get the value '485DB88'. The number in memory is AC B1 72 04, which is a relative offset to the next instruction at 1329DC and it's also in little endian. So the scan finds 1329D5, adds 3, reads the number 0472B1AC (note the apparent byte reversal), adds 4 to 1329D5 to get 1329DC, then finally adds 472B1AC to get 485DB88. This is probably horribly confusing if you're not used to x86 assembly, but it's really just doing the math your CPU does natively, manually.
There's a bit more, too: 'eldenring.exe' is the base address of the game in memory, and that changes every time you launch the game, but fortunately we don't need to worry about that.
The scan has a whole bunch of wildcards (??) because while this code has been stable across patches, it does change slightly.
There's another scan for the offset: 80B9 ????0000 00 48 8B5C24 40
which is a lot simpler:
eldenring.exe+1F28D46 - 80 B9 8B080000 00 - cmp byte ptr [rcx+0000088B],00
eldenring.exe+1F28D4D - 48 8B 5C 24 40 - mov rbx,[rsp+40]
The value '88B' is right there, just in the reversed byte order as 8B08. This is the actual code that decides whether to EnumDevices or not which is why we can trust it to have the right offset.
Hopefully that covers the stuff. The snippet you've got looks like the start of the structure; this will change every patch or maybe every launch and isn't all that useful.
So for the bigger question of "how do we make it patch safe", let's start with why it isn't already.
First, I wanted to make the code as short and fast as possible. Second, I didn't expect From to patch the game so much (oops). Third, I didn't know anyone cared enough for it to be worth the effort (double oops?) of working out reliable scans. I eventually did that for ertool anyway and just sort of forgot about the stutter fix.
So to actually do it, the simplest way, at least for the .exe version, would be to copy paste the essential parts from ertool. We'd need scans for DS3 and Sekiro too but that shouldn't be too hard. The .dll version would more work but other open source .dll mods have AOB scan code which we could copy paste.
Or, there's another scan/patch I've got which works on all games but is maybe more likely to crash. Or, the DLL version which already hooks directinput8 could patch EnumDevices to work when the game is starting up and just do nothing after that. This would work for all fromsoft games and maybe non-fromsoft games too but it might also cause controller issues for some people and it's more work. Or, if nobody cares about DS3 or Sekiro, I could just make a startup option for ertool that just applies the fix and then closes itself. I'm not good at making decisions so nothing has happened yet.
Do you happen to know which version is more popular, DLL or EXE?
Wow, thanks for the incredibly in-depth and helpful response. I am fairly new to assembly, having really only worked on this and a Tomb Raider mod, however it's something that is actually really interesting to me and I'm quite eager to learn more about, so the section on finding the '485DB88' value, while confusing, is helpful.
As for which version is more popular, the Nexus page speaks for itself really - the DLL version of each update is far, far more popular than the EXE version, to the point I may continue to just update the DLL version.
For DLL scanning, I've done some testing with using the mods here as a template, also using a slightly patched up version of the ModUtils.h there, which seems to be working decently - just need to have it search for the right AOBs / have it do the same thing ertool does.
Question - could you explain what the array of bytes called "buf" (short for buffer I assume) does? It seems like a copy of some of the game's memory from my semi-educated guess.
The ertool aob scan first makes a copy of the two .text sections (which contain the actual game code) for faster scanning, but a DLL mod doesn't need to do that, and if you're using another mod as a base then all the hard work is done already.
We could probably discuss this a lot faster on discord. If you're not in the modding discord I suggest you join it: https://discord.gg/mT2JJjx
@t0mtee Check out https://github.com/kh0nsu/FromStutterFix when you have a moment. It's not final yet but there's a new version up which does a scan which should work with all the games and patches.
Wow, this looks great! Was not expecting this haha. If it's done, would you like me to upload it to the Nexus page, changing all the credit and links to point to your repo instead - if you'd rather make your own Nexus page, that's understandable and fine by me :)
I wasn't expecting it either haha, just suddenly got the motivation. That all sounds fine. I don't really use nexus and it's easier for existing users if the page stays the same.
Just consider that I've spent a grand total of 10 minutes testing this version, if you could get a few others to try it first that'd be great!
Sounds good! I'll be going away until Saturday, however once I'm back I plan on testing the mod in all ways that I can - and I can definitely pass it to some others to try too.
Thanks a ton for all of this. It seems lots of people are really benefiting from this issue you've fixed and it's all down to you.
I think this can be closed now?
Hi there!
I'm the maintainer of FromStutterFixUpdated, and I'd like to add some functionality so that the mod can find the pointers by itself to make it more patch-proof. To do this, I was wondering if I could get an explanation of how this tool finds those pointers - looking at the source code it uses the AOB
48 8905 ???????? 48 8B05 ???????? E8 ???????? 4C 8B08 41 B8 ??000000 48 8D15 ????0000 48 8BC8 41 FF51 ?? 48 8B1D ????????
in some way, however it seems to search for these values in a byte array, then gives the index of that and does some stuff with it to give the final address. That "stuff" is what is confusing me - how does the byte array relate to the game's memory, and what do the other values that get added to the index do.What I have managed to do is simply take the address that the tool finds and use Cheat Engine to see the memory at that location, take a snippet of it (
90 96 fd e6 f7 7f 00 00 00 26 fd e6 f7 7f 00 00 ff ff ff ff ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 d0 07 00 02 00 00 00 00 40 40 76 e8 f7 7f
) and put it into my mod - which works, however I'm just not sure how update-proof or interachangable between games this is so I feel uncomfortable leaving it like this.If you could help me out I would be very grateful, thank you for reading.