Svart magi med Lua och C++
I slutet av 90-talet och början av 2000-talet släpptes ett antal klassiska datorrollspel av utgivaren Interplay. Bland dessa ingick Baldur’s Gate, Icewind Dale och Planescape: Torment. Dessa tidiga datorspel hade några olika element gemensamt: isometriskt perspektiv, gameplay i realtid, regler baserade på klassiska bordsrollspel samt en spelmotor skriven i C++ med namnet Infinity Engine.
Eftersom datorerna och operativsystemen förändrats mycket sen dessa spel utvecklades kan det idag vara ganska svårt att installera och köra spelen. En spelstudio vid namn Beamdog (numera en del av det problemtyngda svenska konglomeratet Embracer) gjorde dock ett antal omtyckta moderniserade versioner av dessa spel. Alla utom ett: Icewind Dale 2, ett spel som gavs ut i mitten av 2002.
Producenterna på Beamdog hade kontaktat utgivare av de tidigare spelen, spelens licensägare, spelstudios som utvecklat dem, spelstudios som skapats av resterna av tidigare inblandade spelstudios som gått i konkurs, samt till och med individuella teammedlemmar som arbetat på Icewind Dale 2, men utan resultat. Det visade sig att källkoden till Icewind Dale 2 var försvunnen. Det enda som återstod var en kompilerad version av spelet för Windows som bland annat säljs av Good Old Games.
År 2020 hade hela spelindustrin gett upp tanken på att en moderniserad version av Icewind Dale 2 någonsin skulle släppas. Hela spelindustrin? Nej, i ett litet hörn av internet samlades en skara fans som vägrade ge upp hoppet. Tre år senare publicerade fansen, eller dem som nu kallade sig Red Chimera Group, vad de döpt till Icewind Dale 2: Enhanced Edition. Denna massiva mod till spelet innehöll stöd för widescreen, nya magier, föremål, fiender och allierade, tillägg och ändringar på reglerna samt mycket, mycket mer.
Men hur var det möjligt att åstadkomma alla dessa ändringar på ett spel där källkoden varit förlorad i nästan 20 år? För att svara på det måste vi först titta på hur ett C++-program ser ut och fungerar.
Vi börjar med att skriva ett enkelt Hello World-program i C++:
#include <iostream>
int main() {
std::cout << "hello world!\n";
return 0;
}
Som du kanske gissat så skriver detta program ut strängen “hello world!
”, returnerar en nolla och stänger ned sig. I nästa steg utökar vi programmet något och bryter ut std::cout
-satsen till en separat funktion:
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include <iostream>
#include <luabind/luabind.hpp>
void greet() {
std::cout << "hello world!\n";
}
extern "C" int init(lua_State* L) {
using namespace luabind;
open(L);
module(L)
[
def("greet", &greet)
];
return 0;
}
Som du ser ovan så har vi bibliotek från språket Lua. Lua är ett imperativt, procedurellt och dynamiskt skriptspråk som är gjort för att interagera med C och C++. Genom att göra ovanstående modifieringar på vår C++-kod så gör vi greet-funktionen tillgänglig för Lua-kontexten. Nästa steg är att kompilera ovanstående kod som ett dynamiskt bibliotek (så kallade DLL-filer i Windows-världen och SO-filer i Linux-världen) som vi döper till hello_world.dll och sedan skapa ett lua-skript med följande innehåll:
loadlib('hello_world.dll', 'init')()
greet()
Detta skript laddar helt enkelt in vårt dynamiska bibliotek och kör funktionen som vi skapade i C++. Detta är samma trick som användes av Red Chimera-gruppen för att kunna ändra i funktionerna som används av Icewind Dale 2 utan att ha tillgång till källkoden. Långsamt och metodiskt gick man igenom alla dynamiska bibliotek för spelet och bytte vid behov ut de ursprungliga funktionerna mot nya funktioner skrivna i Lua. Sedan ersatte man den ursprungliga binärfilen för att starta spelet med en ny modifierad binärfil som använder de nya funktionerna.
Resultatet (inklusive källkoden) är nu fritt tillgängligt för alla som har en utgåva av spelet, oavsett om det är de ursprungliga CD-skivorna eller en digital version. Att kunna skapa ny kod i Lua som kan anropa existerande, kompilerad kod skriven i C eller C++ är ett kraftfullt och användbart verktyg som hör hemma i varje utvecklares verktygslåda. Vem vet när du kan behöva återuppliva en kodbas från världen efter denna?