├── .gitignore ├── A-Hdrive ├── B-Hdrive ├── LICENSE.txt ├── Makefile ├── README.md ├── TERM.md ├── TODO ├── bdos.c ├── bios.c ├── bye.mac ├── cpm.c ├── cpmdisc.h ├── cpmtool.c ├── cpmws.png ├── defs.h ├── disassem.c ├── getunix.mac ├── main.c ├── putunix.mac ├── tests ├── adv.com ├── advi.dat ├── advi.ptr ├── advt.dat ├── advt.ptr ├── decode.dat ├── ed.com ├── mbasic.com ├── winstall.com ├── ws.com ├── ws.ins ├── wsmsgs.ovr ├── wsovly1.ovr └── wsu.com ├── vt.c ├── vt.h └── z80.c /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.o 2 | cpm 3 | cpmtool 4 | nbproject/* 5 | -------------------------------------------------------------------------------- /A-Hdrive: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/A-Hdrive -------------------------------------------------------------------------------- /B-Hdrive: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/B-Hdrive -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Licenses for included code. These need to be checked if you are worried 2 | about licensing. 3 | 4 | Parag Patel's MIT-like 5 | 6 | Parts from cpm-0.2.1-mod2 GPL 7 | 8 | ZCPR2 and P2DOS... who knows? 9 | 10 | Here is the license for my (Joe Allen's) contributions to this CP/M 11 | emulator. 12 | 13 | Copyright 2016 Joseph Allen 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to 17 | deal in the Software without restriction, including without limitation the 18 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 19 | sell copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 30 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 31 | IN THE SOFTWARE. 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # optional CFLAGS include: -O -g -Wall 2 | # -DNO_LARGE_SWITCH compiler cannot handle really big switch statements 3 | # so break them into smaller pieces 4 | # -DENDIAN_LITTLE machine's byte-sex is like x86 instead of 68k 5 | # -DPOSIX_TTY use Posix termios instead of older termio (FreeBSD) 6 | # -DMEM_BREAK support memory-mapped I/O and breakpoints, 7 | # which will noticably slow down emulation 8 | 9 | ifeq ($(OS),Windows_NT) 10 | EXE := .exe 11 | else 12 | EXE := 13 | endif 14 | 15 | CC = gcc 16 | CFLAGS = -g -pipe -Wall -Wextra -pedantic -ansi \ 17 | -D_POSIX_C_SOURCE=200809L -DPOSIX_TTY \ 18 | -DENDIAN_LITTLE -DMEM_BREAK 19 | LDFLAGS = 20 | 21 | FILES = README.md Makefile A-Hdrive B-Hdrive cpmws.png \ 22 | bdos.c bios.c cpm.c cpmdisc.h defs.h disassem.c main.c vt.c vt.h z80.c \ 23 | bye.mac getunix.mac putunix.mac cpmtool.c 24 | 25 | OBJS = bios.o \ 26 | disassem.o \ 27 | main.o \ 28 | vt.o \ 29 | bdos.o \ 30 | z80.o 31 | 32 | all: cpm$(EXE) cpmtool$(EXE) 33 | 34 | cpmtool$(EXE): cpmtool.o 35 | $(CC) $(CFLAGS) $(LDFLAGS) -o cpmtool$(EXE) cpmtool.o 36 | 37 | cpm$(EXE): $(OBJS) 38 | $(CC) $(CFLAGS) $(LDFLAGS) -o cpm$(EXE) $(OBJS) 39 | 40 | 41 | bios.o: bios.c defs.h cpmdisc.h cpm.c 42 | z80.o: z80.c defs.h 43 | disassem.o: disassem.c defs.h 44 | main.o: main.c defs.h 45 | 46 | clean: 47 | rm -f cpm$(EXE) cpmtool$(EXE) *.o *~ 48 | 49 | tags: $(FILES) 50 | cxxtags *.[hc] 51 | 52 | tar: 53 | tar -zcf cpm.tgz $(FILES) 54 | 55 | files: 56 | @echo $(FILES) 57 | 58 | difflist: 59 | @for f in $(FILES); do rcsdiff -q $$f >/dev/null || echo $$f; done 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ANSI CP/M Emulator and disk image tool 3 | 4 | This emulator allows you to execute CP/M commands on UNIX (Linux or Cygwin). 5 | 6 | If a CP/M command is provided on the command line, it is executed 7 | immediately. For example, this will start WordStar: 8 | 9 | cpm ws 10 | 11 | Otherwise, you will get the CP/M command prompt: 12 | 13 | cpm 14 | A> 15 | 16 | By default, BDOS is emulated so that the current directory in UNIX appears 17 | as the __A>__ drive from CP/M. The BDOS emulation can be optionally 18 | disabled. In this case, P2DOS (a BDOS clone) is used to access real disk 19 | images. 20 | 21 | This is a modified version of Parag Patel's [CP/M](https://en.wikipedia.org/wiki/CP/M) emulator. This 22 | version includes a filter/emulator for an [ADM-3A](https://en.wikipedia.org/wiki/ADM-3A) terminal as 23 | emulated by a [Kaypro 2x](http://www.oldcomputers.net/kayproii.html). Basically it converts ADM-3A codes 24 | into ANSI codes on output and converts arrow keys and PgUp / PgDn into 25 | WordStar motion keys on input. 26 | 27 | This allows screen oriented programs written for Kaypro-II such as WordStar, 28 | Turbo PASCAL and dBASE-II to operate directly with ANSI terminal emulators, 29 | such as Xterm or RXVT for Linux and Cygwin. 30 | 31 | The output side of the VT52/ADM-3A and the BDOS emulation comes from 32 | Benjamin C. Sittler bsittler@iname.com from another emulator: 33 | cpm-0.2.1-mod2. CPM-0.2.1 is i686 only- it's partially written in assembly 34 | language. Hence I prefer Parag's all C emulator. 35 | 36 | Type 'make' to build the program. 37 | 38 | Type './cpm __command__' to execute a CP/M .COM file located in the current 39 | directory. 40 | 41 | Type './cpm' to get the __A>__ prompt. Type __bye__ to exit back to UNIX. 42 | 43 | Type './cpm --nobdos' to start it without BDOS emulation and instead use 44 | disk images called A-Hdrive and B-Hdrive. In this case: 45 | 46 | At the __A>__ prompt: 47 | 48 | Type __bye__ to exit back to Linux 49 | 50 | Type __putunix cpm-file unixfile__ to copy a file out of CP/M and to UNIX/Linux. 51 | 52 | Type __getunix unixfile cpm-file__ to copy a file from UNIX/Linux into CP/M. 53 | 54 | The A-Hdrive and B-Hdrive disk images include some programs. Many more are 55 | available from here. 56 | 57 | [http://www.retroarchive/org/cpm/](http://www.retroarchive.org/cpm/) 58 | 59 | Here is CP/M WordStar running on Ubuntu in an xterm: 60 | 61 | ![CP/M WordStar](cpmws.png) 62 | 63 | ### Command mode 64 | 65 | You may hit Ctrl-_ (Ctrl-underscore- usually you have hold Shift and Ctrl at 66 | the same time to do this), or any of the F1 - F4 keys to interrupt the 67 | emulation and bring up the Z-80 command prompt. From this prompt a number 68 | of commands are available: 69 | 70 | Cmd: ? 71 | Q(uit) T(race on/off) S(tep trace) D(ump regs) 72 | E(xamine memory) P(oke memory) R(egister modify) 73 | L(oad binary) C(ontinue running - if Step) 74 | G(o) B(oot CP/M) Z(80 disassembled dump) 75 | W(write memory to file) X,Y(-set/clear breakpoint) 76 | O(output to "logfile") 77 | 78 | !(fork shell) ?(command list) V(ersion) 79 | 80 | 81 | "Q"uit is the most useful: you can forcibly quit the emulator. If you want to 82 | continue the emulation use the "C"ontinue command. 83 | 84 | -- Joe Allen 85 | 86 | ## cpmtool 87 | 88 | This is a tool which allows you to manipulate CP/M disk images. It 89 | currently supports two image formats and automatically detects the correct 90 | format: 91 | 92 | * 8 inch CP/M 1.4 floppy images of exactly 256,256 bytes 93 | * 128 byte sectors / records 94 | * 77 tracks 95 | * 26 sectors per track 96 | * Two reserved tracks at the beginning 97 | * 243 1K blocks 98 | * 2 blocks for the directory 99 | * 6 unused sectors at the end 100 | * Sector interleave: 1,7,13,19,25,5,11,17,23,3,9,15,21,2,8,14,20,26,6,12,18,24,4,10,16,22 101 | 102 | * 5 MB ST-506 hard disk image 103 | * 128 byte sectors 104 | * 64 sectors per track 105 | * Two reserved tracks at the beginning 106 | * 2442 2K blocks 107 | * 16 blocks for the directory 108 | * No sector interleave 109 | 110 | Syntax: cpmtool path-to-disk-image command args 111 | 112 | Commands: 113 | 114 | ls [-la1A] Directory listing 115 | -l for long 116 | -a to show system files 117 | -1 to show a single name per line 118 | 119 | cat cpm-name Type file to console 120 | 121 | get cpm-name [local-name] Copy file from diskette to local-name 122 | 123 | put local-name [cpm-name] Copy file to diskette to cpm-name 124 | 125 | free Print amount of free space 126 | 127 | rm cpm-name Delete a file 128 | 129 | # Original README 130 | 131 | This is a Z80 instruction-set simulator written entirely ANSI C. It can 132 | boot and run CP/M or CP/M clones. It's also got a builtin debugger 133 | which supports tracing, single-stepping, breakpoints, read or write 134 | protected memory, memory-mapped I/O, logging, and disassembly. This is 135 | not the fastest emulator around, but it's very portable. 136 | 137 | So far z80 has been tested under Linux, FreeBSD, DEC's UNIX on Alpha, 138 | and SunOS, the Macintosh using either Think C or CodeWarrior (PPC and 139 | 68k), MS-DOS using DJGPP, and the BeOS. It should be quite easy to port 140 | to other UNIX-line systems. 141 | 142 | The file "Makefile" will need tweaking for your system. The two key 143 | compilation flags are -DLITTLE_ENDIAN and -DPOSIX_TTY as described 144 | further in the Makefile. You'll almost certainly have to tweak the 145 | other make variables, such as CFLAGS and CC. Then just type "make". 146 | 147 | For the Macintosh, the file "MacProj.hqx" is a BinHex SIT archive with 148 | three project files for Think C, CodeWarrior 68k, and CW PowerPC. 149 | 150 | The file "main.c" contains UNIX routines for character-mode I/O, if 151 | these need to be ported to your flavor of UNIX. They're pretty generic 152 | and ought to work under just about anything. 153 | 154 | Once z80 is built, run it, and enter 'b' at the prompt to boot CP/M. 155 | Enter '?' to see a list of debugging commands it understands. The 156 | interrupt character, ^C, is wanted by CP/M for its own uses, so to force 157 | the z80 into its debugger, use ^_ (control-underscore). (You may have 158 | to type after the ^_ to wake it up.) 159 | 160 | If z80 is linked or renamed to "cpm", it will directly boot into CP/M 161 | without displaying the debugger prompt. If an "A-Hdrive" doesn't exist 162 | in the current working directory for CP/M, it will be created. 163 | 164 | The file "z80.c" contains the emulator proper. Most variables are 165 | accessed via macros defined in "defs.h". The file "bios.c" contains the 166 | mock-BIOS code that allows the emulator to boot and run CP/M or its 167 | clones. The file "cpm.c" contains an image of P2D0S 2.3 and ZCPR1, so 168 | it should be safe to distribute freely. (This allows the z80 to 169 | directly boot CP/M.) 170 | 171 | The z80 program will automatically create virtual CP/M "drives" as they 172 | are accessed, and will only allocate disk space for them when it is 173 | needed. The obsolete "makedisc.c" program is included to allocate all 174 | the space for a floppy at once and also allows placing a single Unix 175 | file within it. 176 | 177 | The "bios.c" code is built for two 5Mb virtual hard-drives (A-Hdrive, 178 | and B-Hdrive), and a bunch of floppy drives (C-drive, D-drive, and so 179 | on). It is possible to rebuild it for different numbers of hard-drives 180 | by changing the macro NUMHDISCS at the top. The hard disks emulated are 181 | the venerable ST-506 5Mb 5" 5Mb drives. The floppies are the 182 | traditional 8" 256k drives. The code names the virtual hard-drives as 183 | "?-Hdrive" and floppies as "?-drive" to help avoid accidentlally 184 | confusing one for the other. Their contents should be identical to the 185 | real thing, even down to the reserved tracks, assuming I did it right. 186 | 187 | There are several "*.mac" files which are Z80 macro-assembly sources for 188 | getting files from UNIX into CP/M (GETUNIX.MAC), putting files out from 189 | CP/M to UNIX (PUTUNIX.MAC), and quitting the emulator (BYE.MAC). All 190 | these files are also in "A-Hdrive", all assembled and ready to run. 191 | They do little useful error checking, but it should be pretty obvious 192 | from the code how they work. Any text files transfered into CP/M must 193 | have CR-LF as the line separator or it gets awfully confused. 194 | 195 | Usage: 196 | PUTUNIX 197 | GETUNIX 198 | BYE 199 | 200 | Additional utilities that come with the P2DOS23 distribution are also 201 | included to support the date/time functions of P2DOS. These are located 202 | within the "A-Hdrive" and within the P2DOS archive. These are date.com, 203 | ddir.com, initdir.com, public.com, set.com, and touch.com. 204 | 205 | I had picked up a Z80 assembler named "zmac" from the Internet quite 206 | some time ago. I've hacked it heavily to add a lot of support for 207 | different assembler formats but it's still kind of finicky. You'll need 208 | yacc of some flavor to compile it up. Sorry, but I don't have a 209 | Makefile for it any longer. The "*.z" files there are sample assembly 210 | sources. There's also a separate Z80 disassembler that came with zmac. 211 | Unfortunately the sources did not come with any copyright, but the file 212 | "zmac.y" does contain a somewhat cryptic list of folks who hacked it. I 213 | don't know if "zdis" is by the same authors or not - there are no 214 | comments in it. 215 | 216 | zmac assembled P2DOS 2.3 (off of the CP/M archives on oak.oakland.edu) 217 | without too much trouble. ZCPR1 (from the archives) needed to be 218 | converted from the .ASM format to .MAC, which I did using XLATE5 (also 219 | from the Oak archives). Then the resulting output had to be hacked 220 | somewhat so that zmac could assemble it. Z80DOS23 and Z80CCP from the 221 | archives also assembled up without much trouble. 222 | 223 | To play with different BDOS/CCP replacements, just rename the assembler 224 | output HEX file from wherever you built it into the files "bdos.hex" and 225 | "ccp.hex". If z80 sees these in the current directory, it will load 226 | them to boot CP/M instead of using its builtin versions of P2DOS/ZCPR1. 227 | 228 | There's a lot of stuff to do in this code, but it's functional enough to 229 | leave the rest as an exercise for the student. The Mac version needs a 230 | really cool interface, with separate windows for the out, setting 231 | registers, tracing, a virtual terminal, etc. The debug-level prompting 232 | code pretty much sucks and needs to be thrown out. 233 | 234 | This code has been designed so that the z80 emulator proper should be 235 | thread-safe and runs independently of CP/M. It should be possible to 236 | run multiple z80s within one program if desired. 237 | 238 | The CP/M layer may be built so that the fake BIOS is turned into real 239 | Z80 code with all I/O occurring through fake devices using either 240 | INPUT/OUTPUT or the memory-mapped I/O hooks. Conversely, it's also 241 | possible to turn then entire BDOS/CCP into C code much as the BIOS is 242 | currently coded with magic hooks that trigger the appropriate actions. 243 | 244 | Use this code as you wish, so long as it is properly attributed. That 245 | is, display our copyrights where you would display yours (manuals, 246 | boot-up, etc) and do not claim that this code is yours. If you do use 247 | it, please drop me a note. This code is "as-is" without any warrantee. 248 | Be warned that P2DOS23 and ZCPR1 only allow free redistribution for 249 | non-commercial use. (See their documentation for more details.) 250 | 251 | Copyright 1986-1988 by Parag Patel. All Rights Reserved. 252 | Copyright 1994-1995 by CodeGen, Inc. All Rights Reserved. 253 | 254 | 255 | -- Parag Patel 256 | 257 | 258 | Version history: 259 | 260 | 3.1 Added support for virtual 5Mb ST-506 drives in bios.c. 261 | Fixed a few bugs in emulator, fake BIOS, and console I/O. 262 | String search for "cpm" in argv[0] now uses strrchr() in main.c. 263 | Change all "(char)" casts to "(signed char)" for AIX and other 264 | systems that have "char" default to "unsigned". 265 | Making a start at adding support for printing. 266 | 267 | 3.0 First public release 1995. 268 | Use publicly available P2DOS23 and ZCPR1 instead of 269 | copyrighted CP/M 2.2. 270 | Added date/time support for P2DOS and its utilities. 271 | 272 | ## Readme from cpm-0.2.1-mod2 273 | 274 | The Kaypro 2x supports several extensions to the ADM 3A terminal 275 | escapes, and I added translations to vt52() for some of these 276 | extensions. vt52() should probably be replaced by a proper curses 277 | applications so we don't depend on having an ANSI terminal, but I'm 278 | lazy... 279 | 280 | Here are the added escape sequences: 281 | 282 | ESC E (insert line) 283 | ESC R (delete line) 284 | ESC B 0 (start reverse video) 285 | ESC C 0 (stop reverse video) 286 | ESC B 1 (start half intensity) 287 | ESC C 1 (stop half intensity) 288 | ESC B 2 (start blinking) 289 | ESC C 2 (stop blinking) 290 | ESC B 3 (start underlining) 291 | ESC C 3 (stop underlining) 292 | ESC B 4 (cursor on) 293 | ESC C 4 (cursor off) 294 | ESC B 6 (remember cursor position) 295 | ESC C 6 (restore cursor position) 296 | 297 | The following sequences are recognized by vt52() but don't cause any 298 | ANSI output: 299 | 300 | ESC B 5 (enter video mode) 301 | ESC C 5 (leave video mode) 302 | ESC B 7 (preserve status line) 303 | ESC C 7 (don't preserve status line) 304 | ESC * row+32 col+32 (set pixel) 305 | ESC SPC row+32 col+32 (clear pixel) 306 | ESC L row+32 col+32 row+32 col+32 (set line) 307 | ESC D row+32 col+32 row+32 col+32 (delete line) 308 | 309 | I also added a VBELL #define in io.c which causes those annoying BELs 310 | to be replaced by temporary flashing, providing your terminal supports 311 | the ESC [ ? 5 h and ESC ? 5 l sequences (invert and revert screen.) 312 | Both XFree86 3.3.1 xterm and the Linux console support them, so I'm 313 | happy. 314 | 315 | -- "Benjamin C. W. Sittler" 316 | -------------------------------------------------------------------------------- /TERM.md: -------------------------------------------------------------------------------- 1 | # Terminal emulation 2 | 3 | Kaypro was originally designed to imitate most of the control sequences of a 4 | Lear-Siegler ADM-3A terminal. 5 | 6 | Kaypro 2, 4 or 10: 7 | 8 | Code | Function 9 | -----|--------- 10 | 07| ring bell 11 | 08| cursor left 12 | 0C| cursor right 13 | 0A| cursor down 14 | 0B| cursor up 15 | 17| erase to end of screen 16 | 18| erase to end of line 17 | 1A| clear screen / home cursor 18 | 1E| home cursor 19 | ESC R| insert line 20 | ESC E| delete line 21 | ESC = (row+32) (col+32)| goto 22 | 23 | Kaypro 2/84, 2x, 4/84, 4x, 10 and robie also have these: 24 | 25 | Code | Functions 26 | -----|---------- 27 | ESC B 0 | inverse start 28 | ESC C 0 | inverse stop 29 | ESC B 1 | dim start 30 | ESC C 1 | dim stop 31 | ESC B 2 | blinking start 32 | ESC C 2 | blinking stop 33 | ESC B 3 | underline start 34 | ESC C 3 | underline stop 35 | ESC B 4 | cursor on 36 | ESC C 4 | cursor off 37 | ESC B 5 | video mode on 38 | ESC C 5 | video mode off 39 | ESC B 6 | save cursor position 40 | ESC C 6 | restore cursor position 41 | ESC B 7 | status line preservation on 42 | ESC C 7 | status line preservation off 43 | ESC * V H | set pixel 44 | ESC SPACE V H | clear pixel 45 | ESC L V1 H1 V2 H2 | set line 46 | ESC D V1 H1 V2 H2 | delete line 47 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | missing bdos functions: 2 | 3 get reader input 3 | 4 write punch output 4 | 5 write listing output 5 | 7 get i/o byte 6 | 8 set i/o byte 7 | 27 get alloc address 8 | 28 write protect disk 9 | 30 set file attributes 10 | 11 | something wrong when winstall is run (but it works with BDOS) 12 | 13 | provide simple key to exit simulator? 14 | 15 | mount directories as drives in bdos emulator: ./cpm --A /foo --B /bar ... 16 | 17 | take yaze z80 simulator: it's probably more accurate (anyway it passes a 18 | CPU tester). 19 | 20 | improve putunix / getunix so that you can supply just a single file name 21 | 22 | include a cpm disk manager utility: find one (maybe yaze) 23 | 24 | - - - 25 | 26 | something wrong when 'e' is given to ed.com (but it works with BDOS) 27 | [fixed- only random access commands should write FCB bytes R0 - R2] 28 | 29 | adventure doesn't work- it's seeking by modifying FCB directly 30 | [fixed- now we keep file position in the FCB] 31 | 32 | restore terminal on bdos errors 33 | [fixed] 34 | 35 | allow ESC alone to work 36 | [fixed] 37 | 38 | allow ^Z to work 39 | [fixed] 40 | -------------------------------------------------------------------------------- /bdos.c: -------------------------------------------------------------------------------- 1 | /* BDOS emulation */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "defs.h" 12 | #include "vt.h" 13 | 14 | #define BIOS 0xFE00 15 | #define DPH0 (BIOS + 0x0036) 16 | #define DPB0 (DPH0 + 0x0010) 17 | #define DIRBUF 0xff80 18 | #define CPMLIBDIR "./" 19 | static int storedfps = 0; 20 | unsigned short usercode = 0x00; 21 | int restricted_mode = 0; 22 | int silent_exit = 0; 23 | char *stuff_cmd = 0; 24 | int exec = 0; 25 | int trace_bdos = 0; 26 | 27 | /* Kill CP/M command line prompt */ 28 | 29 | static void killprompt() 30 | { 31 | vt52('\b'); 32 | vt52(' '); 33 | vt52('\b'); 34 | vt52('\b'); 35 | vt52(' '); 36 | vt52('\b'); 37 | } 38 | 39 | char *rdcmdline(z80info *z80, int max, int ctrl_c_enable) 40 | { 41 | int i, c; 42 | static char s[259]; 43 | 44 | fflush(stdout); 45 | max &= 0xff; 46 | i = 1; /* number of next character */ 47 | 48 | if (stuff_cmd) { 49 | killprompt(); 50 | strcpy(s + i, stuff_cmd); 51 | /* printf("'%s'\n", stuff_cmd); */ 52 | i = 1 + strlen(s + i); 53 | stuff_cmd = 0; 54 | silent_exit = 1; 55 | goto hit_rtn; 56 | } else if (exec) { 57 | killprompt(); 58 | printf("\r\n"); 59 | finish(z80); 60 | return s; 61 | } 62 | 63 | loop: 64 | c = kget(0); 65 | if (c == INTR_CHAR) { 66 | command(z80); 67 | i = 1; 68 | s[0] = i - 1; 69 | s[i] = 0; 70 | return s; 71 | } else if (c < ' ' || c == 0x7f) { 72 | switch (c) { 73 | case 3: 74 | if (ctrl_c_enable) { 75 | vt52('^'); 76 | vt52('C'); 77 | z80->regpc = BIOS+3; 78 | s[0] = 0; 79 | return s; 80 | } 81 | break; 82 | case 8: 83 | case 0x7f: 84 | if (i > 1) { 85 | --i; 86 | vt52('\b'); 87 | vt52(' '); 88 | vt52('\b'); 89 | fflush(stdout); 90 | } 91 | break; 92 | case '\n': 93 | case '\r': 94 | hit_rtn: 95 | s[0] = i-1; 96 | s[i] = 0; 97 | if (!strcmp(s + 1, "bye")) { 98 | printf("\r\n"); 99 | finish(z80); 100 | } 101 | if (i <= max) 102 | s[i] = '\r'; 103 | return s; 104 | } 105 | goto loop; 106 | } else if (i <= max) { 107 | s[i++] = c; 108 | vt52(c); 109 | fflush(stdout); 110 | } 111 | goto loop; 112 | } 113 | 114 | 115 | #if 0 116 | static struct FCB { 117 | char drive; 118 | char name[11]; 119 | char data[24]; 120 | } samplefcb; 121 | #endif 122 | 123 | static void FCB_to_filename(unsigned char *p, char *name) { 124 | int i; 125 | char *org = name; 126 | /* strcpy(name, "test/"); 127 | name += 5; */ 128 | for (i = 0; i < 8; ++i) 129 | if (p[i+1] != ' ') 130 | *name++ = tolower(p[i+1]); 131 | if (p[9] != ' ') { 132 | *name++ = '.'; 133 | for (i = 0; i < 3; ++i) 134 | if (p[i+9] != ' ') 135 | *name++ = tolower(p[i+9]); 136 | } 137 | *name = '\0'; 138 | if (trace_bdos) 139 | printf("File name is %s\r\n", org); 140 | } 141 | 142 | static void FCB_to_ufilename(unsigned char *p, char *name) { 143 | int i; 144 | char *org = name; 145 | /* strcpy(name, "test/"); 146 | name += 5; */ 147 | for (i = 0; i < 8; ++i) 148 | if (p[i+1] != ' ') 149 | *name++ = toupper(p[i+1]); 150 | if (p[9] != ' ') { 151 | *name++ = '.'; 152 | for (i = 0; i < 3; ++i) 153 | if (p[i+9] != ' ') 154 | *name++ = toupper(p[i+9]); 155 | } 156 | *name = '\0'; 157 | if (trace_bdos) 158 | printf("File name is %s\r\n", org); 159 | } 160 | 161 | static struct stfps { 162 | FILE *fp; 163 | unsigned where; 164 | char name[12]; 165 | } stfps[100]; 166 | 167 | static void storefp(z80info *z80, FILE *fp, unsigned where) { 168 | int i; 169 | int ind = -1; 170 | for (i = 0; i < storedfps; ++i) 171 | if (stfps[i].where == 0xffffU) 172 | ind = i; 173 | else if (stfps[i].where == where) { 174 | ind = i; 175 | goto putfp; 176 | } 177 | if (ind < 0) { 178 | if (++storedfps > 100) { 179 | fprintf(stderr, "out of fp stores!\n"); 180 | resetterm(); 181 | exit(1); 182 | } 183 | ind = storedfps - 1; 184 | } 185 | stfps[ind].where = where; 186 | putfp: 187 | stfps[ind].fp = fp; 188 | memcpy(stfps[ind].name, z80->mem+z80->regde+1, 11); 189 | stfps[ind].name[11] = '\0'; 190 | } 191 | 192 | /* Lookup an FCB to find the host file. */ 193 | 194 | static FILE *lookfp(z80info *z80, unsigned where) { 195 | int i; 196 | for (i = 0; i < storedfps; ++i) 197 | if (stfps[i].where == where) 198 | if (memcmp(stfps[i].name, z80->mem+z80->regde+1, 11) == 0) 199 | return stfps[i].fp; 200 | /* fcb not found. maybe it has been moved? */ 201 | for (i = 0; i < storedfps; ++i) 202 | if (stfps[i].where != 0xffffU && 203 | !memcmp(z80->mem+z80->regde+1, stfps[i].name, 11)) { 204 | stfps[i].where = where; /* moved FCB */ 205 | return stfps[i].fp; 206 | } 207 | return NULL; 208 | } 209 | 210 | /* Report an error finding an FCB. */ 211 | 212 | static void fcberr(z80info *z80, unsigned where) { 213 | int i; 214 | 215 | fprintf(stderr, "error: cannot find fp entry for FCB at %04x" 216 | " fctn %d, FCB named %s\n", where, z80->regbc & 0xff, 217 | z80->mem+where+1); 218 | for (i = 0; i < storedfps; ++i) 219 | if (stfps[i].where != 0xffffU) 220 | printf("%s %04x\n", stfps[i].name, stfps[i].where); 221 | resetterm(); 222 | exit(1); 223 | } 224 | 225 | /* Get the host file for an FCB when it should be open. */ 226 | 227 | static FILE *getfp(z80info *z80, unsigned where) { 228 | FILE *fp; 229 | 230 | if (!(fp = lookfp(z80, where))) 231 | fcberr(z80, where); 232 | return fp; 233 | } 234 | 235 | static void delfp(z80info *z80, unsigned where) { 236 | int i; 237 | for (i = 0; i < storedfps; ++i) 238 | if (stfps[i].where == where) { 239 | stfps[i].where = 0xffffU; 240 | return; 241 | } 242 | fcberr(z80, where); 243 | } 244 | 245 | /* FCB fields */ 246 | #define FCB_DR 0 247 | #define FCB_F1 1 248 | #define FCB_T1 9 249 | #define FCB_EX 12 250 | #define FCB_S1 13 251 | #define FCB_S2 14 252 | #define FCB_RC 15 253 | #define FCB_CR 32 254 | #define FCB_R0 33 255 | #define FCB_R1 34 256 | #define FCB_R2 35 257 | 258 | /* S2: only low 6 bits have extent number: upper bits are flags.. */ 259 | /* Upper bit: means file is open? */ 260 | 261 | /* Support 8MB files */ 262 | 263 | #define ADDRESS (((long)z80->mem[z80->regde+FCB_R0] + \ 264 | (long)z80->mem[z80->regde+FCB_R1] * 256) * 128L) 265 | /* (long)z80->mem[z80->regde+35] * 65536L; */ 266 | 267 | 268 | /* For sequential access, the record number is also in the FCB: 269 | * EX + 32 * S2 is extent number (0 .. 8191). Each extent has 16 blocks. Each block is 1K. 270 | * extent number = (file offset) / 16384 271 | * 272 | * RC has number of records in current extent: (0 .. 128) 273 | * maybe just set to 128? 274 | * otherwise: 275 | * 276 | * CR is current record offset within current extent: (0 .. 127) 277 | * ((file offset) % 16384) / 128 278 | */ 279 | 280 | /* Current extent number */ 281 | #define SEQ_EXT ((long)z80->mem[DE + FCB_EX] + 32L * (long)(0x3F & z80->mem[DE + FCB_S2])) 282 | 283 | /* Current byte offset */ 284 | #define SEQ_ADDRESS (16384L * SEQ_EXT + 128L * (long)z80->mem[DE + FCB_CR]) 285 | 286 | /* Convert offset to CR */ 287 | #define SEQ_CR(n) (((n) % 16384) / 128) 288 | 289 | /* Convert offset to extent number */ 290 | #define SEQ_EXTENT(n) ((n) / 16384) 291 | 292 | /* Convert offset to low byte of extent number */ 293 | #define SEQ_EX(n) (SEQ_EXTENT(n) % 32) 294 | 295 | /* Convert offset to high byte of extent number */ 296 | #define SEQ_S2(n) (SEQ_EXTENT(n) / 32) 297 | 298 | static DIR *dp = NULL; 299 | static unsigned sfn = 0; 300 | 301 | char *bdos_decode(int n) 302 | { 303 | switch (n) { 304 | case 0: return "System Reset"; 305 | case 1: return "Console Input"; 306 | case 2: return "Console Output"; 307 | case 3: return "Reader input"; 308 | case 4: return "Punch output"; 309 | case 5: return "List output"; 310 | case 6: return "direct I/O"; 311 | case 7: return "get I/O byte"; 312 | case 8: return "set I/O byte"; 313 | case 9: return "Print String"; 314 | case 10: return "Read Command Line"; 315 | case 11: return "Console Status"; 316 | case 12: return "Return Version Number"; 317 | case 13: return "reset disk system"; 318 | case 14: return "select disk"; 319 | case 15: return "open file"; 320 | case 16: return "close file"; 321 | case 17: return "search for first"; 322 | case 18: return "search for next"; 323 | case 19: return "delete file (no wildcards yet)"; 324 | case 20: return "read sequential"; 325 | case 21: return "write sequential"; 326 | case 22: return "make file"; 327 | case 23: return "rename file"; 328 | case 24: return "return login vector"; 329 | case 25: return "return current disk"; 330 | case 26: return "Set DMA Address"; 331 | case 27: return "Get alloc addr"; 332 | case 28: return "Set r/o vector"; 333 | case 29: return "return r/o vector"; 334 | case 30: return "Set file attributes"; 335 | case 31: return "get disk parameters"; 336 | case 32: return "Get/Set User Code"; 337 | case 33: return "read random record"; 338 | case 34: return "write random record"; 339 | case 35: return "compute file size"; 340 | case 36: return "set random record"; 341 | case 41: 342 | default: return "unknown"; 343 | } 344 | } 345 | 346 | /* True if DE points to an FCB for a given BDOS call */ 347 | 348 | int bdos_fcb(int n) 349 | { 350 | switch (n) { 351 | case 15: return 1; /* "open file"; */ 352 | case 16: return 1; /* "close file"; */ 353 | case 17: return 1; /* "search for first"; */ 354 | case 18: return 1; /* "search for next"; */ 355 | case 19: return 1; /* "delete file (no wildcards yet)"; */ 356 | case 20: return 1; /* "read sequential"; */ 357 | case 21: return 1; /* "write sequential"; */ 358 | case 22: return 1; /* "make file"; */ 359 | case 23: return 1; /* "rename file"; */ 360 | case 30: return 1; /* set attribute */ 361 | case 33: return 1; /* "read random record"; */ 362 | case 34: return 1; /* "write random record"; */ 363 | case 35: return 1; /* "compute file size"; */ 364 | case 36: return 1; /* "set random record"; */ 365 | default: return 0; 366 | } 367 | } 368 | 369 | void bdos_fcb_dump(z80info *z80) 370 | { 371 | char buf[80]; 372 | printf("FCB %x: ", DE); 373 | buf[0] = (0x7F & z80->mem[DE + 1]); 374 | buf[1] = (0x7F & z80->mem[DE + 2]); 375 | buf[2] = (0x7F & z80->mem[DE + 3]); 376 | buf[3] = (0x7F & z80->mem[DE + 4]); 377 | buf[4] = (0x7F & z80->mem[DE + 5]); 378 | buf[5] = (0x7F & z80->mem[DE + 6]); 379 | buf[6] = (0x7F & z80->mem[DE + 7]); 380 | buf[7] = (0x7F & z80->mem[DE + 8]); 381 | buf[8] = '.'; 382 | buf[9] = (0x7F & z80->mem[DE + 9]); 383 | buf[10] = (0x7F & z80->mem[DE + 10]); 384 | buf[11] = (0x7F & z80->mem[DE + 11]); 385 | buf[12] = 0; 386 | printf("DR=%x F='%s' EX=%x S1=%x S2=%x RC=%x CR=%x R0=%x R1=%x R2=%x\n", 387 | z80->mem[DE + 0], buf, z80->mem[DE + 12], z80->mem[DE + 13], 388 | z80->mem[DE + 14], z80->mem[DE + 15], z80->mem[DE + 32], z80->mem[DE + 33], 389 | z80->mem[DE + 34], z80->mem[DE + 35]); 390 | } 391 | 392 | /* Get count of records in current extent */ 393 | 394 | int fixrc(z80info *z80, FILE *fp) 395 | { 396 | struct stat stbuf; 397 | unsigned long size; 398 | unsigned long full; 399 | unsigned long ext; 400 | /* Get file size */ 401 | if (fstat(fileno(fp), &stbuf) || !S_ISREG(stbuf.st_mode)) { 402 | return -1; 403 | } 404 | 405 | size = (stbuf.st_size + 127) >> 7; /* number of records in file */ 406 | 407 | full = size - (size % 128); /* record number of first partially full extent */ 408 | ext = SEQ_EXT * 128; /* record number of current extent */ 409 | 410 | if (ext < full) 411 | /* Current extent is full */ 412 | z80->mem[DE + FCB_RC] = 128; 413 | else if (ext > full) 414 | /* We are pointing past the end */ 415 | z80->mem[DE + FCB_RC] = 0; 416 | else 417 | /* We are pointing to a partial extent */ 418 | z80->mem[DE + FCB_RC] = size - full; 419 | 420 | return 0; 421 | } 422 | 423 | /* emulation of BDOS calls */ 424 | 425 | void check_BDOS_hook(z80info *z80) { 426 | int i; 427 | char name[32]; 428 | char name2[32]; 429 | FILE *fp; 430 | char *s, *t; 431 | const char *mode; 432 | if (trace_bdos) 433 | { 434 | printf("\r\nbdos %d %s (AF=%04x BC=%04x DE=%04x HL =%04x SP=%04x STACK=", C, bdos_decode(C), AF, BC, DE, HL, SP); 435 | for (i = 0; i < 8; ++i) 436 | printf(" %4x", z80->mem[SP + 2*i] 437 | + 256 * z80->mem[SP + 2*i + 1]); 438 | printf(")\r\n"); 439 | } 440 | switch (C) { 441 | case 0: /* System Reset */ 442 | warmboot(z80); 443 | return; 444 | #if 0 445 | for (i = 0; i < 0x1600; ++i) 446 | z80->mem[i+BIOS-0x1600] = cpmsys[i]; 447 | BC = 0; 448 | PC = BIOS-0x1600+3; 449 | SP = 0x80; 450 | #endif 451 | break; 452 | case 1: /* Console Input */ 453 | HL = kget(0); 454 | B = H; A = L; 455 | if (A < ' ') { 456 | switch(A) { 457 | case '\r': 458 | case '\n': 459 | case '\t': 460 | vt52(A); 461 | break; 462 | default: 463 | vt52('^'); 464 | vt52((A & 0xff)+'@'); 465 | if (A == 3) { /* ctrl-C pressed */ 466 | /* PC = BIOS+3; 467 | check_BIOS_hook(); */ 468 | warmboot(z80); 469 | return; 470 | } 471 | } 472 | } else { 473 | vt52(A); 474 | } 475 | break; 476 | case 2: /* Console Output */ 477 | vt52(0x7F & E); 478 | HL = 0; 479 | B = H; A = L; 480 | break; 481 | case 3: /* AUX (Reader) input */ 482 | /* FIXME */ 483 | /* Returns A=L=ASCII character */ 484 | goto UNSUP; 485 | case 4: /* Punch output */ 486 | /* FIXME */ 487 | /* Character to punch is in E */ 488 | goto UNSUP; 489 | case 5: /* Printer output */ 490 | /* FIXME */ 491 | /* Character to print is in E */ 492 | goto UNSUP; 493 | case 6: /* direct I/O */ 494 | switch (E) { 495 | case 0xff: if (!constat()) { 496 | HL = 0; 497 | B = H; A = L; 498 | F = 0; 499 | break; 500 | } /* FALLTHRU */ 501 | case 0xfd: HL = kget(0); 502 | B = H; A = L; 503 | F = 0; 504 | break; 505 | case 0xfe: HL = constat() ? 0xff : 0; 506 | B = H; A = L; 507 | F = 0; 508 | break; 509 | default: vt52(0x7F & E); 510 | HL = 0; 511 | B = H; A = L; 512 | } 513 | break; 514 | case 7: /* Get IOBYTE */ 515 | /* FIXME */ 516 | goto UNSUP; 517 | case 8: /* Set IOBYTE */ 518 | /* FIXME */ 519 | goto UNSUP; 520 | case 9: /* Print String */ 521 | s = (char *)(z80->mem + DE); 522 | while (*s != '$') 523 | vt52(0x7F & *s++); 524 | HL = 0; 525 | B = H; A = L; 526 | break; 527 | case 10: /* Read Command Line */ 528 | s = rdcmdline(z80, *(unsigned char *)(t = (char *)(z80->mem + DE)), 1); 529 | if (PC == BIOS+3) { /* ctrl-C pressed */ 530 | /* check_BIOS_hook(); */ /* execute WBOOT */ 531 | warmboot(z80); 532 | return; 533 | } 534 | ++t; 535 | for (i = 0; i <= *(unsigned char *)s; ++i) 536 | t[i] = s[i]; 537 | HL = 0; 538 | B = H; A = L; 539 | break; 540 | case 11: /* Console Status */ 541 | HL = (constat() ? 0xff : 0x00); 542 | B = H; A = L; 543 | F = 0; 544 | break; 545 | case 12: /* Return Version Number */ 546 | HL = 0x22; /* Emulate CP/M 2.2 */ 547 | B = H; A = L; 548 | F = 0; 549 | break; 550 | case 13: /* reset disk system */ 551 | /* storedfps = 0; */ /* WS crashes then */ 552 | HL = 0; 553 | B = H; A = L; 554 | if (dp) 555 | closedir(dp); 556 | { struct dirent *de; 557 | if ((dp = opendir("."))) { 558 | while ((de = readdir(dp))) { 559 | if (strchr(de->d_name, '$')) { 560 | A = 0xff; 561 | break; 562 | } 563 | } 564 | closedir(dp); 565 | } 566 | } 567 | dp = NULL; 568 | z80->dma = 0x80; 569 | /* select only A:, all r/w */ 570 | break; 571 | case 14: /* select disk */ 572 | HL = 0; 573 | B = H; A = L; 574 | break; 575 | case 15: /* open file */ 576 | mode = "r+b"; 577 | fileio: 578 | /* check if the file is already open */ 579 | if (!(fp = lookfp(z80, DE))) { 580 | /* not already open - try lowercase */ 581 | FCB_to_filename(z80->mem+DE, name); 582 | if (!(fp = fopen(name, mode))) { 583 | FCB_to_ufilename(z80->mem+DE, name); /* Try all uppercase instead */ 584 | if (!(fp = fopen(name, mode))) { 585 | FCB_to_filename(z80->mem+DE, name); 586 | if (*mode == 'r') { 587 | char ss[50]; 588 | snprintf(ss, sizeof(ss), "%s/%s", CPMLIBDIR, name); 589 | fp = fopen(ss, mode); 590 | if (!fp) 591 | fp = fopen(ss, "rb"); 592 | } 593 | if (!fp) { 594 | /* still no success */ 595 | HL = 0xFF; 596 | B = H; A = L; 597 | F = 0; 598 | break; 599 | } 600 | } 601 | } 602 | /* where to store fp? */ 603 | storefp(z80, fp, DE); 604 | } 605 | /* success */ 606 | 607 | /* memset(z80->mem + DE + 12, 0, 33-12); */ 608 | 609 | /* User has to set EX, S2, and CR: don't change these- some set them to non-zero */ 610 | z80->mem[DE + FCB_S1] = 0; 611 | /* memset(z80->mem + DE + 16, 0, 16); */ /* Clear D0-Dn */ 612 | 613 | /* Should we clear R0 - R2? Nope: then we overlap the following area. */ 614 | /* memset(z80->mem + DE + 33, 0, 3); */ 615 | 616 | /* We need to set high bit of S2: means file is open? */ 617 | z80->mem[DE + FCB_S2] |= 0x80; 618 | 619 | z80->mem[DE + FCB_RC] = 0; /* rc field of FCB */ 620 | 621 | if (fixrc(z80, fp)) { /* Not a real file? */ 622 | HL = 0xFF; 623 | B = H; A = L; 624 | F = 0; 625 | fclose(fp); 626 | delfp(z80, DE); 627 | break; 628 | } 629 | HL = 0; 630 | B = H; A = L; 631 | F = 0; 632 | /* printf("opening file %s\n", name); */ 633 | break; 634 | case 16: /* close file */ 635 | { 636 | long host_size, host_exts; 637 | 638 | if (!(fp = lookfp(z80, DE))) { 639 | /* if the FBC is unknown, return an error */ 640 | HL = 0xFF; 641 | B = H, A = L; 642 | break; 643 | } 644 | fseek(fp, 0, SEEK_END); 645 | host_size = ftell(fp); 646 | host_exts = SEQ_EXTENT(host_size); 647 | if (host_exts == SEQ_EXT) { 648 | /* this is the last extent of the file so we allow the 649 | CP/M program to truncate it by reducing RC */ 650 | if (z80->mem[DE + FCB_RC] < SEQ_CR(host_size)) { 651 | host_size = (16384L * SEQ_EXT + 128L * (long)z80->mem[DE + FCB_RC]); 652 | ftruncate(fileno(fp), host_size); 653 | } 654 | } 655 | delfp(z80, DE); 656 | fclose(fp); 657 | z80->mem[DE + FCB_S2] &= 0x7F; /* Clear high bit: indicates closed */ 658 | HL = 0; 659 | B = H; A = L; 660 | /* printf("close file\n"); */ 661 | } 662 | break; 663 | case 17: /* search for first */ 664 | if (dp) 665 | closedir(dp); 666 | if (!(dp = opendir("."))) { 667 | fprintf(stderr, "opendir fails\n"); 668 | resetterm(); 669 | exit(1); 670 | } 671 | sfn = DE; 672 | /* fall through */ 673 | case 18: /* search for next */ 674 | if (!dp) 675 | goto retbad; 676 | { struct dirent *de; 677 | unsigned char *p; 678 | const char *sr; 679 | nocpmname: 680 | if (!(de = readdir(dp))) { 681 | closedir(dp); 682 | dp = NULL; 683 | retbad: 684 | HL = 0xff; 685 | B = H; A = L; 686 | F = 0; 687 | break; 688 | } 689 | /* printf("\r\nlooking at %s\r\n", de->d_name); */ 690 | /* compare data */ 691 | memset(p = z80->mem+z80->dma, 0, 128); /* dmaaddr instead of DIRBUF!! */ 692 | if (*de->d_name == '.') 693 | goto nocpmname; 694 | if (strchr(sr = de->d_name, '.')) { 695 | if (strlen(de->d_name) > 12) /* POSIX: namlen */ 696 | goto nocpmname; 697 | } else if (strlen(de->d_name) > 8) 698 | goto nocpmname; 699 | /* seems OK */ 700 | for (i = 0; i < 8; ++i) 701 | if (*sr != '.' && *sr) { 702 | *++p = toupper(*(unsigned char *)sr); sr++; 703 | } else 704 | *++p = ' '; 705 | /* skip dot */ 706 | while (*sr && *sr != '.') 707 | ++sr; 708 | while (*sr == '.') 709 | ++sr; 710 | for (i = 0; i < 3; ++i) 711 | if (*sr != '.' && *sr) { 712 | *++p = toupper(*(unsigned char *)sr); sr++; 713 | } else 714 | *++p = ' '; 715 | /* OK, fcb block is filled */ 716 | /* match name */ 717 | p -= 11; 718 | sr = (char *)(z80->mem + sfn); 719 | for (i = 1; i <= 12; ++i) 720 | if (sr[i] != '?' && sr[i] != p[i]) 721 | goto nocpmname; 722 | /* yup, it matches */ 723 | HL = 0x00; /* always at pos 0 */ 724 | B = H; A = L; 725 | F = 0; 726 | p[32] = p[64] = p[96] = 0xe5; 727 | } 728 | break; 729 | case 19: /* delete file (no wildcards yet) */ 730 | FCB_to_filename(z80->mem + DE, name); 731 | unlink(name); 732 | HL = 0; 733 | B = H; A = L; 734 | break; 735 | case 20: /* read sequential */ 736 | fp = getfp(z80, DE); 737 | readseq: 738 | if (!fseek(fp, SEQ_ADDRESS, SEEK_SET) && ((i = fread(z80->mem+z80->dma, 1, 128, fp)) > 0)) { 739 | long ofst = ftell(fp) + 127; 740 | if (i != 128) 741 | memset(z80->mem+z80->dma+i, 0x1a, 128-i); 742 | z80->mem[DE + FCB_CR] = SEQ_CR(ofst); 743 | z80->mem[DE + FCB_EX] = SEQ_EX(ofst); 744 | z80->mem[DE + FCB_S2] = (0x80 | SEQ_S2(ofst)); 745 | fixrc(z80, fp); 746 | HL = 0x00; 747 | B = H; A = L; 748 | } else { 749 | HL = 0x1; /* ff => pip error */ 750 | B = H; A = L; 751 | } 752 | break; 753 | case 21: /* write sequential */ 754 | fp = getfp(z80, DE); 755 | writeseq: 756 | if (!fseek(fp, SEQ_ADDRESS, SEEK_SET) && fwrite(z80->mem+z80->dma, 1, 128, fp) == 128) { 757 | long ofst = ftell(fp); 758 | z80->mem[DE + FCB_CR] = SEQ_CR(ofst); 759 | z80->mem[DE + FCB_EX] = SEQ_EX(ofst); 760 | z80->mem[DE + FCB_S2] = (0x80 | SEQ_S2(ofst)); 761 | fflush(fp); 762 | fixrc(z80, fp); 763 | HL = 0x00; 764 | B = H; A = L; 765 | } else { 766 | HL = 0xff; 767 | B = H; A = L; 768 | } 769 | break; 770 | case 22: /* make file */ 771 | mode = "w+b"; 772 | goto fileio; 773 | case 23: /* rename file */ 774 | FCB_to_filename(z80->mem + DE, name); 775 | FCB_to_filename(z80->mem + DE + 16, name2); 776 | /* printf("rename %s %s called\n", name, name2); */ 777 | rename(name, name2); 778 | HL = 0; 779 | B = H; A = L; 780 | break; 781 | case 24: /* return login vector */ 782 | HL = 1; /* only A: online */ 783 | B = H; A = L; 784 | F = 0; 785 | break; 786 | case 25: /* return current disk */ 787 | HL = 0; /* only A: */ 788 | B = H; A = L; 789 | F = 0; 790 | break; 791 | case 26: /* Set DMA Address */ 792 | z80->dma = DE; 793 | HL = 0; 794 | B = H; A = L; 795 | break; 796 | case 27: /* Get Allocation ADR */ 797 | /* FIXME */ 798 | goto UNSUP; 799 | case 28: /* Write protect disk */ 800 | /* FIXME */ 801 | goto UNSUP; 802 | case 29: /* return r/o vector */ 803 | HL = 0; /* none r/o */ 804 | B = H; A = L; 805 | F = 0; 806 | break; 807 | case 30: /* Set file attributes */ 808 | /* FIXME */ 809 | goto UNSUP; 810 | case 31: /* get disk parameters */ 811 | HL = DPB0; /* only A: */ 812 | B = H; A = L; 813 | break; 814 | case 32: /* Get/Set User Code */ 815 | if (E == 0xff) { /* Get Code */ 816 | HL = usercode; 817 | B = H; A = L; 818 | } else { 819 | usercode = E; 820 | HL = 0; /* Or does it get usercode? */ 821 | B = H; A = L; 822 | } 823 | break; 824 | case 33: /* read random record */ 825 | { 826 | long ofst; 827 | fp = getfp(z80, DE); 828 | /* printf("data is %02x %02x %02x\n", z80->mem[z80->regde+33], 829 | z80->mem[z80->regde+34], z80->mem[z80->regde+35]); */ 830 | ofst = ADDRESS; 831 | z80->mem[DE + FCB_CR] = SEQ_CR(ofst); 832 | z80->mem[DE + FCB_EX] = SEQ_EX(ofst); 833 | z80->mem[DE + FCB_S2] = (0x80 | SEQ_S2(ofst)); 834 | goto readseq; 835 | } 836 | case 34: /* write random record */ 837 | { 838 | long ofst; 839 | RANDWRITE: 840 | fp = getfp(z80, DE); 841 | /* printf("data is %02x %02x %02x\n", z80->mem[z80->regde+33], 842 | z80->mem[z80->regde+34], z80->mem[z80->regde+35]); */ 843 | ofst = ADDRESS; 844 | z80->mem[DE + FCB_CR] = SEQ_CR(ofst); 845 | z80->mem[DE + FCB_EX] = SEQ_EX(ofst); 846 | z80->mem[DE + FCB_S2] = (0x80 | SEQ_S2(ofst)); 847 | goto writeseq; 848 | } 849 | case 35: /* compute file size */ 850 | fp = getfp(z80, DE); 851 | fseek(fp, 0L, SEEK_END); 852 | /* fall through */ 853 | case 36: /* set random record */ 854 | fp = getfp(z80, DE); 855 | { 856 | long ofst = ftell(fp) + 127; 857 | long pos = (ofst >> 7); 858 | HL = 0x00; /* dunno, if necessary */ 859 | B = H; A = L; 860 | z80->mem[DE + FCB_R0] = pos & 0xff; 861 | z80->mem[DE + FCB_R1] = pos >> 8; 862 | z80->mem[DE + FCB_R2] = pos >> 16; 863 | z80->mem[DE + FCB_CR] = SEQ_CR(ofst); 864 | z80->mem[DE + FCB_EX] = SEQ_EX(ofst); 865 | z80->mem[DE + FCB_S2] = (0x80 | SEQ_S2(ofst)); 866 | fixrc(z80, fp); 867 | } 868 | break; 869 | case 37: /* Selectively reset disk drives (allow writing after calling BDOS 28) */ 870 | A=0; /* Return with no errors */ 871 | break; 872 | case 40: /* Write rand with 0 fill */ 873 | /* FIXME */ 874 | goto RANDWRITE; 875 | case 41: /* Change directory- this is non-standard */ 876 | for (s = (char *)(z80->mem + DE); *s; ++s) 877 | *s = tolower(*(unsigned char *)s); 878 | HL = (restricted_mode || chdir((char *)(z80->mem + DE))) ? 0xff : 0x00; 879 | B = H; A = L; 880 | break; 881 | default: 882 | UNSUP: 883 | printf("\n\nUnrecognized BDOS-Function %d:\n", C); 884 | printf("AF=%04x BC=%04x DE=%04x HL=%04x SP=%04x\nStack =", 885 | AF, BC, DE, HL, SP); 886 | for (i = 0; i < 8; ++i) 887 | printf(" %4x", z80->mem[SP + 2*i] 888 | + 256 * z80->mem[SP + 2*i + 1]); 889 | printf("\r\n"); 890 | /*resetterm(); */ 891 | /*exit(1); */ 892 | break; 893 | } 894 | z80->mem[PC = DIRBUF-1] = 0xc9; /* Return instruction */ 895 | return; 896 | } 897 | -------------------------------------------------------------------------------- /bios.c: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------*\ 2 | | bios.c -- CP/M emulator "front-end" for the Z80 emulator -- runs | 3 | | standard CP/M executables with no changes as long as they use CP/M | 4 | | system calls to do all their work. | 5 | | | 6 | | Originally by Kevin Kayes, but he refused any responsibility for it, | 7 | | then I later hacked it up, enhanced it, and debugged it. | 8 | | | 9 | | Copyright 1986-1988 by Parag Patel. All Rights Reserved. | 10 | | Copyright 1994-1995,2000 by CodeGen, Inc. All Rights Reserved. | 11 | \*-----------------------------------------------------------------------*/ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "cpmdisc.h" 20 | #include "defs.h" 21 | 22 | #ifdef macintosh 23 | #include 24 | #else 25 | #include 26 | #endif 27 | 28 | /* definition of: extern unsigned char cpm_array[]; */ 29 | #include "cpm.c" 30 | 31 | /* The BDOS/CCP had better be built with these values! */ 32 | #define CCP 0xD400 /* (BIOS - 0x1600) */ 33 | #define BDOS 0xDC00 34 | #define CBIOS 0xEA00 35 | 36 | #define NENTRY 30 /* number of BIOS entries */ 37 | #define STACK 0xEF00 /* grows down from here */ 38 | 39 | /* The first NUMHDISCS drives may be specified as hard-drives. */ 40 | #define NUMHDISCS 2 41 | #define NUMDISCS (MAXDISCS - NUMHDISCS) 42 | 43 | #if NUMHDISCS > MAXDISCS 44 | # error Too many hard-discs specified here. 45 | #endif 46 | 47 | /* disk parameter block information */ 48 | #define DPBSIZE 15 49 | #define HDPBLOCK (CBIOS + NENTRY * 8) 50 | #define DPBLOCK (HDPBLOCK + DPBSIZE) 51 | #define DIRBUF (DPBLOCK + DPBSIZE) 52 | #define DPHSIZE 16 53 | 54 | /* hard disc parameter info */ 55 | #define HDPBASE (DIRBUF + SECTORSIZE) 56 | 57 | #define DPBASE (HDPBASE + DPHSIZE * NUMHDISCS) 58 | #define CVSIZE 0 59 | #define ALVSIZE 33 60 | #define CVBASE (DPBASE + DPHSIZE * NUMDISCS) 61 | #define ALVBASE (CVBASE + CVSIZE * NUMDISCS) 62 | 63 | /* ST-506 allocation vector size */ 64 | #define HALVBASE (ALVBASE + ALVSIZE * NUMDISCS) 65 | #define HALVSIZE 306 66 | 67 | /* buffer for where to return time/date info */ 68 | #define TIMEBUF (HALVBASE + HALVSIZE * NUMHDISCS) 69 | #define TIMEBUFSIZE 5 70 | 71 | /* just a marker for future expansion */ 72 | #define END_OF_BIOS (TIMEBUF + TIMEBUFSIZE) 73 | 74 | 75 | /* ST-506 HD sector info (floppy defs are in cpmdisc.h for makedisc.c) */ 76 | #define HDSECTORSPERTRACK 64 77 | #define HDTRACKSPERDISC 610 78 | 79 | /* offsets into FCB needed for reading/writing Unix files */ 80 | #define FDOFFSET 12 81 | #define BLKOFFSET 16 82 | #define SZOFFSET 20 83 | 84 | /* this batch of macros are not used at the moment */ 85 | #define CBUFF CCP+7 86 | #define CIBUFF CCP+8 87 | #define DEFAULTFCB 0x005C 88 | #define DEFAULTBUF 0x0080 89 | #define IOBYTE 0x0003 90 | #define DISK 0x0004 91 | #define DMA 0x0008 92 | #define USER 0x000A 93 | #define USERSTART 0x0100 94 | 95 | 96 | /* forward declarations: */ 97 | static void seldisc(z80info *z80); 98 | 99 | 100 | static void 101 | closeall(z80info *z80) 102 | { 103 | int i; 104 | 105 | for (i = 0; i < MAXDISCS; i++) 106 | { 107 | if (z80->drives[i] != NULL) 108 | { 109 | fclose(z80->drives[i]); 110 | z80->drives[i] = NULL; 111 | } 112 | } 113 | } 114 | 115 | void 116 | warmboot(z80info *z80) 117 | { 118 | unsigned int i; 119 | 120 | closeall(z80); 121 | 122 | if (silent_exit) { 123 | finish(z80); 124 | } 125 | 126 | /* load CCP and BDOS into memory (max 0x1600 in size) */ 127 | for (i = 0; i < 0x1600 && i < sizeof cpm_array; i++) 128 | SETMEM(CCP + i, cpm_array[i]); 129 | 130 | /* try to load CCP/BDOS from disk, but ignore any errors */ 131 | loadfile(z80, "bdos.hex"); 132 | loadfile(z80, "ccp.hex"); 133 | 134 | /* CP/M system reset via "JP 00" - entry into BIOS warm-boot */ 135 | SETMEM(0x0000, 0xC3); /* JP CBIOS+3 */ 136 | SETMEM(0x0001, ((CBIOS + 3) & 0xFF)); 137 | SETMEM(0x0002, ((CBIOS + 3) >> 8)); 138 | 139 | /* 0x0003 is the IOBYTE, 0x0004 is the current DISK */ 140 | SETMEM(0x0003, 0x00); 141 | SETMEM(0x0004, z80->drive); 142 | 143 | /* CP/M syscall via "CALL 05" - entry into BDOS */ 144 | SETMEM(0x0005, 0xC3); /* JP BDOS+6 */ 145 | SETMEM(0x0006, ((BDOS+6) & 0xFF)); 146 | SETMEM(0x0007, ((BDOS+6) >> 8)); 147 | 148 | /* fake BIOS entry points */ 149 | for (i = 0; i < NENTRY; i++) 150 | { 151 | /* JP */ 152 | SETMEM(CBIOS + 3 * i, 0xC3); 153 | SETMEM(CBIOS + 3 * i + 1, (CBIOS + NENTRY * 3 + i * 5) & 0xFF); 154 | SETMEM(CBIOS + 3 * i + 2, (CBIOS + NENTRY * 3 + i * 5) >> 8); 155 | 156 | /* LD A, - start of bios-entry */ 157 | SETMEM(CBIOS + NENTRY * 3 + i * 5, 0x3E); 158 | SETMEM(CBIOS + NENTRY * 3 + i * 5 + 1, i); 159 | 160 | /* OUT A,0FFH - we use port 0xFF to fake the BIOS call */ 161 | SETMEM(CBIOS + NENTRY * 3 + i * 5 + 2, 0xD3); 162 | SETMEM(CBIOS + NENTRY * 3 + i * 5 + 3, 0xFF); 163 | 164 | /* RET - end of bios-entry */ 165 | SETMEM(CBIOS + NENTRY * 3 + i * 5 + 4, 0xC9); 166 | } 167 | 168 | /* disc parameter block - a 5Mb ST-506 hard disc */ 169 | SETMEM(HDPBLOCK, HDSECTORSPERTRACK & 0xFF);/* SPT - sectors per track */ 170 | SETMEM(HDPBLOCK + 1, HDSECTORSPERTRACK >> 8); 171 | SETMEM(HDPBLOCK + 2, 4); /* BSH - data block shift factor */ 172 | SETMEM(HDPBLOCK + 3, 15); /* BLM - data block mask */ 173 | SETMEM(HDPBLOCK + 4, 0); /* EXM - extent mask */ 174 | SETMEM(HDPBLOCK + 5, 2441 & 0xFF); /* DSM - total drive capacity */ 175 | SETMEM(HDPBLOCK + 6, 2441 >> 8); 176 | SETMEM(HDPBLOCK + 7, 1023 & 0xFF); /* DRM - total dir entries */ 177 | SETMEM(HDPBLOCK + 8, 1023 >> 8); 178 | SETMEM(HDPBLOCK + 9, 0xFF); /* AL0 - blocks for directory entries */ 179 | SETMEM(HDPBLOCK + 10, 0xFF); /* AL1 */ 180 | SETMEM(HDPBLOCK + 11, 0x00); /* CKS - directory check vector */ 181 | SETMEM(HDPBLOCK + 12, 0x00); 182 | SETMEM(HDPBLOCK + 13, RESERVEDTRACKS & 0xFF);/* OFF - reserved tracks */ 183 | SETMEM(HDPBLOCK + 14, RESERVEDTRACKS >> 8); 184 | 185 | /* disk parameter headers for hard disc */ 186 | for (i = 0; i < NUMHDISCS; i++) 187 | { 188 | SETMEM(HDPBASE + DPHSIZE * i, 0x00); /* XLT */ 189 | SETMEM(HDPBASE + DPHSIZE * i + 1, 0x00); 190 | SETMEM(HDPBASE + DPHSIZE * i + 2, 0x00); /* scratch 1 */ 191 | SETMEM(HDPBASE + DPHSIZE * i + 3, 0x00); 192 | SETMEM(HDPBASE + DPHSIZE * i + 4, 0x00); /* scratch 2 */ 193 | SETMEM(HDPBASE + DPHSIZE * i + 5, 0x00); 194 | SETMEM(HDPBASE + DPHSIZE * i + 6, 0x00); /* scratch 3 */ 195 | SETMEM(HDPBASE + DPHSIZE * i + 7, 0x00); 196 | SETMEM(HDPBASE + DPHSIZE * i + 8, DIRBUF & 0xFF); /* DIRBUF */ 197 | SETMEM(HDPBASE + DPHSIZE * i + 9, DIRBUF >> 8); 198 | SETMEM(HDPBASE + DPHSIZE * i + 10, HDPBLOCK & 0xFF); /* DPB */ 199 | SETMEM(HDPBASE + DPHSIZE * i + 11, HDPBLOCK >> 8); 200 | SETMEM(HDPBASE + DPHSIZE * i + 12, 0x00); 201 | SETMEM(HDPBASE + DPHSIZE * i + 13, 0x00); 202 | SETMEM(HDPBASE + DPHSIZE * i + 14, 203 | (HALVBASE + HALVSIZE * i) & 0xFF); 204 | SETMEM(HDPBASE + DPHSIZE * i + 15, 205 | (HALVBASE + HALVSIZE * i) >> 8); 206 | } 207 | 208 | /* disc parameter block - a single-sided single-density 8" 256k disc */ 209 | SETMEM(DPBLOCK, SECTORSPERTRACK & 0xFF); /* SPT - sectors per track */ 210 | SETMEM(DPBLOCK + 1, SECTORSPERTRACK >> 8); 211 | SETMEM(DPBLOCK + 2, 3); /* BSH - data block shift factor */ 212 | SETMEM(DPBLOCK + 3, 7); /* BLM - data block mask */ 213 | SETMEM(DPBLOCK + 4, 0); /* EXM - extent mask */ 214 | SETMEM(DPBLOCK + 5, 242); /* DSM - total capacity of drive */ 215 | SETMEM(DPBLOCK + 6, 0); 216 | SETMEM(DPBLOCK + 7, 63); /* DRM - total directory entries */ 217 | SETMEM(DPBLOCK + 8, 0); 218 | SETMEM(DPBLOCK + 9, 0xC0); /* AL0 - blocks for directory entries */ 219 | SETMEM(DPBLOCK + 10, 0x00); /* AL1 */ 220 | SETMEM(DPBLOCK + 11, 0x00); /* CKS - directory check vector */ 221 | SETMEM(DPBLOCK + 12, 0x00); 222 | SETMEM(DPBLOCK + 13, RESERVEDTRACKS & 0xFF); /* OFF - reserved tracks */ 223 | SETMEM(DPBLOCK + 14, RESERVEDTRACKS >> 8); 224 | 225 | /* disc parameter headers */ 226 | for (i = 0; i < NUMDISCS; i++) 227 | { 228 | SETMEM(DPBASE + DPHSIZE * i, 0x00); /* XLT */ 229 | SETMEM(DPBASE + DPHSIZE * i + 1, 0x00); 230 | SETMEM(DPBASE + DPHSIZE * i + 2, 0x00); /* scratch 1 */ 231 | SETMEM(DPBASE + DPHSIZE * i + 3, 0x00); 232 | SETMEM(DPBASE + DPHSIZE * i + 4, 0x00); /* scratch 2 */ 233 | SETMEM(DPBASE + DPHSIZE * i + 5, 0x00); 234 | SETMEM(DPBASE + DPHSIZE * i + 6, 0x00); /* scratch 3 */ 235 | SETMEM(DPBASE + DPHSIZE * i + 7, 0x00); 236 | SETMEM(DPBASE + DPHSIZE * i + 8, DIRBUF & 0xFF); /* DIRBUF */ 237 | SETMEM(DPBASE + DPHSIZE * i + 9, DIRBUF >> 8); 238 | SETMEM(DPBASE + DPHSIZE * i + 10, DPBLOCK & 0xFF); /* DPB */ 239 | SETMEM(DPBASE + DPHSIZE * i + 11, DPBLOCK >> 8); 240 | #if (CVSIZE == 0) 241 | SETMEM(DPBASE + DPHSIZE * i + 12, 0x00); 242 | SETMEM(DPBASE + DPHSIZE * i + 13, 0x00); 243 | #else 244 | SETMEM(DPBASE + DPHSIZE * i + 12, 245 | (CVBASE + CVSIZE * i) & 0xFF); 246 | SETMEM(DPBASE + DPHSIZE * i + 13, 247 | (CVBASE + CVSIZE * i) >> 8); 248 | #endif 249 | SETMEM(DPBASE + DPHSIZE * i + 14, 250 | (ALVBASE + ALVSIZE * i) & 0xFF); 251 | SETMEM(DPBASE + DPHSIZE * i + 15, 252 | (ALVBASE + ALVSIZE * i) >> 8); 253 | } 254 | 255 | /* set up the stack for an 8-level RET to do a system reset */ 256 | SP = STACK; 257 | 258 | for (i = 0; i < 8; i++) 259 | { 260 | /* push reboot entry (CBIOS + 3) onto stack */ 261 | --SP; 262 | SETMEM(SP, (CBIOS + 3) >> 8); 263 | --SP; 264 | SETMEM(SP, (CBIOS + 3) & 0xFF); 265 | } 266 | 267 | /* set up the default disk (A:) and dma address */ 268 | z80->dma = 0x0080; 269 | 270 | /* and all our default drive info */ 271 | z80->track = 0; 272 | z80->sector = 1; 273 | 274 | /* make sure the current file/disk is open */ 275 | B = 0; 276 | C = z80->drive; 277 | seldisc(z80); 278 | 279 | PC = CCP; 280 | } 281 | 282 | static void 283 | boot(z80info *z80) 284 | { 285 | z80->drive = 0; 286 | warmboot(z80); 287 | } 288 | 289 | void 290 | sysreset(z80info *z80) 291 | { 292 | boot(z80); 293 | } 294 | 295 | static void 296 | consstat(z80info *z80) 297 | { 298 | input(z80, 0x01, 0x01, &A); 299 | } 300 | 301 | static void 302 | consin(z80info *z80) 303 | { 304 | input(z80, 0x00, 0x00, &A); 305 | 306 | /* What is this for? It messing up Ctrl-S... 307 | if (A == CNTL('S')) 308 | input(z80, 0x00, 0x00, &A); 309 | */ 310 | } 311 | 312 | static void 313 | consout(z80info *z80) 314 | { 315 | output(z80, 0x00, 0x00, C & 0x7F); 316 | } 317 | 318 | /* list character in C */ 319 | static void 320 | list(z80info *z80) 321 | { 322 | static FILE *fp = NULL; 323 | 324 | if (fp == NULL) 325 | { 326 | fp = fopen("list", "w"); 327 | 328 | if (fp == NULL) 329 | return; 330 | } 331 | 332 | /* close up on EOF */ 333 | if (C == CNTL('D') || C == '\0') 334 | { 335 | fclose(fp); 336 | fp = NULL; 337 | return; 338 | } 339 | 340 | putc(C, fp); 341 | } 342 | 343 | /* punch character in C */ 344 | static void 345 | punch(z80info *z80) 346 | { 347 | (void)z80; 348 | } 349 | 350 | /* return reader char in A, ^Z is EOF */ 351 | static void 352 | reader(z80info *z80) 353 | { 354 | (void)z80; 355 | A = CNTL('Z'); 356 | } 357 | 358 | static void 359 | home(z80info *z80) 360 | { 361 | z80->track = 0; 362 | z80->sector = 1; 363 | } 364 | 365 | /* Open disk image */ 366 | 367 | static void 368 | realizedisk(z80info *z80) 369 | { 370 | int drive = z80->drive; 371 | char drivestr[80]; 372 | 373 | strcpy(drivestr, drive < NUMHDISCS ? "A-Hdrive" : "A-drive"); 374 | drivestr[0] += drive; /* set the 1st letter to the drive name */ 375 | 376 | if (z80->drives[drive] == NULL) 377 | { 378 | struct stat statbuf; 379 | long secs; 380 | FILE *fp; 381 | 382 | fp = fopen(drivestr, "rb+"); 383 | 384 | if (fp == NULL) 385 | fp = fopen(drivestr, "wb+"); 386 | 387 | if (fp == NULL) 388 | { 389 | fprintf(stderr, "seldisc(): Cannot open file '%s'!\r\n", 390 | drivestr); 391 | return; 392 | } 393 | 394 | if (stat(drivestr, &statbuf) < 0) 395 | { 396 | fprintf(stderr, "seldisc(): Cannot stat file '%s'!\r\n", 397 | drivestr); 398 | fclose(fp); 399 | return; 400 | } 401 | 402 | secs = statbuf.st_size / SECTORSIZE; 403 | 404 | if (secs == 0) 405 | { 406 | char buf[SECTORSIZE]; 407 | memset(buf, 0xE5, SECTORSIZE); 408 | 409 | if (fwrite(buf, 1, SECTORSIZE, fp) != SECTORSIZE) 410 | { 411 | fprintf(stderr, "seldisc(): Cannot create file '%s'!\r\n", 412 | drivestr); 413 | 414 | fclose(fp); 415 | return; 416 | } 417 | 418 | secs = 1; 419 | } 420 | 421 | /* printf(stderr,"\r\nOpen %s on drive %d\n", drivestr, drive); */ 422 | 423 | z80->drives[drive] = fp; 424 | z80->drivelen[drive] = secs * SECTORSIZE; 425 | } 426 | } 427 | 428 | static void 429 | seldisc(z80info *z80) 430 | { 431 | H = 0; 432 | L = 0; 433 | 434 | if (C >= MAXDISCS) 435 | { 436 | fprintf(stderr, "seldisc(): Attempt to open bogus drive %d\r\n", 437 | C); 438 | return; 439 | } 440 | 441 | z80->drive = C; 442 | 443 | if (z80->drive < NUMHDISCS) 444 | { 445 | L = (HDPBASE + DPHSIZE * C) & 0xFF; 446 | H = (HDPBASE + DPHSIZE * C) >> 8; 447 | } 448 | else 449 | { 450 | L = (DPBASE + DPHSIZE * C) & 0xFF; 451 | H = (DPBASE + DPHSIZE * C) >> 8; 452 | } 453 | 454 | home(z80); 455 | } 456 | 457 | static void 458 | settrack(z80info *z80) 459 | { 460 | int tracks = (z80->drive < NUMHDISCS) ? 461 | HDTRACKSPERDISC : TRACKSPERDISC; 462 | 463 | z80->track = (B << 8) + C; 464 | 465 | if (z80->track < RESERVEDTRACKS || z80->track >= tracks) 466 | fprintf(stderr, "settrack(): bogus track %d!\r\n", 467 | z80->track); 468 | } 469 | 470 | static void 471 | setsector(z80info *z80) 472 | { 473 | int sectors = (z80->drive < NUMHDISCS) ? 474 | HDSECTORSPERTRACK : SECTORSPERTRACK; 475 | 476 | z80->sector = (B << 8) + C; 477 | 478 | if (z80->sector < SECTOROFFSET || z80->sector > sectors) 479 | fprintf(stderr, "setsector(): bogus sector %d!\r\n", 480 | z80->sector); 481 | } 482 | 483 | static void 484 | setdma(z80info *z80) 485 | { 486 | z80->dma = (B << 8) + C; 487 | } 488 | 489 | 490 | static void 491 | rdsector(z80info *z80) 492 | { 493 | int n; 494 | int drive = z80->drive; 495 | int sectors = (drive < NUMHDISCS) ? HDSECTORSPERTRACK : SECTORSPERTRACK; 496 | long offset = SECTORSIZE * ((long)z80->sector - SECTOROFFSET + 497 | sectors * ((long)z80->track - TRACKOFFSET)); 498 | FILE *fp; 499 | long len; 500 | realizedisk(z80); 501 | fp = z80->drives[drive]; 502 | len = z80->drivelen[drive]; 503 | 504 | if (fp == NULL) 505 | { 506 | fprintf(stderr, "rdsector(): file/drive %d not open!\r\n", 507 | drive); 508 | A = 1; 509 | return; 510 | } 511 | 512 | if (len && offset >= len) 513 | { 514 | memset(&(z80->mem[z80->dma]), 0xE5, SECTORSIZE); 515 | A = 0; 516 | return; 517 | } 518 | 519 | if (fseek(fp, offset, SEEK_SET) != 0) 520 | { 521 | fprintf(stderr, "rdsector(): fseek failure offset=0x%lX!\r\n", 522 | offset); 523 | A = 1; 524 | return; 525 | } 526 | 527 | n = fread(&(z80->mem[z80->dma]), 1, SECTORSIZE, fp); 528 | 529 | if (n != SECTORSIZE) 530 | { 531 | fprintf(stderr, "rdsector(): read failure %d!\r\n", n); 532 | A = 1; 533 | } 534 | else 535 | A = 0; 536 | } 537 | 538 | 539 | static void 540 | wrsector(z80info *z80) 541 | { 542 | int drive = z80->drive; 543 | int sectors = (drive < NUMHDISCS) ? HDSECTORSPERTRACK : SECTORSPERTRACK; 544 | long offset = SECTORSIZE * ((long)z80->sector - SECTOROFFSET + 545 | sectors * ((long)z80->track - TRACKOFFSET)); 546 | FILE *fp; 547 | long len; 548 | realizedisk(z80); 549 | fp = z80->drives[drive]; 550 | len = z80->drivelen[drive]; 551 | 552 | if (fp == NULL) 553 | { 554 | fprintf(stderr, "wrsector(): file/drive %d not open!\r\n", 555 | drive); 556 | A = 1; 557 | return; 558 | } 559 | 560 | if (len && offset > len) 561 | { 562 | char buf[SECTORSIZE]; 563 | 564 | if (fseek(fp, len, SEEK_SET) != 0) 565 | { 566 | fprintf(stderr, "wrsector(): fseek failure offset=0x%lX!\r\n", 567 | len); 568 | A = 1; 569 | return; 570 | } 571 | 572 | memset(buf, 0xE5, SECTORSIZE); 573 | 574 | while (offset > len) 575 | { 576 | if (fwrite(buf, 1, SECTORSIZE, fp) != SECTORSIZE) 577 | { 578 | fprintf(stderr, "wrsector(): write failure!\r\n"); 579 | A = 1; 580 | return; 581 | } 582 | 583 | len += SECTORSIZE; 584 | z80->drivelen[drive] = len; 585 | } 586 | } 587 | 588 | if (fseek(fp, offset, SEEK_SET) != 0) 589 | { 590 | fprintf(stderr, "wrsector(): fseek failure offset=0x%lX!\r\n", 591 | offset); 592 | A = 1; 593 | return; 594 | } 595 | 596 | if (fwrite(&(z80->mem[z80->dma]), 1, SECTORSIZE, fp) != SECTORSIZE) 597 | { 598 | fprintf(stderr, "wrsector(): write failure!\r\n"); 599 | A = 1; 600 | } 601 | else 602 | { 603 | A = 0; 604 | 605 | if (offset + SECTORSIZE > len) 606 | z80->drivelen[drive] = offset + SECTORSIZE; 607 | } 608 | } 609 | 610 | static void 611 | secttran(z80info *z80) 612 | { 613 | if (z80->drive < NUMHDISCS) 614 | { 615 | /* simple sector translation for hard disc */ 616 | HL = BC + 1; 617 | 618 | if (BC >= HDSECTORSPERTRACK) 619 | fprintf(stderr, "secttran(): bogus sector %d!\r\n", BC); 620 | } 621 | else 622 | { 623 | /* we do not need to use DE to find our translation table */ 624 | HL = sectorxlat[BC]; 625 | 626 | if (BC >= SECTORSPERTRACK) 627 | fprintf(stderr, "secttran(): bogus sector %d!\r\n", BC); 628 | } 629 | } 630 | 631 | static void 632 | liststat(z80info *z80) 633 | { 634 | A = 0xFF; 635 | } 636 | 637 | /* These two routines read and write ints at arbitrary aligned addrs. 638 | * The values are stored in the z80 in little-endian order regardless 639 | * of the byte-order on the host. 640 | */ 641 | static int 642 | addr2int(unsigned char *addr) 643 | { 644 | unsigned char *a = (unsigned char*)addr; 645 | unsigned int t; 646 | 647 | t = a[0] | (a[1] << 8) | (a[2] << 16) | (a[3] << 24); 648 | return (int)t; 649 | } 650 | 651 | static void 652 | int2addr(unsigned char *addr, int val) 653 | { 654 | unsigned char *a = (unsigned char*)addr; 655 | unsigned int t = (unsigned int)val; 656 | 657 | a[0] = t & 0xFF; 658 | a[1] = (t >> 8) & 0xFF; 659 | a[2] = (t >> 16) & 0xFF; 660 | a[3] = (t >> 24) & 0xFF; 661 | } 662 | 663 | /* Allocate file pointers - index is stored in DE */ 664 | 665 | #define CPM_FILES 4 666 | 667 | FILE *cpm_file[CPM_FILES]; 668 | 669 | int cpm_file_alloc(FILE *f) 670 | { 671 | int x; 672 | for (x = 0; x != CPM_FILES; ++x) 673 | if (!cpm_file[x]) { 674 | cpm_file[x] = f; 675 | return x; 676 | } 677 | return -1; 678 | } 679 | 680 | FILE *cpm_file_get(int idx) 681 | { 682 | if (idx < 0 || idx > CPM_FILES) 683 | return 0; 684 | else 685 | return cpm_file[idx]; 686 | } 687 | 688 | int cpm_file_free(int x) 689 | { 690 | if (x >= 0 && x < CPM_FILES && cpm_file[x]) { 691 | int rtn = fclose(cpm_file[x]); 692 | cpm_file[x] = 0; 693 | return rtn; 694 | } else { 695 | return -1; 696 | } 697 | } 698 | 699 | /* DE points to a CP/M FCB. 700 | On return, A contains 0 if all went well, 0xFF otherwise. 701 | The algorithm uses the FCB to store info about the UNIX file. 702 | */ 703 | static void 704 | openunix(z80info *z80) 705 | { 706 | char filename[20], *fp; 707 | byte *cp; 708 | int i; 709 | FILE *fd; 710 | int fd_no; 711 | 712 | cp = &(z80->mem[DE + 1]); 713 | fp = filename; 714 | 715 | for (i = 0; (*cp != ' ') && (i < 8); i++) 716 | *fp++ = tolower(*cp++); 717 | 718 | cp = &(z80->mem[DE + 9]); 719 | 720 | if (*cp != ' ') 721 | { 722 | *fp++ = '.'; 723 | 724 | for (i = 0; (*cp != ' ') && (i < 3); i++) 725 | *fp++ = tolower(*cp++); 726 | } 727 | 728 | *fp = 0; 729 | A = 0xFF; 730 | 731 | /* if file is not readable, try opening it read-only */ 732 | if ((fd = fopen(filename, "rb+")) == NULL) 733 | if ((fd = fopen(filename, "rb")) == NULL) 734 | return; 735 | 736 | fd_no = cpm_file_alloc(fd); 737 | if (fd_no != -1) 738 | A = 0; 739 | 740 | int2addr(&z80->mem[DE + FDOFFSET], fd_no); 741 | int2addr(&z80->mem[DE + BLKOFFSET], 0); 742 | int2addr(&z80->mem[DE + SZOFFSET], 0); 743 | } 744 | 745 | 746 | /* DE points to a CP/M FCB. 747 | On return, A contains 0 if all went well, 0xFF otherwise. 748 | The algorithm uses the FCB to store info about the UNIX file. 749 | */ 750 | static void 751 | createunix(z80info *z80) 752 | { 753 | char filename[20], *fp; 754 | byte *cp; 755 | int i; 756 | FILE *fd; 757 | int fd_no; 758 | 759 | cp = &(z80->mem[DE + 1]); 760 | fp = filename; 761 | 762 | for (i = 0; (*cp != ' ') && (i < 8); i++) 763 | *fp++ = tolower(*cp++); 764 | 765 | cp = &(z80->mem[DE + 9]); 766 | 767 | if (*cp != ' ') 768 | { 769 | *fp++ = '.'; 770 | 771 | for (i = 0; (*cp != ' ') && (i < 3); i++) 772 | *fp++ = tolower(*cp++); 773 | } 774 | 775 | *fp = 0; 776 | A = 0xFF; 777 | 778 | if ((fd = fopen(filename, "wb+")) == NULL) 779 | return; 780 | 781 | fd_no = cpm_file_alloc(fd); 782 | if (fd_no != -1) 783 | A = 0; 784 | 785 | int2addr(&z80->mem[DE + FDOFFSET], fd_no); 786 | int2addr(&z80->mem[DE + BLKOFFSET], 0); 787 | int2addr(&z80->mem[DE + SZOFFSET], 0); 788 | } 789 | 790 | 791 | /* DE points to a CP/M FCB. 792 | On return, A contains 0 if all went well, 0xFF otherwise. 793 | The algorithm uses the FCB to store info about the UNIX file. 794 | */ 795 | static void 796 | rdunix(z80info *z80) 797 | { 798 | byte *cp; 799 | int i, blk, size; 800 | FILE *fd; 801 | int fd_no; 802 | 803 | cp = &(z80->mem[z80->dma]); 804 | fd = cpm_file_get((fd_no = addr2int(&z80->mem[DE + FDOFFSET]))); 805 | blk = addr2int(&z80->mem[DE + BLKOFFSET]); 806 | size = addr2int(&z80->mem[DE + SZOFFSET]); 807 | 808 | A = 0xFF; 809 | 810 | if (!fd) 811 | return; 812 | 813 | if (fseek(fd, (long)blk << 7, SEEK_SET) != 0) 814 | return; 815 | 816 | i = fread(cp, 1, SECTORSIZE, fd); 817 | size = i; 818 | 819 | if (i == 0) 820 | return; 821 | 822 | for (; i < SECTORSIZE; i++) 823 | cp[i] = CNTL('Z'); 824 | 825 | A = 0; 826 | blk += 1; 827 | 828 | int2addr(&z80->mem[DE + FDOFFSET], fd_no); 829 | int2addr(&z80->mem[DE + BLKOFFSET], blk); 830 | int2addr(&z80->mem[DE + SZOFFSET], size); 831 | } 832 | 833 | 834 | /* DE points to a CP/M FCB. 835 | On return, A contains 0 if all went well, 0xFF otherwise. 836 | The algorithm uses the FCB to store info about the UNIX file. 837 | */ 838 | static void 839 | wrunix(z80info *z80) 840 | { 841 | byte *cp; 842 | int i, blk, size; 843 | FILE *fd; 844 | int fd_no; 845 | 846 | cp = &(z80->mem[z80->dma]); 847 | fd = cpm_file_get((fd_no = addr2int(&z80->mem[DE + FDOFFSET]))); 848 | blk = addr2int(&z80->mem[DE + BLKOFFSET]); 849 | size = addr2int(&z80->mem[DE + SZOFFSET]); 850 | 851 | A = 0xFF; 852 | 853 | if (fseek(fd, (long)blk << 7, SEEK_SET) != 0) 854 | return; 855 | 856 | i = fwrite(cp, 1, size = SECTORSIZE, fd); 857 | 858 | if (i != SECTORSIZE) 859 | return; 860 | 861 | A = 0; 862 | blk += 1; 863 | 864 | int2addr(&z80->mem[DE + FDOFFSET], fd_no); 865 | int2addr(&z80->mem[DE + BLKOFFSET], blk); 866 | int2addr(&z80->mem[DE + SZOFFSET], size); 867 | } 868 | 869 | 870 | /* DE points to a CP/M FCB. 871 | On return, A contains 0 if all went well, 0xFF otherwise. 872 | */ 873 | static void 874 | closeunix(z80info *z80) 875 | { 876 | int fd_no; 877 | 878 | fd_no = addr2int(&z80->mem[DE + FDOFFSET]); 879 | A = 0xFF; 880 | 881 | if (cpm_file_free(fd_no)) 882 | return; 883 | 884 | A = 0; 885 | } 886 | 887 | /* clean up and quit - never returns */ 888 | void 889 | finish(z80info *z80) 890 | { 891 | (void)z80; 892 | resetterm(); 893 | exit(0); 894 | } 895 | 896 | /* Get/set the time - although only the get-time part is implemented. 897 | If C==0, then get the time, else of C==0xFF, then set the time. 898 | HL returns a pointer to our time table: 899 | HL+0:DATE LSB SINCE 1,1,1978 900 | HL+1:DATE MSB 901 | HL+2:HOURS (BCD) 902 | HL+3:MINUTES (BCD) 903 | HL+4:SECONDS (BCD) 904 | */ 905 | static void 906 | dotime(z80info *z80) 907 | { 908 | time_t now; 909 | struct tm *t; 910 | word days; 911 | int y; 912 | 913 | if (C != 0) /* do not support setting the time yet */ 914 | return; 915 | 916 | time(&now); 917 | t = localtime(&now); 918 | 919 | /* days since Jan 1, 1978 + one since tm_yday starts at zero */ 920 | days = (t->tm_year - 78) * 365 + t->tm_yday + 1; 921 | 922 | /* add in the number of days for the leap years - dumb but accurate */ 923 | for (y = 78; y < t->tm_year; y++) 924 | if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) 925 | days++; 926 | 927 | HL = TIMEBUF; 928 | SETMEM(HL + 0, days & 0xFF); 929 | SETMEM(HL + 1, days >> 8); 930 | SETMEM(HL + 2, ((t->tm_hour / 10) << 4) + (t->tm_hour % 10)); 931 | SETMEM(HL + 3, ((t->tm_min / 10) << 4) + (t->tm_min % 10)); 932 | SETMEM(HL + 4, ((t->tm_sec / 10) << 4) + (t->tm_sec % 10)); 933 | } 934 | 935 | void 936 | bios(z80info *z80, unsigned int fn) 937 | { 938 | static void (*bioscall[])(z80info *z80) = 939 | { 940 | boot, /* 0 */ 941 | warmboot, /* 1 */ 942 | consstat, /* 2 */ 943 | consin, /* 3 */ 944 | consout, /* 4 */ 945 | list, /* 5 */ 946 | punch, /* 6 */ 947 | reader, /* 7 */ 948 | home, /* 8 */ 949 | seldisc, /* 9 */ 950 | settrack, /* 10 */ 951 | setsector, /* 11 */ 952 | setdma, /* 12 */ 953 | rdsector, /* 13 */ 954 | wrsector, /* 14 */ 955 | liststat, /* 15 */ 956 | secttran, /* 16 */ 957 | openunix, /* 17 */ 958 | createunix, /* 18 */ 959 | rdunix, /* 19 */ 960 | wrunix, /* 20 */ 961 | closeunix, /* 21 */ 962 | finish, /* 22 */ 963 | dotime /* 23 */ 964 | }; 965 | 966 | if (fn >= sizeof bioscall / sizeof *bioscall) 967 | { 968 | fprintf(stderr, "Illegal BIOS call %d\r\n", fn); 969 | return; 970 | } 971 | 972 | bioscall[fn](z80); 973 | /* let z80 handle return */ 974 | } 975 | -------------------------------------------------------------------------------- /bye.mac: -------------------------------------------------------------------------------- 1 | ; EXIT CP/M - return to Unix 2 | ; 3 | .Z80 4 | CBIOS EQU 0EA00H 5 | FINISH EQU CBIOS+42H 6 | 7 | CSEG 8 | ENTRY START 9 | START: 10 | CALL FINISH 11 | END 12 | -------------------------------------------------------------------------------- /cpm.c: -------------------------------------------------------------------------------- 1 | /* This is an image of P2DOS 2.3 + ZCPR1 and should be OK to redistribute. */ 2 | 3 | unsigned char cpm_array[] = { 4 | 0xC3, 0x5, 0xD5, 0xC3, 0x1, 0xD5, 0x50, 0x4, 0x44, 0x41, 5 | 0x54, 0x45, 0x0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 6 | 0x20, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 7 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 8 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 9 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 10 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 11 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 12 | 0x0, 0x0, 0xC, 0xD4, 0xC, 0xD4, 0x0, 0x0, 0x0, 0x0, 0x0, 13 | 0x0, 0xB6, 0xD5, 0xEA, 0xD8, 0xE2, 0xD5, 0x14, 0x0, 0xB6, 0xD5, 14 | 0xB6, 0xD5, 0x59, 0xD6, 0x20, 0x0, 0xC6, 0xD6, 0x63, 0xD5, 0x43, 15 | 0x4F, 0x4D, 0x1, 0x24, 0x24, 0x24, 0x20, 0x20, 0x20, 0x20, 0x20, 16 | 0x53, 0x55, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 17 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 18 | 0x0, 0x0, 0x0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 19 | 0x20, 0x20, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x20, 0x20, 20 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0, 0x0, 0x0, 21 | 0x0, 0x0, 0x16, 0x0, 0x0, 0x44, 0x49, 0x52, 0x20, 0x82, 0xD8, 22 | 0x4C, 0x49, 0x53, 0x54, 0x5B, 0xD9, 0x54, 0x59, 0x50, 0x45, 0x5F, 23 | 0xD9, 0x55, 0x53, 0x45, 0x52, 0xBE, 0xDA, 0x44, 0x46, 0x55, 0x20, 24 | 0xC8, 0xDA, 0x47, 0x4F, 0x20, 0x20, 0xD5, 0xDA, 0x45, 0x52, 0x41, 25 | 0x20, 0x33, 0xD9, 0x53, 0x41, 0x56, 0x45, 0xF8, 0xD9, 0x52, 0x45, 26 | 0x4E, 0x20, 0x7A, 0xDA, 0x47, 0x45, 0x54, 0x20, 0x7F, 0xDB, 0x4A, 27 | 0x55, 0x4D, 0x50, 0xD0, 0xDA, 0xAF, 0x32, 0x7, 0xD4, 0x31, 0x77, 28 | 0xD4, 0xC5, 0x79, 0x1F, 0x1F, 0x1F, 0x1F, 0xE6, 0xF, 0x5F, 0xCD, 29 | 0x45, 0xD6, 0xCD, 0x8, 0xD6, 0x32, 0x2A, 0xD5, 0xC1, 0x79, 0xE6, 30 | 0xF, 0x32, 0x59, 0xD6, 0x28, 0x3, 0xCD, 0xD, 0xD6, 0x11, 0x7A, 31 | 0xD4, 0x3E, 0x0, 0xB7, 0x2F, 0xC4, 0x27, 0xD6, 0x2F, 0x32, 0x2A, 32 | 0xD5, 0x3A, 0x7, 0xD4, 0xB7, 0x20, 0x29, 0x31, 0x77, 0xD4, 0xCD, 33 | 0xA7, 0xD5, 0xCD, 0xFD, 0xD5, 0xC6, 0x41, 0xCD, 0xAE, 0xD5, 0xCD, 34 | 0x43, 0xD6, 0xB7, 0x28, 0x12, 0xFE, 0xA, 0x38, 0x9, 0xD6, 0xA, 35 | 0xF5, 0x3E, 0x31, 0xCD, 0xAE, 0xD5, 0xF1, 0xC6, 0x30, 0xCD, 0xAE, 36 | 0xD5, 0xCD, 0x67, 0xD6, 0xCD, 0x1, 0xD6, 0xCD, 0xFD, 0xD5, 0x32, 37 | 0x59, 0xD6, 0xCD, 0xE1, 0xD7, 0xC4, 0xF0, 0xD6, 0x11, 0x88, 0xD5, 38 | 0xD5, 0x3A, 0xD8, 0xD7, 0xB7, 0xC2, 0xDA, 0xDA, 0xCD, 0x60, 0xD8, 39 | 0xC2, 0xDA, 0xDA, 0x7E, 0x23, 0x66, 0x6F, 0xE9, 0xCD, 0xCD, 0xD7, 40 | 0xCD, 0xE1, 0xD7, 0x3A, 0x9C, 0xD4, 0xD6, 0x20, 0x21, 0xD8, 0xD7, 41 | 0xB6, 0xC2, 0xF0, 0xD6, 0x18, 0x9E, 0xCD, 0xE5, 0xD5, 0x4E, 0x6F, 42 | 0x20, 0x46, 0x69, 0x6C, 0xE5, 0xC9, 0x3E, 0xD, 0xCD, 0xAE, 0xD5, 43 | 0x3E, 0xA, 0xC5, 0xE, 0x2, 0x5F, 0xE5, 0xCD, 0x5, 0x0, 0xE1, 44 | 0xC1, 0xC9, 0xE, 0x1, 0xCD, 0xDE, 0xD5, 0xC3, 0x5E, 0xD6, 0xF5, 45 | 0x3E, 0x0, 0xB7, 0x28, 0x6, 0xF1, 0xC5, 0xE, 0x5, 0x18, 0xE4, 46 | 0xF1, 0xF5, 0xCD, 0xAE, 0xD5, 0xF1, 0xFE, 0xA, 0xCA, 0xDF, 0xD9, 47 | 0xC9, 0x11, 0x9B, 0xD4, 0xE, 0x14, 0xC5, 0xCD, 0x5, 0x0, 0xC1, 48 | 0xB7, 0xC9, 0xF5, 0xCD, 0xA7, 0xD5, 0xF1, 0xE3, 0xF5, 0xCD, 0xF2, 49 | 0xD5, 0xF1, 0xE3, 0xC9, 0x7E, 0xCD, 0xAE, 0xD5, 0x7E, 0x23, 0xB7, 50 | 0xC8, 0xF8, 0x18, 0xF5, 0xE, 0x19, 0x18, 0x9, 0x11, 0x80, 0x0, 51 | 0xE, 0x1A, 0x18, 0x2, 0xE, 0xD, 0xC3, 0x5, 0x0, 0x5F, 0xE, 52 | 0xE, 0x18, 0xF8, 0xAF, 0x32, 0xBB, 0xD4, 0x11, 0x9B, 0xD4, 0xE, 53 | 0xF, 0xCD, 0x5, 0x0, 0x3C, 0xC9, 0xE, 0x10, 0x18, 0xF7, 0x11, 54 | 0x9B, 0xD4, 0xE, 0x11, 0x18, 0xF0, 0xE, 0x12, 0x18, 0xEC, 0x21, 55 | 0x2A, 0xD5, 0x7E, 0xB7, 0xC8, 0x36, 0x0, 0x11, 0x7A, 0xD4, 0xE, 56 | 0x13, 0x18, 0xCC, 0x3E, 0x0, 0x5F, 0x18, 0x2, 0x1E, 0xFF, 0xE, 57 | 0x20, 0x18, 0xC1, 0xCD, 0x43, 0xD6, 0x87, 0x87, 0x87, 0x87, 0x21, 58 | 0x59, 0xD6, 0xB6, 0x32, 0x4, 0x0, 0xC9, 0x3E, 0x0, 0x32, 0x4, 59 | 0x0, 0xC9, 0xFE, 0x61, 0xD8, 0xFE, 0x7B, 0xD0, 0xE6, 0x5F, 0xC9, 60 | 0x3A, 0x2A, 0xD5, 0xB7, 0x28, 0x46, 0x11, 0x7A, 0xD4, 0xD5, 0xCD, 61 | 0x19, 0xD6, 0xD1, 0x28, 0x3C, 0x3A, 0x89, 0xD4, 0x3D, 0x32, 0x9A, 62 | 0xD4, 0xCD, 0xDC, 0xD5, 0x20, 0x30, 0x11, 0x7, 0xD4, 0x21, 0x80, 63 | 0x0, 0x1, 0x50, 0x0, 0xED, 0xB0, 0x21, 0x88, 0xD4, 0x36, 0x0, 64 | 0x23, 0x35, 0x11, 0x7A, 0xD4, 0xCD, 0x20, 0xD6, 0x28, 0x16, 0x3E, 65 | 0x24, 0xCD, 0xAE, 0xD5, 0x21, 0x8, 0xD4, 0xCD, 0xF2, 0xD5, 0xCD, 66 | 0xDF, 0xD6, 0x28, 0x1C, 0xCD, 0x2F, 0xD6, 0xC3, 0x3A, 0xD5, 0xCD, 67 | 0x2F, 0xD6, 0xCD, 0x49, 0xD6, 0x3E, 0x3E, 0xCD, 0xAE, 0xD5, 0xE, 68 | 0xA, 0x11, 0x6, 0xD4, 0xCD, 0x5, 0x0, 0xCD, 0x58, 0xD6, 0x21, 69 | 0x7, 0xD4, 0x46, 0x4, 0x23, 0x7E, 0xCD, 0x5E, 0xD6, 0x77, 0x10, 70 | 0xF8, 0x36, 0x0, 0x21, 0x8, 0xD4, 0x22, 0x59, 0xD4, 0xC9, 0xD5, 71 | 0xE, 0xB, 0xCD, 0xDE, 0xD5, 0xC4, 0xB9, 0xD5, 0xD1, 0xC9, 0xCD, 72 | 0x3C, 0xD7, 0xFE, 0x10, 0xD8, 0xCD, 0xA7, 0xD5, 0x2A, 0x5B, 0xD4, 73 | 0x7E, 0xFE, 0x21, 0x38, 0x8, 0xE5, 0xCD, 0xAE, 0xD5, 0xE1, 0x23, 74 | 0x18, 0xF3, 0xCD, 0xEA, 0xD5, 0xBF, 0xCD, 0x2F, 0xD6, 0xC3, 0x3A, 75 | 0xD5, 0x1A, 0xB7, 0xC8, 0xFE, 0x20, 0x38, 0xDC, 0xC8, 0xFE, 0x3D, 76 | 0xC8, 0xFE, 0x5F, 0xC8, 0xFE, 0x2E, 0xC8, 0xFE, 0x3A, 0xC8, 0xFE, 77 | 0x3B, 0xC8, 0xFE, 0x3C, 0xC8, 0xFE, 0x3E, 0xC9, 0xED, 0x5B, 0x59, 78 | 0xD4, 0x1A, 0xB7, 0xC8, 0xFE, 0x20, 0xC0, 0x13, 0x18, 0xF7, 0x85, 79 | 0x6F, 0xD0, 0x24, 0xC9, 0xCD, 0xE1, 0xD7, 0x21, 0xA6, 0xD4, 0x6, 80 | 0xB, 0x7E, 0x2B, 0xFE, 0x20, 0x20, 0x4, 0x10, 0xF8, 0x18, 0x4, 81 | 0xFE, 0x48, 0x28, 0x2B, 0x21, 0x9C, 0xD4, 0x1, 0x0, 0x11, 0x7E, 82 | 0xFE, 0x20, 0x28, 0x18, 0x23, 0xD6, 0x30, 0xFE, 0xA, 0x30, 0x13, 83 | 0x57, 0x79, 0x7, 0x7, 0x7, 0x81, 0x38, 0xB, 0x81, 0x38, 0x8, 84 | 0x82, 0x38, 0x5, 0x4F, 0x10, 0xE3, 0x79, 0xC9, 0xC3, 0xF0, 0xD6, 85 | 0xCD, 0xE1, 0xD7, 0x21, 0x9C, 0xD4, 0x11, 0x0, 0x0, 0x6, 0xB, 86 | 0x7E, 0xFE, 0x20, 0x28, 0x2D, 0xFE, 0x48, 0x28, 0x29, 0xD6, 0x30, 87 | 0x38, 0xE5, 0xFE, 0xA, 0x38, 0x6, 0xD6, 0x7, 0xFE, 0x10, 0x30, 88 | 0xDB, 0x23, 0x4F, 0x7A, 0x7, 0x7, 0x7, 0x7, 0xE6, 0xF0, 0x57, 89 | 0x7B, 0x7, 0x7, 0x7, 0x7, 0x5F, 0xE6, 0xF, 0xB2, 0x57, 0x7B, 90 | 0xE6, 0xF0, 0xB1, 0x5F, 0x10, 0xCE, 0xEB, 0x7D, 0xC9, 0x21, 0x80, 91 | 0x0, 0x81, 0xCD, 0x37, 0xD7, 0x7E, 0xC9, 0xAF, 0x32, 0x9B, 0xD4, 92 | 0xCD, 0xD7, 0xD7, 0xC8, 0x18, 0x7, 0xCD, 0xD7, 0xD7, 0xC8, 0x3A, 93 | 0x59, 0xD6, 0xC3, 0xD, 0xD6, 0x3E, 0x0, 0xB7, 0xC8, 0x3D, 0x21, 94 | 0x59, 0xD6, 0xBE, 0xC9, 0x21, 0x9B, 0xD4, 0xAF, 0x32, 0xD8, 0xD7, 95 | 0xCD, 0x2A, 0xD7, 0xED, 0x53, 0x5B, 0xD4, 0x1A, 0xB7, 0x28, 0xA, 96 | 0xDE, 0x40, 0x47, 0x13, 0x1A, 0xFE, 0x3A, 0x28, 0x7, 0x1B, 0x3A, 97 | 0x59, 0xD6, 0x77, 0x18, 0x6, 0x78, 0x32, 0xD8, 0xD7, 0x70, 0x13, 98 | 0xAF, 0x32, 0xBE, 0xD4, 0x6, 0x8, 0xCD, 0x31, 0xD8, 0x6, 0x3, 99 | 0xFE, 0x2E, 0x20, 0x6, 0x13, 0xCD, 0x31, 0xD8, 0x18, 0x3, 0xCD, 100 | 0x52, 0xD8, 0x6, 0x4, 0x23, 0x36, 0x0, 0x10, 0xFB, 0xED, 0x53, 101 | 0x59, 0xD4, 0x3A, 0xBE, 0xD4, 0xB7, 0xC9, 0xCD, 0xD, 0xD7, 0x28, 102 | 0x1C, 0x23, 0xFE, 0x2A, 0x20, 0x7, 0x36, 0x3F, 0xCD, 0x58, 0xD8, 103 | 0x18, 0x7, 0x77, 0x13, 0xFE, 0x3F, 0xCC, 0x58, 0xD8, 0x10, 0xE6, 104 | 0xCD, 0xD, 0xD7, 0xC8, 0x13, 0x18, 0xF9, 0x23, 0x36, 0x20, 0x10, 105 | 0xFB, 0xC9, 0x3A, 0xBE, 0xD4, 0x3C, 0x32, 0xBE, 0xD4, 0xC9, 0x21, 106 | 0xBF, 0xD4, 0xE, 0xB, 0x11, 0x9C, 0xD4, 0x6, 0x4, 0x1A, 0xBE, 107 | 0x20, 0xA, 0x13, 0x23, 0x10, 0xF8, 0x1A, 0xFE, 0x20, 0x20, 0x4, 108 | 0xC9, 0x23, 0x10, 0xFD, 0x23, 0x23, 0xD, 0x20, 0xE5, 0xC, 0xC9, 109 | 0x3E, 0x80, 0xF5, 0xCD, 0xE1, 0xD7, 0xCD, 0xC3, 0xD7, 0x21, 0x9C, 110 | 0xD4, 0x7E, 0xFE, 0x20, 0xCC, 0x2B, 0xD9, 0xCD, 0x2A, 0xD7, 0x6, 111 | 0x0, 0x28, 0x16, 0xFE, 0x41, 0x28, 0x6, 0xFE, 0x53, 0x20, 0xE, 112 | 0x6, 0x80, 0x13, 0xED, 0x53, 0x59, 0xD4, 0xFE, 0x53, 0x28, 0x3, 113 | 0xF1, 0xAF, 0xF5, 0xF1, 0x57, 0x1E, 0x0, 0xD5, 0x78, 0x32, 0xD2, 114 | 0xD8, 0xCD, 0x24, 0xD6, 0xCC, 0x9C, 0xD5, 0x28, 0x67, 0x3D, 0xF, 115 | 0xF, 0xF, 0xE6, 0x60, 0x4F, 0x3E, 0xA, 0xCD, 0xBA, 0xD7, 0xD1, 116 | 0xD5, 0xA2, 0xFE, 0x0, 0x20, 0x4A, 0xD1, 0x7B, 0x1C, 0xD5, 0xE6, 117 | 0x3, 0xF5, 0x20, 0x5, 0xCD, 0xA7, 0xD5, 0x18, 0x8, 0xCD, 0xEA, 118 | 0xD5, 0x20, 0x20, 0x7C, 0x20, 0xA0, 0x6, 0x1, 0x78, 0xCD, 0xBA, 119 | 0xD7, 0xE6, 0x7F, 0xFE, 0x20, 0x20, 0x13, 0xF1, 0xF5, 0xFE, 0x3, 120 | 0x20, 0xB, 0x3E, 0x9, 0xCD, 0xBA, 0xD7, 0xE6, 0x7F, 0xFE, 0x20, 121 | 0x28, 0x16, 0x3E, 0x20, 0xCD, 0xAE, 0xD5, 0x4, 0x78, 0xFE, 0xC, 122 | 0x30, 0xB, 0xFE, 0x9, 0x20, 0xD6, 0x3E, 0x2E, 0xCD, 0xAE, 0xD5, 123 | 0x18, 0xCF, 0xF1, 0xCD, 0xDF, 0xD6, 0x20, 0x5, 0xCD, 0x2B, 0xD6, 124 | 0x18, 0x97, 0xD1, 0xC9, 0x6, 0xB, 0x36, 0x3F, 0x23, 0x10, 0xFB, 125 | 0xC9, 0xCD, 0xE1, 0xD7, 0xFE, 0xB, 0x20, 0x12, 0xCD, 0xE5, 0xD5, 126 | 0x41, 0x6C, 0x6C, 0xBF, 0xCD, 0xB9, 0xD5, 0xFE, 0x59, 0xC2, 0x3A, 127 | 0xD5, 0xCD, 0xA7, 0xD5, 0xCD, 0xC3, 0xD7, 0xAF, 0x47, 0xCD, 0xB2, 128 | 0xD8, 0x11, 0x9B, 0xD4, 0xCD, 0x3A, 0xD6, 0xC9, 0x3E, 0xFF, 0x18, 129 | 0x1, 0xAF, 0x32, 0xC3, 0xD5, 0xCD, 0xE1, 0xD7, 0xC2, 0xF0, 0xD6, 130 | 0xCD, 0x2A, 0xD7, 0x32, 0xE9, 0xD9, 0x28, 0x5, 0x13, 0xEB, 0x22, 131 | 0x59, 0xD4, 0xCD, 0xC3, 0xD7, 0xCD, 0x12, 0xD6, 0xCA, 0xDC, 0xD9, 132 | 0xCD, 0xA7, 0xD5, 0x3E, 0x17, 0x32, 0xBC, 0xD4, 0x21, 0xBD, 0xD4, 133 | 0x36, 0xFF, 0x6, 0x0, 0x21, 0xBD, 0xD4, 0x7E, 0xFE, 0x80, 0x38, 134 | 0x9, 0xE5, 0xCD, 0xD9, 0xD5, 0xE1, 0x20, 0x3D, 0xAF, 0x77, 0x34, 135 | 0x21, 0x80, 0x0, 0xCD, 0x37, 0xD7, 0x7E, 0xE6, 0x7F, 0xFE, 0x1A, 136 | 0xC8, 0xFE, 0xD, 0x28, 0xE, 0xFE, 0xA, 0x28, 0xA, 0xFE, 0x9, 137 | 0x28, 0xD, 0xCD, 0xC1, 0xD5, 0x4, 0x18, 0x12, 0xCD, 0xC1, 0xD5, 138 | 0x6, 0x0, 0x18, 0xB, 0x3E, 0x20, 0xCD, 0xC1, 0xD5, 0x4, 0x78, 139 | 0xE6, 0x7, 0x20, 0xF5, 0xCD, 0xDF, 0xD6, 0x28, 0xB9, 0xFE, 0x3, 140 | 0xC8, 0x18, 0xB4, 0x3D, 0xC8, 0xC3, 0x79, 0xDB, 0xE5, 0x21, 0xBC, 141 | 0xD4, 0x35, 0x20, 0x10, 0x36, 0x16, 0x3E, 0x0, 0xFE, 0x50, 0x28, 142 | 0x8, 0xCD, 0xB9, 0xD5, 0xFE, 0x3, 0xCA, 0x88, 0xD5, 0xE1, 0xC9, 143 | 0xCD, 0x3C, 0xD7, 0x6F, 0x26, 0x0, 0xE5, 0xCD, 0x4B, 0xDA, 0xE, 144 | 0x16, 0xCD, 0x1B, 0xD6, 0xE1, 0x28, 0x3A, 0xAF, 0x32, 0xBB, 0xD4, 145 | 0xCD, 0x2A, 0xD7, 0x13, 0xFE, 0x53, 0x28, 0x2, 0x1B, 0x29, 0xED, 146 | 0x53, 0x59, 0xD4, 0x11, 0x0, 0x1, 0x7C, 0xB5, 0x28, 0x18, 0x2B, 147 | 0xE5, 0x21, 0x80, 0x0, 0x19, 0xE5, 0xCD, 0x4, 0xD6, 0x11, 0x9B, 148 | 0xD4, 0xE, 0x15, 0xCD, 0xDE, 0xD5, 0xD1, 0xE1, 0x20, 0xB, 0x18, 149 | 0xE4, 0x11, 0x9B, 0xD4, 0xCD, 0x20, 0xD6, 0x3C, 0x20, 0x3, 0xCD, 150 | 0xDF, 0xDB, 0xCD, 0x1, 0xD6, 0xC9, 0xCD, 0xE1, 0xD7, 0xC2, 0xF0, 151 | 0xD6, 0xCD, 0xC3, 0xD7, 0xCD, 0x24, 0xD6, 0x11, 0x9B, 0xD4, 0xC8, 152 | 0xD5, 0xCD, 0xE5, 0xD5, 0x44, 0x65, 0x6C, 0x65, 0x74, 0x65, 0x20, 153 | 0x46, 0x69, 0x6C, 0x65, 0xBF, 0xCD, 0xB9, 0xD5, 0xD1, 0xFE, 0x59, 154 | 0xC2, 0x88, 0xD5, 0xD5, 0xCD, 0x3A, 0xD6, 0xD1, 0xC9, 0xCD, 0x4B, 155 | 0xDA, 0x3A, 0xD8, 0xD7, 0xF5, 0x21, 0x9B, 0xD4, 0x11, 0xAB, 0xD4, 156 | 0x1, 0x10, 0x0, 0xED, 0xB0, 0xCD, 0x2A, 0xD7, 0xFE, 0x3D, 0x20, 157 | 0x28, 0xEB, 0x23, 0x22, 0x59, 0xD4, 0xCD, 0xE1, 0xD7, 0x20, 0x1E, 158 | 0xF1, 0x47, 0x21, 0xD8, 0xD7, 0x7E, 0xB7, 0x28, 0x4, 0xB8, 0x70, 159 | 0x20, 0x11, 0x70, 0xAF, 0x32, 0x9B, 0xD4, 0x11, 0x9B, 0xD4, 0xE, 160 | 0x17, 0xCD, 0x1B, 0xD6, 0xC0, 0xCD, 0x9C, 0xD5, 0xC3, 0x79, 0xDB, 161 | 0xCD, 0xEA, 0xD6, 0x5F, 0xCD, 0x45, 0xD6, 0xC3, 0x8B, 0xD5, 0xCD, 162 | 0xEA, 0xD6, 0x32, 0xA7, 0xDB, 0x18, 0xF5, 0xCD, 0x7A, 0xD7, 0x18, 163 | 0x3C, 0x21, 0x0, 0x1, 0x18, 0x37, 0x3A, 0x9C, 0xD4, 0xFE, 0x20, 164 | 0x20, 0x14, 0x3A, 0xD8, 0xD7, 0xB7, 0xCA, 0x8B, 0xD5, 0x3D, 0x32, 165 | 0x59, 0xD6, 0xCD, 0x58, 0xD6, 0xCD, 0xD, 0xD6, 0xC3, 0x8B, 0xD5, 166 | 0x3A, 0xA4, 0xD4, 0xFE, 0x20, 0xC2, 0xF0, 0xD6, 0x21, 0x77, 0xD4, 167 | 0x11, 0xA4, 0xD4, 0x1, 0x3, 0x0, 0xED, 0xB0, 0x21, 0x0, 0x1, 168 | 0xE5, 0xCD, 0x89, 0xDB, 0xE1, 0xC0, 0x22, 0x67, 0xDB, 0xCD, 0xCD, 169 | 0xD7, 0xCD, 0xE1, 0xD7, 0x21, 0xD8, 0xD7, 0xE5, 0x7E, 0x32, 0x9B, 170 | 0xD4, 0x21, 0xAB, 0xD4, 0xCD, 0xE4, 0xD7, 0xE1, 0x7E, 0x32, 0xAB, 171 | 0xD4, 0xAF, 0x32, 0xBB, 0xD4, 0x11, 0x5C, 0x0, 0x21, 0x9B, 0xD4, 172 | 0x1, 0x21, 0x0, 0xED, 0xB0, 0x21, 0x8, 0xD4, 0x7E, 0xB7, 0x28, 173 | 0x7, 0xFE, 0x20, 0x28, 0x3, 0x23, 0x18, 0xF5, 0x6, 0x0, 0x11, 174 | 0x81, 0x0, 0x7E, 0x12, 0xB7, 0x28, 0x5, 0x4, 0x23, 0x13, 0x18, 175 | 0xF6, 0x78, 0x32, 0x80, 0x0, 0xCD, 0xA7, 0xD5, 0xCD, 0x1, 0xD6, 176 | 0xCD, 0x49, 0xD6, 0xCD, 0x0, 0x1, 0xCD, 0x1, 0xD6, 0xCD, 0x58, 177 | 0xD6, 0xCD, 0xD, 0xD6, 0xC3, 0x3A, 0xD5, 0xE1, 0xCD, 0x3E, 0xD6, 178 | 0xCD, 0xCD, 0xD7, 0xC3, 0xF0, 0xD6, 0xCD, 0x7A, 0xD7, 0xE5, 0xCD, 179 | 0xE1, 0xD7, 0xE1, 0x20, 0xF3, 0xCD, 0x92, 0xDB, 0xF5, 0xCD, 0x3E, 180 | 0xD6, 0xF1, 0xC9, 0x22, 0xC2, 0xDB, 0xCD, 0x43, 0xD6, 0x32, 0x3F, 181 | 0xD6, 0x32, 0xA9, 0xDB, 0xCD, 0xC3, 0xD7, 0xCD, 0x12, 0xD6, 0x20, 182 | 0x1B, 0x3E, 0x0, 0xFE, 0x0, 0x28, 0x9, 0x32, 0xA9, 0xDB, 0x5F, 183 | 0xCD, 0x45, 0xD6, 0x18, 0xE9, 0x21, 0xD8, 0xD7, 0xAF, 0xB6, 0xC2, 184 | 0x75, 0xDB, 0x36, 0x1, 0x18, 0xDD, 0x21, 0x0, 0x1, 0x3E, 0xD3, 185 | 0xBC, 0x38, 0x16, 0xE5, 0xEB, 0xCD, 0x4, 0xD6, 0x11, 0x9B, 0xD4, 186 | 0xCD, 0xDC, 0xD5, 0xE1, 0x20, 0x6, 0x11, 0x80, 0x0, 0x19, 0x18, 187 | 0xE7, 0x3D, 0xC8, 0xCD, 0xE5, 0xD5, 0x46, 0x75, 0x6C, 0xEC, 0x3E, 188 | 0x1, 0xB7, 0xC9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 189 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 190 | 0x0, 0x0, 0x0, 0x50, 0x32, 0x44, 0x4F, 0x53, 0x23, 0xC3, 0x16, 191 | 0xDC, 0x92, 0xDF, 0x93, 0xDF, 0xAC, 0xDF, 0x98, 0xDF, 0x0, 0x0, 192 | 0x45, 0xEA, 0xFF, 0x79, 0x32, 0x79, 0xE9, 0x21, 0x0, 0x0, 0x22, 193 | 0x7A, 0xE9, 0xAF, 0x32, 0x7C, 0xE9, 0x32, 0x7D, 0xE9, 0xED, 0x73, 194 | 0x8F, 0xE9, 0x31, 0xCF, 0xE9, 0xDD, 0xE5, 0xD5, 0xDD, 0xE1, 0x21, 195 | 0x2A, 0xE9, 0xE5, 0x79, 0xFE, 0xC8, 0xCA, 0x14, 0xE9, 0xFE, 0xC9, 196 | 0xCA, 0x21, 0xE9, 0xFE, 0x29, 0xD0, 0x21, 0x51, 0xDC, 0x6, 0x0, 197 | 0x9, 0x9, 0x7E, 0x23, 0x66, 0x6F, 0xE9, 0x3, 0xEA, 0xA3, 0xDC, 198 | 0xB0, 0xDC, 0xB3, 0xDC, 0xB8, 0xDC, 0xBC, 0xDC, 0xC0, 0xDC, 0xD7, 199 | 0xDC, 0xDC, 0xDC, 0xBA, 0xDD, 0x17, 0xDE, 0xE1, 0xDC, 0xB7, 0xDE, 200 | 0xBB, 0xDE, 0x5, 0xE1, 0xA1, 0xE5, 0xD9, 0xE5, 0xD9, 0xDE, 0x0, 201 | 0xDF, 0xC, 0xDF, 0x24, 0xE7, 0x6F, 0xE7, 0x5B, 0xE6, 0x17, 0xDF, 202 | 0x1F, 0xDF, 0x26, 0xDF, 0xC, 0xE3, 0x2C, 0xDF, 0x6F, 0xE3, 0x31, 203 | 0xDF, 0x36, 0xDF, 0x3E, 0xDF, 0x43, 0xDF, 0x1A, 0xE7, 0x64, 0xE7, 204 | 0x51, 0xDF, 0x59, 0xDF, 0x69, 0xDF, 0xAF, 0xDC, 0xAF, 0xDC, 0x64, 205 | 0xE7, 0xCD, 0x5C, 0xDD, 0xCD, 0x9E, 0xDD, 0xD4, 0x1, 0xDD, 0x32, 206 | 0x7A, 0xE9, 0xC9, 0x7B, 0x18, 0x4E, 0xCD, 0x15, 0xEA, 0x18, 0xF4, 207 | 0x4B, 0xC3, 0x12, 0xEA, 0x4B, 0xC3, 0xF, 0xEA, 0x4B, 0x1C, 0x28, 208 | 0x9, 0x1C, 0xC2, 0xC, 0xEA, 0xCD, 0x6, 0xEA, 0x18, 0xDF, 0xCD, 209 | 0x6, 0xEA, 0xB7, 0xC8, 0xCD, 0x9, 0xEA, 0x18, 0xD5, 0x3A, 0x3, 210 | 0x0, 0x18, 0xD0, 0x7B, 0x32, 0x3, 0x0, 0xC9, 0xCD, 0x67, 0xDD, 211 | 0x18, 0xC6, 0xCD, 0x9E, 0xDD, 0x30, 0x16, 0xF5, 0x3E, 0x5E, 0xCD, 212 | 0x1, 0xDD, 0xF1, 0xF5, 0xC6, 0x40, 0xCD, 0x1, 0xDD, 0xF1, 0xC9, 213 | 0x3E, 0xD, 0xCD, 0x1, 0xDD, 0x3E, 0xA, 0xFE, 0x9, 0x20, 0xF, 214 | 0x3E, 0x20, 0xCD, 0x1, 0xDD, 0x3A, 0x4F, 0xE9, 0xE6, 0x7, 0x20, 215 | 0xF4, 0x3E, 0x9, 0xC9, 0xF5, 0xCD, 0x67, 0xDD, 0xF1, 0xF5, 0x4F, 216 | 0xCD, 0xC, 0xEA, 0xF1, 0xF5, 0x4F, 0x3A, 0x51, 0xE9, 0xB7, 0xC4, 217 | 0xF, 0xEA, 0x3A, 0x15, 0xDC, 0xCB, 0x4F, 0x28, 0x8, 0x21, 0x53, 218 | 0xE9, 0xAF, 0xB6, 0x28, 0x1, 0x35, 0xF1, 0x21, 0x4F, 0xE9, 0xFE, 219 | 0x7F, 0xC8, 0x34, 0xFE, 0x20, 0xD0, 0x35, 0xFE, 0x8, 0x20, 0x2, 220 | 0x35, 0xC9, 0xFE, 0xD, 0x20, 0x3, 0x36, 0x0, 0xC9, 0xFE, 0x9, 221 | 0xC0, 0xF5, 0x7E, 0xC6, 0x8, 0xE6, 0xF8, 0x77, 0xF1, 0xC9, 0x21, 222 | 0x52, 0xE9, 0x7E, 0x36, 0x0, 0xB7, 0xC0, 0xC3, 0x9, 0xEA, 0x3A, 223 | 0x53, 0xE9, 0xB7, 0x20, 0x6, 0xCD, 0x6, 0xEA, 0xB7, 0x20, 0xB, 224 | 0x3A, 0x52, 0xE9, 0xB7, 0x20, 0x22, 0xCD, 0x6, 0xEA, 0xB7, 0xC8, 225 | 0xCD, 0x9, 0xEA, 0xFE, 0x13, 0x20, 0xE, 0xCD, 0x9, 0xEA, 0xFE, 226 | 0x3, 0xCA, 0x0, 0x0, 0xFE, 0x11, 0x20, 0xF4, 0x18, 0xD4, 0x32, 227 | 0x52, 0xE9, 0x3E, 0xFF, 0x32, 0x53, 0xE9, 0x3E, 0x1, 0xC9, 0xFE, 228 | 0xD, 0xC8, 0xFE, 0xA, 0xC8, 0xFE, 0x9, 0xC8, 0xFE, 0x8, 0xC8, 229 | 0xFE, 0x20, 0xC9, 0xCD, 0xB5, 0xDD, 0xE, 0x20, 0xCD, 0xC, 0xEA, 230 | 0xE, 0x8, 0xC3, 0xC, 0xEA, 0x1A, 0xFE, 0x24, 0xC8, 0x13, 0xD5, 231 | 0xCD, 0x1, 0xDD, 0xD1, 0x18, 0xF4, 0x3E, 0x23, 0xCD, 0x1, 0xDD, 232 | 0xCD, 0xFA, 0xDC, 0x21, 0x4F, 0xE9, 0x3A, 0x50, 0xE9, 0xBE, 0xC8, 233 | 0x3E, 0x20, 0xCD, 0x1, 0xDD, 0x18, 0xF1, 0x5, 0x3A, 0x4F, 0xE9, 234 | 0xF5, 0xC5, 0x3A, 0x50, 0xE9, 0x32, 0x4F, 0xE9, 0x78, 0xB7, 0x28, 235 | 0x13, 0x5, 0x23, 0x7E, 0xE5, 0xCD, 0x9E, 0xDD, 0x30, 0x4, 0x1F, 236 | 0xCD, 0x38, 0xDD, 0xCD, 0x38, 0xDD, 0xE1, 0x18, 0xE9, 0xC1, 0xF1, 237 | 0xE5, 0xC5, 0x21, 0x4F, 0xE9, 0x96, 0x3D, 0xFE, 0x8, 0x30, 0x7, 238 | 0xF5, 0xCD, 0xAD, 0xDD, 0xF1, 0x18, 0xF4, 0xC1, 0xE1, 0xC9, 0x3A, 239 | 0x4F, 0xE9, 0x32, 0x50, 0xE9, 0xDD, 0xE5, 0xE1, 0x4E, 0x23, 0x6, 240 | 0x0, 0xE5, 0xE5, 0xC5, 0xCD, 0x5C, 0xDD, 0xC1, 0xE1, 0xE6, 0x7F, 241 | 0xFE, 0x5, 0x20, 0x7, 0xE5, 0xC5, 0xCD, 0xCB, 0xDD, 0x18, 0xEE, 242 | 0xFE, 0x8, 0x20, 0xB, 0x78, 0xB7, 0x28, 0xE4, 0xE1, 0xE5, 0xCD, 243 | 0xDD, 0xDD, 0x18, 0xDD, 0xFE, 0x10, 0x20, 0x9, 0x3A, 0x51, 0xE9, 244 | 0x2F, 0x32, 0x51, 0xE9, 0x18, 0xD0, 0xFE, 0x12, 0x20, 0x1B, 0xC5, 245 | 0xCD, 0xC6, 0xDD, 0xC1, 0xE1, 0xE5, 0xC5, 0x78, 0xB7, 0x28, 0xC, 246 | 0x23, 0x7E, 0x5, 0xE5, 0xC5, 0xCD, 0xE6, 0xDC, 0xC1, 0xE1, 0x18, 247 | 0xF0, 0xC1, 0x18, 0xDF, 0xFE, 0x15, 0x20, 0x6, 0xE1, 0xCD, 0xC6, 248 | 0xDD, 0x18, 0x99, 0xFE, 0x18, 0x20, 0xB, 0xE1, 0x78, 0xB7, 0x28, 249 | 0xF5, 0xE5, 0xCD, 0xDD, 0xDD, 0x18, 0xF5, 0xFE, 0x7F, 0x28, 0xAC, 250 | 0xFE, 0xD, 0x28, 0x1B, 0xFE, 0xA, 0x28, 0x17, 0x23, 0x77, 0x4, 251 | 0xE5, 0xC5, 0xCD, 0xE6, 0xDC, 0xC1, 0xE1, 0xFE, 0x3, 0x78, 0x20, 252 | 0x5, 0xFE, 0x1, 0xCA, 0x0, 0x0, 0xB9, 0x20, 0xC2, 0xE1, 0x70, 253 | 0x3E, 0xD, 0xC3, 0x1, 0xDD, 0x3E, 0x22, 0x18, 0x6E, 0x21, 0x0, 254 | 0x0, 0x22, 0x75, 0xE9, 0x22, 0x73, 0xE9, 0x21, 0x80, 0x0, 0x22, 255 | 0x77, 0xE9, 0xCD, 0x10, 0xE3, 0xAF, 0x32, 0x81, 0xE9, 0xCD, 0x6, 256 | 0xE1, 0x3A, 0x87, 0xE9, 0x18, 0x50, 0xCD, 0xD5, 0xE0, 0xDD, 0x7E, 257 | 0x0, 0xD6, 0x3F, 0x28, 0xD, 0xDD, 0x7E, 0xE, 0xFE, 0x3F, 0x28, 258 | 0x4, 0xDD, 0x36, 0xE, 0x0, 0x3E, 0xF, 0xCD, 0xE1, 0xE3, 0x2A, 259 | 0x5C, 0xE9, 0xED, 0x5B, 0x77, 0xE9, 0x1, 0x80, 0x0, 0xED, 0xB0, 260 | 0xC9, 0xDD, 0x2A, 0x88, 0xE9, 0xCD, 0xD5, 0xE0, 0xCD, 0xF7, 0xE3, 261 | 0x18, 0xE7, 0xCD, 0xD5, 0xE0, 0xCD, 0x9F, 0xE4, 0x3A, 0x8A, 0xE9, 262 | 0x18, 0x12, 0xCD, 0xD5, 0xE0, 0xCD, 0xC0, 0xE4, 0x18, 0xF3, 0x2A, 263 | 0x75, 0xE9, 0x22, 0x7A, 0xE9, 0xC9, 0x3A, 0x81, 0xE9, 0xC3, 0xAC, 264 | 0xDC, 0x2A, 0x62, 0xE9, 0x18, 0xF1, 0x2A, 0x73, 0xE9, 0x18, 0xEC, 265 | 0xCD, 0xD5, 0xE0, 0xCD, 0xDA, 0xE4, 0x18, 0xD4, 0x2A, 0x5E, 0xE9, 266 | 0x18, 0xDF, 0x7B, 0x3C, 0x3A, 0x7F, 0xE9, 0x28, 0xDF, 0x7B, 0xE6, 267 | 0x1F, 0x32, 0x7F, 0xE9, 0xC9, 0xCD, 0xD5, 0xE0, 0xCD, 0xF1, 0xE4, 268 | 0x18, 0xB9, 0x21, 0x20, 0x0, 0xCD, 0xCA, 0xE8, 0xDD, 0x72, 0x21, 269 | 0xDD, 0x71, 0x22, 0xDD, 0x70, 0x23, 0xC9, 0x7B, 0x2F, 0x5F, 0x7A, 270 | 0x2F, 0x57, 0x2A, 0x75, 0xE9, 0x7B, 0xA5, 0x6F, 0x7A, 0xA4, 0x67, 271 | 0x22, 0x75, 0xE9, 0xEB, 0x2A, 0x73, 0xE9, 0x7B, 0xA5, 0x6F, 0x7A, 272 | 0xA4, 0x67, 0x22, 0x73, 0xE9, 0x3A, 0x81, 0xE9, 0xCD, 0x6, 0xE1, 273 | 0xAF, 0xC3, 0xAC, 0xDC, 0xE9, 0x11, 0x71, 0xE0, 0x18, 0x10, 0x11, 274 | 0x84, 0xE0, 0x1, 0xFF, 0xFF, 0x18, 0x13, 0x11, 0xBE, 0xE0, 0x18, 275 | 0x3, 0x11, 0xC9, 0xE0, 0x6, 0x0, 0x18, 0x5, 0x11, 0x8C, 0xE0, 276 | 0x6, 0xFF, 0xE, 0x0, 0xC5, 0xD5, 0xCD, 0xFA, 0xDC, 0x3A, 0x81, 277 | 0xE9, 0xC6, 0x41, 0x32, 0xA4, 0xE0, 0x11, 0x96, 0xE0, 0xCD, 0xBA, 278 | 0xDD, 0xD1, 0xCD, 0xBA, 0xDD, 0xCD, 0xFA, 0xDC, 0x11, 0xA8, 0xE0, 279 | 0xCD, 0xBA, 0xDD, 0x3A, 0x79, 0xE9, 0xF5, 0x1, 0x64, 0x0, 0xCD, 280 | 0x4C, 0xE0, 0xE, 0xA, 0xCD, 0x4C, 0xE0, 0x1, 0x1, 0x1, 0xCD, 281 | 0x4C, 0xE0, 0xF1, 0xC1, 0xC5, 0xFE, 0xF, 0x38, 0x39, 0xFE, 0x18, 282 | 0x38, 0x10, 0xFE, 0x1E, 0x28, 0xC, 0xFE, 0x21, 0x38, 0x2D, 0xFE, 283 | 0x25, 0x38, 0x4, 0xFE, 0x28, 0x20, 0x25, 0xDD, 0xE5, 0xD6, 0x13, 284 | 0x20, 0x7, 0xB1, 0x28, 0x4, 0xCD, 0x5F, 0xE2, 0xE3, 0x11, 0xB4, 285 | 0xE0, 0xCD, 0xBA, 0xDD, 0xE1, 0x6, 0x8, 0xCD, 0x63, 0xE0, 0x3E, 286 | 0x2E, 0xE5, 0xCD, 0x1, 0xDD, 0xE1, 0x6, 0x3, 0xCD, 0x63, 0xE0, 287 | 0xCD, 0x67, 0xDD, 0xB7, 0x28, 0x5, 0xCD, 0x5C, 0xDD, 0x18, 0xF5, 288 | 0xCD, 0x5C, 0xDD, 0xC1, 0xFE, 0x3, 0x28, 0x8, 0xA0, 0x28, 0x5, 289 | 0x21, 0x15, 0xDC, 0xCB, 0x56, 0xCA, 0x0, 0x0, 0xFE, 0x18, 0xC8, 290 | 0xC5, 0x18, 0xE7, 0x16, 0xFF, 0x14, 0x91, 0x30, 0xFC, 0x81, 0xF5, 291 | 0x7A, 0xB0, 0x28, 0x9, 0x42, 0x7A, 0xC6, 0x30, 0xC5, 0xCD, 0x1, 292 | 0xDD, 0xC1, 0xF1, 0xC9, 0x23, 0x7E, 0xE6, 0x7F, 0xE5, 0xC5, 0xCD, 293 | 0x1, 0xDD, 0xC1, 0xE1, 0x10, 0xF3, 0xC9, 0x4E, 0x6F, 0x6E, 0x2D, 294 | 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6E, 0x74, 0x20, 0x64, 0x72, 295 | 0x69, 0x76, 0x65, 0x24, 0x46, 0x69, 0x6C, 0x65, 0x20, 0x69, 0x73, 296 | 0x20, 0x52, 0x65, 0x61, 0x64, 0x2D, 0x4F, 0x6E, 0x6C, 0x79, 0x24, 297 | 0x44, 0x69, 0x73, 0x6B, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x20, 298 | 0x6F, 0x6E, 0x20, 0x0, 0x3A, 0x20, 0x24, 0x46, 0x75, 0x6E, 0x63, 299 | 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x3D, 0x20, 0x24, 0x3B, 0x20, 0x46, 300 | 0x69, 0x6C, 0x65, 0x20, 0x3D, 0x20, 0x24, 0x52, 0x65, 0x61, 0x64, 301 | 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x24, 0x57, 0x72, 0x69, 0x74, 302 | 0x65, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x24, 0x3E, 0xFF, 0x32, 303 | 0x7C, 0xE9, 0x3A, 0x81, 0xE9, 0x32, 0x80, 0xE9, 0x5F, 0xDD, 0x7E, 304 | 0x0, 0x32, 0x7E, 0xE9, 0xFE, 0x3F, 0x28, 0x1A, 0xE6, 0x1F, 0x7B, 305 | 0x28, 0x4, 0xDD, 0x7E, 0x0, 0x3D, 0xCD, 0x6, 0xE1, 0xDD, 0x7E, 306 | 0x0, 0xE6, 0xE0, 0x47, 0x3A, 0x7F, 0xE9, 0xB0, 0xDD, 0x77, 0x0, 307 | 0xC9, 0x7B, 0xE6, 0xF, 0x47, 0xED, 0x5B, 0x75, 0xE9, 0xB7, 0x28, 308 | 0x6, 0xCB, 0x1A, 0xCB, 0x1B, 0x10, 0xFA, 0x21, 0x81, 0xE9, 0xCB, 309 | 0x43, 0x28, 0x2, 0xBE, 0xC8, 0x77, 0xD5, 0x4F, 0xCD, 0x1B, 0xEA, 310 | 0x7C, 0xB5, 0x20, 0x4, 0x2A, 0xB, 0xDC, 0xE9, 0x5E, 0x23, 0x56, 311 | 0x23, 0xED, 0x53, 0x54, 0xE9, 0x22, 0x56, 0xE9, 0x23, 0x23, 0x22, 312 | 0x58, 0xE9, 0x23, 0x23, 0x22, 0x5A, 0xE9, 0x23, 0x23, 0x11, 0x5C, 313 | 0xE9, 0x1, 0x8, 0x0, 0xED, 0xB0, 0x2A, 0x5E, 0xE9, 0xE, 0xF, 314 | 0xED, 0xB0, 0xD1, 0xCB, 0x43, 0xC0, 0x2A, 0x75, 0xE9, 0xCD, 0xBF, 315 | 0xE1, 0x22, 0x75, 0xE9, 0xED, 0x5B, 0x69, 0xE9, 0x3E, 0x3, 0xCB, 316 | 0x3A, 0xCB, 0x1B, 0x3D, 0x20, 0xF9, 0x2A, 0x62, 0xE9, 0xE5, 0x23, 317 | 0x36, 0x0, 0x1B, 0x7A, 0xB3, 0x20, 0xF8, 0xE1, 0xED, 0x5B, 0x6D, 318 | 0xE9, 0x73, 0x23, 0x72, 0x2A, 0x56, 0xE9, 0x77, 0x23, 0x77, 0x32, 319 | 0x87, 0xE9, 0x32, 0x8E, 0xE9, 0xCD, 0x6A, 0xE2, 0x3E, 0xFF, 0xCD, 320 | 0x8E, 0xE2, 0xCD, 0x71, 0xE2, 0xC8, 0xCD, 0x5F, 0xE2, 0x7E, 0xFE, 321 | 0xE5, 0x28, 0xEF, 0xFE, 0x21, 0x28, 0xEB, 0x3A, 0x7F, 0xE9, 0xBE, 322 | 0x20, 0xA, 0x23, 0x7E, 0xD6, 0x24, 0x20, 0x4, 0x3D, 0x32, 0x87, 323 | 0xE9, 0xE, 0x1, 0xCD, 0x46, 0xE3, 0xCD, 0x78, 0xE2, 0x18, 0xD1, 324 | 0xEB, 0x21, 0x1, 0x0, 0x3A, 0x81, 0xE9, 0xB7, 0x28, 0x4, 0x29, 325 | 0x3D, 0x20, 0xFC, 0x7A, 0xB4, 0x67, 0x7B, 0xB5, 0x6F, 0xC9, 0x2A, 326 | 0x84, 0xE9, 0xCB, 0x3C, 0xCB, 0x1D, 0xCB, 0x3C, 0xCB, 0x1D, 0x22, 327 | 0x82, 0xE9, 0xEB, 0x21, 0x0, 0x0, 0xED, 0x4B, 0x64, 0xE9, 0x3E, 328 | 0x11, 0xB7, 0xED, 0x42, 0x3F, 0x38, 0x2, 0x9, 0xB7, 0xCB, 0x13, 329 | 0xCB, 0x12, 0x3D, 0x28, 0x6, 0xCB, 0x15, 0xCB, 0x14, 0x18, 0xEB, 330 | 0xE5, 0x2A, 0x71, 0xE9, 0x19, 0x44, 0x4D, 0xCD, 0x1E, 0xEA, 0xC1, 331 | 0xED, 0x5B, 0x54, 0xE9, 0xCD, 0x30, 0xEA, 0x44, 0x4D, 0xC3, 0x21, 332 | 0xEA, 0xDD, 0x4E, 0x20, 0x3A, 0x66, 0xE9, 0x47, 0xCB, 0x39, 0x10, 333 | 0xFC, 0x2F, 0xC6, 0x9, 0x47, 0x3A, 0x68, 0xE9, 0xDD, 0xA6, 0xC, 334 | 0xF, 0x7, 0x10, 0xFD, 0x81, 0xDD, 0xE5, 0xE1, 0xE, 0x10, 0x9, 335 | 0x4F, 0x9, 0x3A, 0x6A, 0xE9, 0xB7, 0x5E, 0x57, 0xC8, 0x9, 0x5E, 336 | 0x23, 0x56, 0x2B, 0xC9, 0x21, 0x0, 0x0, 0x3A, 0x66, 0xE9, 0x47, 337 | 0xCB, 0x23, 0xCB, 0x12, 0xCB, 0x15, 0x10, 0xF8, 0x3A, 0x67, 0xE9, 338 | 0xDD, 0xA6, 0x20, 0xB3, 0x5F, 0xC9, 0x2A, 0x5C, 0xE9, 0x3A, 0x86, 339 | 0xE9, 0x85, 0x6F, 0xD0, 0x24, 0xC9, 0x21, 0xFF, 0xFF, 0x22, 0x84, 340 | 0xE9, 0xC9, 0x2A, 0x84, 0xE9, 0x7C, 0xA5, 0x3C, 0xC9, 0xCD, 0x81, 341 | 0xE2, 0xD8, 0x13, 0x72, 0x2B, 0x73, 0xC9, 0x2A, 0x56, 0xE9, 0xED, 342 | 0x5B, 0x84, 0xE9, 0x7B, 0x96, 0x23, 0x7A, 0x9E, 0xC9, 0x4F, 0x2A, 343 | 0x84, 0xE9, 0x23, 0x22, 0x84, 0xE9, 0xED, 0x5B, 0x6B, 0xE9, 0xB7, 344 | 0xED, 0x52, 0x19, 0x28, 0x2, 0x30, 0xC8, 0x7D, 0x87, 0x87, 0x87, 345 | 0x87, 0x87, 0xE6, 0x60, 0x32, 0x86, 0xE9, 0xC0, 0xC5, 0xCD, 0xD4, 346 | 0xE1, 0xCD, 0xF5, 0xE2, 0xC1, 0x2A, 0x6F, 0xE9, 0xED, 0x5B, 0x82, 347 | 0xE9, 0xB7, 0xED, 0x52, 0xC8, 0xD8, 0x2A, 0x5C, 0xE9, 0x6, 0x80, 348 | 0xAF, 0x86, 0x23, 0x10, 0xFC, 0x2A, 0x60, 0xE9, 0x19, 0xC, 0x28, 349 | 0xA, 0xBE, 0xC8, 0x3E, 0xFF, 0x32, 0x8E, 0xE9, 0xC3, 0x78, 0xE3, 350 | 0x77, 0xC9, 0xCD, 0x27, 0xEA, 0x21, 0xA0, 0xDF, 0x18, 0x6, 0xCD, 351 | 0x2A, 0xEA, 0x21, 0xA5, 0xDF, 0xB7, 0xC8, 0xE5, 0x2A, 0x9, 0xDC, 352 | 0xE3, 0xC9, 0xCD, 0x16, 0xE3, 0xCD, 0xDF, 0xE2, 0x18, 0x13, 0xE, 353 | 0xFF, 0xCD, 0xB6, 0xE2, 0xCD, 0x16, 0xE3, 0xE, 0x1, 0xCD, 0xE7, 354 | 0xE2, 0x18, 0x4, 0xED, 0x53, 0x77, 0xE9, 0xED, 0x4B, 0x77, 0xE9, 355 | 0x18, 0x4, 0xED, 0x4B, 0x5C, 0xE9, 0xC3, 0x24, 0xEA, 0x7B, 0xE6, 356 | 0x7, 0x3C, 0x47, 0x4F, 0xCB, 0x3A, 0xCB, 0x1B, 0xCB, 0x3A, 0xCB, 357 | 0x1B, 0xCB, 0x3A, 0xCB, 0x1B, 0x2A, 0x62, 0xE9, 0x19, 0x7E, 0x7, 358 | 0x10, 0xFD, 0x41, 0xC9, 0xC5, 0xCD, 0x1D, 0xE3, 0xE6, 0xFE, 0xD1, 359 | 0xB3, 0xF, 0x10, 0xFD, 0x77, 0xC9, 0xCD, 0x5F, 0xE2, 0x11, 0x10, 360 | 0x0, 0x19, 0x43, 0x5E, 0x23, 0x16, 0x0, 0x3A, 0x6A, 0xE9, 0xB7, 361 | 0x28, 0x3, 0x5, 0x56, 0x23, 0x7A, 0xB3, 0x28, 0xD, 0xE5, 0xC5, 362 | 0x2A, 0x69, 0xE9, 0xB7, 0xED, 0x52, 0xD4, 0x39, 0xE3, 0xC1, 0xE1, 363 | 0x10, 0xE0, 0xC9, 0x2A, 0x73, 0xE9, 0xCD, 0xBF, 0xE1, 0x22, 0x73, 364 | 0xE9, 0xED, 0x5B, 0x6B, 0xE9, 0x13, 0x2A, 0x56, 0xE9, 0x73, 0x23, 365 | 0x72, 0xC9, 0xCD, 0x5F, 0xE2, 0x11, 0x2, 0x0, 0x19, 0xCB, 0x7E, 366 | 0x20, 0xB, 0x1E, 0x7, 0x19, 0xCB, 0x7E, 0x20, 0x4, 0x23, 0xCB, 367 | 0x7E, 0xC8, 0x2A, 0xF, 0xDC, 0xE9, 0x2A, 0x73, 0xE9, 0xCD, 0xBF, 368 | 0xE1, 0xED, 0x52, 0xC0, 0x2A, 0xD, 0xDC, 0xE9, 0x62, 0x6B, 0x7A, 369 | 0xB3, 0x28, 0xB, 0x1B, 0xE5, 0xD5, 0xCD, 0x1D, 0xE3, 0x1F, 0x30, 370 | 0x1F, 0xD1, 0xE1, 0xED, 0x4B, 0x69, 0xE9, 0xB7, 0xED, 0x42, 0x9, 371 | 0x30, 0xE, 0x23, 0xD5, 0xE5, 0xEB, 0xCD, 0x1D, 0xE3, 0x1F, 0x30, 372 | 0x9, 0xE1, 0xD1, 0x18, 0xD9, 0x7A, 0xB3, 0x20, 0xD5, 0xC9, 0x37, 373 | 0x17, 0xCD, 0x41, 0xE3, 0xD1, 0xE1, 0xC9, 0x32, 0x8B, 0xE9, 0x3A, 374 | 0x8E, 0xE9, 0xA7, 0xC4, 0x60, 0xE1, 0x3E, 0xFF, 0x32, 0x8A, 0xE9, 375 | 0xDD, 0x22, 0x88, 0xE9, 0xCD, 0x6A, 0xE2, 0xAF, 0xCD, 0x8E, 0xE2, 376 | 0xCD, 0x71, 0xE2, 0x28, 0x7C, 0xED, 0x5B, 0x88, 0xE9, 0x1A, 0xFE, 377 | 0xE5, 0x28, 0x7, 0xD5, 0xCD, 0x81, 0xE2, 0xD1, 0x30, 0x6C, 0xCD, 378 | 0x5F, 0xE2, 0x7E, 0xFE, 0x21, 0x28, 0xDF, 0x3A, 0x8B, 0xE9, 0x47, 379 | 0xAF, 0x32, 0x8C, 0xE9, 0x32, 0x8D, 0xE9, 0x4F, 0x78, 0xB7, 0x28, 380 | 0x5D, 0x1A, 0xD6, 0x3F, 0x28, 0x3C, 0x79, 0xB7, 0x20, 0x23, 0x3A, 381 | 0x15, 0xDC, 0xCB, 0x47, 0x28, 0x1C, 0x23, 0x23, 0xCB, 0x7E, 0x2B, 382 | 0x2B, 0x28, 0x14, 0x1A, 0xFE, 0xE5, 0x28, 0xF, 0xAE, 0xE6, 0x7F, 383 | 0x28, 0x19, 0xE6, 0xE0, 0x20, 0x6, 0x3D, 0x32, 0x8D, 0xE9, 0x18, 384 | 0xF, 0x79, 0xFE, 0xD, 0x28, 0xA, 0xFE, 0xC, 0x1A, 0x28, 0x11, 385 | 0xAE, 0xE6, 0x7F, 0x20, 0x94, 0x13, 0x23, 0xC, 0x5, 0x18, 0xBB, 386 | 0x3D, 0x32, 0x8C, 0xE9, 0x18, 0xF4, 0xC5, 0xAE, 0x47, 0x3A, 0x68, 387 | 0xE9, 0x2F, 0xE6, 0x1F, 0xA0, 0xC1, 0x18, 0xE5, 0xCD, 0x6A, 0xE2, 388 | 0x3E, 0xFF, 0x32, 0x7A, 0xE9, 0xC9, 0x3A, 0x8C, 0xE9, 0x47, 0x3A, 389 | 0x8D, 0xE9, 0xA0, 0x20, 0xD2, 0xCD, 0x78, 0xE2, 0x3A, 0x84, 0xE9, 390 | 0xE6, 0x3, 0x32, 0x7A, 0xE9, 0xAF, 0x32, 0x8A, 0xE9, 0xC9, 0xCD, 391 | 0x9E, 0xE3, 0x3E, 0xC, 0xCD, 0xE1, 0xE3, 0xCD, 0x71, 0xE2, 0xC8, 392 | 0xCD, 0x84, 0xE3, 0xCD, 0x5F, 0xE2, 0x36, 0xE5, 0xE, 0x0, 0xCD, 393 | 0x46, 0xE3, 0xCD, 0x34, 0xE5, 0xCD, 0xF7, 0xE3, 0x18, 0xE7, 0xCD, 394 | 0x9E, 0xE3, 0x3E, 0xC, 0xCD, 0xE1, 0xE3, 0xCD, 0x71, 0xE2, 0xC8, 395 | 0xCD, 0x84, 0xE3, 0x1, 0x10, 0xC, 0xCD, 0x1F, 0xE5, 0xCD, 0xF7, 396 | 0xE3, 0x18, 0xEE, 0xCD, 0x9E, 0xE3, 0x3E, 0xC, 0xCD, 0xE1, 0xE3, 397 | 0xCD, 0x71, 0xE2, 0xC8, 0x1, 0x0, 0xC, 0xCD, 0x1F, 0xE5, 0xCD, 398 | 0xF7, 0xE3, 0x18, 0xF1, 0x1, 0x0, 0x0, 0x51, 0xCD, 0x5F, 0xDF, 399 | 0x3E, 0xC, 0xCD, 0xE1, 0xE3, 0xCD, 0x71, 0xE2, 0xC8, 0xCD, 0x5F, 400 | 0xE2, 0xEB, 0x21, 0xF, 0x0, 0xCD, 0xCA, 0xE8, 0x7A, 0xDD, 0x96, 401 | 0x21, 0x79, 0xDD, 0x9E, 0x22, 0x78, 0xDD, 0x9E, 0x23, 0xD4, 0x5F, 402 | 0xDF, 0xCD, 0xF7, 0xE3, 0x18, 0xDE, 0xCD, 0x5F, 0xE2, 0xE5, 0x7E, 403 | 0xEB, 0xDD, 0xE5, 0xE1, 0xC5, 0x6, 0x0, 0x9, 0xC1, 0x48, 0x6, 404 | 0x0, 0xED, 0xB0, 0xE1, 0x77, 0xCD, 0xD4, 0xE1, 0xC3, 0xFD, 0xE2, 405 | 0x3E, 0xF, 0xCD, 0xE1, 0xE3, 0xCD, 0x71, 0xE2, 0xC0, 0x3A, 0x7D, 406 | 0xE9, 0xB7, 0xC0, 0x3A, 0x8C, 0xE9, 0xB7, 0xC0, 0x2A, 0x11, 0xDC, 407 | 0x7C, 0xB5, 0xC8, 0x7E, 0x23, 0xB7, 0xCA, 0x7C, 0xE4, 0xE6, 0x7F, 408 | 0xFE, 0x24, 0x20, 0x4, 0x3A, 0x80, 0xE9, 0x3C, 0x3D, 0xE5, 0xCD, 409 | 0x6, 0xE1, 0xE1, 0x7E, 0x23, 0xE6, 0x7F, 0xFE, 0x24, 0x20, 0x3, 410 | 0x3A, 0x7F, 0xE9, 0xE6, 0x1F, 0x47, 0xDD, 0x7E, 0x0, 0xE6, 0xE0, 411 | 0xB0, 0xDD, 0x77, 0x0, 0xE5, 0x3E, 0xF, 0xCD, 0xE1, 0xE3, 0xCD, 412 | 0x71, 0xE2, 0xE1, 0x28, 0xC7, 0xE5, 0xCD, 0x5F, 0xE2, 0x11, 0xA, 413 | 0x0, 0x19, 0xCB, 0x7E, 0xE1, 0x28, 0xBA, 0x3A, 0x81, 0xE9, 0x3C, 414 | 0x32, 0x7E, 0xE9, 0xC9, 0xCD, 0xD5, 0xE0, 0xDD, 0x36, 0xE, 0x0, 415 | 0xCD, 0x3A, 0xE5, 0xCD, 0x71, 0xE2, 0xC8, 0xDD, 0x7E, 0xC, 0xF5, 416 | 0xCD, 0x5F, 0xE2, 0xDD, 0xE5, 0xD1, 0x1, 0x20, 0x0, 0xED, 0xB0, 417 | 0xDD, 0xCB, 0xE, 0xFE, 0xDD, 0x46, 0xC, 0xDD, 0x4E, 0xF, 0xF1, 418 | 0xDD, 0x77, 0xC, 0xB8, 0x28, 0x6, 0xE, 0x0, 0x30, 0x2, 0xE, 419 | 0x80, 0xDD, 0x71, 0xF, 0xC9, 0xCD, 0xD5, 0xE0, 0xDD, 0xCB, 0xE, 420 | 0x7E, 0xC0, 0xCD, 0x9E, 0xE3, 0x3E, 0xF, 0xCD, 0xE1, 0xE3, 0xCD, 421 | 0x71, 0xE2, 0xC8, 0xCD, 0x84, 0xE3, 0xCD, 0x5F, 0xE2, 0x1, 0x10, 422 | 0x0, 0x9, 0xEB, 0xDD, 0xE5, 0xE1, 0x9, 0x3A, 0x6A, 0xE9, 0xB7, 423 | 0x28, 0x1, 0x5, 0xCD, 0x39, 0xE6, 0xEB, 0xCD, 0x39, 0xE6, 0xEB, 424 | 0x20, 0x26, 0x23, 0x13, 0xCB, 0x40, 0x28, 0x3, 0x23, 0x13, 0xD, 425 | 0xD, 0x20, 0xEA, 0x21, 0xEC, 0xFF, 0x19, 0xDD, 0x7E, 0xC, 0xBE, 426 | 0x38, 0x8, 0x77, 0x23, 0x23, 0x23, 0xDD, 0x7E, 0xF, 0x77, 0x1E, 427 | 0x5, 0xCD, 0xEF, 0xE8, 0xC3, 0x34, 0xE5, 0x3E, 0xFF, 0x32, 0x7A, 428 | 0xE9, 0xC9, 0x7E, 0xCB, 0x40, 0x28, 0x3, 0x23, 0xB6, 0x2B, 0xB7, 429 | 0x20, 0xB, 0x1A, 0x77, 0xCB, 0x40, 0xC8, 0x23, 0x13, 0x1A, 0x77, 430 | 0x18, 0x7, 0x1A, 0x96, 0xC0, 0xB0, 0xC8, 0x23, 0x13, 0x1A, 0x96, 431 | 0x2B, 0x1B, 0xC9, 0xCD, 0xD5, 0xE0, 0xDD, 0x36, 0xE, 0x0, 0xCD, 432 | 0x9E, 0xE3, 0xDD, 0x7E, 0x0, 0xF5, 0xDD, 0x36, 0x0, 0xE5, 0x3E, 433 | 0x1, 0xCD, 0xE1, 0xE3, 0xF1, 0xDD, 0x77, 0x0, 0xCD, 0x71, 0xE2, 434 | 0xC8, 0xAF, 0xDD, 0x77, 0xD, 0xDD, 0xE5, 0xE1, 0x11, 0xF, 0x0, 435 | 0x19, 0x6, 0x11, 0x77, 0x23, 0x10, 0xFC, 0xCD, 0x5F, 0xE2, 0xDD, 436 | 0x7E, 0x0, 0x77, 0x1E, 0x1, 0xCD, 0xEF, 0xE8, 0x1E, 0x5, 0xCD, 437 | 0xEF, 0xE8, 0x1, 0x0, 0x20, 0xCD, 0x1F, 0xE5, 0xDD, 0xCB, 0xE, 438 | 0xFE, 0xC9, 0xDD, 0xCB, 0xE, 0x7E, 0x20, 0x2F, 0xCD, 0xDC, 0xE5, 439 | 0x3A, 0x7A, 0xE9, 0x3C, 0xC8, 0xCD, 0xF4, 0xE6, 0x38, 0x1A, 0x20, 440 | 0x30, 0x3E, 0xF, 0xCD, 0xE1, 0xE3, 0xCD, 0x71, 0xE2, 0x20, 0x26, 441 | 0x3A, 0x7D, 0xE9, 0xB7, 0x28, 0x8, 0xCD, 0x62, 0xE6, 0xCD, 0x71, 442 | 0xE2, 0x20, 0x1B, 0xDD, 0xCB, 0xE, 0xFE, 0x3E, 0xFF, 0x18, 0x14, 443 | 0xCD, 0xF4, 0xE6, 0x38, 0xF3, 0xDD, 0xCB, 0xA, 0x7E, 0x28, 0xD5, 444 | 0xCD, 0x3A, 0xE5, 0x18, 0xD5, 0xCD, 0xAF, 0xE5, 0xAF, 0x32, 0x7A, 445 | 0xE9, 0xC9, 0xDD, 0x46, 0xC, 0xDD, 0x4E, 0xE, 0xCB, 0x71, 0x37, 446 | 0xC0, 0x4, 0x78, 0xE6, 0x1F, 0x47, 0x20, 0xA, 0xC, 0x79, 0xE6, 447 | 0x3F, 0x4F, 0x37, 0xC8, 0xAF, 0x18, 0x4, 0x3A, 0x68, 0xE9, 0xA0, 448 | 0xDD, 0x70, 0xC, 0xDD, 0x71, 0xE, 0xC9, 0xCD, 0xD5, 0xE0, 0xAF, 449 | 0xCD, 0x2B, 0xE8, 0x28, 0x4, 0xC9, 0xCD, 0xD5, 0xE0, 0xAF, 0x32, 450 | 0x7D, 0xE9, 0xDD, 0x7E, 0x20, 0xFE, 0x80, 0x30, 0xB, 0xDD, 0xBE, 451 | 0xF, 0x38, 0x13, 0x3E, 0x1, 0x32, 0x7A, 0xE9, 0xC9, 0xCD, 0xA7, 452 | 0xE6, 0x3A, 0x7A, 0xE9, 0xB7, 0x20, 0xF1, 0xDD, 0x36, 0x20, 0x0, 453 | 0xCD, 0x18, 0xE2, 0x7A, 0xB3, 0x28, 0xE6, 0xCD, 0x47, 0xE2, 0xCD, 454 | 0xE6, 0xE1, 0xCD, 0xDF, 0xE2, 0x3A, 0x79, 0xE9, 0xFE, 0x14, 0xC0, 455 | 0xDD, 0x34, 0x20, 0xC9, 0xCD, 0xD5, 0xE0, 0x3E, 0xFF, 0xCD, 0x2B, 456 | 0xE8, 0x28, 0x4, 0xC9, 0xCD, 0xD5, 0xE0, 0x3E, 0xFF, 0x32, 0x7D, 457 | 0xE9, 0xCD, 0x9E, 0xE3, 0xDD, 0xE5, 0xE1, 0xCD, 0x87, 0xE3, 0xDD, 458 | 0x7E, 0x20, 0xFE, 0x80, 0x38, 0xE, 0xCD, 0xA7, 0xE6, 0x3A, 0x7A, 459 | 0xE9, 0xB7, 0xC2, 0x25, 0xE8, 0xDD, 0x36, 0x20, 0x0, 0xCD, 0x18, 460 | 0xE2, 0x7A, 0xB3, 0x20, 0x58, 0xE5, 0x79, 0xB7, 0x28, 0x4, 0x3D, 461 | 0xCD, 0x32, 0xE2, 0xCD, 0xAB, 0xE3, 0xE1, 0x7A, 0xB3, 0x28, 0x72, 462 | 0xDD, 0xCB, 0xE, 0xBE, 0x73, 0x3A, 0x6A, 0xE9, 0xB7, 0x28, 0x2, 463 | 0x23, 0x72, 0xE, 0x2, 0x3A, 0x79, 0xE9, 0xD6, 0x28, 0x20, 0x33, 464 | 0xD5, 0x21, 0x5C, 0xE9, 0x6, 0x80, 0x77, 0x23, 0x10, 0xFC, 0xCD, 465 | 0x47, 0xE2, 0x3A, 0x67, 0xE9, 0x47, 0x4, 0x2F, 0xA3, 0x5F, 0xE, 466 | 0x2, 0xE5, 0xD5, 0xC5, 0xCD, 0xE6, 0xE1, 0xCD, 0x16, 0xE3, 0xC1, 467 | 0xC5, 0xCD, 0xE7, 0xE2, 0xC1, 0xD1, 0xE1, 0xE, 0x0, 0x1C, 0x10, 468 | 0xEA, 0xCD, 0x10, 0xE3, 0xD1, 0xE, 0x0, 0xDD, 0xCB, 0xE, 0xBE, 469 | 0xC5, 0xCD, 0x47, 0xE2, 0xCD, 0xE6, 0xE1, 0xC1, 0xCD, 0xE7, 0xE2, 470 | 0xDD, 0x7E, 0x20, 0xDD, 0xBE, 0xF, 0x38, 0x8, 0x3C, 0xDD, 0x77, 471 | 0xF, 0xDD, 0xCB, 0xE, 0xBE, 0x3A, 0x79, 0xE9, 0xFE, 0x15, 0xC0, 472 | 0xDD, 0x34, 0x20, 0xC9, 0x3E, 0x2, 0x32, 0x7A, 0xE9, 0xC9, 0x3E, 473 | 0x1, 0x32, 0x7A, 0xE9, 0xC9, 0x32, 0x7D, 0xE9, 0xDD, 0x7E, 0x21, 474 | 0x57, 0xCB, 0xBA, 0x17, 0xDD, 0x7E, 0x22, 0x17, 0xF5, 0xE6, 0x1F, 475 | 0x4F, 0xF1, 0x17, 0x17, 0x17, 0x17, 0xE6, 0xF, 0x47, 0xDD, 0x7E, 476 | 0x23, 0x1E, 0x6, 0xFE, 0x4, 0x30, 0x72, 0x7, 0x7, 0x7, 0x7, 477 | 0x80, 0x47, 0xDD, 0x72, 0x20, 0xDD, 0x56, 0xE, 0xCB, 0x72, 0x20, 478 | 0xE, 0x79, 0xDD, 0xBE, 0xC, 0x20, 0x8, 0x78, 0xDD, 0xAE, 0xE, 479 | 0xE6, 0x3F, 0x28, 0x4B, 0xCB, 0x7A, 0x20, 0xF, 0xD5, 0xC5, 0xCD, 480 | 0xDC, 0xE5, 0xC1, 0xD1, 0x1E, 0x3, 0x3A, 0x7A, 0xE9, 0x3C, 0x28, 481 | 0x3D, 0xDD, 0x71, 0xC, 0xDD, 0x70, 0xE, 0xCB, 0x7A, 0x20, 0x7, 482 | 0x3E, 0xF, 0xCD, 0xE1, 0xE3, 0x18, 0x9, 0xDD, 0xCB, 0xA, 0x7E, 483 | 0x28, 0xF3, 0xCD, 0x3A, 0xE5, 0x3A, 0x7A, 0xE9, 0x3C, 0x20, 0x15, 484 | 0x3A, 0x7D, 0xE9, 0x1E, 0x4, 0x3C, 0x20, 0x15, 0xCD, 0x62, 0xE6, 485 | 0x1E, 0x5, 0x3A, 0x7A, 0xE9, 0x3C, 0x28, 0xA, 0x18, 0x3, 0xCD, 486 | 0xAF, 0xE5, 0xAF, 0x32, 0x7A, 0xE9, 0xC9, 0xDD, 0x36, 0xE, 0xC0, 487 | 0x7B, 0x32, 0x7A, 0xE9, 0xDD, 0xCB, 0xE, 0xFE, 0xB7, 0xC9, 0x19, 488 | 0x7E, 0x21, 0xC, 0x0, 0x19, 0x57, 0x7E, 0xE6, 0x1F, 0xCB, 0x12, 489 | 0xCE, 0x0, 0x1F, 0xCB, 0x1A, 0x4F, 0x23, 0x23, 0x7E, 0xF, 0xF, 490 | 0xF, 0xF, 0xF5, 0xE6, 0x3, 0x47, 0xF1, 0xE6, 0xF0, 0x81, 0x4F, 491 | 0xD0, 0x4, 0xC9, 0x2A, 0x5C, 0xE9, 0x1, 0x60, 0x0, 0x9, 0x7E, 492 | 0xD6, 0x21, 0xC0, 0x57, 0x19, 0x3A, 0x86, 0xE9, 0xF, 0xF, 0x5F, 493 | 0xF, 0xF, 0x83, 0x5F, 0x19, 0xE5, 0xE, 0x0, 0xCD, 0x24, 0xE9, 494 | 0xD1, 0x1, 0x4, 0x0, 0xED, 0xB0, 0xC9, 0xD5, 0xE, 0x0, 0xCD, 495 | 0x24, 0xE9, 0xD1, 0x1, 0x5, 0x0, 0xED, 0xB0, 0xC9, 0xEB, 0xE, 496 | 0xFF, 0xE5, 0x2A, 0x13, 0xDC, 0xE3, 0xC9, 0x3A, 0x7C, 0xE9, 0xB7, 497 | 0x28, 0xC, 0x3A, 0x7E, 0xE9, 0xDD, 0x77, 0x0, 0x3A, 0x80, 0xE9, 498 | 0xCD, 0x6, 0xE1, 0xDD, 0xE5, 0xD1, 0xDD, 0xE1, 0xED, 0x7B, 0x8F, 499 | 0xE9, 0x2A, 0x7A, 0xE9, 0x3A, 0x79, 0xE9, 0x4F, 0x7D, 0x44, 0xC9, 500 | 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xF0, 0x4, 0xF0, 501 | 0x6, 0xF0, 0x80, 0xEF, 0x70, 0xEF, 0x0, 0x0, 0x0, 0xF1, 0x1A, 502 | 0x0, 0x3, 0x7, 0x0, 0xF2, 0x0, 0x3F, 0x0, 0xC0, 0x0, 0x0, 503 | 0x0, 0x2, 0x0, 0x0, 0x0, 0x1, 0x0, 0x80, 0x0, 0xA, 0x0, 504 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x22, 0x0, 505 | 0x40, 0x0, 0x9B, 0xD4, 0x0, 0xF, 0x0, 0x0, 0x0, 0x73, 0xD4, 506 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 507 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 508 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 509 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 510 | 0xE2, 0xE2, 0xFB, 0xE2, 0xB5, 0xE2, 0x2A, 0xDE, 0x50, 0x0, 0x7, 511 | 0xD4, 0x7, 0xD4, 0x2A, 0xE9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 512 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 513 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 514 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 515 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 516 | 0x0, 517 | }; 518 | -------------------------------------------------------------------------------- /cpmdisc.h: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------*\ 2 | | Originally by Kevin Kayes, but he refused any responsibility for it. | 3 | | | 4 | | Copyright 1986-1988 by Parag Patel. All Rights Reserved. | 5 | | Copyright 1994 by CodeGen, Inc. All Rights Reserved. | 6 | \*-----------------------------------------------------------------------*/ 7 | 8 | 9 | #define SECTORSIZE 128 10 | #define SECTORSPERTRACK 26 11 | #define TRACKSPERDISC 77 12 | #define SECTOROFFSET 1 13 | #define TRACKOFFSET 0 14 | #define RESERVEDTRACKS 2 15 | #define SECTORSPERBLOCK 8 16 | #define SECTORSPEREXTENT 128 17 | #define EXTENTSIZE 32 18 | #define TOTALEXTENTS 64 19 | 20 | #define EXTENTSPERSECTOR (SECTORSIZE / EXTENTSIZE) 21 | #define TRACKSIZE (long)(SECTORSIZE * SECTORSPERTRACK) 22 | #define DISCSIZE (long)(TRACKSIZE * TRACKSPERDISC) 23 | 24 | unsigned char sectorxlat[] = { 25 | 1, 7, 13, 19, 26 | 25, 5, 11, 17, 27 | 23, 3, 9, 15, 28 | 21, 2, 8, 14, 29 | 20, 26, 6, 12, 30 | 18, 24, 4, 10, 31 | 16, 22 32 | }; 33 | 34 | -------------------------------------------------------------------------------- /cpmtool.c: -------------------------------------------------------------------------------- 1 | /* CP/M disk */ 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define SECTOR_SIZE 128 8 | 9 | /* Layout is: 10 | * Zero or more reserved tracks (off) 11 | * One or more data blocks (dsm+1), power of 2 at least 1K 12 | * Directory starts with block 0 13 | * Directory has (drm+1) entries 14 | * Spare sectors (ignored by CP/M) 15 | * 16 | * CP/M 1.4 on 8 inch 250.25K disk: 17 | * 77 tracks 18 | * 26 128-byte sectors per track, software skewed 19 | * skew table [1,7,13,19,25,5,11,17,23,3,9,15,21,2,8,14,20,26,6,12,18,24,4,10,16,22] 20 | * 2 reserved tracks 21 | * 2 1K directory blocks, giving 64 directory entries 22 | * 243 1K data blocks numbered 2 - 242 23 | * 6 extra sectors 24 | */ 25 | 26 | struct cpm_dpb { 27 | unsigned short spt; /* 128 byte sectors per track (26) */ 28 | unsigned char bsh; /* Block shift: 3 = 1K, 4 = 2K, etc. (3) */ 29 | unsigned char blm; /* Block mask: 0x7 = 1K, 0xF = 2K, etc. (7) */ 30 | unsigned char exm; /* Extent mask: full record count = 128 * (EX & exm) + rc */ 31 | unsigned short dsm; /* Number of blocks on the disk - 1 */ 32 | unsigned short drm; /* Number of directory entries - 1 */ 33 | unsigned char al0; /* Directory allocation bitmap, first byte */ 34 | unsigned char al1; /* Directory allocation bitmap, second byte */ 35 | unsigned char cks; /* Checksum vector size: 0 for fixed disk */ 36 | unsigned short off; /* Offset, number of reserved tracks */ 37 | unsigned char skew[64]; /* Skew table */ 38 | }; 39 | 40 | struct cpm_dpb dpb_fd = { 41 | /* SPT */ 26, 42 | /* BSH */ 3, 43 | /* BLM */ 7, 44 | /* EXM */ 0, 45 | /* DSM */ 242, 46 | /* DRM */ 63, 47 | /* AL0 */ 192, 48 | /* AL1 */ 0, 49 | /* CKS */ 16, 50 | /* OFF */ 2, 51 | /* Skew */ { 0, 6, 12, 18, 24, 4, 10, 16, 22, 2, 8, 14, 20, 52 | 1, 7, 13, 19, 25, 5, 11, 17, 23, 3, 9, 15, 21 } 53 | }; 54 | 55 | struct cpm_dpb dpb_hd = { 56 | /* SPT */ 64, 57 | /* BSH */ 4, 58 | /* BLM */ 15, 59 | /* EXM */ 0, 60 | /* DSM */ 2441, 61 | /* DRM */ 1023, 62 | /* AL0 */ 255, 63 | /* AL1 */ 255, 64 | /* CKS */ 0, 65 | /* OFF */ 2, 66 | /* Skew */ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 67 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 68 | 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 69 | 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 70 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 71 | 60, 61, 62, 63 } 72 | }; 73 | 74 | /* Pointer to drive parameter block */ 75 | struct cpm_dpb *dpb = &dpb_fd; 76 | 77 | /* Starting sector of directory */ 78 | #define SECTOR_DIR 0 79 | 80 | /* Number of directory sectors */ 81 | #define SECTOR_DIR_SIZE ((dpb->drm + 1) * 32 / SECTOR_SIZE) 82 | 83 | /* Sectors per block */ 84 | #define SECTORS_PER_BLOCK (1 << dpb->bsh) 85 | 86 | /* True for a big disk: use two bytes for allocation map entries */ 87 | #define BIG_DISK (dpb->dsm > 255) 88 | 89 | /* Max sectors for one extent */ 90 | #define SECTORS_PER_EXTENT ((BIG_DISK ? 8 : 16) * SECTORS_PER_BLOCK) 91 | 92 | /* Blocks per extent */ 93 | #define BLOCKS_PER_EXTENT (BIG_DISK ? 8 : 16) 94 | 95 | /* Compute extent number of a directory entry */ 96 | #define EXTENT_NO(d) (((0x1f & (d)->ex) + 32 * (d)->s2) / (dpb->exm + 1)) 97 | 98 | /* Compute record count of a directory entry */ 99 | #define RC(d) (128 * ((d)->ex & dpb->exm) + (d)->rc) 100 | 101 | 102 | /* Directory entry size */ 103 | #define ENTRY_SIZE 32 104 | 105 | /* CP/M 2.2 disk parameter block offsets */ 106 | 107 | #define CPM_WORD_DPB_SPT 0 108 | #define CPM_BYTE_DPB_BSH 2 109 | #define CPM_BYTE_DPB_BLM 3 110 | #define CPM_BYTE_DPB_EXM 4 111 | #define CPM_WORD_DPB_DSM 5 112 | #define CPM_WORD_DPB_DRM 7 113 | #define CPM_BYTE_DPB_AL0 9 114 | #define CPM_BYTE_DPB_AL1 10 115 | #define CPM_WORD_DPB_CKS 11 116 | #define CPM_WORD_DPB_OFF 13 117 | 118 | /* CP/M 2.2 directory entry offsets */ 119 | 120 | #define CPM_BYTE_DIR_UU 0 121 | #define CPM_BYTE8_DIR_F 1 122 | #define CPM_BYTE3_DIR_T 9 123 | #define CPM_BYTE_DIR_EX 12 124 | #define CPM_BYTE_DIR_S1 13 125 | #define CPM_BYTE_DIR_S2 14 126 | #define CPM_BYTE_DIR_RC 15 127 | #define CPM_BYTE16_DIR_AL 16 128 | 129 | struct dirent { 130 | unsigned char uu; /* User number: 0xE5 means deleted */ 131 | unsigned char f[8]; /* File name */ 132 | unsigned char t[3]; /* File type t[0].7=readonly, t[1].7=hidden */ 133 | unsigned char ex; /* Extent counter: 0-31 */ 134 | unsigned char s1; /* Reserved, set to 0 */ 135 | unsigned char s2; /* Extent counter high byte */ 136 | unsigned char rc; /* Number of records in this extent (0x80 for full extent) */ 137 | unsigned char al[16]; /* Allocation map: 0 means free, otherwise block number */ 138 | }; 139 | 140 | /* Extent number is (32*s2 + ex) / (exm+1) 141 | * Record count is (ex & exm)*128 + rc 142 | * Basically non-zero exm means extra rc bits are stuffed into the extent number 143 | * This could be needed to handle large block sizes, where there could be more than 144 | * 128 records in a single extent. 145 | */ 146 | 147 | FILE *disk; 148 | 149 | /* Get sector. Skip reserved tracks. Deskew. */ 150 | 151 | void getsect(unsigned char *buf, int sect) 152 | { 153 | int track = sect / dpb->spt; 154 | int sector = sect % dpb->spt; 155 | sector = dpb->skew[sector]; /* De-interleave sectors */ 156 | track += dpb->off; /* Skip over reserved tracks */ 157 | fseek(disk, (track * dpb->spt + sector) * SECTOR_SIZE, SEEK_SET); 158 | fread((char *)buf, SECTOR_SIZE, 1, disk); 159 | } 160 | 161 | void putsect(unsigned char *buf, int sect) 162 | { 163 | int track = sect / dpb->spt; 164 | int sector = sect % dpb->spt; 165 | sector = dpb->skew[sector]; /* De-interleave sectors */ 166 | track += dpb->off; /* Skip over reserved tracks */ 167 | fseek(disk, (track * dpb->spt + sector) * SECTOR_SIZE, SEEK_SET); 168 | fwrite((char *)buf, SECTOR_SIZE, 1, disk); 169 | } 170 | 171 | int lower(int c) 172 | { 173 | if (c >= 'A' && c <= 'Z') 174 | return c - 'A' + 'a'; 175 | else 176 | return c; 177 | } 178 | 179 | /* Convert file name from directory into UNIX zero-terminated C string name */ 180 | 181 | char *getname(struct dirent *d) 182 | { 183 | static char s[50]; 184 | int p = 0; 185 | int r; 186 | int i; 187 | /* Get name */ 188 | for (i = 0; i != sizeof(d->f); i++) { 189 | s[p++] = lower(d->f[i]); 190 | } 191 | /* Zap trailing spaces */ 192 | while (p && s[p - 1] == ' ') --p; 193 | /* Append '.' */ 194 | s[p++] = '.'; 195 | r = p; 196 | /* Get extension */ 197 | for (i = 0; i != sizeof(d->t); i++) { 198 | s[p++] = lower(d->t[i]); 199 | } 200 | /* Zap tailing spaces */ 201 | while (p && s[p - 1] == ' ') --p; 202 | /* Zap '.' if no extension */ 203 | if (p == r) --p; 204 | /* Terminate */ 205 | s[p] = 0; 206 | return s; 207 | } 208 | 209 | /* Write UNIX name into Atari directory entry */ 210 | 211 | void putname(struct dirent *d, char *name) 212 | { 213 | int x; 214 | /* Copy file name into directory entry */ 215 | x = 0; 216 | while (*name && *name != '.' && x < 8) { 217 | if (*name >= 'a' && *name <= 'z') 218 | d->f[x++] = *name++ - 'a' + 'A'; 219 | else 220 | d->f[x++] = *name++; 221 | } 222 | while (x < 8) { 223 | d->f[x++] = ' '; 224 | } 225 | x = 0; 226 | while (*name && *name != '.') 227 | ++name; 228 | if (*name == '.') { 229 | ++name; 230 | while (*name && x < 3) { 231 | if (*name >= 'a' && *name <= 'z') 232 | d->t[x++] = *name++ - 'a' + 'A'; 233 | else 234 | d->t[x++] = *name++; 235 | } 236 | } 237 | while (x < 3) { 238 | d->t[x++] = ' '; 239 | } 240 | } 241 | 242 | /* Internal version of name for nice listing */ 243 | 244 | struct name 245 | { 246 | char *name; 247 | 248 | /* From directory entry */ 249 | int locked; /* Set if write-protected */ 250 | int sector; /* Starting sector of file */ 251 | int sects; /* Sector count */ 252 | 253 | int is_sys; /* Set if it's a .SYS file */ 254 | int is_cm; /* Set if it's a .COM file */ 255 | 256 | /* From file itself */ 257 | int load_start; 258 | int load_size; 259 | int init; 260 | int run; 261 | int size; 262 | }; 263 | 264 | struct name *names[1024]; 265 | int name_n; 266 | 267 | int comp(struct name **l, struct name **r) 268 | { 269 | return strcmp((*l)->name, (*r)->name); 270 | } 271 | 272 | /* Return list of free directory entries (extents) for a file */ 273 | 274 | int alloc_extents(int *list, int extents) 275 | { 276 | unsigned char buf[SECTOR_SIZE]; 277 | int x; 278 | for (x = SECTOR_DIR; x != SECTOR_DIR + SECTOR_DIR_SIZE; ++x) { 279 | int y; 280 | getsect(buf, x); 281 | for (y = 0; y != SECTOR_SIZE; y += ENTRY_SIZE) { 282 | struct dirent *d = (struct dirent *)(buf + y); 283 | if (d->uu == 0xE5) { 284 | *list++ = x * (SECTOR_SIZE / ENTRY_SIZE) + (y / ENTRY_SIZE); 285 | if (!--extents) 286 | return 0; 287 | } 288 | } 289 | } 290 | return -1; 291 | } 292 | 293 | /* get allocation map (index by block number) */ 294 | 295 | unsigned short *alloc_map; /* 0xFFFF is free, 0xFFFE allocated for directory, otherwise entry no. */ 296 | 297 | void get_map() 298 | { 299 | unsigned char buf[SECTOR_SIZE]; 300 | int x; 301 | int entry_no; 302 | if (!alloc_map) { 303 | alloc_map = malloc(sizeof(alloc_map[0]) * (dpb->dsm + 1)); 304 | /* Initialize to all free */ 305 | for (x = 0; x != dpb->dsm + 1; ++x) 306 | alloc_map[x] = 0xFFFF; 307 | /* Reserve space for directory */ 308 | for (x = 0; x != (dpb->drm + 1) / ((SECTOR_SIZE << dpb->bsh) / ENTRY_SIZE); ++x) 309 | alloc_map[x] = 0xFFFE; 310 | } 311 | entry_no = 0; 312 | for (x = 0; x != SECTOR_DIR_SIZE; ++x) { 313 | int y; 314 | /* fprintf(stderr, "sector %d\n", x); */ 315 | getsect(buf, x + SECTOR_DIR); 316 | for (y = 0; y != SECTOR_SIZE; y += ENTRY_SIZE) { 317 | struct dirent *d = (struct dirent *)(buf + y); 318 | if (d->uu < 0x20) { /* d->uu != 0xe5 (date stamp is 0x21) */ 319 | int z; 320 | /* 321 | char *s = getname(d); 322 | printf("%d %s\n", entry_no, s); */ 323 | for (z = 0; z != 16; ++z) { 324 | int blk; 325 | if (BIG_DISK) { 326 | blk = d->al[z] + (256 * d->al[z + 1]); 327 | ++z; 328 | } else { 329 | blk = d->al[z]; 330 | } 331 | if (blk) { 332 | if (blk >= dpb->dsm + 1) { 333 | fprintf(stderr, "Entry %d: Found block number (%d) exceeding device size\n", entry_no, blk); 334 | } else if (alloc_map[blk] != 0xFFFF) { 335 | fprintf(stderr, "Entry %d: Found doubly allocated block number (%d) by entry %d\n", entry_no, blk, alloc_map[blk]); 336 | } else { 337 | /* Record directory entry number */ 338 | /* printf("setting %d\n", d->al[z]); */ 339 | alloc_map[blk] = entry_no; 340 | } 341 | } else { 342 | break; 343 | } 344 | } 345 | } 346 | ++entry_no; 347 | } 348 | } 349 | } 350 | 351 | /* Allocate a block. Returns block number or -1 for out of space. */ 352 | 353 | int alloc_block(int entry_no) 354 | { 355 | int x; 356 | if (!alloc_map) 357 | get_map(); 358 | for (x = 0; x != (dpb->dsm + 1); ++x) 359 | if (alloc_map[x] == 0xFFFF) { 360 | alloc_map[x] = entry_no; 361 | return x; 362 | } 363 | return -1; 364 | } 365 | 366 | /* Count free blocks */ 367 | 368 | int amount_free(void) 369 | { 370 | int count = 0; 371 | int x; 372 | if (!alloc_map) 373 | get_map(); 374 | for (x = 0; x != (dpb->dsm + 1); ++x) 375 | if (alloc_map[x] == 0xFFFF) { 376 | ++count; 377 | } 378 | return count; 379 | } 380 | 381 | /* Get directory entry with specific name and extent number 382 | * (or delete all entries with specified name if del is set) 383 | * Returns 0 if found, -1 if not found. 384 | */ 385 | 386 | int find_file(struct dirent *dir, char *filename, int ex, int del) 387 | { 388 | unsigned char buf[SECTOR_SIZE]; 389 | int x; 390 | int flg = -1; 391 | for (x = 0; x != SECTOR_DIR_SIZE; ++x) { 392 | int y; 393 | getsect(buf, x + SECTOR_DIR); 394 | for (y = 0; y != SECTOR_SIZE; y += ENTRY_SIZE) { 395 | struct dirent *d = (struct dirent *)(buf + y); 396 | if (d->uu < 0x20 && (del || ex == EXTENT_NO(d))) { 397 | char *s = getname(d); 398 | if (!strcmp(s, filename)) { 399 | if (del) { 400 | d->uu = 0xe5; 401 | flg = 0; 402 | putsect(buf, x + SECTOR_DIR); 403 | } else { 404 | memcpy(dir, d, ENTRY_SIZE); 405 | flg = 0; 406 | return 0; 407 | } 408 | } 409 | } 410 | } 411 | } 412 | return flg; 413 | } 414 | 415 | /* Read a file: provide with first extent */ 416 | 417 | int read_file(char *filename, struct dirent *dir, FILE *f) 418 | { 419 | int rtn = 0; 420 | unsigned char buf[SECTOR_SIZE]; 421 | int exno = 0; /* Extent number */ 422 | for (;;) { 423 | int recno; /* Record number within extent */ 424 | for (recno = 0; recno != RC(dir) && recno != SECTORS_PER_EXTENT; ++recno) { 425 | int blkno; /* Block number within extent */ 426 | int recblk; /* Record number within block */ 427 | int blk; /* Current block */ 428 | blkno = (recno >> dpb->bsh); 429 | recblk = (recno & dpb->blm); 430 | if (BIG_DISK) 431 | blk = dir->al[blkno * 2] + 256 * dir->al[blkno * 2 + 1]; 432 | else 433 | blk = dir->al[blkno]; 434 | if (blk) { 435 | getsect(buf, (blk << dpb->bsh) + recblk); 436 | fwrite(buf, SECTOR_SIZE, 1, f); 437 | } else { 438 | fprintf(stderr, "allocation map ran out before cr count reached!\n"); 439 | rtn = -1; 440 | break; 441 | } 442 | } 443 | if (RC(dir) != SECTORS_PER_EXTENT) { 444 | break; 445 | } else { 446 | ++exno; 447 | if (find_file(dir, filename, exno, 0)) { 448 | /* fprintf(stderr, "can't find next extent!\n"); 449 | rtn = -1; */ 450 | /* This is normal for case where file is maximum extent size! */ 451 | break; 452 | } 453 | } 454 | } 455 | return rtn; 456 | } 457 | 458 | /* cat a file */ 459 | 460 | void cat(char *name) 461 | { 462 | struct dirent dir[1]; 463 | if (find_file(dir, name, 0, 0)) { 464 | printf("File '%s' not found\n", name); 465 | exit(-1); 466 | } else { 467 | /* printf("Found file. Sector of rib is %d\n", sector); */ 468 | read_file(name, dir, stdout); 469 | } 470 | } 471 | /* get a file from the disk */ 472 | 473 | int get_file(char *atari_name, char *local_name) 474 | { 475 | struct dirent dir[1]; 476 | if (find_file(dir, atari_name, 0, 0)) { 477 | printf("File '%s' not found\n", atari_name); 478 | return -1; 479 | } else { 480 | FILE *f = fopen(local_name, "w"); 481 | if (!f) { 482 | printf("Couldn't open local file '%s'\n", local_name); 483 | return -1; 484 | } 485 | /* printf("Found file. Sector of rib is %d\n", sector); */ 486 | read_file(atari_name, dir, f); 487 | if (fclose(f)) { 488 | printf("Couldn't close local file '%s'\n", local_name); 489 | return -1; 490 | } 491 | return 0; 492 | } 493 | } 494 | 495 | /* Delete file name */ 496 | 497 | int rm(char *name, int ignore) 498 | { 499 | struct dirent dir[1]; 500 | if (!find_file(dir, name, 0, 1)) { 501 | return 0; 502 | } else { 503 | if (!ignore) 504 | printf("File '%s' not found\n", name); 505 | return -1; 506 | } 507 | } 508 | 509 | /* Free command */ 510 | 511 | int do_free(void) 512 | { 513 | int amount = amount_free() * SECTORS_PER_BLOCK; 514 | printf("%d free sectors, %d free bytes\n", amount, amount * SECTOR_SIZE); 515 | return 0; 516 | } 517 | 518 | /* Pre-allocate space for file */ 519 | 520 | int alloc_space(int *list, int blocks) 521 | { 522 | while (blocks) { 523 | int x = alloc_block(0xFFFD); 524 | if (x == -1) { 525 | printf("Not enough space\n"); 526 | return -1; 527 | } else { 528 | *list++ = x; 529 | } 530 | --blocks; 531 | } 532 | return 0; 533 | } 534 | 535 | /* Write a directory entry */ 536 | 537 | int write_dir(char *name, int extentno, int extent, int rc, int *al) 538 | { 539 | int z, i; 540 | struct dirent d[1]; 541 | unsigned char buf[SECTOR_SIZE]; 542 | int sect = extent / (SECTOR_SIZE / ENTRY_SIZE); 543 | int ofst = extent % (SECTOR_SIZE / ENTRY_SIZE); 544 | getsect(buf, sect); 545 | putname(d, name); 546 | extentno *= (dpb->exm + 1); 547 | while (rc > 128) { 548 | rc -= 128; 549 | ++extentno; 550 | } 551 | d->uu = 0; 552 | d->s1 = 0; 553 | d->ex = (extentno % 32); 554 | d->s2 = (extentno / 32); 555 | d->rc = rc; 556 | for (i = z = 0; z != 16; ++z) { 557 | if (BIG_DISK) { 558 | d->al[z++] = al[i]; 559 | d->al[z] = (al[i] >> 8); 560 | } else { 561 | d->al[z] = al[i++]; 562 | } 563 | } 564 | /* printf("%s uu=%d s1=%d s2=%d ex=%d rc=%d\n", getname(d), d->uu, d->s1, d->s2, d->ex, d->rc); */ 565 | memcpy(buf + ofst * ENTRY_SIZE, d, ENTRY_SIZE); 566 | putsect(buf, sect); 567 | return 0; 568 | } 569 | 570 | /* Write a file */ 571 | 572 | int write_file(char *name, unsigned char *buf, long sects) 573 | { 574 | int x, z; 575 | int *blk_list; 576 | int *extent_list; 577 | long blks; /* Number of blocks needed for this file */ 578 | long extents; /* Number of extents needed for this file */ 579 | int rc; /* Counter for current extent */ 580 | int secno; /* Sector number within extent */ 581 | int extent_blkno; /* Block number within extent */ 582 | int extentno; /* Extent number of file */ 583 | int blkno; /* Block number */ 584 | int al[16]; /* Allocation map for current extent */ 585 | 586 | 587 | /* Compute number of blocks needed for file */ 588 | blks = (sects + SECTORS_PER_BLOCK - 1) / SECTORS_PER_BLOCK; 589 | 590 | /* Compute number of extents needed for file */ 591 | extents = ((blks * SECTORS_PER_BLOCK) + SECTORS_PER_EXTENT - 1) / SECTORS_PER_EXTENT; 592 | /* Force at least one extent for case of empty file */ 593 | if (!extents) 594 | extents = 1; 595 | 596 | /* Allocate space for file */ 597 | blk_list = (int *)malloc(sizeof(int) * blks); 598 | if (alloc_space(blk_list, blks)) 599 | return -1; 600 | 601 | /* Allocate extents for file */ 602 | extent_list = (int *)malloc(sizeof(int) * extents); 603 | if (alloc_extents(extent_list, extents)) 604 | return -1; 605 | 606 | /* Write file */ 607 | for (z = 0; z != 16; ++z) 608 | al[z] = 0; 609 | blkno = 0; 610 | extent_blkno = 0; 611 | extentno = 0; 612 | secno = 0; 613 | rc = 0; 614 | for (x = 0; x != sects; ++x) { 615 | al[extent_blkno] = blk_list[blkno]; 616 | putsect(buf + x * SECTOR_SIZE, (blk_list[blkno] << dpb->bsh) + secno); 617 | ++rc; 618 | if (++secno == SECTORS_PER_BLOCK) { 619 | secno = 0; 620 | ++blkno; 621 | 622 | if (++extent_blkno == BLOCKS_PER_EXTENT) { 623 | /* Write current extent */ 624 | write_dir(name, extentno, extent_list[extentno], rc, al); 625 | ++extentno; 626 | rc = 0; 627 | for (z = 0; z != 16; ++z) 628 | al[z] = 0; 629 | extent_blkno = 0; 630 | } 631 | } 632 | } 633 | if (rc || !extentno) { 634 | /* Write final extent */ 635 | write_dir(name, extentno, extent_list[extentno], rc, al); 636 | } 637 | 638 | return 0; 639 | } 640 | 641 | /* Put a file on the disk */ 642 | 643 | int put_file(char *local_name, char *atari_name) 644 | { 645 | FILE *f = fopen(local_name, "r"); 646 | long x, size; /* File size in bytes */ 647 | long up; /* Size in bytes rounded up to sectors */ 648 | unsigned char *buf; 649 | int rtn; 650 | if (!f) { 651 | printf("Couldn't open '%s'\n", local_name); 652 | return -1; 653 | } 654 | if (fseek(f, 0, SEEK_END)) { 655 | printf("Couldn't get file size of '%s'\n", local_name); 656 | fclose(f); 657 | return -1; 658 | } 659 | size = ftell(f); 660 | if (size < 0) { 661 | printf("Couldn't get file size of '%s'\n", local_name); 662 | fclose(f); 663 | return -1; 664 | } 665 | rewind(f); 666 | /* Round up to a multiple of (SECTOR_SIZE) */ 667 | up = size + (SECTOR_SIZE) - 1; 668 | up -= up % (SECTOR_SIZE); 669 | 670 | buf = (unsigned char *)malloc(up); 671 | if (fread(buf, 1, size, f) != (size_t)size) { 672 | printf("Couldn't read file '%s'\n", local_name); 673 | fclose(f); 674 | free(buf); 675 | return -1; 676 | } 677 | fclose(f); 678 | 679 | /* Fill with ^Zs to end of sector */ 680 | for (x = size; x != up; ++x) 681 | buf[x] = 0x1a; 682 | 683 | /* Delete existing file */ 684 | rm(atari_name, 1); 685 | 686 | /* Allocate space and write file */ 687 | rtn = write_file(atari_name, buf, up / SECTOR_SIZE); 688 | 689 | if (rtn) { 690 | printf("Couldn't write file\n"); 691 | return -1; 692 | } 693 | 694 | return 0; 695 | } 696 | 697 | /* Get file size in sectors */ 698 | 699 | int get_info(struct dirent *dir, char *name) 700 | { 701 | int rtn = 0; 702 | int count = 0; 703 | int exno = 0; /* Extent number */ 704 | /* fprintf(stderr, "info for %s\n", name); */ 705 | for (;;) { 706 | int recno; /* Record number within extent */ 707 | int cnt = 0; 708 | /* fprintf(stderr, "extent %d rc=%d\n", exno, dir->rc); */ 709 | for (recno = 0; recno != RC(dir) && recno != SECTORS_PER_EXTENT; ++recno) { 710 | int blkno; /* Block number within extent */ 711 | int blk; /* Current block */ 712 | blkno = (recno >> dpb->bsh); 713 | if (BIG_DISK) 714 | blk = dir->al[blkno * 2] + 256 * dir->al[blkno * 2 + 1]; 715 | else 716 | blk = dir->al[blkno]; 717 | if (blk) { 718 | ++count; 719 | ++cnt; 720 | } else { 721 | fprintf(stderr,"allocation map ran out before rc count reached! %d\n", recno); 722 | rtn = -1; 723 | break; 724 | } 725 | } 726 | /* fprintf(stderr, " count=%d\n", cnt); */ 727 | if (RC(dir) != SECTORS_PER_EXTENT) { 728 | /* Must be the end */ 729 | break; 730 | } else { 731 | ++exno; 732 | if (find_file(dir, name, exno, 0)) { 733 | /* fprintf(stderr, "%s: can't find next extent (%d)!\n", name, exno); 734 | rtn = -1; */ 735 | break; 736 | } 737 | } 738 | } 739 | if (rtn) 740 | return -1; 741 | else 742 | return count; 743 | } 744 | 745 | void atari_dir(int all, int full, int single) 746 | { 747 | unsigned char buf[SECTOR_SIZE]; 748 | struct dirent dir[1]; 749 | int x, y; 750 | int rows; 751 | int cols = (80 / 13); 752 | for (x = 0; x != SECTOR_DIR_SIZE; ++x) { 753 | int y; 754 | getsect(buf, x + SECTOR_DIR); 755 | for (y = 0; y != SECTOR_SIZE; y += ENTRY_SIZE) { 756 | struct dirent *d = (struct dirent *)(buf + y); 757 | if (d->uu < 0x20 && EXTENT_NO(d) == 0) { 758 | struct name *nam; 759 | char *s = getname(d); 760 | nam = (struct name *)malloc(sizeof(struct name)); 761 | nam->name = strdup(s); 762 | if (d->t[0] & 0x80) 763 | nam->locked = 1; 764 | else 765 | nam->locked = 0; 766 | if (d->t[1] & 0x80) 767 | nam->is_sys = 1; 768 | else 769 | nam->is_sys = 0; 770 | nam->sector = 0; 771 | nam->sects = 0; 772 | nam->load_start = -1; 773 | nam->load_size = -1; 774 | nam->init = -1; 775 | nam->run = -1; 776 | nam->size = -1; 777 | memcpy(dir, d, ENTRY_SIZE); 778 | nam->sects = get_info(dir, nam->name); 779 | nam->size = nam->sects * SECTOR_SIZE; 780 | 781 | if ((all || !nam->is_sys)) 782 | names[name_n++] = nam; 783 | } 784 | } 785 | } 786 | qsort(names, name_n, sizeof(struct name *), (int (*)(const void *, const void *))comp); 787 | 788 | if (full) { 789 | int totals = 0; 790 | int total_bytes = 0; 791 | printf("\n"); 792 | for (x = 0; x != name_n; ++x) { 793 | if (names[x]->load_start != -1) 794 | printf("-r%c%c%c %6d (%3d) %-13s (load_start=$%x load_end=$%x)\n", 795 | (names[x]->locked ? '-' : 'w'), 796 | (names[x]->is_cm ? 'x' : '-'), 797 | (names[x]->is_sys ? 's' : '-'), 798 | names[x]->size, names[x]->sects, names[x]->name, names[x]->load_start, names[x]->load_start + names[x]->load_size - 1); 799 | else 800 | printf("-r%c%c%c %6d (%3d) %-13s\n", 801 | (names[x]->locked ? '-' : 'w'), 802 | (names[x]->is_cm ? 'x' : '-'), 803 | (names[x]->is_sys ? 's' : '-'), 804 | names[x]->size, names[x]->sects, names[x]->name); 805 | totals += names[x]->sects; 806 | total_bytes += names[x]->size; 807 | } 808 | printf("\n%d entries\n", name_n); 809 | printf("\n%d sectors, %d bytes\n", totals, total_bytes); 810 | printf("\n"); 811 | do_free(); 812 | printf("\n"); 813 | } else if (single) { 814 | int x; 815 | for (x = 0; x != name_n; ++x) { 816 | printf("%s\n", names[x]->name); 817 | } 818 | } else { 819 | 820 | /* Rows of 12 names each ordered like ls */ 821 | 822 | rows = (name_n + cols - 1) / cols; 823 | 824 | for (y = 0; y != rows; ++y) { 825 | for (x = 0; x != cols; ++x) { 826 | int n = y + x * rows; 827 | /* printf("%11d ", n); */ 828 | if (n < name_n) 829 | printf("%-12s ", names[n]->name); 830 | else 831 | printf(" "); 832 | } 833 | printf("\n"); 834 | } 835 | } 836 | } 837 | 838 | int mkfs() 839 | { 840 | unsigned char buf[SECTOR_SIZE]; 841 | int tracks, x, n; 842 | for (x = 0; x != SECTOR_SIZE; ++x) 843 | buf[x] = 0xe5; 844 | tracks = dpb->off + (((dpb->dsm + 1) << dpb->bsh) + dpb->spt - 1) / dpb->spt; 845 | n = tracks * dpb->spt; 846 | printf("%d tracks\n", tracks); 847 | printf("%d sectors\n", n); 848 | for (x = 0; x != n; ++x) 849 | fwrite((char *)buf, SECTOR_SIZE, 1, disk); 850 | return 0; 851 | } 852 | 853 | int main(int argc, char *argv[]) 854 | { 855 | int all = 0; 856 | int full = 0; 857 | int single = 0; 858 | int x; 859 | long size; 860 | char *disk_name; 861 | dpb = &dpb_fd; 862 | x = 1; 863 | if (x == argc || !strcmp(argv[x], "--help") || !strcmp(argv[x], "-h")) { 864 | printf("\nCP/M disk image tool\n"); 865 | printf("\n"); 866 | printf("Syntax: cpmtool path-to-disk-image [command] [args]\n"); 867 | printf("\n"); 868 | printf(" Commands: (default is ls)\n\n"); 869 | printf(" ls [-la1] Directory listing\n"); 870 | printf(" -l for long\n"); 871 | printf(" -a to show system files\n"); 872 | printf(" -1 to show a single name per line\n\n"); 873 | printf(" cat cpm-name Type file to console\n\n"); 874 | printf(" get cpm-name [local-name] Copy file from diskette to local-name\n\n"); 875 | printf(" put local-name [cpm-name] Copy file from local-name to diskette\n\n"); 876 | printf(" free Print amount of free space\n\n"); 877 | printf(" rm cpm-name Delete a file\n\n"); 878 | printf(" mkfs Format disk\n\n"); 879 | return -1; 880 | } 881 | disk_name = argv[x++]; 882 | 883 | if (x != argc && !strcmp(argv[x], "mkfs")) { 884 | disk = fopen(disk_name, "w"); 885 | if (!disk) { 886 | printf("Couldn't open '%s'\n", disk_name); 887 | return -1; 888 | } 889 | mkfs(); 890 | fclose(disk); 891 | return 0; 892 | } 893 | 894 | disk = fopen(disk_name, "r+"); 895 | if (!disk) { 896 | printf("Couldn't open '%s'\n", disk_name); 897 | return -1; 898 | } 899 | if (fseek(disk, 0, SEEK_END)) { 900 | printf("Couldn't seek disk?\n"); 901 | return -1; 902 | } 903 | size = ftell(disk); 904 | if (size == 128 * 77 * 26) { /* Single sided floppy */ 905 | dpb = &dpb_fd; 906 | } else { 907 | fprintf(stderr, "assuming hard drive\n"); 908 | dpb = &dpb_hd; 909 | } 910 | 911 | /* Directory options */ 912 | dir: 913 | while (x != argc && argv[x][0] == '-') { 914 | int y; 915 | for (y = 1;argv[x][y];++y) { 916 | int opt = argv[x][y]; 917 | switch (opt) { 918 | case 'l': full = 1; break; 919 | case 'a': all = 1; break; 920 | case '1': single = 1; break; 921 | default: printf("Unknown option '%c'\n", opt); return -1; 922 | } 923 | } 924 | ++x; 925 | } 926 | 927 | if (x == argc) { 928 | /* Just print a directory listing */ 929 | atari_dir(all, full, single); 930 | return 0; 931 | } else if (!strcmp(argv[x], "ls")) { 932 | ++x; 933 | goto dir; 934 | } else if (!strcmp(argv[x], "free")) { 935 | return do_free(); 936 | /* } else if (!strcmp(argv[x], "check")) { 937 | return do_check(); */ 938 | } else if (!strcmp(argv[x], "cat")) { 939 | ++x; 940 | if (x == argc) { 941 | printf("Missing file name to cat\n"); 942 | return -1; 943 | } else { 944 | cat(argv[x++]); 945 | return 0; 946 | } 947 | } else if (!strcmp(argv[x], "get")) { 948 | char *local_name; 949 | char *atari_name; 950 | ++x; 951 | if (x == argc) { 952 | printf("Missing file name to get\n"); 953 | return -1; 954 | } 955 | atari_name = argv[x]; 956 | local_name = atari_name; 957 | if (x + 1 != argc) 958 | local_name = argv[++x]; 959 | return get_file(atari_name, local_name); 960 | } else if (!strcmp(argv[x], "put")) { 961 | char *local_name; 962 | char *atari_name; 963 | ++x; 964 | if (x == argc) { 965 | printf("Missing file name to put\n"); 966 | return -1; 967 | } 968 | local_name = argv[x]; 969 | if (strrchr(local_name, '/')) 970 | atari_name = strrchr(local_name, '/') + 1; 971 | else 972 | atari_name = local_name; 973 | printf("%s\n", atari_name); 974 | if (x + 1 != argc) 975 | atari_name = argv[++x]; 976 | return put_file(local_name, atari_name); 977 | } else if (!strcmp(argv[x], "rm")) { 978 | char *name; 979 | ++x; 980 | if (x == argc) { 981 | printf("Missing name to delete\n"); 982 | return -1; 983 | } else { 984 | name = argv[x]; 985 | } 986 | return rm(name, 0); 987 | } else { 988 | printf("Unknown command '%s'\n", argv[x]); 989 | return -1; 990 | } 991 | return 0; 992 | } 993 | -------------------------------------------------------------------------------- /cpmws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/cpmws.png -------------------------------------------------------------------------------- /defs.h: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------*\ 2 | | defs.h -- main definitions for z80 emulator | 3 | | | 4 | | Copyright 1986-1988 by Parag Patel. All Rights Reserved. | 5 | | Copyright 1994-1995 by CodeGen, Inc. All Rights Reserved. | 6 | \*-----------------------------------------------------------------------*/ 7 | 8 | #ifndef __DEFS_H_ 9 | #define __DEFS_H_ 10 | 11 | #include 12 | 13 | /* the current version of the z80 emulator */ 14 | #define VERSION "3.1" 15 | 16 | 17 | /* system definitions */ 18 | #if defined THINK_C || defined applec || defined macintosh 19 | # ifndef macintosh 20 | # define macintosh 21 | # endif 22 | #elif defined __MWERKS__ 23 | # define BeBox 24 | #elif defined MSDOS && defined GO32 25 | # define DJGPP 26 | # ifndef ENDIAN_LITTLE 27 | # define ENDIAN_LITTLE 28 | # endif 29 | #else 30 | # define UNIX /* cannot use "unix" since DJGPP defines it as well */ 31 | #endif 32 | 33 | 34 | /* some headers define macros this way */ 35 | #ifdef BYTE_ORDER 36 | # if BYTE_ORDER == LITTLE_ENDIAN 37 | # ifndef ENDIAN_LITTLE 38 | # define ENDIAN_LITTLE 39 | # endif 40 | # else 41 | # undef ENDIAN_LITTLE 42 | # endif 43 | #endif 44 | 45 | 46 | /* misc. handy defs */ 47 | 48 | #ifndef TRUE 49 | #define TRUE 1 50 | #endif 51 | 52 | #ifndef FALSE 53 | #define FALSE 0 54 | #endif 55 | 56 | typedef int boolean; 57 | 58 | 59 | #define CNTL(c) ((c) & 037) /* convert a char to its control equivalent */ 60 | 61 | 62 | /* handy typedefs for an 8-bit byte, 16-bit word, & 32-bit longword */ 63 | 64 | typedef unsigned char byte; 65 | typedef unsigned short word; 66 | typedef unsigned long longword; 67 | 68 | 69 | /* handy bit definitions - bit fields are not used as they are generally 70 | much slower than the equivalent logical masking operations */ 71 | 72 | #define BIT16 0x10000L 73 | #define BIT15 0x8000 74 | #define BIT14 0x4000 75 | #define BIT13 0x2000 76 | #define BIT12 0x1000 77 | #define BIT11 0x0800 78 | #define BIT10 0x0400 79 | #define BIT9 0x0200 80 | #define BIT8 0x0100 81 | #define BIT7 0x0080 82 | #define BIT6 0x0040 83 | #define BIT5 0x0020 84 | #define BIT4 0x0010 85 | #define BIT3 0x0008 86 | #define BIT2 0x0004 87 | #define BIT1 0x0002 88 | #define BIT0 0x0001 89 | 90 | /* handy masks to get a particular number of bits out */ 91 | 92 | #define MASK1 0x01 93 | #define MASK2 0x03 94 | #define MASK3 0x07 95 | #define MASK4 0x0F 96 | #define MASK5 0x1F 97 | #define MASK6 0x3F 98 | #define MASK7 0x7F 99 | #define MASK8 0xFF 100 | #define MASK12 0xFFF 101 | 102 | #define MASKU4 0xF0 103 | #define MASK16 0xFFFF 104 | 105 | 106 | /* z80 flag register definitions */ 107 | 108 | #define SIGN 0x80 109 | #define ZERO 0x40 110 | #define HALF 0x10 111 | #define PARITY 0x04 112 | #define OVERFLOW PARITY 113 | #define NEGATIVE 0x02 114 | #define CARRY 0x01 115 | 116 | 117 | /* z80 interrupt types - used to set the intr struct var */ 118 | 119 | #define INTRMASK 0xF00 120 | #define INT_FLAG 0x100 121 | #define NM_FLAG 0x200 122 | #define RESET_FLAG 0x400 123 | 124 | 125 | /* max number of the BIOS drive tables */ 126 | #define MAXDISCS 16 127 | 128 | 129 | typedef struct z80info 130 | { 131 | boolean event; 132 | /* byte regaf[2], regbc[2], regde[2], reghl[2]; */ 133 | word regaf, regbc, regde, reghl; 134 | word regaf2, regbc2, regde2, reghl2; 135 | word regsp, regpc, regix, regiy; 136 | byte regi, regr; 137 | byte iff, iff2, imode; 138 | byte reset, nmi, intr, halt; 139 | 140 | /* these point to the addresses of the above registers */ 141 | byte *reg[8]; 142 | word *regpairaf[4]; 143 | word *regpairsp[4]; 144 | word *regpairxy[4]; 145 | word *regixy[2]; 146 | byte *regir[2]; 147 | 148 | /* these are for the I/O, CP/M, and outside needs */ 149 | boolean trace; /* trace mode off/on */ 150 | boolean step; /* step-trace mode off/on */ 151 | int sig; /* caught a signal */ 152 | int syscall; /* CP/M syscall to be done */ 153 | int biosfn; /* BIOS function be done */ 154 | 155 | /* these are for the CP/M BIOS */ 156 | int drive; 157 | word dma; 158 | word track; 159 | word sector; 160 | FILE *drives[MAXDISCS]; 161 | long drivelen[MAXDISCS]; 162 | 163 | /* 64k bytes - may be allocated separately if desired */ 164 | byte mem[0x10000L]; 165 | 166 | #ifdef MEM_BREAK 167 | /* one for each byte of memory for breaks, memory-mapped I/O, etc */ 168 | byte membrk[0x10000L]; 169 | long numbrks; 170 | #endif 171 | } z80info; 172 | 173 | 174 | /* All the following macros assume that a variable named "z80" is 175 | available to access the above info. This is to allow multiple 176 | z80s to run without stepping on each other. 177 | */ 178 | 179 | 180 | /* These macros allow memory-mapped I/O if MEM_BREAK is defined. 181 | Because of this, these macros must be very carefully used, and 182 | there must not be ANY side-effects, such as increment/decerement 183 | in any of the macro args. Customizations go into read_mem() and 184 | write_mem(). 185 | */ 186 | 187 | #ifdef MEM_BREAK 188 | # define MEM(addr) \ 189 | (z80->membrk[(word)(addr)] ? \ 190 | read_mem(z80, addr) : \ 191 | z80->mem[(word)(addr)]) 192 | # define SETMEM(addr, val) \ 193 | (z80->membrk[(word)(addr)] ? \ 194 | write_mem(z80, addr, val) : \ 195 | (z80->mem[(word)(addr)] = (byte)(val))) 196 | 197 | /* various flags for "membrk" - others may be added */ 198 | # define M_BREAKPOINT 0x01 /* breakpoint */ 199 | # define M_READ_PROTECT 0x02 /* read-protected memory */ 200 | # define M_WRITE_PROTECT 0x04 /* write-protected memory */ 201 | # define M_MEM_MAPPED_IO 0x08 /* memory-mapped I/O addr */ 202 | 203 | #else 204 | # define MEM(addr) z80->mem[(word)(addr)] 205 | # define SETMEM(addr, val) (z80->mem[(word)(addr)] = (byte)(val)) 206 | #endif 207 | 208 | 209 | /* how to access the z80 registers & register pairs */ 210 | 211 | #ifdef ENDIAN_LITTLE 212 | # define A ((unsigned char *)&z80->regaf)[1] 213 | # define F ((unsigned char *)&z80->regaf)[0] 214 | # define B ((unsigned char *)&z80->regbc)[1] 215 | # define C ((unsigned char *)&z80->regbc)[0] 216 | # define D ((unsigned char *)&z80->regde)[1] 217 | # define E ((unsigned char *)&z80->regde)[0] 218 | # define H ((unsigned char *)&z80->reghl)[1] 219 | # define L ((unsigned char *)&z80->reghl)[0] 220 | #else 221 | # define A ((unsigned char *)&z80->regaf)[0] 222 | # define F ((unsigned char *)&z80->regaf)[1] 223 | # define B ((unsigned char *)&z80->regbc)[0] 224 | # define C ((unsigned char *)&z80->regbc)[1] 225 | # define D ((unsigned char *)&z80->regde)[0] 226 | # define E ((unsigned char *)&z80->regde)[1] 227 | # define H ((unsigned char *)&z80->reghl)[0] 228 | # define L ((unsigned char *)&z80->reghl)[1] 229 | #endif 230 | 231 | #define I z80->regi 232 | #define R z80->regr 233 | #define AF z80->regaf 234 | #define BC z80->regbc 235 | #define DE z80->regde 236 | #define HL z80->reghl 237 | #define AF2 z80->regaf2 238 | #define BC2 z80->regbc2 239 | #define DE2 z80->regde2 240 | #define HL2 z80->reghl2 241 | #define SP z80->regsp 242 | #define PC z80->regpc 243 | #define IX z80->regix 244 | #define IY z80->regiy 245 | #define IFF z80->iff 246 | #define IFF2 z80->iff2 247 | #define IMODE z80->imode 248 | #define RESET z80->reset 249 | #define NMI z80->nmi 250 | #define INTR z80->intr 251 | #define HALT z80->halt 252 | 253 | #define EVENT z80->event 254 | 255 | 256 | /* function externs: */ 257 | 258 | /* z80.c */ 259 | extern z80info *new_z80info(void); 260 | extern z80info *init_z80info(z80info *z80); 261 | extern z80info *destroy_z80info(z80info *z80); 262 | extern void delete_z80info(z80info *z80); 263 | 264 | extern boolean z80_emulator(z80info *z80, int count); 265 | 266 | extern int nobdos; 267 | 268 | /* main.c */ 269 | extern void resetterm(void); 270 | extern void setterm(void); 271 | extern boolean input(z80info *z80, byte haddr, byte laddr, byte *val); 272 | extern void output(z80info *z80, byte haddr, byte laddr, byte data); 273 | extern void haltcpu(z80info *z80); 274 | extern word read_mem(z80info *z80, word addr); 275 | extern word write_mem(z80info *z80, word addr, byte val); 276 | extern void undefinstr(z80info *z80, byte instr); 277 | extern boolean loadfile(z80info *z80, const char *fname); 278 | 279 | /* bios.c */ 280 | extern void bios(z80info *z80, unsigned int fn); 281 | extern void sysreset(z80info *z80); 282 | extern void warmboot(z80info *z80); 283 | extern void finish(z80info *z80); 284 | extern void command(z80info *z80); 285 | 286 | /* disassem.c */ 287 | extern int disassemlen(void); 288 | extern int disassem(z80info *z80, word start, FILE *fp); 289 | 290 | /* bdos */ 291 | #define BDOS_HOOK 0xDC06 292 | void check_BDOS_hook(z80info *z80); 293 | extern int silent_exit; 294 | extern char *stuff_cmd; 295 | extern int exec; 296 | extern int trace_bdos; 297 | extern int strace; 298 | char *bdos_decode(int n); 299 | int bdos_fcb(int n); 300 | void bdos_fcb_dump(z80info *z80); 301 | 302 | #endif /* __DEFS_H_ */ 303 | -------------------------------------------------------------------------------- /disassem.c: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------*\ 2 | | disassem.c -- Z80 disassembler | 3 | | | 4 | | Originally by T.J. Merritt but modified and debugged to run in the | 5 | | Z80 emulator instead of being standalone. | 6 | | | 7 | | Copyright 1986-1988 by Parag Patel. All Rights Reserved. | 8 | | Copyright 1994-1995 by CodeGen, Inc. All Rights Reserved. | 9 | \*-----------------------------------------------------------------------*/ 10 | 11 | 12 | #include 13 | #include 14 | #include "defs.h" 15 | 16 | 17 | #define bits(l,r) (((z80->mem[loc]) >> (r)) & mask[(l) - (r)]) 18 | #define put_cc(cc) put_str(cc_names[cc]) 19 | 20 | #define OPC_LD "LD " 21 | #define OPC_INC "INC " 22 | #define OPC_DEC "DEC " 23 | #define OPC_ADD "ADD " 24 | #define OPC_RET "RET " 25 | #define OPC_POP "POP " 26 | #define OPC_EXX "EXX " 27 | #define OPC_JP "JP " 28 | #define OPC_OUT "OUT " 29 | #define OPC_IN "IN " 30 | #define OPC_EX "EX " 31 | #define OPC_DI "DI " 32 | #define OPC_EI "EI " 33 | #define OPC_CALL "CALL " 34 | #define OPC_PUSH "PUSH " 35 | #define OPC_ILLEGAL "***" 36 | #define OPC_RST "RST " 37 | 38 | static int 39 | mask[] = 40 | { 41 | 0x001, 42 | 0x003, 43 | 0x007, 44 | 0x00F, 45 | 0x01F, 46 | 0x03F, 47 | 0x07F, 48 | 0x0FF 49 | }; 50 | 51 | static char * 52 | rr_names[] = 53 | { 54 | "B", 55 | "C", 56 | "D", 57 | "E", 58 | "H", 59 | "L", 60 | "(HL)", 61 | "A" 62 | }; 63 | 64 | static char * 65 | dd_names[] = 66 | { 67 | "BC", 68 | "DE", 69 | "HL", 70 | "SP" 71 | }; 72 | 73 | static char * 74 | qq_names[] = 75 | { 76 | "BC", 77 | "DE", 78 | "HL", 79 | "AF" 80 | }; 81 | 82 | static char * 83 | cc_names[] = 84 | { 85 | "NZ", 86 | "Z", 87 | "NC", 88 | "C", 89 | "PO", 90 | "PE", 91 | "P", 92 | "M" 93 | }; 94 | 95 | static char * 96 | op_names[] = 97 | { 98 | "ADD A,", 99 | "ADC A,", 100 | "SUB ", 101 | "SBC A,", 102 | "AND ", 103 | "XOR ", 104 | "OR ", 105 | "CP ", 106 | }; 107 | 108 | static char * 109 | jr_op_names[] = 110 | { 111 | 0, 112 | 0, 113 | "DJNZ ", 114 | "JR ", 115 | "JR NZ,", 116 | "JR Z,", 117 | "JR NC,", 118 | "JR C," 119 | }; 120 | 121 | static char * 122 | log_op_names[] = 123 | { 124 | "RLCA", 125 | "RRCA", 126 | "RLA", 127 | "RRA", 128 | "DAA", 129 | "CPL", 130 | "SCF", 131 | "CCF" 132 | }; 133 | 134 | static char * 135 | shf_op_names[] = 136 | { 137 | "RLC ", 138 | "RRC ", 139 | "RL ", 140 | "RR ", 141 | "SLA ", 142 | "SRA ", 143 | "SLL? ", 144 | "SRL " 145 | }; 146 | 147 | static char * 148 | bit_op_names[] = 149 | { 150 | 0, 151 | "BIT ", 152 | "RES ", 153 | "SET ", 154 | }; 155 | 156 | static char * 157 | rep_op_names[] = 158 | { 159 | "LDI", 160 | "CPI", 161 | "INI", 162 | "OUTI", 163 | "LDD", 164 | "CPD", 165 | "IND", 166 | "OUTD", 167 | "LDIR", 168 | "CPIR", 169 | "INIR", 170 | "OTIR", 171 | "LDDR", 172 | "CPDR", 173 | "INDR", 174 | "OTDR" 175 | }; 176 | 177 | static char *index_reg; 178 | static int str_length; 179 | static FILE *fp; 180 | 181 | static void 182 | put_byte(byte b) 183 | { 184 | fprintf(fp, "%02X", b & 0x0FF); 185 | str_length += 2; 186 | } 187 | 188 | static void 189 | put_addr(z80info *z80, int loc) 190 | { 191 | put_byte(z80->mem[loc]); 192 | put_byte(z80->mem[(loc - 1) & 0x0FFFF]); 193 | } 194 | 195 | static void 196 | put_word(word w) 197 | { 198 | fprintf(fp, "%04X", w & 0x0FFFF); 199 | str_length += 4; 200 | } 201 | 202 | static void 203 | put_digit(byte digit) 204 | { 205 | fprintf(fp, "%01X", digit & 0x00F); 206 | str_length += 1; 207 | } 208 | 209 | static void 210 | put_char(char ch) 211 | { 212 | putc(ch, fp); 213 | str_length += 1; 214 | } 215 | 216 | static void 217 | put_str(char *str) 218 | { 219 | fprintf(fp, "%s", str); 220 | str_length += strlen(str); 221 | } 222 | 223 | static void 224 | put_reg(z80info *z80, int reg_num, int loc) 225 | { 226 | int d; 227 | 228 | if (index_reg && (reg_num == 6)) 229 | { 230 | put_str("("); 231 | put_str(index_reg); 232 | d = (signed char)z80->mem[loc + 1]; 233 | 234 | if (d < 0) 235 | { 236 | put_str("-"); 237 | put_byte(-d); 238 | } 239 | else 240 | { 241 | put_str("+"); 242 | put_byte(d); 243 | } 244 | 245 | put_str(")"); 246 | } 247 | else 248 | { 249 | put_str(rr_names[reg_num]); 250 | } 251 | } 252 | 253 | static void 254 | put_dd_reg(int reg_num) 255 | { 256 | if (index_reg) 257 | { 258 | if (reg_num == 2) 259 | put_str(index_reg); 260 | else if (reg_num == 3) 261 | put_str("SP"); 262 | else 263 | put_str(OPC_ILLEGAL); 264 | } 265 | else 266 | put_str(dd_names[reg_num]); 267 | } 268 | 269 | static void 270 | put_qq_reg(int reg_num) 271 | { 272 | if (index_reg) 273 | { 274 | if (reg_num == 2) 275 | put_str(index_reg); 276 | else 277 | put_str(OPC_ILLEGAL); 278 | } 279 | else 280 | put_str(qq_names[reg_num]); 281 | } 282 | 283 | int 284 | disassemlen(void) 285 | { 286 | return str_length; 287 | } 288 | 289 | int 290 | disassem(z80info *z80, word start, FILE *file) 291 | { 292 | word loc; 293 | int byte_count; 294 | byte last_byte; 295 | 296 | loc = start; 297 | byte_count = 0; 298 | str_length = 0; 299 | 300 | if (file != NULL) 301 | { 302 | fp = file; 303 | index_reg = NULL; 304 | } 305 | 306 | switch (bits(7,6)) 307 | { 308 | case 0: 309 | switch (bits(3,0)) 310 | { 311 | case 0: 312 | case 8: 313 | if (bits(5,3) == 0) 314 | { 315 | put_str("NOP"); 316 | break; 317 | } 318 | 319 | if (bits(5,3) == 1) 320 | { 321 | put_str("EX AF,AF'"); 322 | break; 323 | } 324 | 325 | put_str(jr_op_names[bits(5,3)]); 326 | loc++; 327 | byte_count++; 328 | put_word((signed char)z80->mem[loc] + (int)loc + 1); 329 | break; 330 | 331 | case 1: 332 | put_str(OPC_LD); 333 | put_dd_reg(bits(5,4)); 334 | put_char(','); 335 | byte_count += 2; 336 | loc += 2; 337 | put_addr(z80, loc); 338 | break; 339 | 340 | case 2: 341 | last_byte = z80->mem[loc]; 342 | put_str(OPC_LD); 343 | 344 | switch (bits(5,4)) 345 | { 346 | case 0: 347 | put_str("(BC)"); 348 | break; 349 | 350 | case 1: 351 | put_str("(DE)"); 352 | break; 353 | 354 | case 2: 355 | case 3: 356 | put_char('('); 357 | byte_count += 2; 358 | loc += 2; 359 | put_addr(z80, loc); 360 | put_char(')'); 361 | break; 362 | } 363 | 364 | if ((last_byte & 0x030) == 0x020) 365 | { 366 | put_char(','); 367 | 368 | if (index_reg) 369 | put_str(index_reg); 370 | else 371 | put_str("HL"); 372 | } 373 | else 374 | { 375 | put_str(",A"); 376 | } 377 | 378 | break; 379 | 380 | case 3: 381 | put_str(OPC_INC); 382 | put_dd_reg(bits(5,4)); 383 | break; 384 | 385 | case 4: 386 | case 0x0C: 387 | put_str(OPC_INC); 388 | put_reg(z80, bits(5,3), loc); 389 | break; 390 | 391 | case 5: 392 | case 0x0D: 393 | put_str(OPC_DEC); 394 | put_reg(z80, bits(5,3), loc); 395 | break; 396 | 397 | case 6: 398 | case 0x0E: 399 | put_str(OPC_LD); 400 | put_reg(z80, bits(5,3), loc); 401 | put_char(','); 402 | 403 | if (index_reg && (bits(5,3) == 6)) 404 | { 405 | loc++; 406 | byte_count++; 407 | } 408 | 409 | loc++; 410 | byte_count++; 411 | put_byte(z80->mem[loc]); 412 | break; 413 | 414 | case 7: 415 | case 0x0F: 416 | put_str(log_op_names[bits(5,3)]); 417 | break; 418 | 419 | case 9: 420 | put_str(OPC_ADD); 421 | put_str("HL,"); 422 | put_dd_reg(bits(5,4)); 423 | break; 424 | 425 | case 0x0A: 426 | put_str(OPC_LD); 427 | 428 | if (bits(5,4) == 2) 429 | { 430 | if (index_reg) 431 | { 432 | put_str(index_reg); 433 | put_str(","); 434 | } 435 | else 436 | put_str("HL,"); 437 | } 438 | else 439 | { 440 | put_str("A,"); 441 | } 442 | 443 | switch (bits(5,4)) 444 | { 445 | case 0: 446 | put_str("(BC)"); 447 | break; 448 | 449 | case 1: 450 | put_str("(DE)"); 451 | break; 452 | 453 | case 2: 454 | case 3: 455 | put_char('('); 456 | byte_count += 2; 457 | loc += 2; 458 | put_addr(z80, loc); 459 | put_char(')'); 460 | break; 461 | } 462 | 463 | break; 464 | 465 | case 0x0B: 466 | put_str(OPC_DEC); 467 | put_dd_reg(bits(5,4)); 468 | break; 469 | } 470 | 471 | break; 472 | 473 | case 1: 474 | /* if ((bits(5,3) != 6) && (bits(2,0) != 6)) */ 475 | if (z80->mem[loc] == 0x76) 476 | { 477 | put_str("HALT"); 478 | } 479 | else 480 | { 481 | put_str(OPC_LD); 482 | put_reg(z80, bits(5,3), loc); 483 | put_char(','); 484 | put_reg(z80, bits(2,0), loc); 485 | } 486 | 487 | break; 488 | 489 | case 2: 490 | put_str(op_names[bits(5,3)]); 491 | put_reg(z80, bits(2,0), loc); 492 | break; 493 | 494 | case 3: 495 | if (z80->mem[loc] == 0xCB) 496 | { 497 | loc++; 498 | byte_count++; 499 | 500 | if (bits(7,6) == 0) 501 | { 502 | put_str(shf_op_names[bits(5,3)]); 503 | } 504 | else 505 | { 506 | put_str(bit_op_names[bits(7,6)]); 507 | put_digit(bits(5,3)); 508 | put_char(','); 509 | } 510 | 511 | put_reg(z80, bits(2,0), loc); 512 | break; 513 | } 514 | 515 | /*if ((z80->mem[loc] & 0x0DD) == 0x0DD)*/ 516 | if (z80->mem[loc] == 0xDD) 517 | { 518 | if (bits(5,5)) 519 | index_reg = "IY"; 520 | else 521 | index_reg = "IX"; 522 | 523 | byte_count = disassem(z80, loc + 1, NULL); 524 | loc += byte_count; 525 | 526 | break; 527 | } 528 | 529 | if (z80->mem[loc] == 0xED) 530 | { 531 | loc++; 532 | byte_count++; 533 | 534 | switch (bits(7,6)) 535 | { 536 | case 0: 537 | put_str(OPC_ILLEGAL); 538 | break; 539 | 540 | case 1: 541 | switch (bits(3,0)) 542 | { 543 | case 0: 544 | case 8: 545 | put_str("IN "); 546 | put_reg(z80, bits(5,3), loc); 547 | put_str(",[C]"); 548 | break; 549 | 550 | case 1: 551 | case 9: 552 | put_str("OUT [C],"); 553 | put_reg(z80, bits(5,3), loc); 554 | break; 555 | 556 | case 2: 557 | put_str("SBC HL,"); 558 | put_dd_reg(bits(5,4)); 559 | break; 560 | 561 | case 0xA: 562 | put_str("ADC HL,"); 563 | put_dd_reg(bits(5,4)); 564 | break; 565 | 566 | case 3: 567 | put_str(OPC_LD); 568 | put_char('('); 569 | put_addr(z80, loc + 2); 570 | put_str("),"); 571 | put_dd_reg(bits(5,4)); 572 | byte_count += 2; 573 | loc += 2; 574 | break; 575 | 576 | case 0xB: 577 | put_str(OPC_LD); 578 | put_dd_reg(bits(5,4)); 579 | put_str(",("); 580 | byte_count += 2; 581 | loc += 2; 582 | put_addr(z80, loc); 583 | put_char(')'); 584 | break; 585 | 586 | case 4: 587 | if (bits(5,4) == 0) 588 | put_str("NEG"); 589 | else 590 | put_str(OPC_ILLEGAL); 591 | 592 | break; 593 | 594 | case 0xC: 595 | put_str(OPC_ILLEGAL); 596 | break; 597 | 598 | case 5: 599 | if (bits(5,4) == 0) 600 | put_str("RETN"); 601 | else 602 | put_str(OPC_ILLEGAL); 603 | 604 | break; 605 | 606 | case 0xD: 607 | if (bits(5,4) == 0) 608 | put_str("RETI"); 609 | else 610 | put_str(OPC_ILLEGAL); 611 | 612 | break; 613 | 614 | case 6: 615 | if (bits(5,4) == 0) 616 | put_str("IM 0"); 617 | else if (bits(5,4) == 1) 618 | put_str("IM 1"); 619 | else 620 | put_str(OPC_ILLEGAL); 621 | 622 | break; 623 | 624 | case 0xE: 625 | if (bits(5,4) == 1) 626 | put_str("IM 2"); 627 | else 628 | put_str(OPC_ILLEGAL); 629 | break; 630 | 631 | case 7: 632 | if (bits(5,4) == 0) 633 | put_str("LD I,A"); 634 | else if (bits(5,4) == 1) 635 | put_str("LD A,I"); 636 | else if (bits(5,4) == 2) 637 | put_str("RRD"); 638 | else 639 | put_str(OPC_ILLEGAL); 640 | 641 | break; 642 | 643 | case 0xF: 644 | if (bits(5,4) == 0) 645 | put_str("LD R,A"); 646 | else if (bits(5,4) == 1) 647 | put_str("LD A,R"); 648 | else if (bits(5,4) == 2) 649 | put_str("RLD"); 650 | else 651 | put_str(OPC_ILLEGAL); 652 | 653 | break; 654 | } 655 | 656 | break; 657 | 658 | case 2: 659 | if (bits(5,5) == 0) 660 | { 661 | put_str(OPC_ILLEGAL); 662 | break; 663 | } 664 | 665 | if (bits(2,2) == 1) 666 | { 667 | put_str(OPC_ILLEGAL); 668 | break; 669 | } 670 | 671 | put_str(rep_op_names[ 672 | ((z80->mem[loc] >> 1) & 0xC) 673 | | (z80->mem[loc] & 3) 674 | ]); 675 | break; 676 | 677 | case 3: 678 | put_str(OPC_ILLEGAL); 679 | break; 680 | } 681 | 682 | break; 683 | } 684 | 685 | if ((z80->mem[loc] & 0x06) == 0x06) 686 | { 687 | put_str(op_names[bits(5,3)]); 688 | loc++; 689 | byte_count++; 690 | put_byte(z80->mem[loc]); 691 | break; 692 | } 693 | 694 | switch (bits(2,0)) 695 | { 696 | case 0: 697 | put_str(OPC_RET); 698 | put_cc(bits(5,3)); 699 | break; 700 | 701 | case 1: 702 | switch (bits(5,3)) 703 | { 704 | case 0: 705 | case 2: 706 | case 4: 707 | case 6: 708 | put_str(OPC_POP); 709 | put_qq_reg(bits(5,4)); 710 | break; 711 | 712 | case 1: 713 | put_str(OPC_RET); 714 | break; 715 | 716 | case 3: 717 | put_str(OPC_EXX); 718 | break; 719 | 720 | case 5: 721 | put_str(OPC_JP); 722 | 723 | if (index_reg) 724 | { 725 | put_str("("); 726 | put_str( index_reg); 727 | put_str(")"); 728 | } 729 | else 730 | put_str("(HL)"); 731 | 732 | break; 733 | 734 | case 7: 735 | put_str(OPC_LD); 736 | put_str("SP,"); 737 | 738 | if (index_reg) 739 | put_str(index_reg); 740 | else 741 | put_str("HL"); 742 | 743 | break; 744 | } 745 | 746 | break; 747 | 748 | case 2: 749 | put_str(OPC_JP); 750 | put_cc(bits(5,3)); 751 | put_char(','); 752 | byte_count += 2; 753 | loc += 2; 754 | put_addr(z80, loc); 755 | break; 756 | 757 | case 3: 758 | switch (bits(5,3)) 759 | { 760 | case 0: 761 | put_str(OPC_JP); 762 | byte_count += 2; 763 | loc += 2; 764 | put_addr(z80, loc); 765 | break; 766 | 767 | case 2: 768 | loc++; 769 | byte_count++; 770 | put_str(OPC_OUT); 771 | put_char('('); 772 | put_byte(z80->mem[loc]); 773 | put_str("),A"); 774 | break; 775 | 776 | case 3: 777 | loc++; 778 | byte_count++; 779 | put_str(OPC_IN); 780 | put_str("A,("); 781 | put_byte(z80->mem[loc]); 782 | put_char(')'); 783 | break; 784 | 785 | case 4: 786 | put_str(OPC_EX); 787 | put_str("(SP),"); 788 | 789 | if (index_reg) 790 | put_str(index_reg); 791 | else 792 | put_str("HL"); 793 | 794 | break; 795 | 796 | case 5: 797 | put_str(OPC_EX); 798 | put_str("HL,DE"); 799 | break; 800 | 801 | case 6: 802 | put_str(OPC_DI); 803 | break; 804 | 805 | case 7: 806 | put_str(OPC_EI); 807 | break; 808 | } 809 | 810 | break; 811 | 812 | case 4: 813 | put_str(OPC_CALL); 814 | put_cc(bits(5,3)); 815 | put_char(','); 816 | byte_count += 2; 817 | loc += 2; 818 | put_addr(z80, loc); 819 | break; 820 | 821 | case 5: 822 | if (bits(3,3)) 823 | { 824 | put_str(OPC_CALL); 825 | byte_count += 2; 826 | loc += 2; 827 | put_addr(z80, loc); 828 | } 829 | else 830 | { 831 | put_str(OPC_PUSH); 832 | put_qq_reg(bits(5,4)); 833 | } 834 | 835 | break; 836 | 837 | case 6: 838 | put_str(OPC_ILLEGAL); 839 | break; 840 | 841 | case 7: 842 | put_str(OPC_RST); 843 | put_byte(z80->mem[loc] & 0x038); 844 | break; 845 | } 846 | 847 | break; 848 | } 849 | 850 | loc++; 851 | byte_count++; 852 | return byte_count; 853 | } 854 | -------------------------------------------------------------------------------- /getunix.mac: -------------------------------------------------------------------------------- 1 | ; GET UNIX FILE INTO CP/M UTILITY 2 | ; getunix unixfilename.extension driveletter:cpmfilename.extension 3 | ; 4 | .Z80 5 | 6 | SRCFCB EQU 005CH 7 | SECFCB EQU 006CH 8 | 9 | CBIOS EQU 0EA00H 10 | OPNUNX EQU CBIOS+33H 11 | RDUNX EQU CBIOS+39H 12 | CLSUNX EQU CBIOS+3FH 13 | 14 | BDOS EQU 5 15 | CREATE EQU 22 16 | WRITE EQU 21 17 | CLOSE EQU 16 18 | SETDMA EQU 26 19 | PRINT EQU 9 20 | 21 | CSEG 22 | ENTRY START 23 | START: 24 | LD SP,STACK 25 | ;MOVE DESTINATION FCB 26 | LD HL,SECFCB 27 | LD DE,DSTFCB 28 | LD BC,10H 29 | LDIR 30 | LD HL,DSTFCB+16 31 | LD DE,DSTFCB+17 32 | LD (HL),0 33 | LD BC,20H 34 | LDIR 35 | ;OPEN SOURCE FILE 36 | LD DE,SRCFCB 37 | CALL OPNUNX 38 | AND A 39 | JP NZ,ERROR1 40 | ;CREATE DESTINATION FILE 41 | LD DE,DSTFCB 42 | LD C,CREATE 43 | CALL BDOS 44 | CP 0FFH 45 | JP Z,ERROR2 46 | ;SET DMA ADDRESS 47 | LD DE,0080H 48 | LD C,SETDMA 49 | CALL BDOS 50 | ;READ ONE, WRITE ONE 51 | LOOP: LD DE,SRCFCB 52 | CALL RDUNX 53 | AND A 54 | JP NZ,CLOSEM 55 | LD DE,DSTFCB 56 | LD C,WRITE 57 | CALL BDOS 58 | AND A 59 | JP NZ,ERROR4 60 | JP LOOP 61 | ;CLOSE UNIX FILE 62 | CLOSEM: LD DE,SRCFCB 63 | CALL CLSUNX 64 | AND A 65 | JP Z,CLSCPM 66 | 67 | CLSCPM: LD DE,DSTFCB 68 | LD C,CLOSE 69 | CALL BDOS 70 | JP 0 71 | 72 | ERROR1: LD DE,STR1 73 | JP ERROR 74 | STR1: DEFM "ERROR1" 75 | DEFB 13,10,'$' 76 | ERROR2: LD DE,STR2 77 | JP ERROR 78 | STR2: DEFM "ERROR2" 79 | DEFB 13,10,'$' 80 | ERROR3: LD DE,STR3 81 | JP ERROR 82 | STR3: DEFM "ERROR3" 83 | DEFB 13,10,'$' 84 | ERROR4: LD DE,STR4 85 | JP ERROR 86 | STR4: DEFM "ERROR4" 87 | DEFB 13,10,'$' 88 | 89 | ERROR: LD C,PRINT 90 | CALL BDOS 91 | JP 0 92 | 93 | DSTFCB: DEFS 200 94 | STACK: DEFS 1 95 | END 96 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------*\ 2 | | main.c -- main driver program for the z80 emulator -- all I/O | 3 | | to the Unix world is done from this file -- "z80.c" calls various | 4 | | functions within this file | 5 | | | 6 | | Copyright 1986-1988 by Parag Patel. All Rights Reserved. | 7 | | Copyright 1994-1995 by CodeGen, Inc. All Rights Reserved. | 8 | \*-----------------------------------------------------------------------*/ 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "defs.h" 20 | #include "vt.h" 21 | 22 | #if defined macintosh 23 | # include 24 | # include 25 | # ifdef THINK_C 26 | # include 27 | # endif 28 | #elif defined DJGPP 29 | # include 30 | #elif defined _WIN32 31 | # 32 | #else /* UNIX */ 33 | # include 34 | # include 35 | # if defined POSIX_TTY 36 | # include 37 | # elif defined BeBox 38 | # include 39 | # else 40 | # include 41 | # endif 42 | #endif 43 | 44 | 45 | extern int errno; 46 | 47 | 48 | /* globally visible vars */ 49 | static FILE *logfile = NULL; 50 | 51 | 52 | #ifndef _WIN32 53 | # if defined UNIX || defined BeBox 54 | # ifdef POSIX_TTY 55 | # define termio termios 56 | # endif 57 | static struct termio rawterm, oldterm; /* for raw terminal I/O */ 58 | # endif 59 | #endif 60 | 61 | #ifdef _WIN32 62 | static int have_term = 0; /* no terminal in Win32 */ 63 | #else 64 | static int have_term = 1; /* FALSE if terminal initialization failed */ 65 | #endif 66 | 67 | static void dumptrace(z80info *z80); 68 | 69 | 70 | char *jgets(char *s, int len, FILE *f) 71 | { 72 | char *rtn = fgets(s, len, f); 73 | if (rtn) 74 | { 75 | int x; 76 | for (x = 0; s[x] && s[x] != '\r' && s[x] != '\n'; ++x); 77 | s[x] = 0; 78 | } 79 | return rtn; 80 | } 81 | 82 | /*-----------------------------------------------------------------------*\ 83 | | resetterm -- reset terminal characteristics to original settings 84 | \*-----------------------------------------------------------------------*/ 85 | 86 | void 87 | resetterm(void) 88 | { 89 | #ifndef _WIN32 90 | if (have_term) 91 | tcsetattr(fileno(stdin), TCSADRAIN, &oldterm); 92 | #endif 93 | } 94 | 95 | 96 | 97 | /*-----------------------------------------------------------------------*\ 98 | | setterm -- set terminal characteristics to raw mode 99 | \*-----------------------------------------------------------------------*/ 100 | 101 | void 102 | setterm(void) 103 | { 104 | #ifndef _WIN32 105 | if (have_term) 106 | tcsetattr(fileno(stdin), TCSADRAIN, &rawterm); 107 | #endif 108 | } 109 | 110 | 111 | 112 | /*-----------------------------------------------------------------------*\ 113 | | initterm -- initialize terminal stuff -- called once on startup 114 | | and then after returning from a sub-shell 115 | \*-----------------------------------------------------------------------*/ 116 | 117 | static void 118 | initterm(void) 119 | { 120 | #ifdef _WIN32 121 | fprintf(stderr, "Sorry, terminal not found, using cooked mode.\n"); 122 | have_term = 0; 123 | #else 124 | if (tcgetattr(fileno(stdin), &oldterm)) 125 | { 126 | fprintf(stderr, "Sorry, terminal not found, using cooked mode.\n"); 127 | have_term = 0; 128 | } 129 | else { 130 | rawterm = oldterm; 131 | rawterm.c_iflag &= ~(ICRNL | IXON | IXOFF | INLCR | ICRNL); 132 | rawterm.c_lflag &= ~(ICANON | ECHO); 133 | rawterm.c_cc[VINTR] = -1; 134 | rawterm.c_cc[VSUSP] = -1; 135 | rawterm.c_cc[VQUIT] = -1; 136 | rawterm.c_cc[VERASE] = -1; 137 | rawterm.c_cc[VKILL] = -1; 138 | tcsetattr(fileno(stdin), TCSADRAIN, &rawterm); 139 | } 140 | 141 | #if 0 142 | /* rawterm.c_lflag &= ~(ISIG | ICANON | ECHO); */ 143 | rawterm.c_lflag &= ~(ICANON | ECHO); 144 | #ifdef IENQAK 145 | rawterm.c_iflag &= ~(IENQAK | IXON | IXOFF | INLCR | ICRNL); 146 | #else 147 | rawterm.c_iflag &= ~(IXON | IXOFF | INLCR | ICRNL); 148 | #endif 149 | rawterm.c_oflag &= ~OPOST; 150 | rawterm.c_cc[VINTR] = INTR_CHAR; 151 | rawterm.c_cc[VSUSP] = -1; 152 | rawterm.c_cc[VQUIT] = -1; 153 | rawterm.c_cc[VERASE] = -1; 154 | rawterm.c_cc[VKILL] = -1; 155 | rawterm.c_cc[VMIN] = 1; /* MIN number of chars */ 156 | rawterm.c_cc[VTIME] = 0; /* TIME timeout value */ 157 | #endif 158 | #endif 159 | } 160 | 161 | 162 | 163 | 164 | /*-----------------------------------------------------------------------*\ 165 | | command -- called when user-level commands are needed by the z80 166 | | for some reason or another 167 | \*-----------------------------------------------------------------------*/ 168 | 169 | void 170 | command(z80info *z80) 171 | { 172 | unsigned int i, j, t, e; 173 | char str[256], *s; 174 | FILE *fp; 175 | static word pe = 0; 176 | static word po = 0; 177 | 178 | resetterm(); 179 | printf("\n"); 180 | 181 | loop: /* "infinite" loop */ 182 | 183 | /* prompt for a command from the user & then do it */ 184 | printf("Cmd: "); 185 | fflush(stdout); 186 | *str = '\0'; 187 | fgets(str, sizeof str - 1, stdin); 188 | 189 | for (s = str; *s == ' ' || *s == '\t'; s++) 190 | ; 191 | 192 | switch (isupper(*(unsigned char *)s) ? tolower(*(unsigned char *)s) : *s) 193 | { 194 | case '?': /* help */ 195 | printf(" Q(uit) T(race on/off) S(tep trace) D(ump regs)\n"); 196 | printf(" E(xamine memory) P(oke memory) R(egister modify)\n"); 197 | printf(" L(oad binary) C(ontinue running - if Step)\n"); 198 | printf(" G(o) B(oot CP/M) Z(80 disassembled dump)\n"); 199 | printf(" W(write memory to file) X,Y(-set/clear breakpoint)\n"); 200 | printf(" O(output to \"logfile\")\n\n"); 201 | printf(" !(fork shell) ?(command list) V(ersion)\n\n"); 202 | break; 203 | 204 | case 'o': 205 | if (logfile != NULL) 206 | { 207 | fclose(logfile); 208 | logfile = NULL; 209 | printf(" Logging off.\n"); 210 | } 211 | else 212 | { 213 | printf(" Logfile name? "); 214 | jgets(str, sizeof(str), stdin); 215 | 216 | for (s = str; isspace(*(unsigned char *)s); s++) 217 | ; 218 | 219 | if (*s == '\0') 220 | break; 221 | 222 | logfile = fopen(s, "w"); 223 | 224 | if (logfile == NULL) 225 | printf("Cannot open logfile!\n"); 226 | else 227 | printf(" Logging on.\n"); 228 | } 229 | 230 | break; 231 | 232 | case '!': /* fork a shell */ 233 | system("exec ${SHELL:-/bin/sh}"); 234 | initterm(); 235 | printf("\n"); 236 | break; 237 | 238 | case 'q': /* quit */ 239 | if (logfile != NULL) 240 | fclose(logfile); 241 | 242 | exit(0); 243 | break; 244 | 245 | case 'v': /* version */ 246 | printf(" Version %s\n", VERSION); 247 | break; 248 | 249 | case 'b': /* boot cp/m */ 250 | setterm(); 251 | sysreset(z80); 252 | return; 253 | break; 254 | 255 | case 't': /* toggle trace mode */ 256 | z80->trace = !z80->trace; 257 | printf(" Trace %s\n", z80->trace ? "on" : "off"); 258 | break; 259 | 260 | case 's': /* toggle step-trace mode */ 261 | z80->step = !z80->step; 262 | printf(" Step-trace %s\n", z80->step ? "on" : "off"); 263 | printf(" Trace %s\n", z80->trace ? "on" : "off"); 264 | break; 265 | 266 | case 'd': /* dump registers */ 267 | dumptrace(z80); 268 | break; 269 | 270 | case 'e': /* examine memory */ 271 | printf(" Starting at loc? (%.4X) : ", pe); 272 | jgets(str, sizeof(str), stdin); 273 | t = pe; 274 | sscanf(str, "%x", &t); 275 | pe = t; 276 | 277 | for (i = 0; i <= 8; i++) 278 | { 279 | printf(" %.4X: ", pe); 280 | 281 | for (j = 0; j <= 0xF; j++) 282 | printf("%.2X ", z80->mem[pe++]); 283 | 284 | printf("\n"); 285 | } 286 | 287 | break; 288 | 289 | case 'w': /* write memory to file */ 290 | printf(" Starting at loc? "); 291 | jgets(str, sizeof(str), stdin); 292 | sscanf(str, "%x", &t); 293 | printf(" Ending at loc? "); 294 | jgets(str, sizeof(str), stdin); 295 | sscanf(str, "%x", &e); 296 | fp = fopen("mem", "w"); 297 | 298 | if (fp == NULL) 299 | printf("Cannot open file 'mem' for writing!\n"); 300 | else 301 | { 302 | j = 0; 303 | 304 | for (i = t; i < e; i++) 305 | { 306 | if (j++ > 9) 307 | { 308 | fprintf(fp, "\n"); 309 | j = 0; 310 | } 311 | 312 | fprintf(fp, "0x%X, ", z80->mem[i]); 313 | } 314 | 315 | fprintf(fp, "\n"); 316 | fclose(fp); 317 | } 318 | 319 | break; 320 | 321 | case 'x': /* set breakpoint */ 322 | #ifdef MEM_BREAK 323 | printf(" Set breakpoint at loc? (A for abort): "); 324 | jgets(str, sizeof(str), stdin); 325 | 326 | if (tolower(*(unsigned char *)str) == 'a' || *str == '\0') 327 | break; 328 | 329 | sscanf(str, "%x", &t); 330 | 331 | if (t >= sizeof z80->mem) 332 | { 333 | printf("Cannot set breakpoint at addr 0x%X\n", t); 334 | break; 335 | } 336 | 337 | if (!(z80->membrk[t] & M_BREAKPOINT)) 338 | { 339 | printf(" Breakpoint set at addr 0x%X\n", t); 340 | z80->membrk[t] |= M_BREAKPOINT; 341 | z80->numbrks++; 342 | } 343 | #else 344 | printf("Sorry, Z80 has not been compiled with MEM_BREAK.\n"); 345 | #endif /* MEM_BREAK */ 346 | break; 347 | 348 | case 'y': /* clear breakpoints */ 349 | #ifdef MEM_BREAK 350 | printf(" Clear breakpoint at loc? (A for all) : "); 351 | jgets(str, sizeof(str), stdin); 352 | 353 | if (tolower(*(unsigned char *)str) == 'a') 354 | { 355 | for (i = 0; i < sizeof z80->membrk; i++) 356 | z80->membrk[i] &= ~M_BREAKPOINT; 357 | 358 | z80->numbrks = 0; 359 | printf(" All breakpoints cleared\n"); 360 | break; 361 | } 362 | 363 | sscanf(str, "%x", &t); 364 | 365 | if (t >= sizeof z80->mem) 366 | { 367 | printf(" Cannot clear breakpoint at addr 0x%X\n", t); 368 | break; 369 | } 370 | 371 | if (z80->membrk[t] & M_BREAKPOINT) 372 | { 373 | printf("Breakpoint cleared at addr 0x%X\n", t); 374 | z80->membrk[t] &= ~M_BREAKPOINT; 375 | z80->numbrks--; 376 | } 377 | #else 378 | printf("Sorry, Z80 has not been compiled with MEM_BREAK.\n"); 379 | #endif /* MEM_BREAK */ 380 | break; 381 | 382 | case 'z': /* z80 disassembled memory dump */ 383 | printf(" Starting at loc? (%.4X) : ", pe); 384 | jgets(str, sizeof(str), stdin); 385 | t = pe; 386 | sscanf(str, "%x", &t); 387 | pe = t; 388 | 389 | for (i = 0; i < 0x10; i++) 390 | { 391 | printf(" %.4X: ", pe); 392 | j = pe; 393 | pe += disassem(z80, pe, stdout); 394 | t = disassemlen(); 395 | 396 | while (t++ < 15) 397 | putchar(' '); 398 | 399 | while (j < pe) 400 | printf(" %.2X", z80->mem[j++]); 401 | 402 | printf("\n"); 403 | } 404 | 405 | break; 406 | 407 | case 'p': /* poke memory */ 408 | printf(" Start at loc? (%.4X) : ", po); 409 | jgets(str, sizeof(str), stdin); 410 | sscanf(str, "%x", &i); 411 | po = i; 412 | 413 | for (;;) 414 | { 415 | printf(" Mem[%.4X] (%.2X) = ", po, z80->mem[po]); 416 | jgets(str, sizeof(str), stdin); 417 | 418 | for (s = str; *s == ' ' || *s == '\t'; s++) 419 | ; 420 | 421 | if (*s == '~') /* exit? */ 422 | { 423 | po = i; 424 | break; 425 | } 426 | 427 | if (*s == '\0') /* leave the value alone */ 428 | continue; 429 | 430 | j = 0; 431 | sscanf(str, "%x", &j); 432 | z80->mem[po] = j; 433 | po++; 434 | } 435 | break; 436 | 437 | case 'r': /* set a register */ 438 | printf(" Value? = "); 439 | jgets(str, sizeof(str), stdin); 440 | i = 0; 441 | sscanf(str, "%x", &i); 442 | printf(" Reg? (A,F,B,C,D,E,H,L,IX,IY,SP,PC) : "); 443 | jgets(str, sizeof(str), stdin); 444 | 445 | for (s = str; *s == ' ' || *s == '\t'; s++) 446 | ; 447 | 448 | switch (tolower(*(unsigned char *)s)) 449 | { 450 | case 'a': A = i; break; 451 | case 'f': F = i; break; 452 | case 'b': B = i; break; 453 | case 'c': C = i; break; 454 | case 'd': D = i; break; 455 | case 'e': E = i; break; 456 | case 'h': H = i; break; 457 | case 'l': L = i; break; 458 | case 'i': 459 | if (tolower(((unsigned char *)s)[1]) == 'x') 460 | IX = i; 461 | else if (tolower(((unsigned char *)s)[1]) == 'y') 462 | IY = i; 463 | 464 | break; 465 | 466 | case 'x': IX = i; break; 467 | case 'y': IY = i; break; 468 | case 's': SP = i; break; 469 | case 'p': PC = i; break; 470 | 471 | default: 472 | printf("No such register\n"); 473 | break; 474 | } 475 | 476 | break; 477 | 478 | case 'l': /* load a file into z80 memory */ 479 | printf(" File-name: "); 480 | jgets(str, sizeof(str), stdin); 481 | 482 | if (!loadfile(z80, str)) 483 | fprintf(stderr, "Cannot load file %s!\r\n", str); 484 | 485 | break; 486 | 487 | case '\0': /* carriage-return */ 488 | case '\r': 489 | case '\n': 490 | if (z80->trace && z80->step) 491 | goto cont; 492 | 493 | break; 494 | 495 | case 'c': /* continue z80 execution */ 496 | case 'g': 497 | cont: 498 | setterm(); 499 | 500 | if (z80->trace) 501 | { 502 | z80->event = TRUE; 503 | z80->halt = TRUE; 504 | } 505 | 506 | return; 507 | 508 | default: 509 | /*putchar('\007');*/ 510 | printf("\007Command \"%s\" not recognized\n", s); 511 | break; 512 | } 513 | 514 | goto loop; 515 | } 516 | 517 | 518 | 519 | 520 | /*-----------------------------------------------------------------------*\ 521 | | dumptrace -- dump the z80 registers in an easy-to-trace format 522 | | -- note that the dump takes exactly one line so that changes in 523 | | register values are easier to spot -- disassembles the z80 code 524 | \*-----------------------------------------------------------------------*/ 525 | 526 | static void 527 | dumptrace(z80info *z80) 528 | { 529 | printf("a%.2X f%.2X bc%.4X de%.4X hl%.4X ", 530 | A, F, BC, DE, HL); 531 | printf("ix%.4X iy%.4X sp%.4X pc%.4X:%.2X ", 532 | IX, IY, SP, PC, z80->mem[PC]); 533 | disassem(z80, PC, stdout); 534 | printf("\r\n"); 535 | 536 | if (logfile) 537 | { 538 | fprintf(logfile, "a%.2X f%.2X bc%.4X de%.4X hl%.4X ", 539 | A, F, BC, DE, HL); 540 | fprintf(logfile, "ix%.4X iy%.4X sp%.4X pc%.4X:%.2X ", 541 | IX, IY, SP, PC, z80->mem[PC]); 542 | disassem(z80, PC, logfile); 543 | fprintf(logfile, "\r\n"); 544 | } 545 | } 546 | 547 | 548 | 549 | #define HEXVAL(c) (('0' <= (c) && (c) <= '9') ? (c) - '0' :\ 550 | (('a' <= (c) && (c) <= 'f') ? (c) - 'a' + 10 :\ 551 | (('A' <= (c) && (c) <= 'F') ? (c) - 'A' + 10 :\ 552 | -1 ))) 553 | 554 | static int 555 | gethex(FILE *fp) 556 | { 557 | int i, j; 558 | 559 | i = getc(fp); 560 | j = getc(fp); 561 | 562 | if (i < 0 || j < 0) 563 | return -1; 564 | 565 | i = HEXVAL(i); 566 | j = HEXVAL(j); 567 | 568 | if (i < 0 || j < 0) 569 | return -1; 570 | 571 | return (i << 4) | j; 572 | } 573 | 574 | 575 | static int 576 | loadhex(z80info *z80, FILE *fp) 577 | { 578 | int start = TRUE; 579 | int len, line, i; 580 | word addr, check, t; 581 | 582 | for (line = 1; getc(fp) >= 0; line++) /* should be a ':' */ 583 | { 584 | if ((len = gethex(fp)) <= 0) 585 | break; 586 | 587 | check = len; 588 | 589 | if ((i = gethex(fp)) < 0) 590 | break; 591 | 592 | addr = (word)i; 593 | check += addr; 594 | 595 | if ((i = gethex(fp)) < 0) 596 | break; 597 | 598 | t = (word)i; 599 | check += t; 600 | addr = (addr << 8) | t; 601 | 602 | if (start) 603 | PC = addr, start = FALSE; 604 | 605 | if ((i = gethex(fp)) < 0) /* ??? */ 606 | break; 607 | 608 | check += (word)i; 609 | 610 | while (len-- > 0) 611 | { 612 | if ((i = gethex(fp)) < 0) 613 | break; 614 | 615 | t = (word)i; 616 | check += t; 617 | z80->mem[addr] = t; 618 | addr++; 619 | } 620 | 621 | if ((i = gethex(fp)) < 0) /* checksum */ 622 | break; 623 | 624 | t = (word)i; 625 | 626 | if ((t + check) & 0xFF) 627 | { 628 | fprintf(stderr, "%d: Checksum error: %.2X != 0!\r\n", 629 | line, (t + check) & 0xFF); 630 | return FALSE; 631 | } 632 | 633 | if (getc(fp) < 0) /* should be a '\n' */ 634 | break; 635 | } 636 | 637 | return TRUE; 638 | } 639 | 640 | 641 | 642 | /*-----------------------------------------------------------------------*\ 643 | | getword -- return a 16-bit word from the specified file 644 | \*-----------------------------------------------------------------------*/ 645 | 646 | static int 647 | getword(FILE *file) 648 | { 649 | int w; 650 | 651 | w = getc(file) << 8; 652 | w |= getc(file); 653 | return w; 654 | } 655 | 656 | 657 | 658 | /*-----------------------------------------------------------------------*\ 659 | | loadpisces -- load the specified file (assumed to be in Pisces+ 660 | | format) into the z80 memory for subsequent execution 661 | \*-----------------------------------------------------------------------*/ 662 | 663 | static int 664 | loadpisces(z80info *z80, FILE *file) 665 | { 666 | int numbytes, i; 667 | unsigned short loadaddr; 668 | 669 | /* ignore the 1st 12 words in the file - the 13th word is the starting 670 | PC value - the 14th is also ignored */ 671 | for (i = 0; i < 12; i++) 672 | getword(file); 673 | 674 | PC = getword(file); 675 | getword(file); 676 | 677 | /* read in each block of words into the z80 memory - each block 678 | specifies the number of bytes in the block and the address to load 679 | the data into */ 680 | while (getword(file) != EOF) 681 | { 682 | numbytes = getword(file); 683 | loadaddr = getword(file); 684 | getword(file); 685 | 686 | for (; numbytes > 0; numbytes -= 2) 687 | { 688 | z80->mem[loadaddr] = getc(file); 689 | loadaddr++; 690 | z80->mem[loadaddr] = getc(file); 691 | loadaddr++; 692 | } 693 | } 694 | 695 | return TRUE; 696 | } 697 | 698 | 699 | static void 700 | suffix(char *str, const char *suff) 701 | { 702 | while(*str != '\0' && *str != '.') 703 | str++; 704 | 705 | strcpy(str, suff); 706 | } 707 | 708 | 709 | boolean 710 | loadfile(z80info *z80, const char *fname) 711 | { 712 | char buf[200]; 713 | FILE *fp; 714 | int ret; 715 | 716 | if ((fp = fopen(fname, "r")) != NULL) 717 | { 718 | ret = loadhex(z80, fp); 719 | fclose(fp); 720 | return ret; 721 | } 722 | 723 | strcpy(buf, fname); 724 | suffix(buf, ".hex"); 725 | 726 | if ((fp = fopen(buf, "r")) != NULL) 727 | { 728 | ret = loadhex(z80, fp); 729 | fclose(fp); 730 | return ret; 731 | } 732 | 733 | strcpy(buf, fname); 734 | suffix(buf, ".X"); 735 | 736 | if ((fp = fopen(buf, "r")) != NULL) 737 | { 738 | ret = loadpisces(z80, fp); 739 | fclose(fp); 740 | return ret; 741 | } 742 | 743 | return FALSE; 744 | } 745 | 746 | 747 | 748 | /* input -- z80 input instruction -- this function is called whenever 749 | an input ports is referenced from the z80 to handle the real I/O -- 750 | it returns a byte to the z80 just like the real I/O instruction -- 751 | the arguments represent the data on the bus as it would be for a real 752 | z80 - this routine is restarted later if there is no input pending, 753 | and we must wait for some to occur */ 754 | 755 | boolean 756 | input(z80info *z80, byte haddr, byte laddr, byte *val) 757 | { 758 | unsigned int data; 759 | 760 | /* just uses the lower 8-bits of the I/O address for now... */ 761 | switch (laddr) 762 | { 763 | 764 | /* return a character from the keyboard - wait for it if necessary -- 765 | return "last" if we have already read in something via 0x01 */ 766 | case 0x00: 767 | if (1) 768 | { 769 | #if defined macintosh 770 | EventRecord ev; 771 | 772 | again: 773 | fflush(stdout); 774 | 775 | while (!WaitNextEvent(keyDownMask | autoKeyMask, 776 | &ev, 20, nil)) 777 | ; 778 | 779 | data = ev.message & charCodeMask; 780 | 781 | if ((data == '.' && (ev.modifiers & cmdKey)) || 782 | data == INTR_CHAR) 783 | { 784 | command(z80); 785 | goto again; 786 | } 787 | else if (data == 'q' && (ev.modifiers & cmdKey)) 788 | exit(0); 789 | #elif defined DJGPP 790 | fflush(stdout); 791 | data = getkey(); 792 | 793 | while (data == INTR_CHAR) 794 | { 795 | command(z80); 796 | data = getkey(); 797 | } 798 | #else /* TCGETA */ 799 | fflush(stdout); 800 | data = kget(0); 801 | /* data = getchar(); */ 802 | 803 | while ((data > 0x7f && errno == EINTR) || 804 | data == INTR_CHAR) 805 | { 806 | command(z80); 807 | data = kget(0); 808 | /* data = getchar(); */ 809 | } 810 | #endif 811 | } 812 | 813 | *val = data & 0x7F; 814 | break; 815 | 816 | /* return 0xFF if we have a character waiting to be read - save the 817 | character in "last" for 0x00 above */ 818 | case 0x01: 819 | #if defined macintosh 820 | { 821 | EventRecord ev; 822 | *val = EventAvail(keyDownMask | autoKeyMask, &ev) ? 823 | 0xFF : 0; 824 | } 825 | #elif defined DJGPP 826 | *val = (kbhit()) ? 0xFF : 0; 827 | #else /* UNIX or BeBox */ 828 | fflush(stdout); 829 | 830 | if (constat()) 831 | *val = 0xFF; 832 | else 833 | *val = 0x00; 834 | 835 | #endif 836 | break; 837 | 838 | /* default - prompt the user for an input byte */ 839 | default: 840 | resetterm(); 841 | printf("INPUT : addr = %X%X DATA = ", haddr, laddr); 842 | fflush(stdout); 843 | scanf("%x", &data); 844 | setterm(); 845 | *val = data; 846 | break; 847 | } 848 | 849 | return TRUE; 850 | } 851 | 852 | 853 | /*-----------------------------------------------------------------------*\ 854 | | output -- output the data at the specified I/O address 855 | \*-----------------------------------------------------------------------*/ 856 | 857 | void 858 | output(z80info *z80, byte haddr, byte laddr, byte data) 859 | { 860 | if (laddr == 0xFF) { 861 | /* BIOS call - interrupt the z80 before the next instruction 862 | since we may have to mess with the PC & other stuff - 863 | otherwise we would do it right here */ 864 | z80->event = TRUE; 865 | z80->halt = TRUE; 866 | z80->syscall = TRUE; 867 | z80->biosfn = data; 868 | 869 | if (z80->trace) 870 | { 871 | printf("BIOS call %d\r\n", z80->biosfn); 872 | 873 | if (logfile) 874 | fprintf(logfile, "BIOS call %d\r\n", 875 | z80->biosfn); 876 | } 877 | } else if (laddr == 0) { 878 | /* output a character to the screen */ 879 | /* putchar(data); */ 880 | vt52(data); 881 | 882 | if (logfile != NULL) 883 | putc(data, logfile); 884 | } else { 885 | /* dump the data for our user */ 886 | printf("OUTPUT: addr = %X%X DATA = %X\r\n", haddr, laddr,data); 887 | } 888 | } 889 | 890 | 891 | 892 | /*-----------------------------------------------------------------------*\ 893 | | haltcpu -- this is called after the z80 halts -- it is used for 894 | | tracing & such 895 | \*-----------------------------------------------------------------------*/ 896 | 897 | void 898 | haltcpu(z80info *z80) 899 | { 900 | z80->halt = FALSE; 901 | 902 | /* we were interrupted by a Unix signal */ 903 | if (z80->sig) 904 | { 905 | if (z80->sig != SIGINT) 906 | printf("\r\nCaught signal %d.\r\n", z80->sig); 907 | 908 | z80->sig = 0; 909 | command(z80); 910 | return; 911 | } 912 | 913 | /* we are tracing execution of the z80 */ 914 | if (z80->trace) 915 | { 916 | /* re-enable tracing */ 917 | z80->event = TRUE; 918 | z80->halt = TRUE; 919 | dumptrace(z80); 920 | 921 | if (z80->step) 922 | command(z80); 923 | } 924 | 925 | /* a CP/M syscall - done here so tracing still works */ 926 | if (z80->syscall) 927 | { 928 | z80->syscall = FALSE; 929 | bios(z80, z80->biosfn); 930 | } 931 | } 932 | 933 | word 934 | read_mem(z80info *z80, word addr) 935 | { 936 | #ifdef MEM_BREAK 937 | if (z80->membrk[addr] & M_BREAKPOINT) 938 | { 939 | fprintf(stderr, "\r\nBreak at 0x%X\r\n", addr); 940 | } 941 | else if (z80->membrk[addr] & M_READ_PROTECT) 942 | { 943 | fprintf(stderr, 944 | "\r\nAttempt to read protected memory at 0x%X\r\n", 945 | addr); 946 | } 947 | else if (z80->membrk[addr] & M_MEM_MAPPED_IO) 948 | { 949 | fprintf(stderr, 950 | "\r\nAttempt to perform mem-mapped input at 0x%X\r\n", 951 | addr); 952 | /* fake some sort of I/O here and return its value */ 953 | } 954 | 955 | dumptrace(z80); 956 | command(z80); 957 | #endif /* MEM_BREAK */ 958 | 959 | return z80->mem[addr]; 960 | } 961 | 962 | word 963 | write_mem(z80info *z80, word addr, byte val) 964 | { 965 | #ifdef MEM_BREAK 966 | if (z80->membrk[addr] & M_BREAKPOINT) 967 | { 968 | fprintf(stderr, "\r\nBreak at 0x%X\r\n", addr); 969 | } 970 | else if (z80->membrk[addr] & M_WRITE_PROTECT) 971 | { 972 | fprintf(stderr, 973 | "\r\nAttempt to write to protected memory at 0x%X\r\n", 974 | addr); 975 | } 976 | else if (z80->membrk[addr] & M_MEM_MAPPED_IO) 977 | { 978 | fprintf(stderr, 979 | "\r\nAttempt to perform mem-mapped output at 0x%X\r\n", 980 | addr); 981 | /* fake some sort of I/O here and set mem to its value, */ 982 | /* then return */ 983 | } 984 | 985 | dumptrace(z80); 986 | command(z80); 987 | #endif /* MEM_BREAK */ 988 | 989 | return z80->mem[addr] = val; 990 | } 991 | 992 | void 993 | undefinstr(z80info *z80, byte instr) 994 | { 995 | printf("\r\nIllegal instruction 0x%.2X at PC=0x%.4X\r\n", 996 | instr, PC - 1); 997 | command(z80); 998 | } 999 | 1000 | 1001 | 1002 | /*-----------------------------------------------------------------------*\ 1003 | | quit -- terminate this program after cleaning up -- this it is | 1004 | | intended to catch unused signals & not leave the terminal hosed | 1005 | \*-----------------------------------------------------------------------*/ 1006 | 1007 | static void 1008 | quit(int sig) 1009 | { 1010 | printf("\r\nCaught signal %d.\r\n", sig); 1011 | resetterm(); 1012 | exit(2); 1013 | } 1014 | 1015 | 1016 | /* this is needed by both interrupt() and main() */ 1017 | static z80info *z80 = NULL; 1018 | 1019 | 1020 | /*-----------------------------------------------------------------------*\ 1021 | | interrupt -- this is called when we get a usable signal from Unix 1022 | \*-----------------------------------------------------------------------*/ 1023 | 1024 | static void 1025 | interrupt(int s) 1026 | { 1027 | /* we tell the z80 to stop when convenient, then reset & continue */ 1028 | if (z80 != NULL) 1029 | { 1030 | z80->event = TRUE; 1031 | z80->halt = TRUE; 1032 | z80->sig = s; 1033 | } 1034 | 1035 | signal(s, interrupt); 1036 | } 1037 | 1038 | /*-----------------------------------------------------------------------*\ 1039 | | main -- set up the global vars & run the z80 1040 | \*-----------------------------------------------------------------------*/ 1041 | 1042 | int 1043 | main(int argc, const char *argv[]) 1044 | { 1045 | int x; 1046 | char cmd[256]; 1047 | int help = 0; 1048 | 1049 | cmd[0] = 0; 1050 | 1051 | for (x = 1; x < argc; ++x) { 1052 | if (argv[x][0] == '-' && argv[x][1] == '-') { 1053 | if (!strcmp(argv[x], "--help")) { 1054 | help = 1; 1055 | } else if (!strcmp(argv[x], "--exec")) { 1056 | exec = 1; 1057 | } else if (!strcmp(argv[x], "--nobdos")) { 1058 | nobdos = 1; 1059 | } else if (!strcmp(argv[x], "--trace_bdos")) { 1060 | trace_bdos = 1; 1061 | } else if (!strcmp(argv[x], "--strace")) { 1062 | strace = 1; 1063 | } else { 1064 | fprintf(stderr, "Unknown option %s\n", argv[x]); 1065 | exit(1); 1066 | } 1067 | } else { 1068 | if (!cmd[0]) { 1069 | strcpy(cmd, argv[x]); 1070 | } else { 1071 | strcat(cmd, " "); 1072 | strcat(cmd, argv[x]); 1073 | } 1074 | } 1075 | } 1076 | 1077 | if (help) { 1078 | fprintf(stderr, "\n%s [options] [CP/M command]\n", argv[0]); 1079 | fprintf(stderr, "\n Options:\n\n"); 1080 | fprintf(stderr, " --help Show this help\n"); 1081 | fprintf(stderr, " --exec Execute the command and exit\n"); 1082 | fprintf(stderr, " --nobdos Do not emulate BDOS: only emulate BIOS\n"); 1083 | fprintf(stderr, " Real disk images will be used. \n"); 1084 | fprintf(stderr, " --trace_bdos Trace BDOS calls\n"); 1085 | fprintf(stderr, "\n"); 1086 | exit(0); 1087 | } 1088 | 1089 | if (cmd[0]) { 1090 | stuff_cmd = cmd; 1091 | } 1092 | 1093 | z80 = new_z80info(); 1094 | 1095 | if (z80 == NULL) 1096 | return -1; 1097 | 1098 | initterm(); 1099 | 1100 | /* set up the signals */ 1101 | #ifdef SIGQUIT 1102 | signal(SIGQUIT, quit); 1103 | #endif 1104 | #ifdef SIGHUP 1105 | signal(SIGHUP, quit); 1106 | #endif 1107 | #ifdef SIGTERM 1108 | signal(SIGTERM, quit); 1109 | #endif 1110 | #ifdef SIGINT 1111 | signal(SIGINT, interrupt); 1112 | #endif 1113 | 1114 | setterm(); 1115 | 1116 | sysreset(z80); 1117 | 1118 | while (1) 1119 | { 1120 | #ifdef macintosh 1121 | EventRecord ev; 1122 | WaitNextEvent(0, &ev, 0, nil); 1123 | #endif 1124 | z80_emulator(z80, 100000); 1125 | } 1126 | } 1127 | -------------------------------------------------------------------------------- /putunix.mac: -------------------------------------------------------------------------------- 1 | ; PUT CP/M FILE INTO UNIX UTILITY 2 | ; putunix driveletter:cpmfilename.extension unixfilename.extension 3 | ; 4 | .Z80 5 | 6 | SRCFCB EQU 005CH 7 | SECFCB EQU 006CH 8 | 9 | CBIOS EQU 0EA00H 10 | OPNUNX EQU CBIOS+33H 11 | CRTUNX EQU CBIOS+36H 12 | RDUNX EQU CBIOS+39H 13 | WRUNX EQU CBIOS+3CH 14 | CLSUNX EQU CBIOS+3FH 15 | 16 | BDOS EQU 5 17 | OPEN EQU 15 18 | CLOSE EQU 16 19 | READ EQU 20 20 | WRITE EQU 21 21 | CREATE EQU 22 22 | SETDMA EQU 26 23 | PRINT EQU 9 24 | 25 | CSEG 26 | ENTRY START 27 | START: 28 | LD SP,STACK 29 | ;MOVE DESTINATION FCB 30 | LD HL,SECFCB 31 | LD DE,DSTFCB 32 | LD BC,10H 33 | LDIR 34 | LD HL,DSTFCB+16 35 | LD DE,DSTFCB+17 36 | LD (HL),0 37 | LD BC,20H 38 | LDIR 39 | 40 | ;OPEN SOURCE FILE 41 | LD DE,SRCFCB 42 | LD C,OPEN 43 | CALL BDOS 44 | CP 0FFH 45 | JP Z,ERROR1 46 | ;CREATE DESTINATION FILE 47 | LD DE,DSTFCB 48 | CALL CRTUNX 49 | AND A 50 | JP NZ,ERROR2 51 | 52 | ;SET DMA ADDRESS 53 | LD DE,0080H 54 | LD C,SETDMA 55 | CALL BDOS 56 | 57 | ;READ ONE, WRITE ONE 58 | LOOP: LD DE,SRCFCB 59 | LD C,READ 60 | CALL BDOS 61 | AND A 62 | JP NZ,CLOSEM 63 | LD DE,DSTFCB 64 | CALL WRUNX 65 | AND A 66 | JP NZ,ERROR4 67 | JP LOOP 68 | 69 | ;CLOSE UNIX FILE 70 | CLOSEM: LD DE,DSTFCB 71 | CALL CLSUNX 72 | AND A 73 | JP Z,CLSCPM 74 | 75 | CLSCPM: LD DE,SRCFCB 76 | LD C,CLOSE 77 | CALL BDOS 78 | JP 0 79 | 80 | ERROR1: LD DE,STR1 81 | JP ERROR 82 | STR1: DEFM "ERROR1" 83 | DEFB 13,10,'$' 84 | ERROR2: LD DE,STR2 85 | JP ERROR 86 | STR2: DEFM "ERROR2" 87 | DEFB 13,10,'$' 88 | ERROR3: LD DE,STR3 89 | JP ERROR 90 | STR3: DEFM "ERROR3" 91 | DEFB 13,10,'$' 92 | ERROR4: LD DE,STR4 93 | JP ERROR 94 | STR4: DEFM "ERROR4" 95 | DEFB 13,10,'$' 96 | 97 | ERROR: LD C,PRINT 98 | CALL BDOS 99 | JP 0 100 | 101 | DSTFCB: DEFS 200 102 | STACK: DEFS 1 103 | END 104 | -------------------------------------------------------------------------------- /tests/adv.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/adv.com -------------------------------------------------------------------------------- /tests/advi.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/advi.dat -------------------------------------------------------------------------------- /tests/advi.ptr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/advi.ptr -------------------------------------------------------------------------------- /tests/advt.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/advt.dat -------------------------------------------------------------------------------- /tests/advt.ptr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/advt.ptr -------------------------------------------------------------------------------- /tests/decode.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/decode.dat -------------------------------------------------------------------------------- /tests/ed.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/ed.com -------------------------------------------------------------------------------- /tests/mbasic.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/mbasic.com -------------------------------------------------------------------------------- /tests/winstall.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/winstall.com -------------------------------------------------------------------------------- /tests/ws.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/ws.com -------------------------------------------------------------------------------- /tests/ws.ins: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/ws.ins -------------------------------------------------------------------------------- /tests/wsmsgs.ovr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/wsmsgs.ovr -------------------------------------------------------------------------------- /tests/wsovly1.ovr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/wsovly1.ovr -------------------------------------------------------------------------------- /tests/wsu.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhallen/cpm/7f15e5aa45972b2fdfa4dd5537ce7a3b730fc4a0/tests/wsu.com -------------------------------------------------------------------------------- /vt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "vt.h" 6 | 7 | int last = -1; 8 | 9 | int kpoll(int w) 10 | { 11 | int c; 12 | unsigned char d; 13 | int tries; 14 | if (last != -1) { 15 | c = last; 16 | last = -1; 17 | return c; 18 | } 19 | for (tries = 0; tries != 1; ++tries) { 20 | #ifndef _WIN32 21 | int flags; 22 | if (w) { 23 | flags = fcntl(fileno(stdin), F_GETFL); 24 | fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK); 25 | } 26 | #endif 27 | c = read(fileno(stdin), &d, 1); 28 | #ifndef _WIN32 29 | if (w) { 30 | fcntl(fileno(stdin), F_SETFL, flags); 31 | } 32 | #endif 33 | if (c == 1) { 34 | /* printf("\r\nkpoll got %d \r\n", d); */ 35 | return d; 36 | } 37 | /* usleep(1); */ 38 | } 39 | /* printf("\r\n--- no char after esc? ---\r\n"); fflush(stdout); */ 40 | return -1; 41 | } 42 | 43 | int constat() 44 | { 45 | if (last != -1) 46 | return 1; 47 | last = kpoll(1); 48 | if (last != -1) 49 | return 1; 50 | else 51 | return 0; 52 | } 53 | 54 | /* Input FIFO */ 55 | 56 | #define FIFO_SIZE 4 57 | int stuff[FIFO_SIZE]; 58 | int stuff_ptr; 59 | 60 | void kpush(int c) 61 | { 62 | if (c != -1 && stuff_ptr != FIFO_SIZE) { 63 | stuff[stuff_ptr++] = c; 64 | } 65 | } 66 | 67 | int kget(int w) 68 | { 69 | int c; 70 | if (stuff_ptr) { 71 | int x; 72 | c = stuff[0]; 73 | for (x = 0; x != stuff_ptr - 1; ++x) { 74 | stuff[x] = stuff[x + 1]; 75 | } 76 | stuff_ptr--; 77 | stuff[x] = 0; 78 | return c; 79 | } 80 | 81 | c = kpoll(w); 82 | if (c != 27) { 83 | return c; 84 | } 85 | /* We got ESC.. see if any chars follow */ 86 | c = kpoll(1); 87 | 88 | if (c == -1) { /* Just ESC */ 89 | return 27; 90 | } else if (c == '[') { 91 | c = kpoll(0); 92 | if (c == 'A') { /* Up arrow */ 93 | return 'E' - '@'; 94 | } else if (c == 'B') { /* Down arrow */ 95 | return 'X' - '@'; 96 | } else if (c == 'C') { /* Right arrow */ 97 | return 'D' - '@'; 98 | } else if (c == 'D') { /* Left arrow */ 99 | return 'S' - '@'; 100 | } else if (c == '3') { /* Delete key */ 101 | c = kpoll(0); 102 | return 'G' - '@'; 103 | } else if (c == '2') { /* Insert key */ 104 | c = kpoll(0); 105 | return 'V' - '@'; 106 | } else if (c == '5') { /* PgUp */ 107 | c = kpoll(0); 108 | return 'R' - '@'; 109 | } else if (c == '6') { /* PgDn */ 110 | c = kpoll(0); 111 | return 'C' - '@'; 112 | } else if (c == '1' || c == '7') { /* Home */ 113 | kpush('s'); 114 | c = kpoll(0); 115 | return 'Q' - '@'; 116 | } else if (c == '4' || c == '8') { /* End */ 117 | kpush('d'); 118 | c = kpoll(0); 119 | return 'Q' - '@'; 120 | } else if (c == 'H') { /* Home */ 121 | kpush('s'); 122 | return 'Q' - '@'; 123 | } else if (c == 'F') { /* End */ 124 | kpush('d'); 125 | return 'Q' - '@'; 126 | } else { 127 | kpush('['); 128 | kpush(c); 129 | return 27; 130 | } 131 | } else if (c == 'O') { 132 | c = kpoll(0); 133 | if (c == 'A') { /* Up arrow */ 134 | return 'E' - '@'; 135 | } else if (c == 'B') { /* Down arrow */ 136 | return 'X' - '@'; 137 | } else if (c == 'C') { /* Right arrow */ 138 | return 'D' - '@'; 139 | } else if (c == 'D') { /* Left arrow */ 140 | return 'S' - '@'; 141 | } else if (c == 'd') { /* Ctrl left arrow (rxvt) */ 142 | return 'A' - '@'; 143 | } else if (c == 'c') { /* Ctrl right arrow (rxvt) */ 144 | return 'F' - '@'; 145 | } else if (c == 'H') { /* Home */ 146 | kpush('s'); 147 | return 'Q' - '@'; 148 | } else if (c == 'F') { /* End */ 149 | kpush('d'); 150 | return 'Q' - '@'; 151 | } else if (c == 'P' || c == 'Q' || c == 'R' || c == 'S') { 152 | return INTR_CHAR; 153 | } else { 154 | kpush('O'); 155 | kpush(c); 156 | return 27; 157 | } 158 | } else { 159 | kpush(c); 160 | return 27; 161 | } 162 | } 163 | 164 | /* 165 | [5~ PgUp 166 | [6~ PgDn 167 | [7~ Home 168 | [8~ End 169 | Od Ctrl-Ltarw 170 | Oc Ctrl-Rtarw 171 | */ 172 | 173 | void putch(char c) { /* output character without postprocessing */ 174 | write(fileno(stdout), &c, 1); 175 | } 176 | 177 | void putmes(const char *s) { 178 | write(fileno(stdout), s, strlen(s)); 179 | } 180 | 181 | void vt52(int c) { /* simple vt52,adm3a => ANSI conversion */ 182 | static int state = 0, x, y; 183 | char buff[32]; 184 | #ifdef DEBUGLOG 185 | static FILE *log = NULL; 186 | if (!log) 187 | log = fopen("cpm.out", "w"); 188 | fputc(c, log); 189 | #endif 190 | switch (state) { 191 | case 0: 192 | switch (c) { 193 | #ifdef VBELL 194 | case 0x07: /* BEL: flash screen */ 195 | putmes("\033[?5h\033[?5l"); 196 | break; 197 | #endif 198 | case 0x7f: /* DEL: echo BS, space, BS */ 199 | putmes("\b \b"); 200 | break; 201 | case 0x1a: /* adm3a clear screen */ 202 | case 0x0c: /* vt52 clear screen */ 203 | putmes("\033[H\033[2J"); 204 | break; 205 | case 0x1e: /* adm3a cursor home */ 206 | putmes("\033[H"); 207 | break; 208 | case 0x1b: 209 | state = 1; /* esc-prefix */ 210 | break; 211 | case 1: 212 | state = 2; /* cursor motion prefix */ 213 | break; 214 | case 2: /* insert line */ 215 | putmes("\033[L"); 216 | break; 217 | case 3: /* delete line */ 218 | putmes("\033[M"); 219 | break; 220 | case 0x18: case 5: /* clear to eol */ 221 | putmes("\033[K"); 222 | break; 223 | case 0x12: case 0x13: 224 | break; 225 | default: 226 | putch(c); 227 | } 228 | break; 229 | case 1: /* esc was sent */ 230 | switch (c) { 231 | case 0x1b: 232 | putch(c); 233 | break; 234 | case '=': 235 | case 'Y': 236 | state = 2; 237 | break; 238 | case 'E': /* insert line */ 239 | putmes("\033[L"); 240 | break; 241 | case 'R': /* delete line */ 242 | putmes("\033[M"); 243 | break; 244 | case 'B': /* enable attribute */ 245 | state = 4; 246 | break; 247 | case 'C': /* disable attribute */ 248 | state = 5; 249 | break; 250 | case 'L': /* set line */ 251 | case 'D': /* delete line */ 252 | state = 6; 253 | break; 254 | case '*': /* set pixel */ 255 | case ' ': /* clear pixel */ 256 | state = 8; 257 | break; 258 | default: /* some true ANSI sequence? */ 259 | state = 0; 260 | putch(0x1b); 261 | putch(c); 262 | } 263 | break; 264 | case 2: 265 | y = c - ' '+1; 266 | state = 3; 267 | break; 268 | case 3: 269 | x = c - ' '+1; 270 | state = 0; 271 | sprintf(buff, "\033[%d;%dH", y, x); 272 | putmes(buff); 273 | break; 274 | case 4: /* +B prefix */ 275 | state = 0; 276 | switch (c) { 277 | case '0': /* start reverse video */ 278 | putmes("\033[7m"); 279 | break; 280 | case '1': /* start half intensity */ 281 | putmes("\033[1m"); 282 | break; 283 | case '2': /* start blinking */ 284 | putmes("\033[5m"); 285 | break; 286 | case '3': /* start underlining */ 287 | putmes("\033[4m"); 288 | break; 289 | case '4': /* cursor on */ 290 | putmes("\033[?25h"); 291 | break; 292 | case '6': /* remember cursor position */ 293 | putmes("\033[s"); 294 | break; 295 | case '5': /* video mode on */ 296 | case '7': /* preserve status line */ 297 | break; 298 | default: 299 | putch(0x1b); 300 | putch('B'); 301 | putch(c); 302 | } 303 | break; 304 | case 5: /* +C prefix */ 305 | state = 0; 306 | switch (c) { 307 | case '0': /* stop reverse video */ 308 | putmes("\033[27m"); 309 | break; 310 | case '1': /* stop half intensity */ 311 | putmes("\033[m"); 312 | break; 313 | case '2': /* stop blinking */ 314 | putmes("\033[25m"); 315 | break; 316 | case '3': /* stop underlining */ 317 | putmes("\033[24m"); 318 | break; 319 | case '4': /* cursor off */ 320 | putmes("\033[?25l"); 321 | break; 322 | case '6': /* restore cursor position */ 323 | putmes("\033[u"); 324 | break; 325 | case '5': /* video mode off */ 326 | case '7': /* don't preserve status line */ 327 | break; 328 | default: 329 | putch(0x1b); 330 | putch('C'); 331 | putch(c); 332 | } 333 | break; 334 | /* set/clear line/point */ 335 | case 6: 336 | case 7: 337 | case 8: 338 | state ++; 339 | break; 340 | case 9: 341 | state = 0; 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /vt.h: -------------------------------------------------------------------------------- 1 | 2 | /* Return true if input character available */ 3 | int constat(); 4 | 5 | /* Get input character: 6 | w = 0: wait until we have a character 7 | w = 1: return -1 if we don' have one 8 | */ 9 | int kget(int w); 10 | 11 | /* Write character to terminal */ 12 | void vt52(int c); 13 | 14 | #define INTR_CHAR 31 /* control-underscore */ 15 | --------------------------------------------------------------------------------