├── .editorconfig
├── .gitattributes
├── .gitignore
├── LICENSE.txt
├── README.md
├── ResoniteModLoader.sln
├── ResoniteModLoader
├── AssemblyFile.cs
├── AssemblyHider.cs
├── AssemblyLoader.cs
├── Attributes
│ └── AutoRegisterConfigKeyAttribute.cs
├── ConfigurationChangedEvent.cs
├── DebugInfo.cs
├── DelegateExtensions.cs
├── ExecutionHook.cs
├── GlobalDirectives.cs
├── HarmonyWorker.cs
├── JsonConverters
│ ├── EnumConverter.cs
│ └── ResonitePrimitiveConverter.cs
├── LoadProgressIndicator.cs
├── Logger.cs
├── ModConfiguration.cs
├── ModConfigurationDefinitionBuilder.cs
├── ModConfigurationKey.cs
├── ModLoader.cs
├── ModLoaderConfiguration.cs
├── Properties
│ └── AssemblyInfo.cs
├── ResoniteMod.cs
├── ResoniteModBase.cs
├── ResoniteModLoader.csproj
├── Util.cs
└── Utility
│ └── EnumerableInjector.cs
└── doc
├── config.md
├── directories.md
├── example_log.log
├── faq.md
├── guidelines.md
├── img
├── steam_game_properties_1.png
└── steam_game_properties_2.png
├── launch_options.md
├── linux.md
├── making_mods.md
├── modloader_config.md
├── problem_solving_techniques.md
├── start_resonite.bat
└── troubleshooting.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://EditorConfig.org
2 | root = true
3 |
4 | [*]
5 | # Indentation and spacing
6 | indent_size = 4
7 | indent_style = tab
8 | tab_width = 4
9 |
10 | # New line preferences
11 | end_of_line = lf
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 | charset = utf-8
15 | csharp_indent_labels = one_less_than_current
16 | csharp_using_directive_placement = outside_namespace:silent
17 | csharp_prefer_simple_using_statement = false:suggestion
18 | csharp_prefer_braces = true:silent
19 | csharp_style_namespace_declarations = file_scoped:silent
20 | csharp_style_prefer_method_group_conversion = true:silent
21 | csharp_style_prefer_top_level_statements = true:silent
22 | csharp_style_expression_bodied_methods = false:silent
23 | csharp_style_expression_bodied_constructors = false:silent
24 | csharp_style_expression_bodied_operators = false:silent
25 | csharp_style_expression_bodied_properties = true:silent
26 | csharp_style_expression_bodied_indexers = true:silent
27 | csharp_style_expression_bodied_accessors = true:silent
28 | csharp_style_expression_bodied_lambdas = true:silent
29 | csharp_style_expression_bodied_local_functions = false:silent
30 | csharp_indent_block_contents = true
31 | csharp_space_around_binary_operators = before_and_after
32 | csharp_style_prefer_primary_constructors = true:suggestion
33 | csharp_new_line_before_open_brace = none
34 |
35 | [*.yml]
36 | indent_style = space
37 | indent_size = 2
38 |
39 | [*.md]
40 | trim_trailing_whitespace = false
41 |
42 | [*.{cs,vb}]
43 | #### Naming styles ####
44 |
45 | # Naming rules
46 |
47 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
48 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
49 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
50 |
51 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
52 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
53 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
54 |
55 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
56 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
57 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
58 |
59 | # Symbol specifications
60 |
61 | dotnet_naming_symbols.interface.applicable_kinds = interface
62 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
63 | dotnet_naming_symbols.interface.required_modifiers =
64 |
65 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
66 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
67 | dotnet_naming_symbols.types.required_modifiers =
68 |
69 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
70 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
71 | dotnet_naming_symbols.non_field_members.required_modifiers =
72 |
73 | # Naming styles
74 |
75 | dotnet_naming_style.begins_with_i.required_prefix = I
76 | dotnet_naming_style.begins_with_i.required_suffix =
77 | dotnet_naming_style.begins_with_i.word_separator =
78 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
79 |
80 | dotnet_naming_style.pascal_case.required_prefix =
81 | dotnet_naming_style.pascal_case.required_suffix =
82 | dotnet_naming_style.pascal_case.word_separator =
83 | dotnet_naming_style.pascal_case.capitalization = pascal_case
84 |
85 | dotnet_naming_style.pascal_case.required_prefix =
86 | dotnet_naming_style.pascal_case.required_suffix =
87 | dotnet_naming_style.pascal_case.word_separator =
88 | dotnet_naming_style.pascal_case.capitalization = pascal_case
89 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
90 | tab_width = 4
91 | dotnet_style_coalesce_expression = true:suggestion
92 | dotnet_style_null_propagation = true:suggestion
93 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
94 | dotnet_style_prefer_auto_properties = true:silent
95 | dotnet_style_object_initializer = true:suggestion
96 | dotnet_style_collection_initializer = true:suggestion
97 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
98 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
99 | dotnet_style_prefer_conditional_expression_over_return = true:silent
100 | dotnet_style_explicit_tuple_names = true:suggestion
101 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
102 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
103 | dotnet_style_prefer_compound_assignment = true:suggestion
104 | dotnet_style_prefer_simplified_interpolation = true:suggestion
105 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.cs text eol=lf
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
298 | *.vbp
299 |
300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
301 | *.dsw
302 | *.dsp
303 |
304 | # Visual Studio 6 technical files
305 | *.ncb
306 | *.aps
307 |
308 | # Visual Studio LightSwitch build output
309 | **/*.HTMLClient/GeneratedArtifacts
310 | **/*.DesktopClient/GeneratedArtifacts
311 | **/*.DesktopClient/ModelManifest.xml
312 | **/*.Server/GeneratedArtifacts
313 | **/*.Server/ModelManifest.xml
314 | _Pvt_Extensions
315 |
316 | # Paket dependency manager
317 | .paket/paket.exe
318 | paket-files/
319 |
320 | # FAKE - F# Make
321 | .fake/
322 |
323 | # CodeRush personal settings
324 | .cr/personal
325 |
326 | # Python Tools for Visual Studio (PTVS)
327 | __pycache__/
328 | *.pyc
329 |
330 | ResoniteHeadless/*
331 |
332 | # Cake - Uncomment if you are using it
333 | # tools/**
334 | # !tools/packages.config
335 |
336 | # Tabs Studio
337 | *.tss
338 |
339 | # Telerik's JustMock configuration file
340 | *.jmconfig
341 |
342 | # BizTalk build output
343 | *.btp.cs
344 | *.btm.cs
345 | *.odx.cs
346 | *.xsd.cs
347 |
348 | # OpenCover UI analysis results
349 | OpenCover/
350 |
351 | # Azure Stream Analytics local run output
352 | ASALocalRun/
353 |
354 | # MSBuild Binary and Structured Log
355 | *.binlog
356 |
357 | # NVidia Nsight GPU debugger configuration file
358 | *.nvuser
359 |
360 | # MFractors (Xamarin productivity tool) working folder
361 | .mfractor/
362 |
363 | # Local History for Visual Studio
364 | .localhistory/
365 |
366 | # Visual Studio History (VSHistory) files
367 | .vshistory/
368 |
369 | # BeatPulse healthcheck temp database
370 | healthchecksdb
371 |
372 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
373 | MigrationBackup/
374 |
375 | # Ionide (cross platform F# VS Code tools) working folder
376 | .ionide/
377 |
378 | # Fody - auto-generated XML schema
379 | FodyWeavers.xsd
380 |
381 | # VS Code files for those working on multiple tools
382 | .vscode/*
383 | !.vscode/settings.json
384 | !.vscode/tasks.json
385 | !.vscode/launch.json
386 | !.vscode/extensions.json
387 | *.code-workspace
388 |
389 | # Local History for Visual Studio Code
390 | .history/
391 |
392 | # Windows Installer files from build outputs
393 | *.cab
394 | *.msi
395 | *.msix
396 | *.msm
397 | *.msp
398 |
399 | # JetBrains Rider
400 | *.sln.iml
401 | # Specifically allow the example log
402 | !/doc/example_log.log
403 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ResoniteModLoader
2 |
3 | A mod loader for [Resonite](https://resonite.com/). Consider joining our community on [Discord][Resonite Modding Discord] for support, updates, and more.
4 |
5 | ## Installation
6 |
7 | 1. Download [ResoniteModLoader.dll](https://github.com/resonite-modding-group/ResoniteModLoader/releases/latest/download/ResoniteModLoader.dll) to Resonite's `Libraries` folder (`C:\Program Files (x86)\Steam\steamapps\common\Resonite\Libraries`). You may need to create this folder if it's missing.
8 | 2. Place [0Harmony.dll](https://github.com/resonite-modding-group/ResoniteModLoader/releases/latest/download/0Harmony.dll) into a `rml_libs` folder under your Resonite install directory (`C:\Program Files (x86)\Steam\steamapps\common\Resonite\rml_libs`). You will need to create this folder.
9 | 3. Add the following to Resonite's [launch options](https://github.com/resonite-modding-group/ResoniteModLoader/wiki/Launch-Options): `-LoadAssembly Libraries/ResoniteModLoader.dll`. If you put `ResoniteModLoader.dll` somewhere else you will need to change the path.
10 | 4. Optionally add mod DLL files to a `rml_mods` folder under your Resonite install directory (`C:\Program Files (x86)\Steam\steamapps\common\Resonite\rml_mods`). You can create the folder if it's missing, or launch Resonite once with ResoniteModLoader installed and it will be created automatically.
11 | 5. Start the game. If you want to verify that ResoniteModLoader is working you can check the Resonite logs. (`C:\Program Files (x86)\Steam\steamapps\common\Resonite\Logs`). The modloader adds some very obvious logs on startup, and if they're missing something has gone wrong. Here is an [example log file](https://github.com/resonite-modding-group/ResoniteModLoader/wiki/Example-Log) where everything worked correctly.
12 |
13 | If ResoniteModLoader isn't working after following these steps, take a look at our [troubleshooting page](doc/troubleshooting.md).
14 |
15 | ### Example Directory Structure
16 |
17 | Your Resonite directory should now look similar to the following. Files not related to modding are not shown.
18 |
19 | ```
20 |
21 | │ Resonite.exe
22 | │
23 | ├───Logs
24 | │
25 | │
26 | ├───rml_mods
27 | │
28 | │
29 | ├───rml_libs
30 | │ 0Harmony.dll
31 | │
32 | │
33 | ├───rml_config
34 | │
35 | │
36 | └───Libraries
37 | ResoniteModLoader.dll
38 | ```
39 |
40 | Note that additional libraries (rml_libs) can also be in the root of the Resonite install directory if you prefer, but the loading of those happens outside of RML itself.
41 |
42 | ## Finding Mods
43 |
44 | For an easy way to find and manage mods, check out [Resolute](https://github.com/Gawdl3y/Resolute). It simplifies the installation and updating for verified mods from the [mod manifest](https://github.com/resonite-modding-group/resonite-mod-manifest).
45 |
46 | New mods and updates also are posted in [our Discord][Resonite Modding Discord].
47 |
48 | ## Frequently Asked Questions
49 |
50 | Many questions about what RML is and how it works are answered on our [frequently asked questions page](doc/faq.md).
51 |
52 | ## Making a Mod
53 |
54 | Check out the [Mod Creation Guide](https://github.com/resonite-modding-group/ResoniteModLoader/wiki/Creating-Mods).
55 |
56 | ## Configuration
57 |
58 | ResoniteModLoader aims to have a reasonable default configuration, but certain things can be adjusted via an [optional config file](https://github.com/resonite-modding-group/ResoniteModLoader/wiki/Modloader-Config).
59 |
60 | ## Contributing
61 |
62 | Issues and PRs are welcome. Please read our [Contributing Guidelines](.github/CONTRIBUTING.md)!
63 |
64 | ## Licensing and Credits
65 |
66 | ResoniteModLoader is licensed under the GNU Lesser General Public License (LGPL). See [LICENSE.txt](LICENSE.txt) for the full license.
67 |
68 | Third-party libraries distributed alongside ResoniteModLoader:
69 |
70 | - [LibHarmony] ([MIT License](https://github.com/pardeike/Harmony/blob/v2.2.2.0/LICENSE))
71 |
72 | Third-party libraries used in source:
73 |
74 | - [.NET](https://github.com/dotnet) (Various licenses)
75 | - [Resonite](https://resonite.com/) ([EULA](https://resonite.com/policies/EULA.html))
76 | - [Json.NET](https://github.com/JamesNK/Newtonsoft.Json) ([MIT License](https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md))
77 |
78 |
79 | [LibHarmony]: https://github.com/pardeike/Harmony
80 | [Resonite Modding Discord]: https://discord.gg/ZMRyQ8bryN
81 |
--------------------------------------------------------------------------------
/ResoniteModLoader.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33627.172
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResoniteModLoader", "ResoniteModLoader\ResoniteModLoader.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 |
--------------------------------------------------------------------------------
/ResoniteModLoader/AssemblyFile.cs:
--------------------------------------------------------------------------------
1 | namespace ResoniteModLoader;
2 |
3 | internal sealed class AssemblyFile {
4 | internal string File { get; }
5 | internal Assembly Assembly { get; set; }
6 | internal AssemblyFile(string file, Assembly assembly) {
7 | File = file;
8 | Assembly = assembly;
9 | }
10 | internal string Name => Assembly.GetName().Name;
11 | internal string Version => Assembly.GetName().Version.ToString();
12 | private string? sha256;
13 | internal string Sha256 {
14 | get {
15 | if (sha256 == null) {
16 | try {
17 | sha256 = Util.GenerateSHA256(File);
18 | } catch (Exception e) {
19 | Logger.ErrorInternal($"Exception calculating sha256 hash for {File}:\n{e}");
20 | sha256 = "failed to generate hash";
21 | }
22 | }
23 | return sha256;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ResoniteModLoader/AssemblyHider.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | using Elements.Core;
4 |
5 | using HarmonyLib;
6 |
7 | namespace ResoniteModLoader;
8 |
9 | internal static class AssemblyHider {
10 | ///
11 | /// Companies that indicate an assembly is part of .NET
12 | /// This list was found by debug logging the AssemblyCompany
13 | /// for all loaded assemblies.
14 | ///
15 | private readonly static HashSet knownDotNetCompanies = new List() {
16 | "Mono development team", // used by .NET stuff and Mono.Security
17 | }.Select(company => company.ToLowerInvariant()).ToHashSet();
18 |
19 | ///
20 | /// Products that indicate an assembly is part of .NET.
21 | /// This list was found by debug logging the AssemblyProductAttribute for all loaded assemblies.
22 | ///
23 | private readonly static HashSet knownDotNetProducts = new List() {
24 | "Microsoft® .NET", // used by a few System.* assemblies
25 | "Microsoft® .NET Framework", // used by most of the System.* assemblies
26 | "Mono Common Language Infrastructure", // used by mscorlib stuff
27 | }.Select(product => product.ToLowerInvariant()).ToHashSet();
28 |
29 | ///
30 | /// Assemblies that were already loaded when RML started up, minus a couple known non-assemblies.
31 | ///
32 | private static HashSet? resoniteAssemblies;
33 |
34 | ///
35 | /// Assemblies that 100% exist due to a mod
36 | ///
37 | private static HashSet? modAssemblies;
38 |
39 | ///
40 | /// .NET assembiles we want to ignore in some cases, like the callee check for the AppDomain.GetAssemblies() patch
41 | ///
42 | private static HashSet? dotNetAssemblies;
43 |
44 | ///
45 | /// Patch Resonite's type lookup code to not see mod-related types. This is needed, because users can pass
46 | /// arbitrary strings to TypeHelper.FindType(), which can be used to detect if someone is running mods.
47 | ///
48 | /// Our RML harmony instance
49 | /// Assemblies that were loaded when RML first started
50 | internal static void PatchResonite(Harmony harmony, HashSet initialAssemblies) {
51 | //if (ModLoaderConfiguration.Get().HideModTypes) {
52 | // initialize the static assembly sets that our patches will need later
53 | resoniteAssemblies = GetResoniteAssemblies(initialAssemblies);
54 | modAssemblies = GetModAssemblies(resoniteAssemblies);
55 | dotNetAssemblies = resoniteAssemblies.Where(LooksLikeDotNetAssembly).ToHashSet();
56 |
57 | // TypeHelper.FindType explicitly does a type search
58 | MethodInfo findTypeTarget = AccessTools.DeclaredMethod(typeof(TypeHelper), nameof(TypeHelper.FindType), new Type[] { typeof(string) });
59 | MethodInfo findTypePatch = AccessTools.DeclaredMethod(typeof(AssemblyHider), nameof(FindTypePostfix));
60 | harmony.Patch(findTypeTarget, postfix: new HarmonyMethod(findTypePatch));
61 |
62 | // ReflectionExtensions.IsValidGenericType checks a type for validity, and if it returns `true` it reveals that the type exists
63 | MethodInfo isValidGenericTypeTarget = AccessTools.DeclaredMethod(typeof(ReflectionExtensions), nameof(ReflectionExtensions.IsValidGenericType), new Type[] { typeof(Type), typeof(bool) });
64 | MethodInfo isValidGenericTypePatch = AccessTools.DeclaredMethod(typeof(AssemblyHider), nameof(IsValidTypePostfix));
65 | harmony.Patch(isValidGenericTypeTarget, postfix: new HarmonyMethod(isValidGenericTypePatch));
66 |
67 | // FrooxEngine likes to enumerate all types in all assemblies, which is prone to issues (such as crashing FrooxCode if a type isn't loadable)
68 | MethodInfo getAssembliesTarget = AccessTools.DeclaredMethod(typeof(AppDomain), nameof(AppDomain.GetAssemblies), Array.Empty());
69 | MethodInfo getAssembliesPatch = AccessTools.DeclaredMethod(typeof(AssemblyHider), nameof(GetAssembliesPostfix));
70 | harmony.Patch(getAssembliesTarget, postfix: new HarmonyMethod(getAssembliesPatch));
71 | //}
72 | }
73 |
74 | private static HashSet GetResoniteAssemblies(HashSet initialAssemblies) {
75 | // Remove RML itself, as its types should be hidden but it's guaranteed to be loaded.
76 | initialAssemblies.Remove(Assembly.GetExecutingAssembly());
77 |
78 | // Remove Harmony, as users who aren't using rml_libs will already have it loaded.
79 | initialAssemblies.Remove(typeof(Harmony).Assembly);
80 |
81 | return initialAssemblies;
82 | }
83 |
84 | private static HashSet GetModAssemblies(HashSet resoniteAssemblies) {
85 | // start with ALL assemblies
86 | HashSet assemblies = AppDomain.CurrentDomain.GetAssemblies().ToHashSet();
87 |
88 | // remove assemblies that we know to have come with Resonite
89 | assemblies.ExceptWith(resoniteAssemblies);
90 |
91 | // what's left are assemblies that magically appeared during the mod loading process. So mods and their dependencies.
92 | return assemblies;
93 | }
94 |
95 | ///
96 | /// Checks if an belongs to a mod or not.
97 | ///
98 | /// The to check.
99 | /// Type of root check being performed. Should be "type" or "assembly". Used in logging.
100 | /// Name of the root check being performed. Used in logging.
101 | /// If `true`, this will emit logs. If `false`, this function will not log.
102 | /// If `true`, then this function will always return `false` for late-loaded types
103 | /// `true` if this assembly belongs to a mod.
104 | private static bool IsModAssembly(Assembly assembly, string typeOrAssembly, string name, bool log, bool forceShowLate) {
105 | if (resoniteAssemblies!.Contains(assembly)) {
106 | return false; // The assembly belongs to Resonite and shouldn't be hidden
107 | } else {
108 | if (modAssemblies!.Contains(assembly)) {
109 | // The assembly belongs to a mod and should be hidden
110 | if (log) {
111 | Logger.DebugFuncInternal(() => $"Hid {typeOrAssembly} \"{name}\" from Resonite");
112 | }
113 | return true;
114 | } else {
115 | // an assembly was in neither resoniteAssemblies nor modAssemblies
116 | // this implies someone late-loaded an assembly after RML, and it was later used in-game
117 | // this is super weird, and probably shouldn't ever happen... but if it does, I want to know about it.
118 | // since this is an edge case users may want to handle in different ways, the HideLateTypes rml config option allows them to choose.
119 | //bool hideLate = true;// ModLoaderConfiguration.Get().HideLateTypes;
120 | /*if (log) {
121 | Logger.WarnInternal($"The \"{name}\" {typeOrAssembly} does not appear to part of Resonite 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")}");
122 | }*/
123 | // if forceShowLate == true, then this function will always return `false` for late-loaded types
124 | // if forceShowLate == false, then this function will return `true` when hideLate == true
125 | return !forceShowLate;
126 | }
127 | }
128 | }
129 |
130 | ///
131 | /// Checks if an belongs to a mod or not.
132 | ///
133 | /// The to check
134 | /// If true, then this function will always return false for late-loaded types.
135 | /// true if this belongs to a mod.
136 | private static bool IsModAssembly(Assembly assembly, bool forceShowLate = false) {
137 | // this generates a lot of logspam, as a single call to AppDomain.GetAssemblies() calls this many times
138 | return IsModAssembly(assembly, "assembly", assembly.ToString(), log: false, forceShowLate);
139 | }
140 |
141 | ///
142 | /// Checks if a belongs to a mod or not.
143 | ///
144 | /// The to check.
145 | /// true if this belongs to a mod.
146 | private static bool IsModType(Type type) {
147 | return IsModAssembly(type.Assembly, "type", type.ToString(), log: true, forceShowLate: false);
148 | }
149 |
150 | // postfix for a method that searches for a type, and returns a reference to it if found (TypeHelper.FindType and WorkerManager.GetType)
151 | private static void FindTypePostfix(ref Type? __result) {
152 | if (__result != null) {
153 | // we only need to think about types if the method actually returned a non-null result
154 | if (IsModType(__result)) {
155 | __result = null;
156 | }
157 | }
158 | }
159 |
160 | // postfix for a method that validates a type (ReflectionExtensions.IsValidGenericType)
161 | private static void IsValidTypePostfix(ref bool __result, Type type) {
162 | if (__result == true) {
163 | // we only need to think about types if the method actually returned a true result
164 | if (IsModType(type)) {
165 | __result = false;
166 | }
167 | }
168 | }
169 |
170 | private static void GetAssembliesPostfix(ref Assembly[] __result) {
171 | Assembly? callingAssembly = GetCallingAssembly(new(1));
172 | if (callingAssembly != null && resoniteAssemblies!.Contains(callingAssembly)) {
173 | // if we're being called by Resonite code, then hide mod assemblies
174 | Logger.DebugFuncInternal(() => $"Intercepting call to AppDomain.GetAssemblies() from {callingAssembly}");
175 | __result = __result
176 | .Where(assembly => !IsModAssembly(assembly, forceShowLate: true)) // it turns out Resonite itself late-loads a bunch of stuff, so we force-show late-loaded assemblies here
177 | .ToArray();
178 | }
179 | }
180 |
181 | ///
182 | /// Get the calling using stack trace analysis, ignoring .NET assemblies.
183 | /// This implementation is SPECIFICALLY for the patch and may not be valid for other use-cases.
184 | ///
185 | /// The stack trace captured by the callee.
186 | /// The calling , or null if none was found.
187 | private static Assembly? GetCallingAssembly(StackTrace stackTrace) {
188 | for (int i = 0; i < stackTrace.FrameCount; i++) {
189 | Assembly? assembly = stackTrace.GetFrame(i)?.GetMethod()?.DeclaringType?.Assembly;
190 | // .NET calls AppDomain.GetAssemblies() a bunch internally, and we don't want to intercept those calls UNLESS they originated from Resonite code.
191 | if (assembly != null && !dotNetAssemblies!.Contains(assembly)) {
192 | return assembly;
193 | }
194 | }
195 | return null;
196 | }
197 |
198 | private static bool LooksLikeDotNetAssembly(Assembly assembly) {
199 | // check the assembly's company
200 | string? company = assembly.GetCustomAttribute()?.Company;
201 | if (company != null && knownDotNetCompanies.Contains(company.ToLowerInvariant())) {
202 | return true;
203 | }
204 |
205 | // check the assembly's product
206 | string? product = assembly.GetCustomAttribute()?.Product;
207 | if (product != null && knownDotNetProducts.Contains(product.ToLowerInvariant())) {
208 | return true;
209 | }
210 |
211 | // nothing matched, this is probably not part of .NET
212 | return false;
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/ResoniteModLoader/AssemblyLoader.cs:
--------------------------------------------------------------------------------
1 | namespace ResoniteModLoader;
2 |
3 | internal static class AssemblyLoader {
4 | private static string[]? GetAssemblyPathsFromDir(string dirName) {
5 |
6 | string assembliesDirectory = Path.Combine(Directory.GetCurrentDirectory(), dirName);
7 |
8 | Logger.MsgInternal($"Loading assemblies from {dirName}");
9 |
10 | string[]? assembliesToLoad = null;
11 | try {
12 | // 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)
13 | // Searching for "*.dll" would return results like "foo.dll_disabled"
14 |
15 | assembliesToLoad = Directory.EnumerateFiles(assembliesDirectory, "*.dll")
16 | .Where(file => file.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase))
17 | .ToArray();
18 | Array.Sort(assembliesToLoad, string.CompareOrdinal);
19 | } catch (DirectoryNotFoundException) {
20 | Logger.MsgInternal($"{dirName} directory not found, creating it now.");
21 | try {
22 | Directory.CreateDirectory(assembliesDirectory);
23 | } catch (Exception e2) {
24 | Logger.ErrorInternal($"Error creating ${dirName} directory:\n{e2}");
25 | }
26 | } catch (Exception e) {
27 | Logger.ErrorInternal($"Error enumerating ${dirName} directory:\n{e}");
28 | }
29 | return assembliesToLoad;
30 | }
31 |
32 | private static Assembly? LoadAssembly(string filepath) {
33 | string filename = Path.GetFileName(filepath);
34 | LoadProgressIndicator.SetCustom($"Loading file: {filename}");
35 | Assembly assembly;
36 | try {
37 | Logger.DebugFuncInternal(() => $"Load assembly {filename}");
38 | assembly = Assembly.LoadFrom(filepath);
39 | } catch (Exception e) {
40 | Logger.ErrorInternal($"Error loading assembly from {filepath}: {e}");
41 | return null;
42 | }
43 | if (assembly == null) {
44 | Logger.ErrorInternal($"Unexpected null loading assembly from {filepath}");
45 | return null;
46 | }
47 | return assembly;
48 | }
49 |
50 | internal static AssemblyFile[] LoadAssembliesFromDir(string dirName) {
51 | List assemblyFiles = new();
52 | if (GetAssemblyPathsFromDir(dirName) is string[] assemblyPaths) {
53 | foreach (string assemblyFilepath in assemblyPaths) {
54 | try {
55 | if (LoadAssembly(assemblyFilepath) is Assembly assembly) {
56 | assemblyFiles.Add(new AssemblyFile(assemblyFilepath, assembly));
57 | }
58 | } catch (Exception e) {
59 | Logger.ErrorInternal($"Unexpected exception loading assembly from {assemblyFilepath}:\n{e}");
60 | }
61 | }
62 | }
63 | return assemblyFiles.ToArray();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/ResoniteModLoader/Attributes/AutoRegisterConfigKeyAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace ResoniteModLoader;
2 | ///
3 | /// Marks a field of type on a class
4 | /// deriving from to be automatically included in that mod's configuration.
5 | ///
6 | [AttributeUsage(AttributeTargets.Field)]
7 | public sealed class AutoRegisterConfigKeyAttribute : Attribute { }
8 |
--------------------------------------------------------------------------------
/ResoniteModLoader/ConfigurationChangedEvent.cs:
--------------------------------------------------------------------------------
1 | namespace ResoniteModLoader;
2 | ///
3 | /// Represents the data for the and events.
4 | ///
5 | public class ConfigurationChangedEvent {
6 | ///
7 | /// The in which the change occured.
8 | ///
9 | public ModConfiguration Config { get; private set; }
10 |
11 | ///
12 | /// The specific who's value changed.
13 | ///
14 | public ModConfigurationKey Key { get; private set; }
15 |
16 | ///
17 | /// A custom label that may be set by whoever changed the configuration.
18 | ///
19 | public string? Label { get; private set; }
20 |
21 | internal ConfigurationChangedEvent(ModConfiguration config, ModConfigurationKey key, string? label) {
22 | Config = config;
23 | Key = key;
24 | Label = label;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ResoniteModLoader/DebugInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Runtime.Versioning;
3 |
4 | namespace ResoniteModLoader;
5 |
6 | internal static class DebugInfo {
7 | internal static void Log() {
8 | Logger.MsgInternal($"ResoniteModLoader v{ModLoader.VERSION} starting up!{(ModLoaderConfiguration.Get().Debug ? " Debug logs will be shown." : "")}");
9 | Logger.DebugFuncInternal(() => $"Launched with args: {string.Join(" ", Environment.GetCommandLineArgs())}");
10 | Logger.MsgInternal($"CLR v{Environment.Version}");
11 | Logger.DebugFuncInternal(() => $"Using .NET Framework: \"{AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\"");
12 | Logger.DebugFuncInternal(() => $"Using .NET Core: \"{Assembly.GetEntryAssembly()?.GetCustomAttribute()?.FrameworkName}\"");
13 | Logger.MsgInternal($".NET Runtime: {RuntimeInformation.FrameworkDescription}");
14 | Logger.MsgInternal($"Using Harmony v{GetAssemblyVersion(typeof(HarmonyLib.Harmony))}");
15 | Logger.MsgInternal($"Using Elements.Core v{GetAssemblyVersion(typeof(Elements.Core.floatQ))}");
16 | Logger.MsgInternal($"Using FrooxEngine v{GetAssemblyVersion(typeof(FrooxEngine.IComponent))}");
17 | Logger.MsgInternal($"Using Json.NET v{GetAssemblyVersion(typeof(Newtonsoft.Json.JsonSerializer))}");
18 | }
19 |
20 | private static string? GetAssemblyVersion(Type typeFromAssembly) {
21 | return typeFromAssembly.Assembly.GetName()?.Version?.ToString();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ResoniteModLoader/DelegateExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace ResoniteModLoader;
2 |
3 | internal static class DelegateExtensions {
4 | internal static void SafeInvoke(this Delegate del, params object[] args) {
5 | var exceptions = new List();
6 |
7 | foreach (var handler in del.GetInvocationList()) {
8 | try {
9 | handler.Method.Invoke(handler.Target, args);
10 | } catch (Exception ex) {
11 | exceptions.Add(ex);
12 | }
13 | }
14 |
15 | if (exceptions.Count != 0) {
16 | throw new AggregateException(exceptions);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ResoniteModLoader/ExecutionHook.cs:
--------------------------------------------------------------------------------
1 | using FrooxEngine;
2 |
3 | namespace ResoniteModLoader;
4 |
5 | [ImplementableClass(true)]
6 | internal static class ExecutionHook {
7 | #pragma warning disable CS0169, IDE0051, CA1823, IDE0044
8 | // fields must exist due to reflective access
9 | private static Type? __connectorType;
10 | private static Type? __connectorTypes;
11 |
12 | // implementation not strictly required, but method must exist due to reflective access
13 | private static DummyConnector InstantiateConnector() {
14 | return new DummyConnector();
15 | }
16 | #pragma warning restore CS0169, IDE0051, CA1823, IDE0044
17 |
18 | static ExecutionHook() {
19 | Logger.DebugInternal($"Start of ExecutionHook");
20 | try {
21 | BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic;
22 | var byName = (Dictionary)typeof(GlobalTypeRegistry).GetField("_byName", flags).GetValue(null);
23 |
24 | var firstAsm = byName.FirstOrDefault(asm => asm.Value.Assembly == typeof(ExecutionHook).Assembly);
25 | if (firstAsm.Value != null && byName.ContainsKey(firstAsm.Key)) {
26 | Logger.DebugInternal($"Removing Assembly {firstAsm.Key} from global type registry");
27 | byName.Remove(firstAsm.Key);
28 | }
29 |
30 | HashSet initialAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToHashSet();
31 | LoadProgressIndicator.SetCustom("Loading Libraries");
32 | AssemblyFile[] loadedAssemblies = AssemblyLoader.LoadAssembliesFromDir("rml_libs");
33 | // note that harmony may not be loaded until this point, so this class cannot directly import HarmonyLib.
34 |
35 | if (loadedAssemblies.Length != 0) {
36 | string loadedAssemblyList = string.Join("\n", loadedAssemblies.Select(a => a.Name + ", Version=" + a.Version + ", Sha256=" + a.Sha256));
37 | Logger.MsgInternal($"Loaded libraries from rml_libs:\n{loadedAssemblyList}");
38 | }
39 | LoadProgressIndicator.SetCustom("Initializing");
40 | DebugInfo.Log();
41 | HarmonyWorker.LoadModsAndHideModAssemblies(initialAssemblies);
42 | LoadProgressIndicator.SetCustom("Loaded");
43 | } catch (Exception e) {
44 | // it's important that this doesn't send exceptions back to Resonite
45 | Logger.ErrorInternal($"Exception in execution hook!\n{e}");
46 | }
47 | }
48 |
49 |
50 | // type must match return type of InstantiateConnector()
51 | private sealed class DummyConnector : IConnector {
52 | public IImplementable? Owner { get; private set; }
53 | public void ApplyChanges() { }
54 | public void AssignOwner(IImplementable owner) => Owner = owner;
55 | public void Destroy(bool destroyingWorld) { }
56 | public void Initialize() { }
57 | public void RemoveOwner() => Owner = null;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/ResoniteModLoader/GlobalDirectives.cs:
--------------------------------------------------------------------------------
1 | /*
2 | // auto-generated Implicit Using Directives
3 | global using global::System;
4 | global using global::System.Collections.Generic;
5 | global using global::System.IO;
6 | global using global::System.Linq;
7 | global using global::System.Threading;
8 | global using global::System.Threading.Tasks;
9 |
10 | //System.Net.Http is a default implicit. While we are still on 4.6.2, it needs to be manually removed via csproj ``
11 | */
12 | global using System.Reflection;
13 | global using System.Text;
14 |
--------------------------------------------------------------------------------
/ResoniteModLoader/HarmonyWorker.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 |
3 | namespace ResoniteModLoader;
4 | // this class does all the harmony-related RML work.
5 | // this is needed to avoid importing harmony in ExecutionHook, where it may not be loaded yet.
6 | internal sealed class HarmonyWorker {
7 | internal static void LoadModsAndHideModAssemblies(HashSet initialAssemblies) {
8 | Harmony harmony = new("com.resonitemodloader.ResoniteModLoader");
9 | ModLoader.LoadMods();
10 | ModConfiguration.RegisterShutdownHook(harmony);
11 | AssemblyHider.PatchResonite(harmony, initialAssemblies);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ResoniteModLoader/JsonConverters/EnumConverter.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace ResoniteModLoader.JsonConverters;
4 |
5 | // serializes and deserializes enums as strings
6 | internal sealed class EnumConverter : JsonConverter {
7 | public override bool CanConvert(Type objectType) {
8 | return objectType.IsEnum;
9 | }
10 |
11 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
12 | // handle old behavior where enums were serialized as underlying type
13 | Type underlyingType = Enum.GetUnderlyingType(objectType);
14 | if (TryConvert(reader!.Value!, underlyingType, out object? deserialized)) {
15 | Logger.DebugFuncInternal(() => $"Deserializing a Core Element type: {objectType} from a {reader!.Value!.GetType()}");
16 | return deserialized!;
17 | }
18 |
19 | // handle new behavior where enums are serialized as strings
20 | if (reader.Value is string serialized) {
21 | return Enum.Parse(objectType, serialized);
22 | }
23 |
24 | throw new ArgumentException($"Could not deserialize a Core Element type: {objectType} from a {reader?.Value?.GetType()}. Expected underlying type was {underlyingType}");
25 | }
26 |
27 | public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
28 | string serialized = Enum.GetName(value!.GetType(), value);
29 | writer.WriteValue(serialized);
30 | }
31 |
32 | private static bool TryConvert(object value, Type newType, out object? converted) {
33 | try {
34 | converted = Convert.ChangeType(value, newType);
35 | return true;
36 | } catch {
37 | converted = null;
38 | return false;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/ResoniteModLoader/JsonConverters/ResonitePrimitiveConverter.cs:
--------------------------------------------------------------------------------
1 | using Elements.Core;
2 |
3 | using Newtonsoft.Json;
4 |
5 | namespace ResoniteModLoader.JsonConverters;
6 |
7 | internal sealed class ResonitePrimitiveConverter : JsonConverter {
8 | private static readonly Assembly ElementsCore = typeof(floatQ).Assembly;
9 |
10 | public override bool CanConvert(Type objectType) {
11 | // handle all non-enum Resonite Primitives in the Elements.Core assembly
12 | return !objectType.IsEnum && ElementsCore.Equals(objectType.Assembly) && Coder.IsEnginePrimitive(objectType);
13 | }
14 |
15 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
16 | if (reader.Value is string serialized) {
17 | // use Resonite's built-in decoding if the value was serialized as a string
18 | return typeof(Coder<>).MakeGenericType(objectType).GetMethod("DecodeFromString").Invoke(null, new object[] { serialized });
19 | }
20 |
21 | throw new ArgumentException($"Could not deserialize a Core Element type: {objectType} from a {reader?.Value?.GetType()}");
22 | }
23 |
24 | public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
25 | string serialized = (string)typeof(Coder<>).MakeGenericType(value!.GetType()).GetMethod("EncodeToString").Invoke(null, new object[] { value });
26 | writer.WriteValue(serialized);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ResoniteModLoader/LoadProgressIndicator.cs:
--------------------------------------------------------------------------------
1 | using FrooxEngine;
2 |
3 | namespace ResoniteModLoader;
4 |
5 | // Custom LoadProgressIndicator logic failing shouldn't stop the rest of the modloader.
6 | internal static class LoadProgressIndicator {
7 | private static bool failed;
8 |
9 | private static FieldInfo? _showSubphase;
10 | private static FieldInfo? ShowSubphase {
11 | get {
12 | if (_showSubphase is null) {
13 | try {
14 | _showSubphase = typeof(EngineLoadProgress).GetField("_showSubphase", BindingFlags.NonPublic | BindingFlags.Instance);
15 | } catch (Exception ex) {
16 | if (!failed) {
17 | Logger.WarnInternal("_showSubphase not found: " + ex.ToString());
18 | }
19 | failed = true;
20 | }
21 | }
22 | return _showSubphase;
23 | }
24 | }
25 |
26 | // Returned true means success, false means something went wrong.
27 | internal static bool SetCustom(string text) {
28 | if (ModLoaderConfiguration.Get().HideVisuals) { return true; }
29 | if (!ModLoader.IsHeadless) {
30 | ShowSubphase?.SetValue(Engine.Current.InitProgress, text);
31 | return true;
32 | }
33 | return false;
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/ResoniteModLoader/Logger.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | using Elements.Core;
4 |
5 | namespace ResoniteModLoader;
6 |
7 | internal sealed class Logger {
8 | // logged for null objects
9 | internal const string NULL_STRING = "null";
10 |
11 | internal static bool IsDebugEnabled() {
12 | #if DEBUG
13 | return true;
14 | #endif
15 | return ModLoaderConfiguration.Get().Debug;
16 | }
17 |
18 | internal static void DebugFuncInternal(Func messageProducer) {
19 | if (IsDebugEnabled()) {
20 | LogInternal(LogType.DEBUG, messageProducer());
21 | }
22 | }
23 |
24 | internal static void DebugFuncExternal(Func