techiew / EldenRingMods

A collection of mods I've made for Elden Ring.
MIT License
170 stars 23 forks source link

"Could not find signature" issues when loading DLLs with Mod Engine 2 #15

Open ividyon opened 1 year ago

ividyon commented 1 year ago

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.

ividyon commented 1 year ago

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.

ividyon commented 1 year ago

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

Nordgaren commented 1 year ago

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.

Nordgaren commented 1 year ago

P.S. I wouldn't bother you with this if these weren't popular mods, but a lot of people DO use them.

techiew commented 1 year ago

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.

Nordgaren commented 1 year ago

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

techiew commented 1 year ago

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.

soarqin commented 1 year ago

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.

ividyon commented 1 year ago

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.

Nordgaren commented 1 year ago

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.

blackwind commented 11 months ago

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.

ividyon commented 11 months ago

@blackwind Chainfailure has made a different intro skip dll which works better. It's unfortunate that the DLLs here receive no updates.

blackwind commented 11 months ago

@ividyon I see a repo on GitHub but no releases anywhere. Do you have a link to the DLL?

madindehead commented 2 months ago

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
Rikj000 commented 2 months ago

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.

Then launch the game through ME2, which also loads EML with it's mods.

Linked ME2 Issue

https://github.com/soulsmods/ModEngine2/issues/210

EzioTheDeadPoet commented 2 months ago

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 .dlls 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 .dlls 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
Yashirow commented 2 months ago

@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 commented 2 months ago

@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

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.

hanslhansl commented 2 months ago

@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.