Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.PlatformNotSupportedException: Operation is not supported on this platform. #67

Closed
AnonBit opened this issue Mar 22, 2019 · 8 comments

Comments

@AnonBit
Copy link

AnonBit commented Mar 22, 2019

Describe the bug
I think preloader doesn't work because in new version of Unity is using .NET(v4.0.30319)

To Reproduce
Just start the game

Expected behavior
To use plugin with new version of game.

Screenshots and logs

 4.1.1.0 - AnyGame
[Message] Preloader started
[Fatal] Could not run preloader!
[Fatal] System.PlatformNotSupportedException: Operation is not supported on this platform.
  at System.AppDomain.DefineDynamicAssembly (System.Reflection.AssemblyName name, System.Reflection.Emit.AssemblyBuilderAccess access) [0x00000] in <0000d56de0ae43ca875d7babfd990580>:0 
  at (wrapper remoting-invoke-with-check) System.AppDomain.DefineDynamicAssembly(System.Reflection.AssemblyName,System.Reflection.Emit.AssemblyBuilderAccess)
  at Harmony.HarmonySharedState.GetState () [0x00027] in <e89cc46b19014ee0becb32fcb0502f3d>:0 
  at Harmony.HarmonySharedState.GetPatchInfo (System.Reflection.MethodBase method) [0x00000] in <e89cc46b19014ee0becb32fcb0502f3d>:0 
  at Harmony.PatchProcessor.Patch () [0x00046] in <e89cc46b19014ee0becb32fcb0502f3d>:0 
  at Harmony.HarmonyInstance.<PatchAll>b__8_0 (System.Reflection.MethodInfo method) [0x000ce] in <e89cc46b19014ee0becb32fcb0502f3d>:0 
  at Harmony.CollectionExtensions.Do[T] (System.Collections.Generic.IEnumerable 1[T] sequence, System.Action 1[T] action) [0x00014] in <e89cc46b19014ee0becb32fcb0502f3d>:0 
  at Harmony.HarmonyInstance.PatchAll (System.Type type) [0x00008] in <e89cc46b19014ee0becb32fcb0502f3d>:0 
  at BepInEx.Bootstrap.PatchedAssemblyResolver.ApplyPatch () [0x0000a] in <5b314db111fe4288967731af4f02cd3a>:0 
  at BepInEx.Bootstrap.AssemblyPatcher.PatchAll (System.String directory, System.Collections.Generic.IDictionary 2[TKey,TValue] patcherMethodDictionary, System.Collections.Generic.IEnumerable 1[T] initializers, System.Collections.Generic.IEnumerable 1[T] finalizers) [0x00203] in <5b314db111fe4288967731af4f02cd3a>:0 
  at BepInEx.Bootstrap.Preloader.Run () [0x001c2] in <5b314db111fe4288967731af4f02cd3a>:0

Desktop (please complete the following information):

  • Any game compiled with Unity 2018.3.0
  • BepInEx version. 4.1.1

PEiD of Assembly-CSharp.dll
изображение

PEiD of BepInEx.dll
изображение

@ghorsington
Copy link
Contributor

ghorsington commented Mar 23, 2019

Thank you for this very important report! This issue is known but unfortunately not really documented anywhere, as the primary goal in BepInEx 4.x is to support pre-Unity 2017 games.
As such, this seems like a good opportunity to elaborate on limitations of BepInEx in terms of the .NET API and scripting backends that Unity supports.

Pre-emptive TL; DR (or a TL; WR, I suppose): New Unity games built against .NET Standard have API Harmony relies on disabled. There is a fix coming in BepInEx 5.0.

Note: if you notice any mistakes or typos, feel free to correct me!

Unity and the panoply of scripting backends

While Unity is known for using .NET as its scripting backend, different Unity games may use a different common language runtimes (that runs the code) and different API profiles (what .NET API is included with the game).

Currently, Unity supports the following CLRs (Unity Documentation):

  • Mono -- an open-source alternative to .NET Framework's CLR that existed before coreclr. The most common runtime used by most PC games.
  • .NET for UWP -- a "normal" CLR from .NET Framework that is compatible only with Microsoft App Store games. It was planned to be removed in 2019.1 in favour of IL2CPP.
  • IL2CPP -- a special backend in which all code gets transpiled to C++ and then compiled to target machine's native code. Allows to build games for mobile and UWP, but has limited support for Reflection and IL emitting.

In addition to that, each backend provides different .NET API (i.e. standard libraries that are used to write code). In Unity those are usually called the .NET profiles (or the combination of scripting runtime and API compatibility level). There are quite a few variations depending on the version of Unity, but here are the most interesting ones (Unity Documentation):

  • .NET 2.0 -- in essence, all of the API provided by Mono 3.x. That means all of API available in .NET Framework 3.5 is implemented either fully or partially.
  • .NET 2.0 subset -- stripped down version of .NET Framework 3.5 API that has some of the things like pipes and proper appdomain API missing.
  • .NET 4.x -- a .NET Framework 4.6 equivalent API.
  • .NET Standard 2.0 -- all of API specified in .NET Standard 2.0 implemented.

Note different versions of Unity have different availability for these API. While in older versions of Unity only the first two are available, the newer versions (2018.1 and up) have the ability to switch between the first and the last two APIs (via the scripting runtime option) (MSDN).

Now that we covered the what, here's the why (if you're only interested in the reasoning for this issue, skip to the next section.

BepInEx and scripting backends

The main goal of BepInEx is to provide support for games using Mono as the CLR. This is simply because supporting the other two runtimes is not practical or possible:

  • UWP games are by default locked down, which makes injecting code into them quite tedious. Even then, the Microsoft App Store is only one of the distribution options, so game developers usually may provide the game as a standalone download, which uses Mono as the backed. Moreover, Unity encourages to use IL2CPP for UWP games, which brings us to the next point.
  • IL2CPP games simply don't ship with a CLR. Instead, IL2CPP compiles all of the game code, required .NET API and the supporting code to run said code into one massive binary file. As a result, there is nothing to attach BepInEx to. Even if there was, all of game's code is compiled into machine code, which makes writing plugins too difficult to be worth it.

As a result, BepInEx only works on Unity games with Mono backend. If Unity decides to move to coreclr in the future, BepInEx might be updated to accommodate that.

BepInEx and .NET APIs

It is important to understand that .NET Standard 2.0 and .NET 4.x are not the same in contents. Most importantly, .NET Standard 2.0 does not have API for dynamically generate code in memory (Unity Forums, Unity Forums).
This feature is key to Harmony, a library BepInEx uses to patch in-game methods in memory without touching the original assembly. As a result, if a game is built to use .NET Standard 2.0 API, Unity simply marks all unneeded API as not supported via the PlatformNotSupportedException.

Since .NET Standard 2.0 became an option in newer versions of Unity, some games built with 2018.1 will simply not have the API Harmony needs to work. And since preloader relies on Harmony, preloader will crash.

TL; DR: How to fix?

Wait until BepInEx 5.0 is out (or build feature-config-overhaul from source).

For devs: how to patch methods in .NET Standard games

As it is unlikely that Harmony will ever update to address this issue (since that would require a massive library overhaul), here's a list of alternatives for method hooking:

  • Use BepInEx preloader patches: you'll have to use Cecil to write patches, but it will always work.
  • Manually detour the method: here's a minimal example of how to do that in x64 games. Note that for compatibility you'd have to make different detours for different architectures.
  • Statically patch the target DLL: the best way is to always manually patch the target method and replace the patched DLL/EXE with the patched one.

If you are aware of any other alternatives, do let me know.

I'll close the issue when the fix is merged to master and when I manage to port this reply to BepInEx docs.

@Shoko84
Copy link

Shoko84 commented May 10, 2019

I don't know if that the right place to update this issue here or create a new one but it's related to that same issue so I'll comment here 😄

So basically, I installed the BepInEx 5.0RC from the BepInEx.5.0.RC1.v2017.x64.zip release package on an x64 Unity game I build myself on Unity 2018.3.f1 (just for testing purposes).

Thing is I still have the System.PlatformNotSupportedException error even with that version:

System.PlatformNotSupportedException: Operation is not supported on this platform.
  at System.AppDomain.DefineDynamicAssembly (System.Reflection.AssemblyName name, System.Reflection.Emit.AssemblyBuilderAccess access) [0x00000] in <c6bd535f6ab848b4a13f34d01b756eef>:0 
  at (wrapper remoting-invoke-with-check) System.AppDomain.DefineDynamicAssembly(System.Reflection.AssemblyName,System.Reflection.Emit.AssemblyBuilderAccess)
  at HarmonyLib.PatchTools.DefineDynamicAssembly (System.String name) [0x0000c] in <b43a2d323b0847bf9c4b07080a7911f5>:0 
  at HarmonyLib.HarmonySharedState.GetState () [0x00015] in <b43a2d323b0847bf9c4b07080a7911f5>:0 
  at HarmonyLib.HarmonySharedState.GetPatchInfo (System.Reflection.MethodBase method) [0x00000] in <b43a2d323b0847bf9c4b07080a7911f5>:0 
  at HarmonyLib.PatchProcessor.Patch () [0x0005f] in <b43a2d323b0847bf9c4b07080a7911f5>:0 
  at BepInEx.Harmony.HarmonyWrapper+<>c__DisplayClass3_0.<PatchAll>b__0 (System.Reflection.MethodInfo method) [0x00303] in <05d2f3161c7446688d95f31ea90496f9>:0 
  at HarmonyLib.CollectionExtensions.Do[T] (System.Collections.Generic.IEnumerable`1[T] sequence, System.Action`1[T] action) [0x00014] in <b43a2d323b0847bf9c4b07080a7911f5>:0 
  at BepInEx.Harmony.HarmonyWrapper.PatchAll (System.Type type, HarmonyLib.Harmony harmonyInstance) [0x00032] in <05d2f3161c7446688d95f31ea90496f9>:0 
  at BepInEx.Preloader.RuntimeFixes.UnityPatches.Apply () [0x0000f] in <202ff50d8a7c4e5791dbcf37fa83e8f3>:0 
  at BepInEx.Preloader.Preloader.Run () [0x00011] in <202ff50d8a7c4e5791dbcf37fa83e8f3>:0

It appears to happen even without any plugins inside the BepInEx\plugins, so I guess it's not related when loading plugins and just about preloading BepInEx? And it was supposedly intended to fix that exception with your latest version if I'm not wrong so is there a specific procedure now to make it work with the 5.0 RC?

@ghorsington
Copy link
Contributor

Greetings!

Thanks for bringing this back up! This issue does indeed now have a fix in 5.0, but currently it is not fully finalised in RC1. However, there is a workaround for it:

To make BepInEx work on .NET Standard games, open BepInEx\config\BepInEx.cfg (or create the configuration file if it's not there), locate and edit the configuration as follows:

[Preloader]
ApplyRuntimePatches = false

Setting ApplyRuntimePatches to false will disable some internal BepInEx patches that rely on Harmony. That will in turn fix the error, but might cause some unintended behaviour as some bugfixes rely on runtime patches (for example #55).

Note that in .NET Standard games Harmony is unavailable to use (for now), if you need to do runtime patching, BepInEx now included MonoMod's RuntimeDetour library that allows to do that.

Finally, I must reiterate that this is still an issue that is being worked on. In full release this workaround may not be necessary.

@Shoko84
Copy link

Shoko84 commented May 10, 2019

Ok so I think to have found a good workaround since I really want to use Harmony and I thought it would be interesting for anyone wanting to use BepInEx and Harmony!

TL;DR: pardeike/Harmony#172 (comment)

In the latest .NET Framework versions starting 4.6, some namespaces seem to have been stripped down from the exported libraries of the built Unity game (especially Reflection.Emit, which looks like to be important for Harmony?).

Basically, someone tried to drop the mscorlib.dll directly from the Unity Editor files (Editor\Data\MonoBleedingEdge\lib\mono\4.5) into the Managed folder of the built game data and Harmony. Feedbacks have been positive and can confirm it works!

I haven't try patching something yet (so no deep testing from my side) but it looks like Harmony now load correctly and BepInEx plugins are perfectly loaded since the exception disappeared.

@ghorsington
Copy link
Contributor

ghorsington commented May 10, 2019

This workaround is known and is apparently used by some mod loaders.

While it might be very tempting to just replace DLLs that are core to CLR runtime itself, I do not recommend it. If it was recommended and fine, this workaround would've been posted here a long time ago.

As I mentioned in my original comment on this issue, Unity games that ship with .NET Standard 2.0 API instead of .NET 4.x have some of their API stripped.
As later it was found out, while the managed API has System.Reflection.Emit stripped, the underlying runtime still includes support for dynamic code generation (which Harmony relies on). And since .NET 4.6 and .NET Standard share most (but not all) of their API, you can in theory interchange mscorlib.dll (that contains the System.Reflection.Emit namespaces) to get back the code generation features.

It all seems nice and easy, but there are some major caveats.

Why it is not recommended to swap mscorlib.dll

  1. The workaround relies on the fact that the functionality is not stripped on the runtime level. This can easily change in future Unity versions, so the quickfix is likely very, very temporary.
  2. There are API in .NET Standard that are not present in .NET 4.x. If a game relies on such API, you'll break the game.
  3. You are permanently replacing the core file of the runtime. It's equivalent of replacing Windows 10's kernel32.dll with one from Windows 7 because there is one feature you really want.
  4. The fix is not viable option for mod/plugin loaders. Asking end-users to swap important files of the game just to make developers' life easier is just silly.

Just please, don't encourage people to do this. I know, I love Harmony too, but you're encouraging practices that are simply not viable for release to end-users.

But what should I do then?

Good question. Here's a list of currently known actual workarounds that will be permanent and work in the future:

  • If you need to implement a small patch and you're fine with using Cecil, writing preloader patches still works
  • If you need a simple API, MonoMod.RuntimeDetour is included in 5.0 RC1 and has a very simple API and allows to do runtime hooking in .NET Standard games. In fact, BepInEx is currently being used by the majority of Risk of Rain 2 modding community with the help with MonoMod. The developer also has a Discord server where you can get technical support pretty quickly.
  • Wait for Harmony to work on non-SRE runtimes. There are efforts to merge MonoMod.RuntimeDetour into Harmony (some other solutions are in the works too). When such new version is available, BepInEx will be shipped with it.

@Shoko84
Copy link

Shoko84 commented May 10, 2019

Thank you for your reply! To be fair, I'm still bit new into injections and patching and only know a few things from Harmony 😄 I didn't think swapping that .dll would be this bad haha

I guess for now I'll stick with MonoMod.RuntimeDetour, doesn't seem to be too much difficult though

Thanks again for your complete reply, that helped me understand the actual issue of it and how it works internally better!

@Efimero
Copy link

Efimero commented May 19, 2019

for linux: you'll get this error when running with the builtin winhttp
set it to native to use the modded one
https://www.reddit.com/r/linux_gaming/comments/be4kmp/fyi_risk_of_rain_2_mods_work_on_linux_at_least/

@bbepis
Copy link
Member

bbepis commented Nov 12, 2019

Thanks to significant work done by Horse and 0x0ade, in BepInEx 5.0, Harmony can now be used in games that use the .NET Standard runtime by piggybacking off of MonoMod.RuntimeDetour.

I'll be closing this issue, but if the issue reappears, please reopen it.

@bbepis bbepis closed this as completed Nov 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants