Home > Reverse Engineering, Unity (Mono) > Nacon treat you like garbage: Roguebook and Day 1 DLC (Apex Predator Pack)

Nacon treat you like garbage: Roguebook and Day 1 DLC (Apex Predator Pack)


[Update 21.06.2021: Since I penned this article, Nacon have released a statement announcing that the Apex Predator Pack will be included in the base game for all customers at no extra cost. I would like to thank them for paying attention to this matter and appreciate this decision]

Slay The Spire is an absolute classic roguelite deckbuilder / card battler that any roguelite or card game fan should play. More recently, Monster Train iterated on the formula, adding stacked vertical areas for hero and enemy placement among various other new mechanics. At the time of writing, both of these games are rated Overwhelmingly Positive on Steam.

Then there is Roguebook, developed by Abrakam Entertainment SA (the same folks who brought you the $90 card game Faeria) and published by Nacon. Roguebook expands on the Slay The Spire formula primarily by allowing you to choose a pair of heroes to work together, and adding an extra layer of strategy to the game via a freely explorable overworld. Some balance and polish issues aside it’s basically a great game that can easily sell itself to its target audience based on merit alone – which makes it even more disappointing that Nacon have decided to defecate all over its potential customer base and burn through any generated goodwill even before day 1 of launch.

The Doomed Demo

Back in February 2021 Nacon released a demo for Roguebook as part of the Steam Game Festival marketing campaign. I hadn’t been aware of the game before then, and what I found after completing the first area immediately set alarm bells ringing:

Let’s step back for a moment here and remember that a game demo is a piece of advertising that end users interact with. Nacon literally crippled their own demo (the Doomed Coin makes it essentially unplayable) and played on the well-known principle of hot state decisionmaking to manipulate you into pre-ordering a game 5 months before it released. There is actually another name for this behaviour, from Healthline:

Emotional blackmail describes a style of manipulation where someone uses your feelings as a way to control your behavior or persuade you to see things their way.

(emphasis mine)

If you are reading this thinking these are strong words for video game marketing and I’m being rather sensationalist, chances are good you are part of the reason why the games industry feels empowered to normalize this kind of abhorrent practice in the first place. Please keep reading, as you’ll see below just how little effort it takes publishers to capitalize on this mindset.

The Pre-order Full Game Demo

Normally such a marketing tactic would result in me pressing Alt+F4 and never thinking about the product again. Regular readers will know, however, that reverse engineering games by greedy publishers who raise my ire is like a pastime for me, so like an obedient microtransactionable sheeple I dutifully placed my pre-order and oh boy, ladies and gentlemen, Roguebook’s Steam package is a doozy.

The pre-order demo contains three key limitations:

  • Only 2 of the 4 playable characters from the final game are available
  • Player-controlled heroes have an artificial progress cap at level 7, which prevents some cards from being unlocked
  • Most of the skills in the perk tree (renamed to “embellishments” in the final release) are locked

The first thing to do when reverse engineering a piece of software of this complexity is to just click around all the folders and files to see if there is anything recognizable, any human readable files, any obvious assets such as images or music, any obvious indications of what game engine is being used etc.

The pre-order demo arrives as an unobfuscated Unity game using the Mono backend. We know it’s a Unity game because of the telltale folder structure with a UnityPlayer.dll in the game’s root path and a xxx_Data folder where xxx is the name of the product, with folders inside like Managed and Resources. Whenever you see this structure, you know you’re dealing with a Unity game.

We know the game is using the Mono scripting backend (as opposed to the alternative, IL2CPP) because the Managed folder contains many .NET DLLs, in particular Assembly-CSharp.dll which is required and must always be present for a Mono-based Unity game.

We scout around looking for anything interesting. The Roguebook_Data\StreamingAssets\GameBoxResources folder contains some really juicy stuff, like cards.json which contains definitions of every card, the card_scripts folder which contains behaviour scripts for every card (which includes treasures and gems since everything is defined in terms of being a card), the enemies folder which contains behaviour scripts for every enemy and so on. It’s extremely rare that so much mineable data is exposed so readily, and it’s always fun to explore games like this. My favourite part was that they even provided a handy macro-enabled Excel workbook (.xlsm file) which imports cards.json and presents it in a nice sortable, filterable table for us – thanks guys!

Although we haven’t confirmed it 100%, it seems plausible that the pre-order demo might actually contain all of the assets from the full game – such as the locked heroes and cards – which means if we can find a way to unlock them, we can more or less play the full game.

To cut an already fairly short story even shorter, the demo has very well-defined data structures for each asset that can be easily traced through in the code to find the locks and remove them.

Using dnSpy to load the folder of DLLs and edit them, we make a few tweaks.

The level cap is controlled by the method Abrakam.Common.LevelXp.LevelUpService.IsAtMaxAllowedLevel(int level) in Roguebook.Common.dll:

        public bool IsAtMaxAllowedLevel(int level)
        {
            if (level >= 6)
            {
                return true;
            }
            return false;
        }

Changing the inner condition’s return value to false ensures that the method always returns false, removing the upper level cap.

Whether a hero is locked in the demo or not is determined by getting the property Abrakam.Data.ScriptableObjects.HeroDefinition.IsEnabled defined in Roguebook.Data.dll:

        public bool IsEnabled
        {
            get
            {
                return this._isEnabled;
            }
        }

Changing the return value to true makes the two other heroes appear on the map at the start of the game and you can select them to play with.

Whether a perk/embellishment is locked in the demo or not is determined by getting the property Abrakam.SkillTree.PerkTreeNodeLayout.LockedInDemo defined in SkillTree.dll:

        public bool LockedInDemo
        {
            get
            {
                return this._lockedInDemos;
            }
        }

Once again, trivially changing the return value to false ensures no perks are locked and everything can be acquired via gameplay.

As it happens, the pre-order demo contained the entire game; the only thing that seemed to be missing were icons for the perk tree: they were mostly a generic padlock icon, but the perks themselves still worked.

Doge Wow GIFs | Tenor

As lazy as they are greedy

Having racked up 100 hours in the “demo”, I was legitimately astonished when the game came out 5 months later and they had somehow managed to make it worse, more buggy and less polished in June than it was in February. Some things have been much better balanced while other things have become unbalanced. Bugs with tile activation present in the demo are still present in the final game. Font sizes have been made less consistent and unnecessarily small on the card titles. There are card animation issues with stacking cards, Wound cards being added to your hand, card draw when the player is dead, the last applied gem is missing from the gem slot in the deck list and various other issues that weren’t present in the demo. And don’t even get me started on this:

Why is this icon not aligned with all the others? Come on guys, take some pride in your work.

I really do not know what they have been doing for the intervening five months between the demo and release, but working on the game doesn’t seem to have been high on their shopping list.

You know what they did find time to do, though? Pick a random selection of cards, gems and treasures that are included in the base game download, add an “APEX” (DLC) tag to them in the card definition file and slap them behind a $7.99 paywall. Because it’s never enough to just make a good video game is it, we always have to gouge the dumb customer and make them cough up a little more cash for the last 0.5% of the content don’t we.

Let’s take a closer look at the hard work that went into creating this DLC.

How to shit on your customers in 5 easy steps

Step 1. Mark the items you want to paywall

You already created the content when you made the original game so luckily there is no need to actually create any new content – we can just select some of the existing assets to wall off. In Roguebook, this is done by specifying in cards.json which DLC you need to own via the addOn field. For example, for the Mask of the Predator treasure:

    {
        "id": 314,
        "orderId": 6500,
        "heroType": "NEUTRAL",
        "name": "Mask of the Predator",
        "type": "treasure",
        "faeria": 0,
        "life": -1,
        "text": "When you destroy an enemy, shuffle a {refer|315} into your deck.",
        "scope": "DEFAULT",
        "rarity": "EPIC",
        "price": 250,
        "addOn": "APEX"
    },

Cards which do not require you to own any particular DLC do not have an addOn field.

Step 2. Create a DLC entitlement on Steam

Each entitlement has a unique ID number. A product can inspect which entitlement IDs are linked to the user’s account to determine which DLCs they own.

Step 3. Fetch the list of owned entitlements (DLCs) in the game

In Roguebook, this is handled by Abrakam.Durandal.AddOn.AddOnService.GetAddOns(IUserId userId) in Durandal.Steam.dll as follows:

        public override Task<List<string>> GetAddOns(IUserId userId)
        {
            uint num;
            List<string> strs = new List<string>();
            if (userId == null)
            {
                Debug.LogWarning("User not logged in so AddOns could not be retrieved");
                return Task.FromResult<List<string>>(strs);
            }
            byte[] numArray = new byte[0x400];
            if (!(SteamUser.GetAuthSessionTicket(numArray, (int)numArray.Length, out num) != HAuthTicket.Invalid) || num <= 0)
            {
                Debug.LogError("Failed to acquire Steam auth session ticket.");
            }
            else
            {
                CSteamID uniqueId = ((UserId)userId).UniqueId;
                SteamUser.BeginAuthSession(numArray, (int)num, uniqueId);
                foreach (AppId_t allDLC in this.GetAllDLCs())
                {
                    if (SteamUser.UserHasLicenseForApp(uniqueId, allDLC) != EUserHasLicenseForAppResult.k_EUserHasLicenseResultHasLicense)
                    {
                        continue;
                    }
                    strs.Add(allDLC.ToString());
                }
                SteamUser.EndAuthSession(uniqueId);
            }
            return Task.FromResult<List<string>>(strs);
        }

In a nutshell, this method simply checks that the user is logged in to Steam, fetches all the DLC IDs attached to the account, adds them (as strings) to a list and returns the list as an awaitable.

Step 4. Map the owned entitlements to our DLC tag names

The class Abrakam.Common.AddOnService in Roguebook.Common.dll handles the translation of Steam DLC IDs to DLC tags like “APEX”. The initialization looks like this:

        public async void Initialize()
        {
            Dictionary<string, Dictionary<string, AddOn>> strs = AddOnService._addOnPlatformMappings;
            string sTEAM = DurandalPlatformConstants.STEAM;
            Dictionary<string, AddOn> strs1 = new Dictionary<string, AddOn>()
            {
                { "1518150", AddOn.HEROIC_PACK },
                { "1518151", AddOn.APEX },
                { "1518152", AddOn.TIME_OF_LEGENDS }
            };
            strs[sTEAM] = strs1;
            IUserId userId = DurandalCore.PlatformUserService.GetUserId(0);
            foreach (string addOn in await DurandalCore.AddOnService.GetAddOns(userId))
            {
                string platformName = DurandalCore.Platform.PlatformName;
                if (!AddOnService._addOnPlatformMappings[platformName].ContainsKey(addOn))
                {
                    continue;
                }
                this._ownedAddOns.Add(AddOnService._addOnPlatformMappings[platformName][addOn]);
            }
            AddOnService._logger.Info(string.Concat("Owned DLCs: ", string.Join<AddOn>(", ", this._ownedAddOns)));
        }

The dictionary strs1 contains the mappings: we can see that DLC ID 1518151 on Steam corresponds to Roguebook’s Apex Predator DLC pack. This method calls the method shown in step 3 and stores a list of owned DLCs by their tag names in _ownedAddOns.

The rest of the application actually fetches the list via a call to GetOwnedAddOns():

        public List<AddOn> GetOwnedAddOns()
        {
            return this._ownedAddOns;
        }

This method is presumably called whenever the game wants to check that you have paid up for your day 1 DLC before allowing any items from it to spawn in-game.

Step 5. Make a patronizing post telling your customers why Day 1 DLC is okay

Nacon provide an excellent example of this on Roguebook’s Steam discussion page.

And that’s it! It’s literally an hour or two of work for an experienced developer.

Planning for the future

Notice that this builds a framework for future DLCs, and indeed besides HEROIC_PACK (the Heroes Skins Pack, a cosmetic day 1 DLC) and APEX (the Apex Predator Pack containing game-altering items), there is also a TIME_OF_LEGENDS DLC already defined in the game code which as of yet has – as far as I’m aware – not been mentioned.

On the deployment of single-player DLC

In online multiplayer games, delivering DLC to all users via patches whether they have purchased it or not is not only common practice, it’s necessary. If other visible players are using skins or equipment that an end user doesn’t own, that user’s client still has to be able to render the other players correctly, and perhaps access their equipment stats. This means all of the textures, asset descriptions, voiceovers etc. for multiplayer DLC must generally be present and installed for all users. Access to ownership is controlled by flags on the user’s account. These flags are stored and checked server-side when the player is connected to the game servers, making piracy difficult. A player might be able to trick their client into believing they own a particular skin, but only they will be able to see it – the server knows which skin the player is really using and will present that data to all of the other players.

With single player DLC, the situation is much different. If theoretically unowned content is on the end user’s hard drive, you are relying on the client to provide secure DRM and do entitlement enforcement. As we’ve seen above with both the pre-order demo (containing the full game assets) and the day 1 release (containing all of the DLCs), the client can absolutely not be trusted to do this. I am not sure whether Nacon were naive, simply unaware or just didn’t care enough to worry about it, but stop for a minute to consider how dangerous this is. If a malicious actor had leaked modified DLLs in February, it could have completely disrupted the launch of the game – 5 months ahead of schedule – and had huge ramifications for the studio. Given the arrogance they have displayed and how sloppy they have been with asset management in the packages they have released so far, they should be thanking their lucky stars right now that they had the luxury of conducting a relatively orderly product launch.

The correct solution to single-player DLC distribution is to bundle the relevant asset files into the DLC package and only allow them to be downloaded once the user has purchased the entitlement. The base game can be patched upon each DLC release to check for the presence or absence of said assets in the game folder and then choose to load them and register that the DLC exists on the user’s machine if present. In this way, the user can own any combination of DLC – or none at all – and the game will detect them and work correctly. Simply using a client-side flag per asset as is the case with Roguebook is incredibly sloppy and I would definitely recommend that the developers consider revising this model.

Day 1 DLC Is Not Okay

In terms of gameplay, Roguebook is – in my opinion – a relatively high quality game for its budget bracket. It might be a little rough around the edges but there is potential for brilliance here; I’ve certainly enjoyed my time with it. You would think that among the hardcore community of roguelite deckbuilder fans, such a solid product would be an easy sell. You would think… but no, Nacon/Abrakam just had to snatch defeat from the jaws of victory by trying to juice the pot harder.

Part of the problem is in the marketing. I’ve worked as an indie developer, I love to support other indies by buying their games and I know extremely well how money can be tight. I know what it’s like to pour blood, sweat and tears into making the best product you can then facing a wall of criticism from what can sometimes feel like an onslaught of indignant brats. It hurts; I’m not oblivious to this, and it’s obvious that Abrakam worked very hard to make an excellent game here. But goddammit I am so bewilderingly tired of these exploitative, predatory marketing tactics. I have come to expect this kind of behaviour from AAA publishers and that’s a large part of the reason I mostly don’t use their products anymore, favouring instead the smaller studios who are – mostly – not actually creatively and morally bankrupt yet.

If you really want to release paid DLC, don’t release it on day 1. Regardless of whether your intentions are good or otherwise, the optics are really bad. Don’t release day 1 DLC that adds gameplay elements. It doesn’t matter that it’s “only” a single player game, you are literally denying your most enthusiastic early adopters access to the entire game at its base price – which I might add is already more expensive than its two main competitors with all of the additional content they have already received. And you know who those enthusiastic early adopters are? They are your real marketing tools. They are the ones who will recommend the game to their friends. Or tell their friends to avoid the game because the publishers are greedy.

Wait a while. Develop a bunch of new content. Bundle that new content as a significant upgrade. Then sell it as DLC if you really must. That is the intended spirit of DLC, I believe.

Finally, don’t underestimate the consequences of black swan events. The only reason I reverse engineered the pre-order demo is because I was so annoyed by the manipulative Doomed Coin marketing tactic. The only reason this article exists is because the manipulation continued with the Apex Predator DLC, followed by the developers actually guilt-tripping users leaving negative reviews on Steam in the comments. As a developer myself I am strongly against piracy of any kind, but it turns out everybody who bought this game has this DLC installed anyway because they literally handed it to us. I don’t think I’m going to shed a tear if somebody figures out how to use it.

  1. No comments yet.
  1. No trackbacks yet.

Share your thoughts! Note: to post source code, enclose it in [code lang=...] [/code] tags. Valid values for 'lang' are cpp, csharp, xml, javascript, php etc. To post compiler errors or other text that is best read monospaced, use 'text' as the value for lang.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: