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

Feature: Stub EXE For Dependency Checking #43

Closed
Sewer56 opened this issue Aug 19, 2020 · 6 comments
Closed

Feature: Stub EXE For Dependency Checking #43

Sewer56 opened this issue Aug 19, 2020 · 6 comments
Labels
accepted Proposed change was accepted to be fixed/handled by the developers. completed Work has been completed by the developers. enhancement New feature or request

Comments

@Sewer56
Copy link
Member

Sewer56 commented Aug 19, 2020

The Problem
I'm continuously frustrated with users every other day asking for help because they did not install the dependencies required.

This generally amounts to what feels like 75-80% of all questions asked at least on the Heroes Hacking Central where I run my operations for a certain 16 year old game.

The Current Solution
In the past, the required dependencies were at the top of readme. This did not work very well on Github, as the user required to scroll the page to see the dependencies; so it was changed in favour of a more optimised system. However, surprisingly, a lot of GameBanana users had a tendency of missing the prerequisites too, which was an annoyance.

I then figured that placing the dependencies beside the download buttons so that the users literally can't miss them would be better. This worked out pretty well at the time, however as more and more users started to use Reloaded, the issue would once again become an annoyance.

As seen on Github:
image

As seen on GameBanana:
image

Describe the solution you'd like
We should have a proxy EXE that runs on .NET Framework which performs a dependency check for Core and informs the user if the dependencies are missing. We may download Core ourselves using the dotnet-install script after informing and receiving consent from the end user.

Additional context
There is no official library or recommended means of checking what versions of Core are installed on the machine.
The easiest way to check however would be by inspecting the Program Files\dotnet\shared\ and Program Files (x86)\dotnet\shared\ folders.

These folders contain a folder for each of the installed runtimes ASP, Desktop, App and inside those, folders for each version, including previews.

Ideally we wouldn't want to have to manually specify the required version, so we would want to parse runtimeconfig.json and then extract the required framework from the given configuration file. It is important however that we parse any custom rollforward option as specified per https://github.com/dotnet/designs/blob/master/accepted/2019/runtime-binding.md#rollforward and appropriately resolve if the user has compatible Core versions installed.

@Sewer56 Sewer56 added enhancement New feature or request untriaged No decision has been made by the developers. labels Aug 19, 2020
@dreamsyntax
Copy link
Contributor

dreamsyntax commented Aug 20, 2020

For both proposed solutions, assumes the real Reloaded-II.exe is not launched by the user but rather by the proxy solutions below:
An advanced user could still ignore using these processes by directly launching R-II, but by default they should be used.
Both also assume .netframework 4.7.2 (can be changed even lower depending on target OS). netframework 4.8 is installed by default on win 10 build 2004.


Solution 1

Reloaded-II-Doctor

Does not explicitly check for netcore installs.
Instead launches Reloaded-II process and monitors for n number of known bad scenarios.

Example:
Process ends near immediately on launch? Likely missing netcore dependencies. Suggest a solution (or prompt asking if install is okay). Also allows for easily letting users know to upgrade to 3.1/5.0 etc in the future.

Reloaded launches but throws an exception when launching a game? Again suggest solution or automatically perform solution (with permission)
Example:
"Loader Config gets Corrupted due to Unknown Cause"
When .NET Core x86 is Missing (DLL Injector Failure):

These two however could just be handled by Reloaded-II's exception handler. The main problem being caught would be the lack of any error on missing both dependencies.

Reloaded-II doctor should terminate after successful/safe timeout period.


Solution 2

Reloaded-II-DepCheck

Explicitly checks for netcore installs (or whatever current dependencies needed) once every Reloaded-II update.

On an R-II update:
assumes this program will be updated too with whatever current dependency checks are needed.
the flag (text file or w/e) that skips the check should be cleared.

Potential problems:

  • mixed version issues (3.0 x86, 3.1 x64) | We should not uninstall user's redists without consent, we do not know what other programs require them
  • assumes admin access (reg check, install check, protected folder check)
  • on reinstalling an OS or drag & dropping having the 'skip' feature would bring us back to the original problem of no error message on R-II launch with no dependencies

Solution 3
User environment netcore
Package and ship local netcore and have R-II and libraries point to current directory rather than installed netcore


After writing this out I think I'll make a prototype of Solution 2, from there if you like it you can use it or write your own factoring in successes/failures of the prototype.

@Sewer56
Copy link
Member Author

Sewer56 commented Aug 20, 2020

the flag (text file or w/e) that skips the check should be cleared.

Don't think this will be necessary, as I'd expect most time to be spent JIT-ting the code on startup.
For example, here are hot and cold boot times for a program printing a Hello World.

Cold Boot:

PS C:\Users\sewer\source\repos\StartupTimeTest\StartupTimeTest\bin\Release> Measure-Command {.\StartupTimeTest.exe}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 103
Ticks             : 1032793
TotalDays         : 1.19536226851852E-06
TotalHours        : 2.86886944444444E-05
TotalMinutes      : 0.00172132166666667
TotalSeconds      : 0.1032793
TotalMilliseconds : 103.2793

Hot Boot:

PS C:\Users\sewer\source\repos\StartupTimeTest\StartupTimeTest\bin\Release> Measure-Command {.\StartupTimeTest.exe}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 59
Ticks             : 597631
TotalDays         : 6.91702546296296E-07
TotalHours        : 1.66008611111111E-05
TotalMinutes      : 0.000996051666666667
TotalSeconds      : 0.0597631
TotalMilliseconds : 59.7631

This is on an above average machine: Ryzen Mobile 3550H and an SSD; but it should check out on lower end machines too.

Most of this is probably setting up the runtime and jitting the initial code. In practice, JIT-ting the JSON library to parse out the runtimeconfig.json file will also be required, so give-take maybe up to another 8ms for it to be read from IO (if using traditional hard drive) and 30-40ms to get JITted. I should probably test that.

Might be able to kill that up to 8ms IO overhead by embedding the Json parser directly though (see: https://github.com/Fody/Costura).

@Sewer56
Copy link
Member Author

Sewer56 commented Aug 21, 2020

I added System.Text.Json and tried parsing a dummy Json file in a dummy program:

Cold Boot:

PS C:\Users\sewer\source\repos\StartupTimeTest\StartupTimeTest\bin\Release> Measure-Command {.\StartupTimeTest.exe}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 457
Ticks             : 4579720
TotalDays         : 5.30060185185185E-06
TotalHours        : 0.000127214444444444
TotalMinutes      : 0.00763286666666667
TotalSeconds      : 0.457972
TotalMilliseconds : 457.972

Hot Boot:

PS C:\Users\sewer\source\repos\StartupTimeTest\StartupTimeTest\bin\Release> Measure-Command {.\StartupTimeTest.exe}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 202
Ticks             : 2025686
TotalDays         : 2.34454398148148E-06
TotalHours        : 5.62690555555556E-05
TotalMinutes      : 0.00337614333333333
TotalSeconds      : 0.2025686
TotalMilliseconds : 202.5686

Seems to drop a lot of dependencies since a lot of these libs aren't in Framework, oof.
Adding Costura to embed references actually increases startup time to levels cold boot levels, due to more code being JIT-ted, oof.

Maybe try Utf8Json.
Edit: Utf8Json was the same speed pretty much.


Edit:
Obviously the optimal way to do this is to use C/C++ or another language that compiles to native code ahead of time, but I'd prefer to have a C# library for this as it may be useful for other tools in the future.

Maybe we can try CoreRT?

@Sewer56
Copy link
Member Author

Sewer56 commented Aug 21, 2020

CoreRT knocks it out of the park (and this includes deserialization).

Cold Boot:

PS C:\Users\sewer\source\repos\CoreRTStartupTest\StartupTimeTestRT\bin\Release\netcoreapp3.1\publish> Measure-Command { .\StartupTimeTestRT.exe }


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 68
Ticks             : 684497
TotalDays         : 7.92241898148148E-07
TotalHours        : 1.90138055555556E-05
TotalMinutes      : 0.00114082833333333
TotalSeconds      : 0.0684497
TotalMilliseconds : 68.4497

Hot Boot:

PS C:\Users\sewer\source\repos\CoreRTStartupTest\StartupTimeTestRT\bin\Release\netcoreapp3.1\publish> Measure-Command { .\StartupTimeTestRT.exe }


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 34
Ticks             : 343161
TotalDays         : 3.97177083333333E-07
TotalHours        : 9.53225E-06
TotalMinutes      : 0.000571935
TotalSeconds      : 0.0343161
TotalMilliseconds : 34.3161

Large app size though
image

I used this doc to get started: https://github.com/dotnet/corert/tree/master/samples/HelloWorld

This is still possible to optimize though: https://github.com/dotnet/corert/blob/master/Documentation/using-corert/optimizing-corert.md

The sad part though is it's probably not possible to load Core DLLs from a CoreRT compiled program (as per dotnet/corert#6949), which makes complete sense. I would totally have liked to ship the launcher using this tech but it's a no-go because of Configure Mod button which actually loads in the actual mods.

@Sewer56
Copy link
Member Author

Sewer56 commented Aug 21, 2020

Just some notes for interesting/nontrivial stuff: @dreamsyntax

Semantic Versioning

.NET Core uses a versioning scheme similar to Semantic Versioning 2.0.0 (it has preview versions); we will need a compatible version parser such as NuGet.Versioning.
This is undocumented (as usual with NuGet libraries) so just look at the tests.

Will need to write a basic converter to this type using System.Text.Json
https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#sample-basic-converter

Framework Name

Probably want an enum like this:

enum FrameworkName
{
   // Default/Null
   App, // Microsoft.NETCore.App
   Asp, // Microsoft.AspNetCore.App
   WindowsDesktop // Microsoft.WindowsDesktop.App
}

Because string comparisons are a no-no.

Once again, will need a basic converter to this type using System.Text.Json
https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#sample-basic-converter


Ideally we wouldn't want to have to manually specify the required version, so we would want to parse runtimeconfig.json and then extract the required framework from the given configuration file. It is important however that we parse any custom rollforward option as specified per https://github.com/dotnet/designs/blob/master/accepted/2020/global-json-updates.md and appropriately resolve if the user has compatible Core versions installed.

The biggest part will be this and this is where unit tests will be necessary heh.

@Sewer56 Sewer56 added accepted Proposed change was accepted to be fixed/handled by the developers. and removed untriaged No decision has been made by the developers. labels Aug 23, 2020
@Sewer56 Sewer56 added the completed Work has been completed by the developers. label Sep 1, 2020
@Sewer56 Sewer56 closed this as completed Sep 1, 2020
@Sewer56
Copy link
Member Author

Sewer56 commented Sep 1, 2020

Implemented as of commit #ad1ce65ca775dda3ce57aad3fa4c06471dcf49f1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted Proposed change was accepted to be fixed/handled by the developers. completed Work has been completed by the developers. enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants