10 Commits
Author SHA1 Message Date
HetCreep 4a879f582c Hash the full UTF-8 byte length in HashStrings (#1355)
HashStrings passed str.Length (a char count) as the byte count to
MD5.TransformBlock, so for any non-ASCII string only a truncated prefix
of the UTF-8 bytes was hashed. Capture the byte array and use its Length.
2026-06-21 12:20:41 +02:00
HetCreep fb5f191b36 Enumerate a config snapshot under the lock (#1357)
ConfigFile documents its public methods as thread-safe, but GetEnumerator
returned the live Dictionary enumerator with no lock, so a concurrent
Bind/Reload could invalidate it mid-foreach. Snapshot under _ioLock instead.
2026-06-21 12:13:51 +02:00
HetCreep 32fb12f2e9 Remove the duplicate byte TomlTypeConverter registration (#1359)
typeof(byte) was registered twice in the TypeConverters initializer; the
second entry silently overwrote the first. Keep the one grouped with the
other integral types.
2026-06-21 12:10:28 +02:00
HetCreep 5f39645992 Allow multiple [TargetType] and [TargetAssembly] attributes (#1343)
Patch-method discovery already reads both with the plural
MetadataHelper.GetAttributes<T>(), and the docs say additional target
attributes can be specified, but the attributes lacked
AllowMultiple = true, so the compiler rejected a second one (CS0579).
Set AllowMultiple = true on both.
2026-06-11 14:52:43 +02:00
HetCreep da64b220eb Do not abort startup when the config file cannot be written (#1340)
* Don't abort startup when the config file can't be written

ConfigFile.Save opens the file for writing with no exception handling and is invoked automatically via SaveOnConfigSet while settings are bound during startup, before logging is initialized. A read-only or otherwise unwritable config file (a read-only install directory, a file locked by another process or antivirus) therefore throws UnauthorizedAccessException out of the early startup path and shows the Failed to start BepInEx dialog.
2026-06-11 14:49:59 +02:00
HetCreep a06688e85b Degrade instead of crashing when the IL2CPP interop assemblies are missing (#1338)
* Degrade instead of crashing when the interop assemblies are missing

OnInvokeMethod references interop types (UnityEngine.Application/LogType) in its body. The CLR JIT-compiles the whole method before running any of it, so when the interop assemblies cannot be resolved the method throws FileNotFoundException at JIT time -- before its own try is entered -- and the exception escapes into the native runtime-invoke detour and crashes the process with nothing logged.

Move the interop-referencing call into its own SetupUnityLogging method called inside the try. OnInvokeMethod then JITs without the interop assemblies present, the missing-interop failure is caught, and the loader degrades (logs and continues) so the game runs unmodded. Set unhook at the top of the try so the detour fires exactly once even on failure.

* Condense the interop-degrade comments and reword the fatal log

Per review: trim the JIT-bypass comments to one line each and change
the fatal message to "Unable to execute IL2CPP chainloader, no plugins
will be loaded".
2026-06-11 11:54:22 +02:00
HetCreep a1afbfb169 Skip missing search directories to fix preloader DirectoryNotFoundException (#1331) 2026-06-11 11:46:18 +02:00
HetCreep f568f0e7b5 Fix misleading error label for Finished handlers (#1329)
BaseChainloader.Execute invoked the Finished event inside the same
try/catch that reports plugin-loading failures, so an exception thrown
by a Finished event listener was logged as "Error occurred loading
plugins:" even though it was unrelated to plugin loading.

Move Finished?.Invoke() into its own try/catch with an accurate
message. Control flow is otherwise unchanged: Finished still runs only
after LoadPlugins succeeds, a throwing listener is still caught, and
"Chainloader startup complete" is still logged afterwards.

Fixes #842
2026-06-08 14:49:39 +02:00
HetCreep 77d438cf69 Clarify BepInDependency version errors and docs (#1330)
The chainloader collected both not-installed dependencies and
installed-but-incompatible-version dependencies into one list and
reported them all as "missing dependencies", which is confusing when
the plugin is present but on a version outside the requested range.

Separate the two cases and log distinct, actionable messages: one for
dependencies that are not installed, and one for dependencies present
with an incompatible version (showing the found version and the
required range).

Also expand the BepInDependency(string, string) xmldoc: the version is
a SemVer range, link the range syntax, and warn about the BepInEx 5 ->
6 behaviour change (a bare version was a minimum, now it is exact).

Fixes #779
2026-06-08 14:48:55 +02:00
HetCreep f8ba0a41af Give actionable error when base-libs download fails (#1332)
When the Unity base-libraries download fails (commonly a 404 for a
Unity version not yet hosted on unity.bepinex.dev), the uncaught
HttpRequestException surfaced only as a cryptic "Failed to generate
Il2Cpp interop assemblies" and the game would not launch -- even
though a local fallback (the unity-libs directory) already exists.

Catch the download failure and throw a clear message naming the Unity
version, the URL and the two workarounds: place a matching
base-libraries .zip in the unity-libs directory, or point the
[IL2CPP] UnityBaseLibrariesSource config at a valid URL.

Refs #1295, #1084, #986, #941, #515
2026-06-08 14:36:17 +02:00