Revisiting Video Game 'God Mode' With Space Invaders and a Debugger
2024-6-4 05:6:47 Author: hackernoon.com(查看原文) 阅读量:3 收藏

Back in the day, computer games were different. Not only have graphics and mechanics evolved since, but there's also one characteristic that doesn't seem very common in games today: nearly all of them had cheat codes.

Cheat codes were sequences of keys that would give you something extraordinary, such as infinite ammo or the ability to walk through walls. The most common and powerful of them was 'god mode' – it made you invincible.

Screenshot of the marine from Doom with 'god mode' enabled

This is what your character would look like when you entered IDDQD – the key combination for 'god mode' in Doom. In fact, this particular key sequence was so popular that it became a meme and gained popularity beyond the game itself.

While 'god mode' is not as prevalent in games as it once was, and the era of the IDDQD meme seems to be fading, one might wonder if a contemporary equivalent exists. Personally, I have my own modern take on IDDQD. Though it's not necessarily related to games, it does evoke the same sense of having superpowers.

Space Invaders

To illustrate my point, I'd like to bring in a fun scenario right here. Even if you aren't familiar with Doom, you've most probably seen this even older game called Space Invaders. Like Doom, its plot centers around the theme of fighting invaders in space.

My friend and colleague, Eugene, has written an IntelliJ IDEA plugin, which lets you play this game right in the editor – a great way to spend some time while waiting for indexing to complete.

Space Invaders in IntelliJ IDEA's editor

There's no god mode in this game, but if we are very determined, can we add it ourselves? Let's bring back the classic tradition of hacking programs with a debugger and find out!

Note: Be responsible! I got Eugene's consent before tampering with his program. If you're using the debugger on code that is not yours, make sure it's ethical. Otherwise, don't do that.

Prepare for a meta experience – we're going to debug IntelliJ IDEA using its own debugger.

Here's a little problem that we need to solve: We need to suspend the IntelliJ IDEA to debug it. This will consequently render the IDE unresponsive. Therefore, we need an extra IDE instance that will remain functional and serve as our debugging tool.

To manage several IDE instances, I will be using JetBrains Toolbox. This is a utility that organizes installed JetBrains's IDEs. With it, you can install several versions or create shortcuts to run them with different sets of VM options.

Let's install two instances of IntelliJ IDEA:

JetBrains Toolbox shows several JetBrains IDEs including two instances of IntelliJ IDEA called Space Invaders and Debug

If you are using the same IDE version for both instances, make sure to specify a different system, config, and logs directories in Tool actions | Settings | Configuration. On this page, you can also assign names to the IDE instances for convenience.

To be able to debug the 'Space Invaders' instance, click More near it, then go to Settings | Edit JVM options. In the file that opens, paste the following line:

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

The file with VM options that are going to be passed to the IDE instance

This will make the target JVM run with the debug agent and listen to incoming debugger connections on port 5005.

Run the game

Run the Space Invaders instance, install the game, and launch it by running the Space Invaders action. To find the action, hit Shift twice and start typing Space Invaders:

Running the Space Invaders action through the dialog that opens on hitting double Shift

Let's play for a while and observe the behavior that we want to fix: when enemy missiles hit the spaceship, the health bar in the top-left corner of the screen goes down.

Attach and suspend

Our debugging journey begins with opening the 'Debug' IDE instance and setting up a new Kotlin project. The primary reason why we need this project is that it would not be possible to launch the debugger without one. Additionally, IntelliJ IDEA includes the Java/Kotlin standard library in the new project template, which we might use later. I'll explain the use of the standard library in the subsequent chapters.

After creating the project, go to the main menu and select Run | Attach to Process. This will show the list of local JVMs listening for debugger attach requests. Select the other running IDE from the list.

A popup with the list of locally running JVMs

We should see the following message in the console confirming that the debugger has successfully attached to the target VM.

Connected to the target VM, address: 'localhost:5005', transport: 'socket'

We're getting to the interesting part: how do we suspend the application?

Typically, one would set a breakpoint in the application code, but in this case, we lack the sources for both IntelliJ IDEA and the Space Invaders plugin. Not only does this prevent us from setting a breakpoint, it also complicates our understanding of how the program operates. At first glance, there appears to be nothing to inspect or step through.

Luckily for us, IntelliJ IDEA has a feature known as Pause. It allows you to suspend the program at any arbitrary point in time, without needing to specify the corresponding line of code. You can find it in the debugger toolbar or in the main menu: Run | Debugging Actions | Pause.

The Debug tool window for the suspended Space Invaders instance

The application gets suspended. This gives us a starting point for debugging.

Tip: Pause is a very powerful technique, which is especially helpful in several advanced scenarios. To learn more, check out the related articles:

Find the relevant objects

If we look at our goal in programming terms, it boils down to preventing the spaceship's health from going down. Let's find the object that holds the corresponding state. Since we don't know anything about the plugin code, we can directly inspect the heap using the IntelliJ IDEA debugger's Memory view:

A menu appears on clicking Layout Settings in the top-right corner of the Debug tool window

This feature gives you information about all objects that are currently alive. Let's type invaders and see if we can find anything:

Typing 'invaders' in the Memory view's search field shows objects of classes that belong to the 'spaceinvaders' package

Apparently, the plugin classes come under the package com.github.nizienko.spaceinvaders. Within this package, there is GameState class with several live instances. It looks like what we need.

Double-clicking GameState shows all the instances of this class:

A dialog opens showing live GameState instances

As it turns out, it's an enum – which isn't exactly what we were looking for. Continuing our search, we stumble upon a single instance of Game.

Expanding the node lets us inspect the instance's fields:

Memory view with an expanded object node showing the object's fields

The health property appears to be the one of interest here. Among its fields, we can find _value. In my case, it was 100, which correlates with the health bar being full when I suspended the game. So, it's likely the correct field to consider, and its value seems to range from 0 to 100.

Let's put this hypothesis to the test. Right-click _value, then select Set Value. Choose a value that is different from your current one. For instance, I chose 50.

Memory view with a text field against the 'health' field containing the user-entered value of 50

At this step, we bump into an error that reads Cannot evaluate methods after Pause action:

An error message saying 'Cannot evaluate methods after Pause

The problem arises because we used Pause instead of breakpoints, and this feature comes with some limitations. However, we can turn to a little trick to work around this.

I described it in one of the previous posts dedicated to Pause. In case you missed it there, here's what needs to be done: once the application has paused, perform a stepping action, such as Step Into or Step Over. Doing so will enable the use of advanced features like Set Value and Evaluate Expression.

After applying the trick described above, we should be able to set the value for health. Try modifying the value, then resume the application to see if the health bar displays any changes. Indeed, it does!

So, we've located the object that holds the relevant state. At the very least, we can manually refill the health bar from time to time. Although it isn't a complete success just yet, it's already a significant step forward.

Labels and expressions

Now that we have identified the object to focus on, it would be handy to mark it. For those unfamiliar with debug labels, this is what a marked object looks like:

Variables tab showing an array of User objects, with one of them marked with a debug label saying User_Charlie

Labels can be beneficial in many ways. Within the context of this article, marking the relevant object ensures that we can directly use it in features like Evaluate Expression without being dependent on the current execution context.

Unfortunately, it's not possible to direcly mark _value, but we can mark the object that encloses it. To do this, right click health, select Mark Object, then give it a name.

Select Object Label dialog prompting the user to enter a name for the object

We can now test how the label works elsewhere. Open the Evaluate Expression dialog and enter health_object_DebugLabel as the expression. As you can see, the object is accessible from any place in the program through the Evaluate dialog:

Evaluate dialog with debug label entered as the expression

What about changing the spaceship's health from Evaluate? health_object_DebugLabel._value = 100 does not work.

At the same time, _value appears to be a backing field of a Kotlin property. If this is true, Kotlin must have generated a corresponding getter:

health_object_DebugLabel.getValue()

The Evaluate dialog doesn't think this is valid code, but we'll try anyway:

Referencing a property through a debug label in the Evaluate dialog

The expression returns the current spaceship's health, so this approach works! Reasonably, the setter should work too:

health_object_DebugLabel.setValue(100)

After evaluating the setter, let's resume the application and verify that the changes took effect. Indeed, the health bar is full!

Hook the expression

The only remaining step to reach our goal is to automate the modification of the state so that the 'health refill' happens behind the scenes, letting us enjoy the gameplay without interruptions.

This can be done using non-suspending breakpoints. This type of breakpoint is commonly used for logging; however, the logging expression does not necessarily need to be pure. Therefore, we can introduce the desired side effect within the logging expression. Still, it remains uncertain where to set this breakpoint since we don't have the application's source code.

Remember I said that we might use the sources of the Java standard library? Here is the idea. IntelliJ IDEA and its plugins are written in Java/Kotlin, and they use Swing as the UI framework. Consequently, Space Invaders is sure to call code from these dependencies. This means that we can use their sources to set breakpoints.

Note: To maintain simplicity, we didn't specify a particular JDK version and initialized the project with the one suggested by IntelliJ IDEA. However, for best results, it's recommended to use sources that closely match the version used in the runtime.

There are numerous locations suitable for setting a breakpoint. I decided to set a method breakpoint in java.awt.event.KeyListener::keyPressed. This will trigger the side effect every time we press a key:

Breakpoints dialog showing a logging breakpoint for java.awt.event.KeyListener::keyPressed

Note: Setting a method breakpoint in hot code might significantly slow down the target application.

Let's return to Space Invaders and see if our home-cooked IDDQD works. It does!

Playing Space Invaders in IntelliJ IDEA – every time that the spaceship gets hit, its health bar automatically refills

Conclusion

In this article, we used the debugger to find out how an application works under the hood. After sorting this out, we were able to navigate in its memory and modify its functionality, all without accessing the application's sources! I hope my comparison of the debugger with IDDQD didn't come across as too audacious and that you learned some techniques that will empower you in your debugging challenges.

I'd like to extend my kudos to Eugene Nizienko for making the Space Invaders plugin and Egor Ushakov for constantly sharing with me a wealth of creativity in debugging and programming. Computers are twice as fun with such people around.

If you have debugging challenges in mind that you want me to tackle in the upcoming posts, let me know!

For updates on new posts like this, you can subscribe to my blog or follow me on Twitter.

Happy hacking!


文章来源: https://hackernoon.com/revisiting-video-game-god-mode-with-space-invaders-and-a-debugger?source=rss
如有侵权请联系:admin#unsafe.sh