; ES:DI = String ptr ; RET: DI = End of string ptr BASE_TTYWriteDOSString_ES: .loop: MOV AL, [ES:DI] CMP AL, 0x24 JE .end ; The mess of branches is here to avoid printing the $ char CALL BIOS_WriteTTYChar INC DI JMP .loop .end: RET ; ES:DI = String ptr ; RET: DI = End of string ptr BASE_TTYWriteASCIIZString_ES: .loop: MOV AL, [ES:DI] CMP AL, 0x00 JE .end ; The mess of branches is here to avoid printing the NUL char CALL BIOS_WriteTTYChar INC DI JMP .loop .end: RET ; DS:DI = String ptr ; RET: DX = End of string ptr BASE_TTYWriteASCIIZString_DS: .loop: MOV AL, [DS:DI] CMP AL, 0x00 JE .end ; The mess of branches is here to avoid printing the NUL char CALL BIOS_WriteTTYChar INC DI JMP .loop .end: RET ; DS:DX = ASCIIZ Filename ptr ; OUT: ES = Loaded file, remember to free it with DOS_FreeMemory BASE_AllocLoadFile: ; CALL DOS_OpenReadonly ; We only need DX set, which is set by the caller JC ERROR_GeneralDOSError PUSH AX ; Store the file handle on the stack MOV BX, 4096 ; Allocate a full segment, we'll modify that in a second CALL DOS_AllocateMemory JC ERROR_GeneralDOSError MOV ES, AX ; Segment now in ES POP BX ; File handle now in BX PUSH BX MOV CX, 0xFFFF ; Read to end XOR DX, DX ; Offset in ES:DX CALL DOS_ReadFileIntoES JC ERROR_GeneralDOSError ; AX = Actual number of bytes SHR AX, 4 ; Divide by 16 to get the para number INC AX ; Failsafe if not divisible by 16 MOV BX, AX ; Input to INT 21H AH = 4AH requested size in paras ; ES Still has our file segment MOV AH, 4AH ; INT 21H AH = 4AH MODIFY MEMORY BLOCK INT 21H JC ERROR_GeneralDOSError ; BX still has our file handle POP BX ; No it didn't. Debugging moment CALL DOS_CloseFile ; Free it and done JC ERROR_GeneralDOSError RET ; ES:SI = Bitmap ptr, header and everything ; CX = X ; DX = Y BASE_DrawBitmapFromFile: CMP BYTE [ES:SI], 'B' ; Check JNE .badmagic ; The INC SI ; Magic CMP BYTE [ES:SI], 'M' ; Number JNE .badmagic ; Or INC SI ; Fail CMP BYTE [ES:SI], 'A' ; If JNE .badmagic ; Invalid INC SI CMP BYTE [ES:SI], 0x00 ; Bitmap version, 0 is the only valid one JNE .nosupport ; If other, throw an error INC SI CMP BYTE [ES:SI], 0x00 ; Bitmap kind, this fx only supports unindexed bitmaps JNE .nosupport ; If other, throw an error INC SI ; Next field CMP BYTE [ES:SI], 0x01 ; Color range, 0 is 4bpp, 1 is 8bpp, 0 is not supported at this time JNE .nosupport ; Say so INC SI ; Next field MOV WORD AX, [ES:SI] ; Stride, save it in AX ADD SI, 2 ; Bump up by a WORD MOV WORD BX, [ES:SI] ; Lines, save in BX ADD SI, 2 ; And bump up, SI is now our bitmap data PUSH CX ;PUSH DX PUSH DS ; Since the bitmap routine wants the data ptr in DS, we save it MOV CX, ES ; Then mess with intermediates MOV DS, CX ; And set the DS POP ES ; Set ES to our old DS ;POP DX ; Get the Y coord POP CX ; And the X coord PUSH ES ; Save ES (Holding DS) again CALL BASE_DrawBitmap ; And finally draw our bitmap POP DS ; And set DS to its former value RET .badmagic: CALL BIOS_SetVideoMode03H MOV DX, STR_ErrInvalidMagic CALL DOS_PrintString JMP Exit .nosupport: CALL BIOS_SetVideoMode03H MOV DX, STR_ErrNotSupported CALL DOS_PrintString JMP Exit RET ; AL = Color BASE_ClearScreen: SHL EAX, 16 ; Store AX in the upper 16b of EAX MOV AX, [RAM_Framebuffer] ; Segment A000h (VGA framebuffer) MOV ES, AX ; Store in ES XOR DI, DI ; Clear DI, we want to start at the, well, start SHR EAX, 16 ; Restore AX from the upper 16b of EAX ; We need to fill the entirety of EAX with 4 bytes as in [AL:AL:AL:AL] ; There's probably an instruction for that which I'm not aware of... MOV AH, AL ; Now, copy lower 8b of AX to upper 8b of AX MOV CX, AX ; Then cache AX, we set CX later so it's not important here SHL EAX, 16 ; Shift the bits MOV AX, CX ; And fill the lower 16b of EAX with its upper 16b MOV CX, 16000 ; Fill 320 * 200 / 4 DWORDs REP STOSD ; Write EAX to ES:DI, inc DI AND EAX, 0xFFFF ; Remove the upper 16 bits of EAX so that they're not just randomly there RET ; DS:SI = Input ; AX = Stride (MUST be 4 or greater) ; BX = Lines ; CX = X ; DX = Y ; NOTE: EBP is used as general-purpose storage, BP is preserved, but the upper 16 bits are not. ; NOTE: All upper 16 bits of 32-bit registers will be lost BASE_DrawBitmap: ; Let's think about it ; DS:SI = Input ; AX = Stride ; BX = Lines ; CX = X ; DX = Y ; And so ; Wait, we need to save AX, we can't use the SHL/SHR trick because CalculateBitmapOffset uses MUL and ADD... ; But DrawLine destroys DI, and it's unused in CalculateBitmapOffset! MOV DI, AX ; So save AX first SHL EDI, 16 ; Shift it to upper 16b MOV DI, BX ; And save BX in the lower 16b CALL BASE_CalculateBitmapOffset ; X = CX Y = DX; RET AX, But it messes with BX MOV BX, DI ; Then first BX -> DI -> BX SHR EDI, 16 ; Shift MOV CX, DI ; And AX -> DI -> CX ; Now AX = Offset, CX = Stride, DrawLine expects that exactly ; Wait, but we need CX for the counter. Fuck. Could ditch LOOP and use CMP/Jxx... ;XOR DX, DX ; Okay, so we can safely clean this, we already calc'd the offset ;INC BX ; Implementation detail because JLE is weird MOV DX, BX MOV BX, CX ; Backup the stride SHL EBP, 16 ; Okay, we're doing A LOT of weirdness here, we're using the stack pointer as general-putpose storage MOV BP, DX ; Store the lines there .loop: CALL BASE_DrawImageLine ; Expects AX = Offset, CX = Stride, DS:SI = Input, Destroys DI ADD SI, CX ; Increment the data ptr by stride ADD AX, 320 ; And increment the offset by 320 to wrap around ;INC DX ; Finally, DX is our line counter, so increment it by 1 ;CMP DX, BX ; And compare it against lines + 1 ;JNE .loop ; If not there yet, loop DEC DX JNZ .loop MOV AX, BX ; And bring back the stride MOV BX, BP ; Now get back the lines from the base pointer SHR EBP, 16 ; And fix the base pointer or the program will... well... RET ; CX = X ; DX = Y ; RET: AX = Offset BASE_CalculateBitmapOffset: PUSH DX MOV AX, DX ; AX = Y MOV BX, 320 MUL BX ; DX:AX = Y * 320 POP DX ADD AX, CX ; AX = offset RET ; AX = Offset ; CX = Count/Stride (MUST be 4 or greater, and a power of 2) ; NOTE: Make sure the upper 16 bits of the 32-bit regs are clear and not important ; But, 16-bit registers are preserved ; Destroys DI ; DS:SI = Input BASE_DrawImageLine: ; Okay, so ; First of all, MOVSx copies from DS:SI to ES:DI ; So we need DI to be the offset with regards to the framebuffer ; And that offset can be calculated via BASE_CalculateBitmapOffset MOV DI, AX SHL EBX, 16 ; Save BX in the upper 16b of EBX MOV BX, SI ; Then store the old SI in lower 16b of EBX ; ES is used quite a bit throughout the program, so we save it SHL EDX, 16 ; Save DX in the upper 16b of EDX MOV DX, ES ; Then save ES in DX SHL EAX, 16 ; Store AX in the upper 16 bits of EAX ; Then we set ES to the framebuffer segment address MOV AX, [CS:RAM_Framebuffer] ; Working framebuffer MOV ES, AX ; DO NOT Assume CS = DS ; MOV AX, CX ; Store CX in the temporarily free AX ; === Note note note === ; Don't touch EAX/AX/AL/AH before after the MOV CX, AX instruction ; Pretty much every procedure call messes with it, so just don't ; === Note note note === ; Since we're using MOVSD, we need to do / 4 SHR CX, 2 ; And go ahead REP MOVSD ; DS:SI -> ES:DI MOV CX, AX ; Bring back CX and then SHR EAX, 16 ; Bring back the old AX MOV SI, BX ; Restore SI SHR EBX, 16 ; And restore BX MOV ES, DX ; Restore ES SHR EDX, 16 ; And restore DX RET ; AL = Color ; CX = X ; DX = Y BASE_DrawPixel: PUSH AX MOV AX, [RAM_Framebuffer] ; Segment A000h (VGA framebuffer) MOV ES, AX ; Coordinates ; MOV CX, 100 ; X ; MOV DX, 50 ; Y ; Calculate offset: offset = Y * 320 + X ;MOV AX, DX ; AX = Y ;MOV BX, 320 ;MUL BX ; DX:AX = Y * 320 ;ADD AX, CX ; AX = offset CALL BASE_CalculateBitmapOffset MOV DI, AX POP AX ; MOV AL, 0x4F ; Color index (some pinkish violet maybe?) STOSB ; Write AL to ES:DI, inc DI RET ; Wait for the vertical blanking period, do nothing and return. ; Use BASE_Present instead for rendering BASE_WaitForVerticalBlank: ;PUSHA PUSH AX PUSH DX MOV DX, 0x3DA .v1: IN AL, DX TEST AL, 0x08 JZ .v1 ; Wait for VBLANK to begin .v2: IN AL, DX TEST AL, 0x08 JNZ .v2 ; Wait for VBLANK to end ;POPA POP DX POP AX RET ; Wait for the vertical retrace, fill the VGA framebuffer, wait for the vertical retrace to end, and return ; No input BASE_Present: PUSH AX ; Save AX PUSH DX ; Then save DX MOV DX, 0x3DA .v1: IN AL, DX TEST AL, 0x08 JZ .v1 ; Wait for VBLANK to begin PUSH DS ; Importantly, store the old data segment because the code will crap itself if we don't bring it back MOV DS, [RAM_Framebuffer] ; Working buffer MOV AX, 0xA000 MOV ES, AX ; VGA Framebuffer MOV CX, 16000 ; 320 * 200 / 4 XOR SI, SI ; Clear source offset XOR DI, DI ; And clear destination offset REP MOVSD ; TIMES CX DS:SI -> ES:DI POP DS ; Restore the data segment MOV DX, 0x3DA .v2: IN AL, DX TEST AL, 0x08 JNZ .v2 ; Wait for VBLANK to end POP DX ; Restore old DX POP AX ; And restore old AX RET ; ES = Game file segment ; Exit on fail BASE_VerifyGameFileMagic: ; Magic is DGF and then 0xCF MOV AL, 'D' MOV AH, [ES:0] CMP AL, AH JNE .badmagic MOV AL, 'G' MOV AH, [ES:1] CMP AL, AH JNE .badmagic MOV AL, 'F' MOV AH, [ES:2] CMP AL, AH JNE .badmagic MOV AL, 0xCF MOV AH, [ES:3] CMP AL, AH JNE .badmagic JMP .goodmagic .badmagic: MOV DX, STR_ErrInvalidMagic CALL DOS_PrintString JMP Exit .goodmagic: RET ; AL = 0 = 16K 1 = 32K 2 = 64L ; RET: AX = Segment or Early exit BASE_AllocFileMem: ; Not using a lookup table because it'd be overkill CMP AL, 0 JE .a16k CMP AL, 1 JE .a32k CMP AL, 2 JE .a64k MOV DX, STR_ErrInvalidAllocation CALL DOS_PrintString JMP Exit .a16k: MOV BX, 1024 JMP .alnst .a32k: MOV BX, 2048 JMP .alnst .a64k: MOV BX, 4096 .alnst: CALL DOS_AllocateMemory JC ERROR_GeneralDOSError RET