]> Witch of Git - web/blog/blob - posts/2019/modding-games-and-freezing-fish.md
Improve .dim contrast
[web/blog] / posts / 2019 / modding-games-and-freezing-fish.md
1 ---
2 title: "Modding Games and Freezing Fish"
3 date: 2019-11-06
4 summary: "I learn how to write my own mod for a C# game so that I can skip a part I was stuck on. You can do it to! (This post contains minor spoilers for the Dark Bramble in Outer Wilds)"
5 ---
6
7 {% aside "warning" %}
8 This article will contain minor spoilers for the Dark Bramble in *Outer Wilds*.
9 {% endaside %}
10
11 In October I played [*Outer Wilds*][outer wilds], a game about being an astronaut and a space archaeologist in a weird solar system.
12 Their website describes:
13
14 > Outer wilds is an exploration game about curiosity, roasting marshmallows, and unraveling the mysteries of the cosmos.
15
16 It very quickly became one of my favorite games that I've ever played.
17 But unfortunately, even though it's almost entirely a pure joy to play, there's one *single* part that I hate.
18
19 [outer wilds]: http://outerwilds.com/
20
21 It's the anglerfish.
22 These guys.
23
24 {% figure src="/img/2019-anglerfish.png", alt="An anglerfish floating in fog" %}
25 Image from [the Outer Wilds Wiki][], licensed as [CC BY-SA 3.0][].
26
27 [the outer wilds wiki]: https://outerwilds.gamepedia.com/File:Angler_crop.png
28 [cc by-sa 3.0]: https://creativecommons.org/licenses/by-sa/3.0/
29 {% endfigure %}
30
31 In the Dark Bramble, these anglerfish are constantly looming out in the fog, making everything terrifying.
32 They're *great* for the spooky atmosphere, but I found them absolutely miserable mechanically.
33 At one point you need to sneak past them in an enclosed space, not making any noise with your engines.
34 I was never able to manage it.
35 This was almost the last thing I needed to do in the entire game, and I was so stuck I just put the game down for about two weeks in frustration.
36
37 But finally, I came back with a solution.
38
39 ### Hacking the Game
40
41 I decided that I could probably mod the game.
42 I knew it was made in Unity, which means it had a .NET core, so I set out to figure out how you mod a Unity game or a .NET executable.
43 My initial searching found [ILSpy][], which is an absolutely excellent tool for disassembling and viewing .NET code, but it didn't let you modify it.
44 Nonetheless, I used it to look around in some Unity games to make sure I could understand them.
45 What I ended up actually using was [dnSpy][], which in addition to letting you disassemble and view code, also acts as a debugger and an editor.
46
47 [ilspy]: https://github.com/icsharpcode/ILSpy
48 [dnspy]: https://github.com/0xd4d/dnSpy
49
50 Armed with this tool, I set out to hack apart the game.
51
52 {% figure src="/img/2019-outer-wilds-mod-01.png", alt="A Windows file explorer dialogue." %}
53 To disassemble a game, you have to find where the code is.
54 For Unity games, it's the `Assembly-CSharp.dll`, generally stored in a folder next to the game executable.
55 {% endfigure %}
56
57 To do this, make sure you make a copy of the original `Assembly-CSharp.dll` so you can revert to that if you want to.
58 You'll also want to make a backup of your save files, just in case something goes save-corruptingly wrong.
59 Once you're all set up with backup files, you can open the assembly in dnSpy.
60
61 {% figure src="/img/2019-outer-wilds-mod-02.png", alt="Using the file > open dialogue in dnSpy." %}
62 {% endfigure %}
63
64 When we open up the assembly, it gets added to the assembly listing.
65 Inside it is a single DLL, and expanding that shows a list of namespaces.
66 There are some various editor related namespaces that have some classes, but the vast majority are in the top-level namespace, labeled with empty braces and no name.
67 If we look inside this namespace, near the top is the `AnglerfishController` class, which seems like it's probably exactly what we want.
68
69 {% figure src="/img/2019-outer-wilds-mod-03.png", alt="dnSpy showing the assembly listing, with the AnglerfishController selected." %}
70 {% endfigure %}
71
72 Once we've located it, we should look around inside the class.
73 Because I know some Unity, I expect that the `Awake` and `Start` methods are likely to have interesting initialization in them.
74 And indeed, if we look at `Awake()`, we see can see a `_noiseSensor` field.
75 Since the anglerfish chase you when you make noise, this seems like a useful thing to disable.
76
77 ```csharp
78 protected override void Awake() {
79 base.Awake();
80 this._anglerBody = this.GetRequiredComponent<OWRigidbody>();
81 this._impactSensor = this.GetRequiredComponent<ImpactSensor>();
82 this._noiseSensor = this.GetRequiredComponentInChildren<NoiseSensor>();
83 this._anglerfishFluidVolume = this.GetRequiredComponentInChildren<AnglerfishFluidVolume>();
84 this._currentState = AnglerfishController.AnglerState.Lurking;
85 this._turningInPlace = false;
86 this._stunTimer = 0f;
87 this._consumeStartTime = -1f;
88 this._consumeComplete = false;
89 }
90 ```
91
92 If we go look for the other places that this field is used, we find that the enable and disable methods register a noise listener `OnClosestAudibleNoise`.
93 If we look at *that* method, it looks like it starts chasing the source of the noise, which is exactly what we don't want.
94
95 ```csharp
96 private void OnEnable() {
97 this._impactSensor.OnImpact += this.OnImpact;
98 this._noiseSensor.OnClosestAudibleNoise += this.OnClosestAudibleNoise;
99 this._anglerfishFluidVolume.OnCaughtObject += this.OnCaughtObject;
100 }
101
102 private void OnDisable() {
103 this._impactSensor.OnImpact -= this.OnImpact;
104 this._noiseSensor.OnClosestAudibleNoise -= this.OnClosestAudibleNoise;
105 this._anglerfishFluidVolume.OnCaughtObject -= this.OnCaughtObject;
106 }
107 ```
108
109 So let's remove that registration entirely.
110 We can right click on a method and select "Edit Method" to modify the code.
111
112 {% figure src="/img/2019-outer-wilds-mod-04.png", alt="Right clicking on a method to demonstrate the 'Edit Method' option in the context menu." %}
113 {% endfigure %}
114
115 In here, let's remove the registration and deregistration statements.
116 I didn't touch the `_impactSensor` registration, so potentially if you run into an anglerfish it'll still kill you.
117 Just that alone was plenty tension to leave them terrifying monsters sitting in the fog.
118 If you want to get rid of that difficulty as well, you could remove that registration, or even modify one of the update methods to delete the anglerfish itself.
119
120 ```diff
121 private void OnEnable() {
122 this._impactSensor.OnImpact += this.OnImpact;
123 - this._noiseSensor.OnClosestAudibleNoise += this.OnClosestAudibleNoise;
124 this._anglerfishFluidVolume.OnCaughtObject += this.OnCaughtObject;
125 }
126
127 private void OnDisable() {
128 this._impactSensor.OnImpact -= this.OnImpact;
129 - this._noiseSensor.OnClosestAudibleNoise -= this.OnClosestAudibleNoise;
130 this._anglerfishFluidVolume.OnCaughtObject -= this.OnCaughtObject;
131 }
132 ```
133
134 When I tried to click the compile button after making my modifications, it gave me some confusing error messages.
135 Apparently some of the attributes that disassembly put on `event` declarations aren't valid to go there, or something?
136
137 {% figure src="/img/2019-outer-wilds-mod-05.png", alt="A list of errors and warnings from the compile dialogue, saying that events can't be DebuggerBrowsable." %}
138 {% endfigure %}
139
140 Double-clicking on one of the errors leads to this attribute declaration:
141
142 {% figure src="/img/2019-outer-wilds-mod-06.png", alt="A System.Diagnostics.DebuggerBrowsable attribute on an event declaration." %}
143 {% endfigure %}
144
145 I just deleted every attribute it was complaining about, and then it compiled cleanly.
146 After you compile, you can save the assembly... and then try out the game!
147
148 ### The Test
149
150 It worked perfectly for me.
151 I could sneak right past anglerfish at full engine blast if I wanted to and they would never hear... and I was still terrified of accidentally bumping into them, so the atmosphere of the scene was preserved pretty well.
152 With this little mod, I was able to beat the game (the end of *Outer Wilds* is absolutely fantastic).
153
154 I found it super empowering to be able to change a game so that it accommodated the way I wanted to play it.
155 I hope it's useful to some of you too.