Moonlighter is a nice game. Over the new year break, I played it for 10 hours a day for 2-3 days. It's your typical dungeon crawler with a twist. You have a shop and you can sell items in your shop and do a bit of price manipulation based on supply and demand.
It has some grinding. At each dungeon level (there are four), you have grind the items needed for crafting the next level equipment. After farming for multiple hours to get a few drops of one item, I decided to cheat at it.
This post talks about how I discovered the save file and how we can modify it to give ourselves any item in the game. In the next part, I will discuss modifying the game to one-shot enemies and other things. It's a straightforward game for getting into "game hacking."
I am using the Steam version. If you have a different version, your save file location will be different. We can discover the save file in two ways:
This reddit comment mentions two locations:
[Your Steam Installation Folder]\Steam\userdata[Your Steam ID]\606150\remote
C:\Users[Your Windows Username]\AppData\LocalLow\11BitStudios\Moonlighter
Procmon logs many events. We need to minimize the total number of captured events and then use filter to further reduce the number. To do this, we need to find an event that forces a game save. There are different ways to do this (I think every loading screen does it). Two of the easiest are:
I chose the first.
File > Capture Events
.Now I had 27000
events (excluding profiling events).
This is a great tool. Open the Process Tree at Tools > Process Tree
.
In the process tree, right-click Steam
and select Add process and children to include filter
. This is a handy filter. It allows us to filter all children. It filters by process ID so it will not work between different executions.
Note: It's tempting to just select Moonlighter
and that is what I initially did. But in this case, Steam is handling the save file.
Procmon's Procee Tree
After selecting, the filtering takes a few seconds and we are down to around 409
events. Oh boy. This turned out easier than I thought.
Results after filtering by process
Assuming things are saved in a file and not registry (registry is an option but usually not in games because save files are big). We can filter by File System Activity
using the icon in the toolbar. This reduces the number of events to 341
.
Filtering by File System Activity
But our most important filter is next. We forced a save game write event, we only need to keep write events.
We need to pay attention to two operations here: WriteFile
and CreateFile
. I am not sure about the difference in procmon vs. WinAPI but I use both to be sure. We need to add two filters:
Operation
- is
- WriteFile
then Include
.Operation
- is
- CreateFile
then Include
.Write events
We're now down to 89
events which is pretty manageable for manual review.
We can learn a few things. For example, Unity analytics files are at this location:
C:\Users\IEUser\AppData\LocalLow\11BitStudios\Moonlighter\Unity\f522e32a-6e64-4c70-afbd-bf0463165292\Analytics
The config
file has some interesting info.
{
"prefs": {},
"analytics": {
"enabled": true
},
"connect": {
"limit_user_tracking": false,
"player_opted_out": false,
"enabled": true
},
"performance": {
"enabled": true
}
}
Supposedly they are collecting analytics and users have not opted out. I have not seen such a setting in the game and I have no idea what kind of data are being collected in game. But that is for another day.
If our guess is correct, we can stop the analytics by disabling it in that file.
But what we are looking for is at:
C:\Program Files (x86)\Steam\userdata\{{steamID}}\606150\remote
Save file location
Unsurprisingly, it's the largest file gameslot
. It's a JSON file.
Beautify it with CyberChef:
Beautified save file
It does not get easier than this. Let's search for gold
.
"willWishlistedRecipes": "",
"equippedWeaponSet": 0,
"willGold": 100,
We can modify it and give ourselves more gold. Will is the name of the main character.
I gave Will 10 million gold. It's a reasonable number.
Will is rich
willEquippedItems
is an array of items. Some are empty. Seems like there's an order, meaning the first items could be head, next could be weapon and etc. We do not know which is which yet. Starting from zero we have:
Training Sword
.Broom Spear
.Dash
.HP Potion I
.8, 9: empty
"willEquippedItems": [
{
"prefabName": null,
"plusLevel": 0,
"name": "",
"quantity": 0,
"sellingPrize": 0,
"curseName": null,
"enchantmentLevel": 0,
"enchantmentType": 0,
"enchantmentEffectName": null
},
// .. removed
{
"prefabName": "ItemStack",
"plusLevel": 0,
"name": "Training Sword",
"quantity": 1,
"sellingPrize": 0,
"curseName": "",
"enchantmentLevel": 0,
"enchantmentType": 0,
"enchantmentEffectName": ""
},
{
"prefabName": "ItemStack",
"plusLevel": 0,
"name": "Broom Spear",
"quantity": 1,
"sellingPrize": 0,
"curseName": "",
"enchantmentLevel": 0,
"enchantmentType": 0,
"enchantmentEffectName": ""
},
{
"prefabName": "ItemStack",
"plusLevel": 0,
"name": "Dash",
"quantity": 1,
"sellingPrize": 0,
"curseName": "",
"enchantmentLevel": 0,
"enchantmentType": 0,
"enchantmentEffectName": ""
},
// removed
]
Let's look at our equipped items to see what's what.
Will's inventory before modification
Training Sword
.Broom Spear
.Dash
. These two do not appear in the inventory. It's the ability Dash
forward and backward (bound to the space
key) but I am not sure why it's a separate skill. These might be the two inventory slots on top and under the potion slot (see the picture).HP Potion I
.There's also a willInventory
array with items in the inventory. We can see the top row in the picture.
{
"prefabName": "ItemStack",
"plusLevel": 0,
"name": "Rich Jelly",
"quantity": 3,
"sellingPrize": 0,
"curseName": "",
"enchantmentLevel": 0,
"enchantmentType": 0,
"enchantmentEffectName": null
},
{
"prefabName": "ItemStack",
"plusLevel": 0,
"name": "Whetstone",
"quantity": 10,
"sellingPrize": 0,
"curseName": "",
"enchantmentLevel": 0,
"enchantmentType": 0,
"enchantmentEffectName": null
},
We can modify any slot and give will any item/quantity. To figure out item names, we can look under recipesSeen
and see a handy list.
"recipesSeen": [
{
"name": "Training Sword",
"seen": true,
"plusLevel": 0
},
{
"name": "Training Big Sword",
"seen": true,
"plusLevel": 0
},
{
"name": "Training Gloves",
"seen": true,
"plusLevel": 0
},
]
But it is not complete. Especially for this playthrough inside the Virtual Machine.
To test our theory, let's give ourselves another Broom Spear
in weapon slot 1 instead of the Training Sword
. And now we have two broom spears.
Two broom spears
That was fun, but how do we give ourselves better items? We do not know their names.
Assuming we cannot search online (which has all the item names), we can use an in-game feature. The blacksmith can craft armor and weapons. To get the blacksmith, we need to build the building for 500 gold. Luckily, we have 10 million. Looking at his items, we can see their names and use them.
Blacksmith's items
We cannot see all items yet, but their names appear in the top-right corner of the screen. Being a sneaky archer, I want to get the items with speed bonuses:
Fabric Bandana IV
Fabric Chestplate IV
Fabric Boots IV
King Sword
. The label says King Short Sword
but the actual in-game item is King Sword
.Exeter Bow
The only remaining problem is figuring out which willEquippedItems
array host which item. That's easy, we will add the items in our inventory and then equip them in game.
Items can also be enchanted (and cursed for elemental items).enchantmentLevel
and enchantmentType
set to 1
. For example, the max non-elemental bow is set like this in inventory.
{
"prefabName": "ItemStack",
"plusLevel": 0,
"name": "Exeter Bow",
"quantity": 1,
"sellingPrize": 0,
"curseName": "",
"enchantmentLevel": 1,
"enchantmentType": 1,
"enchantmentEffectName": null
}
And now Will has all these shiny items (I made a mistake and gave him 10 chest plates).
Items added to Will's inventory
Equip them and trigger another save event. For example, enter a dungeon and then leave using the pendant.
Items equipped in game Equipped items in save file
We can update the willEquippedItems
array:
Fabric Bandana IV
.Fabric Chestplate IV
.Fabric Boots IV
.Training Sword
.Broom Spear
.Dash
.HP Potion I
.Adding items to the shop and chests is similar. Later in the game, we get quests. People ask Will to get them a certain number of an item in a few days. Quests appear in the save file under willActiveQuests
and completed quests are under completedQuests
. We can modify the quest and rewards to anything we want to get that money. This is a completed quest.
"completedQuests": [
{
"Key": 21,
"Value": {
"quest": {
"culture": "Golem",
"floor": 0,
"target": "Ancient Wood",
"quantity": 5,
"daysToComplete": 3,
"reward": 20000,
"killQuestTarget": "",
"killQuestTargetKey": "",
"giverType": "Merchant",
"description": "QUEST_ANCIENT_WOOD_GENDER_DESCRIPTION"
},
"completed": true,
"failed": true,
"completeDay": 21,
"giverVisitorPrefabName": "Freak Girl - Tomo",
"giverVisitorIsMale": false
}
},
I am not going to talk about the rest of the save file. It's pretty obvious.
We learned:
In the next part, I will mess with the game files to change game mechanics (e.g. one shotting everything).