| 1 |
; ES:DI = String ptr |
| 2 |
; RET: DI = End of string ptr |
| 3 |
BASE_TTYWriteDOSString_ES: |
| 4 |
.loop: |
| 5 |
MOV AL, [ES:DI] |
| 6 |
CMP AL, 0x24 |
| 7 |
JE .end ; The mess of branches is here to avoid printing the $ char |
| 8 |
CALL BIOS_WriteTTYChar |
| 9 |
INC DI |
| 10 |
JMP .loop |
| 11 |
.end: |
| 12 |
RET |
| 13 |
|
| 14 |
|
| 15 |
; ES:DI = String ptr |
| 16 |
; RET: DI = End of string ptr |
| 17 |
BASE_TTYWriteASCIIZString_ES: |
| 18 |
.loop: |
| 19 |
MOV AL, [ES:DI] |
| 20 |
CMP AL, 0x00 |
| 21 |
JE .end ; The mess of branches is here to avoid printing the NUL char |
| 22 |
CALL BIOS_WriteTTYChar |
| 23 |
INC DI |
| 24 |
JMP .loop |
| 25 |
.end: |
| 26 |
RET |
| 27 |
|
| 28 |
; DS:DI = String ptr |
| 29 |
; RET: DX = End of string ptr |
| 30 |
BASE_TTYWriteASCIIZString_DS: |
| 31 |
.loop: |
| 32 |
MOV AL, [DS:DI] |
| 33 |
CMP AL, 0x00 |
| 34 |
JE .end ; The mess of branches is here to avoid printing the NUL char |
| 35 |
CALL BIOS_WriteTTYChar |
| 36 |
INC DI |
| 37 |
JMP .loop |
| 38 |
.end: |
| 39 |
RET |
| 40 |
; DS:DX = ASCIIZ Filename ptr |
| 41 |
; OUT: ES = Loaded file, remember to free it with DOS_FreeMemory |
| 42 |
BASE_AllocLoadFile: |
| 43 |
; |
| 44 |
CALL DOS_OpenReadonly ; We only need DX set, which is set by the caller |
| 45 |
JC ERROR_GeneralDOSError |
| 46 |
PUSH AX ; Store the file handle on the stack |
| 47 |
MOV BX, 4096 ; Allocate a full segment, we'll modify that in a second |
| 48 |
CALL DOS_AllocateMemory |
| 49 |
JC ERROR_GeneralDOSError |
| 50 |
MOV ES, AX ; Segment now in ES |
| 51 |
POP BX ; File handle now in BX |
| 52 |
PUSH BX |
| 53 |
MOV CX, 0xFFFF ; Read to end |
| 54 |
XOR DX, DX ; Offset in ES:DX |
| 55 |
CALL DOS_ReadFileIntoES |
| 56 |
JC ERROR_GeneralDOSError |
| 57 |
; AX = Actual number of bytes |
| 58 |
SHR AX, 4 ; Divide by 16 to get the para number |
| 59 |
INC AX ; Failsafe if not divisible by 16 |
| 60 |
MOV BX, AX ; Input to INT 21H AH = 4AH requested size in paras |
| 61 |
; ES Still has our file segment |
| 62 |
MOV AH, 4AH ; INT 21H AH = 4AH MODIFY MEMORY BLOCK |
| 63 |
INT 21H |
| 64 |
JC ERROR_GeneralDOSError |
| 65 |
; BX still has our file handle |
| 66 |
POP BX ; No it didn't. Debugging moment |
| 67 |
CALL DOS_CloseFile ; Free it and done |
| 68 |
JC ERROR_GeneralDOSError |
| 69 |
RET |
| 70 |
|
| 71 |
|
| 72 |
; ES:SI = Bitmap ptr, header and everything |
| 73 |
; CX = X |
| 74 |
; DX = Y |
| 75 |
BASE_DrawBitmapFromFile: |
| 76 |
CMP BYTE [ES:SI], 'B' ; Check |
| 77 |
JNE .badmagic ; The |
| 78 |
INC SI ; Magic |
| 79 |
CMP BYTE [ES:SI], 'M' ; Number |
| 80 |
JNE .badmagic ; Or |
| 81 |
INC SI ; Fail |
| 82 |
CMP BYTE [ES:SI], 'A' ; If |
| 83 |
JNE .badmagic ; Invalid |
| 84 |
|
| 85 |
INC SI |
| 86 |
CMP BYTE [ES:SI], 0x00 ; Bitmap version, 0 is the only valid one |
| 87 |
JNE .nosupport ; If other, throw an error |
| 88 |
|
| 89 |
INC SI |
| 90 |
CMP BYTE [ES:SI], 0x00 ; Bitmap kind, this fx only supports unindexed bitmaps |
| 91 |
JNE .nosupport ; If other, throw an error |
| 92 |
INC SI ; Next field |
| 93 |
CMP BYTE [ES:SI], 0x01 ; Color range, 0 is 4bpp, 1 is 8bpp, 0 is not supported at this time |
| 94 |
JNE .nosupport ; Say so |
| 95 |
INC SI ; Next field |
| 96 |
MOV WORD AX, [ES:SI] ; Stride, save it in AX |
| 97 |
ADD SI, 2 ; Bump up by a WORD |
| 98 |
MOV WORD BX, [ES:SI] ; Lines, save in BX |
| 99 |
ADD SI, 2 ; And bump up, SI is now our bitmap data |
| 100 |
|
| 101 |
PUSH CX |
| 102 |
;PUSH DX |
| 103 |
PUSH DS ; Since the bitmap routine wants the data ptr in DS, we save it |
| 104 |
MOV CX, ES ; Then mess with intermediates |
| 105 |
MOV DS, CX ; And set the DS |
| 106 |
POP ES ; Set ES to our old DS |
| 107 |
;POP DX ; Get the Y coord |
| 108 |
POP CX ; And the X coord |
| 109 |
PUSH ES ; Save ES (Holding DS) again |
| 110 |
CALL BASE_DrawBitmap ; And finally draw our bitmap |
| 111 |
POP DS ; And set DS to its former value |
| 112 |
RET |
| 113 |
|
| 114 |
.badmagic: |
| 115 |
CALL BIOS_SetVideoMode03H |
| 116 |
MOV DX, STR_ErrInvalidMagic |
| 117 |
CALL DOS_PrintString |
| 118 |
JMP Exit |
| 119 |
.nosupport: |
| 120 |
CALL BIOS_SetVideoMode03H |
| 121 |
MOV DX, STR_ErrNotSupported |
| 122 |
CALL DOS_PrintString |
| 123 |
JMP Exit |
| 124 |
RET |
| 125 |
|
| 126 |
|
| 127 |
; AL = Color |
| 128 |
BASE_ClearScreen: |
| 129 |
SHL EAX, 16 ; Store AX in the upper 16b of EAX |
| 130 |
MOV AX, [RAM_Framebuffer] ; Segment A000h (VGA framebuffer) |
| 131 |
MOV ES, AX ; Store in ES |
| 132 |
XOR DI, DI ; Clear DI, we want to start at the, well, start |
| 133 |
SHR EAX, 16 ; Restore AX from the upper 16b of EAX |
| 134 |
; We need to fill the entirety of EAX with 4 bytes as in [AL:AL:AL:AL] |
| 135 |
; There's probably an instruction for that which I'm not aware of... |
| 136 |
MOV AH, AL ; Now, copy lower 8b of AX to upper 8b of AX |
| 137 |
MOV CX, AX ; Then cache AX, we set CX later so it's not important here |
| 138 |
SHL EAX, 16 ; Shift the bits |
| 139 |
MOV AX, CX ; And fill the lower 16b of EAX with its upper 16b |
| 140 |
MOV CX, 16000 ; Fill 320 * 200 / 4 DWORDs |
| 141 |
REP STOSD ; Write EAX to ES:DI, inc DI |
| 142 |
AND EAX, 0xFFFF ; Remove the upper 16 bits of EAX so that they're not just randomly there |
| 143 |
RET |
| 144 |
|
| 145 |
; DS:SI = Input |
| 146 |
; AX = Stride (MUST be 4 or greater) |
| 147 |
; BX = Lines |
| 148 |
; CX = X |
| 149 |
; DX = Y |
| 150 |
; NOTE: EBP is used as general-purpose storage, BP is preserved, but the upper 16 bits are not. |
| 151 |
; NOTE: All upper 16 bits of 32-bit registers will be lost |
| 152 |
BASE_DrawBitmap: |
| 153 |
; Let's think about it |
| 154 |
; DS:SI = Input |
| 155 |
; AX = Stride |
| 156 |
; BX = Lines |
| 157 |
; CX = X |
| 158 |
; DX = Y |
| 159 |
; And so |
| 160 |
; Wait, we need to save AX, we can't use the SHL/SHR trick because CalculateBitmapOffset uses MUL and ADD... |
| 161 |
; But DrawLine destroys DI, and it's unused in CalculateBitmapOffset! |
| 162 |
MOV DI, AX ; So save AX first |
| 163 |
SHL EDI, 16 ; Shift it to upper 16b |
| 164 |
MOV DI, BX ; And save BX in the lower 16b |
| 165 |
CALL BASE_CalculateBitmapOffset ; X = CX Y = DX; RET AX, But it messes with BX |
| 166 |
MOV BX, DI ; Then first BX -> DI -> BX |
| 167 |
SHR EDI, 16 ; Shift |
| 168 |
MOV CX, DI ; And AX -> DI -> CX |
| 169 |
; Now AX = Offset, CX = Stride, DrawLine expects that exactly |
| 170 |
|
| 171 |
; Wait, but we need CX for the counter. Fuck. Could ditch LOOP and use CMP/Jxx... |
| 172 |
;XOR DX, DX ; Okay, so we can safely clean this, we already calc'd the offset |
| 173 |
;INC BX ; Implementation detail because JLE is weird |
| 174 |
MOV DX, BX |
| 175 |
MOV BX, CX ; Backup the stride |
| 176 |
SHL EBP, 16 ; Okay, we're doing A LOT of weirdness here, we're using the stack pointer as general-putpose storage |
| 177 |
MOV BP, DX ; Store the lines there |
| 178 |
.loop: |
| 179 |
CALL BASE_DrawImageLine ; Expects AX = Offset, CX = Stride, DS:SI = Input, Destroys DI |
| 180 |
ADD SI, CX ; Increment the data ptr by stride |
| 181 |
ADD AX, 320 ; And increment the offset by 320 to wrap around |
| 182 |
;INC DX ; Finally, DX is our line counter, so increment it by 1 |
| 183 |
;CMP DX, BX ; And compare it against lines + 1 |
| 184 |
;JNE .loop ; If not there yet, loop |
| 185 |
DEC DX |
| 186 |
JNZ .loop |
| 187 |
MOV AX, BX ; And bring back the stride |
| 188 |
MOV BX, BP ; Now get back the lines from the base pointer |
| 189 |
SHR EBP, 16 ; And fix the base pointer or the program will... well... |
| 190 |
RET |
| 191 |
|
| 192 |
; CX = X |
| 193 |
; DX = Y |
| 194 |
; RET: AX = Offset |
| 195 |
BASE_CalculateBitmapOffset: |
| 196 |
PUSH DX |
| 197 |
MOV AX, DX ; AX = Y |
| 198 |
MOV BX, 320 |
| 199 |
MUL BX ; DX:AX = Y * 320 |
| 200 |
POP DX |
| 201 |
ADD AX, CX ; AX = offset |
| 202 |
RET |
| 203 |
|
| 204 |
; AX = Offset |
| 205 |
; CX = Count/Stride (MUST be 4 or greater, and a power of 2) |
| 206 |
; NOTE: Make sure the upper 16 bits of the 32-bit regs are clear and not important |
| 207 |
; But, 16-bit registers are preserved |
| 208 |
; Destroys DI |
| 209 |
; DS:SI = Input |
| 210 |
BASE_DrawImageLine: |
| 211 |
; Okay, so |
| 212 |
; First of all, MOVSx copies from DS:SI to ES:DI |
| 213 |
; So we need DI to be the offset with regards to the framebuffer |
| 214 |
; And that offset can be calculated via BASE_CalculateBitmapOffset |
| 215 |
MOV DI, AX |
| 216 |
SHL EBX, 16 ; Save BX in the upper 16b of EBX |
| 217 |
MOV BX, SI ; Then store the old SI in lower 16b of EBX |
| 218 |
; ES is used quite a bit throughout the program, so we save it |
| 219 |
SHL EDX, 16 ; Save DX in the upper 16b of EDX |
| 220 |
MOV DX, ES ; Then save ES in DX |
| 221 |
|
| 222 |
SHL EAX, 16 ; Store AX in the upper 16 bits of EAX |
| 223 |
; Then we set ES to the framebuffer segment address |
| 224 |
MOV AX, [CS:RAM_Framebuffer] ; Working framebuffer |
| 225 |
MOV ES, AX ; DO NOT Assume CS = DS |
| 226 |
|
| 227 |
; |
| 228 |
MOV AX, CX ; Store CX in the temporarily free AX |
| 229 |
|
| 230 |
; === Note note note === |
| 231 |
; Don't touch EAX/AX/AL/AH before after the MOV CX, AX instruction |
| 232 |
; Pretty much every procedure call messes with it, so just don't |
| 233 |
; === Note note note === |
| 234 |
|
| 235 |
; Since we're using MOVSD, we need to do / 4 |
| 236 |
SHR CX, 2 |
| 237 |
|
| 238 |
; And go ahead |
| 239 |
REP MOVSD ; DS:SI -> ES:DI |
| 240 |
|
| 241 |
MOV CX, AX ; Bring back CX and then |
| 242 |
SHR EAX, 16 ; Bring back the old AX |
| 243 |
|
| 244 |
MOV SI, BX ; Restore SI |
| 245 |
SHR EBX, 16 ; And restore BX |
| 246 |
|
| 247 |
MOV ES, DX ; Restore ES |
| 248 |
SHR EDX, 16 ; And restore DX |
| 249 |
RET |
| 250 |
|
| 251 |
; AL = Color |
| 252 |
; CX = X |
| 253 |
; DX = Y |
| 254 |
BASE_DrawPixel: |
| 255 |
|
| 256 |
PUSH AX |
| 257 |
MOV AX, [RAM_Framebuffer] ; Segment A000h (VGA framebuffer) |
| 258 |
MOV ES, AX |
| 259 |
|
| 260 |
|
| 261 |
; Coordinates |
| 262 |
; MOV CX, 100 ; X |
| 263 |
; MOV DX, 50 ; Y |
| 264 |
|
| 265 |
; Calculate offset: offset = Y * 320 + X |
| 266 |
|
| 267 |
;MOV AX, DX ; AX = Y |
| 268 |
;MOV BX, 320 |
| 269 |
;MUL BX ; DX:AX = Y * 320 |
| 270 |
;ADD AX, CX ; AX = offset |
| 271 |
|
| 272 |
CALL BASE_CalculateBitmapOffset |
| 273 |
|
| 274 |
MOV DI, AX |
| 275 |
POP AX |
| 276 |
; MOV AL, 0x4F ; Color index (some pinkish violet maybe?) |
| 277 |
STOSB ; Write AL to ES:DI, inc DI |
| 278 |
RET |
| 279 |
|
| 280 |
; Wait for the vertical blanking period, do nothing and return. |
| 281 |
; Use BASE_Present instead for rendering |
| 282 |
BASE_WaitForVerticalBlank: |
| 283 |
;PUSHA |
| 284 |
PUSH AX |
| 285 |
PUSH DX |
| 286 |
MOV DX, 0x3DA |
| 287 |
.v1: IN AL, DX |
| 288 |
TEST AL, 0x08 |
| 289 |
JZ .v1 ; Wait for VBLANK to begin |
| 290 |
.v2: IN AL, DX |
| 291 |
TEST AL, 0x08 |
| 292 |
JNZ .v2 ; Wait for VBLANK to end |
| 293 |
;POPA |
| 294 |
POP DX |
| 295 |
POP AX |
| 296 |
RET |
| 297 |
|
| 298 |
; Wait for the vertical retrace, fill the VGA framebuffer, wait for the vertical retrace to end, and return |
| 299 |
; No input |
| 300 |
BASE_Present: |
| 301 |
PUSH AX ; Save AX |
| 302 |
PUSH DX ; Then save DX |
| 303 |
MOV DX, 0x3DA |
| 304 |
.v1: IN AL, DX |
| 305 |
TEST AL, 0x08 |
| 306 |
JZ .v1 ; Wait for VBLANK to begin |
| 307 |
|
| 308 |
PUSH DS ; Importantly, store the old data segment because the code will crap itself if we don't bring it back |
| 309 |
MOV DS, [RAM_Framebuffer] ; Working buffer |
| 310 |
|
| 311 |
MOV AX, 0xA000 |
| 312 |
MOV ES, AX ; VGA Framebuffer |
| 313 |
|
| 314 |
MOV CX, 16000 ; 320 * 200 / 4 |
| 315 |
XOR SI, SI ; Clear source offset |
| 316 |
XOR DI, DI ; And clear destination offset |
| 317 |
REP MOVSD ; TIMES CX DS:SI -> ES:DI |
| 318 |
POP DS ; Restore the data segment |
| 319 |
|
| 320 |
MOV DX, 0x3DA |
| 321 |
.v2: IN AL, DX |
| 322 |
TEST AL, 0x08 |
| 323 |
JNZ .v2 ; Wait for VBLANK to end |
| 324 |
POP DX ; Restore old DX |
| 325 |
POP AX ; And restore old AX |
| 326 |
RET |
| 327 |
|
| 328 |
; ES = Game file segment |
| 329 |
; Exit on fail |
| 330 |
BASE_VerifyGameFileMagic: |
| 331 |
; Magic is DGF and then 0xCF |
| 332 |
MOV AL, 'D' |
| 333 |
MOV AH, [ES:0] |
| 334 |
CMP AL, AH |
| 335 |
JNE .badmagic |
| 336 |
MOV AL, 'G' |
| 337 |
MOV AH, [ES:1] |
| 338 |
CMP AL, AH |
| 339 |
JNE .badmagic |
| 340 |
MOV AL, 'F' |
| 341 |
MOV AH, [ES:2] |
| 342 |
CMP AL, AH |
| 343 |
JNE .badmagic |
| 344 |
MOV AL, 0xCF |
| 345 |
MOV AH, [ES:3] |
| 346 |
CMP AL, AH |
| 347 |
JNE .badmagic |
| 348 |
JMP .goodmagic |
| 349 |
.badmagic: |
| 350 |
MOV DX, STR_ErrInvalidMagic |
| 351 |
CALL DOS_PrintString |
| 352 |
JMP Exit |
| 353 |
.goodmagic: |
| 354 |
RET |
| 355 |
|
| 356 |
; AL = 0 = 16K 1 = 32K 2 = 64L |
| 357 |
; RET: AX = Segment or Early exit |
| 358 |
BASE_AllocFileMem: |
| 359 |
; Not using a lookup table because it'd be overkill |
| 360 |
CMP AL, 0 |
| 361 |
JE .a16k |
| 362 |
CMP AL, 1 |
| 363 |
JE .a32k |
| 364 |
CMP AL, 2 |
| 365 |
JE .a64k |
| 366 |
MOV DX, STR_ErrInvalidAllocation |
| 367 |
CALL DOS_PrintString |
| 368 |
JMP Exit |
| 369 |
.a16k: |
| 370 |
MOV BX, 1024 |
| 371 |
JMP .alnst |
| 372 |
.a32k: |
| 373 |
MOV BX, 2048 |
| 374 |
JMP .alnst |
| 375 |
.a64k: |
| 376 |
MOV BX, 4096 |
| 377 |
.alnst: |
| 378 |
CALL DOS_AllocateMemory |
| 379 |
JC ERROR_GeneralDOSError |
| 380 |
RET |