EverestAPI / Everest

Everest - Celeste Mod Loader / Mod API

Home Page:https://everestapi.github.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Game crashed when a zipped mod trying to load native library

NoMathExpectation opened this issue · comments

My mods and Everest install are up to date

Yes

I have recreated the bug with only Everest OR a minimum number of mods enabled

Yes

Describe the bug

I am developing a mod and I need to use some native library.
I implemented this by extracting libraries to mod cache and referencing using DllImport. code
The library loading works fine when running the mod in an unzipped form.
But when I zip the mod and run, the native library loading will fail and the game will crash:

Ver 1.4.0.0-fna [Everest: 4739-azure-62e69]
04/26/2024 19:22:13
System.InvalidOperationException: Sequence contains no matching element
   at System.Linq.ThrowHelper.ThrowNoMatchException()
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate)
   at Celeste.Mod.EverestModuleAssemblyContext.LoadUnmanagedFromThisMod(String name) in /home/vsts/work/1/s/Celeste.Mod.mm/Mod/Module/EverestModuleAssemblyContext.cs:line 516
   at Celeste.Mod.EverestModuleAssemblyContext.LoadUnmanagedLocal(String name) in /home/vsts/work/1/s/Celeste.Mod.mm/Mod/Module/EverestModuleAssemblyContext.cs:line 372
   at Celeste.Mod.EverestModuleAssemblyContext.LoadUnmanagedDll(String name) in /home/vsts/work/1/s/Celeste.Mod.mm/Mod/Module/EverestModuleAssemblyContext.cs:line 309
   at System.Runtime.Loader.AssemblyLoadContext.ResolveUnmanagedDll(String unmanagedDllName, IntPtr gchManagedAssemblyLoadContext)
   at NoMathExpectation.Celeste.Celestibility.Speech.ZDSRSpeechProvider.InitTTS(Int32 type, String channelName, Boolean bKeyDownInterrupt)
   at NoMathExpectation.Celeste.Celestibility.Speech.ZDSRSpeechProvider..ctor()
   at NoMathExpectation.Celeste.Celestibility.Speech.SpeechEngine.Init()
   at NoMathExpectation.Celeste.Celestibility.CelestibilityModule.Load()
   at Celeste.Mod.Everest.Register(EverestModule module)
   at Celeste.Mod.Everest.Loader.LoadModAssembly(EverestModuleMetadata meta, Assembly asm)
   at Celeste.Mod.Everest.Loader.LoadMod(EverestModuleMetadata meta)
   at Celeste.Mod.Everest.Loader.LoadModDelayed(EverestModuleMetadata meta, Action callback)
   at Celeste.Mod.Everest.Loader.LoadZip(String archive)
   at Celeste.Mod.Everest.Loader.LoadAuto()
   at Celeste.Mod.Everest.Boot()
   at Celeste.Celeste..ctor()
   at Celeste.Celeste.orig_Main(String[] args)

Steps to reproduce

  1. Go to the mod repository, compile the source code and pack to a zip.
  2. Put the zipped mod to the mod folder and run the game.

Expected behavior

The mod will load correctly just like in unzipped form.

Operating System

Windows 11

Everest Version

4739

Mods required to reproduce

No response

Additional context

errorLog.txt
log.txt

private const string dll = "Mods/Cache/Celestibility/nativebin/ZDSRAPI_x64";

99% sure thats your issue. The DLL name should just be the the name, not the full path.
You can look at ImGuiHelper or TASRecorder on how to properly use native libraries.
Theses are the paths, file names you'll need (with the root being the directory of your mod DLL):

internal static string UnmanagedLibraryFolder =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (Environment.Is64BitOperatingSystem ? "lib-win-x64" : "lin-win-x86") :
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "lib-linux" :
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "lib-osx" :
null
;
and
string osName =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? $"{name}.dll" :
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? $"lib{name}.so" :
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"lib{name}.dylib" :
name
;

I have changed and committed the code according to your suggestion, but the game still crashes and throws the same exception.
Code: https://github.com/NoMathExpectation/Celestibility/blob/75969d0e4c7c49f4e3c6e82ce2373fd0c63b4341/Source/Speech/ZDSRSpeechProvider.cs#L45

Ver 1.4.0.0-fna [Everest: 4739-azure-62e69]
04/26/2024 21:53:36
System.InvalidOperationException: Sequence contains no matching element
   at System.Linq.ThrowHelper.ThrowNoMatchException()
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate)
   at Celeste.Mod.EverestModuleAssemblyContext.LoadUnmanagedFromThisMod(String name) in /home/vsts/work/1/s/Celeste.Mod.mm/Mod/Module/EverestModuleAssemblyContext.cs:line 516
   at Celeste.Mod.EverestModuleAssemblyContext.LoadUnmanagedLocal(String name) in /home/vsts/work/1/s/Celeste.Mod.mm/Mod/Module/EverestModuleAssemblyContext.cs:line 372
   at Celeste.Mod.EverestModuleAssemblyContext.LoadUnmanagedDll(String name) in /home/vsts/work/1/s/Celeste.Mod.mm/Mod/Module/EverestModuleAssemblyContext.cs:line 309
   at System.Runtime.Loader.AssemblyLoadContext.ResolveUnmanagedDll(String unmanagedDllName, IntPtr gchManagedAssemblyLoadContext)
   at NoMathExpectation.Celeste.Celestibility.Speech.ZDSRSpeechProvider.InitTTS(Int32 type, String channelName, Boolean bKeyDownInterrupt)
   at NoMathExpectation.Celeste.Celestibility.Speech.ZDSRSpeechProvider..ctor()
   at NoMathExpectation.Celeste.Celestibility.Speech.SpeechEngine.Init()
   at NoMathExpectation.Celeste.Celestibility.CelestibilityModule.Load()
   at Celeste.Mod.Everest.Register(EverestModule module)
   at Celeste.Mod.Everest.Loader.LoadModAssembly(EverestModuleMetadata meta, Assembly asm)
   at Celeste.Mod.Everest.Loader.LoadMod(EverestModuleMetadata meta)
   at Celeste.Mod.Everest.Loader.LoadModDelayed(EverestModuleMetadata meta, Action callback)
   at Celeste.Mod.Everest.Loader.LoadZip(String archive)
   at Celeste.Mod.Everest.Loader.LoadAuto()
   at Celeste.Mod.Everest.Boot()
   at Celeste.Celeste..ctor()
   at Celeste.Celeste.orig_Main(String[] args)

commented

This seems to be the cause of the crash. The mod's Hashes are null and as such the LINQ query crashes.

string modHash = (ModuleMeta.Hash ?? ModuleMeta.Multimeta.First(meta => meta.Hash != null).Hash).ToHexadecimalString();

The stacktrace tells most of the story. Here's a section of Everest.Loader.LoadMod():

// Try to load a module from a DLL
if (!string.IsNullOrEmpty(meta.DLL)) {
if (meta.AssemblyContext.LoadAssemblyFromModPath(meta.DLL) is not Assembly asm) {
// Don't register a module - this will cause dependencies to not load
ModsWithAssemblyLoadFailures.Add(meta);
return false;
}
LoadModAssembly(meta, asm);
goto success;
}
// Register a null module for content mods.
new NullModule(meta).Register();
success:
meta.RegisterMod();
return true;

Hashes are calculated in EverestModuleMetadata.RegisterMod():

internal void RegisterMod() {
Everest.InvalidateInstallationHash();
Hash = Everest.GetChecksum(this);
// Audio banks are cached, and as such use the module's hash. We can only ingest those now.
if (patch_Audio.AudioInitialized) {
patch_Audio.IngestNewBanks();
}
Everest.CheckDependenciesOfDelayedMods();
}

  • Everest.Loader.LoadModAssembly() is called, which invokes Everest.Register(EverestModule), which then calls EverestModule.Load().
  • Skipping ahead, ZDSRSpeechProvider.InitTTS(int, string, bool) is P/Invoked, which triggers the unmanaged DLL resolution.
  • Skipping further ahead, EverestModuleAssemblyContext.LoadUnmanagedFromThisMod(string) is called, which tries to retrieve the mod's Hash.
  • Because EverestModuleMetadata.RegisterMod() has not been called yet, the mod's Hash is null and the game crashes.