Between 1980 and 1990 Assembly was the most used language for everything, from videogames to nasty viruses or most of your everyday programs. After playing the game, reading and watching some technical details about La Abadía del Crímen (of which by the way there are two books about in Spanish: I & II), it's been mentioned multiple times that the original AMSTRAD CPC version was amazing technically, but that it wasn't easy to port because of the self-modifying code usages it contains.
As it sounds heretic as of today to think about patching in-memory code, I've read a bit about how it works, these scenarios being the most common ones:
- Avoiding branching code, conditional checks, etcetera by modifying in-memory instructions to jump to a different location
- Reusing memory structures with less code (e.g. make different characters use the same memory struct)
- Hiding things like interrupt call or certain strings/numbers/memory addresses, mostly either for viruses or for copy protection mechanisms
Now, back in the day this made sense: Memory and CPU were so restricted, that performing an if
frequently could really hurt your game, or keeping properly scoped functions with different logic pieces could mean extra precious cycles spent on pushing and popping registers from the stack [1]. But nowadays nobody would even think about something as simple as manually changing the instruction pointer (except trying to circumvent videogame protections, hacking videogame consoles and other shady areas). And even if you wanted, Data Execution Protection, memory pages protection, and the tons of caches between the code and it's real execution makes it a really bad idea to even try for anything normal.
But another point is that there are more modern ways to do something exactly the same as self-replicating code in videogames mostly try to do (avoiding conditionals):
- Bit masks/manipulation: Old but still very valid when performance is relevant. Caveat is code is not as readable and not everything can be made bit masks...
- Functional programming: This strictly is not removing conditionals, but you tend to reduce their usage when you think in pipelining functions and handling just input/output instead of keeping state all around.
- Object orientation/duck typing: Different classes (or functions if the language allows) provide methods that share a same interface, and you inverse where the conditional lives (although eliminating it or not depends on how you instantiate the object): instead of doing
if X then A else B
, you provide either anX-object
that doesA
or anon-X-object
that doesB
). - Function handlers: Almost the same as previous point; You define a function handler, a C# delegate, you name it, and just change it by another one when you wish to modify the current behaviour. Super-simple C# example I did long ago here.
I'd definitely go for function handlers if I were to build any non-trivial game today: it is clean and very friendly towards testing at both sides, as you can inject fake AIs, and test each one in isolation.
In the end you're just modifying a function pointer to have a different address, so it is really really similar to adding or changing a JMP
assembly instruction. Plus nobody will get crazy trying to debug logic that has been patched in-memory and no longer resembles the source code.
Which other ways of avoiding conditionals and/or organizing your code to avoid huge switches do you know?
[1] : Actually, those anti-object orientation "patterns" would come back with J2ME, where hardware constraints once again favoured a non-Java approach of having a single class with as much inline code, global variables and few methods as possible.
Tags: Development