├── .editorconfig
├── .git-blame-ignore-revs
├── .gitattributes
├── .github
├── CONTRIBUTING.md
├── dependabot.yml
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE.txt
├── NeosModLoader.sln
├── NeosModLoader
├── AssemblyFile.cs
├── AssemblyHider.cs
├── AssemblyLoader.cs
├── AutoRegisterConfigKeyAttribute.cs
├── ConfigurationChangedEvent.cs
├── DebugInfo.cs
├── DelegateExtensions.cs
├── ExecutionHook.cs
├── HarmonyWorker.cs
├── IgnoresAccessChecksToAttribute.cs
├── JsonConverters
│ ├── EnumConverter.cs
│ └── NeosPrimitiveConverter.cs
├── LoadedNeosMod.cs
├── Logger.cs
├── ModConfiguration.cs
├── ModConfigurationDefinitionBuilder.cs
├── ModConfigurationKey.cs
├── ModLoader.cs
├── ModLoaderConfiguration.cs
├── NeosMod.cs
├── NeosModBase.cs
├── NeosModLoader.csproj
├── NeosVersionReset.cs
├── Properties
│ └── AssemblyInfo.cs
├── SplashChanger.cs
├── Util.cs
└── Utility
│ ├── EnumerableInjector.cs
│ └── PlatformHelper.cs
├── README.md
└── doc
├── config.md
├── directories.md
├── example_log.log
├── faq.md
├── how_nml_works.md
├── img
├── NeosProLauncher.png
├── NeosPublicSetup.png
├── add_non_steam_game.png
├── non_steam_game_properties_1.png
├── non_steam_game_properties_2.png
├── steam_game_properties.png
└── windows_unblock.png
├── linux.md
├── making_mods.md
├── modloader_config.md
├── neos_guidelines.md
├── neos_standalone_setup.md
├── problem_solving_techniques.md
├── start_neos.bat
└── troubleshooting.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://EditorConfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = tab
6 | indent_size = 4
7 | end_of_line = lf
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | charset = utf-8
11 |
12 | [*.yml]
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.md]
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | ## tabs standardization
2 | 321ac86f17d62425ebaaf909b8b493c04ea23afb
3 |
4 | ## prior standardization
5 | 7555f8b407aec05d9b69b754f373fe9e2345dcd1
6 |
7 | ## dotnet format
8 | ce83bfe4a26f3662e08b5a3f3089518ad54a51a0
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.cs text eol=lf
3 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # NeosModLoader Contributing Guidelines
2 |
3 | If you are interested in contributing to this project via issues or PRs, please follow these guidelines.
4 |
5 | ## Issue Guidelines
6 |
7 | If you are reporting a bug, please include a log, or at the very least the relevant stack trace. NeosModLoader logs to the Neos log by default (`C:\Program Files (x86)\Steam\steamapps\common\NeosVR\Logs`).
8 |
9 | ## PR Guidelines
10 |
11 | If you want your PR to be approved and merged quickly, please read the following:
12 |
13 | ### Code Style
14 |
15 | If your PR does not follow the project's code style it **will not be approved**. Code style should match our [editor config](../.editorconfig). If you aren't sure, use your IDE's formatter (Analyze > Code Cleanup in Visual Studio).
16 |
17 | ### New Features
18 |
19 | Please consider the NML [design goals](#design-goals) before adding a new feature. If you aren't sure if your new feature makes sense for NML, I'm happy to talk with you about a potential new feature in our [discussions area](https://github.com/zkxs/NeosModLoader/discussions).
20 |
21 | ## Design Goals
22 |
23 | - NML should be kept as simple as possible. Its purpose is to load mods, not mod Neos itself. Additionally, NML is not intended to fix Neos bugs. Please do not attempt to add Neos bugfixes directly to NML. Instead, ensure there's an issue open on [the Neos issue tracker](https://github.com/Neos-Metaverse/NeosPublic/issues), and only consider making a mod if the Neos team is unable to provide a fix in a reasonably timeframe.
24 | - NML should only create APIs where the added API complexity is paid for by added ease of development for mod creators. If a proposed API is only useful to a very small percentage of mods, NeosModLoader probably isn't the place for it.
25 | - NML should try to prevent mod developers from shooting themselves in the foot. For example, NML only supports mods with a single NeosMod implementation. Instead of silently ignoring extra implementations in a mod, NML will instead throw an error message and abort loading the mod entirely.
26 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "nuget"
9 | directory: "/"
10 | schedule:
11 | interval: "weekly"
12 | ignore:
13 | - dependency-name: "Newtonsoft.Json"
14 | - package-ecosystem: "github-actions"
15 | directory: "/"
16 | schedule:
17 | interval: "weekly"
18 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # This workflow handles all the CI checks, builds, and artifact publishing for NeosModLoader.
2 |
3 | name: Test, Build, and Release
4 | on: [push, pull_request]
5 |
6 | env:
7 | NeosPath: "${{ github.workspace }}/neos_install/" # set up in neos-modding-group/neos-plugin-setup-action
8 |
9 | jobs:
10 | build:
11 | if: github.repository == 'neos-modding-group/NeosModLoader' # prevent forks from unintentionally running this workflow
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: checkout NML
15 | uses: actions/checkout@v4
16 | - name: setup build environment
17 | uses: neos-modding-group/neos-plugin-setup-action@master # This is where the Neos dependencies come from. Learn more at https://github.com/neos-modding-group/neos-plugin-setup-action
18 | - name: lint
19 | run: dotnet format --verbosity detailed --verify-no-changes ./NeosModLoader.sln
20 | - name: build headed
21 | run: |
22 | dotnet build ./NeosModLoader.sln --configuration Release "-property:Headless=false;CopyToLibraries=false"
23 | mv ./NeosModLoader/bin/Release/net462/NeosModLoader.dll ./NeosModLoader.dll
24 | - name: build headless
25 | if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/master' # only build headless run for pushes to master or tags
26 | run: |
27 | dotnet build ./NeosModLoader.sln --configuration Release "-property:Headless=true;CopyToLibraries=false"
28 | mv ./NeosModLoader/bin/Release/net462/NeosModLoader.dll ./NeosModLoaderHeadless.dll
29 | - name: upload workflow artifacts
30 | if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/master' # only publish workflow artifacts for pushes to master or tags
31 | uses: actions/upload-artifact@v3
32 | with:
33 | name: NeosModLoader
34 | path: |
35 | ./NeosModLoader.dll
36 | ./NeosModLoaderHeadless.dll
37 | if-no-files-found: error
38 | - name: upload release artifacts
39 | if: startsWith(github.ref, 'refs/tags/') # only publish release artifacts for pushes to tags
40 | uses: softprops/action-gh-release@v1
41 | with:
42 | draft: true # if creating a new release, make it a draft
43 | files: |
44 | ./NeosModLoader/bin/Release/net462/0Harmony.dll
45 | ./NeosModLoader.dll
46 | ./NeosModLoaderHeadless.dll
47 | fail_on_unmatched_files: true
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 |
3 | ## Ignore Visual Studio temporary files, build results, and
4 | ## files generated by popular Visual Studio add-ons.
5 | ##
6 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
7 |
8 | # User-specific files
9 | *.rsuser
10 | *.suo
11 | *.user
12 | *.userosscache
13 | *.sln.docstates
14 |
15 | # User-specific files (MonoDevelop/Xamarin Studio)
16 | *.userprefs
17 |
18 | # Mono auto generated files
19 | mono_crash.*
20 |
21 | # Build results
22 | [Dd]ebug/
23 | [Dd]ebugPublic/
24 | [Rr]elease/
25 | [Rr]eleases/
26 | x64/
27 | x86/
28 | [Ww][Ii][Nn]32/
29 | [Aa][Rr][Mm]/
30 | [Aa][Rr][Mm]64/
31 | bld/
32 | [Bb]in/
33 | [Oo]bj/
34 | [Ll]og/
35 | [Ll]ogs/
36 |
37 | # Visual Studio 2015/2017 cache/options directory
38 | .vs/
39 | # Uncomment if you have tasks that create the project's static files in wwwroot
40 | #wwwroot/
41 |
42 | # Visual Studio 2017 auto generated files
43 | Generated\ Files/
44 |
45 | # MSTest test Results
46 | [Tt]est[Rr]esult*/
47 | [Bb]uild[Ll]og.*
48 |
49 | # NUnit
50 | *.VisualState.xml
51 | TestResult.xml
52 | nunit-*.xml
53 |
54 | # Build Results of an ATL Project
55 | [Dd]ebugPS/
56 | [Rr]eleasePS/
57 | dlldata.c
58 |
59 | # Benchmark Results
60 | BenchmarkDotNet.Artifacts/
61 |
62 | # .NET Core
63 | project.lock.json
64 | project.fragment.lock.json
65 | artifacts/
66 |
67 | # ASP.NET Scaffolding
68 | ScaffoldingReadMe.txt
69 |
70 | # StyleCop
71 | StyleCopReport.xml
72 |
73 | # Files built by Visual Studio
74 | *_i.c
75 | *_p.c
76 | *_h.h
77 | *.ilk
78 | *.meta
79 | *.obj
80 | *.iobj
81 | *.pch
82 | *.pdb
83 | *.ipdb
84 | *.pgc
85 | *.pgd
86 | *.rsp
87 | *.sbr
88 | *.tlb
89 | *.tli
90 | *.tlh
91 | *.tmp
92 | *.tmp_proj
93 | *_wpftmp.csproj
94 | *.log
95 | *.vspscc
96 | *.vssscc
97 | .builds
98 | *.pidb
99 | *.svclog
100 | *.scc
101 |
102 | # Chutzpah Test files
103 | _Chutzpah*
104 |
105 | # Visual C++ cache files
106 | ipch/
107 | *.aps
108 | *.ncb
109 | *.opendb
110 | *.opensdf
111 | *.sdf
112 | *.cachefile
113 | *.VC.db
114 | *.VC.VC.opendb
115 |
116 | # Visual Studio profiler
117 | *.psess
118 | *.vsp
119 | *.vspx
120 | *.sap
121 |
122 | # Visual Studio Trace Files
123 | *.e2e
124 |
125 | # TFS 2012 Local Workspace
126 | $tf/
127 |
128 | # Guidance Automation Toolkit
129 | *.gpState
130 |
131 | # ReSharper is a .NET coding add-in
132 | _ReSharper*/
133 | *.[Rr]e[Ss]harper
134 | *.DotSettings.user
135 |
136 | # TeamCity is a build add-in
137 | _TeamCity*
138 |
139 | # DotCover is a Code Coverage Tool
140 | *.dotCover
141 |
142 | # AxoCover is a Code Coverage Tool
143 | .axoCover/*
144 | !.axoCover/settings.json
145 |
146 | # Coverlet is a free, cross platform Code Coverage Tool
147 | coverage*.json
148 | coverage*.xml
149 | coverage*.info
150 |
151 | # Visual Studio code coverage results
152 | *.coverage
153 | *.coveragexml
154 |
155 | # NCrunch
156 | _NCrunch_*
157 | .*crunch*.local.xml
158 | nCrunchTemp_*
159 |
160 | # MightyMoose
161 | *.mm.*
162 | AutoTest.Net/
163 |
164 | # Web workbench (sass)
165 | .sass-cache/
166 |
167 | # Installshield output folder
168 | [Ee]xpress/
169 |
170 | # DocProject is a documentation generator add-in
171 | DocProject/buildhelp/
172 | DocProject/Help/*.HxT
173 | DocProject/Help/*.HxC
174 | DocProject/Help/*.hhc
175 | DocProject/Help/*.hhk
176 | DocProject/Help/*.hhp
177 | DocProject/Help/Html2
178 | DocProject/Help/html
179 |
180 | # Click-Once directory
181 | publish/
182 |
183 | # Publish Web Output
184 | *.[Pp]ublish.xml
185 | *.azurePubxml
186 | # Note: Comment the next line if you want to checkin your web deploy settings,
187 | # but database connection strings (with potential passwords) will be unencrypted
188 | *.pubxml
189 | *.publishproj
190 |
191 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
192 | # checkin your Azure Web App publish settings, but sensitive information contained
193 | # in these scripts will be unencrypted
194 | PublishScripts/
195 |
196 | # NuGet Packages
197 | *.nupkg
198 | # NuGet Symbol Packages
199 | *.snupkg
200 | # The packages folder can be ignored because of Package Restore
201 | **/[Pp]ackages/*
202 | # except build/, which is used as an MSBuild target.
203 | !**/[Pp]ackages/build/
204 | # Uncomment if necessary however generally it will be regenerated when needed
205 | #!**/[Pp]ackages/repositories.config
206 | # NuGet v3's project.json files produces more ignorable files
207 | *.nuget.props
208 | *.nuget.targets
209 |
210 | # Microsoft Azure Build Output
211 | csx/
212 | *.build.csdef
213 |
214 | # Microsoft Azure Emulator
215 | ecf/
216 | rcf/
217 |
218 | # Windows Store app package directories and files
219 | AppPackages/
220 | BundleArtifacts/
221 | Package.StoreAssociation.xml
222 | _pkginfo.txt
223 | *.appx
224 | *.appxbundle
225 | *.appxupload
226 |
227 | # Visual Studio cache files
228 | # files ending in .cache can be ignored
229 | *.[Cc]ache
230 | # but keep track of directories ending in .cache
231 | !?*.[Cc]ache/
232 |
233 | # Others
234 | ClientBin/
235 | ~$*
236 | *~
237 | *.dbmdl
238 | *.dbproj.schemaview
239 | *.jfm
240 | *.pfx
241 | *.publishsettings
242 | orleans.codegen.cs
243 |
244 | # Including strong name files can present a security risk
245 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
246 | #*.snk
247 |
248 | # Since there are multiple workflows, uncomment next line to ignore bower_components
249 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
250 | #bower_components/
251 |
252 | # RIA/Silverlight projects
253 | Generated_Code/
254 |
255 | # Backup & report files from converting an old project file
256 | # to a newer Visual Studio version. Backup files are not needed,
257 | # because we have git ;-)
258 | _UpgradeReport_Files/
259 | Backup*/
260 | UpgradeLog*.XML
261 | UpgradeLog*.htm
262 | ServiceFabricBackup/
263 | *.rptproj.bak
264 |
265 | # SQL Server files
266 | *.mdf
267 | *.ldf
268 | *.ndf
269 |
270 | # Business Intelligence projects
271 | *.rdl.data
272 | *.bim.layout
273 | *.bim_*.settings
274 | *.rptproj.rsuser
275 | *- [Bb]ackup.rdl
276 | *- [Bb]ackup ([0-9]).rdl
277 | *- [Bb]ackup ([0-9][0-9]).rdl
278 |
279 | # Microsoft Fakes
280 | FakesAssemblies/
281 |
282 | # GhostDoc plugin setting file
283 | *.GhostDoc.xml
284 |
285 | # Node.js Tools for Visual Studio
286 | .ntvs_analysis.dat
287 | node_modules/
288 |
289 | # Visual Studio 6 build log
290 | *.plg
291 |
292 | # Visual Studio 6 workspace options file
293 | *.opt
294 |
295 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
296 | *.vbw
297 |
298 | # Visual Studio LightSwitch build output
299 | **/*.HTMLClient/GeneratedArtifacts
300 | **/*.DesktopClient/GeneratedArtifacts
301 | **/*.DesktopClient/ModelManifest.xml
302 | **/*.Server/GeneratedArtifacts
303 | **/*.Server/ModelManifest.xml
304 | _Pvt_Extensions
305 |
306 | # Paket dependency manager
307 | .paket/paket.exe
308 | paket-files/
309 |
310 | # FAKE - F# Make
311 | .fake/
312 |
313 | # CodeRush personal settings
314 | .cr/personal
315 |
316 | # Python Tools for Visual Studio (PTVS)
317 | __pycache__/
318 | *.pyc
319 |
320 | NeosHeadless/*
321 |
322 | # Cake - Uncomment if you are using it
323 | # tools/**
324 | # !tools/packages.config
325 |
326 | # Tabs Studio
327 | *.tss
328 |
329 | # Telerik's JustMock configuration file
330 | *.jmconfig
331 |
332 | # BizTalk build output
333 | *.btp.cs
334 | *.btm.cs
335 | *.odx.cs
336 | *.xsd.cs
337 |
338 | # OpenCover UI analysis results
339 | OpenCover/
340 |
341 | # Azure Stream Analytics local run output
342 | ASALocalRun/
343 |
344 | # MSBuild Binary and Structured Log
345 | *.binlog
346 |
347 | # NVidia Nsight GPU debugger configuration file
348 | *.nvuser
349 |
350 | # MFractors (Xamarin productivity tool) working folder
351 | .mfractor/
352 |
353 | # Local History for Visual Studio
354 | .localhistory/
355 |
356 | # BeatPulse healthcheck temp database
357 | healthchecksdb
358 |
359 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
360 | MigrationBackup/
361 |
362 | # Ionide (cross platform F# VS Code tools) working folder
363 | .ionide/
364 |
365 | # Fody - auto-generated XML schema
366 | FodyWeavers.xsd
367 |
368 | # Specifically allow the example log
369 | !/doc/example_log.log
370 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/NeosModLoader.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31410.357
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NeosModLoader", "NeosModLoader\NeosModLoader.csproj", "{D4627C7F-8091-477A-ABDC-F1465D94D8D9}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {D4627C7F-8091-477A-ABDC-F1465D94D8D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {D4627C7F-8091-477A-ABDC-F1465D94D8D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {D4627C7F-8091-477A-ABDC-F1465D94D8D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {D4627C7F-8091-477A-ABDC-F1465D94D8D9}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {757072E6-E985-4EC2-AB38-C4D1588F6A15}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/NeosModLoader/AssemblyFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace NeosModLoader
5 | {
6 | internal class AssemblyFile
7 | {
8 | internal string File { get; }
9 | internal Assembly Assembly { get; set; }
10 | internal AssemblyFile(string file, Assembly assembly)
11 | {
12 | File = file;
13 | Assembly = assembly;
14 | }
15 | private string? sha256;
16 | internal string Sha256
17 | {
18 | get
19 | {
20 | if (sha256 == null)
21 | {
22 | try
23 | {
24 | sha256 = Util.GenerateSHA256(File);
25 | }
26 | catch (Exception e)
27 | {
28 | Logger.ErrorInternal($"Exception calculating sha256 hash for {File}:\n{e}");
29 | sha256 = "failed to generate hash";
30 | }
31 | }
32 | return sha256;
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/NeosModLoader/AssemblyHider.cs:
--------------------------------------------------------------------------------
1 | using BaseX;
2 | using FrooxEngine;
3 | using HarmonyLib;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Diagnostics;
7 | using System.Linq;
8 | using System.Reflection;
9 |
10 | namespace NeosModLoader
11 | {
12 | internal static class AssemblyHider
13 | {
14 | ///
15 | /// Companies that indicate an assembly is part of .NET.
16 | /// This list was found by debug logging the AssemblyCompanyAttribute for all loaded assemblies.
17 | ///
18 | private static HashSet knownDotNetCompanies = new List()
19 | {
20 | "Mono development team", // used by .NET stuff and Mono.Security
21 | }.Select(company => company.ToLower()).ToHashSet();
22 |
23 | ///
24 | /// Products that indicate an assembly is part of .NET.
25 | /// This list was found by debug logging the AssemblyProductAttribute for all loaded assemblies.
26 | ///
27 | private static HashSet knownDotNetProducts = new List()
28 | {
29 | "Microsoft® .NET", // used by a few System.* assemblies
30 | "Microsoft® .NET Framework", // used by most of the System.* assemblies
31 | "Mono Common Language Infrastructure", // used by mscorlib stuff
32 | }.Select(product => product.ToLower()).ToHashSet();
33 |
34 | ///
35 | /// Assemblies that were already loaded when NML started up, minus a couple known non-Neos assemblies.
36 | ///
37 | private static HashSet? neosAssemblies;
38 |
39 | ///
40 | /// Assemblies that 100% exist due to a mod
41 | ///
42 | private static HashSet? modAssemblies;
43 |
44 | ///
45 | /// .NET assembiles we want to ignore in some cases, like the callee check for the AppDomain.GetAssemblies() patch
46 | ///
47 | private static HashSet? dotNetAssemblies;
48 |
49 | ///
50 | /// Patch Neos's type lookup code to not see mod-related types. This is needed, because users can pass
51 | /// arbitrary strings to TypeHelper.FindType(), which can be used to detect if someone is running mods.
52 | ///
53 | /// Our NML harmony instance
54 | /// Assemblies that were loaded when NML first started
55 | internal static void PatchNeos(Harmony harmony, HashSet initialAssemblies)
56 | {
57 | if (ModLoaderConfiguration.Get().HideModTypes)
58 | {
59 | // initialize the static assembly sets that our patches will need later
60 | neosAssemblies = GetNeosAssemblies(initialAssemblies);
61 | modAssemblies = GetModAssemblies(neosAssemblies);
62 | dotNetAssemblies = neosAssemblies.Where(LooksLikeDotNetAssembly).ToHashSet();
63 |
64 | // TypeHelper.FindType explicitly does a type search
65 | MethodInfo findTypeTarget = AccessTools.DeclaredMethod(typeof(TypeHelper), nameof(TypeHelper.FindType), new Type[] { typeof(string) });
66 | MethodInfo findTypePatch = AccessTools.DeclaredMethod(typeof(AssemblyHider), nameof(FindTypePostfix));
67 | harmony.Patch(findTypeTarget, postfix: new HarmonyMethod(findTypePatch));
68 |
69 | // WorkerManager.IsValidGenericType checks a type for validity, and if it returns `true` it reveals that the type exists
70 | MethodInfo isValidGenericTypeTarget = AccessTools.DeclaredMethod(typeof(WorkerManager), nameof(WorkerManager.IsValidGenericType), new Type[] { typeof(Type), typeof(bool) });
71 | MethodInfo isValidGenericTypePatch = AccessTools.DeclaredMethod(typeof(AssemblyHider), nameof(IsValidTypePostfix));
72 | harmony.Patch(isValidGenericTypeTarget, postfix: new HarmonyMethod(isValidGenericTypePatch));
73 |
74 | // WorkerManager.GetType uses FindType, but upon failure fails back to doing a (strangely) exhausitive reflection-based search for the type
75 | MethodInfo getTypeTarget = AccessTools.DeclaredMethod(typeof(WorkerManager), nameof(WorkerManager.GetType), new Type[] { typeof(string) });
76 | MethodInfo getTypePatch = AccessTools.DeclaredMethod(typeof(AssemblyHider), nameof(FindTypePostfix));
77 | harmony.Patch(getTypeTarget, postfix: new HarmonyMethod(getTypePatch));
78 |
79 | // FrooxEngine likes to enumerate all types in all assemblies, which is prone to issues (such as crashing FrooxCode if a type isn't loadable)
80 | MethodInfo getAssembliesTarget = AccessTools.DeclaredMethod(typeof(AppDomain), nameof(AppDomain.GetAssemblies), new Type[] { });
81 | MethodInfo getAssembliesPatch = AccessTools.DeclaredMethod(typeof(AssemblyHider), nameof(GetAssembliesPostfix));
82 | harmony.Patch(getAssembliesTarget, postfix: new HarmonyMethod(getAssembliesPatch));
83 | }
84 | }
85 |
86 | private static HashSet GetNeosAssemblies(HashSet initialAssemblies)
87 | {
88 | // Remove NML itself, as its types should be hidden but it's guaranteed to be loaded.
89 | initialAssemblies.Remove(Assembly.GetExecutingAssembly());
90 |
91 | // Remove Harmony, as users who aren't using nml_libs will already have it loaded.
92 | initialAssemblies.Remove(typeof(Harmony).Assembly);
93 |
94 | return initialAssemblies;
95 | }
96 |
97 | private static HashSet GetModAssemblies(HashSet neosAssemblies)
98 | {
99 | // start with ALL assemblies
100 | HashSet assemblies = AppDomain.CurrentDomain.GetAssemblies().ToHashSet();
101 |
102 | // remove assemblies that we know to have come with Neos
103 | assemblies.ExceptWith(neosAssemblies);
104 |
105 | // what's left are assemblies that magically appeared during the mod loading process. So mods and their dependencies.
106 | return assemblies;
107 | }
108 |
109 | ///
110 | /// Checks if an belongs to a mod or not.
111 | ///
112 | /// The to check.
113 | /// Type of root check being performed. Should be "type" or "assembly". Used in logging.
114 | /// Name of the root check being performed. Used in logging.
115 | /// If `true`, this will emit logs. If `false`, this function will not log.
116 | /// If `true`, then this function will always return `false` for late-loaded types
117 | /// `true` if this assembly belongs to a mod.
118 | private static bool IsModAssembly(Assembly assembly, string typeOrAssembly, string name, bool log, bool forceShowLate)
119 | {
120 | if (neosAssemblies!.Contains(assembly))
121 | {
122 | // the type belongs to a Neos assembly
123 | return false; // don't hide the thing
124 | }
125 | else
126 | {
127 | if (modAssemblies!.Contains(assembly))
128 | {
129 | // known type from a mod assembly
130 | if (log)
131 | {
132 | Logger.DebugFuncInternal(() => $"Hid {typeOrAssembly} \"{name}\" from Neos");
133 | }
134 | return true; // hide the thing
135 | }
136 | else
137 | {
138 | // an assembly was in neither neosAssemblies nor modAssemblies
139 | // this implies someone late-loaded an assembly after NML, and it was later used in-game
140 | // this is super weird, and probably shouldn't ever happen... but if it does, I want to know about it.
141 | // since this is an edge case users may want to handle in different ways, the HideLateTypes nml config option allows them to choose.
142 | bool hideLate = ModLoaderConfiguration.Get().HideLateTypes;
143 | if (log)
144 | {
145 | Logger.WarnInternal($"The \"{name}\" {typeOrAssembly} does not appear to part of Neos or a mod. It is unclear whether it should be hidden or not. Due to the HideLateTypes config option being {hideLate} it will be {(hideLate ? "Hidden" : "Shown")}");
146 | }
147 | // if forceShowLate == true, then this function will always return `false` for late-loaded types
148 | // if forceShowLate == false, then this function will return `true` when hideLate == true
149 | return hideLate && !forceShowLate;
150 | }
151 | }
152 | }
153 |
154 | ///
155 | /// Checks if an belongs to a mod or not.
156 | ///
157 | /// The to check
158 | /// If true, then this function will always return false for late-loaded types.
159 | /// true if this belongs to a mod.
160 | private static bool IsModAssembly(Assembly assembly, bool forceShowLate = false)
161 | {
162 | // this generates a lot of logspam, as a single call to AppDomain.GetAssemblies() calls this many times
163 | return IsModAssembly(assembly, "assembly", assembly.ToString(), log: false, forceShowLate);
164 | }
165 |
166 | ///
167 | /// Checks if a belongs to a mod or not.
168 | ///
169 | /// The to check.
170 | /// true if this belongs to a mod.
171 | private static bool IsModType(Type type)
172 | {
173 | return IsModAssembly(type.Assembly, "type", type.ToString(), log: true, forceShowLate: false);
174 | }
175 |
176 | // postfix for a method that searches for a type, and returns a reference to it if found (TypeHelper.FindType and WorkerManager.GetType)
177 | private static void FindTypePostfix(ref Type? __result)
178 | {
179 | if (__result != null)
180 | {
181 | // we only need to think about types if the method actually returned a non-null result
182 | if (IsModType(__result))
183 | {
184 | __result = null;
185 | }
186 | }
187 | }
188 |
189 | // postfix for a method that validates a type (WorkerManager.IsValidGenericType)
190 | private static void IsValidTypePostfix(ref bool __result, Type type)
191 | {
192 | if (__result == true)
193 | {
194 | // we only need to think about types if the method actually returned a true result
195 | if (IsModType(type))
196 | {
197 | __result = false;
198 | }
199 | }
200 | }
201 |
202 | private static void GetAssembliesPostfix(ref Assembly[] __result)
203 | {
204 | Assembly? callingAssembly = GetCallingAssembly(new(1));
205 | if (callingAssembly != null && neosAssemblies!.Contains(callingAssembly))
206 | {
207 | // if we're being called by Neos code, then hide mod assemblies
208 | Logger.DebugFuncInternal(() => $"Intercepting call to AppDomain.GetAssemblies() from {callingAssembly}");
209 | __result = __result
210 | .Where(assembly => !IsModAssembly(assembly, forceShowLate: true)) // it turns out Neos itself late-loads a bunch of stuff, so we force-show late-loaded assemblies here
211 | .ToArray();
212 | }
213 | }
214 |
215 | ///
216 | /// Get the calling using stack trace analysis, ignoring .NET assemblies.
217 | /// This implementation is SPECIFICALLY for the patch and may not be valid for other use-cases.
218 | ///
219 | /// The stack trace captured by the callee.
220 | /// The calling , or null if none was found.
221 | private static Assembly? GetCallingAssembly(StackTrace stackTrace)
222 | {
223 | for (int i = 0; i < stackTrace.FrameCount; i++)
224 | {
225 | Assembly? assembly = stackTrace.GetFrame(i)?.GetMethod()?.DeclaringType?.Assembly;
226 | // .NET calls AppDomain.GetAssemblies() a bunch internally, and we don't want to intercept those calls UNLESS they originated from Neos code.
227 | if (assembly != null && !dotNetAssemblies!.Contains(assembly))
228 | {
229 | return assembly;
230 | }
231 | }
232 | return null;
233 | }
234 |
235 | private static bool LooksLikeDotNetAssembly(Assembly assembly)
236 | {
237 | // check the assembly's company
238 | string? company = assembly.GetCustomAttribute()?.Company;
239 | if (company != null && knownDotNetCompanies.Contains(company.ToLower()))
240 | {
241 | return true;
242 | }
243 |
244 | // check the assembly's product
245 | string? product = assembly.GetCustomAttribute()?.Product;
246 | if (product != null && knownDotNetProducts.Contains(product.ToLower()))
247 | {
248 | return true;
249 | }
250 |
251 | // nothing matched, this is probably not part of .NET
252 | return false;
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/NeosModLoader/AssemblyLoader.cs:
--------------------------------------------------------------------------------
1 | using NeosModLoader.Utility;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Reflection;
7 |
8 | namespace NeosModLoader
9 | {
10 | internal static class AssemblyLoader
11 | {
12 | private static string[]? GetAssemblyPathsFromDir(string dirName)
13 | {
14 | string assembliesDirectory = Path.Combine(PlatformHelper.MainDirectory, dirName);
15 |
16 | Logger.MsgInternal($"loading assemblies from {dirName}");
17 |
18 | string[]? assembliesToLoad = null;
19 | try
20 | {
21 | // Directory.GetFiles and Directory.EnumerateFiles have a fucked up API: https://learn.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netframework-4.6.2#system-io-directory-getfiles(system-string-system-string-system-io-searchoption)
22 | // long story short if I searched for "*.dll" it would unhelpfully use some incredibly inconsistent behavior and return results like "foo.dll_disabled"
23 | // So I have to filter shit after the fact... ugh
24 | assembliesToLoad = Directory.EnumerateFiles(assembliesDirectory, "*.dll", SearchOption.AllDirectories)
25 | .Where(file => file.EndsWith(".dll"))
26 | .ToArray();
27 | Array.Sort(assembliesToLoad, string.CompareOrdinal);
28 | }
29 | catch (Exception e)
30 | {
31 | if (e is DirectoryNotFoundException)
32 | {
33 | Logger.MsgInternal($"{dirName} directory not found, creating it now.");
34 | try
35 | {
36 | Directory.CreateDirectory(assembliesDirectory);
37 | }
38 | catch (Exception e2)
39 | {
40 | Logger.ErrorInternal($"Error creating ${dirName} directory:\n{e2}");
41 | }
42 | }
43 | else
44 | {
45 | Logger.ErrorInternal($"Error enumerating ${dirName} directory:\n{e}");
46 | }
47 | }
48 | return assembliesToLoad;
49 | }
50 |
51 | private static Assembly? LoadAssembly(string filepath)
52 | {
53 | string filename = Path.GetFileName(filepath);
54 | SplashChanger.SetCustom($"Loading file: {filename}");
55 | Assembly assembly;
56 | try
57 | {
58 | Logger.DebugFuncInternal(() => $"load assembly {filename}");
59 | assembly = Assembly.LoadFrom(filepath);
60 | }
61 | catch (Exception e)
62 | {
63 | Logger.ErrorInternal($"error loading assembly from {filepath}: {e}");
64 | return null;
65 | }
66 | if (assembly == null)
67 | {
68 | Logger.ErrorInternal($"unexpected null loading assembly from {filepath}");
69 | return null;
70 | }
71 | return assembly;
72 | }
73 |
74 | internal static AssemblyFile[] LoadAssembliesFromDir(string dirName)
75 | {
76 | List assemblyFiles = new();
77 | if (GetAssemblyPathsFromDir(dirName) is string[] assemblyPaths)
78 | {
79 | foreach (string assemblyFilepath in assemblyPaths)
80 | {
81 | try
82 | {
83 | if (LoadAssembly(assemblyFilepath) is Assembly assembly)
84 | {
85 | assemblyFiles.Add(new AssemblyFile(assemblyFilepath, assembly));
86 | }
87 | }
88 | catch (Exception e)
89 | {
90 | Logger.ErrorInternal($"Unexpected exception loading assembly from {assemblyFilepath}:\n{e}");
91 | }
92 | }
93 | }
94 | return assemblyFiles.ToArray();
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/NeosModLoader/AutoRegisterConfigKeyAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NeosModLoader
4 | {
5 | ///
6 | /// Marks a field of type on a class
7 | /// deriving from to be automatically included in that mod's configuration.
8 | ///
9 | [AttributeUsage(AttributeTargets.Field)]
10 | public class AutoRegisterConfigKeyAttribute : Attribute
11 | { }
12 | }
13 |
--------------------------------------------------------------------------------
/NeosModLoader/ConfigurationChangedEvent.cs:
--------------------------------------------------------------------------------
1 | namespace NeosModLoader
2 | {
3 | ///
4 | /// Represents the data for the and events.
5 | ///
6 | public class ConfigurationChangedEvent
7 | {
8 | ///
9 | /// The in which the change occured.
10 | ///
11 | public ModConfiguration Config { get; private set; }
12 |
13 | ///
14 | /// The specific who's value changed.
15 | ///
16 | public ModConfigurationKey Key { get; private set; }
17 |
18 | ///
19 | /// A custom label that may be set by whoever changed the configuration.
20 | ///
21 | public string? Label { get; private set; }
22 |
23 | internal ConfigurationChangedEvent(ModConfiguration config, ModConfigurationKey key, string? label)
24 | {
25 | Config = config;
26 | Key = key;
27 | Label = label;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/NeosModLoader/DebugInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Runtime.Versioning;
4 |
5 | namespace NeosModLoader
6 | {
7 | internal class DebugInfo
8 | {
9 | internal static void Log()
10 | {
11 | Logger.MsgInternal($"NeosModLoader v{ModLoader.VERSION} starting up!{(ModLoaderConfiguration.Get().Debug ? " Debug logs will be shown." : "")}");
12 | Logger.MsgInternal($"CLR v{Environment.Version}");
13 | Logger.DebugFuncInternal(() => $"Using .NET Framework: \"{AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\"");
14 | Logger.DebugFuncInternal(() => $"Using .NET Core: \"{Assembly.GetEntryAssembly()?.GetCustomAttribute()?.FrameworkName}\"");
15 | Logger.MsgInternal($"Using Harmony v{GetAssemblyVersion(typeof(HarmonyLib.Harmony))}");
16 | Logger.MsgInternal($"Using BaseX v{GetAssemblyVersion(typeof(BaseX.floatQ))}");
17 | Logger.MsgInternal($"Using FrooxEngine v{GetAssemblyVersion(typeof(FrooxEngine.IComponent))}");
18 | Logger.MsgInternal($"Using Json.NET v{GetAssemblyVersion(typeof(Newtonsoft.Json.JsonSerializer))}");
19 | }
20 |
21 | private static string? GetAssemblyVersion(Type typeFromAssembly)
22 | {
23 | return typeFromAssembly.Assembly.GetName()?.Version?.ToString();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/NeosModLoader/DelegateExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace NeosModLoader
6 | {
7 | internal static class DelegateExtensions
8 | {
9 | internal static void SafeInvoke(this Delegate del, params object[] args)
10 | {
11 | var exceptions = new List();
12 |
13 | foreach (var handler in del.GetInvocationList())
14 | {
15 | try
16 | {
17 | handler.Method.Invoke(handler.Target, args);
18 | }
19 | catch (Exception ex)
20 | {
21 | exceptions.Add(ex);
22 | }
23 | }
24 |
25 | if (exceptions.Any())
26 | {
27 | throw new AggregateException(exceptions);
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/NeosModLoader/ExecutionHook.cs:
--------------------------------------------------------------------------------
1 | using FrooxEngine;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Reflection;
6 |
7 | namespace NeosModLoader
8 | {
9 | [ImplementableClass(true)]
10 | internal class ExecutionHook
11 | {
12 | #pragma warning disable CS0169
13 | // fields must exist due to reflective access
14 | private static Type? __connectorType; // needed in all Neos versions
15 | private static Type? __connectorTypes; // needed in Neos 2021.10.17.1326 and later
16 | #pragma warning restore CS0169
17 |
18 | static ExecutionHook()
19 | {
20 | try
21 | {
22 | HashSet initialAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToHashSet();
23 | SplashChanger.SetCustom("Loading libraries");
24 | AssemblyFile[] loadedAssemblies = AssemblyLoader.LoadAssembliesFromDir("nml_libs");
25 | // note that harmony may not be loaded until this point, so this class cannot directly inport HarmonyLib.
26 |
27 | if (loadedAssemblies.Length != 0)
28 | {
29 | string loadedAssemblyList = string.Join("\n", loadedAssemblies.Select(a => a.Assembly.FullName + " Sha256=" + a.Sha256));
30 | Logger.MsgInternal($"Loaded libraries from nml_libs:\n{loadedAssemblyList}");
31 | }
32 |
33 | SplashChanger.SetCustom("Initializing");
34 | DebugInfo.Log();
35 | NeosVersionReset.Initialize();
36 | HarmonyWorker.LoadModsAndHideModAssemblies(initialAssemblies);
37 | SplashChanger.SetCustom("Loaded");
38 | }
39 | catch (Exception e) // it's important that this doesn't send exceptions back to Neos
40 | {
41 | Logger.ErrorInternal($"Exception in execution hook!\n{e}");
42 | }
43 | }
44 |
45 | // implementation not strictly required, but method must exist due to reflective access
46 | private static DummyConnector InstantiateConnector()
47 | {
48 | return new DummyConnector();
49 | }
50 |
51 | // type must match return type of InstantiateConnector()
52 | private class DummyConnector : IConnector
53 | {
54 | public IImplementable? Owner { get; private set; }
55 | public void ApplyChanges() { }
56 | public void AssignOwner(IImplementable owner) => Owner = owner;
57 | public void Destroy(bool destroyingWorld) { }
58 | public void Initialize() { }
59 | public void RemoveOwner() => Owner = null;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/NeosModLoader/HarmonyWorker.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 |
5 | namespace NeosModLoader
6 | {
7 | // this class does all the harmony-related NML work.
8 | // this is needed to avoid importing harmony in ExecutionHook, where it may not be loaded yet.
9 | internal class HarmonyWorker
10 | {
11 | internal static void LoadModsAndHideModAssemblies(HashSet initialAssemblies)
12 | {
13 | Harmony harmony = new("com.neosmodloader");
14 | ModLoader.LoadMods(harmony);
15 | AssemblyHider.PatchNeos(harmony, initialAssemblies);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/NeosModLoader/IgnoresAccessChecksToAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace System.Runtime.CompilerServices
9 | {
10 | ///
11 | /// Makes the .NET runtime ignore access of private members of the with the given name.
12 | /// Use when building against publicized assemblies to prevent problems if Neos ever switches from running on Mono,
13 | /// where checking the "Allow Unsafe Code" option in the Project Settings is enough.
14 | ///
15 | /// Usage: [assembly: IgnoresAccessChecksTo("FrooxEngine")]
16 | ///
17 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
18 | public class IgnoresAccessChecksToAttribute : Attribute
19 | {
20 | ///
21 | /// Gets the name of the Assembly to ignore access checks to.
22 | ///
23 | public string AssemblyName { get; }
24 |
25 | ///
26 | /// Makes the .NET runtime ignore access of private members of the with the given name.
27 | /// Use when building against publicized assemblies to prevent problems if Neos ever switches from running on Mono,
28 | /// where checking the "Allow Unsafe Code" option in the Project Settings is enough.
29 | ///
30 | /// Usage: [assembly: IgnoresAccessChecksTo("FrooxEngine")]
31 | ///
32 | /// The name of the Assembly to ignore access checks to.
33 | public IgnoresAccessChecksToAttribute(string assemblyName)
34 | {
35 | AssemblyName = assemblyName;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/NeosModLoader/JsonConverters/EnumConverter.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 |
4 | namespace NeosModLoader.JsonConverters
5 | {
6 | // serializes and deserializes enums as strings
7 | internal class EnumConverter : JsonConverter
8 | {
9 | public override bool CanConvert(Type objectType)
10 | {
11 | return objectType.IsEnum;
12 | }
13 |
14 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
15 | {
16 | // handle old behavior where enums were serialized as underlying type
17 | Type underlyingType = Enum.GetUnderlyingType(objectType);
18 | if (TryConvert(reader!.Value!, underlyingType, out object? deserialized))
19 | {
20 | Logger.DebugFuncInternal(() => $"Deserializing a BaseX type: {objectType} from a {reader!.Value!.GetType()}");
21 | return deserialized!;
22 | }
23 |
24 | // handle new behavior where enums are serialized as strings
25 | if (reader.Value is string serialized)
26 | {
27 | return Enum.Parse(objectType, serialized);
28 | }
29 |
30 | throw new ArgumentException($"Could not deserialize a BaseX type: {objectType} from a {reader?.Value?.GetType()}. Expected underlying type was {underlyingType}");
31 | }
32 |
33 | public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
34 | {
35 | string serialized = Enum.GetName(value!.GetType(), value);
36 | writer.WriteValue(serialized);
37 | }
38 |
39 | private bool TryConvert(object value, Type newType, out object? converted)
40 | {
41 | try
42 | {
43 | converted = Convert.ChangeType(value, newType);
44 | return true;
45 | }
46 | catch
47 | {
48 | converted = null;
49 | return false;
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/NeosModLoader/JsonConverters/NeosPrimitiveConverter.cs:
--------------------------------------------------------------------------------
1 | using BaseX;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.Reflection;
5 |
6 | namespace NeosModLoader.JsonConverters
7 | {
8 | internal class NeosPrimitiveConverter : JsonConverter
9 | {
10 | private static readonly Assembly BASEX = typeof(color).Assembly;
11 |
12 | public override bool CanConvert(Type objectType)
13 | {
14 | // handle all non-enum Neos Primitives in the BaseX assembly
15 | return !objectType.IsEnum && BASEX.Equals(objectType.Assembly) && Coder.IsNeosPrimitive(objectType);
16 | }
17 |
18 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
19 | {
20 | if (reader.Value is string serialized)
21 | {
22 | // use Neos's built-in decoding if the value was serialized as a string
23 | return typeof(Coder<>).MakeGenericType(objectType).GetMethod("DecodeFromString").Invoke(null, new object[] { serialized });
24 | }
25 |
26 | throw new ArgumentException($"Could not deserialize a BaseX type: {objectType} from a {reader?.Value?.GetType()}");
27 | }
28 |
29 | public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
30 | {
31 | string serialized = (string)typeof(Coder<>).MakeGenericType(value!.GetType()).GetMethod("EncodeToString").Invoke(null, new object[] { value });
32 | writer.WriteValue(serialized);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/NeosModLoader/LoadedNeosMod.cs:
--------------------------------------------------------------------------------
1 | namespace NeosModLoader
2 | {
3 | internal class LoadedNeosMod
4 | {
5 | internal LoadedNeosMod(NeosMod neosMod, AssemblyFile modAssembly)
6 | {
7 | NeosMod = neosMod;
8 | ModAssembly = modAssembly;
9 | }
10 |
11 | internal NeosMod NeosMod { get; private set; }
12 | internal AssemblyFile ModAssembly { get; private set; }
13 | internal ModConfiguration? ModConfiguration { get; set; }
14 | internal bool AllowSavingConfiguration = true;
15 | internal bool FinishedLoading { get => NeosMod.FinishedLoading; set => NeosMod.FinishedLoading = value; }
16 | internal string Name { get => NeosMod.Name; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/NeosModLoader/Logger.cs:
--------------------------------------------------------------------------------
1 | using BaseX;
2 | using System;
3 | using System.Diagnostics;
4 |
5 | namespace NeosModLoader
6 | {
7 | internal class Logger
8 | {
9 | // logged for null objects
10 | internal readonly static string NULL_STRING = "null";
11 |
12 | internal static bool IsDebugEnabled()
13 | {
14 | return ModLoaderConfiguration.Get().Debug;
15 | }
16 |
17 | internal static void DebugFuncInternal(Func messageProducer)
18 | {
19 | if (IsDebugEnabled())
20 | {
21 | LogInternal(LogType.DEBUG, messageProducer());
22 | }
23 | }
24 |
25 | internal static void DebugFuncExternal(Func