Nowadays, most x86 Assembly programs are intended to run in 64 bit or at least 32 bit environments. This means that most features specific to 16 bit development are now no longer supported. Thus, some workarounds related to this were needed in the development of Kapow.
What is Real Mode?
You might notice that in Real Mode we have access to a whole megabyte of RAM, but yet we are using 16 bit registers to address it. How is it possible to address one million bytes, if in 16 bits we can only represent 64 thousand distinct values? The answer to this question lies in Intel's solution to this problem, Memory Segmentation. This is where another register is used to represent an additional 4 bits for addressing. This gives us a total of 20 bits, enough to address one megabyte. In this model, every 64 kilobytes of RAM is referred to as one segment.
The main problem with using NASM in 16 bit code starts when you are trying to modify the value at a certain label. For example, consider the following code:
var: dw 0x123 mov ax, [var] mul 20 mov [var], ax
Normally, you would expect this to work. You could use the location at
var effectively as a variable, addressing and using its contents. However, when dealing with 16 bit code NASM refuses to do this. Upon further digging, it seemed possible to use
seg in order to load the segment of the label, and from this be able to address. But this is impossible to do without using Microsoft
COM files. Thus, in order to preserve the debugging advantages of using a modern ELF format, I had to find another solution.
Bootlegged Effective Addressing
In order to solve this problem, it was necessary to define an area in memory where variables would be stored. This was done in the
game/var_locs.asm file. Here, a base offset is defined, in this case
0xC000 (Which is an offset from the beginning of the segment where the game's executable resides), and subsequently the location of every single variable used by the game is defined. This also necessitates the initialization of each defined memory location, which is done in the respective file where the variables are used.
The easiest solution to loading and using sprites would be something like this:
sprite: incbin "./mysprite.bin" mov ax, seg sprite ; Moves the segment the sprite is in to ax mov es, ax mov ax, sprite call my_draw_routine
However, due to the aforementioned problems, this is simply not possible. Instead, a similar approach must be taken to the one used with variables, and the locations used are stored within the file
game/sprite_constants.asm. This time, the sprites are stored and loaded in a different segment.
In order to do this, the first thing that is done is to fill up the rest of the code segment with 0's. This is done with the following line in
times 0xFFFF - ($-$$) db 0
Doing this greatly aids in the loading process, as we know where the sprites are going to be, exactly 64 kilobytes from the start of the code segment, or in other words in the following segment.
Makefile then takes care of writing the assets to the binary executable, as can be seen from this snippet:
assets: mkdir -p out/assets gcc -o out/extract_palette.o src/assets/extract_palette.c $(CONV) -compress none assets/PALETTE.PCX out/assets/PALETTE.bmp out/extract_palette.o out/assets/PALETTE.bmp out/assets/palette.bin cat out/assets/palette.bin >> out/HD.img gcc -o out/conv_asset.o src/assets/conv_asset.c $(call add_asset,BOMB,16,32) $(call add_asset,BOMB8S,8,8) $(call add_asset,BOMBER,32,32) $(call add_asset,PADDLE,32,8) $(call add_asset,CHAR0,12,12) $(call add_asset,CHAR1,12,12) $(call add_asset,CHAR2,12,12) $(call add_asset,CHAR3,12,12) $(call add_asset,CHAR4,12,12) $(call add_asset,CHAR5,12,12) $(call add_asset,CHAR6,12,12) $(call add_asset,CHAR7,12,12) $(call add_asset,CHAR8,12,12) $(call add_asset,CHAR9,12,12) $(call add_asset,TOPSC,40,12) $(call add_asset,EXP1,8,8) $(call add_asset,EXP2,8,8) $(call add_asset,EXP3,8,8) $(call add_asset,EXP4,8,8) $(call add_asset,CHALKBOA,100,32) $(call add_asset,ENTER,96,20)
Finally, upon booting,
bootup/booter.asm takes care of loading all of the sprite data at the correct segment, where
asset_storage is the segment where sprites are to be loaded to. This can be seen here:
mov ax, asset_storage mov es, ax mov ah, 0x2 mov al, 128 mov ch, 0 mov dh, 2 mov cl, 4 mov dl, 0x80 mov bx, 0 ; address to copy to int 0x13