├── .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 ToArray(object obj) 12 | { 13 | IEnumerable asArray = obj as IEnumerable; 14 | if (asArray == null) 15 | { 16 | if (obj == null) 17 | { 18 | return Enumerable.Empty(); 19 | } 20 | return new[] { obj }; 21 | } 22 | return asArray.Cast(); 23 | } 24 | 25 | public static IEnumerable ToStringArray(object obj) 26 | { 27 | return ToArray(obj).Select(o => o?.ToString() ?? NullString); 28 | } 29 | } -------------------------------------------------------------------------------- /ExampleCallMethodMod/Utils/CommandParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace ExampleCallMethodMod.Utils; 7 | 8 | /// 9 | /// Example command parser. Can be removed. 10 | /// 11 | public static class CommandParser 12 | { 13 | private static readonly Func[] segmentTakers = 14 | { 15 | TakeInt, 16 | TakeFloat, 17 | TakeString, 18 | TakeIdentifier 19 | }; 20 | 21 | public enum SegmentType 22 | { 23 | Unknown, 24 | Int, 25 | Float, 26 | String, 27 | Identifier 28 | } 29 | 30 | public static IEnumerable Parse(string input) 31 | { 32 | if (string.IsNullOrWhiteSpace(input)) 33 | { 34 | yield break; 35 | } 36 | var index = 0; 37 | TakerResult takerResult; 38 | while ((takerResult = TakeSegment(input, index)) != null) 39 | { 40 | yield return takerResult.Segment; 41 | index = takerResult.Offset; 42 | } 43 | } 44 | 45 | private static TakerResult TakeSegment(string input, int offset, SegmentType type = SegmentType.Unknown) 46 | { 47 | while (true) 48 | { 49 | if (offset >= input.Length) 50 | { 51 | return null; 52 | } 53 | if (input[offset] != ' ') 54 | { 55 | break; 56 | } 57 | offset++; 58 | } 59 | 60 | var start = offset; 61 | TakerResult takerResult = null; 62 | foreach (var taker in segmentTakers) 63 | { 64 | takerResult = taker(input, offset); 65 | if (takerResult?.Segment != null) 66 | { 67 | break; 68 | } 69 | } 70 | return takerResult ?? new TakerResult(new CommandSegment(SegmentType.Unknown, input.Substring(start))); 71 | } 72 | 73 | private static TakerResult TakeIdentifier(string input, int offset) 74 | { 75 | Match match = Regex.Match(input.Substring(offset), @"^\w+"); 76 | CommandSegment segment = match.Success ? new CommandSegment(SegmentType.Identifier, match.Value) : null; 77 | return new TakerResult(segment, offset + segment?.Text.Length ?? offset); 78 | } 79 | 80 | private static TakerResult TakeInt(string input, int offset) 81 | { 82 | Match match = Regex.Match(input.Substring(offset), @"^\-?\d+"); 83 | CommandSegment segment = match.Success ? new CommandSegment(SegmentType.Int, match.Value) : null; 84 | return new TakerResult(segment, offset + segment?.Text.Length ?? offset); 85 | } 86 | 87 | private static TakerResult TakeFloat(string input, int offset) 88 | { 89 | Match match = Regex.Match(input.Substring(offset), @"^\-?\d+(?:\.\d+)?"); 90 | CommandSegment segment = match.Success ? new CommandSegment(SegmentType.Float, match.Value) : null; 91 | return new TakerResult(segment, offset + segment?.Text.Length ?? offset); 92 | } 93 | 94 | private static TakerResult TakeString(string input, int offset) 95 | { 96 | if (input[offset] != '"') 97 | { 98 | return null; 99 | } 100 | 101 | StringBuilder sb = new(); 102 | offset++; 103 | while (offset < input.Length) 104 | { 105 | if (input[offset] == '\\' && offset + 1 < input.Length) 106 | { 107 | // TODO: Special handling when '\t', '\n' or other. 108 | sb.Append(input[offset + 1]); 109 | offset += 2; 110 | continue; 111 | } 112 | if (input[offset] == '"') 113 | { 114 | offset++; 115 | break; 116 | } 117 | 118 | sb.Append(input[offset]); 119 | offset++; 120 | } 121 | return new TakerResult(new CommandSegment(SegmentType.String, sb.ToString()), offset); 122 | } 123 | 124 | public class CommandSegment 125 | { 126 | public CommandSegment(SegmentType type, string text) 127 | { 128 | Type = type; 129 | Text = text ?? ""; 130 | } 131 | 132 | public SegmentType Type { get; } 133 | public string Text { get; } 134 | 135 | public override string ToString() 136 | { 137 | return Text; 138 | } 139 | } 140 | 141 | public class TakerResult 142 | { 143 | public TakerResult(CommandSegment segment) 144 | { 145 | Segment = segment; 146 | Offset = segment?.Text.Length ?? 0; 147 | } 148 | 149 | public TakerResult(CommandSegment segment, int offset) : this(segment) 150 | { 151 | Segment = segment; 152 | Offset = offset; 153 | } 154 | 155 | public CommandSegment Segment { get; } 156 | public int Offset { get; } 157 | } 158 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Measurity 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ModTemplateValheim.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleCallMethodMod", "ExampleCallMethodMod\ExampleCallMethodMod.csproj", "{3B95EEF7-4A97-4393-91A0-3626434F3631}" 4 | EndProject 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{FEF26A41-3A1C-4F98-AF14-9D30E2E3F35A}" 6 | ProjectSection(SolutionItems) = preProject 7 | Directory.Build.props = Directory.Build.props 8 | README.md = README.md 9 | Directory.Build.targets = Directory.Build.targets 10 | .gitignore = .gitignore 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Analyzers", "Analyzers\Analyzers.csproj", "{516DA37E-BADB-463E-A094-E29478203E35}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Release|Any CPU = Release|Any CPU 18 | Debug|Any CPU = Debug|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {3B95EEF7-4A97-4393-91A0-3626434F3631}.Release|Any CPU.ActiveCfg = Debug|Any CPU 22 | {3B95EEF7-4A97-4393-91A0-3626434F3631}.Release|Any CPU.Build.0 = Debug|Any CPU 23 | {3B95EEF7-4A97-4393-91A0-3626434F3631}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {3B95EEF7-4A97-4393-91A0-3626434F3631}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {516DA37E-BADB-463E-A094-E29478203E35}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {516DA37E-BADB-463E-A094-E29478203E35}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {516DA37E-BADB-463E-A094-E29478203E35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {516DA37E-BADB-463E-A094-E29478203E35}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModTemplateValheim 2 | Template mod for Valheim using Bepinex. Works for Steam on either Windows or Linux systems. 3 | Everything required is **automatically installed into Valheim**: 4 | - BepInEx (Unstripped UnityEngine dlls too) + HarmonyX 5 | - Publicizes dlls from game as project dependencies 6 | - Moves your mod into the BepInEx plugins folder. 7 | 8 | ## How to use template 9 | 1. Download or clone this repository then open in Visual Studio or JetBrains Rider. 10 | 2. (optional) Rename the `ExampleCallMethodMod.csproj` to your mod name. 11 | 3. Rebuild the solution. 12 | 4. If there are errors, restart your IDE. 13 | 5. Play Valheim to test your mod! 14 | 15 | Any project in this solution that ends with `Mod` will automatically install itself into Valheim and reference the game code. 16 | 17 | ## Requirements 18 | - [MSBuild 16+](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022) (also installed with Visual Studio) 19 | - If you want modern C# lang support you need https://dotnet.microsoft.com/en-us/download as well. See Directory.Build.props file to change language version. 20 | 21 | ## Troubleshooting 22 | - **Missing dependencies** 23 | - Run rebuild again 24 | - **Code is red - IntelliSense broken** 25 | - Restart your IDE 26 | 27 | ## I want to.. 28 | 29 | - **Rename my mod** 30 | Add (or change) the `AssemblyName` property to the mod's .csproj file. Note to run `clean` on this solution beforehand to uninstall your mods. Example: 31 | ```xml 32 | 33 | 34 | My cool mod name 35 | 36 | 37 | ``` 38 | - **Change mod author or version** 39 | Add (or change) the `Authors` property to the mod's .csproj file. Example: 40 | ```xml 41 | 42 | 43 | Measurity;geocine;eai04191 44 | My cool mod - the second 45 | 1.0.0.1 46 | 47 | 48 | ``` 49 | Which will generate the BepInEx plugin metadata as: 50 | ```cs 51 | public const string PluginAuthor = "Measurity & geocine & eai04191"; 52 | public const string PluginGuid = "com.github.measurity.Mycoolmodthesecond"; 53 | public const string PluginName = "My cool mod - the second"; 54 | public const string PluginVersion = "1.0.0.1"; 55 | ``` 56 | 57 | ## Credits 58 | https://github.com/MrPurple6411 - First to make a proper template. Used their declared project dependencies as a base. 59 | https://github.com/sebastienvercammen - Providing help with overriding game DLLs with unstripped DLLs through UnityDoorstop. 60 | https://github.com/js6pak - Primary author of BepInEx.AssemblyPublicizer.MSBuild which was a big help in figuring out MSBuild tasks. 61 | 62 | And thanks to all contributors! 63 | --------------------------------------------------------------------------------