EldenRingMods
EldenRingMods copied to clipboard
"Could not find signature" issues when loading DLLs with Mod Engine 2
Turns out ME2 has an undocumented, working DLL loader:
Under [modengine]
put external_dlls = [ "dllname1.dll", "dllname2.dll" ]
etc. (If providing folder paths, use double slashes \\
)
Looks like your mods don't play too well with this, though.
This way of loading mods is increasingly popular, given that EML makes it a chore to boot up the Vanilla game with having to remove the dinput8.dll and all. Metis Mod Launcher makes use of Mod Engine 2 standalone for loading DLLs, which leaves the vanilla folder unmodified.
I hope you will be able to find some time to consult with the community irt how your hooking mechanism can be improved to work with ME2 (a few people have told me that ME2 loading isn't "the problem" per se, but rather something about the hooking in your mods), as this is affecting a good amount of users by now. Chainfailure, Nordgaren, sfix or katalash can probably advise you; both Nord and Chain have made DLLs which hook into both EML and ME2 just fine.
Assorted comments:
at least one of the mods always can't find the signature
yes its always the same one depending onn load order so for example if i load skip intro, remove chromatic, and ultrawide fix in that order, then remove chromatic will allways not load if i switch up the load order some other dll will not load
"Is it always the same one?"
no in the ultrawide fix, skip intro, chromatic load order ultrawide fix doesnt find signatures
it works fine if its just one dll and if its two you swap them until you find the right order
but at 3 dlls its impossible to not have one of the dlls fail
there is no combination of ultrawidefix, skiptheintro, and removechromaticaberration that does not result in at least one of the dlls not loading
Looking into this, I have found that it seems to be an issue of time? I replaced SigScan with a version of it that, instead of calling VirtualQuery, just scans the entire mapped game image by reading the PE header and setting the start and end region.
This caused all the mods to find their signature.
I also removed the pattern conversion from byte array to string, in the SigScan method. I can try replacing it and see what happens, but likely this takes a lot of time, as well, as it is probably a lot of allocations. Could try using patternString.reserve(pattern.size() * 3);
to turn the entire process into a single allocation. Or could make it a debug build only thing.
SkipTheIntro is, unfortunately, incompatible with loading in ModEngine2, as ME2 doesn't have a priority load feature for extensions, and it doesn't load the module fast enough for it to be effective.
I can PR the new method of scanning, if you'd like. I can also PR you my SigScanner from ErdTools, if you wanna use that. It's not perfect, but it does seem to scan quick enough, as well as convert from string to byte array, so you could print out the string version a lot easier. I have quite a few signatures in ErdTools.
P.S. I wouldn't bother you with this if these weren't popular mods, but a lot of people DO use them.
All good brother, I will take a look this weekend to see what can be done.
I've never actually used ME2, so I don't have any insight into the issues. I've always wanted to take the loose file loading from ME2 and put it into EML, so I can have full control of what's going on. But probably that would take too much time for me to bother doing it, I'm not familiar with the rabbit hole of souls file formats and such. I'll see what I can do though.
All good brother, I will take a look this weekend to see what can be done.
I've never actually used ME2, so I don't have any insight into the issues. I've always wanted to take the loose file loading from ME2 and put it into EML, so I can have full control of what's going on. But probably that would take too much time for me to bother doing it, I'm not familiar with the rabbit hole of souls file formats and such. I'll see what I can do though.
Alright. No worries. If I can help, at all, let me know!
the issue happens when trying to use multiple mods. example: intro skip, remove vignette and remove chromatic abberation, etc
Seems to simply be an issue of ME2 loading the mods too quickly. Not sure how this is affected by having 1, 2 or 3+ mods installed, or why getting the image size from the PE header yields different results compared to using VirtualQuery. Perhaps loading a certain amount of DLLs is increasing the load time of the game a tad too much, causing the game memory to not be in the state the mods are expecting when there is no forced delay.
I'll see if I will bother adding loose file loading to EML (looking at ME2 source code, it looks somewhat digestible), or simply implement my mod loading into ME2.
ME2 loads dlls as soon as it starts eldenring.exe
, and does not have a delay load option like ModLoader.
Just add a single Sleep(xxxx)
before mod's sigscan to avoid this issue.
I can confirm that what Nord outlined in his post has led to the problem being solved for ME2 (I have been running additional DLLs for a while). I no longer get the message and the mods seem to run fine.
I would not recommend re-inventing the ME2 wheel in EML, because EML is currently strictly the less practical solution due to requiring a DLL in the game folder, which needs to be moved in and out if you want to play the vanilla game. Support should be shifted to ME2 instead, either through patching your mods or integrating EML practices in ME2.
Seems to simply be an issue of ME2 loading the mods too quickly. Not sure how this is affected by having 1, 2 or 3+ mods installed, or why getting the image size from the PE header yields different results compared to using VirtualQuery. Perhaps loading a certain amount of DLLs is increasing the load time of the game a tad too much, causing the game memory to not be in the state the mods are expecting when there is no forced delay.
I'll see if I will bother adding loose file loading to EML (looking at ME2 source code, it looks somewhat digestible), or simply implement my mod loading into ME2.
Hey @techiew ,
So it turns out that adding a 5 second delay at the start of each MainThread function (except the mod for skipping the intro) seems to work.
So far have tested: SkipTheIntro IncreaseAnimationDistance and UnlockTheFps
I am sending a PR with these changes. I can also put a release of these DLLs on my fork so that people can test them, to make sure they all work, if you are okay with that.
Has anyone figured out SkipTheIntro yet? I was able to get it loading using Nordgaren's fork, but, as already noted, it doesn't actually do what it says on the tin and skip the intro.
@blackwind Chainfailure has made a different intro skip dll which works better. It's unfortunate that the DLLs here receive no updates.
@ividyon I see a repo on GitHub but no releases anywhere. Do you have a link to the DLL?
The best workaround I've found for the Mod Engine 2 issue is to load these DLL files with SpecialK.
You need to add a section like to the SpecialK config for Elden Ring. One for each DLL file.
[Import.ModName]
Architecture=x64
Role=ThirdParty
When=Early
Filename=X:\Path\To\DLL\File\FileToLoad\ModName.dll
Issue Description
EML (EldenRingModLoader
) mods appear to depend on the load_delay
which EML offers.
However ME2 (ModEngine2
) instantly loads the mods,
without a load_delay
, which causes issues for EML mods.
Work Around
Use both EML (EldenRingModLoader
) and ME2 (ModEngine2
) in combination with each other.
- EML for
EldenRingMods
(and other mods that specify EML) - ME2 for other mods
Then launch the game through ME2, which also loads EML with it's mods.
Linked ME2 Issue
https://github.com/soulsmods/ModEngine2/issues/210
So if anyone is similar to me obsessed with not altering the Game
folder and having all mods placed in separate folders and the .toml
file configured correctly instead, then compiling the .dll
s from https://github.com/gixxpunk/EldenRingMods works.
Just make sure to put the skip intro as the very first .dll
that gets loaded by ModEngine2 and make sure to build the whole solution so that it created all the .dll
s in one go.
This is my .toml
:
# Global mod engine configuration
[modengine]
# If set to true the debug console will appear while the game is running
debug = false
# List of files that will be loaded into the game as DLL mods.
external_dlls = [
"mods/skip_the_intro/mods/SkipTheIntro.dll",
"mods/adjust_fov/mods/AdjustTheFov.dll",
"mods/disable_chromatic_aberration/mods/RemoveChromaticAberration.dll",
"mods/disable_sharpen_filter/DisableSharpenFilter.dll",
"mods/disable_vignette/mods/RemoveVignette.dll",
"mods/increase_animation_distance/mods/IncreaseAnimationDistance.dll",
"mods/unlock_fps/mods/UnlockTheFps.dll",
"mods/fix_camera/mods/CameraFix.dll",
"mods/pause_the_game/mods/PauseTheGame.dll",
"mods/temporal_upscaling/dxgi.dll",
"mods/temporal_upscaling/ERSS2.dll",
"mods/transmogify_armor/ertransmogrify.dll",
]
# Mod loader configuration
[extension.mod_loader]
enabled = true
# Not currently supported for Elden Ring
loose_params = false
mods = [
{ enabled = true, name = "skip_the_intro", path = "mods/skip_the_intro"},
{ enabled = true, name = "unlock_fps", path = "mods/unlock_fps" },
{ enabled = true, name = "adjust_fov", path = "mods/adjust_fov" },
{ enabled = true, name = "disable_chromatic_aberration", path = "mods/disable_chromatic_aberration" },
{ enabled = true, name = "disable_sharpen_filter", path = "mods/disable_sharpen_filter" },
{ enabled = true, name = "disable_vignette", path = "mods/disable_vignette" },
{ enabled = true, name = "increase_animation_distance", path = "mods/increase_animation_distance" },
{ enabled = true, name = "fix_camera", path = "mods/fix_camera"},
{ enabled = true, name = "pause_the_game", path = "mods/pause_the_game" },
{ enabled = true, name = "ps5_icons", path = "mods/ps5_icons" },
{ enabled = true, name = "temporal_upscaling", path = "mods/temporal_upscaling" },
{ enabled = true, name = "texture_improvement", path = "mods/texture_improvement" },
{ enabled = true, name = "transmogify_armor", path = "mods/transmogify_armor" }
]
[extension.scylla_hide]
enabled = false
All EldenRingModLoader mods are installed like they are uploaded on nexus and I just replaced the .dll
files with the ones build from the linked repository.
I did the same with a different .toml
to play Seamless CO-OP (Haven't played this one beyond testing that it launches with seamless coop installed properly, so I don't know if the stuff I read about corrupted saves when not launched without other mods via the default seemless coop launcher for creating the character and first save are still relevant and would need to do some more testing for that):
# Global mod engine configuration
[modengine]
# If set to true the debug console will appear while the game is running
debug = false
# List of files that will be loaded into the game as DLL mods.
external_dlls = [
"mods/skip_the_intro/mods/SkipTheIntro.dll",
"mods/adjust_fov/mods/AdjustTheFov.dll",
"mods/disable_chromatic_aberration/mods/RemoveChromaticAberration.dll",
"mods/disable_sharpen_filter/DisableSharpenFilter.dll",
"mods/disable_vignette/mods/RemoveVignette.dll",
"mods/increase_animation_distance/mods/IncreaseAnimationDistance.dll",
"mods/unlock_fps/mods/UnlockTheFps.dll",
"mods/fix_camera/mods/CameraFix.dll",
"mods/transmogify_armor/ertransmogrify.dll",
"mods/temporal_upscaling/dxgi.dll",
"mods/temporal_upscaling/ERSS2.dll",
"mods/seamless_coop/SeamlessCoop/ersc.dll"
]
# Mod loader configuration
[extension.mod_loader]
enabled = true
# Not currently supported for Elden Ring
loose_params = false
mods = [
{ enabled = true, name = "skip_the_intro", path = "mods/skip_the_intro"},
{ enabled = true, name = "adjust_fov", path = "mods/adjust_fov" },
{ enabled = true, name = "disable_chromatic_aberration", path = "mods/disable_chromatic_aberration" },
{ enabled = true, name = "disable_sharpen_filter", path = "mods/disable_sharpen_filter" },
{ enabled = true, name = "disable_vignette", path = "mods/disable_vignette" },
{ enabled = true, name = "increase_animation_distance", path = "mods/increase_animation_distance" },
{ enabled = true, name = "fix_camera", path = "mods/fix_camera"},
{ enabled = true, name = "ps5_icons", path = "mods/ps5_icons" },
{ enabled = true, name = "temporal_upscaling", path = "mods/temporal_upscaling" },
{ enabled = true, name = "texture_improvement", path = "mods/texture_improvement" },
{ enabled = true, name = "transmogify_armor", path = "mods/transmogify_armor" },
{ enabled = true, name = "unlock_fps", path = "mods/unlock_fps" },
{ enabled = true, name = "seamless_coop", path = "mods/seamless_coop"}
]
[extension.scylla_hide]
enabled = false
@EzioTheDeadPoet I tried this but as soon as I added the config file for the SkipTheIntro mod then I got the signature error. Where did you put config files ?
Edit: I have this error when I change hide_initial_white_screen = 1
to hide_initial_white_screen = 0
@EzioTheDeadPoet I tried this but as soon as I added the config file for the SkipTheIntro mod then I got the signature error. Where did you put config files ?
Edit: I have this error when I change
hide_initial_white_screen = 1
tohide_initial_white_screen = 0
using
[skip_the_intro]
skip_intro_logos = 1
hide_initial_white_screen = 1
hide_initial_white_screen_duration = 0
and with that it works. I don't see why one would like to be flahbanged by an unnecessary white screen so testing that functionality ~~seems~~ did seem pointless to me.
@EzioTheDeadPoet @Yashirow @Rikj000
I had the same problem while using AdjustTheFov, UnlockTheFps and SkipTheIntro (and some unrelated mods that seemingly didn't have any impact and didn't fail to inject) with ModEngine 2.
After some testing I found that the order of injection (as specified with external_dlls
in ModEngines's config_eldenring.toml) affects which of the dlls fails to inject. I am assuming the ordering to affect the timing of the injection which is most likely the actual cause.
The failure to inject always stems from the same source: the function ModUtils::AobScan(std::string)
in ModUtils.h. It uses VirtualQuery
at line 323 to check the access protection of the memory pages. If the protection is PAGE_EXECUTE_READWRITE
, PAGE_READWRITE
, PAGE_READONLY
, PAGE_WRITECOPY
or PAGE_EXECUTE_WRITECOPY
(line 342) it proceeds to scan the page for given signature. It ignores all other pages. In my testing, the protection of the relevant page was PAGE_EXECUTE_WRITECOPY
whenever the injection succeeded. It was PAGE_EXECUTE_READ
whenever the injection failed.
Interestingly, sometimes two different dlls receive different access protection constants for the same page. For example, either AdjustTheFov received PAGE_EXECUTE_WRITECOPY
and UnlockTheFps recevied PAGE_EXECUTE_READ
or vice versa meaning one dll succeeds and the other doesn't. At no point did both or neither succeed.
These two mods scan for different signatures which are however always located on the same memory page. This means that something changes the page's access protection after the first but before the second dll gets injected. Further testing and experimenting revealed that it is not the dlls themselves that change the access protection (they do change it but those changes aren't causing the injection of the other dll to fail). Either ModEingine 2 or the game itself have to be responsible for the interference.
The solution to the problem is quite simple: Add PAGE_EXECUTE_READ
to the list of accepted access protection constants at line 342:
bool isMemoryReadable = (
protection == PAGE_EXECUTE_READWRITE
|| protection == PAGE_READWRITE
|| protection == PAGE_READONLY
|| protection == PAGE_WRITECOPY
|| protection == PAGE_EXECUTE_WRITECOPY
|| protection == PAGE_EXECUTE_READ // added by myself
)
&& state == MEM_COMMIT;
The list already contains execute constants and no-write constants. Adding a constant that is both execute as well as no-write shouldn't cause any problems. I recompiled the two mods, injected them with ModEngine 2 and everything worked as expected.
Delaying the injection might work but IMO it is a band-aid solution. This fix should be a bit more robust.
Btw. it should be fine to add the other two access protection constants (PAGE_EXECUTE
and PAGE_EXECUTE_READWRITE
) as well but i didn't test that.