{"id":1400,"date":"2025-11-04T07:23:43","date_gmt":"2025-11-04T07:23:43","guid":{"rendered":"https:\/\/haxed.me.uk\/?p=1400"},"modified":"2025-11-05T05:30:37","modified_gmt":"2025-11-05T05:30:37","slug":"how-i-fixed-the-missing-strings-in-oxygen-not-includeds-dirt-tile-mod-the-full-story","status":"publish","type":"post","link":"https:\/\/haxed.me.uk\/index.php\/2025\/11\/04\/how-i-fixed-the-missing-strings-in-oxygen-not-includeds-dirt-tile-mod-the-full-story\/","title":{"rendered":"How I Fixed the Missing Strings in Oxygen Not Included\u2019s Dirt Tile Mod \u2013 The Full Story"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\">Modding <em>Oxygen Not Included<\/em> (ONI) is an adventure in itself, but recently I undertook a project to fix a <strong>persistent strings issue<\/strong> in a dirt tile mod. I can see why nobody has fixed it. It was not straightforward. The problem? The mod allowed you to build dirt tiles, but the game kept showing <code>MISSING.STRINGS.MISC.TAGS.DIRT<\/code> instead of the proper name. After countless hours of troubleshooting, dependency wrangling, and code editing, I finally got it working. Here\u2019s the full journey.<br><br>For the original repository please see https:\/\/github.com\/ecool\/ONI-Mods<\/h1>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/haxed.me.uk\/wp-content\/uploads\/2025\/11\/image.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"462\" src=\"https:\/\/haxed.me.uk\/wp-content\/uploads\/2025\/11\/image-1024x462.png\" alt=\"\" class=\"wp-image-1404\" srcset=\"https:\/\/haxed.me.uk\/wp-content\/uploads\/2025\/11\/image-1024x462.png 1024w, https:\/\/haxed.me.uk\/wp-content\/uploads\/2025\/11\/image-300x135.png 300w, https:\/\/haxed.me.uk\/wp-content\/uploads\/2025\/11\/image-768x347.png 768w, https:\/\/haxed.me.uk\/wp-content\/uploads\/2025\/11\/image-1536x694.png 1536w, https:\/\/haxed.me.uk\/wp-content\/uploads\/2025\/11\/image-500x226.png 500w, https:\/\/haxed.me.uk\/wp-content\/uploads\/2025\/11\/image.png 1838w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Understanding the Problem<\/h2>\n\n\n\n<p>The original mod was written for pre-U52 versions of ONI. With U52:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The string system changed significantly, so old mods often report missing strings for materials like Dirt.<\/li>\n\n\n\n<li>Assigning <strong>raw elements<\/strong> directly (like <code>SimHashes.Dirt<\/code>) to a building\u2019s construction caused string corruption.<\/li>\n\n\n\n<li>API changes meant <code>PlanScreen.PlanInfo.data<\/code> was now obsolete. While this didn\u2019t break the mod, it threw warnings.<\/li>\n<\/ul>\n\n\n\n<p>Our mission became clear: <strong>modernize the mod<\/strong> so that it works with the new string system and uses proper material categories.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Preparing the Development Environment<\/h2>\n\n\n\n<p>Before touching code, I needed a working project environment. This was <strong>non-trivial<\/strong>, because ONI mods rely on multiple dependencies:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Game DLLs<\/strong> from ONI itself: copy them all if in doubt\n<ul class=\"wp-block-list\">\n<li>Assembly-CSharp.dll<\/li>\n\n\n\n<li>Assembly-CSharp-firstpass.dll<\/li>\n\n\n\n<li>UnityEngine.dll<\/li>\n\n\n\n<li>UnityEngine.CoreModule.dll<\/li>\n\n\n\n<li>0Harmony.dll<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Mod helper DLLs<\/strong>:\n<ul class=\"wp-block-list\">\n<li>CaiLib.dll<\/li>\n\n\n\n<li>CoolLib.dll<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>These DLLs needed to be <strong>collected, referenced in the project, and in the right folder structure<\/strong>. The ONI game DLLs sat in <code>vendor\\ONI-DLC<\/code>, and mod helper DLLs sat in <code>vendor\\<\/code>.<\/p>\n\n\n\n<p>We also needed <strong>NuGet packages<\/strong>, most importantly <code>Newtonsoft.Json<\/code> 12.0.2, which the project used:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ndotnet add package Newtonsoft.Json --version 12.0.2\n\n<\/pre><\/div>\n\n\n<p>This resolved namespace errors like:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nCS0246: The type or namespace name 'Newtonsoft' could not be found\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Step 3: Cleaning the Environment<\/h2>\n\n\n\n<p>Before building, I had to make sure <strong>no leftover artifacts<\/strong> interfered with the build. This included:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nrd \/s \/q bin\nrd \/s \/q obj\ndotnet nuget locals all --clear\n\n<\/pre><\/div>\n\n\n<p>Then restoring NuGet packages freshly:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ndotnet restore\n\n<\/pre><\/div>\n\n\n<p>This prevented duplicate reference warnings and ensured a clean build environment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Fixing Project References<\/h2>\n\n\n\n<p>Even with all DLLs in the right folders, the project file initially failed to compile. The issues included:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CoolLib and CaiLib not being recognized.<\/li>\n\n\n\n<li>Newtonsoft.Json not found.<\/li>\n\n\n\n<li>Old MSBuild paths pointing incorrectly to ONI DLLs.<\/li>\n<\/ul>\n\n\n\n<p>We fixed this by editing the <code>.csproj<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&amp;lt;Reference Include=&quot;CaiLib&quot;&gt;\n  &amp;lt;HintPath&gt;vendor\\CaiLib.dll&amp;lt;\/HintPath&gt;\n&amp;lt;\/Reference&gt;\n\n&amp;lt;Reference Include=&quot;CoolLib&quot;&gt;\n  &amp;lt;HintPath&gt;vendor\\CoolLib.dll&amp;lt;\/HintPath&gt;\n&amp;lt;\/Reference&gt;\n\n&amp;lt;Reference Include=&quot;Newtonsoft.Json&quot;&gt;\n  &amp;lt;HintPath&gt;vendor\\Newtonsoft.Json.dll&amp;lt;\/HintPath&gt;\n&amp;lt;\/Reference&gt;\n\n<\/pre><\/div>\n\n\n<p>&#8211; Vendor DLLs were now accessible to the compiler.<br>&#8211; Removed old duplicates of <code>Newtonsoft.Json<\/code> references to eliminate <code>NU1504<\/code> warnings.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Updating the Dirt Tile Configuration<\/h2>\n\n\n\n<p>The core of the problem was the missing strings. The original mod tried to register them directly:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nStrings.Add(&quot;STRINGS.ELEMENTS.DIRT.NAME&quot;, &quot;Dirt&quot;);\nStrings.Add(&quot;STRINGS.ELEMENTS.DIRT.DESC&quot;, Description);\nStrings.Add(&quot;STRINGS.ELEMENTS.DIRT.EFFECT&quot;, Effect);\n\n<\/pre><\/div>\n\n\n<p>But in U52+, this no longer worked because:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Direct element assignment in building materials causes string corruption.<\/li>\n\n\n\n<li>The game now requires <strong>material categories<\/strong> instead of specific elements to display strings correctly.<\/li>\n<\/ol>\n\n\n\n<p>So we updated the building definition:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n        string&#x5B;] construction_materials = MATERIALS.RAW_MINERALS;\n        float&#x5B;] construction_mass = new float&#x5B;] {     BuildableDirtTile.BuildableDirtTilePatches.Settings.BuildMass };\n\n<\/pre><\/div>\n\n\n<p>And kept string registration in <code>DoPostConfigureComplete<\/code> so Dirt displayed correctly in-game.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Compiling and Merging DLLs<\/h2>\n\n\n\n<p>Once the references and code were fixed:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ndotnet build -c Release\n\n<\/pre><\/div>\n\n\n<p>Merged DLLs using <code>ilmerge<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&amp;amp;lt;PostBuildEvent&amp;amp;gt;\n  ilmerge \/wildcards \/out:BuildableDirtTile-merged.dll BuildableDirtTile.dll vendor\\*.dll \/targetplatform:v2,C:\/Windows\/Microsoft.NET\/Framework64\/v4.0.30319\n&amp;amp;lt;\/PostBuildEvent&amp;amp;gt;\n\n<\/pre><\/div>\n\n\n<p>Copied the merged DLL to the <code>mods\/dev<\/code> folder of ONI.<\/p>\n\n\n\n<p>Getting <code>ilmerge<\/code> to work with all dependencies was a mammoth task. It required exact paths, wildcards, and knowledge of which assemblies were required at runtime.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Testing in Game<\/h2>\n\n\n\n<p>After enabling the mod:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The mod loaded <strong>separately from the Steam version<\/strong>.<\/li>\n\n\n\n<li>Strings no longer showed as missing. \u2705<\/li>\n\n\n\n<li>Tiles displayed correctly, and construction worked.<\/li>\n<\/ul>\n\n\n\n<p>A warning about <code>PlanScreen.PlanInfo.data<\/code> being obsolete appeared, but it didn\u2019t break functionality:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nPlanScreen.PlanInfo.data' is obsolete: 'Modders: Use ModUtil.AddBuildingToPlanScreen'\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Step 8: Lessons Learned<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>DLL management is critical<\/strong>: Every dependency must be referenced and present at runtime.<\/li>\n\n\n\n<li><strong>Material categories matter<\/strong>: Direct element references in building costs cause string corruption in U52+.<\/li>\n\n\n\n<li><strong>API changes matter<\/strong>: Obsolete warnings hint at future-proofing needs.<\/li>\n\n\n\n<li><strong>Persistence pays off<\/strong>: Cleaning <code>bin<\/code>\/<code>obj<\/code> and NuGet caches often resolves mysterious build errors.<\/li>\n\n\n\n<li><strong>Patience is key<\/strong>: The journey from build errors to a working merged DLL took multiple iterations.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>After a <strong>month of troubleshooting, code changes, and dependency wrangling<\/strong>, the Dirt Tile mod finally works with U52+ ONI:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Strings display correctly.<\/li>\n\n\n\n<li>Tiles can be built without breaking the UI.<\/li>\n\n\n\n<li>The mod is fully separated from Steam Workshop dependencies.<\/li>\n<\/ul>\n\n\n\n<p>This experience reinforced that modding isn\u2019t just coding\u2014it\u2019s <strong>dependency management, environment setup, and problem-solving all rolled into one<\/strong>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Modding Oxygen Not Included (ONI) is an adventure in itself, but recently I undertook a project to fix a persistent strings issue in a dirt tile mod. I can see why nobody has fixed it. It was not straightforward. The &hellip; <a href=\"https:\/\/haxed.me.uk\/index.php\/2025\/11\/04\/how-i-fixed-the-missing-strings-in-oxygen-not-includeds-dirt-tile-mod-the-full-story\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1400","post","type-post","status-publish","format-standard","hentry","category-uncategorised"],"_links":{"self":[{"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/posts\/1400","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/comments?post=1400"}],"version-history":[{"count":3,"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/posts\/1400\/revisions"}],"predecessor-version":[{"id":1406,"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/posts\/1400\/revisions\/1406"}],"wp:attachment":[{"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/media?parent=1400"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/categories?post=1400"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/haxed.me.uk\/index.php\/wp-json\/wp\/v2\/tags?post=1400"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}