From 31c3da28b57864665702dea6b18d5507415469fd Mon Sep 17 00:00:00 2001
From: Gareth Parker
Date: Fri, 10 May 2024 18:31:40 +0100
Subject: [PATCH 1/5] Add support for Parent="" (#45)
---
CHANGELOG.md | 3 +
README.md | 2 +
.../References/RimworldReferenceProvider.cs | 22 ++++--
.../RimworldXMLItemProvider.cs | 56 ++++++++++---
.../SymbolScope/RimworldSymbolScope.cs | 79 ++++++++++++++++---
5 files changed, 136 insertions(+), 26 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 51f246f..3bf051c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog
+## 2024.2
+* Adds support for [Parent=""] attributes
+
## 2024.1.7
* Solved some incompatibilities for ReSharper
diff --git a/README.md b/README.md
index 2a5257b..b44ead2 100644
--- a/README.md
+++ b/README.md
@@ -19,12 +19,14 @@ into the definitions on which the XML sits.
* Autocomplete DefNames when using `DefDatabase.GetNamed()`
* Autocomplete DefNames when creating fields in `[DefOf]` classes
* Autocomplete certain values for properties with fixed options (Such as Altitude Layer, boolean and directions)
+ * Autocompletion for `Parent=""` attributes
* Use `Ctrl+Click` to go references
* When using them on DefTypes, just to the C# class for that Def
* When using them on XML Properties, jump to the C# definition for that property
* When using them on DefName references, jump to the XML definition for that DefName
* When using them on certain XML values, jump to the C# definition for that value
* When using them on `[DefOf]` fields, or `DefDatabase.GetNamed()` calls, jump to the XML definition for that DefName
+ * When using them on `Parent=""` attributes, jump to the XML definition for that parent
* Read the values in `Class=""` attributes to fetch the correct class to autocomplete from, such as in comps
* Support for Custom Def Classes (Such as ``)
* A Rimworld Run Configuration
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs
index f3f43bc..d24c37e 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs
@@ -50,6 +50,12 @@ public ReferenceCollection GetReferences(ITreeNode element, ReferenceCollection
element.Parent.GetText().StartsWith(""))
return GetReferenceForDeclaredElement(element, oldReferences);
+ if (element.Parent != null && element.NodeType.ToString() == "STRING" &&
+ (element.Parent.GetText().StartsWith("Name") || element.Parent.GetText().StartsWith("ParentName")))
+ {
+ return GetReferenceForDeclaredElement(element, oldReferences);
+ }
+
if (element.NodeType.ToString() == "TEXT") return GetReferencesForText(element, oldReferences);
if (element is not XmlIdentifier identifier) return new ReferenceCollection();
if (element.GetSourceFile() is not { } sourceFile) return new ReferenceCollection();
@@ -129,7 +135,6 @@ private ReferenceCollection GetReferencesForText(ITreeNode element, ReferenceCol
if (classContext == null) return new ReferenceCollection();
var rimworldSymbolScope = ScopeHelper.RimworldScope;
- var allSymbolScopes = ScopeHelper.AllScopes;
if (classContext.GetType().Name == "Enum")
{
@@ -137,7 +142,6 @@ private ReferenceCollection GetReferencesForText(ITreeNode element, ReferenceCol
var col = new ReferenceCollection(new RimworldXmlReference(@class, element));
return col;
- // return new ReferenceCollection();
}
if (!ScopeHelper.ExtendsFromVerseDef(classContext.GetClrName().FullName))
@@ -169,15 +173,21 @@ private ReferenceCollection GetReferencesForText(ITreeNode element, ReferenceCol
private ReferenceCollection GetReferenceForDeclaredElement(ITreeNode element, ReferenceCollection oldReferences)
{
// We're currently in a text node inside a inside another ThingDef node. We want to get that node
- var defTypeName = element.Parent?.Parent?
+ // Alternatively, we may be in a string node inside a Name Attribute, so we need to go a step further
+ var parentTag = element.Parent?.Parent;
+ if (parentTag is not XmlTag && parentTag?.Parent is XmlTag) parentTag = parentTag.Parent;
+
+ if (parentTag is null) return new ReferenceCollection();
+
+ var defTypeName = parentTag
// And then get the TagHeader () of that node
.Children().FirstOrDefault(childElement => childElement is XmlTagHeaderNode)?
// And then get the text that provides the ID of that node (ThingDef)
.Children().FirstOrDefault(childElement => childElement is XmlIdentifier)?.GetText();
- if (defTypeName is null) new ReferenceCollection();
-
- var defName = element.GetText();
+ if (defTypeName is null) return new ReferenceCollection();
+
+ var defName = element.GetText().Trim('"');
var xmlSymbolTable = element.GetSolution().GetComponent();
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs
index 97f58e3..d6e935f 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs
@@ -48,6 +48,11 @@ protected override bool IsAvailable(RimworldXmlCodeCompletionContext context)
if (context.TreeNode is XmlTagEndToken && context.TreeNode.PrevSibling is XmlIdentifier &&
context.TreeNode.PrevSibling.PrevSibling?.GetText() != "") return true;
+ if (context.TreeNode is XmlValueToken &&
+ context.TreeNode.Parent is XmlAttribute attribute &&
+ attribute.AttributeName == "ParentName"
+ ) return true;
+
return false;
}
@@ -79,7 +84,6 @@ protected override bool AddLookupItems(RimworldXmlCodeCompletionContext context,
if (context.TreeNode is XmlFloatingTextToken && context.TreeNode.NodeType.ToString() == "TEXT")
{
AddTextLookupItems(context, collector);
- var a = 1 + 1;
return base.AddLookupItems(context, collector);
}
@@ -90,6 +94,15 @@ protected override bool AddLookupItems(RimworldXmlCodeCompletionContext context,
return base.AddLookupItems(context, collector);
}
+ if (context.TreeNode is XmlValueToken &&
+ context.TreeNode.Parent is XmlAttribute attribute &&
+ attribute.AttributeName == "ParentName"
+ )
+ {
+ AddParentNameItems(context, collector);
+ return base.AddLookupItems(context, collector);
+ }
+
/**
*
* <{CARET HERE
@@ -125,6 +138,33 @@ protected override bool AddLookupItems(RimworldXmlCodeCompletionContext context,
return base.AddLookupItems(context, collector);
}
+ protected void AddParentNameItems(RimworldXmlCodeCompletionContext context, IItemsCollector collector)
+ {
+ if (context.TreeNode?.Parent is not XmlAttribute attribute) return;
+ if (attribute.Parent is not XmlTagHeaderNode defTag) return;
+
+ var defClassName = defTag.ContainerName;
+ var defClass = ScopeHelper.GetScopeForClass(defClassName);
+
+ var xmlSymbolTable = context.TreeNode!.GetSolution().GetSolution().GetComponent();
+
+ var keys = xmlSymbolTable.GetDefsByType(defClassName);
+
+ foreach (var key in keys)
+ {
+ if (!xmlSymbolTable.IsDefAbstract(key)) continue;
+
+ var defType = key.Split('/').First();
+ var defName = key.Split('/').Last();
+
+ var item = xmlSymbolTable.GetTagByDef(defType, defName);
+
+ var lookup = LookupFactory.CreateDeclaredElementLookupItem(context, defName,
+ new DeclaredElementInstance(new XMLTagDeclaredElement(item, defType, defName, false)));
+ collector.Add(lookup);
+ }
+ }
+
protected void AddTextLookupItems(RimworldXmlCodeCompletionContext context, IItemsCollector collector)
{
var hierarchy = GetHierarchy(context.TreeNode);
@@ -197,8 +237,6 @@ protected void AddTextLookupItems(RimworldXmlCodeCompletionContext context, IIte
new DeclaredElementInstance(new XMLTagDeclaredElement(item, defType, defName, false)));
collector.Add(lookup);
}
-
- return;
}
protected void AddThingDefClasses(RimworldXmlCodeCompletionContext context, IItemsCollector collector,
@@ -296,7 +334,7 @@ public static List GetAllPublicFields(ITypeElement desiredClass, ISymbol
.FirstOrDefault(attribute => attribute.GetClrName().FullName == "Verse.LoadAliasAttribute");
if (loadAliasAttributes != null) return true;
-
+
return false;
})
.Select(member => member.Member)
@@ -499,18 +537,18 @@ attribute is MetadataAttributeInstance
);
if (loadAliasAttribute == null) continue;
-
+
var writer = new StringWriter(new StringBuilder());
- ((MetadataAttributeInstance) loadAliasAttribute).Dump(writer, "");
+ ((MetadataAttributeInstance)loadAliasAttribute).Dump(writer, "");
writer.Close();
var match = Regex.Match(writer.ToString(), "Arguments: \"(.*?)\"");
if (match.Groups.Count < 2) continue;
-
-
+
+
collector.Add(LookupFactory.CreateDeclaredElementLookupItem(context, match.Groups[1].Value,
new DeclaredElementInstance(field), true, false, QualifierKind.NONE));
-
+
continue;
}
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs b/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs
index d63eed7..f3b434a 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs
@@ -3,6 +3,7 @@
using JetBrains;
using JetBrains.Annotations;
using JetBrains.Application.Parts;
+using JetBrains.Application.Parts;
using JetBrains.Application.Threading;
using JetBrains.Collections;
using JetBrains.Lifetimes;
@@ -15,15 +16,28 @@
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Util;
+using JetBrains.ReSharper.Psi.Xml.Impl.Tree;
using JetBrains.ReSharper.Psi.Xml.Tree;
using ReSharperPlugin.RimworldDev.TypeDeclaration;
namespace ReSharperPlugin.RimworldDev.SymbolScope;
+public struct DefTag
+{
+ public DefTag(ITreeNode treeNode, bool isAbstract = false)
+ {
+ TreeNode = treeNode;
+ IsAbstract = isAbstract;
+ }
+
+ public ITreeNode TreeNode { get; }
+ public bool IsAbstract { get; }
+}
+
[PsiComponent(Instantiation.ContainerAsyncPrimaryThread)]
public class RimworldSymbolScope : SimpleICache>
{
- private Dictionary DefTags = new();
+ private Dictionary DefTags = new();
private Dictionary ExtraDefTagNames = new();
private Dictionary _declaredElements = new();
private SymbolTable _symbolTable;
@@ -58,7 +72,12 @@ public ITreeNode GetTagByDef(string defId)
if (!DefTags.ContainsKey(defId))
return null;
- return DefTags[defId];
+ return DefTags[defId].TreeNode;
+ }
+
+ public bool IsDefAbstract(string defId)
+ {
+ return DefTags.ContainsKey(defId) && DefTags[defId].IsAbstract;
}
public DefNameValue GetDefName(DefNameValue value) =>
@@ -88,15 +107,33 @@ public override object Build(IPsiSourceFile sourceFile, bool isStartup)
var tags = xmlFile.GetNestedTags("Defs/*").Where(tag =>
{
var defNameTag = tag.GetNestedTags("defName").FirstOrDefault();
- return defNameTag is not null;
+ if (defNameTag is not null) return true;
+
+ var nameAttribute = tag.GetAttribute("Name");
+ return nameAttribute is not null;
});
List defs = new();
foreach (var tag in tags)
{
- var defName = tag.GetNestedTags("defName").FirstOrDefault()?.InnerText;
- var defNameTag = tag.GetNestedTags("defName").FirstOrDefault().Children().ElementAt(1);
+ var defName = tag
+ .GetNestedTags("defName")
+ .FirstOrDefault()?.InnerText ??
+ tag
+ .GetAttribute("Name")?
+ .Children()
+ .FirstOrDefault(element => element is IXmlValueToken)?
+ .GetUnquotedText();
+
+ var defNameTag = tag.GetNestedTags("defName").
+ FirstOrDefault()?.
+ Children().
+ ElementAt(1) ??
+ tag.GetAttribute("Name")?.
+ Children().
+ FirstOrDefault(element => element is IXmlValueToken);
+
if (defName is null) continue;
defs.Add(new RimworldXmlDefSymbol(defNameTag, defName, tag.GetTagName()));
@@ -132,12 +169,27 @@ private void AddToLocalCache(IPsiSourceFile sourceFile, [CanBeNull] List
{
var matchingDefTag = xmlFile
- .GetNestedTags("Defs/*/defName").FirstOrDefault(tag =>
- tag.Children().ElementAt(1).GetTreeStartOffset().Offset == item.DocumentOffset);
-
+ .GetNestedTags("Defs/*/defName").FirstOrDefault(tag =>
+ tag.Children().ElementAt(1).GetTreeStartOffset().Offset ==
+ item.DocumentOffset) ??
+ xmlFile
+ .GetNestedTags("Defs/*")
+ .FirstOrDefault(tag =>
+ tag.GetAttribute("Name")?
+ .Children()
+ .FirstOrDefault(element => element is IXmlValueToken)?
+ .GetTreeStartOffset().Offset == item.DocumentOffset
+ )?
+ .GetAttribute("Name")?
+ .Children()
+ .FirstOrDefault(element => element is IXmlValueToken);
+
if (matchingDefTag is null) return;
- var xmlTag = matchingDefTag.Children().ElementAt(1);
+ // If the DefName is in a [Name=""] Attribute, it'll be matched to a XmlValueToken, which doesn't have any
+ // children. Otherwise, it'll be matched to the XmlTag for , where we want the first child as the
+ // string value
+ var xmlTag = matchingDefTag is IXmlValueToken ? matchingDefTag : matchingDefTag.Children().ElementAt(1);
AddDefTagToList(item, xmlTag);
});
@@ -168,10 +220,15 @@ void AddDefTagToList(RimworldXmlDefSymbol item, ITreeNode xmlTag)
}
}
+ var isAbstract = xmlTag is IXmlValueToken &&
+ xmlTag.Parent?.Parent is XmlTagHeaderNode defTypeTag &&
+ defTypeTag.GetAttribute("Abstract") is {} attribute &&
+ attribute.UnquotedValue.ToLower() == "true";
+
if (!DefTags.ContainsKey($"{item.DefType}/{item.DefName}"))
- DefTags.Add($"{item.DefType}/{item.DefName}", xmlTag);
+ DefTags.Add($"{item.DefType}/{item.DefName}", new DefTag(xmlTag, isAbstract));
else
- DefTags[$"{item.DefType}/{item.DefName}"] = xmlTag;
+ DefTags[$"{item.DefType}/{item.DefName}"] = new DefTag(xmlTag, isAbstract);
}
}
}
From 2591d70d128310608bb23120839e8d4ec726cc4d Mon Sep 17 00:00:00 2001
From: Gareth
Date: Fri, 10 May 2024 18:33:12 +0100
Subject: [PATCH 2/5] Change the version
---
gradle.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle.properties b/gradle.properties
index d6599b7..a24c4ab 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,7 +4,7 @@
DotnetPluginId=ReSharperPlugin.RimworldDev
DotnetSolution=ReSharperPlugin.RimworldDev.sln
RiderPluginId=com.jetbrains.rider.plugins.rimworlddev
-PluginVersion=2024.1.7
+PluginVersion=2024.2
BuildConfiguration=Release
From 56a54b061629ea19526a1b434393b0316b1b5fab Mon Sep 17 00:00:00 2001
From: Gareth Parker
Date: Fri, 9 May 2025 21:50:43 +0100
Subject: [PATCH 3/5] Make the template more modular (#47)
* Make the template more modular
* Adds the feature to the changelog
* Let the AssemblyPath be auto-filled again and fetch it from our centralized ScopeHelper
---
CHANGELOG.md | 3 +-
.../.template.config/template.json | 83 ++++++++++--
.../TemplateParameters/ModAuthorParameter.cs | 38 ------
.../RimworldDLLParameter.cs | 127 +++++-------------
4 files changed, 111 insertions(+), 140 deletions(-)
delete mode 100644 src/dotnet/ReSharperPlugin.RimworldDev/TemplateParameters/ModAuthorParameter.cs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bf051c..04c312e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,8 @@
# Changelog
## 2024.2
-* Adds support for [Parent=""] attributes
+ * Adds support for [Parent=""] attributes
+ * Makes the New Mod template modular, allowing you to select the components you want included in your mod
## 2024.1.7
* Solved some incompatibilities for ReSharper
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/.template.config/template.json b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/.template.config/template.json
index 99bcb8d..3e4e45e 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/.template.config/template.json
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/.template.config/template.json
@@ -41,6 +41,38 @@
"isRequired": "false",
"defaultValue": ""
},
+ "PublisherPlus": {
+ "type": "parameter",
+ "datatype": "bool",
+ "displayName": "Include a PublisherPlus config",
+ "description": "If enabled, a PublisherPlus config file will be included in the mod",
+ "isRequired": "false",
+ "defaultValue": "true"
+ },
+ "CSharp": {
+ "type": "parameter",
+ "datatype": "bool",
+ "displayName": "Add a C# Project and Sources",
+ "description": "If enabled, this mod will be created with a .sln file, Sources folder and a csproj file",
+ "isRequired": "false",
+ "defaultValue": "true"
+ },
+ "XML": {
+ "type": "parameter",
+ "datatype": "bool",
+ "displayName": "Add a Defs folder with a starting XML file",
+ "description": "If enabled, this mod will be created with a Defs folder and a starting XML file",
+ "isRequired": "false",
+ "defaultValue": "true"
+ },
+ "Languages": {
+ "type": "parameter",
+ "datatype": "bool",
+ "displayName": "Add a Languages folder",
+ "description": "If enabled, this mod will be created with a Languages folder with a starting English.xml file",
+ "isRequired": "false",
+ "defaultValue": "true"
+ },
"AssemblyDir": {
"type": "derived",
"valueSource": "RimworldDLL",
@@ -64,11 +96,22 @@
{
"type": "conditional",
"configuration": {
- "if": [ "
+ true
@@ -20,8 +23,15 @@
true
+
+
+
+
+
+
+
@@ -42,4 +52,14 @@
+
+
+
+
+
+
+ JetResourceGenerator
+
+
+
\ No newline at end of file
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.csproj b/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.csproj
index bd8468d..edf475a 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.csproj
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.csproj
@@ -8,7 +8,7 @@
- net472
+ net6.0True$(DefineConstants);RESHARPERfalse
@@ -42,6 +42,9 @@
+
+
+
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/Remodder/AssemblyHelper.cs b/src/dotnet/ReSharperPlugin.RimworldDev/Remodder/AssemblyHelper.cs
new file mode 100644
index 0000000..28d37ae
--- /dev/null
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/Remodder/AssemblyHelper.cs
@@ -0,0 +1,47 @@
+using System.IO;
+using AsmResolver;
+using AsmResolver.PE;
+using AsmResolver.PE.DotNet.Builder;
+using AsmResolver.PE.DotNet.Metadata.Strings;
+using AsmResolver.PE.DotNet.Metadata.Tables;
+using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
+
+namespace ReSharperPlugin.RimworldDev.Remodder;
+
+public static class AssemblyHelper
+{
+ public static byte[] RemoveReferenceAssemblyAttribute(byte[] input)
+ {
+ var peImage = PEImage.FromBytes(input);
+ var tables = peImage.DotNetDirectory.Metadata.GetStream();
+ var customAttrsTable = tables.GetTable();
+ var memberRefTable = tables.GetTable();
+ var typeRefTable = tables.GetTable();
+ var stringsStream = peImage.DotNetDirectory.Metadata.GetStream();
+
+ for (int i = customAttrsTable.Count - 1; i >= 0; i--)
+ {
+ var r = customAttrsTable[i];
+ if ((r.Type & 0b111) == 3)
+ {
+ var ctor = memberRefTable.GetByRid(r.Type >> 3);
+ if ((ctor.Parent & 0b111) == 1)
+ {
+ var type = typeRefTable.GetByRid(ctor.Parent >> 3);
+ if (stringsStream.GetStringByIndex(type.Name) == "ReferenceAssemblyAttribute")
+ {
+ customAttrsTable.Remove(r);
+ }
+ }
+ }
+ }
+
+ var fileBuilder = new ManagedPEFileBuilder(EmptyErrorListener.Instance);
+ var file = fileBuilder.CreateFile(peImage);
+
+ var stream = new MemoryStream();
+ file.Write(stream);
+
+ return stream.ToArray();
+ }
+}
\ No newline at end of file
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/Remodder/Decompiler.cs b/src/dotnet/ReSharperPlugin.RimworldDev/Remodder/Decompiler.cs
new file mode 100644
index 0000000..a17d50d
--- /dev/null
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/Remodder/Decompiler.cs
@@ -0,0 +1,200 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Threading;
+using HarmonyLib;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.CSharp;
+using ICSharpCode.Decompiler.DebugInfo;
+using ICSharpCode.Decompiler.Disassembler;
+using ICSharpCode.Decompiler.Metadata;
+using ICSharpCode.Decompiler.TypeSystem;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using MonoMod.Utils;
+
+// Taken from my old TranspilerExplorer project
+// Badly needs a rework...
+namespace ReSharperPlugin.RimworldDev.Remodder;
+
+public static class Decompiler
+{
+ const string OrigType = "OrigType";
+ const string DummyDll = "decomp.dll";
+
+ public static AttributePatch? GetTranspiler(Assembly asm, string typeName)
+ {
+ var type = asm.GetType(typeName);
+ if (type == null)
+ return null;
+ var transpiler = new Harmony("dummy").
+ CreateClassProcessor(type).patchMethods?.
+ FirstOrDefault(p => p.type == HarmonyPatchType.Transpiler);
+ return transpiler;
+ }
+
+ public static string Decompile(MethodBase orig, MethodInfo? transpiler, string[] userAsms, IDebugInfoProvider? debugInfo)
+ {
+ using var stream = new MemoryStream();
+ WriteAssembly(stream, orig, transpiler);
+ stream.Position = 0;
+
+ using var peFile = new PEFile(DummyDll, stream);
+ using var writer = new StringWriter();
+
+ var assemblyResolver = new UniversalAssemblyResolver(
+ userAsms.FirstOrDefault(),
+ false,
+ peFile.DetectTargetFrameworkId(),
+ peFile.DetectRuntimePack()
+ );
+
+ foreach (var userAsm in userAsms.Skip(1))
+ {
+ var dir = Path.GetDirectoryName(userAsm);
+ if (!string.IsNullOrEmpty(dir) && !assemblyResolver.GetSearchDirectories().Contains(dir))
+ assemblyResolver.AddSearchDirectory(Path.GetDirectoryName(userAsm));
+ }
+
+ var settings = new DecompilerSettings
+ {
+ ThrowOnAssemblyResolveErrors = false,
+ AnonymousMethods = false,
+ UseDebugSymbols = debugInfo != null
+ };
+
+ var decompiler = new CSharpDecompiler(peFile, assemblyResolver, settings)
+ {
+ DebugInfoProvider = debugInfo,
+ };
+
+ var code = decompiler.DecompileTypeAsString(new FullTypeName(orig.DeclaringType?.Name ?? OrigType));
+
+ return code;
+ }
+
+ public static string Disasm(MethodBase orig, MethodInfo transpiler)
+ {
+ using var stream = new MemoryStream();
+ WriteAssembly(stream, orig, transpiler);
+ stream.Position = 0;
+
+ using var peFile = new PEFile(DummyDll, stream);
+ using var writer = new StringWriter();
+
+ var output = new PlainTextOutput(writer);
+ ReflectionDisassembler rd = new ReflectionDisassembler(output, CancellationToken.None);
+ rd.DetectControlStructure = false;
+ rd.DisassembleType(peFile, peFile.GetTypeDefinition(new TopLevelTypeName(orig.DeclaringType?.Name ?? OrigType)));
+
+ return writer.ToString();
+ }
+
+ static void WriteAssembly(Stream stream, MethodBase orig, MethodInfo? transpiler)
+ {
+ var patch = MethodPatcher.CreateDynamicMethod(orig, "", false);
+ var il = patch.GetILGenerator();
+ var originalVariables = MethodPatcher.DeclareOriginalLocalVariables(il, orig);
+ var copier = new MethodCopier(orig, il, originalVariables);
+ var emitter = new Emitter(il, false);
+
+ copier.AddTranspiler(transpiler ?? identityTranspiler);
+
+ var endLabels = new List
+
From bab1cc6ed69ec65e1b7edfc8e0ddd6ce6fc2ec00 Mon Sep 17 00:00:00 2001
From: Gareth Parker
Date: Sun, 22 Jun 2025 22:56:59 +0100
Subject: [PATCH 5/5] Update the template for 1.6 and throw in a quick
implementation of using the users locally installed Harmony version for
debugging (#70)
Update the template for 1.6 and throw in a quick implementation of using the users locally installed Harmony version for debugging (#70)
---
.github/workflows/CI.yml | 17 ---
CHANGELOG.md | 2 +
.../Source/MyRimWorldMod.csproj | 22 +--
src/rider/main/kotlin/helpers/ScopeHelper.kt | 125 ++++++++++++++++++
src/rider/main/kotlin/run/RunState.kt | 6 +
5 files changed, 147 insertions(+), 25 deletions(-)
create mode 100644 src/rider/main/kotlin/helpers/ScopeHelper.kt
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 79bc706..c16194a 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -19,14 +19,6 @@ jobs:
distribution: 'corretto'
java-version: '21'
cache: 'gradle'
- - uses: actions/cache@v4
- with:
- path: |
- build/gradle-jvm
- ~/.nuget/packages
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-Build-${{ hashFiles('gradlew.bat', 'src/dotnet/*/*.csproj', './*.props', 'gradle-wrapper.properties') }}
- run: ./gradlew :buildPlugin --no-daemon
- run: ./gradlew :buildResharperPlugin --no-daemon
- uses: actions/upload-artifact@v4
@@ -46,13 +38,4 @@ jobs:
distribution: 'corretto'
java-version: '17'
cache: 'gradle'
- - uses: actions/cache@v4
- with:
- path: |
- build/gradle-jvm
- packages
- ~/.nuget/packages
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-Test-${{ hashFiles('gradlew.bat', 'src/dotnet/*/*.csproj', './*.props', 'gradle-wrapper.properties') }}
- run: ./gradlew :testDotNet --no-daemon
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7986537..7a6fe60 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
* Adds support for [Parent=""] attributes
* Makes the New Mod template modular, allowing you to select the components you want included in your mod
* Bundled in Zetrith's Remodder code into this plugin
+ * Update the Rimworld template for 1.6
+ * Automatically detect and use the user-installed Harmony version for debugging, if available
## 2024.1.7
* Solved some incompatibilities for ReSharper
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.csproj b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.csproj
index 2c6e5ec..0579493 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.csproj
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.csproj
@@ -28,15 +28,21 @@
prompt4
-
-
-
-
-
-
-
+
+
+ AssemblyDir\System.dll
+ False
+
+
+ AssemblyDir\System.Core.dll
+ False
+
+
+ AssemblyDir\System.Xml.dll
+ False
+ Assembly-CSharp.dllFalse
@@ -52,7 +58,7 @@
If that reference does not exist, it will add Krafs Rimworld Ref to the project. If it does exist, Krafs won't be added
as a reference. This basically means that Krafs is treated as a fallback if Assembly-CSharp is not found -->
-
+