├── .gitignore
├── Analyzers
├── Analyzers.csproj
├── Extensions
│ └── DebugExtensions.cs
└── Generators
│ └── ModEntrypointBoilerplateGenerator.cs
├── Directory.Build.props
├── Directory.Build.targets
├── ExampleCallMethodMod
├── ExampleCallMethodMod.csproj
├── Mod.cs
├── Patches
│ └── ConsolePatch.cs
└── Utils
│ ├── ArrayUtils.cs
│ └── CommandParser.cs
├── LICENSE
├── ModTemplateValheim.sln
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Valheim template ignores
2 | BuildTasks.[Dd]ll
3 | generated_files
4 |
5 | # Created by https://www.toptal.com/developers/gitignore/api/intellij,visualstudio,csharp,dotnetcore
6 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,visualstudio,csharp,dotnetcore
7 |
8 | ### Csharp ###
9 | ## Ignore Visual Studio temporary files, build results, and
10 | ## files generated by popular Visual Studio add-ons.
11 | ##
12 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
13 |
14 | # User-specific files
15 | *.rsuser
16 | *.suo
17 | *.user
18 | *.userosscache
19 | *.sln.docstates
20 |
21 | # User-specific files (MonoDevelop/Xamarin Studio)
22 | *.userprefs
23 |
24 | # Mono auto generated files
25 | mono_crash.*
26 |
27 | # Build results
28 | [Dd]ebug/
29 | [Dd]ebugPublic/
30 | [Rr]elease/
31 | [Rr]eleases/
32 | x64/
33 | x86/
34 | [Aa][Rr][Mm]/
35 | [Aa][Rr][Mm]64/
36 | bld/
37 | [Bb]in/
38 | [Oo]bj/
39 | [Ll]og/
40 | [Ll]ogs/
41 |
42 | # Visual Studio 2015/2017 cache/options directory
43 | .vs/
44 | # Uncomment if you have tasks that create the project's static files in wwwroot
45 | #wwwroot/
46 |
47 | # Visual Studio 2017 auto generated files
48 | Generated\ Files/
49 |
50 | # MSTest test Results
51 | [Tt]est[Rr]esult*/
52 | [Bb]uild[Ll]og.*
53 |
54 | # NUnit
55 | *.VisualState.xml
56 | TestResult.xml
57 | nunit-*.xml
58 |
59 | # Build Results of an ATL Project
60 | [Dd]ebugPS/
61 | [Rr]eleasePS/
62 | dlldata.c
63 |
64 | # Benchmark Results
65 | BenchmarkDotNet.Artifacts/
66 |
67 | # .NET Core
68 | project.lock.json
69 | project.fragment.lock.json
70 | artifacts/
71 |
72 | # StyleCop
73 | StyleCopReport.xml
74 |
75 | # Files built by Visual Studio
76 | *_i.c
77 | *_p.c
78 | *_h.h
79 | *.ilk
80 | *.meta
81 | *.obj
82 | *.iobj
83 | *.pch
84 | *.pdb
85 | *.ipdb
86 | *.pgc
87 | *.pgd
88 | *.rsp
89 | *.sbr
90 | *.tlb
91 | *.tli
92 | *.tlh
93 | *.tmp
94 | *.tmp_proj
95 | *_wpftmp.csproj
96 | *.log
97 | *.vspscc
98 | *.vssscc
99 | .builds
100 | *.pidb
101 | *.svclog
102 | *.scc
103 |
104 | # Chutzpah Test files
105 | _Chutzpah*
106 |
107 | # Visual C++ cache files
108 | ipch/
109 | *.aps
110 | *.ncb
111 | *.opendb
112 | *.opensdf
113 | *.sdf
114 | *.cachefile
115 | *.VC.db
116 | *.VC.VC.opendb
117 |
118 | # Visual Studio profiler
119 | *.psess
120 | *.vsp
121 | *.vspx
122 | *.sap
123 |
124 | # Visual Studio Trace Files
125 | *.e2e
126 |
127 | # TFS 2012 Local Workspace
128 | $tf/
129 |
130 | # Guidance Automation Toolkit
131 | *.gpState
132 |
133 | # ReSharper is a .NET coding add-in
134 | _ReSharper*/
135 | *.[Rr]e[Ss]harper
136 | *.DotSettings.user
137 |
138 | # TeamCity is a build add-in
139 | _TeamCity*
140 |
141 | # DotCover is a Code Coverage Tool
142 | *.dotCover
143 |
144 | # AxoCover is a Code Coverage Tool
145 | .axoCover/*
146 | !.axoCover/settings.json
147 |
148 | # Coverlet is a free, cross platform Code Coverage Tool
149 | coverage*[.json, .xml, .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 | # Cake - Uncomment if you are using it
321 | # tools/**
322 | # !tools/packages.config
323 |
324 | # Tabs Studio
325 | *.tss
326 |
327 | # Telerik's JustMock configuration file
328 | *.jmconfig
329 |
330 | # BizTalk build output
331 | *.btp.cs
332 | *.btm.cs
333 | *.odx.cs
334 | *.xsd.cs
335 |
336 | # OpenCover UI analysis results
337 | OpenCover/
338 |
339 | # Azure Stream Analytics local run output
340 | ASALocalRun/
341 |
342 | # MSBuild Binary and Structured Log
343 | *.binlog
344 |
345 | # NVidia Nsight GPU debugger configuration file
346 | *.nvuser
347 |
348 | # MFractors (Xamarin productivity tool) working folder
349 | .mfractor/
350 |
351 | # Local History for Visual Studio
352 | .localhistory/
353 |
354 | # BeatPulse healthcheck temp database
355 | healthchecksdb
356 |
357 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
358 | MigrationBackup/
359 |
360 | # Ionide (cross platform F# VS Code tools) working folder
361 | .ionide/
362 |
363 | ### DotnetCore ###
364 | # .NET Core build folders
365 | bin/
366 | obj/
367 |
368 | # Common node modules locations
369 | /node_modules
370 | /wwwroot/node_modules
371 |
372 | ### Intellij ###
373 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
374 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
375 |
376 | # User-specific stuff
377 | .idea/**/
378 |
379 | # Gradle and Maven with auto-import
380 | # When using Gradle or Maven with auto-import, you should exclude module files,
381 | # since they will be recreated, and may cause churn. Uncomment if using
382 | # auto-import.
383 | # .idea/artifacts
384 | # .idea/compiler.xml
385 | # .idea/jarRepositories.xml
386 | # .idea/modules.xml
387 | # .idea/*.iml
388 | # .idea/modules
389 | # *.iml
390 | # *.ipr
391 |
392 | # CMake
393 | cmake-build-*/
394 |
395 | # Mongo Explorer plugin
396 | .idea/**/mongoSettings.xml
397 |
398 | # File-based project format
399 | *.iws
400 |
401 | # IntelliJ
402 | out/
403 |
404 | # mpeltonen/sbt-idea plugin
405 | .idea_modules/
406 |
407 | # JIRA plugin
408 | atlassian-ide-plugin.xml
409 |
410 | # Cursive Clojure plugin
411 | .idea/replstate.xml
412 |
413 | # Crashlytics plugin (for Android Studio and IntelliJ)
414 | com_crashlytics_export_strings.xml
415 | crashlytics.properties
416 | crashlytics-build.properties
417 | fabric.properties
418 |
419 | # Editor-based Rest Client
420 | .idea/httpRequests
421 |
422 | # Android studio 3.1+ serialized cache file
423 | .idea/caches/build_file_checksums.ser
424 |
425 | ### Intellij Patch ###
426 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
427 |
428 | # *.iml
429 | # modules.xml
430 | # .idea/misc.xml
431 | # *.ipr
432 |
433 | # Sonarlint plugin
434 | # https://plugins.jetbrains.com/plugin/7973-sonarlint
435 | .idea/**/sonarlint/
436 |
437 | # SonarQube Plugin
438 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
439 | .idea/**/sonarIssues.xml
440 |
441 | # Markdown Navigator plugin
442 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
443 | .idea/**/markdown-navigator.xml
444 | .idea/**/markdown-navigator-enh.xml
445 | .idea/**/markdown-navigator/
446 |
447 | # Cache file creation bug
448 | # See https://youtrack.jetbrains.com/issue/JBR-2257
449 | .idea/$CACHE_FILE$
450 |
451 | # CodeStream plugin
452 | # https://plugins.jetbrains.com/plugin/12206-codestream
453 | .idea/codestream.xml
454 |
455 | ### VisualStudio ###
456 |
457 | # User-specific files
458 |
459 | # User-specific files (MonoDevelop/Xamarin Studio)
460 |
461 | # Mono auto generated files
462 |
463 | # Build results
464 |
465 | # Visual Studio 2015/2017 cache/options directory
466 | # Uncomment if you have tasks that create the project's static files in wwwroot
467 |
468 | # Visual Studio 2017 auto generated files
469 |
470 | # MSTest test Results
471 |
472 | # NUnit
473 |
474 | # Build Results of an ATL Project
475 |
476 | # Benchmark Results
477 |
478 | # .NET Core
479 |
480 | # StyleCop
481 |
482 | # Files built by Visual Studio
483 |
484 | # Chutzpah Test files
485 |
486 | # Visual C++ cache files
487 |
488 | # Visual Studio profiler
489 |
490 | # Visual Studio Trace Files
491 |
492 | # TFS 2012 Local Workspace
493 |
494 | # Guidance Automation Toolkit
495 |
496 | # ReSharper is a .NET coding add-in
497 |
498 | # TeamCity is a build add-in
499 |
500 | # DotCover is a Code Coverage Tool
501 |
502 | # AxoCover is a Code Coverage Tool
503 |
504 | # Coverlet is a free, cross platform Code Coverage Tool
505 |
506 | # Visual Studio code coverage results
507 |
508 | # NCrunch
509 |
510 | # MightyMoose
511 |
512 | # Web workbench (sass)
513 |
514 | # Installshield output folder
515 |
516 | # DocProject is a documentation generator add-in
517 |
518 | # Click-Once directory
519 |
520 | # Publish Web Output
521 | # Note: Comment the next line if you want to checkin your web deploy settings,
522 | # but database connection strings (with potential passwords) will be unencrypted
523 |
524 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
525 | # checkin your Azure Web App publish settings, but sensitive information contained
526 | # in these scripts will be unencrypted
527 |
528 | # NuGet Packages
529 | # NuGet Symbol Packages
530 | # The packages folder can be ignored because of Package Restore
531 | # except build/, which is used as an MSBuild target.
532 | # Uncomment if necessary however generally it will be regenerated when needed
533 | # NuGet v3's project.json files produces more ignorable files
534 |
535 | # Microsoft Azure Build Output
536 |
537 | # Microsoft Azure Emulator
538 |
539 | # Windows Store app package directories and files
540 |
541 | # Visual Studio cache files
542 | # files ending in .cache can be ignored
543 | # but keep track of directories ending in .cache
544 |
545 | # Others
546 |
547 | # Including strong name files can present a security risk
548 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
549 |
550 | # Since there are multiple workflows, uncomment next line to ignore bower_components
551 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
552 |
553 | # RIA/Silverlight projects
554 |
555 | # Backup & report files from converting an old project file
556 | # to a newer Visual Studio version. Backup files are not needed,
557 | # because we have git ;-)
558 |
559 | # SQL Server files
560 |
561 | # Business Intelligence projects
562 |
563 | # Microsoft Fakes
564 |
565 | # GhostDoc plugin setting file
566 |
567 | # Node.js Tools for Visual Studio
568 |
569 | # Visual Studio 6 build log
570 |
571 | # Visual Studio 6 workspace options file
572 |
573 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
574 |
575 | # Visual Studio LightSwitch build output
576 |
577 | # Paket dependency manager
578 |
579 | # FAKE - F# Make
580 |
581 | # CodeRush personal settings
582 |
583 | # Python Tools for Visual Studio (PTVS)
584 |
585 | # Cake - Uncomment if you are using it
586 | # tools/**
587 | # !tools/packages.config
588 |
589 | # Tabs Studio
590 |
591 | # Telerik's JustMock configuration file
592 |
593 | # BizTalk build output
594 |
595 | # OpenCover UI analysis results
596 |
597 | # Azure Stream Analytics local run output
598 |
599 | # MSBuild Binary and Structured Log
600 |
601 | # NVidia Nsight GPU debugger configuration file
602 |
603 | # MFractors (Xamarin productivity tool) working folder
604 |
605 | # Local History for Visual Studio
606 |
607 | # BeatPulse healthcheck temp database
608 |
609 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
610 |
611 | # Ionide (cross platform F# VS Code tools) working folder
612 |
613 | # End of https://www.toptal.com/developers/gitignore/api/intellij,visualstudio,csharp,dotnetcore
614 |
--------------------------------------------------------------------------------
/Analyzers/Analyzers.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | netstandard2.0
6 | Analyzers
7 | enable
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Analyzers/Extensions/DebugExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Threading.Tasks;
6 |
7 | namespace Analyzers.Extensions;
8 |
9 | public static class DebugExtensions
10 | {
11 | private static readonly string DesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
12 | private static readonly ConcurrentQueue<(object source, string message)> LogQueue = new();
13 | private static readonly object LogLocker = new();
14 |
15 | ///
16 | /// Can be used to test analyzers.
17 | ///
18 | [Conditional("DEBUG")]
19 | public static void Log(this object analyzer, string message)
20 | {
21 | LogQueue.Enqueue((analyzer, message));
22 | Task.Run(() =>
23 | {
24 | while (!LogQueue.IsEmpty)
25 | {
26 | if (!LogQueue.TryDequeue(out (object source, string message) pair))
27 | {
28 | continue;
29 | }
30 |
31 | lock (LogLocker)
32 | {
33 | File.AppendAllText(Path.Combine(DesktopPath, $"{pair.source.GetType().Name}.log"), pair.message + Environment.NewLine);
34 | }
35 | }
36 | });
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Analyzers/Generators/ModEntrypointBoilerplateGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Immutable;
3 | using System.Linq;
4 | using System.Text;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CSharp;
7 | using Microsoft.CodeAnalysis.CSharp.Syntax;
8 |
9 | namespace Analyzers.Generators;
10 |
11 | ///
12 | /// Wires up mod entrypoint types with attribute and info required by BepInEx.
13 | ///
14 | [Generator(LanguageNames.CSharp)]
15 | public class ModMetadataGenerator : IIncrementalGenerator
16 | {
17 | public void Initialize(IncrementalGeneratorInitializationContext context)
18 | {
19 | // Setup pipeline that targets BepInEx mod classes.
20 | IncrementalValuesProvider modEntrypoints = context.SyntaxProvider
21 | .CreateSyntaxProvider(static (node, _) => IsPartialClass(node), static (syntaxContext, _) => TransformAsBepinexEntrypoint(syntaxContext))
22 | .Where(i => i != null)!;
23 | IncrementalValueProvider<(Compilation Compilation, ImmutableArray Nodes)> compilationAndClasses =
24 | context.CompilationProvider.Combine(modEntrypoints.Collect());
25 | context.RegisterSourceOutput(compilationAndClasses, static (context, source) => Execute(context, source.Compilation, source.Nodes));
26 | }
27 |
28 | private static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray modClasses)
29 | {
30 | if (modClasses.IsDefaultOrEmpty)
31 | {
32 | return;
33 | }
34 |
35 | (string[] authors, string modName, string version) = ExtractMetadataFromAssembly(compilation.Assembly);
36 | foreach (BepInExModData modData in modClasses)
37 | {
38 | string sourceFileName = modData.IsGlobalNamespace ? $"{modData.ClassName}.g.cs" : $"{modData.ContainingNamespace}.{modData.ClassName}.g.cs";
39 | context.AddSource(sourceFileName, $@"
40 | using BepInEx;
41 | using HarmonyLib;
42 |
43 | {(modData.IsGlobalNamespace ? "" : $"namespace {modData.ContainingNamespace};{Environment.NewLine}")}
44 | [BepInPlugin(PluginGuid, PluginName, PluginVersion)]
45 | public partial class {modData.ClassName}
46 | {{
47 | public const string PluginAuthor = ""{string.Join(" & ", authors)}"";
48 | public const string PluginGuid = ""com.github.{CleanupName(authors.FirstOrDefault() ?? modName).ToLowerInvariant()}.{CleanupName(modName)}"";
49 | public const string PluginName = ""{modName.Trim()}"";
50 | public const string PluginVersion = ""{version}"";
51 | }}
52 | ");
53 | }
54 | }
55 |
56 | private static bool IsPartialClass(SyntaxNode node)
57 | {
58 | if (node is not ClassDeclarationSyntax clazz)
59 | {
60 | return false;
61 | }
62 | foreach (SyntaxToken modifier in clazz.Modifiers)
63 | {
64 | if (modifier.IsKind(SyntaxKind.PartialKeyword))
65 | {
66 | return true;
67 | }
68 | }
69 | return false;
70 | }
71 |
72 | private static BepInExModData? TransformAsBepinexEntrypoint(GeneratorSyntaxContext context)
73 | {
74 | ClassDeclarationSyntax modClass = (ClassDeclarationSyntax)context.Node;
75 | if (ModelExtensions.GetDeclaredSymbol(context.SemanticModel, modClass) is not ITypeSymbol classTypeSymbol)
76 | {
77 | return null;
78 | }
79 | if (classTypeSymbol.BaseType is not { Name: "BaseUnityPlugin", ContainingNamespace.Name: "BepInEx" })
80 | {
81 | return null;
82 | }
83 |
84 | return new BepInExModData
85 | {
86 | ClassName = classTypeSymbol.Name,
87 | ContainingNamespace = classTypeSymbol.ContainingNamespace.ToString(),
88 | IsGlobalNamespace = classTypeSymbol.ContainingNamespace.IsGlobalNamespace
89 | };
90 | }
91 |
92 | private static (string[] authors, string modName, string version) ExtractMetadataFromAssembly(IAssemblySymbol assembly)
93 | {
94 | var author = "";
95 | var version = "1.0.0.0";
96 | var modName = "";
97 | foreach (AttributeData attribute in assembly.GetAttributes())
98 | {
99 | switch (attribute.AttributeClass?.Name)
100 | {
101 | case "AssemblyTitleAttribute":
102 | modName = attribute.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? "";
103 | break;
104 | case "AssemblyCompanyAttribute":
105 | author = attribute.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? "";
106 | break;
107 | case "AssemblyVersionAttribute":
108 | version = attribute.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? "1.0.0.0";
109 | break;
110 | }
111 | }
112 | return (author.Split(';'), modName, version);
113 | }
114 |
115 | private static string CleanupName(string name)
116 | {
117 | StringBuilder sb = new();
118 | foreach (var c in name)
119 | {
120 | object? newValue = c switch
121 | {
122 | >= '0' and <= '9' when sb.Length > 1 => c, // Skip numbers if they're at the start.
123 | >= 'a' and <= 'z' => c,
124 | >= 'A' and <= 'Z' => c,
125 | _ => null
126 | };
127 | if (newValue != null)
128 | {
129 | sb.Append(newValue);
130 | }
131 | }
132 | return sb.ToString();
133 | }
134 |
135 | public record BepInExModData
136 | {
137 | public bool IsGlobalNamespace { get; set; }
138 | public string ClassName { get; set; } = "";
139 | public string ContainingNamespace { get; set; } = "";
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 11
4 | false
5 |
6 |
7 |
8 |
9 | Library
10 | net472
11 | $(MSBuildProjectName)
12 |
13 | true
14 | Valheim
15 | https://thunderstore.io/package/download/denikson/BepInExPack_Valheim/5.4.2202/
16 |
17 |
18 |
19 |
20 |
21 |
22 | all
23 | build; native; contentfiles; analyzers
24 |
25 |
26 |
27 | all
28 | build; native; contentfiles; analyzers
29 |
30 |
31 |
32 |
33 |
34 | all
35 | runtime; build; native; contentfiles; analyzers; buildtransitive
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | $(GameDir)\
11 | $(GameDir)valheim_Data\Managed\
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | $(GameDir)BepInEx\core\0Harmony.dll
59 | False
60 |
61 |
62 | $(GameDir)BepInEx\core\BepInEx.dll
63 | False
64 |
65 |
66 |
67 | $(GameManagedDir)assembly_valheim.dll
68 | False
69 |
70 |
71 | $(GameManagedDir)assembly_googleanalytics.dll
72 | False
73 |
74 |
75 | $(GameManagedDir)assembly_guiutils.dll
76 | False
77 |
78 |
79 | $(GameManagedDir)gui_framework.dll
80 | False
81 |
82 |
83 | $(GameManagedDir)assembly_lux.dll
84 | False
85 |
86 |
87 | $(GameManagedDir)assembly_postprocessing.dll
88 | False
89 |
90 |
91 | $(GameManagedDir)assembly_simplemeshcombine.dll
92 | False
93 |
94 |
95 | $(GameManagedDir)assembly_sunshafts.dll
96 | False
97 |
98 |
99 | $(GameManagedDir)assembly_utils.dll
100 | False
101 |
102 |
103 |
104 | $(GameManagedDir)Mono.Security.dll
105 | False
106 |
107 |
108 | $(GameManagedDir)UnityEngine.dll
109 | False
110 |
111 |
112 | $(GameManagedDir)UnityEngine.AIModule.dll
113 | False
114 |
115 |
116 | $(GameManagedDir)UnityEngine.AndroidJNIModule.dll
117 | False
118 |
119 |
120 | $(GameManagedDir)UnityEngine.AnimationModule.dll
121 | False
122 |
123 |
124 | $(GameManagedDir)UnityEngine.AssetBundleModule.dll
125 | False
126 |
127 |
128 | $(GameManagedDir)UnityEngine.AudioModule.dll
129 | False
130 |
131 |
132 | $(GameManagedDir)UnityEngine.ClothModule.dll
133 | False
134 |
135 |
136 | $(GameManagedDir)UnityEngine.CoreModule.dll
137 | False
138 |
139 |
140 | $(GameManagedDir)UnityEngine.DirectorModule.dll
141 | False
142 |
143 |
144 | $(GameManagedDir)UnityEngine.GridModule.dll
145 | False
146 |
147 |
148 | $(GameManagedDir)UnityEngine.IMGUIModule.dll
149 | False
150 |
151 |
152 | $(GameManagedDir)UnityEngine.InputLegacyModule.dll
153 | False
154 |
155 |
156 | $(GameManagedDir)UnityEngine.InputModule.dll
157 | False
158 |
159 |
160 | $(GameManagedDir)UnityEngine.ParticleSystemModule.dll
161 | False
162 |
163 |
164 | $(GameManagedDir)UnityEngine.Physics2DModule.dll
165 | False
166 |
167 |
168 | $(GameManagedDir)UnityEngine.PhysicsModule.dll
169 | False
170 |
171 |
172 | $(GameManagedDir)UnityEngine.ScreenCaptureModule.dll
173 | False
174 |
175 |
176 | $(GameManagedDir)UnityEngine.SharedInternalsModule.dll
177 | False
178 |
179 |
180 | $(GameManagedDir)UnityEngine.SubsystemsModule.dll
181 | False
182 |
183 |
184 | $(GameManagedDir)UnityEngine.TerrainModule.dll
185 | False
186 |
187 |
188 | $(GameManagedDir)UnityEngine.TextRenderingModule.dll
189 | False
190 |
191 |
192 | $(GameManagedDir)UnityEngine.TilemapModule.dll
193 | False
194 |
195 |
196 | $(GameManagedDir)UnityEngine.UI.dll
197 | False
198 |
199 |
200 | $(GameManagedDir)UnityEngine.UIModule.dll
201 | False
202 |
203 |
204 | $(GameManagedDir)UnityEngine.UnityAnalyticsModule.dll
205 | False
206 |
207 |
208 | $(GameManagedDir)UnityEngine.UnityWebRequestModule.dll
209 | False
210 |
211 |
212 | $(GameManagedDir)UnityEngine.UnityWebRequestWWWModule.dll
213 | False
214 |
215 |
216 | $(GameManagedDir)UnityEngine.VFXModule.dll
217 | False
218 |
219 |
220 | $(GameManagedDir)UnityEngine.VideoModule.dll
221 | False
222 |
223 |
224 | $(GameManagedDir)UnityEngine.VRModule.dll
225 | False
226 |
227 |
228 | $(GameManagedDir)UnityEngine.XRModule.dll
229 | False
230 |
231 |
232 | $(GameManagedDir)Unity.TextMeshPro.dll
233 | False
234 |
235 |
236 |
237 |
238 |
--------------------------------------------------------------------------------
/ExampleCallMethodMod/ExampleCallMethodMod.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/ExampleCallMethodMod/Mod.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using HarmonyLib;
3 |
4 | namespace ExampleCallMethodMod;
5 |
6 | public partial class Mod : BaseUnityPlugin
7 | {
8 | private static readonly Harmony harmony = new(PluginGuid);
9 |
10 | private void Awake()
11 | {
12 | // Add or change your patches in "Patches" folder.
13 | harmony.PatchAll();
14 | }
15 |
16 | private void OnDestroy()
17 | {
18 | harmony.UnpatchSelf();
19 | }
20 | }
--------------------------------------------------------------------------------
/ExampleCallMethodMod/Patches/ConsolePatch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Reflection;
6 | using ExampleCallMethodMod.Utils;
7 | using HarmonyLib;
8 |
9 | namespace ExampleCallMethodMod.Patches;
10 |
11 | ///
12 | /// TODO: Remove this patch and write your mod :)
13 | /// Example harmony Prefix patch for in-game console input.
14 | ///
15 | public static class ConsolePatch
16 | {
17 | ///
18 | /// Press F5 to show in-game console.
19 | ///
20 | [HarmonyPatch(typeof(Console), nameof(Console.IsConsoleEnabled))]
21 | public static class ConsoleEnabled
22 | {
23 | public static bool Prefix(ref bool __result)
24 | {
25 | __result = true; // Change console to always be on
26 | return false; // false = ignore original code
27 | }
28 | }
29 |
30 | [HarmonyPatch(typeof(Terminal), nameof(Terminal.InputText))]
31 | public static class InputText
32 | {
33 | public static bool Prefix()
34 | {
35 | Console console = Console.instance;
36 | var input = console.m_input.text;
37 | if (!input.StartsWith("call", true, CultureInfo.InvariantCulture))
38 | {
39 | return true;
40 | }
41 | var parts = CommandParser.Parse(input).ToArray();
42 | if (parts.Length < 3)
43 | {
44 | console.AddString(
45 | "call command requires at least 3 arguments: call [arg1] [arg2]");
46 | return false;
47 | }
48 | console.AddString(input);
49 | Type targetClass = typeof(Console).Assembly.GetTypes()
50 | .FirstOrDefault(t => t.Name.Equals(parts[1].Text, StringComparison.InvariantCultureIgnoreCase));
51 | if (targetClass == null)
52 | {
53 | console.AddString($"Could not find class with name '{parts[1]}'");
54 | return false;
55 | }
56 | MethodInfo method = targetClass.GetMethods(BindingFlags.Public |
57 | BindingFlags.Static |
58 | BindingFlags.NonPublic |
59 | BindingFlags.Instance |
60 | BindingFlags.InvokeMethod)
61 | .FirstOrDefault(m => m.Name.Equals(parts[2].Text, StringComparison.InvariantCultureIgnoreCase));
62 | if (method == null)
63 | {
64 | console.AddString($"Could not find method '{parts[2].Text}' on class '{targetClass.FullName}'");
65 | return false;
66 | }
67 | // Parameters must match target method.
68 | var methodParams = method.GetParameters();
69 | if (methodParams.Length != parts.Length - 3) // -3 for command, class and method name args
70 | {
71 | console.AddString(
72 | $"Command does not match method parameters. Expected parameter types: {string.Join(", ", methodParams.Select(p => p.ParameterType.Name))}");
73 | return false;
74 | }
75 | var methodArgsToSupply = new object[methodParams.Length];
76 | for (var i = 0; i < methodParams.Length; i++)
77 | {
78 | var argText = parts[i + 3].Text;
79 | Type paramType = methodParams[i].ParameterType;
80 | try
81 | {
82 | methodArgsToSupply[i] = TypeDescriptor.GetConverter(paramType).ConvertFrom(argText);
83 | }
84 | catch (NotSupportedException)
85 | {
86 | console.AddString(
87 | $"Cannot convert argument {i + 1} '{argText}' to type of '{paramType.FullName}'");
88 | return false;
89 | }
90 | }
91 | // Handle instance method calls by getting singleton instance (if possible).
92 | object targetInstance = null;
93 | if (!method.IsStatic)
94 | {
95 | targetInstance = targetClass == typeof(Player)
96 | ? Player.m_localPlayer
97 | : targetClass.GetField("m_instance", BindingFlags.Static | BindingFlags.NonPublic)
98 | ?.GetValue(null);
99 | if (targetInstance == null)
100 | {
101 | console.AddString(
102 | $"Could not call method '{method.Name}' because it's an instance method and no static instance field is defined on it");
103 | return false;
104 | }
105 | }
106 |
107 | // Call method, print result.
108 | var result = method.Invoke(targetInstance, methodArgsToSupply);
109 | if (result != null)
110 | {
111 | console.AddString(string.Join(", ", ArrayUtils.ToStringArray(result)));
112 | }
113 | return false;
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/ExampleCallMethodMod/Utils/ArrayUtils.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace ExampleCallMethodMod.Utils;
6 |
7 | public static class ArrayUtils
8 | {
9 | public const string NullString = "";
10 |
11 | public static IEnumerable