This is the first entry in a series of articles where we will be discussing and going through the process of creating a content shim mod for loading content from the format used by Darkest Dungeon inside its sequel, Darkest Dungeon II. This entry is mainly concerned with defining the concepts we will be making use of, and can thus be mostly skipped safely if one feels that such is appropriate.
Layer Cake
Before we get into shims in specific we need to take a small detour to define the broader category that they belong to, Compatibility Layers.
The very basic view is that a compatibility layer is a piece of software that goes between two other pieces of software at runtime to make them peacefully coexist, certainly not a very detailed explanation, but it will be mostly enough for our purposes. This wider categorization often includes programs which perform a lot of internal logic to make things work, one of the most well known examples for such being wine or its fork Proton, which provides implementations of many internal windows components as well as syscall & format translation to allow PE files1 compiled for windows to run on other systems.
In contrast, a “shim” is defined as a (usually) lightweight compatibility layer that serves to make two different interfaces behave as if they are using each other’s expected interface without having to directly change either one. The name comes from small pieces of material that are used to fill small gaps between two objects, the comparison is perhaps obvious to those who might have encountered the term outside of the computing context, but the etymological origins of a concept’s name can often give us crucial hints about the design space they occupy.
Wedging in between
For the sake of our work here, we shall divide shims into two different categories: we’ll be defining them as API Shims and Content Shims.
API Shims
The usual type of shim, mostly because the conditions that call for their use are way more common. These translate the calls intended for one library or other invokable procedure into calls to another. They are often created out of necessity, either to render an old system operable again or two remove the innate restrictions of the existing version.
One type of additional layer that also falls into this category unintentionally is the type of shim often used when modding games without a mod framework included, which enable code injection/function hooking by performing all the normal functions of an API shim, just to and from the same interface and with extra work in between.
Content Shims
The less frequent type, and our focus for this series. They are rare for two main reasons, the main reason is that frankly, you can simply convert your content/assets offline instead of at runtime in most cases. Combine that with cases forcing translation of old data being relatively rarer compared to the API equivalent2 and these mostly come up when one is gradually re-implementing old software that is still active.
Seeing the above description you might be wondering why we are even bothering with making one, if they are so easily obsoleted by a offline converter. Certainly, if our only goal here was to load the original content in the newer entry we would have been better serve making a converter or, if we felt particularly bored, converting them by hand. There is one simple reason why this path is discouraged in our situation.
Asset Formats As Protocol
Those with particular experience designing or working with systems that interoperate human readable formats with machine targeted ones might groan at that header. Such a thing, in the form of assets taking on duties beyond their nature as assets, often3 indicates a system that has either ballooned past singular understanding or one that has abandoned the distinction between asset and procedure, eventually finding itself almost forced to treat all as unrecognizable sludge that must be purified before every step.
Thankfully we are handling a much more benign, mostly unintentional manifestation of the concept. One borne of continuous, omnidirectional production of content using the format. Of Modding.
There is a significant amount of fanmade content for Darkest Dungeon, the game we will be tinkering on in this series. There is still new fanmade content coming out all the time, as the sequel has somewhat switched genres4. In such a scenario the amount of data converted becomes unbounded, and the idea of repeatedly running offline converters becomes inconvenient. Enabling the newer entry to benefit from the still beating modding scene of the first one without requiring any effort from the creators of the already existent work is enough of a benefit (and as we will hopefully5 find out later, easy enough) to more than justify our runtime layer. Especially considering how (at the time of writing this) the sequel seems to not have any mods on the scale or type of the ones that the older game offers.
Up Ahead, The Seat of Our Research
If any readers are familiar with the games at hand they might feel the need to point out that beyond the changes to the main gameplay loop, the sequel also represents a change in dimension, being composed of 3D models compared to the original sprites. While this isn’t as much of a problem due to the game mostly keeping the same perspective, even if it hadn’t:
Let’s just say that we have solutions.
For the next part in this series, we’ll be diving into what information we have regarding the games themselves and how they treat their (seemingly quite similar5) formats. See you there!
-
PE stands for Portable Executable, which is the often-not-so-portable executable binary format used by Windows (and the UEFI vm!), Macs use the alternative Mach-0 format with Linux and most others preferring ELF (Executable and Linkable Format) as their executable of choice. ↩︎
-
No one feels the need to break asset compatibility the way they do ABI/APIs6, really. At most your format just gets outdone by the new one. ↩︎
-
But not always! Concious decisions to blur the lines, like Interchange Formats (Collada, for example) do exist to make it easier for certain pieces of data to be smoothly moved across data streams of all kinds. ↩︎
-
Thankfully, they have kept some inner gameplay loops mostly intact,our shim wouldn’t be of much use if that wasn’t the case. ↩︎
-
I did take a cursory look or two to make sure this was doable, but part of the point of this series is to go through the steps together,thus I do not yet have a map for that leg of this journey of ours. ↩︎ ↩︎
-
Not that valid reasons to break code interfaces don’t exist, it’s a frequent complaint that some systems are too reluctant to do so. ↩︎