Decoded: Rogue (1980) by Toy, Arnold, Wichman DOS version (1983) by Mel Sibony and Jon Lane Source file: DOS.ASM Beginner friendly, line-by-line code walkthrough by MaiZure DOS.ASM connects Rogue to the hardware via the operating system. These procedures are the 'drivers' that rely on software interrupts and MMIO ports to read and write data. Original code: https://britzl.github.io/roguearchive/ Original code with line numbers http://www.maizure.org/projects/decoded-rogue/DOS_linenum.txt 1 COMMENT (recommended tab stop of 8) 2 COMMENT 3 COMMENT 4 COMMENT 5 BLANK 6 Start of the data segment, paragraph aligned. 7 Export tick variable to C source 8 Store two bytes for the tick value 9 Import the hit multiplier variable from EXTERN.C 10 Import the copy protection check from EXTERN.C 11 Import the player name from EXTERN.C 12 Import the monster's name from EXTERN.C 13 Import the other player name variable from EXTERN.C 14 Import the print buffer pointer from INIT.C 15 Import the no_step variable from PROTECT.C 16 Import base pointer for code segment from Aztec C (not part of Rogue) 17 Import end pointer for code segment from Aztec C (not part of Rogue) 18 End of data segment 19 BLANK 20 Start of code segment, paragraph aligned. 21 Export DMA I/O, Break, beep, output, peek and poke procedures 22 Export character, memory, and clock procedures 23 Export segment, halt, and code check procedures 24 Export the quit procedure 25 Assume the usual for DS and CS. Other segments defined as needed 26 BLANK 27 COMMENT 28 COMMENT 29 COMMENT 30 COMMENT 31 COMMENT 32 COMMENT 33 BLANK 34 COMMENT 35 COMMENT 36 COMMENT 37 COMMENT 38 COMMENT 39 COMMENT 40 COMMENT 41 COMMENT 42 COMMENT 43 COMMENT 44 COMMENT 45 COMMENT 46 BLANK OUTPUT TO MEMORY / PORT 47 Defines dmaout procedure to output bytes to an MMIO port 48 Save the current stack frame 49 Create a new stack frame 50 Store the destination index register 51 Store the source index register 52 Store the previous flags result 53 BLANK 54 Clear direction flag so that upcoming string ops read forward from SI/DI 55 Get the target segment in argument 3 for output in to AX 56 Store the original ES in BX for now 57 Move the target segment in to ES 58 Get the target offset from argument 4 in to DI 59 Get the source offset from argument 1 in to SI 60 Get the length counter from argument 2 in to CX 61 Repeatedly store words from source to destination until CX is 0 62 BLANK 63 Move the 0 that should be in CL to AH 64 Restore the original extra segment 65 BLANK 66 Restore the flags register 67 Restore the previous source index register 68 Restore the previous destination index register 69 Restore previous stack frame 70 Return to caller 71 End of dmaout procedure 72 BLANK INPUT FROM MEMORY / PORT 73 Defines dmain procedure to read in bytes from an MMIO port 74 Save the current stack frame 75 Create a new stack frame 76 Store the destination index register 77 Store the source index register 78 Store the previous flags register 79 BLANK 80 Move the current data segment to AX (we can't directly move to ES) 81 Save the previous extra segment 82 Complete the move from DS to ES via AX 83 BLANK 84 Clear the direction flag to read/write forward from the index registers 85 Get the source segment to read from argument 3 86 Move that source to the data segment via AX 87 Move the companion source offset from argument 4 in to SI 88 Move the start variable offset from argument 1 in to DI 89 Move the size to read from argument 2 in to the counter register 90 Repeatedly read and store words until CX is 0 91 BLANK 92 Swap ES 93 and DS 94 Back to their original places 95 BLANK 96 97 BLANK 98 Restore the previous flags register 99 Restore the previous source index 100 Restore the previous destination index 101 Restore previous stack frame 102 Return to caller 103 End dmain procedure 104 BLANK STORE VALUES IN MEMORY 105 Define wsetmem to push a value to a memory location (pre-memset memset) 106 Save the current stack frame 107 Create a new stack frame 108 Store the destination index 109 Store the source index 110 Store the previosu flags results 111 BLANK 112 Store the previous data segment because... 113 We'll pop it right in to the extra segment to use as the destination 114 BLANK 115 Move the target offset in to DI 116 Move the times to set in to CX 117 Move the value to set in to AX 118 Clear direction flag to move forward from SI->DI 119 Repeat until CX expires or the value transferred is null 120 BLANK 121 Restore the previous flags register 122 Restore the previous source index 123 Restore the previous destination index 124 Restore previous stack frame 125 Return to caller 126 End wsetmem procedure 127 BLANK *BEEP* 128 Defines beep to do just that 129 Save the current stack frame 130 Create a new stack frame 131 BLANK 132 Move 300 in to BX - this is how many cycles we're going to sound off 133 Read in the current state of the speaker (keyboard controller) 134 Save the state on the stack 135 Prepare the speaker bit (bit 1) to off 136 Send it to the speaker to turn it off 137 Set up a 50 tick counter in CX 138 Loop that counter to 0 to wait for speaker to move to off. This is takes approximately 50 / 5000000. 139 Now set the speaker to in (on) 140 Push that to the speaker 141 Set up delay for another 50 cycles 142 Now wait another 50 cycles. This really is a manual speaker pulse 143 Decrement the cycle counter in BX 144 If we still have more cycles to burn, jump back to line 135 145 Restore the original PC speaker port value in to AX 146 Push that back to the keyboard controller. Speaker now in old state 147 BLANK 148 Restore previous stack frame 149 Return to caller 150 End of beep procedure 151 BLANK 152 COMMENT 153 COMMENT 154 COMMENT 155 COMMENT 156 BLANK NEW CTRL-BREAK HANDLER 157 Define ctrlbreak to replace the default DOS handler 158 Store the value in AX 159 Quit the game! 160 Restore the value in AX 161 This is an interrupt, so iret 162 BLANK CLOCK HANDLER 163 Define clock...seems only to mess with people trying to hack the game 164 Disable interrupts 165 Store the old data segment 166 Store the old accumulator 167 COMMENT 168 Clear the accumulator 169 Move data segment to base memory page 170 Offset value at byte 4 in to AX (This is interrupt #1 - Trap/Step) 171 See if 0 was returned... 172 If so..fall through and die. OR jump to line 174 173 End this program 174 Try another trick... 175 Move a partial offset in to the IDT (between Int 1 and 2) 176 See if we have a zero... 177 If so...die, otherwise jump to line 179 178 End this program 179 We've passed two traps 180 COMMENT 181 Get the data segment... 182 And store it in its rightful place 183 Increment the tick counter 184 BLANK 185 COMMENT 186 Match the return values of no_step from PROTECT.C... 187 If they match, we're almost done this proc. Jump to line 192 188 Push the function pointer forward?? Nice... 189 Check that value to see if it's less than 20... 190 If so...jump to line 192, otherwise.. 191 Die! 192 Final check for copy protection 193 COMMENT 194 See if the hit multiplier is a nice and friendly 1x 195 If so, skip the copy protection down to line 203 196 Otherwise, check the copy protection result.. 197 If it DOESNT match? Drop down and set the bad boy names 198 Get a blank space... 199 Store it in the killer's string 200 Get the player's name.. 201 Store it in the global name (replacing the funny copy protection name) 202 Set the hit multiplier to 1x 203 Label to skip copy protection 204 Restore previous accumulator 205 Restore previous data segment 206 Return from interrupt 207 End clock procedure 208 BLANK 209 BLANK SET NEW HANDLER FOR CTRL-BREAK 210 Define COFF to set up the new interrupt handler for ctrl-break 211 Get the offset of the handler in to DX 212 Save the old data segment 213 ...twice... 214 Save the code sgment 215 Move the code segment in to the data segment (handler is now data) 216 Prep to change interrupt vector. AH = 0x25, using number 0x23 217 BOOM goes the DOS interrupt - new ctrl-break handler is now in place 218 Restore the old data segment 219 Put the second copy in to the extra segment 220 Return 221 End of COFF procedure 222 BLANK GET CHARACTER INPUT FROM KEYBOARD 223 Define getch to get a character from the keyboard 224 Set up to read keyboard buffer directly (AH=0) 225 Invoke the interrupt 226 Check to see if any value came back 227 If result is null, CMP al,0 means ZF=1. Real input means jump to 230 228 Otherwise, it's empty. Clear out the scan code 229 Assert bit 7 in the result 230 Clean the scan code... 231 Clear AH 232 Return to caller 233 End getch 234 BLANK CHECK FOR CHARACTER 235 Define no_char to check if keyboard input is waiting 236 Set AH to 1 237 Invoke keyboard interrupt, ZF = 1 if keyboard is empty 238 Clear out the result 239 If there is something waiting....Jump to 241 240 Nothing is waiting...increment ax so that NO CHAR is true 241 Label for skipping 242 Return to caller 243 End of no char procedure 244 BLANK OUTPUT TO MEMORY/PORT 245 Defines out to write value to memory/port 246 Get the current stack pointer (Args below the stack) 247 Get the port number in to DX 248 Get the byte to write in to AL 249 Output AL to DX 250 Return to caller 251 End of out procedure 252 BLANK CHANGE A BYTE OF MEMORY (FAR) 253 Defines pokeb to set a far memory position to specific byte 254 Get the stack pointer 255 Store the data segment 256 Get the value to write in to al 257 Get the memory address in to bx using ds (DS:BX) 258 Move the value in al in to bx 259 Restore the original data segment 260 Return to caller 261 End of pokeb procedure 262 BLANK READ A BYTE FROM MEMORY (FAR) 263 Defines peekb to read a byte from far memory 264 Get the stack pointer 265 Store the data segment 266 Get the memory value in both DS and BX 267 Move the memory value in to AL from DS:BX 268 Clear AH 269 Restore original data segment 270 Return value in AL 271 End of peek procedure 272 BLANK FIND THE DATA SEGMENT 273 Defines getds to find the current data segment 274 Store the data segment 275 Pop it in to AX 276 Return to caller 277 End of getds procedure 278 BLANK HALT AND CATCH FIRE 279 Define halt to sink this ship 280 Reverse memory read/write direction 281 Disable interrupts 282 Move the stack segment... 283 ...in to DS via AX 284 Move the stack pointer in to the destination index 285 Clear the accumulator 286 Prepare 10 writes... 287 Label for repeat 288 Start writing 0 backwards to smash some stack good 289 Count off to zero... 290 Repeat... 291 BLANK 292 May as well stop now 293 End of halt procedure 294 BLANK FIND CODE SEGMENT 295 Define csum to find a specific point in the code segment (checksum!) 296 Move the beginning of the code segment addres in to AX 297 Move the end of the code segment in to BX 298 Set CX to 0...no loops 299 Store the previous source index 300 Store the previous data segment 301 Store the previous code segment 302 Pop the code segment in to the data segment 303 Move 0x200 bytes in to the code segment 304 Move this new position in to the source index 305 Move forward in memory 306 More code segment label 307 Load whatever byte happens to be at this position 308 Store this offset in to CX 309 Compare this position with the end of the code segment. 310 If there's more code segment...jump back to line 306 and read next byte 311 Otherwise, move our total offset in to AX 312 Restore the old data segment 313 Restore the old source index 314 Return the offset used as a checksum -- tada 315 End of csum procedure 316 BLANK 317 End of code segment 318 End of program 319 EOF˙