├── .github └── workflows │ └── main.yml ├── LICENSE ├── README.md ├── djl_con.hxx ├── djl_cycle.hxx ├── djl_os.hxx ├── djl_perf.hxx ├── djl_rssrdr.hxx ├── djltrace.hxx ├── m.bat ├── m.sh ├── makefile ├── mg.bat ├── mgr.bat ├── mmac.sh ├── mr.bat ├── mr.sh ├── mrmac.sh ├── mwatcom.bat ├── ntvcm.cxx ├── ntvcm.h ├── tests ├── 8080EX1.COM ├── 8080EXER.COM ├── 8080PRE.COM ├── CPUTEST.COM ├── TEST.COM ├── TST8080.COM ├── ra.bat ├── ra.sh └── rai.sh ├── x80.cxx ├── x80.hxx └── z80test ├── zexall.com └── zexdoc.com /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: windows-2022 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v3 27 | 28 | # Runs a single command using the runners shell 29 | - name: build the app 30 | run: | 31 | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" 32 | .\mr.bat 33 | shell: cmd 34 | 35 | - name: archive the binary 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: application 39 | path: ntvcm.exe 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ntvcm 2 | NT Virtual CP/M Machine. Emulates CP/M 2.2 and the 8080/Z80 on Linux, MacOS, Windows, and real-mode DOS to run .com files. 3 | 4 | Tested with: 5 | 6 | asm.com 7 | load.com 8 | Turbo Pascal v1.00 9 | Turbo Pascal v2.00A 10 | Turbo Pascal v3.01A 11 | WordStar Release 4 12 | mbasic.com BASIC-80 rev. 5.21 (startrek and other apps) 13 | MANX Aztec C v1.05 and v1.06 (compiler, assembler, linker, and generated apps) 14 | CalcStar v1.0 15 | Microsoft fortran-80 v3.4 16 | Microsoft Multiplan (C) 1981 17 | CamelForth beta test version 18 | strtrk.com built for Kaypro II 19 | BASCOM 5.30a 20 | Microsoft MS-COBOL Version 4.65 21 | Digital Research CBASIC Compiler CB-80 Version 2.0 22 | Turbo Modula 2 23 | ALGOL-M COMPILER VERS 1.1 24 | PASCAL/MT+ (80) Version 5.6.1 25 | PL/I-80 V1.3 26 | Eco-C Compiler Version 3.43 27 | HiSoft C v1.35 and v3.09 28 | JRT Pascal ver 4.0 29 | Hochstrasser Computing AG MODULA-2 Compiler for Z80-CP/M Version 2.01 4-Jun-85 30 | FTL Modula-2 V1.30 31 | BD Software C Compiler v1.60 32 | Small-C v1.2 and 2.7 33 | MIX C v2.0,0 34 | Software Toolworks C/80 v3.1 35 | Janus Ada v1.5.0 36 | SuperSoft Ada v2.10 37 | SuperSoft C v1.1.25 38 | MI - C v3.18I 39 | 40 | Console input/output work for both bios bdos API. Disk input/output work via the bdos API. There is no attempt at emulating physical disks. Apps that use bdos for disk I/O will work but apps that use the BIOS or assume things about disk layout will not. 41 | 42 | For example, assuming CP/M 2.2's asm.com and load.com are in the current directory, this will assemble test.asm and generate test.com on Windows, Linux, or MacOS. 43 | 44 | ntvcm asm test 45 | ntvcm load test 46 | 47 | The x80 emulator passes at 100% for: 48 | 49 | 8080: 8080ex1.com, 8080pre.com, Microcosm v1.0, and cputest.com V1.2 in 8080 mode. 50 | 51 | Z80: zexall.com, zexdoc.com, and cputest.com V1.2 in Z80 mode. 52 | 53 | I implemented this to automate testing of apps generated by the BA BASIC compiler in my TTT repo, so I wouldn't have to copy files to my TRS-80 Model 100 or Z80 CP/M 2.2 machines. It was just as painful copying them to other emulators because those (that I found) can't be invoked with other Windows apps in a test script. 54 | 55 | The app supports tracing to the ntvcm.log file. Instruction tracing includes a disassembler that shows instructions either as 8080 or Z80 depending on the mode. 56 | 57 | I've done some testing on Linux and MacOS with the same set of test apps as Windows. It all seems to work, most importantly the CPU tests. CP/M apps expect cr/lf in text files, so you may need to use a tool like unix2dos to convert .asm and .bas files so they work properly in the emulator in those environments. The -l flag in ntvcm is useful if you want to use lowercase filenames instead of CP/M's default of uppercase. 58 | 59 | Performance of the CPU emulator is in the ballpark of other emulators I looked at. I wrote the code to be more readable than other emulators, whose use of lookup tables and macros I found to be nearly inscrutable (though I'm sure that's just me). I also wanted both 8080 and Z80 60 | modes, which hurts the performance of each. An interesting fact is that on modern CPUs, code for computing parity of a byte is faster than a 256-element lookup table. 61 | 62 | zexall.com runs 5.748 billion instructions shared with the 8080, and just 16 million instructions unique to the Z80; optimizing the emulator for the 8080 will have the most impact. On my AMD 5950x machine, zexall.com runs in about 18.4 seconds using the Microsoft compiler and 16.7 seconds using the Gnu compiler. On my Intel i9-14900KF the times are 12.3 and 11.7 seconds. On a real 4Mhz Z80 it'd take about 3 hours and 14 minutes. 63 | 64 | Cycle counts are pretty close, but not precise. I used documented numbers, which are sometimes incorrect. And I made guesses for cycle counts for undocumented Z80 instructions. 65 | 66 | Support for Z80 undocumented instructions and the Y and X flags passes the tests specified above, but I can't vouch for more than that. Undocumented 8080 instructions are not implemented. 67 | 68 | I've created a repo with Windows and Linux build scripts for a variety of compilers, assemblers, and interpreters along with the sieve, e, tm, and tic-tac-toe benchmarks ported to each compiler: [cpm_compilers](https://github.com/davidly/cpm_compilers) 69 | 70 | Non-standard BDOS calls are added for sleeping for a specified number of milliseconds and to fetch and iterate through RSS feeds. On Linux and MacOS, this creates dependencies on httplib.h from https://github.com/yhirose/cpp-httplib and openssl. You must have these dependencies copied 71 | to your machine for NTVCM to build. Or, remove the RSS feature by not defining NTVCM_RSS_SUPPORT in your build script. Each build script includes lines for building with and without. I've been unable to build on MacOS with RSS enabled. rssrdr.c in the Aztec folder is a sample CP/M RSS reader app that can be built with that compiler using m.bat in that folder. 72 | 73 | NTVCM can be built to target RISC-V using g++ then run in the [RVOS RISC-V / Linux emulator](https://github.com/davidly/rvos). RVOS can run that RISC-V image on Windows/MacOS/Linux on AMD64, x86, RISC-V, ARM32, or ARM64. 74 | 75 | NTVCM can be built to target real-mode DOS using the Watcom compiler. Details in mwatcom.bat. 76 | 77 | The two versions of Turbo Pascal and the apps they generate use a tiny fraction of Z80 instructions. It took a day to implement the instructions for Turbo Pascal, and three more days to implement the full instruction set. Getting the undocumented features to work took an extra couple days. The invaluable resources required to do that are listed in x80.cxx. No single resource was 100% correct or complete. It takes a village :) 78 | 79 | Usage: ntvcm [-?] [-c] [-p] [-s:X] [-t] [arg1] [arg2] 80 | A CP/M 2.2 emulator. 81 | 82 | -b backspace/BS/0x08 key sends delete/DEL/0x7f. 83 | (for use with Turbo Pascal). 84 | -c never auto-detect ESC characters and change 85 | to to 80x24 mode. 86 | -C always switch to 80x24 mode. 87 | -d don't clear the display on exit when in 80x24 mode. 88 | -f: specify an input file containing keystrokes. 89 | -i trace 8080/Z80 instructions when tracing with -t. 90 | -k translate Kaypro II extended characters to ASCII 91 | equivalents. 92 | -l force CP/M filenames to be lowercase. 93 | -n don't sleep for apps in tight bdos 6 loops. (Use 94 | with apps like nvbasic). 95 | -p show performance information at app exit. 96 | -s:X specify clock speed in Hz. 97 | defaults to 0 which is as fast as possible. 98 | -t enable debug tracing to ntvcm.log. 99 | -V display version and exit. 100 | -v:X translate escape sequences to VT-100 101 | where X can be one of: 102 | 5: for vt-52 escape sequences (use with CalcStar etc) 103 | k: for Kaypro II/Lear-Siegler ADM-3A escape sequences. 104 | (use with strtrk). 105 | -z:X applies X as a hex mask to SetProcessAffinityMask. 106 | e.g.: 107 | /z:11 2 performance cores on an i7-1280P 108 | /z:3000 2 efficiency cores on an i7-1280P 109 | /z:11 2 random good cores on a 5950x 110 | -8 use 8080 instruction set, not Z80 111 | 112 | e.g. to assemble, load, and run test.asm: 113 | ntvcm asm.com test 114 | ntvcm load.com test 115 | ntvcm test.com 116 | 117 | e.g. to run test.com at 4MHz: 118 | ntvcm -s:4000000 test 119 | 120 | e.g. to run Star Trek in mbasic in 80x24 mode using i8080 emulation: 121 | ntvcm -8 -C mbasic startrek.bas 122 | 123 | Example usage: 124 | 125 | C:\>ntvcm -p z80test\zexall.com 126 | Z80all instruction exerciser 127 | hl,.... OK 128 | add hl,.......... OK 129 | add ix,.......... OK 130 | add iy,.......... OK 131 | aluop a,nn.................... OK 132 | aluop a,.. OK 133 | aluop a,..... OK 134 | aluop a,(+1)........... OK 135 | bit n,(+1)............. OK 136 | bit n,.... OK 137 | cpd........................ OK 138 | cpi........................ OK 139 | ............. OK 140 | a................... OK 141 | b................... OK 142 | bc.................. OK 143 | c................... OK 144 | d................... OK 145 | de.................. OK 146 | e................... OK 147 | h................... OK 148 | hl.................. OK 149 | ix.................. OK 150 | iy.................. OK 151 | l................... OK 152 | (hl)................ OK 153 | sp.................. OK 154 | (+1)......... OK 155 | ixh................. OK 156 | ixl................. OK 157 | iyh................. OK 158 | iyl................. OK 159 | ld ,(nnnn)............. OK 160 | ld hl,(nnnn).................. OK 161 | ld sp,(nnnn).................. OK 162 | ld ,(nnnn)............. OK 163 | ld (nnnn),............. OK 164 | ld (nnnn),hl.................. OK 165 | ld (nnnn),sp.................. OK 166 | ld (nnnn),............. OK 167 | ld ,nnnn......... OK 168 | ld ,nnnn............... OK 169 | ld a,<(bc),(de)>.............. OK 170 | ld ,nn.... OK 171 | ld (+1),nn............. OK 172 | ld ,(+1)...... OK 173 | ld ,(+1).......... OK 174 | ld a,(+1).............. OK 175 | ld ,nn....... OK 176 | ld ,........ OK 177 | ld ,........ OK 178 | ld a,(nnnn) / ld (nnnn),a..... OK 179 | ldd (1).................... OK 180 | ldd (2).................... OK 181 | ldi (1).................... OK 182 | ldi (2).................... OK 183 | neg........................... OK 184 | ..................... OK 185 | ........... OK 186 | shf/rot (+1)........... OK 187 | shf/rot .. OK 188 | n,..... OK 189 | n,(+1)....... OK 190 | ld (+1),...... OK 191 | ld (+1),.......... OK 192 | ld (+1),a.............. OK 193 | ld (),a................ OK 194 | Tests complete 195 | 196 | elapsed milliseconds: 18,366 197 | Z80 cycles: 46,716,093,718 198 | clock rate: unbounded 199 | approx ms at 4Mhz: 11,679,023 == 0 days, 3 hours, 14 minutes, 39 seconds, 23 milliseconds 200 | 201 | 202 | -------------------------------------------------------------------------------- /djl_con.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef M68K 4 | extern "C" int clock_gettime( clockid_t id, struct timespec * res ); 5 | #endif 6 | 7 | #ifdef WATCOM 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class ConsoleConfiguration 14 | { 15 | private: 16 | bool outputEstablished; 17 | void (__interrupt __far * prev_int_23)(); 18 | static void __interrupt __far controlc_routine() {} 19 | 20 | public: 21 | ConsoleConfiguration() : outputEstablished( false ), prev_int_23( 0 ) {} 22 | ~ConsoleConfiguration() {} 23 | 24 | void EstablishConsoleOutput( int16_t width = 80, int16_t height = 24 ) 25 | { 26 | if ( outputEstablished ) 27 | return; 28 | 29 | outputEstablished = true; 30 | _setvideomode( 3 ); // 80x25 color text 31 | 32 | // hook the ^C interrupt so the default handler doesn't terminate the app 33 | 34 | prev_int_23 = _dos_getvect( 0x23 ); 35 | _dos_setvect( 0x23, controlc_routine ); 36 | } //EstablishConsoleOutput 37 | 38 | static int portable_kbhit() { return kbhit(); } 39 | static int throttled_kbhit() { return kbhit(); } 40 | static int portable_getch() { return getch(); } 41 | static char * portable_gets_s( char * buf, size_t bufsize ) { return gets( buf ); } 42 | 43 | void RestoreConsoleInput() 44 | { 45 | if ( 0 != prev_int_23 ) 46 | { 47 | _dos_setvect( 0x23, prev_int_23 ); 48 | prev_int_23 = 0; 49 | } 50 | } //RestoreConsoleInput 51 | 52 | void RestoreConsoleOutput( bool clearScreen = true ) 53 | { 54 | outputEstablished = false; 55 | } //RestoreConsoleOutput 56 | 57 | void RestoreConsole( bool clearScreen = true ) 58 | { 59 | RestoreConsoleInput(); 60 | RestoreConsoleOutput( clearScreen ); 61 | } //RestoreConsole 62 | 63 | bool IsOutputEstablished() { return outputEstablished; } 64 | }; 65 | 66 | #else 67 | 68 | #include 69 | using namespace std; 70 | using namespace std::chrono; 71 | 72 | class ConsoleConfiguration 73 | { 74 | private: 75 | #ifdef _WIN32 76 | HANDLE consoleOutputHandle; 77 | HANDLE consoleInputHandle; 78 | WINDOWPLACEMENT oldWindowPlacement; 79 | CONSOLE_SCREEN_BUFFER_INFOEX oldScreenInfo; 80 | DWORD oldOutputConsoleMode, oldInputConsoleMode; 81 | CONSOLE_CURSOR_INFO oldCursorInfo; 82 | int16_t setWidth; 83 | UINT oldOutputCP; 84 | static const size_t longestEscapeSequence = 10; // probably overkill 85 | char aReady[ 1 + longestEscapeSequence ]; 86 | #else 87 | #if !defined( OLDGCC ) && !defined( M68K ) 88 | struct termios orig_termios; 89 | #endif 90 | #endif 91 | 92 | bool inputEstablished, outputEstablished; 93 | 94 | #ifdef _WIN32 95 | // this function takes Windows keyboard input and produces Linux keyboard input 96 | bool process_key_event( INPUT_RECORD & rec, char * pout ) 97 | { 98 | *pout = 0; 99 | 100 | if ( KEY_EVENT != rec.EventType ) 101 | return false; 102 | 103 | if ( !rec.Event.KeyEvent.bKeyDown ) 104 | return false; 105 | 106 | const uint8_t asc = rec.Event.KeyEvent.uChar.AsciiChar; 107 | const uint8_t sc = (uint8_t) rec.Event.KeyEvent.wVirtualScanCode; 108 | 109 | // don't pass back just an Alt/ctrl/shift/capslock without another character 110 | if ( ( 0 == asc ) && ( 0x38 == sc || 0x1d == sc || 0x2a == sc || 0x3a == sc || 0x36 == sc ) ) 111 | return false; 112 | 113 | bool fshift = ( 0 != ( rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED ) ); 114 | bool fctrl = ( 0 != ( rec.Event.KeyEvent.dwControlKeyState & ( LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED ) ) ); 115 | bool falt = ( 0 != ( rec.Event.KeyEvent.dwControlKeyState & ( LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED ) ) ); 116 | 117 | tracer.Trace( " process key event sc/asc %02x%02x, shift %d, ctrl %d, alt %d\n", sc, asc, fshift, fctrl, falt ); 118 | 119 | // control+ 1, 3-5, and 7-0 should be swallowed. 2 and 6 are allowed. 120 | if ( fctrl && ( 2 == sc || 4 == sc || 5 == sc || 6 == sc || 8 == sc || 9 == sc || 0xa == sc || 0xb == sc ) ) 121 | return false; 122 | 123 | // control+ =, ;, ', `, ,, ., / should be swallowed 124 | if ( fctrl && ( 0x0d == sc || 0x27 == sc || 0x28 == sc || 0x29 == sc || 0x33 == sc || 0x34 == sc || 0x35 == sc ) ) 125 | return false; 126 | 127 | // alt+ these characters has no meaning 128 | if ( falt && ( '\'' == asc || '`' == asc || ',' == asc || '.' == asc || '/' == asc || 0x4c == sc ) ) 129 | return false; 130 | 131 | pout[ 0 ] = asc; 132 | pout[ 1 ] = 0; 133 | 134 | if ( falt && asc >= ' ' && asc <= 0x7f ) 135 | { 136 | sprintf( pout, "\033%c", asc ); 137 | } 138 | else if ( 0x01 == sc ) // ESC 139 | { 140 | // can't test on Windows if ( falt ) ; 141 | } 142 | else if ( 0x0e == sc ) // Backspace 143 | { 144 | if ( falt ) 145 | { 146 | pout[ 0 ] = 0x1b; 147 | pout[ 1 ] = 0x7f; 148 | pout[ 2 ] = 0; 149 | } 150 | else if ( fctrl ) 151 | pout[ 0 ] = 8; 152 | else 153 | pout[ 0 ] = 0x7f; // shift and no shift 154 | } 155 | else if ( 0x0f == sc ) // Tab 156 | { 157 | // can't test on Windows if ( falt ) ; 158 | // can't test on Windows else if ( fctrl ) ; 159 | if ( fshift ) strcpy( pout, "\033[Z" ); 160 | } 161 | else if ( 0x35 == sc ) // keypad / 162 | { 163 | if ( falt ) strcpy( pout, "\033/" ); 164 | else if ( fctrl ) *pout = 31; 165 | } 166 | else if ( 0x37 == sc ) // keypad * 167 | { 168 | if ( falt ) strcpy( pout, "\033*" ); 169 | else if ( fctrl ) *pout = 10; 170 | } 171 | else if ( sc >= 0x3b && sc <= 0x44 ) // F1 - F10 172 | { 173 | if ( falt ) 174 | { 175 | if ( sc >= 0x3b && sc <= 0x3e ) 176 | sprintf( pout, "\033[1;3%c", sc - 0x3b + 80 ); 177 | else if ( sc == 0x3f ) 178 | strcpy( pout, "\033[15;3~" ); 179 | else if ( sc >= 0x40 && sc <= 0x42 ) 180 | sprintf( pout, "\033[1%c;3~", 55 + sc - 0x40 ); 181 | else if ( sc >= 0x43 && sc <= 0x44 ) 182 | sprintf( pout, "\033[2%c;3~", 48 + sc - 0x43 ); 183 | } 184 | else if ( fctrl ) 185 | { 186 | if ( sc >= 0x3b && sc <= 0x3e ) 187 | sprintf( pout, "\033[1;5%c", sc - 0x3b + 80 ); 188 | else if ( sc == 0x3f ) 189 | strcpy( pout, "\033[15;5~" ); 190 | else if ( sc >= 0x40 && sc <= 0x42 ) 191 | sprintf( pout, "\033[2%c;5~", 55 + sc - 0x40 ); 192 | else if ( sc >= 0x43 && sc <= 0x44 ) 193 | sprintf( pout, "\033[2%c;5~", 48 + sc - 0x43 ); 194 | } 195 | else if ( fshift ) 196 | { 197 | if ( sc >= 0x3b && sc <= 0x3e ) 198 | sprintf( pout, "\033[1;2%c", sc - 0x3b + 80 ); 199 | else if ( sc == 0x3f ) 200 | strcpy( pout, "\033[15;2~" ); 201 | else if ( sc >= 0x40 && sc <= 0x42 ) 202 | sprintf( pout, "\033[1%c;2~", 55 + sc - 0x40 ); 203 | else if ( sc >= 0x43 && sc <= 0x44 ) 204 | sprintf( pout, "\033[2%c;2~", 48 + sc - 0x43 ); 205 | } 206 | else 207 | { 208 | if ( sc >= 0x3b && sc <= 0x3e ) 209 | sprintf( pout, "\033O%c", 80 + sc - 0x3b ); 210 | else if ( sc == 0x3f ) 211 | strcpy( pout, "\033[15~" ); 212 | else if ( sc >= 0x40 && sc <= 0x42 ) 213 | sprintf( pout, "\033[1%c~", 55 + sc - 0x40 ); 214 | else 215 | sprintf( pout, "\033[2%c~", 48 + sc - 0x43 ); 216 | } 217 | } 218 | else if ( 0x47 == sc ) // Home 219 | { 220 | if ( falt ) strcpy( pout, "\033[1;3H" ); 221 | else if ( fctrl ) strcpy( pout, "\033[1;5H" ); 222 | else if ( fshift ) strcpy( pout, "\033[1;2H" ); 223 | else strcpy( pout, "\033[H" ); 224 | } 225 | else if ( 0x48 == sc ) // up arrow 226 | { 227 | if ( falt ) strcpy( pout, "\033[1;3A" ); 228 | else if ( fctrl ) strcpy( pout, "\033[1;5A" ); 229 | else if ( fshift ) strcpy( pout, "\033[1;2A" ); 230 | else strcpy( pout, "\033[A" ); 231 | } 232 | else if ( 0x49 == sc ) // page up 233 | { 234 | if ( falt ) strcpy( pout, "\033[5;3~" ); 235 | else if ( fctrl ) strcpy( pout, "\033[5;5~" ); 236 | else if ( fshift ) strcpy( pout, "\033[5;2~" ); 237 | else strcpy( pout, "\033[5~" ); 238 | } 239 | else if ( 0x4a == sc ) // keypad - 240 | { 241 | if ( falt ) strcpy( pout, "\033-" ); 242 | // can't test on Windows else if ( fctrl ) ; 243 | } 244 | else if ( 0x4b == sc ) // left arrow 245 | { 246 | if ( falt ) strcpy( pout, "\033[1;3D" ); 247 | else if ( fctrl ) strcpy( pout, "\033[1;5D" ); 248 | else if ( fshift ) strcpy( pout, "\033[1;2D" ); 249 | else strcpy( pout, "\033[D" ); 250 | } 251 | else if ( 0x4c == sc ) // keypad 5 252 | { 253 | // no output on Linux if ( fctrl ) ; 254 | // no output on Linux else if ( fshift ) ; 255 | } 256 | else if ( 0x4d == sc ) // right arrow 257 | { 258 | if ( falt ) strcpy( pout, "\033[1;3C" ); 259 | else if ( fctrl ) strcpy( pout, "\033[1;5C" ); 260 | else if ( fshift ) strcpy( pout, "\033[1;2C" ); 261 | else strcpy( pout, "\033[C" ); 262 | } 263 | else if ( 0x4e == sc ) // keypad + 264 | { 265 | if ( falt ) strcpy( pout, "\033+" ); 266 | else if ( fshift ) *pout = 43; 267 | else if ( fctrl ) *pout = 43; 268 | else *pout = 43; 269 | } 270 | else if ( 0x4f == sc ) // End 271 | { 272 | if ( falt ) strcpy( pout, "\033[1;3F" ); 273 | else if ( fctrl ) strcpy( pout, "\033[1;5F" ); 274 | else if ( fshift ) strcpy( pout, "\033[1;2F" ); 275 | else strcpy( pout, "\033[F" ); 276 | } 277 | else if ( 0x50 == sc ) // down arrow 278 | { 279 | if ( falt ) strcpy( pout, "\033[1;3B" ); 280 | else if ( fctrl ) strcpy( pout, "\033[1;5B" ); 281 | else if ( fshift ) strcpy( pout, "\033[1;2B" ); 282 | else strcpy( pout, "\033[B" ); 283 | } 284 | else if ( 0x51 == sc ) // page down 285 | { 286 | if ( falt ) strcpy( pout, "\033[6;3~" ); 287 | else if ( fctrl ) strcpy( pout, "\033[6;5~" ); 288 | else if ( fshift ) strcpy( pout, "\033[6;2~" ); 289 | else strcpy( pout, "\033[6~" ); 290 | } 291 | else if ( 0x52 == sc ) // INS 292 | { 293 | if ( falt ) strcpy( pout, "\033[2;3~" ); 294 | else if ( fctrl ) strcpy( pout, "\033[2;5~" ); 295 | else if ( fshift ) ; // can't repro on windows 296 | else strcpy( pout, "\033[2~" ); 297 | } 298 | else if ( 0x53 == sc ) // Del 299 | { 300 | if ( falt ) strcpy( pout, "\033[3;3~" ); 301 | else if ( fctrl ) strcpy( pout, "\033[3;5~" ); 302 | else if ( fshift ) strcpy( pout, "\033[3;2~" ); 303 | else strcpy( pout, "\033[3~" ); 304 | } 305 | else if ( 0x57 == sc || 0x58 == sc ) // F11 - F12 306 | { 307 | if ( falt ) sprintf( pout, "\033[2%c;3~", sc - 0x57 + 51 ); 308 | else if ( fctrl ) sprintf( pout, "\033[2%c;5~", sc - 0x57 + 51 ); 309 | else if ( fshift ) sprintf( pout, "\033[2%c;2~", sc - 0x57 + 51 ); 310 | else sprintf( pout, "\033[2%c~", 51 + sc - 0x57 ); 311 | } 312 | 313 | return true; 314 | } //process_key_event 315 | #endif // _WIN32 316 | 317 | public: 318 | #ifdef _WIN32 319 | ConsoleConfiguration() : oldOutputConsoleMode( 0 ), oldInputConsoleMode( 0 ), 320 | setWidth( 0 ), oldOutputCP( 0 ), 321 | inputEstablished( false ), outputEstablished( false ) 322 | { 323 | oldWindowPlacement = {0}; 324 | oldScreenInfo = {0}; 325 | oldCursorInfo = {0}; 326 | 327 | consoleOutputHandle = GetStdHandle( STD_OUTPUT_HANDLE ); 328 | consoleInputHandle = GetStdHandle( STD_INPUT_HANDLE ); 329 | 330 | EstablishConsoleInput(); 331 | 332 | memset( aReady, 0, sizeof( aReady ) ); 333 | } //ConsoleConfiguration 334 | #else 335 | ConsoleConfiguration() : inputEstablished( false ), outputEstablished( false ) 336 | { 337 | EstablishConsoleInput(); 338 | } //ConsoleConfiguration 339 | #endif 340 | 341 | ~ConsoleConfiguration() 342 | { 343 | RestoreConsole(); 344 | } 345 | 346 | bool IsOutputEstablished() { return outputEstablished; } 347 | 348 | #ifdef _WIN32 349 | HANDLE GetOutputHandle() { return consoleOutputHandle; }; 350 | HANDLE GetInputHandle() { return consoleInputHandle; }; 351 | #endif 352 | 353 | void SetCursorInfo( uint32_t size ) // 0 to 100 354 | { 355 | #ifdef _WIN32 356 | if ( 0 != consoleOutputHandle ) 357 | { 358 | CONSOLE_CURSOR_INFO info = {0}; 359 | if ( 0 == size ) 360 | { 361 | info.dwSize = 1; 362 | info.bVisible = FALSE; 363 | } 364 | else 365 | { 366 | info.dwSize = size; 367 | info.bVisible = TRUE; 368 | } 369 | 370 | SetConsoleCursorInfo( consoleOutputHandle, &info ); 371 | } 372 | #else 373 | if ( outputEstablished ) 374 | { 375 | if ( 0 == size ) 376 | printf( "\x1b[?25l" ); 377 | else 378 | printf( "\x1b[?25h" ); 379 | fflush( stdout ); 380 | } 381 | #endif 382 | } //SetCursorInfo 383 | 384 | void EstablishConsoleInput( void * pCtrlCRoutine = 0 ) 385 | { 386 | if ( !isatty( fileno( stdin ) ) ) 387 | return; 388 | 389 | if ( inputEstablished ) 390 | RestoreConsoleInput(); 391 | 392 | #ifdef _WIN32 393 | GetConsoleMode( consoleOutputHandle, &oldInputConsoleMode ); 394 | 395 | if ( 0 == pCtrlCRoutine ) 396 | { 397 | DWORD dwMode = oldInputConsoleMode; 398 | dwMode &= ~ENABLE_PROCESSED_INPUT; 399 | SetConsoleMode( consoleInputHandle, dwMode ); 400 | tracer.Trace( "old and new console input mode: %#x, %#x\n", oldInputConsoleMode, dwMode ); 401 | } 402 | else 403 | { 404 | // don't automatically have ^c terminate the app. ^break will still terminate the app 405 | 406 | PHANDLER_ROUTINE handler = (PHANDLER_ROUTINE) pCtrlCRoutine; 407 | SetConsoleCtrlHandler( handler, TRUE ); 408 | } 409 | #else 410 | #if !defined( OLDGCC ) && !defined( M68K ) // these will never run on actual Linux and the emulators or platform already are configured for raw keystrokes 411 | tcgetattr( 0, &orig_termios ); 412 | 413 | // make input raw so it's possible to peek to see if a keystroke is available 414 | 415 | struct termios new_termios; 416 | memcpy( &new_termios, &orig_termios, sizeof( new_termios ) ); 417 | 418 | cfmakeraw( &new_termios ); 419 | new_termios.c_oflag = orig_termios.c_oflag; 420 | tcsetattr( 0, TCSANOW, &new_termios ); 421 | #endif 422 | #endif 423 | 424 | inputEstablished = true; 425 | } //EstablishConsoleInput 426 | 427 | void EstablishConsoleOutput( int16_t width = 80, int16_t height = 24 ) 428 | { 429 | tracer.Trace( " EstablishConsoleOutput width %u height %u, outputEstablished %d\n", width, height, outputEstablished ); 430 | if ( outputEstablished ) 431 | return; 432 | 433 | #ifdef _WIN32 434 | GetConsoleCursorInfo( consoleOutputHandle, &oldCursorInfo ); 435 | 436 | if ( 0 != width ) 437 | { 438 | oldWindowPlacement.length = sizeof oldWindowPlacement; 439 | GetWindowPlacement( GetConsoleWindow(), &oldWindowPlacement ); 440 | 441 | oldOutputCP = GetConsoleOutputCP(); 442 | SetConsoleOutputCP( 437 ); 443 | 444 | oldScreenInfo.cbSize = sizeof oldScreenInfo; 445 | GetConsoleScreenBufferInfoEx( consoleOutputHandle, &oldScreenInfo ); 446 | 447 | CONSOLE_SCREEN_BUFFER_INFOEX newInfo; 448 | memcpy( &newInfo, &oldScreenInfo, sizeof newInfo ); 449 | 450 | setWidth = width; 451 | newInfo.dwSize.X = width; 452 | newInfo.dwSize.Y = height; 453 | newInfo.dwMaximumWindowSize.X = width; 454 | newInfo.dwMaximumWindowSize.Y = height; 455 | 456 | newInfo.wAttributes = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN; 457 | 458 | // legacy DOS RGB values 459 | newInfo.ColorTable[ 0 ] = 0; 460 | newInfo.ColorTable[ 1 ] = 0x800000; 461 | newInfo.ColorTable[ 2 ] = 0x008000; 462 | newInfo.ColorTable[ 3 ] = 0x808000; 463 | newInfo.ColorTable[ 4 ] = 0x000080; 464 | newInfo.ColorTable[ 5 ] = 0x800080; 465 | newInfo.ColorTable[ 6 ] = 0x008080; 466 | newInfo.ColorTable[ 7 ] = 0xc0c0c0; 467 | newInfo.ColorTable[ 8 ] = 0x808080; 468 | newInfo.ColorTable[ 9 ] = 0xff0000; 469 | newInfo.ColorTable[ 10 ] = 0x00ff00; 470 | newInfo.ColorTable[ 11 ] = 0xffff00; 471 | newInfo.ColorTable[ 12 ] = 0x0000ff; 472 | newInfo.ColorTable[ 13 ] = 0xff00ff; 473 | newInfo.ColorTable[ 14 ] = 0x00ffff; 474 | newInfo.ColorTable[ 15 ] = 0xffffff; 475 | SetConsoleScreenBufferInfoEx( consoleOutputHandle, &newInfo ); 476 | 477 | COORD newSize = { width, height }; 478 | SetConsoleScreenBufferSize( consoleOutputHandle, newSize ); 479 | } 480 | 481 | DWORD dwMode = 0; 482 | GetConsoleMode( consoleOutputHandle, &dwMode ); 483 | oldOutputConsoleMode = dwMode; 484 | dwMode |= ( ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_WINDOW_INPUT ); 485 | tracer.Trace( "old and new console output mode: %04x, %04x\n", oldOutputConsoleMode, dwMode ); 486 | SetConsoleMode( consoleOutputHandle, dwMode ); 487 | #else 488 | #ifndef M68K 489 | if ( isatty( fileno( stdout ) ) ) 490 | { 491 | printf( "%c[1 q", 27 ); // 1 == cursor blinking block. 492 | fflush( stdout ); 493 | } 494 | #endif 495 | #endif 496 | 497 | outputEstablished = true; 498 | 499 | if ( 0 != width ) 500 | SendClsSequence(); 501 | } //EstablishConsoleOutput 502 | 503 | void RestoreConsoleInput() 504 | { 505 | if ( inputEstablished ) 506 | { 507 | #ifdef _WIN32 508 | SetConsoleMode( consoleInputHandle, oldInputConsoleMode ); 509 | #else 510 | #if !defined( OLDGCC ) && !defined( M68K ) 511 | tcsetattr( 0, TCSANOW, &orig_termios ); 512 | #endif 513 | #endif 514 | 515 | inputEstablished = false; 516 | } 517 | } //RestoreConsoleInput 518 | 519 | void RestoreConsoleOutput( bool clearScreen = true ) 520 | { 521 | if ( outputEstablished ) 522 | { 523 | #if !defined( _WIN32 ) && !defined( M68K ) 524 | if ( isatty( fileno( stdout ) ) ) 525 | { 526 | printf( "%c[0m", 27 ); // turn off display attributes 527 | fflush( stdout ); 528 | } 529 | #endif 530 | 531 | if ( clearScreen ) 532 | SendClsSequence(); 533 | 534 | #ifdef _WIN32 535 | SetConsoleOutputCP( oldOutputCP ); 536 | SetConsoleCursorInfo( consoleOutputHandle, & oldCursorInfo ); 537 | 538 | if ( 0 != setWidth ) 539 | { 540 | SetConsoleScreenBufferInfoEx( consoleOutputHandle, & oldScreenInfo ); 541 | SetWindowPlacement( GetConsoleWindow(), & oldWindowPlacement ); 542 | } 543 | 544 | SetConsoleMode( consoleOutputHandle, oldOutputConsoleMode ); 545 | #endif 546 | 547 | outputEstablished = false; 548 | } 549 | } //RestoreConsoleOutput 550 | 551 | void RestoreConsole( bool clearScreen = true ) 552 | { 553 | RestoreConsoleInput(); 554 | RestoreConsoleOutput( clearScreen ); 555 | } //RestoreConsole 556 | 557 | void SendClsSequence() 558 | { 559 | if ( isatty( fileno( stdout ) ) ) 560 | { 561 | #if !defined( M68K ) 562 | printf( "\x1b[2J" ); // clear the screen 563 | printf( "\x1b[1G" ); // cursor to top line 564 | printf( "\x1b[1d" ); // cursor to left side 565 | fflush( stdout ); 566 | #endif 567 | } 568 | } //SendClsSequence 569 | 570 | void ClearScreen() 571 | { 572 | #ifdef _WIN32 573 | if ( 0 != consoleOutputHandle ) 574 | SendClsSequence(); 575 | else 576 | { 577 | HANDLE hcon = GetStdHandle( STD_OUTPUT_HANDLE ); 578 | DWORD dwMode = 0; 579 | GetConsoleMode( hcon, &dwMode ); 580 | DWORD oldMode = dwMode; 581 | dwMode |= ( ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_WINDOW_INPUT ); 582 | SetConsoleMode( hcon, dwMode ); 583 | SendClsSequence(); 584 | SetConsoleMode( hcon, oldMode ); 585 | } 586 | #else 587 | SendClsSequence(); 588 | #endif 589 | } //ClearScreen 590 | 591 | int portable_kbhit() 592 | { 593 | if ( !isatty( fileno( stdin ) ) ) 594 | return ( 0 == feof( stdin) ); 595 | 596 | #ifdef _WIN32 597 | if ( 0 != aReady[ 0 ] ) 598 | return true; 599 | return _kbhit(); 600 | #else 601 | fd_set set; 602 | FD_ZERO( &set ); 603 | FD_SET( STDIN_FILENO, &set ); 604 | struct timeval timeout = {0}; 605 | return ( select( 1, &set, NULL, NULL, &timeout ) > 0 ); 606 | #endif 607 | } //portable_kbhit 608 | 609 | static int redirected_getch() 610 | { 611 | assert( !isatty( fileno( stdin ) ) ); 612 | static bool look_ahead_available = false; 613 | static char look_ahead = 0; 614 | 615 | if ( look_ahead_available ) 616 | { 617 | look_ahead_available = false; 618 | return look_ahead; 619 | } 620 | 621 | char data; 622 | if ( 1 == read( 0, &data, 1 ) ) 623 | { 624 | // for files with CR/LF, skip the CR and turn the LF into a CR 625 | // for files with LF, turn the LF into a CR 626 | // Windows does this for free 627 | 628 | #ifndef _WIN32 629 | if ( ( 13 == data ) && ( !feof( stdin ) ) ) 630 | { 631 | if ( 0 == read( 0, &look_ahead, 1 ) ) // make gcc not complain by checking return code 632 | look_ahead = 13; 633 | 634 | if ( 10 != look_ahead ) 635 | look_ahead_available = true; 636 | } 637 | #endif 638 | return data; 639 | } 640 | return EOF; 641 | } //redirected_getch() 642 | 643 | #ifdef _WIN32 644 | // behave like getch() on linux -- extended characters have escape sequences 645 | 646 | int linux_getch() 647 | { 648 | if ( !isatty( fileno( stdin ) ) ) 649 | return redirected_getch(); 650 | 651 | size_t cReady = strlen( aReady ); 652 | if ( 0 != cReady ) 653 | { 654 | int result = aReady[ 0 ]; 655 | memmove( & aReady[ 0 ], & aReady[ 1 ], cReady ); // may just be the null termination 656 | return result; 657 | } 658 | 659 | HANDLE hConsoleInput = GetStdHandle( STD_INPUT_HANDLE ); 660 | 661 | do 662 | { 663 | DWORD available = 0; 664 | BOOL ok = GetNumberOfConsoleInputEvents( hConsoleInput, &available ); 665 | if ( ok && ( 0 != available ) ) 666 | { 667 | INPUT_RECORD records[ 1 ]; 668 | DWORD numRead = 0; 669 | ok = ReadConsoleInput( hConsoleInput, records, 1, &numRead ); 670 | if ( ok ) 671 | { 672 | for ( DWORD x = 0; x < numRead; x++ ) 673 | { 674 | char acSequence[ longestEscapeSequence ] = {0}; 675 | bool used = process_key_event( records[ x ], acSequence ); 676 | if ( !used ) 677 | continue; 678 | 679 | strcat( aReady, acSequence ); 680 | tracer.Trace( " consumed sequence of length %zd, total cached len %zd\n", strlen( acSequence ), strlen( aReady ) ); 681 | } 682 | } 683 | } 684 | 685 | if ( 0 != aReady[ 0 ] ) 686 | return linux_getch(); 687 | } while( true ); 688 | 689 | tracer.Trace( " bug in linux_getch; returning nothing\n" ); 690 | assert( false ); 691 | return 0; 692 | } //linux_getch 693 | #endif // _WIN32 694 | 695 | static int portable_getch() 696 | { 697 | if ( !isatty( fileno( stdin ) ) ) 698 | return redirected_getch(); 699 | 700 | #ifdef _WIN32 701 | return _getch(); 702 | #else 703 | int r; 704 | unsigned char c; 705 | 706 | do 707 | { 708 | if ( ( r = read( 0, &c, sizeof( c ) ) ) < 0 ) 709 | return r; 710 | if ( 0 != r ) // Linux platforms I tested wait for a char to be available. MacOS returns 0 immediately. 711 | break; 712 | //tracer.Trace( " sleeping in portable_getch()\n" ); 713 | sleep_ms( 1 ); 714 | } while( true ); 715 | 716 | return c; 717 | #endif 718 | } //portable_getch 719 | 720 | bool throttled_kbhit() 721 | { 722 | // _kbhit() does device I/O in Windows, which sleeps for a tiny amount waiting for a reply, so 723 | // compute-bound mbasic.com apps run 10x slower than they should because mbasic polls for keyboard input. 724 | // Workaround: only call _kbhit() if 50 milliseconds has gone by since the last call. 725 | 726 | #ifdef M68K // newlib for embedded only has second-level granularity for high_resolution_clock, so use a different codepath for that 727 | static struct timespec last_call; 728 | static int static_result = clock_gettime( CLOCK_REALTIME, &last_call ); 729 | struct timespec tNow; 730 | int result = clock_gettime( CLOCK_REALTIME, &tNow ); 731 | uint32_t difference = (uint32_t) ( ( ( tNow.tv_sec - last_call.tv_sec ) * 1000 ) + ( ( tNow.tv_nsec - last_call.tv_nsec ) / 1000000 ) ); 732 | 733 | #else 734 | static high_resolution_clock::time_point last_call = high_resolution_clock::now(); 735 | high_resolution_clock::time_point tNow = high_resolution_clock::now(); 736 | long long difference = duration_cast( tNow - last_call ).count(); 737 | #endif 738 | 739 | if ( difference > 50 ) 740 | { 741 | last_call = tNow; 742 | return portable_kbhit(); 743 | } 744 | 745 | return false; 746 | } //throttled_kbhit 747 | 748 | static char * portable_gets_s( char * buf, size_t bufsize ) 749 | { 750 | size_t len = 0; 751 | do 752 | { 753 | char ch = (char) portable_getch(); 754 | if ( '\n' == ch || '\r' == ch ) 755 | { 756 | printf( "\n" ); 757 | fflush( stdout ); // fflush is required on linux or it'll be buffered not seen until the app ends. 758 | break; 759 | } 760 | 761 | if ( len >= ( bufsize - 1 ) ) 762 | break; 763 | 764 | if ( 0x7f == ch || 8 == ch ) // backspace (it's not 8 for some reason) 765 | { 766 | if ( len > 0 ) 767 | { 768 | printf( "\x8 \x8" ); 769 | fflush( stdout ); 770 | len--; 771 | } 772 | } 773 | else 774 | { 775 | printf( "%c", ch ); 776 | fflush( stdout ); 777 | buf[ len++ ] = ch; 778 | } 779 | } while( true ); 780 | 781 | buf[ len ] = 0; 782 | return buf; 783 | } //portable_gets_s 784 | }; //ConsoleConfiguration 785 | 786 | #endif // WATCOM 787 | -------------------------------------------------------------------------------- /djl_cycle.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | using namespace std; 5 | 6 | #ifndef WATCOM 7 | using namespace std::chrono; 8 | #endif 9 | 10 | class CPUCycleDelay 11 | { 12 | #ifdef WATCOM // no implementation on DOS 13 | public: 14 | CPUCycleDelay( uint64_t clockRate ) {} 15 | void Reset() {} 16 | void Delay( uint64_t cycles_total ) {} 17 | #else 18 | private: 19 | high_resolution_clock::time_point start_execution; 20 | uint64_t clock_rate; 21 | 22 | public: 23 | CPUCycleDelay( uint64_t clockRate ) : clock_rate( clockRate ) 24 | { 25 | Reset(); 26 | } //CPUCycleDelay 27 | 28 | void Reset() 29 | { 30 | if ( 0 != clock_rate ) 31 | start_execution = high_resolution_clock::now(); 32 | } //Reset 33 | 34 | void Delay( uint64_t cycles_total ) 35 | { 36 | if ( 0 != clock_rate ) 37 | { 38 | uint64_t targetMicroseconds = ( 1000000 * cycles_total ) / clock_rate; 39 | 40 | do 41 | { 42 | high_resolution_clock::time_point right_now = high_resolution_clock::now(); 43 | uint64_t sofar = duration_cast( right_now - start_execution ).count(); 44 | 45 | if ( sofar >= targetMicroseconds ) 46 | break; 47 | 48 | // sleep in a slightly less than busy loop. 49 | // this is slightly slower than running in a busy loop, but it's pretty close 50 | 51 | #ifdef _WIN32 52 | SleepEx( 1, FALSE ); 53 | #elif defined( M68K ) 54 | usleep( 1000 ); // 1 millisecond 55 | #else 56 | struct timespec ts = { 0, 1000000 }; 57 | nanosleep( &ts, 0 ); 58 | #endif 59 | } while ( true ); 60 | } 61 | } //Delay 62 | #endif //WATCOM 63 | }; //CPUCycleDelay 64 | -------------------------------------------------------------------------------- /djl_os.hxx: -------------------------------------------------------------------------------- 1 | /* 2 | These are some utilities and abstractions for building on Windows and Linux 3 | */ 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef _WIN32 12 | 13 | #ifndef UNICODE 14 | #define UNICODE 15 | #endif 16 | #define WIN32_LEAN_AND_MEAN 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define not_inlined __declspec(noinline) 24 | #define force_inlined __forceinline 25 | 26 | inline void sleep_ms( uint64_t ms ) { SleepEx( (DWORD) ms, FALSE ); } 27 | 28 | inline bool file_exists( char const * pfile ) 29 | { 30 | uint32_t attr = GetFileAttributesA( pfile ); 31 | return ( ( INVALID_FILE_ATTRIBUTES != attr ) && ( ! ( FILE_ATTRIBUTE_DIRECTORY & attr ) ) ); 32 | } //file_exists 33 | 34 | inline void bump_thread_priority() { SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST ); } 35 | 36 | inline void set_process_affinity( uint64_t processAffinityMask ) 37 | { 38 | SetProcessAffinityMask( (HANDLE) -1, processAffinityMask ); 39 | } 40 | 41 | #elif defined( WATCOM ) 42 | 43 | #include 44 | #define MAX_PATH 255 45 | #define not_inlined 46 | #define force_inlined __inline 47 | 48 | inline void sleep_ms( uint64_t ms ) {} 49 | 50 | inline bool file_exists( char const * pfile ) 51 | { 52 | FILE * fp = fopen( pfile, "r" ); 53 | bool exists = false; 54 | if ( fp ) 55 | { 56 | fclose( fp ); 57 | exists = true; 58 | } 59 | return exists; 60 | } //file_exists 61 | 62 | inline void bump_thread_priority() {} 63 | inline void set_process_affinity( uint64_t processAffinityMask ) {} 64 | inline int getpid() { return 0; } 65 | #define _countof( X ) ( sizeof( X ) / sizeof( X[0] ) ) 66 | inline void swap( uint8_t & a, uint8_t & b ) { uint8_t c = a; a = b; b = c; } 67 | 68 | #else // Linux, MacOS, etc. 69 | 70 | #if !defined( OLDGCC ) && !defined( M68K ) 71 | #include 72 | #endif 73 | 74 | #ifdef M68K 75 | extern "C" int nanosleep( const struct timespec * duration, struct timespec * rem ); 76 | #endif 77 | 78 | #include 79 | #include 80 | #include 81 | #include 82 | 83 | #define not_inlined __attribute__ ((noinline)) 84 | #define force_inlined inline 85 | 86 | inline void bump_thread_priority() {} 87 | 88 | inline void set_process_affinity( uint64_t processAffinityMask ) 89 | { 90 | #if !defined(__APPLE__) && !defined( OLDGCC ) && !defined( M68K ) 91 | cpu_set_t mask; 92 | CPU_ZERO( &mask ); 93 | 94 | for ( long l = 0; l < 32; l++ ) 95 | { 96 | int b = ( 1 << l ); 97 | if ( 0 != ( b & processAffinityMask ) ) 98 | CPU_SET( l, &mask ); 99 | } 100 | 101 | // this does nothing on WSL 1 or 2 except make you believe it might work until you actually check 102 | int status = sched_setaffinity( 0, sizeof( mask ), &mask ); 103 | #endif 104 | } //set_process_affinity 105 | 106 | template < typename T, size_t N > size_t _countof( T ( & arr )[ N ] ) { return std::extent< T[ N ] >::value; } 107 | #define _stricmp strcasecmp 108 | #define MAX_PATH 1024 109 | 110 | inline char * strupr( char * s ) 111 | { 112 | for ( char * t = s; *t; t++ ) 113 | *t = toupper( *t ); 114 | return s; 115 | } //strupr 116 | 117 | inline char * _strupr( char * s ) { return strupr( s ); } 118 | 119 | inline char * strlwr( char * s ) 120 | { 121 | for ( char * t = s; *t; t++ ) 122 | *t = tolower( *t ); 123 | return s; 124 | } //strlwr 125 | 126 | inline uint64_t _abs64( int64_t x ) { return ( x > 0 ) ? x : -x; } 127 | 128 | inline char * _strlwr( char * s ) { return strlwr( s ); } 129 | 130 | inline void sleep_ms( uint64_t ms ) 131 | { 132 | uint64_t total_ns = ms * 1000000; 133 | long ns = (long) ( total_ns % 1000000000 ); 134 | long sec = (long) ( total_ns / 1000000000 ); 135 | struct timespec ts = { sec, ns }; 136 | 137 | #if !defined( OLDGCC ) 138 | nanosleep( &ts, 0 ); 139 | #endif 140 | } //sleep_ms 141 | 142 | inline bool file_exists( char const * pfile ) 143 | { 144 | FILE * fp = fopen( pfile, "r" ); 145 | bool exists = false; 146 | if ( fp ) 147 | { 148 | fclose( fp ); 149 | exists = true; 150 | } 151 | return exists; 152 | } //file_exists 153 | 154 | #endif 155 | 156 | template inline T get_max( T a, T b ) 157 | { 158 | if ( a > b ) 159 | return a; 160 | return b; 161 | } //get_max 162 | 163 | template inline T get_min( T a, T b ) 164 | { 165 | if ( a < b ) 166 | return a; 167 | return b; 168 | } //get_min 169 | 170 | template inline T round_up( T x, T multiple ) 171 | { 172 | if ( 0 == multiple ) 173 | return x; 174 | 175 | T remainder = x % multiple; 176 | if ( 0 == remainder ) 177 | return x; 178 | 179 | return x + multiple - remainder; 180 | } //round_up 181 | 182 | inline const char * target_platform() 183 | { 184 | #if defined( __riscv ) // g++ on linux 185 | return "riscv"; 186 | #elif defined( __amd64 ) // g++ on linux 187 | return "amd64"; 188 | #elif defined( __aarch64__ ) // g++ on linux 189 | return "arm64"; 190 | #elif defined( _M_AMD64 ) // msft on Windows 191 | return "amd64"; 192 | #elif defined( _M_ARM64 ) // msft on Windows 193 | return "arm64"; 194 | #elif defined( WATCOM ) // WATCOM for 8086 195 | return "8086"; 196 | #elif defined( _M_IX86 ) // msft on Windows 32-bit 197 | return "x86"; 198 | #elif defined( __ARM_32BIT_STATE ) // ARM32 on Raspberry PI (and more) 199 | return "arm32"; 200 | #else 201 | return "(other)"; 202 | #endif 203 | } //target_platform 204 | 205 | inline const char * build_type() 206 | { 207 | #ifdef NDEBUG 208 | return "release"; 209 | #else 210 | return "debug"; 211 | #endif 212 | } //build_type 213 | 214 | inline const char * compiler_used() 215 | { 216 | static char acver[ 100 ]; 217 | 218 | #if defined( __GNUC__ ) 219 | return "g++"; 220 | #elif defined( _MSC_VER ) 221 | snprintf( acver, sizeof( acver ), "msft C++ ver %u", _MSC_VER ); 222 | return acver; 223 | #elif defined( __clang__ ) 224 | return "clang"; 225 | #elif defined( WATCOM ) 226 | return "watcom"; 227 | #else 228 | return "unknown"; 229 | #endif 230 | } //compiler_used 231 | 232 | inline const char * build_platform() 233 | { 234 | #if defined( __APPLE__ ) 235 | return "apple"; 236 | #elif defined( __linux ) 237 | return "linux"; 238 | #elif defined( _WIN32 ) 239 | return "windows"; 240 | #elif defined( WATCOM ) 241 | return "windows"; 242 | #else 243 | return "unknown"; 244 | #endif 245 | } //build_platform 246 | 247 | inline const char * build_string() 248 | { 249 | static char bs[ 320 ]; 250 | snprintf( bs, sizeof( bs ), "Built for %s %s on %c%c %c%c%c %s %s by %s on %s\n", 251 | target_platform(), build_type(), __DATE__[4], __DATE__[5], 252 | __DATE__[0], __DATE__[1], __DATE__[2], &__DATE__[7], __TIME__, compiler_used(), build_platform() ); 253 | return bs; 254 | } //build_string 255 | 256 | #if defined( __GNUC__ ) || defined( __clang__ ) 257 | #define assume_false return( 0 ) // clearly terrible, but this code will never execute. ever. 258 | #define assume_false_return return // clearly terrible, but this code will never execute. ever. 259 | #elif defined( WATCOM ) 260 | #define assume_false return( 0 ) // clearly terrible, but this code will never execute. ever. 261 | #define __assume( x ) 262 | #else 263 | #define assume_false __assume( false ) 264 | #define assume_false_return __assume( false ) 265 | #endif 266 | 267 | inline long portable_filelen( int descriptor ) 268 | { 269 | #ifdef _WIN32 270 | long current = _lseek( descriptor, 0, SEEK_CUR ); 271 | long len = _lseek( descriptor, 0, SEEK_END ); 272 | _lseek( descriptor, current, SEEK_SET ); 273 | #else 274 | long current = lseek( descriptor, 0, SEEK_CUR ); 275 | long len = lseek( descriptor, 0, SEEK_END ); 276 | lseek( descriptor, current, SEEK_SET ); 277 | #endif 278 | return len; 279 | } //portable_filelen 280 | 281 | inline long portable_filelen( FILE * fp ) 282 | { 283 | long current = ftell( fp ); 284 | fseek( fp, 0, SEEK_END ); 285 | long len = ftell( fp ); 286 | fseek( fp, current, SEEK_SET ); 287 | return len; 288 | } //portable_filelen 289 | 290 | inline long portable_filelen( const char * p ) 291 | { 292 | FILE * fp = fopen( p, "r" ); 293 | if ( 0 != fp ) 294 | { 295 | long len = portable_filelen( fp ); 296 | fclose( fp ); 297 | return len; 298 | } 299 | 300 | return 0; 301 | } //portable_filelen 302 | 303 | class CFile 304 | { 305 | private: 306 | FILE * fp; 307 | 308 | public: 309 | CFile( FILE * file ) : fp( file ) {} 310 | ~CFile() { close(); } 311 | FILE * get() { return fp; } 312 | void close() 313 | { 314 | if ( NULL != fp ) 315 | { 316 | fclose( fp ); 317 | fp = NULL; 318 | } 319 | } 320 | }; 321 | 322 | inline char printable( uint8_t x ) 323 | { 324 | if ( x < ' ' || x >= 127 ) 325 | return ' '; 326 | return x; 327 | } //printable 328 | 329 | #if ( ( defined( __clang__ ) || defined( __GNUC__ ) ) && !defined( OLDGCC ) && !defined( M68K ) ) 330 | 331 | inline uint64_t flip_endian64( uint64_t x ) { return __builtin_bswap64( x ); } 332 | inline uint32_t flip_endian32( uint32_t x ) { return __builtin_bswap32( x ); } 333 | inline uint16_t flip_endian16( uint16_t x ) { return __builtin_bswap16( x ); } 334 | 335 | #elif defined( _MSC_VER ) 336 | 337 | inline uint64_t flip_endian64( uint64_t x ) { return _byteswap_uint64( x ); } 338 | inline uint32_t flip_endian32( uint32_t x ) { return _byteswap_ulong( x ); } 339 | inline uint16_t flip_endian16( uint16_t x ) { return _byteswap_ushort( x ); } 340 | 341 | #else 342 | 343 | inline uint64_t flip_endian64( uint64_t x ) 344 | { 345 | return ( ( x & 0xffull ) << 56 ) | ( ( x & 0xff00ull ) << 40 ) | ( ( x & 0xff0000ull ) << 24 ) | ( ( x & 0xff000000ull ) << 8 ) | 346 | ( ( x & 0xff00000000ull ) >> 8 ) | ( ( x & 0xff0000000000ull ) >> 24 ) | ( ( x & 0xff000000000000ull ) >> 40 ) | ( ( x & 0xff00000000000000ull ) >> 56 ); 347 | } //flip_endian64 348 | 349 | inline uint32_t flip_endian32( uint32_t x ) 350 | { 351 | return ( ( x & 0xff ) << 24 ) | ( ( x & 0xff00) << 8 ) | ( ( x & 0xff0000) >> 8 ) | ( ( x & 0xff000000 ) >> 24 ); 352 | } //flip_endian32 353 | 354 | inline uint16_t flip_endian16( uint16_t x ) 355 | { 356 | return ( ( ( x & 0xff00 ) >> 8 ) | ( ( x & 0xff ) << 8 ) ); 357 | } //flip_endian16 358 | 359 | #endif 360 | 361 | -------------------------------------------------------------------------------- /djl_perf.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class CPerfTime 4 | { 5 | private: 6 | LARGE_INTEGER liLastCall; 7 | LARGE_INTEGER liFrequency; 8 | NUMBERFMT NumberFormat; 9 | WCHAR awcRender[ 100 ]; 10 | 11 | public: 12 | CPerfTime() 13 | { 14 | ZeroMemory( &NumberFormat, sizeof NumberFormat ); 15 | NumberFormat.NumDigits = 0; 16 | NumberFormat.Grouping = 3; 17 | NumberFormat.lpDecimalSep = (LPWSTR) L"."; 18 | NumberFormat.lpThousandSep = (LPWSTR) L","; 19 | 20 | Baseline(); 21 | QueryPerformanceFrequency( &liFrequency ); 22 | } 23 | 24 | void Baseline() 25 | { 26 | QueryPerformanceCounter( &liLastCall ); 27 | } 28 | 29 | int RenderLL( LONGLONG ll, WCHAR * pwcBuf, ULONG cwcBuf ) 30 | { 31 | WCHAR awc[100]; 32 | swprintf( awc, L"%I64u", ll ); 33 | 34 | if ( 0 != cwcBuf ) 35 | *pwcBuf = 0; 36 | 37 | return GetNumberFormat( LOCALE_USER_DEFAULT, 0, awc, &NumberFormat, pwcBuf, cwcBuf ); 38 | } //RenderLL 39 | 40 | WCHAR * RenderLL( LONGLONG ll ) 41 | { 42 | WCHAR awc[100]; 43 | swprintf( awc, L"%I64u", ll ); 44 | 45 | awcRender[0] = 0; 46 | GetNumberFormat( LOCALE_USER_DEFAULT, 0, awc, &NumberFormat, awcRender, sizeof awcRender / sizeof WCHAR ); 47 | return awcRender; 48 | } //RenderLL 49 | 50 | void CumulateSince( LONGLONG & running ) 51 | { 52 | LARGE_INTEGER liNow; 53 | QueryPerformanceCounter( &liNow ); 54 | LONGLONG since = liNow.QuadPart - liLastCall.QuadPart; 55 | liLastCall = liNow; 56 | 57 | InterlockedExchangeAdd64( &running, since ); 58 | } 59 | 60 | LONGLONG TimeNow() 61 | { 62 | LARGE_INTEGER liNow; 63 | QueryPerformanceCounter( &liNow ); 64 | return liNow.QuadPart; 65 | } 66 | 67 | LONGLONG DurationToMS( LONGLONG duration ) 68 | { 69 | duration *= 1000000; 70 | return ( duration / liFrequency.QuadPart ) / 1000; 71 | } 72 | 73 | WCHAR * RenderDurationInMS( LONGLONG duration ) 74 | { 75 | LONGLONG x = DurationToMS( duration ); 76 | 77 | RenderLL( x, awcRender, sizeof awcRender / sizeof WCHAR ); 78 | 79 | return awcRender; 80 | } 81 | }; //CPerfTime 82 | 83 | 84 | -------------------------------------------------------------------------------- /djl_rssrdr.hxx: -------------------------------------------------------------------------------- 1 | // This simplistic code extracts ASCII channel title, item title, and item description from RSS feeds. 2 | // The intended consumer is low-end hardware from 1981 that can't deal with or non-original-ASCII characters. 3 | // David Lee. May 2023. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define USE_DEBUG_FILES false 14 | 15 | #ifdef _MSC_VER 16 | #define OPEN_SOURCE_GETURL false // this works on Windows too, but I'd rather avoid the dependencies 17 | #else 18 | #define OPEN_SOURCE_GETURL true 19 | #endif 20 | 21 | #if OPEN_SOURCE_GETURL 22 | 23 | // Dependencies on: 24 | // httplib.h from https://github.com/yhirose/cpp-httplib 25 | // openssl headers and libraries 26 | 27 | #define CPPHTTPLIB_OPENSSL_SUPPORT 28 | #include "httplib.h" 29 | #pragma comment( lib, "libssl.lib" ) 30 | #pragma comment( lib, "libcrypto.lib" ) 31 | 32 | #else 33 | 34 | #include 35 | #include 36 | #pragma comment( lib, "wininet.lib" ) 37 | 38 | class XIHandle 39 | { 40 | public: 41 | XIHandle( HANDLE h = 0 ) : _h(h) {} 42 | ~XIHandle() { Free(); } 43 | HANDLE Get() { return _h; } 44 | BOOL IsNull() { return 0 == _h; } 45 | void Free() 46 | { 47 | if ( 0 != _h ) 48 | { 49 | InternetCloseHandle( _h ); 50 | _h = 0; 51 | } 52 | } 53 | private: 54 | HANDLE _h; 55 | }; 56 | 57 | #endif 58 | 59 | class CRssFeed 60 | { 61 | private: 62 | struct RSSItem 63 | { 64 | string feed; 65 | string title; 66 | string description; 67 | }; 68 | 69 | std::mutex mtx; // protect rssItems from multiple writers 70 | vector rssItems; 71 | 72 | // strcpy is not guaranteed to work if portions of the strings overlap 73 | 74 | static void overlapping_strcpy( char * pto, const char * pfrom ) 75 | { 76 | while ( *pfrom ) 77 | *pto++ = *pfrom++; 78 | *pto = 0; 79 | } //overlapping_strcpy 80 | 81 | static const char * map_feed_to_file( const char * pfeed ) 82 | { 83 | // these are for debugging to avoid hitting the servers -- open local snapshots of the files 84 | 85 | if ( strstr( pfeed, "nytimes" ) ) 86 | return "nytimes.xml"; 87 | 88 | if ( strstr( pfeed, "npr" ) ) 89 | return "npr.xml"; 90 | 91 | if ( strstr( pfeed, "guardian" ) ) 92 | return "guardian.xml"; 93 | 94 | return "wapost.xml"; 95 | } //map_feed_to_file 96 | 97 | static string GetUrl( const string strUrl ) 98 | { 99 | tracer.Trace( "geturl for %s\n", strUrl.c_str() ); 100 | string response; 101 | #if OPEN_SOURCE_GETURL 102 | // Use httplib.h from https://github.com/yhirose/cpp-httplib 103 | // That header pulls in a dependency on opensll -- so that must be in your include and lib paths too 104 | 105 | vector feedname( 1 + strlen( strUrl.c_str() ) ); 106 | strcpy( feedname.data(), strUrl.c_str() ); 107 | char * pslash = strchr( feedname.data() + 8, '/' ); 108 | if ( pslash ) 109 | { 110 | *pslash = 0; 111 | httplib::Client cli( feedname.data() ); 112 | const char * pslash2 = strchr( strUrl.c_str() + 8, '/' ); 113 | if ( pslash2 ) 114 | { 115 | auto res = cli.Get( pslash2 ); 116 | response = res->body; 117 | } 118 | } 119 | #else 120 | XIHandle xhI( InternetOpenA( "djl_rssrdr", INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, 0 ) ); 121 | if ( !xhI.IsNull() ) 122 | { 123 | XIHandle xhUrl( InternetOpenUrlA( xhI.Get(), strUrl.c_str(), "Accept: text/plain", 0, 124 | INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE | 125 | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_NO_CACHE_WRITE | 126 | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | INTERNET_FLAG_SECURE, 127 | 0 ) ); 128 | if ( !xhUrl.IsNull() ) 129 | { 130 | vector buffer( 4096 ); 131 | do 132 | { 133 | DWORD cbRead = 0; 134 | BOOL fOK = InternetReadFile( xhUrl.Get(), buffer.data(), (DWORD) ( buffer.size() - 1 ), &cbRead ); 135 | if ( fOK && ( 0 != cbRead ) ) 136 | { 137 | buffer[ cbRead ] = 0; 138 | response += buffer.data(); 139 | } 140 | else 141 | break; 142 | } while( true ); 143 | } 144 | else 145 | tracer.Trace( "can't InternetOpenUrl, error %d\n", GetLastError() ); 146 | } 147 | else 148 | tracer.Trace( "can't InternetOpenW, error %d\n", GetLastError() ); 149 | #endif 150 | return response; 151 | } //GetUrl 152 | 153 | static void replace_extended_characters( char * p ) 154 | { 155 | // hack to solve a tiny percentage of cases based on actual usage by common RSS feeds. 156 | // many of these conversions are horrific, but it's all there is to work with. 157 | // the target machine is running CP/M from 1981, with US ascii support. 158 | // https://www.i18nqa.com/debug/utf8-debug.html 159 | 160 | while ( *p ) 161 | { 162 | uint8_t x = (uint8_t) p[0]; 163 | uint8_t one = (uint8_t) p[1]; 164 | uint8_t two = (uint8_t) p[2]; 165 | 166 | if ( 0xe2 == x && 0x80 == one) 167 | { 168 | if ( 0x93 == two || 0x94 == two ) 169 | *p = '-'; 170 | else if ( 0x99 == two || 0x98 == two ) 171 | *p = '\''; 172 | else if ( 0x9c == two || 0x9d == two ) 173 | *p = '"'; 174 | else 175 | { 176 | tracer.Trace( "unhandled character [%02x, %02x, %02x]", (uint8_t) p[0], (uint8_t) p[1], (uint8_t) p[2] ); 177 | *p = ' '; 178 | } 179 | 180 | overlapping_strcpy( p + 1, p + 3 ); 181 | } 182 | else if ( 0xc2 == x || 0xc5 == x ) 183 | { 184 | *p = ' '; 185 | overlapping_strcpy( p + 1, p + 2 ); 186 | } 187 | else if ( 0xc3 == x ) // https://www.utf8-chartable.de/unicode-utf8-table.pl?start=128&number=128&names=-&utf8=0x 188 | { 189 | if ( one >= 0x80 && one <= 0x85 ) 190 | *p = 'A'; 191 | else if ( 0x87 == one ) 192 | *p = 'C'; 193 | else if ( one >= 0x88 && one <= 0x8b ) 194 | *p = 'E'; 195 | else if ( one >= 0x8c && one <= 0x8f ) 196 | *p = 'I'; 197 | else if ( one >= 0x92 && one <= 0x96 ) 198 | *p = 'O'; 199 | else if ( one >= 0x99 && one <= 0x9c ) 200 | *p = 'U'; 201 | else if ( 0x9d == one ) 202 | *p = 'Y'; 203 | else if ( one >= 0xa1 && one <= 0xa5 ) 204 | *p = 'a'; 205 | else if ( 0xa7 == one ) 206 | *p = 'c'; 207 | else if ( one >= 0xa8 && one <= 0xab ) 208 | *p = 'e'; 209 | else if ( one >= 0xac && one <= 0xaf ) 210 | *p = 'i'; 211 | else if ( one >= 0xb2 && one <= 0xb6 ) 212 | *p = 'o'; 213 | else if ( one >= 0xb9 && one <= 0xbc ) 214 | *p = 'u'; 215 | else if ( 0xbd == one ) 216 | *p = 'y'; 217 | else 218 | { 219 | tracer.Trace( "unhandled character 0xc3[%02x]", one ); 220 | *p = ' '; 221 | } 222 | 223 | overlapping_strcpy( p + 1, p + 2 ); 224 | } 225 | 226 | p++; 227 | } 228 | } //replace_extended_characters 229 | 230 | static string load_feed( const char * pfeed ) 231 | { 232 | string response; 233 | 234 | if ( USE_DEBUG_FILES ) 235 | { 236 | const char * pdebug_file = map_feed_to_file( pfeed ); 237 | FILE * fp = fopen( pdebug_file, "r" ); 238 | if ( !fp ) 239 | return response; 240 | 241 | fseek( fp, 0, SEEK_END ); 242 | uint32_t file_size = ftell( fp ); 243 | fseek( fp, 0, SEEK_SET ); 244 | 245 | vector file_data; 246 | file_data.resize( file_size + 1 ); 247 | file_data[ file_size ] = 0; 248 | size_t x = fread( file_data.data(), file_size, 1, fp ); 249 | fclose( fp ); 250 | 251 | if ( x ) 252 | response = file_data.data(); 253 | } 254 | else 255 | response = GetUrl( pfeed ); 256 | 257 | return response; 258 | } //load_feed 259 | 260 | static void replace_string( char * p, const char * before, const char * after ) 261 | { 262 | size_t blen = strlen( before ); 263 | size_t alen = strlen( after ); 264 | 265 | if ( blen < alen ) 266 | return; 267 | 268 | while ( *p ) 269 | { 270 | char * pnext = strstr( p, before ); 271 | 272 | if ( !pnext ) 273 | break; 274 | 275 | strcpy( pnext, after ); 276 | overlapping_strcpy( pnext + alen, pnext + blen ); 277 | p = pnext + alen; 278 | } 279 | } //replace_string 280 | 281 | static void remove_tags( char * p ) 282 | { 283 | do 284 | { 285 | char * ptag = strchr( p, '<' ); 286 | if ( !ptag ) 287 | break; 288 | char * pend = strchr( ptag, '>' ); 289 | if ( !pend ) 290 | break; 291 | 292 | overlapping_strcpy( ptag, pend + 1 ); 293 | } while( true ); 294 | } //remove_tags 295 | 296 | static string utf8_to_us_ascii( const char * utf8str ) 297 | { 298 | wstring_convert> wconv; 299 | wstring wstr = wconv.from_bytes( utf8str ); 300 | vector buf( wstr.size() ); 301 | const locale loc( ".20127" ); // ".20127" works on Windows but not on g++ & linux. "C" loses characters. 302 | use_facet>( loc ).narrow( wstr.data(), wstr.data() + wstr.size(), ' ', buf.data() ); 303 | return string( buf.data(), buf.size() ); 304 | } //utf8_to_ascii 305 | 306 | static void make_ascii_string( char * p ) 307 | { 308 | #ifdef _MSC_VER 309 | string us_ascii = utf8_to_us_ascii( p ); 310 | strcpy( p, us_ascii.c_str() ); 311 | #else // iconv is a non-portable option I didn't pursue 312 | replace_extended_characters( p ); 313 | #endif 314 | 315 | // change escaped characters 316 | 317 | replace_string( p, ">", ">" ); 318 | replace_string( p, "<", "<" ); 319 | replace_string( p, "&", "&" ); 320 | replace_string( p, "'", "'" ); 321 | 322 | replace_string( p, ".

", ". " ); 323 | replace_string( p, "

", ". " ); 324 | remove_tags( p ); 325 | } //make_ascii_string 326 | 327 | void parse_items( string response, const size_t max_item_size ) 328 | { 329 | // Don't use an xml parser to reduce dependencies. this code assumes reasonably well-formed RSS. 330 | 331 | const char * p = response.c_str(); 332 | vector buf( 4096 ); 333 | 334 | const char * pchannel = strstr( p, "" ); 335 | if ( !pchannel ) 336 | return; 337 | 338 | const char * pchannel_title = strstr( pchannel, "" ); 339 | if ( !pchannel_title ) 340 | return; 341 | 342 | const char * pchannel_endtitle = strstr( pchannel_title, "" ); 343 | if ( !pchannel_endtitle ) 344 | return; 345 | 346 | size_t channel_title_len = pchannel_endtitle - pchannel_title - 7; 347 | if ( channel_title_len >= buf.size() ) 348 | return; 349 | 350 | memcpy( buf.data(), pchannel_title + 7, channel_title_len ); 351 | buf[ channel_title_len ] = 0; 352 | 353 | make_ascii_string( buf.data() ); 354 | string channel_title( buf.data() ); 355 | 356 | do 357 | { 358 | const char * pitem = strstr( p, "" ); 359 | if ( !pitem ) 360 | break; 361 | 362 | const char * ptitle = strstr( pitem, "" ); 363 | if ( !ptitle ) 364 | break; 365 | 366 | const char * pendtitle = strstr( ptitle, "" ); 367 | if ( !pendtitle ) 368 | return; 369 | 370 | RSSItem item; 371 | item.feed = channel_title; 372 | 373 | size_t len = pendtitle - ptitle - 7; 374 | if ( len < buf.size() ) 375 | { 376 | memcpy( buf.data(), ptitle + 7, len ); 377 | buf[ len ] = 0; 378 | make_ascii_string( buf.data() ); 379 | item.title = buf.data(); 380 | } 381 | else 382 | { 383 | tracer.Trace( "title is too long: %zd\n", len ); 384 | p = pendtitle; 385 | continue; 386 | } 387 | 388 | const char * pdescription = strstr( pendtitle, "" ); 389 | if ( pdescription ) 390 | { 391 | const char * penddescription = strstr( pdescription, "" ); 392 | if ( !penddescription ) 393 | return; 394 | 395 | len = penddescription - pdescription - 13; 396 | if ( len < buf.size() ) 397 | { 398 | memcpy( buf.data(), pdescription + 13, len ); 399 | buf[ len ] = 0; 400 | make_ascii_string( buf.data() ); 401 | item.description = buf.data(); 402 | } 403 | else 404 | { 405 | tracer.Trace( "description is too long: %zd\n", len ); 406 | p = penddescription; 407 | continue; 408 | } 409 | 410 | p = penddescription; 411 | 412 | if ( ( item.feed.length() + item.title.length() + item.description.length() + 3 ) <= max_item_size ) 413 | { 414 | lock_guard lock( mtx ); 415 | rssItems.push_back( item ); 416 | } 417 | else 418 | tracer.Trace( "item is too long for buffer, ignoring\n" ); 419 | } 420 | else 421 | p = pendtitle; 422 | } while ( true ); 423 | } //parse_items 424 | 425 | public: 426 | CRssFeed() {} 427 | ~CRssFeed() { clear(); } 428 | void clear() { rssItems.clear(); } 429 | 430 | size_t load_rss_feeds( char * feedv[10], const size_t max_item_size ) 431 | { 432 | rssItems.clear(); 433 | 434 | // get the count in a separate loop so OMP can work with a simple for loop 435 | 436 | int count = 0; 437 | for ( size_t feed_item = 0; ( 0 != feedv[ feed_item ] ); feed_item++ ) 438 | count++; 439 | 440 | // use OMP to run all http get requests in parallel 441 | 442 | #pragma omp parallel for 443 | for ( int feed_item = 0; feed_item < count; feed_item++ ) 444 | { 445 | string response = load_feed( feedv[ feed_item ] ); 446 | parse_items( response, max_item_size ); 447 | } 448 | 449 | std::random_device rd; 450 | std::mt19937 g(rd()); 451 | std::shuffle( rssItems.begin(), rssItems.end(), g ); 452 | return rssItems.size(); 453 | } //load_rss_feeds 454 | 455 | bool fetch_rss_item( int i, char * item_buf, const size_t buffer_size ) 456 | { 457 | // fill the buffer with 3 null-terminated strings for feed, title, and description 458 | 459 | item_buf[0] = item_buf[1] = item_buf[2] = 0; 460 | 461 | if ( i >= rssItems.size() || i < 0 ) 462 | return false; 463 | 464 | RSSItem item = rssItems[ i ]; 465 | 466 | size_t feed_len = strlen( item.feed.c_str() ); 467 | size_t title_len = strlen( item.title.c_str() ); 468 | size_t description_len = strlen( item.description.c_str() ); 469 | 470 | if ( ( feed_len + title_len + description_len + 3 ) > buffer_size ) 471 | return false; 472 | 473 | strcpy( item_buf, item.feed.c_str() ); 474 | strcpy( item_buf + feed_len + 1, item.title.c_str() ); 475 | strcpy( item_buf + feed_len + 1 + title_len + 1, item.description.c_str() ); 476 | return true; 477 | } //fetch_rss_item 478 | }; 479 | 480 | -------------------------------------------------------------------------------- /djltrace.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // In one source file, declare the CDJLTrace named tracer like this: 4 | // CDJLTrace tracer; 5 | // When the app starts, enable tracing like this: 6 | // tracer.Enable( true ); 7 | // By default the tracing file is placed in %temp%\tracer.txt 8 | // Arguments to Trace() are just like printf. e.g.: 9 | // tracer.Trace( "what to log with an integer argument %d and a wide string %ws\n", 10, pwcHello ); 10 | // 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #ifdef _WIN32 24 | #include 25 | #endif 26 | 27 | #if !defined(_WIN32) && !defined(WATCOM) 28 | 29 | #include 30 | #ifdef __APPLE__ 31 | #include 32 | #endif 33 | #endif 34 | 35 | using namespace std; 36 | 37 | class CDJLTrace 38 | { 39 | private: 40 | FILE * fp; 41 | #if !defined( WATCOM ) && !defined( OLDGCC ) && !defined( M68K ) 42 | std::mutex mtx; 43 | #endif 44 | bool quiet; // no pid 45 | bool flush; // flush after each write 46 | 47 | static char * appendHexNibble( char * p, uint8_t val ) 48 | { 49 | *p++ = ( val <= 9 ) ? val + '0' : val - 10 + 'a'; 50 | return p; 51 | } //appendHexNibble 52 | 53 | static char * appendHexByte( char * p, uint8_t val ) 54 | { 55 | p = appendHexNibble( p, ( val >> 4 ) & 0xf ); 56 | p = appendHexNibble( p, val & 0xf ); 57 | return p; 58 | } //appendBexByte 59 | 60 | static char * appendHexWord( char * p, uint16_t val ) 61 | { 62 | p = appendHexByte( p, ( val >> 8 ) & 0xff ); 63 | p = appendHexByte( p, val & 0xff ); 64 | return p; 65 | } //appendHexWord 66 | 67 | void ShowBinaryData( uint8_t * pData, uint32_t length, uint32_t indent, bool trace ) 68 | { 69 | int32_t offset = 0; 70 | int32_t beyond = length; 71 | const int32_t bytesPerRow = 32; 72 | uint8_t buf[ bytesPerRow ]; 73 | char acLine[ 200 ]; 74 | 75 | while ( offset < beyond ) 76 | { 77 | char * pline = acLine; 78 | 79 | for ( uint32_t i = 0; i < indent; i++ ) 80 | *pline++ = ' '; 81 | 82 | pline = appendHexWord( pline, (uint16_t) offset ); 83 | *pline++ = ' '; 84 | *pline++ = ' '; 85 | 86 | int32_t end_of_row = offset + bytesPerRow; 87 | int32_t cap = ( end_of_row > beyond ) ? beyond : end_of_row; 88 | int32_t toread = ( ( offset + bytesPerRow ) > beyond ) ? ( length % bytesPerRow ) : bytesPerRow; 89 | 90 | memcpy( buf, pData + offset, toread ); 91 | 92 | uint32_t extraSpace = 2; 93 | 94 | for ( int32_t o = offset; o < cap; o++ ) 95 | { 96 | pline = appendHexByte( pline, buf[ o - offset ] ); 97 | *pline++ = ' '; 98 | if ( ( bytesPerRow > 16 ) && ( o == ( offset + 15 ) ) ) 99 | { 100 | *pline++ = ':'; 101 | *pline++ = ' '; 102 | extraSpace = 0; 103 | } 104 | } 105 | 106 | uint32_t spaceNeeded = extraSpace + ( ( bytesPerRow - ( cap - offset ) ) * 3 ); 107 | 108 | for ( uint32_t sp = 0; sp < ( 1 + spaceNeeded ); sp++ ) 109 | *pline++ = ' '; 110 | 111 | for ( int32_t o = offset; o < cap; o++ ) 112 | { 113 | char ch = buf[ o - offset ]; 114 | 115 | if ( (int8_t) ch < ' ' || 127 == ch ) 116 | ch = '.'; 117 | 118 | *pline++ = ch; 119 | } 120 | 121 | offset += bytesPerRow; 122 | *pline = 0; 123 | 124 | if ( trace ) 125 | TraceQuiet( "%s\n", acLine ); 126 | else 127 | printf( "%s\n", acLine ); 128 | } 129 | } //ShowBinaryData 130 | 131 | public: 132 | CDJLTrace() : fp( NULL ), quiet( false ), flush( true ) {} 133 | 134 | bool Enable( bool enable, const wchar_t * pcLogFile = NULL, bool destroyContents = false ) 135 | { 136 | if ( 0 != pcLogFile ) 137 | { 138 | size_t len = wcslen( pcLogFile ); 139 | vector narrow( 1 + len ); 140 | wcstombs( narrow.data(), pcLogFile, 1 + len ); 141 | return Enable( enable, narrow.data(), destroyContents ); 142 | } 143 | 144 | return Enable( enable, (const char *) 0, destroyContents ); 145 | } // Enable 146 | 147 | bool Enable( bool enable, const char * pcLogFile = NULL, bool destroyContents = false ) 148 | { 149 | Shutdown(); 150 | 151 | if ( enable ) 152 | { 153 | const char * mode = destroyContents ? "w+t" : "a+t"; 154 | 155 | if ( NULL == pcLogFile ) 156 | { 157 | const char * tracefile = "tracer.txt"; 158 | size_t len = strlen( tracefile ); 159 | vector tempPath( 1 + len ); 160 | tempPath[0] = 0; 161 | 162 | const char * ptmp = getenv( "TEMP" ); 163 | if ( ptmp ) 164 | { 165 | tempPath.resize( 1 + len + strlen( ptmp ) ); 166 | strcpy( tempPath.data(), ptmp ); 167 | strcat( tempPath.data(), "/" ); 168 | } 169 | 170 | strcat( tempPath.data(), tracefile ); 171 | 172 | fp = fopen( tempPath.data(), mode ); 173 | } 174 | else 175 | { 176 | #ifdef WATCOM // workaround for WATCOM, which doesn't delete the file with "w+t" in spite of its documentation claiming otherwise 177 | if ( !strcmp( mode, "w+t" ) ) 178 | remove( pcLogFile ); 179 | #endif 180 | 181 | fp = fopen( pcLogFile, mode ); 182 | } 183 | } 184 | 185 | return ( NULL != fp ); 186 | } //Enable 187 | 188 | void Shutdown() 189 | { 190 | if ( NULL != fp ) 191 | { 192 | fflush( fp ); 193 | fclose( fp ); 194 | fp = NULL; 195 | } 196 | } //Shutdown 197 | 198 | ~CDJLTrace() 199 | { 200 | Shutdown(); 201 | } //~CDJLTrace 202 | 203 | bool IsEnabled() { return ( 0 != fp ); } 204 | 205 | void SetQuiet( bool q ) { quiet = q; } 206 | 207 | void SetFlushEachTrace( bool f ) { flush = f; } 208 | 209 | void Flush() { if ( 0 != fp ) fflush( fp ); } 210 | 211 | void Trace( const char * format, ... ) 212 | { 213 | if ( NULL != fp ) 214 | { 215 | #if !defined( WATCOM ) && !defined( OLDGCC ) && !defined( M68K ) 216 | lock_guard lock( mtx ); 217 | #endif 218 | 219 | va_list args; 220 | va_start( args, format ); 221 | if ( !quiet ) 222 | fprintf( fp, "PID %6u -- ", 223 | #ifdef _WIN32 224 | (unsigned) _getpid() ); 225 | #else 226 | getpid() ); 227 | #endif 228 | vfprintf( fp, format, args ); 229 | va_end( args ); 230 | if ( flush ) 231 | fflush( fp ); 232 | } 233 | } //Trace 234 | 235 | void TraceVA( const char * format, va_list args ) 236 | { 237 | if ( NULL != fp ) 238 | { 239 | vfprintf( fp, format, args ); 240 | if ( flush ) 241 | fflush( fp ); 242 | } 243 | } //TraceVA 244 | 245 | // Don't prepend the PID to the trace 246 | 247 | void TraceQuiet( const char * format, ... ) 248 | { 249 | if ( NULL != fp ) 250 | { 251 | #if !defined( WATCOM ) && !defined( OLDGCC ) && !defined( M68K ) 252 | lock_guard lock( mtx ); 253 | #endif 254 | va_list args; 255 | va_start( args, format ); 256 | vfprintf( fp, format, args ); 257 | va_end( args ); 258 | if ( flush ) 259 | fflush( fp ); 260 | } 261 | } //TraceQuiet 262 | 263 | void TraceDebug( bool condition, const char * format, ... ) 264 | { 265 | #ifdef DEBUG 266 | if ( NULL != fp && condition ) 267 | { 268 | #if !defined( WATCOM ) && !defined( OLDGCC ) && !defined( M68K ) 269 | lock_guard lock( mtx ); 270 | #endif 271 | 272 | va_list args; 273 | va_start( args, format ); 274 | if ( !quiet ) 275 | fprintf( fp, "PID %6u -- ", 276 | #ifdef _WIN32 277 | _getpid() ); 278 | #else 279 | getpid() ); 280 | #endif 281 | vfprintf( fp, format, args ); 282 | va_end( args ); 283 | if ( flush ) 284 | fflush( fp ); 285 | } 286 | #else 287 | #if !defined( WATCOM ) && !defined( __APPLE__ ) && !defined( __clang__ ) && !defined (OLDGCC) 288 | condition; // unused 289 | format; // unused 290 | #endif 291 | #endif 292 | } //TraceDebug 293 | 294 | void TraceBinaryData( uint8_t * pData, uint32_t length, uint32_t indent ) 295 | { 296 | if ( NULL != fp ) 297 | ShowBinaryData( pData, length, indent, true ); 298 | } //TraceBinaryData 299 | 300 | void PrintBinaryData( uint8_t * pData, uint32_t length, uint32_t indent ) 301 | { 302 | ShowBinaryData( pData, length, indent, false ); 303 | } //PrintBinaryData 304 | 305 | static char * RenderNumberWithCommas( long long n, char * pc ) 306 | { 307 | char actmp[ 32 ]; 308 | long long orig = n; 309 | 310 | if ( 0 == n ) 311 | { 312 | strcpy( pc, "0" ); 313 | return pc; 314 | } 315 | else if ( n < 0 ) 316 | n = -n; 317 | 318 | pc[ 0 ] = 0; 319 | 320 | while ( 0 != n ) 321 | { 322 | strcpy( actmp, pc ); 323 | if ( n >= 1000 ) 324 | snprintf( pc, 5, ",%03lld", n % 1000 ); 325 | else 326 | snprintf( pc, 4, "%lld", n ); 327 | strcat( pc, actmp ); 328 | n /= 1000; 329 | } 330 | 331 | if ( orig < 0 ) 332 | { 333 | strcpy( actmp, pc ); 334 | strcpy( pc, "-" ); 335 | strcat( pc, actmp ); 336 | } 337 | 338 | return pc; 339 | } //RenderNumberWithCommas 340 | }; //CDJLTrace 341 | 342 | extern CDJLTrace tracer; 343 | 344 | -------------------------------------------------------------------------------- /m.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | rem With RSS 5 | cl /nologo ntvcm.cxx x80.cxx /DNTVCM_RSS_SUPPORT /openmp /I. /GS- /GL /Oti2 /Ob3 /Qpar /Fa /FAsc /EHac /Zi /jumptablerdata /D_AMD64_ /link user32.lib /OPT:REF 6 | 7 | rem Without RSS 8 | rem cl /nologo ntvcm.cxx x80.cxx /I. /GS- /GL /Oti2 /Ob3 /Qpar /Fa /FAsc /EHac /Zi /jumptablerdata /D_AMD64_ /link user32.lib ntdll.lib /OPT:REF 9 | 10 | -------------------------------------------------------------------------------- /m.sh: -------------------------------------------------------------------------------- 1 | 2 | COMMIT=$(git log -1 HEAD --format=%h 2> /dev/null) 3 | BUILD=$(printf "%04d" "$(git rev-list --count HEAD 2> /dev/null)" ) 4 | #echo $COMMIT $BUILD 5 | if [[ -z ${COMMIT} ]]; then 6 | # with RSS 7 | #g++ -ggdb -Og -fno-builtin -D NTVCM_RSS_SUPPORT -D DEBUG -I . ntvcm.cxx x80.cxx -lssl -lcrypto -o ntvcm -static 8 | # without RSS 9 | g++ -ggdb -Ofast -fno-builtin -D DEBUG -I . ntvcm.cxx x80.cxx -o ntvcm -static 10 | else 11 | # with RSS 12 | #g++ -ggdb -Og -fno-builtin -D NTVCM_RSS_SUPPORT -D COMMIT_ID="\" [Commit Id:$COMMIT]\"" -D DEBUG -I . ntvcm.cxx x80.cxx -lssl -lcrypto -o ntvcm -static 13 | # without RSS 14 | g++ -ggdb -Ofast -fno-builtin -D BUILD="\".$BUILD\"" -D COMMIT_ID="\" [Commit Id:$COMMIT]\"" -D DEBUG -I . ntvcm.cxx x80.cxx -o ntvcm -static 15 | fi 16 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # 2 | # makefile - NT Virtual CP/M Machine. 3 | # 4 | # License CC0 1.0 Universal 5 | # 6 | # https://stackoverflow.com/questions/1079832/ 7 | # 8 | # 08 Feb 25 0.1 - Initial version - MT 9 | # - Added the ability to create debug code using target- 10 | # specific variable values - MT 11 | # 12 | # Usage: 13 | # 14 | # make - Incremental make. 15 | # make all 16 | # make release - Rebuild application using release flags. 17 | # make debug - Rebuild application using debug flags. 18 | # make clean - Deletes object files 19 | # make VERBOSE=1 - Verbose output 20 | # make backup - Backup files 21 | # 22 | 23 | PROJECT = gcc-ntvcm 24 | PROGRAM = ntvcm 25 | SOURCES = ntvcm.cxx x80.cxx 26 | FILES = *.cxx *.hxx LICENSE README.md makefile *.md .gitignore #.gitattributes 27 | OBJECTS = $(SOURCES:.cxx=.o) 28 | OUTPUT = $(PROGRAM).out 29 | LANG = LANG_$(shell (echo $$LANG | cut -f 1 -d '_')) 30 | COMMIT != git log -1 HEAD --format=%h 2> /dev/null 31 | BUILD != printf "%04d" $(shell git rev-list --count HEAD 2> /dev/null) 32 | UNAME != uname 33 | CC = g++ 34 | 35 | LIBS = 36 | LFLAGS = -static 37 | CFLAGS = -ggdb -fno-builtin -I . 38 | 39 | ifndef VERBOSE 40 | VERBOSE = 0 41 | endif 42 | 43 | ifneq ($(COMMIT),) 44 | CFLAGS += -DCOMMIT_ID='" [Commit Id: $(COMMIT)]"' 45 | endif 46 | 47 | ifneq ($(BUILD),) 48 | CFLAGS += -DBUILD='".$(BUILD)"' 49 | endif 50 | 51 | all: CFLAGS += -flto -Ofast -D NDEBUG 52 | all: $(PROGRAM) $(OBJECTS) 53 | 54 | $(PROGRAM): $(OBJECTS) 55 | ifneq ($(VERBOSE),0) 56 | @echo 57 | @echo $(CC) $(LFLAGS) $(OBJECTS) -o $@ $(LIBS) 58 | @echo 59 | endif 60 | @$(CC) $(LFLAGS) $(OBJECTS) -o $@ $(LIBS) 61 | @ls --color $@ 62 | 63 | $(OBJECTS) : $(SOURCES) 64 | ifneq ($(VERBOSE),0) 65 | @echo 66 | @echo $(CC) $(CFLAGS) -c $(SOURCES) 67 | endif 68 | @$(CC) $(CFLAGS) -c $(SOURCES) 69 | 70 | release: clean 71 | release: all 72 | 73 | debug: clean 74 | debug: CFLAGS += -Og -D DEBUG 75 | debug: $(PROGRAM) 76 | 77 | clean: 78 | # @rm -f $(PROGRAM) # -v 79 | @rm -f $(OBJECTS) # -v 80 | 81 | backup: clean 82 | @echo "$(PROJECT)-`date +'%Y%m%d%H%M'`.tar.gz"; tar -czpf ..\/$(PROJECT)-`date +'%Y%m%d%H%M'`.tar.gz $(FILES) 83 | -------------------------------------------------------------------------------- /mg.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | rem With RSS 5 | g++ -Ofast -ggdb -fopenmp -D NTVCM_RSS_SUPPORT -D _MSC_VER -D _GNU_CPP ntvcm.cxx x80.cxx -I ../djl -D DEBUG -o ntvcmg.exe -static -lwininet 6 | 7 | rem Without RSS 8 | rem g++ -Ofast -ggdb -D _MSC_VER -D _GNU_CPP ntvcm.cxx x80.cxx -I ../djl -D DEBUG -o ntvcmg.exe -static 9 | 10 | 11 | -------------------------------------------------------------------------------- /mgr.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | rem With RSS 5 | g++ -Ofast -ggdb -fopenmp -D NTVCM_RSS_SUPPORT -D _MSC_VER -D _GNU_CPP ntvcm.cxx x80.cxx -I ../djl -D NDEBUG -o ntvcmg.exe -static -lwininet 6 | 7 | rem Without RSS 8 | rem g++ -Ofast -ggdb -D _MSC_VER -D _GNU_CPP ntvcm.cxx x80.cxx -I ../djl -D NDEBUG -o ntvcmg.exe -static 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /mmac.sh: -------------------------------------------------------------------------------- 1 | # with RSS 2 | #g++ -ggdb -Og -fno-builtin -D NTVCM_RSS_SUPPORT -D DEBUG -I . ntvcm.cxx x80.cxx -lssl -lcrypto -o ntvcm -static 3 | 4 | # without RSS 5 | g++ -ggdb -Ofast -fno-builtin -D DEBUG -I . ntvcm.cxx x80.cxx -o ntvcm 6 | -------------------------------------------------------------------------------- /mr.bat: -------------------------------------------------------------------------------- 1 | rem with RSS 2 | cl /W4 /wd4706 /wd4996 /nologo ntvcm.cxx x80.cxx /DNDEBUG /DNTVCM_RSS_SUPPORT /openmp /I. /GS- /GL /Oti2 /Ob3 /Qpar /Fa /FAsc /EHac /Zi /jumptablerdata /D_AMD64_ /link user32.lib /OPT:REF 3 | 4 | rem without RSS 5 | rem cl /W4 /wd4706 /wd4996 /nologo ntvcm.cxx x80.cxx /DNDEBUG /I. /GS- /GL /Oti2 /Ob3 /Qpar /Fa /FAsc /EHac /Zi /jumptablerdata /D_AMD64_ /link user32.lib ntdll.lib /OPT:REF 6 | 7 | -------------------------------------------------------------------------------- /mr.sh: -------------------------------------------------------------------------------- 1 | COMMIT=$(git log -1 HEAD --format=%h 2> /dev/null) 2 | BUILD=$(printf "%04d" "$(git rev-list --count HEAD 2> /dev/null)" ) 3 | #echo $COMMIT $BUILD 4 | if [[ -z ${COMMIT} ]]; then 5 | # with RSS 6 | #g++ -ggdb -flto -Ofast -fopenmp -fno-builtin -D NTVCM_RSS_SUPPORT -D NDEBUG -I . ntvcm.cxx x80.cxx -lssl -lcrypto -o ntvcm -static 7 | # without RSS 8 | g++ -ggdb -flto -Ofast -fno-builtin -D NDEBUG -I . ntvcm.cxx x80.cxx -o ntvcm -static 9 | else 10 | # with RSS 11 | #g++ -ggdb -flto -Ofast -fopenmp -fno-builtin -D NTVCM_RSS_SUPPORT -D COMMIT_ID="\" [Commit Id:$COMMIT]\"" -D NDEBUG -I . ntvcm.cxx x80.cxx -lssl -lcrypto -o ntvcm -static 12 | # without RSS 13 | g++ -ggdb -flto -Ofast -fopenmp -fno-builtin -D BUILD="\".$BUILD\"" -D COMMIT_ID="\" [Commit Id:$COMMIT]\"" -D NDEBUG -I . ntvcm.cxx x80.cxx -o ntvcm -static 14 | fi 15 | 16 | 17 | -------------------------------------------------------------------------------- /mrmac.sh: -------------------------------------------------------------------------------- 1 | # with RSS 2 | #g++ -ggdb -flto -Ofast -fopenmp -fno-builtin -D NTVCM_RSS_SUPPORT -D NDEBUG -I . ntvcm.cxx x80.cxx -lssl -lcrypto -o ntvcm -static 3 | 4 | # without RSS 5 | g++ -ggdb -flto -Ofast -fno-builtin -D NDEBUG -I . ntvcm.cxx x80.cxx -o ntvcm 6 | -------------------------------------------------------------------------------- /mwatcom.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | SET WATCOM=..\WATCOM 4 | SET PATH=%WATCOM%\BINNT64;%WATCOM%\BINNT;%PATH% 5 | SET EDPATH=%WATCOM%\EDDAT 6 | SET INCLUDE=%WATCOM%\H;%WATCOM%\H\NT;..\djl 7 | 8 | wcl -d0 -q -zp=1 -ml -obmr -oh -ei -oi -s -0 -xs -j -oe=160 -ol+ -ot ntvcm.cxx x80.cxx -bcl=DOS -k8192 -fe=ntvcmdos.exe -DWATCOM -DNDEBUG -lr -I. 9 | 10 | -------------------------------------------------------------------------------- /ntvcm.h: -------------------------------------------------------------------------------- 1 | /* BDOS extensions for NTVCM */ 2 | 3 | /* make this one overlap with CP/M 3 and above */ 4 | 5 | #define BDOS_GET_PUT_PROGRAM_RETURN_CODE 108 6 | 7 | /* put the rest where no other BDOS implementations likely conflict */ 8 | 9 | #define BDOS_GET_TIME 180 10 | #define BDOS_SLEEP 181 11 | #define BDOS_INITIALIZE_RSS_FEED 182 12 | #define BDOS_FETCH_RSS_ITEM 183 13 | #define BDOS_RAND 184 14 | #define BDOS_ENABLE_INSTRUCTION_TRACING 185 15 | 16 | -------------------------------------------------------------------------------- /tests/8080EX1.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidly/ntvcm/7ef441b24cb4e8b505e7e2673c1eb7ae9d7a7114/tests/8080EX1.COM -------------------------------------------------------------------------------- /tests/8080EXER.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidly/ntvcm/7ef441b24cb4e8b505e7e2673c1eb7ae9d7a7114/tests/8080EXER.COM -------------------------------------------------------------------------------- /tests/8080PRE.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidly/ntvcm/7ef441b24cb4e8b505e7e2673c1eb7ae9d7a7114/tests/8080PRE.COM -------------------------------------------------------------------------------- /tests/CPUTEST.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidly/ntvcm/7ef441b24cb4e8b505e7e2673c1eb7ae9d7a7114/tests/CPUTEST.COM -------------------------------------------------------------------------------- /tests/TEST.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidly/ntvcm/7ef441b24cb4e8b505e7e2673c1eb7ae9d7a7114/tests/TEST.COM -------------------------------------------------------------------------------- /tests/TST8080.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidly/ntvcm/7ef441b24cb4e8b505e7e2673c1eb7ae9d7a7114/tests/TST8080.COM -------------------------------------------------------------------------------- /tests/ra.bat: -------------------------------------------------------------------------------- 1 | echo test 8080ex1.com 2 | ntvcm -8 8080ex1.com 3 | 4 | echo: 5 | echo test 8080pre.com 6 | ntvcm -8 8080pre.com 7 | 8 | echo: 9 | echo test tst8080.com 10 | ntvcm -8 tst8080.com 11 | 12 | echo: 13 | echo test test.com 14 | ntvcm -8 test.com 15 | 16 | echo: 17 | echo test cputest in 8080 mode 18 | ntvcm -8 cputest.com 19 | 20 | echo: 21 | echo test cputest in Z80 mode 22 | ntvcm cputest.com 23 | echo: 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/ra.sh: -------------------------------------------------------------------------------- 1 | ../ntvcm -8 8080ex1.com 2 | ../ntvcm -8 8080pre.com 3 | ../ntvcm -8 tst8080.com 4 | ../ntvcm -8 test.com 5 | ../ntvcm -8 cputest.com 6 | ../ntvcm cputest.com 7 | -------------------------------------------------------------------------------- /tests/rai.sh: -------------------------------------------------------------------------------- 1 | ../../rvos/rvos -h:10 ../ntvcm -8 8080ex1.com 2 | ../../rvos/rvos -h:10 ../ntvcm -8 8080pre.com 3 | ../../rvos/rvos -h:10 ../ntvcm -8 tst8080.com 4 | ../../rvos/rvos -h:10 ../ntvcm -8 test.com 5 | ../../rvos/rvos -h:10 ../ntvcm -8 cputest.com 6 | ../../rvos/rvos -h:10 ../ntvcm cputest.com 7 | -------------------------------------------------------------------------------- /x80.cxx: -------------------------------------------------------------------------------- 1 | // 8080 and Z80 emulator. 2 | // Written by David Lee 3 | // useful: https://pastraiser.com/cpu/i8080/i8080_opcodes.html 4 | // https://altairclone.com/downloads/manuals/8080%20Programmers%20Manual.pdf 5 | // http://popolony2k.com.br/xtras/programming/asm/nemesis-lonestar/8080-z80-instruction-set.html 6 | // https://www.zilog.com/docs/z80/um0080.pdf 7 | // http://www.z80.info/z80time.txt 8 | // http://www.z80.info/zip/z80-documented.pdf 9 | // http://www.z80.info/z80undoc.htm 10 | // http://www.z80.info/z80sflag.htm 11 | // http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html 12 | // https://mdfs.net/Software/Z80/Exerciser/ 13 | // https://onlinedisassembler.com/odaweb/ 14 | // symbols that start with z80_ are (unsurprisingly) Z80-specific. 80% of this code is Z80-specific. 15 | // 8080 emulation would be >20% faster if not for the z80 checks 16 | // Validated 100% pass for 8080 with 8080ex1.com, 8080pre.com, Microcosm v1.0, and cputest.com Diagnostics II V1.2 in 8080 mode. 17 | // Validated 100% pass for Z80 with zexall.com, zexdoc.com, and cputest.com in Z80 mode. 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | using namespace std; 29 | 30 | #include "x80.hxx" 31 | 32 | uint8_t memory[ 65536 ]; 33 | registers reg; 34 | static const char * reg_strings[ 8 ] = { "b", "c", "d", "e", "h", "l", "m", "a" }; 35 | static const char * rp_strings[ 4 ] = { "bc", "de", "hl", "sp" }; 36 | static const char * z80_math_strings[ 8 ] = { "add", "adc", "sub", "sbb", "and", "xor", "or", "cp" }; 37 | static const char * z80_rotate_strings[ 8 ] = { "rlc", "rrc", "rl", "rr", "sla", "sra", "sll", "srl" }; 38 | static uint8_t g_State = 0; 39 | const uint8_t stateTraceInstructions = 1; 40 | const uint8_t stateEndEmulation = 2; 41 | void x80_trace_instructions( bool t ) { if ( t ) g_State |= stateTraceInstructions; else g_State &= ~stateTraceInstructions; } 42 | void x80_end_emulation() { g_State |= stateEndEmulation; } 43 | 44 | enum z80_value_source { vs_register, vs_memory, vs_indexed }; // this impacts how Z80 undocumented Y and X flags are updated 45 | const uint8_t cyclesnt = 6; // cycles not taken when a conditional call, jump, or return isn't taken 46 | 47 | // instructions starting with '*' are undocumented for 8080, and not implemented here. They also signal likely Z80 instructions 48 | static const char i8080_instructions[ 256 ][ 16 ] = 49 | { 50 | /*00*/ "nop", "lxi b, d16", "stax b", "inx b", "inr b", "dcr b", "mvi b, d8", "rlc", 51 | /*08*/ "*nop", "dad b", "ldax b", "dcx b", "inr c", "dcr c", "mvi c, d8", "rrc", 52 | /*10*/ "*nop", "lxi d, d16", "stax d", "inx d", "inr d", "dcr d", "mvi d, d8", "ral", 53 | /*18*/ "*nop", "dad d", "ldax d", "dcx d", "inr e", "dcr e", "mvi e, d8", "rar", 54 | /*20*/ "*nop", "lxi h, d16", "shld a16", "inx h", "inr h", "dcr h", "mvi h, d8", "daa", 55 | /*28*/ "*nop", "dad h", "lhld a16", "dcx h", "inr l", "dcr l", "mvi l, d8", "cma", 56 | /*30*/ "*nop", "lxi sp, d16", "sta a16", "inx sp", "inr m", "dcr m", "mvi m, d8", "stc", 57 | /*38*/ "*nop", "dad sp", "lda a16", "dcx sp", "inr a", "dcr a", "mvi a, d8", "cmc", 58 | /*40*/ "mov b, b", "mov b, c", "mov b, d", "mov b, e", "mov b, h", "mov b, l", "mov b, m", "mov b, a", 59 | /*48*/ "mov c, b", "mov c, c", "mov c, d", "mov c, e", "mov c, h", "mov c, l", "mov c, m", "mov c, a", 60 | /*50*/ "mov d, b", "mov d, c", "mov d, d", "mov d, e", "mov d, h", "mov d, l", "mov d, m", "mov d, a", 61 | /*58*/ "mov e, b", "mov e, c", "mov e, d", "mov e, e", "mov e, h", "mov e, l", "mov e, m", "mov e, a", 62 | /*60*/ "mov h, b", "mov h, c", "mov h, d", "mov h, e", "(hook)", "mov h, l", "mov h, m", "mov h, a", 63 | /*68*/ "mov l, b", "mov l, c", "mov l, d", "mov l, e", "mov l, h", "mov l, l", "mov l, m", "mov l, a", 64 | /*70*/ "mov m, b", "mov m, c", "mov m, d", "mov m, e", "mov m, h", "mov m, l", "hlt", "mov m, a", 65 | /*78*/ "mov a, b", "mov a, c", "mov a, d", "mov a, e", "mov a, h", "mov a, l", "mov a, m", "mov a, a", 66 | /*80*/ "add b", "add c", "add d", "add e", "add h", "add l", "add m", "add a", 67 | /*88*/ "adc b", "adc c", "adc d", "adc e", "adc h", "adc l", "adc m", "adc a", 68 | /*90*/ "sub b", "sub c", "sub d", "sub e", "sub h", "sub l", "sub m", "sub a", 69 | /*98*/ "sbb b", "sbb c", "sbb d", "sbb e", "sbb h", "sbb l", "sbb m", "sbb a", 70 | /*a0*/ "ana b", "ana c", "ana d", "ana e", "ana h", "ana l", "ana m", "ana a", 71 | /*a8*/ "xra b", "xra c", "xra d", "xra e", "xra h", "xra l", "xra m", "xra a", 72 | /*b0*/ "ora b", "ora c", "ora d", "ora e", "ora h", "ora l", "ora m", "ora a", 73 | /*b8*/ "cmp b", "cmp c", "cmp d", "cmp e", "cmp h", "cmp l", "cmp m", "cmp a", 74 | /*c0*/ "rnz", "pop b", "jnz a16", "jmp a16", "cnz a16", "push b", "adi d8", "rst 0", 75 | /*c8*/ "rz", "ret", "jz a16", "*jmp", "cz a16", "call a16", "aci d8", "rst 1", 76 | /*d0*/ "rnc", "pop d", "jnc a16", "out d8", "cnc a16", "push d", "sui d8", "rst 2", 77 | /*d8*/ "rc", "*ret", "jc a16", "in d8", "cc a16", "*call a16", "sbi d8", "rst 3", 78 | /*e0*/ "rpo", "pop h", "jpo a16", "xthl", "cpo a16", "push h", "ani d8", "rst 4", 79 | /*e8*/ "rpe", "pchl", "jpe a16", "xchg", "cpe a16", "*call a16", "xri d8", "rst 5", 80 | /*f0*/ "rp", "pop psw", "jp a16", "di", "cp a16", "push psw", "ori d8", "rst 6", 81 | /*f8*/ "rm", "sphl", "jm a16", "ei", "cm a16", "*call a16", "cpi d8", "rst 7", 82 | }; 83 | 84 | // instructions starting with '*' are Z80-specific, generally multi-byte, and handled separately. 85 | // instructions listed here are the overlap with 8080 but with the Z80 naming. 86 | static const char z80_instructions[ 256 ][ 16 ] = 87 | { 88 | /*00*/ "nop", "ld bc, d16", "ld (bc), a", "inc bc", "inc b", "dec b", "ld b, d8", "rlca", 89 | /*08*/ "*'", "add hl, bc", "ld a, (bc)", "dec bc", "inc c", "dec c", "ld c, d8", "rrca", 90 | /*10*/ "*", "ld de, d16", "ld (de), a", "inc de", "inc d", "dec d", "ld d, d8", "rla", 91 | /*18*/ "*", "add hl, de", "ld a, (de)", "dec de", "inc e", "dec e", "ld e, d8", "rra", 92 | /*20*/ "*", "ld hl, d16", "ld (a16), hl", "inc hl", "inc h", "dec h", "ld h, d8", "daa", 93 | /*28*/ "*", "add hl, hl", "ld hl, (a16)", "dec hl", "inc l", "dec l", "ld l, d8", "cpl", 94 | /*30*/ "*", "ld sp, d16", "ld (a16), a", "inc sp", "inc (hl)", "dec (hl)", "ld (hl), d8", "scf", 95 | /*38*/ "*", "add hl, sp", "ld a, (a16)", "dec sp", "inc a", "dec a", "ld a, d8", "ccf", 96 | /*40*/ "ld b, b", "ld b, c", "ld b, d", "ld b, e", "ld b, h", "ld b, l", "ld b, (hl)", "ld b, a", 97 | /*48*/ "ld c, b", "ld c, c", "ld c, d", "ld c, e", "ld c, h", "ld c, l", "ld c, (hl)", "ld c, a", 98 | /*50*/ "ld d, b", "ld d, c", "ld d, d", "ld d, e", "ld d, h", "ld d, l", "ld d, (hl)", "ld d, a", 99 | /*58*/ "ld e, b", "ld e, c", "ld e, d", "ld e, e", "ld e, h", "ld e, l", "ld e, (hl)", "ld e, a", 100 | /*60*/ "ld h, b", "ld h, c", "ld h, d", "ld h, e", "(hook)", "ld h, l", "ld h, (hl)", "ld h, a", 101 | /*68*/ "ld l, b", "ld l, c", "ld l, d", "ld l, e", "ld l, h", "ld l, l", "ld l, (hl)", "ld l, a", 102 | /*70*/ "ld (hl), b", "ld (hl), c", "ld (hl), d", "ld (hl), e", "ld (hl), h", "ld (hl), l", "halt", "ld (hl), a", 103 | /*78*/ "ld a, b", "ld a, c", "ld a, d", "ld a, e", "ld a, h", "ld a, l", "ld a, (hl)", "ld a, a", 104 | /*80*/ "add a, b", "add a, c", "add a, d", "add a, e", "add a, h", "add a, l", "add a, (hl)", "add a, a", 105 | /*88*/ "adc a, b", "adc a, c", "adc a, d", "adc a, e", "adc a, h", "adc a, l", "adc a, (hl)", "adc a, a", 106 | /*90*/ "sub b", "sub c", "sub d", "sub e", "sub h", "sub l", "sub (hl)", "sub a", 107 | /*98*/ "sbc a, b", "sbc a, c", "sbc a, d", "sbc a, e", "sbc a, h", "sbc a, l", "sbc a, (hl)", "sbc a, a", 108 | /*a0*/ "and b", "and c", "and d", "and e", "and h", "and l", "and (hl)", "and a", 109 | /*a8*/ "xor b", "xor c", "xor d", "xor e", "xor h", "xor l", "xor (hl)", "xor a", 110 | /*b0*/ "or b", "or c", "or d", "or e", "or h", "or l", "or (hl)", "or a", 111 | /*b8*/ "cp b", "cp c", "cp d", "cp e", "cp h", "cp l", "cp (hl)", "cp a", 112 | /*c0*/ "ret nz", "pop bc", "jp nz, a16", "jp a16", "call nz, a16", "push bc", "add a, d8", "rst 0", 113 | /*c8*/ "ret z", "ret", "jp z, a16", "*", "call z, a16", "call a16", "adc a, d8", "rst 1", 114 | /*d0*/ "ret nc", "pop de", "jp nc, a16", "out (d8), a", "call nc, a16", "push de", "sub d8", "rst 2", 115 | /*d8*/ "ret c", "*", "jp c, a16", "in a, (d8)", "call c, a16", "*", "sbc d8", "rst 3", 116 | /*e0*/ "ret po", "pop hl", "jp po, a16", "ex (sp), hl", "call po, a16", "push hl", "and d8", "rst 4", 117 | /*e8*/ "ret pe", "jp (hl)", "jp pe, a16", "ex de, hl", "call pe, a16", "*", "xor d8", "rst 5", 118 | /*f0*/ "ret p", "pop af", "jp p, a16", "di", "call p, a16", "push af", "or d8", "rst 6", 119 | /*f8*/ "ret m", "ld sp, hl", "jp m, a16", "ei", "call m, a16", "*", "cp d8", "rst 7", 120 | }; 121 | 122 | // base cycles. 8080 conditional calls take 11 (not 17) if not taken. conditional returns take 5 (not 11) if not taken. 123 | typedef uint8_t acycles_t[ 256 ]; 124 | static const acycles_t i8080_cycles = 125 | { 126 | /*00*/ 4, 10, 7, 5, 5, 5, 7, 4, 4, 10, 7, 5, 5, 5, 7, 4, 127 | /*10*/ 4, 10, 7, 5, 5, 5, 7, 4, 4, 10, 7, 5, 5, 5, 7, 4, 128 | /*20*/ 4, 10, 16, 5, 5, 5, 7, 4, 4, 10, 16, 5, 5, 5, 7, 4, 129 | /*30*/ 4, 10, 13, 5, 10, 10, 10, 4, 4, 10, 13, 5, 5, 5, 7, 4, 130 | /*40*/ 5, 5, 5, 5, 5, 5, 7, 5, 5, 5, 5, 5, 5, 5, 7, 5, 131 | /*50*/ 5, 5, 5, 5, 5, 5, 7, 5, 5, 5, 5, 5, 5, 5, 7, 5, 132 | /*60*/ 5, 5, 5, 5, 0, 5, 7, 5, 5, 5, 5, 5, 5, 5, 7, 5, 133 | /*70*/ 7, 7, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, 5, 7, 5, 134 | /*80*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 135 | /*90*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 136 | /*a0*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 137 | /*b0*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 138 | /*c0*/ 11, 10, 10, 10, 17, 11, 7, 11, 11, 10, 10, 10, 17, 17, 7, 11, 139 | /*d0*/ 11, 10, 10, 10, 17, 11, 7, 11, 11, 10, 10, 10, 17, 17, 7, 11, 140 | /*e0*/ 11, 10, 10, 18, 17, 11, 7, 11, 11, 5, 10, 5, 17, 17, 7, 11, 141 | /*f0*/ 11, 10, 10, 4, 17, 11, 7, 11, 11, 5, 10, 4, 17, 17, 7, 11, 142 | }; 143 | 144 | static const acycles_t z80_cycles = 145 | { 146 | /*00*/ 4, 10, 7, 6, 4, 4, 7, 4, 4, 11, 7, 6, 4, 4, 7, 4, 147 | /*10*/ 0, 10, 7, 6, 4, 4, 7, 4, 0, 11, 7, 6, 4, 4, 7, 4, 148 | /*20*/ 0, 10, 16, 6, 4, 4, 7, 4, 0, 11, 20, 6, 4, 4, 7, 4, 149 | /*30*/ 0, 10, 13, 6, 11, 11, 10, 4, 0, 11, 13, 6, 4, 4, 7, 4, 150 | /*40*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 151 | /*50*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 152 | /*60*/ 4, 4, 4, 4, 0, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 153 | /*70*/ 7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4, 154 | /*80*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 155 | /*90*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 156 | /*a0*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 157 | /*b0*/ 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, 158 | /*c0*/ 11, 10, 10, 10, 17, 11, 7, 11, 11, 10, 10, 0, 17, 17, 7, 11, 159 | /*d0*/ 11, 10, 10, 11, 17, 11, 7, 11, 11, 0, 10, 11, 17, 0, 7, 11, 160 | /*e0*/ 11, 10, 10, 19, 17, 11, 7, 11, 11, 4, 10, 4, 17, 0, 7, 11, 161 | /*f0*/ 11, 10, 10, 4, 17, 11, 7, 11, 11, 5, 10, 4, 17, 0, 7, 11, 162 | }; 163 | 164 | static uint8_t pcbyte() { return memory[ reg.pc++ ]; } 165 | 166 | #ifdef TARGET_BIG_ENDIAN 167 | static uint16_t mword( uint16_t offset ) { return flip_endian16( * ( (uint16_t *) & memory[ offset ] ) ); } 168 | static void setmword( uint16_t offset, uint16_t value ) { * (uint16_t *) & memory[ offset ] = flip_endian16( value ); } 169 | #else 170 | static uint16_t mword( uint16_t offset ) { return * ( (uint16_t *) & memory[ offset ] ); } 171 | static void setmword( uint16_t offset, uint16_t value ) { * (uint16_t *) & memory[ offset ] = value; } 172 | #endif 173 | 174 | static uint16_t pcword() { uint16_t r = mword( reg.pc ); reg.pc += 2; return r; } 175 | static void pushword( uint16_t val ) { reg.sp -= 2; setmword( reg.sp, val ); } 176 | static uint16_t popword() { uint16_t val = mword( reg.sp ); reg.sp += 2; return val; } 177 | 178 | bool is_parity_even( uint8_t x ) 179 | { 180 | #if defined(_M_AMD64) && !defined(__GNUC__) 181 | return ( ! ( __popcnt16( x ) & 1 ) ); // less portable, but faster 182 | #else 183 | return ( ! ( std::bitset<8>( x ).count() & 1 ) ); 184 | #endif 185 | } //is_parity_even 186 | 187 | void set_parity( uint8_t x ) { reg.fParityEven_Overflow = is_parity_even( x ); } 188 | 189 | void set_sign_zero( uint8_t x ) 190 | { 191 | reg.fSign = ( 0 != ( 0x80 & x ) ); 192 | reg.fZero = ( 0 == x ); 193 | } //set_sign_zero 194 | 195 | void set_sign_zero_parity( uint8_t x ) 196 | { 197 | set_sign_zero( x ); 198 | set_parity( x ); 199 | } //set_sign_zero_parity 200 | 201 | void z80_set_sign_zero_16( uint16_t x ) 202 | { 203 | reg.fSign = ( 0 != ( 0x8000 & x ) ); 204 | reg.fZero = ( 0 == x ); 205 | } //z80_set_sign_zero_16 206 | 207 | uint8_t op_inc( uint8_t x ) 208 | { 209 | x++; 210 | reg.fAuxCarry = ( 0 == ( x & 0xf ) ); 211 | set_sign_zero( x ); 212 | 213 | if ( reg.fZ80Mode ) 214 | { 215 | reg.fParityEven_Overflow = ( x == 0x80 ); 216 | reg.fWasSubtract = false; 217 | reg.z80_assignYX( x ); 218 | } 219 | else 220 | set_parity( x ); 221 | return x; 222 | } //op_inc 223 | 224 | uint8_t op_dec( uint8_t x ) 225 | { 226 | uint8_t result = x - 1; 227 | set_sign_zero( result ); 228 | 229 | if ( reg.fZ80Mode ) 230 | { 231 | reg.fParityEven_Overflow = ( x == 0x80 ); 232 | reg.fWasSubtract = true; 233 | 234 | // Aux / Half carry returns the opposite value on Z80 as on 8080 235 | reg.fAuxCarry = ( 0xf == ( result & 0xf ) ); 236 | reg.z80_assignYX( result ); 237 | } 238 | else 239 | { 240 | set_parity( result ); 241 | reg.fAuxCarry = ( 0xf != ( result & 0xf ) ); 242 | } 243 | 244 | return result; 245 | } //op_dec 246 | 247 | force_inlined void op_add( uint8_t x, bool carry = false ) 248 | { 249 | uint16_t carry_int = carry ? 1 : 0; 250 | uint16_t r16 = (uint16_t) reg.a + (uint16_t) x + carry_int; 251 | uint8_t r8 = r16 & 0xff; 252 | reg.fCarry = ( 0 != ( r16 & 0x0100 ) ); 253 | 254 | // low nibble add + carry overflows to high nibble 255 | 256 | reg.fAuxCarry = ( 0 != ( ( ( 0xf & reg.a ) + ( 0xf & x ) + carry_int ) & 0x10 ) ); 257 | set_sign_zero( r8 ); 258 | 259 | // if not ( ( one of lhs and rhs are negative ) and ( one of lhs and result are negative ) ) 260 | 261 | if ( reg.fZ80Mode ) 262 | { 263 | reg.fParityEven_Overflow = ( ! ( ( reg.a ^ x ) & 0x80 ) ) && ( ( reg.a ^ r8 ) & 0x80 ); 264 | reg.fWasSubtract = false; 265 | reg.z80_assignYX( r8 ); 266 | } 267 | else 268 | set_parity( r8 ); 269 | 270 | reg.a = r8; 271 | } //op_add 272 | 273 | void op_adc( uint8_t x ) 274 | { 275 | op_add( x, reg.fCarry ); 276 | } //op_adc 277 | 278 | force_inlined uint8_t op_sub( uint8_t x, bool borrow = false ) 279 | { 280 | // com == ones-complement 281 | uint8_t com_x = ~x; 282 | uint8_t borrow_int = borrow ? 0 : 1; 283 | uint16_t res16 = (uint16_t) reg.a + (uint16_t) com_x + (uint16_t) borrow_int; 284 | uint8_t res8 = res16 & 0xff; 285 | reg.fCarry = ( 0 == ( res16 & 0x100 ) ); 286 | set_sign_zero( res8 ); 287 | reg.fAuxCarry = ( 0 != ( ( ( reg.a & 0xf ) + ( com_x & 0xf ) + borrow_int ) & 0x10 ) ); 288 | 289 | if ( reg.fZ80Mode ) 290 | { 291 | // if not ( ( one of lhs and com_x are negative ) and ( one of lhs and result are negative ) ) 292 | 293 | reg.fParityEven_Overflow = ! ( ( reg.a ^ com_x ) & 0x80 ) && ( ( reg.a ^ res8 ) & 0x80 ); 294 | reg.fWasSubtract = true; 295 | reg.fAuxCarry = !reg.fAuxCarry; // opposite meaning on z80 for subtract 296 | reg.z80_assignYX( res8 ); 297 | } 298 | else 299 | set_parity( res8 ); 300 | 301 | // op_sub is the only op_X that doesn't update reg.a because it's also used for op_cmp 302 | return res8; 303 | } //op_sub 304 | 305 | force_inlined void op_sbb( uint8_t x ) 306 | { 307 | reg.a = op_sub( x, reg.fCarry ); 308 | } //op_sbb 309 | 310 | force_inlined void op_cmp( uint8_t x ) 311 | { 312 | op_sub( x, false ); 313 | if ( reg.fZ80Mode ) 314 | reg.z80_assignYX( x ); // done on operand, not the result or reg.a 315 | } //op_cmp 316 | 317 | void op_ana( uint8_t x ) 318 | { 319 | reg.fAuxCarry = ( 0 != ( 0x8 & ( reg.a | x ) ) ); // documented for 8080, not true for 8085 320 | reg.fCarry = false; 321 | reg.a &= x; 322 | set_sign_zero_parity( reg.a ); 323 | 324 | if ( reg.fZ80Mode ) 325 | { 326 | reg.fWasSubtract = false; 327 | reg.fAuxCarry = true; 328 | reg.z80_assignYX( reg.a ); 329 | } 330 | } //op_ana 331 | 332 | void op_ora( uint8_t x ) 333 | { 334 | reg.a |= x; 335 | reg.fAuxCarry = false; 336 | reg.fCarry = false; 337 | set_sign_zero_parity( reg.a ); 338 | 339 | if ( reg.fZ80Mode ) 340 | { 341 | reg.fWasSubtract = false; 342 | reg.z80_assignYX( reg.a ); 343 | } 344 | } //op_ora 345 | 346 | void op_xra( uint8_t x ) 347 | { 348 | reg.a ^= x; 349 | reg.fAuxCarry = false; 350 | reg.fCarry = false; 351 | set_sign_zero_parity( reg.a ); 352 | 353 | if ( reg.fZ80Mode ) 354 | { 355 | reg.fWasSubtract = false; 356 | reg.z80_assignYX( reg.a ); 357 | } 358 | } //op_xra 359 | 360 | void op_math( uint8_t opcode, uint8_t src ) 361 | { 362 | uint8_t math = ( opcode >> 3 ) & 7; 363 | assert( math <= 7 ); 364 | if ( 7 == math ) op_cmp( src ); // in order of usage for performance 365 | else if ( 6 == math ) op_ora( src ); 366 | else if ( 4 == math ) op_ana( src ); 367 | else if ( 5 == math ) op_xra( src ); 368 | else if ( 0 == math ) op_add( src ); 369 | else if ( 2 == math ) reg.a = op_sub( src ); // sub doesn't update reg.a 370 | else if ( 1 == math ) op_adc( src ); 371 | else op_sbb( src ); // 3 372 | } //op_math 373 | 374 | void op_dad( uint16_t x ) 375 | { 376 | // add x to H and set Carry if warranted 377 | 378 | uint32_t result = (uint32_t) reg.H() + (uint32_t) x; 379 | reg.fCarry = ( 0 != ( 0x10000 & result ) ); 380 | 381 | if ( reg.fZ80Mode ) 382 | { 383 | uint32_t auxResult = ( reg.H() & 0xfff ) + ( x & 0xfff ); 384 | reg.fAuxCarry = ( 0 != ( auxResult & 0xf000 ) ); 385 | reg.fWasSubtract = false; 386 | reg.z80_assignYX( (uint8_t) ( result >> 8 ) ); 387 | } 388 | 389 | reg.SetH( (uint16_t) ( result & 0xffff ) ); 390 | } //op_dad 391 | 392 | void op_cma() 393 | { 394 | reg.a = ~reg.a; 395 | if ( reg.fZ80Mode ) 396 | { 397 | reg.fAuxCarry = true; 398 | reg.fWasSubtract = true; 399 | reg.z80_assignYX( reg.a ); 400 | } 401 | } //op_cma 402 | 403 | void op_cmc() 404 | { 405 | reg.fCarry = !reg.fCarry; 406 | if ( reg.fZ80Mode ) 407 | { 408 | reg.fWasSubtract = false; 409 | reg.fAuxCarry = !reg.fCarry; // some docs say !reg.fAuxCarry 410 | reg.z80_assignYX( reg.a ); 411 | } 412 | } //op_cmc 413 | 414 | not_inlined void op_daa() 415 | { 416 | if ( reg.fZ80Mode ) // this BCD add logic pulled from Sean Young's doc 417 | { 418 | uint8_t diff = 0x66; 419 | uint8_t hn = ( ( reg.a >> 4 ) & 0xf ); 420 | uint8_t ln = ( reg.a & 0xf ); 421 | 422 | if ( reg.fCarry ) 423 | { 424 | if ( !reg.fAuxCarry && ln <= 9 ) 425 | diff = 0x60; 426 | } 427 | else 428 | { 429 | if ( hn <= 9 && !reg.fAuxCarry && ln <= 9 ) 430 | diff = 0; 431 | else if ( ( hn <= 9 && reg.fAuxCarry && ln <= 9 ) || ( hn <= 8 && ln > 9 ) ) 432 | diff = 6; 433 | else if ( hn > 9 && !reg.fAuxCarry && ln <= 9 ) 434 | diff = 0x60; 435 | } 436 | 437 | bool newCarry = reg.fCarry; 438 | if ( !reg.fCarry ) 439 | { 440 | if ( ( hn <= 9 && ln <= 9 ) || ( hn <= 8 && ln > 9 ) ) 441 | newCarry = false; 442 | else if ( ( hn >= 9 && ln > 9 ) || ( hn > 9 && ln <= 9 ) ) 443 | newCarry = true; 444 | } 445 | 446 | bool newAuxCarry = reg.fAuxCarry; 447 | if ( reg.fWasSubtract ) 448 | { 449 | if ( !reg.fAuxCarry ) 450 | newAuxCarry = false; 451 | else 452 | newAuxCarry = ( ln < 6 ); 453 | } 454 | else 455 | newAuxCarry = ( ln > 9 ); 456 | 457 | if ( reg.fWasSubtract ) 458 | reg.a -= diff; 459 | else 460 | reg.a += diff; 461 | 462 | set_sign_zero_parity( reg.a ); 463 | reg.z80_assignYX( reg.a ); 464 | reg.fCarry = newCarry; 465 | reg.fAuxCarry = newAuxCarry; 466 | } 467 | else 468 | { 469 | uint8_t loNibble = reg.a & 0xf; 470 | uint8_t toadd = 0; 471 | if ( reg.fAuxCarry || ( loNibble > 9 ) ) 472 | toadd = 6; 473 | 474 | bool carry = reg.fCarry; 475 | uint8_t hiNibble = reg.a & 0xf0; 476 | if ( ( hiNibble > 0x90 ) || ( hiNibble >= 0x90 && loNibble > 0x9 ) || carry ) 477 | { 478 | toadd |= 0x60; 479 | carry = true; 480 | } 481 | 482 | op_add( toadd ); 483 | reg.fCarry = carry; // this doesn't change regardless of the result 484 | } 485 | } //op_daa 486 | 487 | uint8_t * dst_address_rm( uint8_t rm ) 488 | { 489 | assert( rm <= 7 ); 490 | if ( 6 != rm ) 491 | return reg.regOffset( rm ); 492 | 493 | return & memory[ reg.H() ]; 494 | } //dst_address_rm 495 | 496 | uint8_t * dst_address( uint8_t op ) 497 | { 498 | uint8_t rm = 7 & ( op >> 3 ); 499 | return dst_address_rm( rm ); 500 | } //dst_address 501 | 502 | uint8_t src_value_rm( uint8_t rm ) 503 | { 504 | if ( 6 != rm ) 505 | return * ( reg.regOffset( rm ) ); 506 | return memory[ reg.H() ]; 507 | } //src_value_rm 508 | 509 | uint8_t src_value( uint8_t op ) 510 | { 511 | uint8_t rm = 0x7 & op; 512 | return src_value_rm( rm ); 513 | } //src_value 514 | 515 | void z80_ni( uint8_t op, uint8_t op2 ) 516 | { 517 | x80_hard_exit( "bugbug: not-implemented z80 instruction: %#x, next byte is %#x\n", op, op2 ); 518 | } //z80_ni 519 | 520 | void z80_op_bit( uint8_t val, uint8_t bit, z80_value_source vs ) 521 | { 522 | assert( bit <= 7 ); 523 | reg.fAuxCarry = true; // per doc 524 | reg.fWasSubtract = false; 525 | 526 | reg.fSign = ( ( 7 == bit ) && ( 0 != ( 0x80 & val ) ) ); // Zilog doc says fSign is "unknown", but hardware does this 527 | uint8_t cmp = ( 1 << bit ); 528 | reg.fZero = ( 0 == ( val & cmp ) ); 529 | reg.fParityEven_Overflow = reg.fZero; // non-documented 530 | 531 | // Y and X are set from the source value, not as 4.1 from "The Undocumented Z80 Documented" 532 | // has from the value resulting from the bit operation. But only if the source is a register. 533 | 534 | if ( vs_register == vs ) 535 | reg.z80_assignYX( val ); 536 | } //z80_op_bit 537 | 538 | void z80_op_rlc( uint8_t * pval ) 539 | { 540 | // rotate left carry. 7 bit to both C and 0 bit. flags: S, Z, H reset, Parity, N reset, and C 541 | 542 | uint8_t x = *pval; 543 | bool bit7 = ( 0 != ( x & 0x80 ) ); 544 | x <<= 1; 545 | reg.fCarry = bit7; 546 | if ( bit7 ) 547 | x |= 1; 548 | set_sign_zero_parity( x ); 549 | reg.fAuxCarry = false; 550 | reg.fWasSubtract = false; 551 | reg.z80_assignYX( x ); 552 | *pval = x; 553 | } //z80_op_rlc 554 | 555 | void z80_op_rl( uint8_t * pval ) 556 | { 557 | // rotate left. 7 bit to C. Old carry bit to 0. flags: S, Z, H reset, Parity, N reset, and C 558 | 559 | uint8_t x = *pval; 560 | bool bit7 = ( 0 != ( x & 0x80 ) ); 561 | x <<= 1; 562 | if ( reg.fCarry ) 563 | x |= 1; 564 | reg.fCarry = bit7; 565 | set_sign_zero_parity( x ); 566 | reg.fAuxCarry = false; 567 | reg.fWasSubtract = false; 568 | reg.z80_assignYX( x ); 569 | *pval = x; 570 | } //z80_op_rl 571 | 572 | void z80_op_rrc( uint8_t * pval ) 573 | { 574 | // rotate right carry. 0 bit to both C and 7 bit. flags: S, Z, H reset, Parity, N reset, and C 575 | 576 | uint8_t x = *pval; 577 | bool bit0 = ( 0 != ( x & 1 ) ); 578 | x >>= 1; 579 | reg.fCarry = bit0; 580 | if ( bit0 ) 581 | x |= 0x80; 582 | set_sign_zero_parity( x ); 583 | reg.fAuxCarry = false; 584 | reg.fWasSubtract = false; 585 | reg.z80_assignYX( x ); 586 | *pval = x; 587 | } //z80_op_rrc 588 | 589 | void z80_op_rr( uint8_t * pval ) 590 | { 591 | // rotate right. 0 bit to C. Old C to 7 bit. flags: S, Z, H reset, Parity, N reset, and C 592 | 593 | uint8_t x = *pval; 594 | bool bit0 = ( 0 != ( x & 1 ) ); 595 | x >>= 1; 596 | if ( reg.fCarry ) 597 | x |= 0x80; 598 | reg.fCarry = bit0; 599 | set_sign_zero_parity( x ); 600 | reg.fAuxCarry = false; 601 | reg.fWasSubtract = false; 602 | reg.z80_assignYX( x ); 603 | *pval = x; 604 | } //z80_op_rr 605 | 606 | uint16_t z80_op_sub_16( uint16_t lhs, uint16_t rhs, bool borrow = false ) 607 | { 608 | // com == ones-complement 609 | 610 | uint16_t com_rhs = ~rhs; 611 | uint16_t borrow_int = borrow ? 0 : 1; 612 | uint32_t res32 = (uint32_t) lhs + (uint32_t) com_rhs + (uint32_t) borrow_int; 613 | uint16_t res16 = res32 & 0xffff; 614 | 615 | reg.fCarry = ( 0 == ( res32 & 0x10000 ) ); 616 | z80_set_sign_zero_16( res16 ); 617 | 618 | // if not ( ( one of lhs and com_rhs are negative ) and ( one of lhs and result are negative ) ) 619 | 620 | reg.fParityEven_Overflow = ! ( ( lhs ^ com_rhs ) & 0x8000 ) && ( ( lhs ^ res16 ) & 0x8000 ); 621 | reg.fWasSubtract = true; 622 | if ( borrow ) 623 | rhs++; 624 | reg.fAuxCarry = ( ( rhs & 0xfff ) > ( lhs & 0xfff ) ); 625 | reg.z80_assignYX( res16 >> 8 ); 626 | 627 | return res16; 628 | } //z80_op_sub_16 629 | 630 | uint16_t z80_op_add_16( uint16_t a, uint16_t b ) 631 | { 632 | uint32_t resultAux = ( (uint32_t) ( a & 0xfff ) + (uint32_t) ( b & 0xfff ) ); 633 | reg.fAuxCarry = ( 0 != ( resultAux & 0xfffff000 ) ); 634 | reg.fWasSubtract = false; 635 | 636 | uint32_t result = ( (uint32_t) a + (uint32_t) b ); 637 | reg.fCarry = ( 0 != ( result & 0x10000 ) ); 638 | reg.z80_assignYX( (uint8_t) ( result >> 8 ) ); 639 | 640 | return (uint16_t) result; 641 | } //z80_op_add_16 642 | 643 | uint16_t z80_op_adc_16( uint16_t l, uint16_t r ) 644 | { 645 | uint16_t carryOut, result, resultAux; 646 | 647 | if ( reg.fCarry ) 648 | { 649 | carryOut = ( l >= ( 0xffff - r ) ); 650 | result = r + l + 1; 651 | resultAux = ( 0xfff & r ) + ( 0xfff & l ) + 1; 652 | } 653 | else 654 | { 655 | carryOut = ( l > ( 0xffff - r ) ); 656 | result = r + l; 657 | resultAux = ( 0xfff & r ) + ( 0xfff & l ); 658 | } 659 | 660 | reg.z80_assignYX( result >> 8 ); 661 | reg.fAuxCarry = ( 0 != ( 0xf000 & resultAux ) ); 662 | uint16_t carryIns = result ^ l ^ r; 663 | z80_set_sign_zero_16( result ); 664 | reg.fParityEven_Overflow = ( carryIns >> 15 ) ^ carryOut; 665 | reg.fCarry = ( 0 != carryOut ); 666 | reg.fWasSubtract = false; 667 | return result; 668 | } //z80_op_adc_16 669 | 670 | void z80_op_sla( uint8_t * pval ) 671 | { 672 | uint8_t val = *pval; 673 | reg.fCarry = ( 0 != ( val & 0x80 ) ); 674 | val <<= 1; 675 | set_sign_zero_parity( val ); 676 | reg.clearHN(); 677 | reg.z80_assignYX( val ); 678 | *pval = val; 679 | } //z80_op_sla 680 | 681 | void z80_op_sll( uint8_t * pval ) // not a documented opcode 682 | { 683 | uint8_t val = *pval; 684 | reg.fCarry = ( 0 != ( val & 0x80 ) ); 685 | val <<= 1; 686 | val |= 1; 687 | set_sign_zero_parity( val ); 688 | reg.clearHN(); 689 | reg.z80_assignYX( val ); 690 | *pval = val; 691 | } //z80_op_sll 692 | 693 | void z80_op_sra( uint8_t * pval ) 694 | { 695 | uint8_t val = *pval; 696 | reg.fCarry = ( 0 != ( val & 1 ) ); 697 | val >>= 1; 698 | val |= ( ( *pval ) & 0x80 ); // leave high bit unchanged 699 | set_sign_zero_parity( val ); 700 | reg.clearHN(); 701 | reg.z80_assignYX( val ); 702 | *pval = val; 703 | } //z80_op_sra 704 | 705 | void z80_op_srl( uint8_t * pval ) 706 | { 707 | uint8_t val = *pval; 708 | reg.fCarry = ( val & 1 ); 709 | val >>= 1; 710 | set_sign_zero_parity( val ); 711 | reg.clearHN(); 712 | reg.z80_assignYX( val ); 713 | *pval = val; 714 | } //z80_op_srl 715 | 716 | uint16_t z80_emulate( uint8_t op ) // this is just for instructions that aren't shared with 8080 717 | { 718 | uint16_t opaddress = reg.pc - 1; 719 | uint8_t op2 = memory[ reg.pc ]; 720 | uint8_t op3 = memory[ reg.pc + 1 ]; 721 | uint8_t op4 = memory[ reg.pc + 2 ]; 722 | int op3int = (int) (int8_t) op3; 723 | uint16_t cycles = 4; // general-purpose default 724 | 725 | switch ( op ) 726 | { 727 | case 0x08: // ex af and af' 728 | { 729 | swap( reg.a, reg.ap ); 730 | reg.materializeFlags(); 731 | swap( reg.f, reg.fp ); 732 | reg.unmaterializeFlags(); 733 | break; 734 | } 735 | case 0x10: // djnz 736 | { 737 | uint8_t offset = pcbyte(); 738 | reg.b = reg.b - 1; 739 | if ( 0 != reg.b ) 740 | { 741 | reg.pc = opaddress + 2 + (int16_t) (int8_t) offset; 742 | cycles = 13; 743 | } 744 | else 745 | cycles = 8; 746 | break; 747 | } 748 | case 0x18: // jr n 749 | { 750 | uint8_t offset = pcbyte(); 751 | reg.pc = opaddress + 2 + (int16_t) (int8_t) offset; 752 | cycles = 12; 753 | break; 754 | } 755 | case 0x20: // jr nz, n 756 | { 757 | uint8_t offset = pcbyte(); 758 | if ( !reg.fZero ) 759 | { 760 | reg.pc = opaddress + 2 + (int16_t) (int8_t) offset; 761 | cycles = 12; 762 | } 763 | else 764 | cycles = 7; 765 | break; 766 | } 767 | case 0x28: // jr z, n 768 | { 769 | uint8_t offset = pcbyte(); 770 | if ( reg.fZero ) 771 | { 772 | reg.pc = opaddress + 2 + (int16_t) (int8_t) offset; 773 | cycles = 12; 774 | } 775 | else 776 | cycles = 7; 777 | break; 778 | } 779 | case 0x30: // jr nc, n 780 | { 781 | uint8_t offset = pcbyte(); 782 | if ( !reg.fCarry ) 783 | { 784 | reg.pc = opaddress + 2 + (int16_t) (int8_t) offset; 785 | cycles = 12; 786 | } 787 | else 788 | cycles = 7; 789 | break; 790 | } 791 | case 0x38: // jr c, n 792 | { 793 | uint8_t offset = pcbyte(); 794 | if ( reg.fCarry ) 795 | { 796 | reg.pc = opaddress + 2 + (int16_t) (int8_t) offset; 797 | cycles = 12; 798 | } 799 | else 800 | cycles = 7; 801 | break; 802 | } 803 | case 0xcb: // rotate / bits 804 | { 805 | pcbyte(); // get past op2 806 | 807 | if ( 0x20 == ( op2 & 0xf8 ) ) // sla 808 | { 809 | cycles = 8; 810 | uint8_t rm = op2 & 0x7; 811 | if ( 6 == rm ) 812 | cycles += 2; 813 | uint8_t * pdst = dst_address_rm( rm ); 814 | z80_op_sla( pdst ); 815 | } 816 | else if ( 0x28 == ( op2 & 0xf8 ) ) // sra 817 | { 818 | cycles = 8; 819 | uint8_t rm = op2 & 0x7; 820 | if ( 6 == rm ) 821 | cycles += 2; 822 | uint8_t * pdst = dst_address_rm( rm ); 823 | z80_op_sra( pdst ); 824 | } 825 | else if ( op2 >= 0x30 && op2 <= 0x3f ) 826 | { 827 | uint8_t rm = op2 & 0x7; 828 | uint8_t * pdst = dst_address_rm( rm ); 829 | if ( op2 <= 0x37 ) 830 | z80_op_sll( pdst ); 831 | else 832 | z80_op_srl( pdst ); 833 | } 834 | else if ( 0x38 == ( op2 & 0xf8 ) ) // srl r = shift right logical 835 | { 836 | cycles = 8; 837 | uint8_t rm = op2 >> 4; 838 | if ( 6 == rm ) 839 | cycles += 2; 840 | uint8_t * pdst = dst_address_rm( rm ); 841 | z80_op_srl( pdst ); 842 | } 843 | else if ( op2 >= 0x40 && op2 <= 0x7f ) // bit #, rm 844 | { 845 | cycles = 8; 846 | uint8_t rm = op2 & 0x7; 847 | if ( 6 == rm ) 848 | cycles += 4; 849 | uint8_t bit = ( op2 >> 3 ) & 0x7; 850 | uint8_t val = src_value_rm( rm ); 851 | z80_op_bit( val, bit, ( 6 == rm ) ? vs_memory : vs_register ); 852 | } 853 | else if ( op2 >= 0x80 && op2 <= 0xbf ) // res bit #, rm AKA reset 854 | { 855 | cycles = 8; 856 | uint8_t rm = op2 & 0x7; 857 | if ( 6 == rm ) 858 | cycles += 7; 859 | uint8_t bit = ( op2 >> 3 ) & 0x7; 860 | uint8_t val = src_value_rm( rm ); 861 | uint8_t mask = ~ ( 1 << bit ); 862 | val &= mask; 863 | * dst_address_rm( rm ) = val; 864 | } 865 | else if ( op2 >= 0xc0 && op2 <= 0xff ) // set bit #, rm 866 | { 867 | cycles = 8; 868 | uint8_t rm = op2 & 0x7; 869 | if ( 6 == rm ) 870 | cycles += 7; 871 | uint8_t bit = ( op2 >> 3 ) & 0x7; 872 | uint8_t val = src_value_rm( rm ); 873 | uint8_t mask = 1 << bit; 874 | val |= mask; 875 | * dst_address_rm( rm ) = val; 876 | } 877 | else if ( op2 <= 0x1f ) // rlc, rrc, rl, rr on rm 878 | { 879 | cycles = 8; 880 | uint8_t mod = op2; 881 | uint8_t rot = ( mod >> 3 ) & 0x3; 882 | uint8_t rm = mod & 0x7; 883 | if ( 6 == rm ) 884 | cycles += 2; 885 | uint8_t * pval = dst_address_rm( rm ); 886 | if ( 0 == rot ) 887 | z80_op_rlc( pval ); 888 | else if ( 1 == rot ) 889 | z80_op_rrc( pval ); 890 | else if ( 2 == rot ) 891 | z80_op_rl( pval ); 892 | else // if ( 3 == rot ) 893 | z80_op_rr( pval ); 894 | } 895 | else 896 | z80_ni( op, op2 ); 897 | break; 898 | } 899 | case 0xd9: // exx B, D, H with B', D', H' 900 | { 901 | swap( reg.b, reg.bp ); 902 | swap( reg.c, reg.cp ); 903 | swap( reg.d, reg.dp ); 904 | swap( reg.e, reg.ep ); 905 | swap( reg.h, reg.hp ); 906 | swap( reg.l, reg.lp ); 907 | break; 908 | } 909 | case 0xdd: case 0xfd: // ix & iy operations 910 | { 911 | reg.r++; 912 | pcbyte(); // consume op2: the dd or fd 913 | 914 | if ( 0x21 == op2 ) // ld ix/iy word 915 | { 916 | if ( 0xdd == op ) 917 | reg.ix = pcword(); 918 | else 919 | reg.iy = pcword(); 920 | } 921 | else if ( 0x22 == op2 ) // ld (address), ix/iy 922 | { 923 | cycles = 20; 924 | uint16_t address = pcword(); 925 | setmword( address, reg.z80_getIndex( op ) ); 926 | } 927 | else if ( 0x23 == op2 ) // inc ix/iy no flags are affected 928 | { 929 | cycles = 10; 930 | if ( 0xdd == op ) 931 | reg.ix++; 932 | else 933 | reg.iy++; 934 | } 935 | else if ( 0x26 == op2 ) // ld ix/iy h. not documented 936 | * reg.z80_getIndexByteAddress( op, 0 ) = pcbyte(); 937 | else if ( 0x2a == op2 ) // ld ix, (address) 938 | { 939 | cycles = 20; 940 | uint16_t address = pcword(); 941 | reg.z80_setIndex( op, mword( address ) ); 942 | } 943 | else if ( 0x2b == op2 ) // dec ix/iy no flags are affected 944 | { 945 | cycles = 10; 946 | if ( 0xdd == op ) 947 | reg.ix--; 948 | else 949 | reg.iy--; 950 | } 951 | else if ( 0x2e == op2 ) // ld ix/iy l. not documented 952 | { 953 | cycles = 20; // guess 954 | * reg.z80_getIndexByteAddress( op, 1 ) = pcbyte(); 955 | } 956 | else if ( 0x34 == op2 ) // inc (i + index) 957 | { 958 | cycles = 23; 959 | uint16_t i = reg.z80_getIndex( op ) + (int16_t) (int8_t) pcbyte(); 960 | uint8_t x = memory[ i ]; 961 | memory[i] = op_inc( x ); 962 | } 963 | else if ( 0x35 == op2 ) // dec (i + index) 964 | { 965 | cycles = 23; 966 | uint16_t i = reg.z80_getIndex( op ) + (int16_t) (int8_t) pcbyte(); 967 | uint8_t x = memory[ i ]; 968 | memory[ i ] = op_dec( x ); 969 | } 970 | else if ( 0x36 == op2 ) // ld (ix/iy + index), immediate byte 971 | { 972 | cycles = 19; 973 | uint16_t i = reg.z80_getIndex( op ) + (int16_t) (int8_t) pcbyte(); 974 | uint8_t val = pcbyte(); 975 | memory[ i ] = val; 976 | } 977 | else if ( ( ( op2 >= 0x40 && op2 <= 0x6f ) || ( op2 >= 0x78 && op2 <= 0x7f ) ) && // ld [bcdeIhIla][bcdeIhIla] 978 | ( ( ( op2 & 0xf ) != 6 ) && ( ( op2 & 0xf ) != 0xe ) ) ) 979 | { 980 | uint8_t fromval = op2 & 0xf; 981 | if ( fromval >= 8 ) 982 | fromval -= 8; 983 | 984 | uint8_t tmp = ( 0 == fromval ) ? reg.b : 985 | ( 1 == fromval ) ? reg.c : 986 | ( 2 == fromval ) ? reg.d : 987 | ( 3 == fromval ) ? reg.e : 988 | ( 4 == fromval ) ? reg.z80_getIndexByte( op, 0 ) : 989 | ( 5 == fromval ) ? reg.z80_getIndexByte( op, 1 ) : 990 | reg.a; 991 | 992 | if ( op2 <= 0x47 ) 993 | reg.b = tmp; 994 | else if ( op2 <= 0x4f ) 995 | reg.c = tmp; 996 | else if ( op2 <= 0x57 ) 997 | reg.d = tmp; 998 | else if ( op2 <= 0x5f ) 999 | reg.e = tmp; 1000 | else if ( op2 <= 0x67 ) 1001 | *reg.z80_getIndexByteAddress( op, 0 ) = tmp; 1002 | else if ( op2 <= 0x6f ) 1003 | *reg.z80_getIndexByteAddress( op, 1 ) = tmp; 1004 | else 1005 | reg.a = tmp; 1006 | } 1007 | else if ( 0x46 == ( op2 & 0x47 ) ) // ld r, (i + #) 1008 | { 1009 | cycles = 19; 1010 | pcbyte(); // consume op3 1011 | uint16_t address = reg.z80_getIndex( op ) + (uint16_t) op3int; 1012 | * dst_address( op2 ) = memory[ address ]; 1013 | } 1014 | else if ( 0x70 == ( op2 & 0xf8 ) ) // ld (i+#), r/# 1015 | { 1016 | cycles = 19; 1017 | pcbyte(); // consume op3 1018 | 1019 | // if 6, there is an op4 for the index (not hl-indexed memory); otherwise use a register value 1020 | 1021 | uint8_t src = op2 & 0x7; 1022 | uint8_t val = ( 6 == src ) ? pcbyte() : src_value_rm( src ); 1023 | uint16_t i = reg.z80_getIndex( op ); 1024 | i += (uint16_t) op3int; 1025 | memory[ i ] = val; 1026 | } 1027 | else if ( 0x80 == ( op2 & 0xc2 ) ) // math on il and ih with a. 84/85/8c/8d/94/95/a4/a5/b4/b5/bc/bd 1028 | { 1029 | uint8_t value = reg.z80_getIndexByte( op, op2 & 1 ); 1030 | op_math( op2, value ); 1031 | } 1032 | else if ( 0x86 == ( op2 & 0xc7 ) ) // math on [ ix/iy + index ] 1033 | { 1034 | cycles = 19; 1035 | uint16_t x = reg.z80_getIndex( op ); 1036 | x += (int16_t) (int8_t) pcbyte(); 1037 | op_math( op2, memory[ x ] ); 1038 | } 1039 | else if ( 0x24 == ( op2 & 0xf6 ) ) // inc/dec ixh, ixl, iyh, iyl 1040 | { 1041 | uint8_t *pval = reg.z80_getIndexByteAddress( op, ( op2 >> 3 ) & 1 ); 1042 | if ( op2 & 1 ) 1043 | *pval = op_dec( *pval ); 1044 | else 1045 | *pval = op_inc( *pval ); 1046 | } 1047 | else if ( 0x09 == ( op2 & 0xcf ) ) // add ix/iy, rp 1048 | { 1049 | // only sets H (carry from bit 11) and C (carry from bit 15) flags. ignores C flag on input. N is reset 1050 | // for add ix, rp is 0..3 bc, de, ix, sp. 1051 | // for add iy, rp is 0..3 bc, de, iy, sp. 1052 | 1053 | uint16_t rpval = * reg.rpAddressFromOp( op2 ); 1054 | uint8_t rp = ( 0x3 & ( op2 >> 4 ) ); 1055 | if ( 2 == rp ) 1056 | rpval = ( 0xdd == op ) ? reg.ix : reg.iy; 1057 | 1058 | uint16_t oldval = reg.z80_getIndex( op ); 1059 | uint16_t newval = z80_op_add_16( oldval, rpval ); 1060 | reg.z80_setIndex( op, newval ); 1061 | } 1062 | else if ( 0xcb == op2 ) // bit operations 1063 | { 1064 | reg.r++; 1065 | if ( 0x26 == op4 || 0x2e == op4 || 0x3e == op4 ) // sla, sra, srl [ix/iy + offset] 1066 | { 1067 | uint8_t offset = pcbyte(); // the op3 1068 | pcbyte(); // the op4 1069 | uint16_t index = reg.z80_getIndex( op ); 1070 | index += (int16_t) (int8_t) offset; 1071 | if ( 0x26 == op4 ) // sla 1072 | z80_op_sla( & memory[ index ] ); 1073 | else if ( 0x2e == op4 ) // sra 1074 | z80_op_sra( & memory[ index ] ); 1075 | else // ( 0x3e == op4 ) srl 1076 | z80_op_srl( & memory[ index ] ); 1077 | } 1078 | else if ( op4 <= 0x3f ) // bit shift on memory 1079 | { 1080 | cycles = 23; // this is a guess -- it's undocumented 1081 | uint8_t offset = pcbyte(); 1082 | pcbyte(); 1083 | uint16_t index = reg.z80_getIndex( op ); 1084 | index += (int16_t) (int8_t) offset; 1085 | 1086 | if ( op4 <= 0x07 ) 1087 | z80_op_rlc( & memory[ index ] ); 1088 | else if ( op4 <= 0x0f ) 1089 | z80_op_rrc( & memory[ index ] ); 1090 | else if ( op4 <= 0x17 ) 1091 | z80_op_rl( & memory[ index ] ); 1092 | else if ( op4 <= 0x1f ) 1093 | z80_op_rr( & memory[ index ] ); 1094 | else if ( op4 <= 0x27 ) 1095 | z80_op_sla( & memory[ index ] ); 1096 | else if ( op4 <= 0x2f ) 1097 | z80_op_sra( & memory[ index ] ); 1098 | else if ( op4 <= 0x37 ) 1099 | z80_op_sll( & memory[ index ] ); 1100 | else if ( op4 <= 0x3f ) 1101 | z80_op_srl( & memory[ index ] ); 1102 | 1103 | uint8_t rm = op4 & 0x7; 1104 | if ( 6 != rm ) // no write to memory variant 1105 | * dst_address_rm( rm ) = memory[ index ]; 1106 | } 1107 | else if ( ( ( op4 & 0xf ) == 0xe ) || ( ( op4 & 0xf ) == 0x6 ) ) // bit/res/set b, (ix/iy + d) 1108 | { 1109 | cycles = 23; 1110 | uint8_t index = pcbyte(); 1111 | uint8_t mod = pcbyte(); 1112 | uint8_t bit = ( mod >> 3 ) & 0x7; 1113 | uint8_t mask = 1 << bit; 1114 | uint8_t top2bits = mod & 0xc0; 1115 | uint16_t offset = reg.z80_getIndex( op ) + (int16_t) (int8_t) index; 1116 | uint8_t val = memory[ offset ]; 1117 | 1118 | if ( 0x40 == top2bits ) // bit 1119 | { 1120 | cycles++; 1121 | z80_op_bit( val, bit, vs_indexed ); 1122 | } 1123 | else if ( 0x80 == top2bits ) // reset 1124 | { 1125 | mask = ~mask; 1126 | val &= mask; 1127 | memory[ offset ] = val; 1128 | } 1129 | else if ( 0xc0 == top2bits ) // set 1130 | { 1131 | val |= mask; 1132 | memory[ offset ] = val; 1133 | } 1134 | else if ( 0x00 == top2bits ) // rlc/rl/rrc/rr rotate of (i + index) 1135 | { 1136 | if ( 0x06 == mod ) 1137 | z80_op_rlc( & memory[ offset ] ); 1138 | else if ( 0x16 == mod ) 1139 | z80_op_rl( & memory[ offset ] ); 1140 | else if ( 0x0e == mod ) 1141 | z80_op_rrc( & memory[ offset ] ); 1142 | else if ( 0x1e == mod ) 1143 | z80_op_rr( & memory[ offset ] ); 1144 | else 1145 | z80_ni( op, op2 ); 1146 | } 1147 | else 1148 | z80_ni( op, op2 ); 1149 | } 1150 | } 1151 | else if ( 0xe1 == op2 ) // pop ix/iy 1152 | { 1153 | cycles = 14; 1154 | uint16_t val = popword(); 1155 | reg.z80_setIndex( op, val ); 1156 | } 1157 | else if ( 0xe3 == op2 ) // ex (sp), ix/iy 1158 | { 1159 | cycles = 23; 1160 | uint16_t val = reg.z80_getIndex( op ); 1161 | reg.z80_setIndex( op, mword( reg.sp ) ); 1162 | setmword( reg.sp, val ); 1163 | } 1164 | else if ( 0xe5 == op2 ) // push ix/iy 1165 | { 1166 | cycles = 15; 1167 | uint16_t val = reg.z80_getIndex( op ); 1168 | pushword( val ); 1169 | } 1170 | else if ( 0xe9 == op2 ) // jp (ix/iy) // the Z80 name makes it look indirect. It's not. 1171 | { 1172 | cycles = 8; 1173 | reg.pc = reg.z80_getIndex( op ); 1174 | } 1175 | else if ( 0xf9 == op2 ) // ld sp, ix/iy 1176 | { 1177 | cycles = 10; 1178 | reg.sp = reg.z80_getIndex( op ); 1179 | } 1180 | else 1181 | z80_ni( op, op2 ); 1182 | break; 1183 | } 1184 | case 0xed: // 16-bit load/store and i/o operations 1185 | { 1186 | reg.r++; 1187 | pcbyte(); // consume op2 1188 | 1189 | if ( 0x3 == ( op2 & 0xf ) ) // ld (mw), rp AKA ld (nn), dd 1190 | { 1191 | cycles = 20; 1192 | uint16_t * prp = reg.rpAddressFromOp( op2 ); 1193 | setmword( pcword(), *prp ); 1194 | } 1195 | else if ( 0xb == ( op2 & 0xf ) ) // ld rp, (nn) AKA ld dd, (nn) 1196 | { 1197 | cycles = 20; 1198 | uint16_t * prp = reg.rpAddressFromOp( op2 ); 1199 | *prp = mword( pcword() ); 1200 | } 1201 | else if ( 0x44 == op2 ) // neg 1202 | { 1203 | cycles = 8; 1204 | uint8_t prior = reg.a; 1205 | reg.a = 0 - reg.a; 1206 | set_sign_zero( reg.a ); 1207 | reg.fParityEven_Overflow = ( 0x80 == prior ); 1208 | reg.fWasSubtract = true; 1209 | reg.fCarry = ( 0 != prior ); 1210 | reg.fAuxCarry = ( 0 != ( prior & 0xf ) ); 1211 | reg.z80_assignYX( reg.a ); 1212 | } 1213 | else if ( 0x47 == op2 ) // ld i,a 1214 | reg.i = reg.a; 1215 | else if ( 0x4f == op2 ) // ld r,a 1216 | reg.r = reg.a; 1217 | else if ( 0x57 == op2 ) // ld a,i 1218 | { 1219 | reg.a = reg.i; 1220 | set_sign_zero( reg.a ); 1221 | reg.fParityEven_Overflow = false; // no iff2 1222 | reg.fWasSubtract = false; 1223 | reg.fAuxCarry = false; 1224 | reg.z80_assignYX( reg.a ); 1225 | } 1226 | else if ( 0x5f == op2 ) // ld a,r 1227 | { 1228 | reg.a = ( 0x7f & reg.r ); // the high bit is always 0 on Z80 1229 | set_sign_zero( reg.a ); 1230 | reg.fParityEven_Overflow = false; // no iff2 1231 | reg.fWasSubtract = false; 1232 | reg.fAuxCarry = false; 1233 | reg.z80_assignYX( reg.a ); 1234 | } 1235 | else if ( 0x67 == op2 ) // rrd 1236 | { 1237 | uint8_t mem = memory[ reg.H() ]; 1238 | uint8_t a = reg.a; 1239 | 1240 | reg.a = ( a & 0xf0 ) | ( mem & 0x0f ); 1241 | uint8_t newmem = ( ( a << 4 ) & 0xf0 ) | ( ( mem >> 4 ) & 0x0f ); 1242 | memory[ reg.H() ] = newmem; 1243 | 1244 | set_sign_zero_parity( reg.a ); 1245 | reg.clearHN(); 1246 | reg.z80_assignYX( reg.a ); 1247 | } 1248 | else if ( 0x6f == op2 ) // rld 1249 | { 1250 | uint8_t mem = memory[ reg.H() ]; 1251 | uint8_t a = reg.a; 1252 | 1253 | uint8_t newmem = ( ( mem << 4 ) & 0xf0 ) | ( a & 0x0f ); 1254 | reg.a = ( ( mem >> 4 ) & 0xf ) | ( a & 0xf0 ); 1255 | memory[ reg.H() ] = newmem; 1256 | 1257 | set_sign_zero_parity( reg.a ); 1258 | reg.clearHN(); 1259 | reg.z80_assignYX( reg.a ); 1260 | } 1261 | else if ( 0x4a == ( op2 & 0xcf ) ) // adc hl, rp 1262 | { 1263 | uint16_t result = z80_op_adc_16( reg.H(), * reg.rpAddressFromOp( op2 ) ); 1264 | reg.SetH( result ); 1265 | } 1266 | else if ( 0xa0 == op2 ) // ldi 1267 | { 1268 | cycles = 16; 1269 | memory[ reg.D() ] = memory[ reg.H() ]; 1270 | reg.fY = ( 0 != ( ( memory[ reg.H() ] + reg.a ) & 0x02 ) ); 1271 | reg.fX = ( 0 != ( ( memory[ reg.H() ] + reg.a ) & 0x08 ) ); 1272 | reg.SetH( reg.H() + 1 ); 1273 | reg.SetD( reg.D() + 1 ); 1274 | reg.SetB( reg.B() - 1 ); 1275 | reg.fParityEven_Overflow = ( 0 != reg.B() ); 1276 | reg.fAuxCarry = 0; 1277 | reg.fWasSubtract = 0; 1278 | } 1279 | else if ( 0xa1 == op2 ) // cpi 1280 | { 1281 | bool oldCarry = reg.fCarry; 1282 | cycles = 16; 1283 | uint8_t memval = memory[ reg.H() ]; 1284 | op_cmp( memval ); 1285 | reg.SetH( reg.H() + 1 ); 1286 | reg.SetB( reg.B() - 1 ); 1287 | reg.fParityEven_Overflow = ( 0 != reg.B() ); 1288 | uint8_t n = reg.a - memval - ( reg.fAuxCarry ? 1 : 0 ); // n = A - (HL) - HF 1289 | reg.fY = ( 0 != ( n & 0x02 ) ); 1290 | reg.fX = ( 0 != ( n & 0x08 ) ); 1291 | reg.fCarry = oldCarry; // carry is not affected 1292 | } 1293 | else if ( 0xa8 == op2 ) // ldd 1294 | { 1295 | cycles = 16; 1296 | memory[ reg.D() ] = memory[ reg.H() ]; 1297 | reg.fY = ( 0 != ( ( memory[ reg.H() ] + reg.a ) & 0x02 ) ); 1298 | reg.fX = ( 0 != ( ( memory[ reg.H() ] + reg.a ) & 0x08 ) ); 1299 | reg.SetH( reg.H() - 1 ); 1300 | reg.SetD( reg.D() - 1 ); 1301 | reg.SetB( reg.B() - 1 ); 1302 | reg.fParityEven_Overflow = ( 0 != reg.B() ); 1303 | reg.clearHN(); 1304 | } 1305 | else if ( 0xa9 == op2 ) // cpd 1306 | { 1307 | bool oldCarry = reg.fCarry; 1308 | cycles = 16; 1309 | uint8_t memval = memory[ reg.H() ]; 1310 | op_cmp( memval ); 1311 | reg.SetH( reg.H() - 1 ); 1312 | reg.SetB( reg.B() - 1 ); 1313 | reg.fParityEven_Overflow = ( 0 != reg.B() ); 1314 | uint8_t n = reg.a - memval - ( reg.fAuxCarry ? 1 : 0 ); // n = A - (HL) - HF 1315 | reg.fY = ( 0 != ( n & 0x02 ) ); 1316 | reg.fX = ( 0 != ( n & 0x08 ) ); 1317 | reg.fCarry = oldCarry; // carry is not affected 1318 | } 1319 | else if ( 0xb0 == op2 ) // ldir 1320 | { 1321 | cycles = 0; 1322 | do 1323 | { 1324 | cycles += 21; 1325 | memory[ reg.D() ] = memory[ reg.H() ]; 1326 | reg.fY = ( 0 != ( ( memory[ reg.H() ] + reg.a ) & 0x02 ) ); 1327 | reg.fX = ( 0 != ( ( memory[ reg.H() ] + reg.a ) & 0x08 ) ); 1328 | reg.SetH( reg.H() + 1 ); 1329 | reg.SetD( reg.D() + 1 ); 1330 | reg.SetB( reg.B() - 1 ); 1331 | } while ( 0 != reg.B() ); 1332 | 1333 | cycles -= 5; // the last iteration is cheaper 1334 | reg.fParityEven_Overflow = false; 1335 | reg.fAuxCarry = 0; 1336 | reg.fWasSubtract = 0; 1337 | } 1338 | else if ( 0xb1 == op2 ) // cpir 1339 | { 1340 | bool oldCarry = reg.fCarry; 1341 | cycles = 0; 1342 | do 1343 | { 1344 | cycles += 21; 1345 | uint8_t memval = memory[ reg.H() ]; 1346 | op_cmp( memval ); 1347 | uint8_t n = reg.a - memval - ( reg.fAuxCarry ? 1 : 0 ); // n = A - (HL) - HF 1348 | reg.fY = ( 0 != ( n & 0x02 ) ); 1349 | reg.fX = ( 0 != ( n & 0x08 ) ); 1350 | reg.SetH( reg.H() + 1 ); 1351 | reg.SetB( reg.B() - 1 ); 1352 | } while ( !reg.fZero && ( 0 != reg.B() ) ); 1353 | 1354 | cycles -= 5; // the last iteration is cheaper 1355 | reg.fParityEven_Overflow = ( 0 != reg.B() ); // not what the Zilog doc says, but it's what works 1356 | reg.fCarry = oldCarry; // carry is not affected 1357 | } 1358 | else if ( 0xb8 == op2 ) // lddr 1359 | { 1360 | cycles = 0; 1361 | do 1362 | { 1363 | cycles += 21; 1364 | memory[ reg.D() ] = memory[ reg.H() ]; 1365 | reg.fY = ( 0 != ( ( memory[ reg.H() ] + reg.a ) & 0x02 ) ); 1366 | reg.fX = ( 0 != ( ( memory[ reg.H() ] + reg.a ) & 0x08 ) ); 1367 | reg.SetH( reg.H() - 1 ); 1368 | reg.SetD( reg.D() - 1 ); 1369 | reg.SetB( reg.B() - 1 ); 1370 | } while ( 0 != reg.B() ); 1371 | 1372 | cycles -= 5; // the last iteration is cheaper 1373 | reg.fParityEven_Overflow = false; // unlike similar functions 1374 | reg.clearHN(); 1375 | } 1376 | else if ( 0xb9 == op2 ) // cpdr 1377 | { 1378 | bool oldCarry = reg.fCarry; 1379 | cycles = 0; 1380 | do 1381 | { 1382 | cycles += 21; 1383 | uint8_t memval = memory[ reg.H() ]; 1384 | op_cmp( memval ); 1385 | uint8_t n = reg.a - memval - ( reg.fAuxCarry ? 1 : 0 ); // n = A - (HL) - HF 1386 | reg.fY = ( 0 != ( n & 0x02 ) ); 1387 | reg.fX = ( 0 != ( n & 0x08 ) ); 1388 | reg.SetH( reg.H() - 1 ); 1389 | reg.SetB( reg.B() - 1 ); 1390 | } while ( !reg.fZero && ( 0 != reg.B() ) ); 1391 | 1392 | cycles -= 5; // the last iteration is cheaper 1393 | reg.fParityEven_Overflow = ( 0 != reg.B() ); 1394 | reg.fCarry = oldCarry; // carry is not affected 1395 | } 1396 | else if ( 0x02 == ( op2 & 0x8f ) ) // sbc hl, rp AKA sbc hl, ss 1397 | { 1398 | cycles = 15; 1399 | uint16_t val = * reg.rpAddressFromOp( op2 ); 1400 | reg.SetH( z80_op_sub_16( reg.H(), val, reg.fCarry ) ); 1401 | } 1402 | else if ( 0x04 == ( op2 & 0x8f ) ) // adc hl, rp AKA sbc hl, ss 1403 | { 1404 | cycles = 15; 1405 | uint16_t val = * reg.rpAddressFromOp( op2 ); 1406 | reg.SetH( z80_op_adc_16( reg.H(), val ) ); 1407 | } 1408 | else 1409 | z80_ni( op, op2 ); 1410 | break; 1411 | } 1412 | default: 1413 | z80_ni( op, op2 ); 1414 | } 1415 | 1416 | return cycles; 1417 | } //z80_emulate 1418 | 1419 | void z80_renderByteReg( char * acfrom, uint8_t op, uint8_t fromval ) 1420 | { 1421 | memset( acfrom, 0, 4 ); 1422 | assert( 0xdd == op || 0xfd == op ); 1423 | assert( fromval <= 7 ); 1424 | if ( fromval < 4 ) 1425 | acfrom[ 0 ] = 'b' + fromval; 1426 | else if ( 7 == fromval ) 1427 | acfrom[0] = 'a'; 1428 | else 1429 | snprintf( acfrom, 4, "%s%c", 0xdd == op ? "ix" : "iy", 4 == fromval ? 'h' : 'l' ); 1430 | } //z80_renderByteReg 1431 | 1432 | void z80_render( char * ac, size_t bufferSize, uint8_t op, uint16_t address ) 1433 | { 1434 | uint8_t op2 = memory[ address + 1 ]; 1435 | uint8_t op3 = memory[ address + 2 ]; 1436 | uint8_t op4 = memory[ address + 3 ]; 1437 | uint16_t op34 = (uint16_t) op3 + ( (uint16_t) op4 << 8 ); 1438 | int16_t op2int = (int16_t) (int8_t) op2; 1439 | int16_t op3int = (int16_t) (int8_t) op3; 1440 | snprintf( ac, bufferSize, "z80 %02x %02x %02x NYI", op, op2, op3 ); 1441 | 1442 | if ( 0xdd == op || 0xfd == op ) // ix & iy operations 1443 | { 1444 | const char * i = ( 0xdd == op ) ? "ix" : "iy"; 1445 | 1446 | if ( 0x46 == ( op2 & 0x47 ) ) 1447 | { 1448 | uint8_t src = ( ( op2 >> 3 ) & 0x7 ); 1449 | snprintf( ac, bufferSize, "ld %s, (%s%s%d)", reg_strings[ src ], i, op3int >= 0 ? "+" : "", op3int ); 1450 | } 1451 | else if ( 0x70 == ( op2 & 0xf8 ) ) 1452 | { 1453 | uint8_t src = op2 & 0x7; 1454 | if ( 6 == src ) 1455 | snprintf( ac, bufferSize, "ld (%s%s%d), %02x", i, op3int >= 0 ? "+" : "", op3int, op4 ); 1456 | else 1457 | snprintf( ac, bufferSize, "ld (%s%s%d), %s", i, op3int >= 0 ? "+" : "", op3int, reg_strings[ src ] ); 1458 | } 1459 | else if ( 0x09 == ( op2 & 0xcf ) ) 1460 | snprintf( ac, bufferSize, "add %s, %s", i, rp_strings[ ( op2 >> 4 ) & 0x3 ] ); 1461 | else if ( 0x21 == op2 ) 1462 | snprintf( ac, bufferSize, "ld %s, %04xh", i, op34 ); 1463 | else if ( 0x22 == op2 ) 1464 | snprintf( ac, bufferSize, "ld (%04xh), %s", op34, i ); 1465 | else if ( 0x23 == op2 ) 1466 | snprintf( ac, bufferSize, "inc %s", i ); 1467 | else if ( 0x26 == op2 ) 1468 | snprintf( ac, bufferSize, "ld %sh, %02x", i, op3 ); 1469 | else if ( 0x2a == op2 ) 1470 | snprintf( ac, bufferSize, "ld %s, (%04xh)", i, op34 ); 1471 | else if ( 0x2b == op2 ) 1472 | snprintf( ac, bufferSize, "dec %s", i ); 1473 | else if ( 0x2e == op2 ) 1474 | snprintf( ac, bufferSize, "ld %sl, %02x", i, op3 ); 1475 | else if ( 0x34 == op2 ) 1476 | snprintf( ac, bufferSize, "inc (%s%s%d)", i, op3int >= 0 ? "+" : "", op3int ); 1477 | else if ( 0x35 == op2 ) 1478 | snprintf( ac, bufferSize, "dec (%s%s%d)", i, op3int >= 0 ? "+" : "", op3int ); 1479 | else if ( 0x36 == op2 ) 1480 | snprintf( ac, bufferSize, "ld (%s%s%d), %02xh", i, op3int >= 0 ? "+" : "", op3int, op4 ); 1481 | else if ( ( op2 >= 0x40 && op2 <= 0x6f ) || ( op2 >= 0x78 && op2 <= 0x7f ) ) 1482 | { 1483 | char acto[ 4 ] = {0}; 1484 | char acfrom[ 4 ] = {0}; 1485 | uint8_t fromval = op2 & 0xf; 1486 | if ( fromval >= 8 ) 1487 | fromval -= 8; 1488 | z80_renderByteReg( acfrom, op, fromval ); 1489 | 1490 | if ( op2 <= 0x47 ) 1491 | acto[0] = 'b'; 1492 | else if ( op2 <= 0x4f ) 1493 | acto[0] = 'c'; 1494 | else if ( op2 <= 0x57 ) 1495 | acto[0] = 'd'; 1496 | else if ( op2 <= 0x5f ) 1497 | acto[0] = 'e'; 1498 | else if ( op2 <= 0x67 ) 1499 | snprintf( acto, _countof( acto ), "%sh", i ); 1500 | else if ( op2 <= 0x6f ) 1501 | snprintf( acto, _countof( acto ), "%sl", i ); 1502 | else 1503 | acto[0] = 'a'; 1504 | 1505 | snprintf( ac, bufferSize, "ld %s, %s", acto, acfrom ); 1506 | } 1507 | else if ( 0x2a == op2 ) 1508 | snprintf( ac, bufferSize, "ld %s, (%04x)", i, op34 ); 1509 | else if ( 0x22 == op2 ) 1510 | snprintf( ac, bufferSize, "ld (%04x), %s", op34, i ); 1511 | else if ( 0x80 == ( op2 & 0xc2 ) ) // math on il and ih with a. 84/85/8c/8d/94/95/a4/a5/b4/b5/bc/bd 1512 | { 1513 | uint8_t math = ( ( op2 >> 3 ) & 0x7 ); 1514 | snprintf( ac, bufferSize, "%s a, %s%c", z80_math_strings[ math ], i, ( op2 & 1 ) ? 'l' : 'h' ); 1515 | } 1516 | else if ( 0x86 == ( op2 & 0xc7 ) ) // math on [ ix/iy + index ]. 86, 8e, 96, 9e, a6, ae, b6, be 1517 | { 1518 | uint8_t math = ( ( op2 >> 3 ) & 0x7 ); 1519 | snprintf( ac, bufferSize, "%s a, (%s%s%d)", z80_math_strings[ math ], i, op3int >=0 ? "+" : "", op3int ); 1520 | } 1521 | else if ( 0xcb == op2 && ( op4 <= 0x3f ) ) 1522 | { 1523 | uint8_t rot = ( ( op4 >> 3 ) & 0x7 ); 1524 | int offset = (int) (int8_t) op3; 1525 | snprintf( ac, bufferSize, "%s %s, (%s%s%d)", z80_rotate_strings[ rot ], reg_strings[ op4 & 0x7 ], i, offset >= 0 ? "+" : "", offset ); 1526 | } 1527 | else if ( 0xcb == op2 && ( ( ( op4 & 0xf ) == 0xe ) || ( ( op4 & 0xf ) == 0x6 ) ) ) // bit operations on indexed memory 1528 | { 1529 | uint8_t index = op3; 1530 | int32_t index32 = (int32_t) (int8_t) index; 1531 | uint8_t mod = op4; 1532 | uint8_t bit = ( mod >> 3 ) & 0x7; 1533 | uint8_t top2bits = mod & 0xc0; 1534 | 1535 | if ( 0x40 == top2bits ) // bit 1536 | snprintf( ac, bufferSize, "bit %u, (%s%s%d)", bit, i, index32 >= 0 ? "+" : "", index32 ); 1537 | else if ( 0x80 == top2bits ) // reset 1538 | snprintf( ac, bufferSize, "res %u, (%s%s%d)", bit, i, index32 >= 0 ? "+" : "", index32 ); 1539 | else if ( 0xc0 == top2bits ) // set 1540 | snprintf( ac, bufferSize, "set %u, (%s%s%d)", bit, i, index32 >= 0 ? "+" : "", index32 ); 1541 | else if ( 0x26 == op4 ) // sla 1542 | snprintf( ac, bufferSize, "sla (%s%s%d)", i, index32 >= 0 ? "+" : "", index32 ); 1543 | else if ( 0x2e == op4 ) // sra 1544 | snprintf( ac, bufferSize, "sra (%s%s%d)", i, index32 >= 0 ? "+" : "", index32 ); 1545 | else if ( 0x3e == op4 ) // srl 1546 | snprintf( ac, bufferSize, "srl (%s%s%d)", i, index32 >= 0 ? "+" : "", index32 ); 1547 | else if ( 0x00 == top2bits ) // rlc/rl/rrc/rl rotate of (i + index) 1548 | { 1549 | const char * pc = "n/a"; 1550 | if ( 0x06 == op4 ) 1551 | pc = "rlc"; 1552 | else if ( 0x0e == op4 ) 1553 | pc = "rrc"; 1554 | else if ( 0x16 == op4 ) 1555 | pc = "rl"; 1556 | else if ( 0x1e == op4 ) 1557 | pc = "rr"; 1558 | snprintf( ac, bufferSize, "%s (%s%s%d)", pc, i, index32 >= 0 ? "+" : "", index32 ); 1559 | } 1560 | } 1561 | else if ( 0xe1 == op2 ) 1562 | snprintf( ac, bufferSize, "pop %s", i ); 1563 | else if ( 0xe3 == op2 ) 1564 | snprintf( ac, bufferSize, "ex (sp), %s", i ); 1565 | else if ( 0xe5 == op2 ) 1566 | snprintf( ac, bufferSize, "push %s", i ); 1567 | else if ( 0xe9 == op2 ) 1568 | snprintf( ac, bufferSize, "jp (%s)", i ); // this official syntax is bad; it's not indirect 1569 | else if ( 0xf9 == op2 ) 1570 | snprintf( ac, bufferSize, "ld sp, %s", i ); 1571 | else 1572 | snprintf( ac, bufferSize, "unknown 0xdd / 0xfd instruction, op2 %2x", op2 ); 1573 | } 1574 | else if ( 0xed == op ) // load and compare operations 1575 | { 1576 | if ( 0xb == ( op2 & 0xf ) ) 1577 | snprintf( ac, bufferSize, "ld %s, (%04xh)", rp_strings[ ( ( op2 & 0xf0 ) >> 4 ) - 4 ], op34 ); 1578 | else if ( 0x3 == ( op2 & 0xf ) ) 1579 | snprintf( ac, bufferSize, "ld (%04xh), %s", op34, rp_strings[ ( ( op2 & 0xf0 ) >> 4 ) - 4 ] ); 1580 | else if ( 0x44 == op2 ) 1581 | strcpy( ac, "neg" ); 1582 | else if ( 0x47 == op2 ) 1583 | snprintf( ac, bufferSize, "ld i, a" ); 1584 | else if ( 0x4f == op2 ) 1585 | snprintf( ac, bufferSize, "ld r, a" ); 1586 | else if ( 0x57 == op2 ) 1587 | snprintf( ac, bufferSize, "ld a, i" ); 1588 | else if ( 0x5f == op2 ) 1589 | snprintf( ac, bufferSize, "ld a, r" ); 1590 | else if ( 0x67 == op2 ) 1591 | strcpy( ac, "rrd" ); 1592 | else if ( 0x6f == op2 ) 1593 | strcpy( ac, "rld" ); 1594 | else if ( 0xa0 == op2 ) 1595 | strcpy( ac, "ldi" ); 1596 | else if ( 0xa8 == op2 ) 1597 | strcpy( ac, "ldd" ); 1598 | else if ( 0xb0 == op2 ) 1599 | strcpy( ac, "ldir" ); 1600 | else if ( 0xb8 == op2 ) 1601 | strcpy( ac, "lddr" ); 1602 | else if ( 0x02 == ( op2 & 0x8f ) ) 1603 | { 1604 | uint8_t rp = ( ( ( op2 & 0x30 ) >> 4 ) & 3 ); 1605 | snprintf( ac, bufferSize, "sbc hl, %s", rp_strings[ rp ] ); 1606 | } 1607 | else if ( 0x4a == ( op2 & 0xcf ) ) // adc hl, rp 1608 | { 1609 | uint8_t rp = ( ( op2 >> 4 ) & 3 ); 1610 | snprintf( ac, bufferSize, "adc hl, %s", rp_strings[ rp ] ); 1611 | } 1612 | else if ( 0xa1 == op2 ) 1613 | strcpy( ac, "cpi" ); 1614 | else if ( 0xa9 == op2 ) 1615 | strcpy( ac, "cpd" ); 1616 | else if ( 0xb1 == op2 ) 1617 | strcpy( ac, "cpir" ); 1618 | else if ( 0xb9 == op2 ) 1619 | strcpy( ac, "cpdr" ); 1620 | } 1621 | else if ( 0xcb == op ) // rotate / bits. There is no sll in documented Z80 1622 | { 1623 | if ( 0x20 == ( op2 & 0xf8 ) ) // sla = shift left arithmetic 1624 | { 1625 | uint8_t rm = op2 & 0x7; 1626 | snprintf( ac, bufferSize, "sla %s", reg_strings[ rm ] ); 1627 | } 1628 | else if ( 0x28 == ( op2 & 0xf8 ) ) // sra = shift right arithmetic 1629 | { 1630 | uint8_t rm = op2 & 0x7; 1631 | snprintf( ac, bufferSize, "sra %s", reg_strings[ rm ] ); 1632 | } 1633 | else if ( op2 >= 0x30 && op2 <= 0x3f ) 1634 | { 1635 | uint8_t rm = op2 & 0x7; 1636 | if ( op2 <= 0x37 ) 1637 | snprintf( ac, bufferSize, "sll %s", reg_strings[ rm ] ); 1638 | else 1639 | snprintf( ac, bufferSize, "srl %s", reg_strings[ rm ] ); 1640 | } 1641 | else if ( 0x38 == ( op2 & 0xf8 ) ) // srl = shift right logical 1642 | { 1643 | uint8_t rm = op2 >> 4; 1644 | snprintf( ac, bufferSize, "srl %s", reg_strings[ rm ] ); 1645 | } 1646 | else if ( op2 <= 0x1f ) // rlc, rl, rrc, rr on rm 1647 | { 1648 | const char * rotateType = "rlc"; 1649 | if ( op2 >= 0x10 && op2 <= 0x17 ) 1650 | rotateType = "rl"; 1651 | else if ( op2 >= 0x08 && op2 <= 0x0f ) 1652 | rotateType = "rrc"; 1653 | else if ( op2 >= 0x18 && op2 <= 0x1f ) 1654 | rotateType = "rr"; 1655 | 1656 | uint8_t rm = ( op2 & 0x7 ); 1657 | snprintf( ac, bufferSize, "%s %s", rotateType, reg_strings[ rm ] ); 1658 | } 1659 | else if ( op2 >= 0x40 && op2 <= 0x7f ) // bit #, rm 1660 | { 1661 | uint8_t rm = op2 & 0x7; 1662 | uint8_t bit = ( op2 >> 3 ) & 0x7; 1663 | snprintf( ac, bufferSize, "bit %u, %s", bit, reg_strings[ rm ] ); 1664 | } 1665 | else if ( op2 >= 0x80 && op2 <= 0xbf ) // res bit #, rm 1666 | { 1667 | uint8_t rm = op2 & 0x7; 1668 | uint8_t bit = ( op2 >> 3 ) & 0x7; 1669 | snprintf( ac, bufferSize, "res %u, %s", bit, reg_strings[ rm ] ); 1670 | } 1671 | else if ( op2 >= 0xc0 && op2 <= 0xff ) // set bit #, rm 1672 | { 1673 | uint8_t rm = op2 & 0x7; 1674 | uint8_t bit = ( op2 >> 3 ) & 0x7; 1675 | snprintf( ac, bufferSize, "set %u, %s", bit, reg_strings[ rm ] ); 1676 | } 1677 | } 1678 | else if ( 0x08 == op ) 1679 | strcpy( ac, "ex af,af'" ); 1680 | else if ( 0x10 == op ) 1681 | snprintf( ac, bufferSize, "djnz %d", op2int ); 1682 | else if ( 0x18 == op ) 1683 | snprintf( ac, bufferSize, "jr %d", op2int ); 1684 | else if ( 0x20 == op ) 1685 | snprintf( ac, bufferSize, "jr nz, %d", op2int ); 1686 | else if ( 0x28 == op ) 1687 | snprintf( ac, bufferSize, "jr z, %d", op2int ); 1688 | else if ( 0x30 == op ) 1689 | snprintf( ac, bufferSize, "jr nc, %d", op2int ); 1690 | else if ( 0x38 == op ) 1691 | snprintf( ac, bufferSize, "jr c, %d", op2int ); 1692 | else if ( 0xd9 == op ) 1693 | strcpy( ac, "exx" ); 1694 | else 1695 | strcpy( ac, "unknown instruction" ); 1696 | } //z80_render 1697 | 1698 | void replace_with_num( char * pc, const char * psearch, uint16_t num, uint8_t width ) 1699 | { 1700 | char actemp[ 60 ]; 1701 | snprintf( actemp, _countof( actemp ), ( 16 == width ) ? "%04xh" : "%02xh", (uint32_t) num ); 1702 | strcat( actemp, pc + strlen( psearch ) ); 1703 | strcpy( pc, actemp ); 1704 | } //replace_with_num 1705 | 1706 | const char * x80_render_operation( uint16_t address ) 1707 | { 1708 | static char ac[ 60 ]; 1709 | uint8_t op = memory[ address ]; 1710 | bool renderData = true; 1711 | 1712 | if ( reg.fZ80Mode ) 1713 | { 1714 | strcpy( ac, z80_instructions[ op ] ); 1715 | if ( '*' == ac[ 0 ] ) 1716 | { 1717 | z80_render( ac, _countof( ac ), op, address ); 1718 | renderData = false; 1719 | } 1720 | } 1721 | else 1722 | strcpy( ac, i8080_instructions[ op ] ); 1723 | 1724 | if ( renderData ) 1725 | { 1726 | char * p; 1727 | if ( ( p = strstr( ac, "d16" ) ) ) 1728 | replace_with_num( p, "d16", mword( address + 1 ), 16 ); 1729 | else if ( ( p = strstr( ac, "a16" ) ) ) 1730 | replace_with_num( p, "a16", mword( address + 1 ), 16 ); 1731 | else if ( ( p = strstr( ac, "d8" ) ) ) 1732 | replace_with_num( p, "d8", memory[ address + 1 ], 8 ); 1733 | } 1734 | 1735 | return ac; 1736 | } //x80_render_operation 1737 | 1738 | not_inlined void x80_trace_state() 1739 | { 1740 | if ( !tracer.IsEnabled() ) // trace instructions may be on but global tracing turned off 1741 | return; 1742 | 1743 | uint8_t op = memory[ reg.pc ]; 1744 | uint8_t op2 = memory[ reg.pc + 1 ]; 1745 | uint8_t op3 = memory[ reg.pc + 2 ]; 1746 | uint8_t op4 = memory[ reg.pc + 3 ]; 1747 | 1748 | if ( reg.fZ80Mode ) 1749 | tracer.Trace( "pc %04x, op %02x, op2 %02x, op3 %02x, op4 %02x, a %02x, B %04x, D %04x, H %04x, ix %04x, iy %04x, sp %04x, %s, %s\n", 1750 | reg.pc, op, op2, op3, op4, reg.a, reg.B(), reg.D(), reg.H(), reg.ix, reg.iy, reg.sp, 1751 | reg.renderFlags(), x80_render_operation( reg.pc ) ); 1752 | else 1753 | tracer.Trace( "pc %04x, op %02x, op2 %02x, op3 %02x, a %02x, B %04x, D %04x, H %04x, sp %04x, %s, %s\n", 1754 | reg.pc, op, op2, op3, reg.a, reg.B(), reg.D(), reg.H(), reg.sp, 1755 | reg.renderFlags(), x80_render_operation( reg.pc ) ); 1756 | 1757 | //tracer.TraceBinaryData( & memory[ 0x1142 ], 16, 4 ); 1758 | } //x80_trace_state 1759 | 1760 | bool check_conditional( uint8_t op ) // checks for conditional jump, call, and return 1761 | { 1762 | bool condition = reg.getFlag( ( op >> 4 ) & 3 ); 1763 | if ( ( 0 == ( op & 8 ) ) ) 1764 | condition = !condition; 1765 | 1766 | return condition; 1767 | } //check_conditional 1768 | 1769 | not_inlined bool handle_state() // this code exists to reduce what would be multiple checks per instruction loop to just one check 1770 | { 1771 | if ( g_State & stateEndEmulation ) 1772 | return true; 1773 | 1774 | if ( g_State & stateTraceInstructions ) 1775 | x80_trace_state(); 1776 | 1777 | return false; 1778 | } //handle_state 1779 | 1780 | uint16_t x80_emulate( uint16_t maxcycles ) 1781 | { 1782 | uint16_t cycles = 0; 1783 | const acycles_t & acycles = reg.fZ80Mode ? z80_cycles : i8080_cycles; // ms C++ will opportunistically read *both* in the loop below otherwise 1784 | 1785 | // with the Watcom compiler for real-mode DOS, the cycle check, cycle addition, and trace check consume 17% of runtime 1786 | 1787 | while ( cycles < maxcycles ) // 4% of runtime checking if we're done 1788 | { 1789 | if ( 0 != g_State ) 1790 | if ( handle_state() ) 1791 | break; 1792 | 1793 | uint8_t op = memory[ reg.pc ]; // 1% of runtime 1794 | reg.pc++; // 7% of runtime 1795 | cycles += acycles[ op ]; 1796 | 1797 | switch ( op ) // 50% of runtime is completing cycle addition & setting up for the jump table jump 1798 | { 1799 | case 0x00: break; // nop 1800 | case 0x01: case 0x11: case 0x21: case 0x31: { * reg.rpAddressFromLowOp( op ) = pcword(); break; } // lxi rp, d16 1801 | case 0x02: { memory[ reg.B() ] = reg.a; break; } // stax b 1802 | case 0x03: case 0x13: case 0x23: case 0x33: // inx. no status flag updates 1803 | { 1804 | uint16_t * pdst = reg.rpAddressFromLowOp( op ); 1805 | *pdst = 1 + *pdst; 1806 | break; 1807 | } 1808 | case 0x04: case 0x14: case 0x24: case 0x34: case 0x0c: case 0x1c: case 0x2c: case 0x3c: // inr rm. does not set carry 1809 | { 1810 | uint8_t * pdst = dst_address( op ); 1811 | *pdst = op_inc( *pdst ); 1812 | break; 1813 | } 1814 | case 0x05: case 0x15: case 0x25: case 0x35: case 0x0d: case 0x1d: case 0x2d: case 0x3d: // dcr rm. does not set carry 1815 | { 1816 | uint8_t * pdst = dst_address( op ); 1817 | *pdst = op_dec( * pdst ); // 5% of runtime 1818 | break; 1819 | } 1820 | case 0x06: case 0x16: case 0x26: case 0x36: case 0x0e: case 0x1e: case 0x2e: case 0x3e: // mvi rm, d8 1821 | { 1822 | * dst_address( op ) = pcbyte(); 1823 | break; 1824 | } 1825 | case 0x07: // rlc 1826 | { 1827 | reg.fCarry = ( 0 != ( reg.a & 0x80 ) ); 1828 | reg.a <<= 1; 1829 | if ( reg.fCarry ) 1830 | reg.a |= 1; 1831 | if ( reg.fZ80Mode ) 1832 | { 1833 | reg.clearHN(); 1834 | reg.z80_assignYX( reg.a ); 1835 | } 1836 | break; 1837 | } 1838 | case 0x09: case 0x19: case 0x29: case 0x39: { op_dad( * reg.rpAddressFromOp( op ) ); break; } // dad 1839 | case 0x0a: { reg.a = memory[ reg.B() ]; break; } // ldax b 1840 | case 0x0b: case 0x1b: case 0x2b: case 0x3b: // dcx. no status flag updates 1841 | { 1842 | uint16_t * pdst = reg.rpAddressFromOp( op ); 1843 | *pdst = *pdst - 1; 1844 | break; 1845 | } 1846 | case 0x0f: // rrc 1847 | { 1848 | reg.fCarry = ( 0 != ( reg.a & 1 ) ); 1849 | reg.a >>= 1; 1850 | if ( reg.fCarry ) 1851 | reg.a |= 0x80; 1852 | if ( reg.fZ80Mode ) 1853 | { 1854 | reg.clearHN(); 1855 | reg.z80_assignYX( reg.a ); 1856 | } 1857 | break; 1858 | } 1859 | case 0x12: { memory[ reg.D() ] = reg.a; break; } // stax d 1860 | case 0x17: // ral 1861 | { 1862 | bool c = reg.fCarry; 1863 | reg.fCarry = ( 0 != ( 0x80 & reg.a ) ); 1864 | reg.a <<= 1; 1865 | if ( c ) 1866 | reg.a |= 1; 1867 | if ( reg.fZ80Mode ) 1868 | { 1869 | reg.clearHN(); 1870 | reg.z80_assignYX( reg.a ); 1871 | } 1872 | break; 1873 | } 1874 | case 0x1a: { reg.a = memory[ reg.D() ]; break; } // ldax d 1875 | case 0x1f: // rar 1876 | { 1877 | bool c = reg.fCarry; 1878 | reg.fCarry = ( 0 != ( reg.a & 1 ) ); 1879 | reg.a >>= 1; 1880 | if ( c ) 1881 | reg.a |= 0x80; 1882 | if ( reg.fZ80Mode ) 1883 | { 1884 | reg.clearHN(); 1885 | reg.z80_assignYX( reg.a ); 1886 | } 1887 | break; 1888 | } 1889 | case 0x22: { setmword( pcword(), reg.H() ); break; } // shld 1890 | case 0x27: { op_daa(); break; } // daa 1891 | case 0x2a: { reg.SetH( mword( pcword() ) ); break; } // lhld 1892 | case 0x2f: { op_cma(); break; } // cma 1893 | case 0x32: { memory[ pcword() ] = reg.a; break; } // sta a16 1894 | case 0x37: // stc 1895 | { 1896 | reg.fCarry = 1; 1897 | if ( reg.fZ80Mode ) 1898 | { 1899 | reg.clearHN(); 1900 | reg.z80_assignYX( reg.a ); 1901 | } 1902 | break; 1903 | } 1904 | case 0x3a: { reg.a = memory[ pcword() ]; break; } // lda a16 1905 | case 0x3f: { op_cmc(); break; } // cmc 1906 | case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: 1907 | case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f: 1908 | case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: 1909 | case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: 1910 | case 0x60: case 0x61: case 0x62: case 0x63: case 0x65: case 0x66: case 0x67: 1911 | case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f: 1912 | case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x77: 1913 | case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: 1914 | { 1915 | * dst_address( op ) = src_value( op ); 1916 | break; 1917 | } 1918 | case 0x64: // hook 1919 | { 1920 | op = x80_invoke_hook(); 1921 | cycles += acycles[ op ]; 1922 | if ( OPCODE_HLT == op ) // treat each possible return opcode separately to avoid a jump to restart for performance 1923 | goto _op_hlt; 1924 | else if ( OPCODE_NOP == op ) 1925 | break; 1926 | else if ( OPCODE_RET == op ) 1927 | goto _op_ret; 1928 | else 1929 | assert( false ); 1930 | } 1931 | case 0x76: { _op_hlt: x80_invoke_halt(); goto _all_done; } // hlt 1932 | case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: { op_add( src_value( op ) ); break; } 1933 | case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: { op_adc( src_value( op ) ); break; } 1934 | case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: { reg.a = op_sub( src_value( op ) ); break; } 1935 | case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: { op_sbb( src_value( op ) ); break; } 1936 | case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: { op_ana( src_value( op ) ); break; } 1937 | case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf: { op_xra( src_value( op ) ); break; } 1938 | case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: { op_ora( src_value( op ) ); break; } 1939 | case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: { op_cmp( src_value( op ) ); break; } 1940 | case 0xc0: case 0xd0: case 0xe0: case 0xf0: case 0xc8: case 0xd8: case 0xe8: case 0xf8: // conditional return 1941 | { 1942 | if ( check_conditional( op ) ) 1943 | reg.pc = popword(); 1944 | else 1945 | cycles -= cyclesnt; 1946 | break; 1947 | } 1948 | case 0xc1: case 0xd1: case 0xe1: { * reg.rpAddressFromOp( op ) = popword(); break; } // pop rp 1949 | case 0xc2: case 0xd2: case 0xe2: case 0xf2: case 0xca: case 0xda: case 0xea: case 0xfa: // conditional jmp 1950 | { 1951 | uint16_t address = pcword(); // must be consumed regardless of whether jump is taken 1952 | if ( check_conditional( op ) ) 1953 | reg.pc = address; 1954 | else 1955 | cycles -= cyclesnt; 1956 | break; 1957 | } 1958 | case 0xc3: { reg.pc = pcword(); break; } // jmp a16 1959 | case 0xc4: case 0xd4: case 0xe4: case 0xf4: case 0xcc: case 0xdc: case 0xec: case 0xfc: // conditional call 1960 | { 1961 | uint16_t address = pcword(); // must be consumed regardless of whether call is taken 1962 | if ( check_conditional( op ) ) 1963 | { 1964 | pushword( reg.pc ); 1965 | reg.pc = address; 1966 | } 1967 | else 1968 | cycles -= cyclesnt; 1969 | break; 1970 | } 1971 | case 0xc5: case 0xd5: case 0xe5: { pushword( * reg.rpAddressFromOp( op ) ); break; } // push rp 1972 | case 0xc6: { op_add( pcbyte() ); break; } // adi 1973 | case 0xc7: case 0xd7: case 0xe7: case 0xf7: case 0xcf: case 0xdf: case 0xef: case 0xff: // rst 1974 | { 1975 | // bits 5..3 are exp, which form an address 0000000000exp000 that is called. 1976 | // rst is generally invoked by hardware interrupts, which supply the one instruction rst. 1977 | 1978 | pushword( reg.pc ); 1979 | reg.pc = 0x38 & (uint16_t) op; 1980 | break; 1981 | } 1982 | case 0xc9: { _op_ret: reg.pc = popword(); break; } // ret 1983 | case 0xcd: { uint16_t t = pcword(); pushword( reg.pc ); reg.pc = t; break; } // call a16 1984 | case 0xce: { op_adc( pcbyte() ); break; } // aci 1985 | case 0xd3: { x80_invoke_out( pcbyte() ); break; } // out d8 1986 | case 0xd6: { reg.a = op_sub( pcbyte() ); break; } // sui 1987 | case 0xdb: { x80_invoke_in( pcbyte() ); break; } // in d8 1988 | case 0xde: { op_sbb( pcbyte() ); break; } // sbi 1989 | case 0xe3: { uint16_t t = reg.H(); reg.SetH( mword( reg.sp ) ); setmword( reg.sp, t ); break; } // xthl 1990 | case 0xe6: { op_ana( pcbyte() ); break; } // ani 1991 | case 0xe9: { reg.pc = reg.H(); break; } // pchl 1992 | case 0xeb: { uint16_t t = reg.H(); reg.SetH( reg.D() ); reg.SetD( t ); break; } // xchg 1993 | case 0xee: { op_xra( pcbyte() ); break; } // xri 1994 | case 0xf1: { reg.SetPSW( popword() ); break; } // pop psw 1995 | case 0xf3: { reg.fINTE = false; break; } // di 1996 | case 0xf5: { pushword( reg.PSW() ); break; } // push psw 1997 | case 0xf6: { op_ora( pcbyte() ); break; } // ori 1998 | case 0xf9: { reg.sp = reg.H(); break; } // sphl 1999 | case 0xfb: { reg.fINTE = true; break; } // ei 2000 | case 0xfe: { op_cmp( pcbyte() ); break; } // cpi 2001 | default: 2002 | { 2003 | if ( reg.fZ80Mode ) 2004 | cycles += z80_emulate( op ); 2005 | else if ( 0x08 == op ) // Pascal MT++ generates 8080 apps that use 0x08. Treat it as a NOP just like an 8080. 2006 | break; 2007 | else 2008 | x80_hard_exit( "Error: 8080 undocumented instruction: %#x, next byte %#x\n", op, memory[ reg.pc + 1 ] ); 2009 | } //default 2010 | } //switch 2011 | 2012 | reg.z80_increment_r(); // do this for 8080 too to avoid the 'if' statement 2013 | } //while 2014 | _all_done: 2015 | return cycles; 2016 | } //x80_emulate 2017 | -------------------------------------------------------------------------------- /x80.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // i8080 and Z80 emulator 4 | 5 | #define OPCODE_HOOK 0x64 // mov h, h. When executed, x80_invoke_hook is called 6 | #define OPCODE_HLT 0x76 // for ending execution. When executed, x80_invoke_halt is called 7 | #define OPCODE_NOP 0x00 8 | #define OPCODE_JMP 0xC3 // jmp nn 9 | #define OPCODE_RET 0xC9 10 | 11 | #ifdef TARGET_BIG_ENDIAN 12 | const size_t reg_offsets[8] = { 0, 1, 2, 3, 4, 5, 9, 8 }; //bcdehlfa 13 | #else 14 | const size_t reg_offsets[8] = { 1, 0, 3, 2, 5, 4, 8, 9 }; //bcdehlfa 15 | #endif 16 | 17 | struct registers 18 | { 19 | // the memory layout of these registers is assumed by the code below 20 | 21 | #ifdef TARGET_BIG_ENDIAN 22 | uint8_t b, c; 23 | uint8_t d, e; 24 | uint8_t h, l; 25 | uint16_t sp; 26 | uint8_t a, f; 27 | #else 28 | uint8_t c, b; 29 | uint8_t e, d; 30 | uint8_t l, h; 31 | uint16_t sp; 32 | uint8_t f, a; 33 | #endif 34 | uint16_t pc; 35 | 36 | // Z80-specific. p == prime registers swapped via exchange instructions 37 | 38 | uint8_t cp, bp; 39 | uint8_t ep, dp; 40 | uint8_t lp, hp; 41 | uint8_t fp, ap; 42 | 43 | uint16_t ix; 44 | uint16_t iy; 45 | uint8_t r; // refresh 46 | uint8_t i; // interrupt page 47 | 48 | // these first four bool flags must remain in this order for getFlag() to work 49 | 50 | bool fZero; // Z zero 51 | bool fCarry; // C carry 52 | bool fParityEven_Overflow; // P/V. (Overflow) on the Z80. 53 | bool fSign; // S negative; < 0 54 | bool fAuxCarry; // A/H. aka half-carry for the Z80 55 | bool fWasSubtract; // N. Z80-specific. Flag for BCD math (DAA). true for subtract, false for add. 56 | bool fY; // defined to be 0, but physical Z80s and 8085s sometimes set to 1 57 | bool fX; // defined to be 0, but physical Z80s and 8085s sometimes set to 1 58 | 59 | bool fZ80Mode; // Not a cpu flag. true if emulating Z80, false if emulating i8080 60 | bool fINTE; // Not a cpu flag. true if hardware interrupts are enabled. set/reset with ei and di 61 | 62 | uint8_t materializeFlags() 63 | { 64 | // flags bits 7..0 (where they differ, 8080/Z80): 65 | // sign, zero, mustbe0, auxcarry/halfcarry, mustbe0, parityeven/overflow, mustbe1/subtract, carry 66 | 67 | f = fZ80Mode ? ( fWasSubtract ? 2 : 0 ) : 2; 68 | if ( fCarry ) f |= 1; 69 | if ( fParityEven_Overflow ) f |= 4; 70 | if ( fAuxCarry ) f |= 0x10; 71 | if ( fZero ) f |= 0x40; 72 | if ( fSign ) f |= 0x80; 73 | 74 | if ( fZ80Mode ) // these flags are undocumented but must work to run emulation tests 75 | { 76 | if ( fY ) f |= 0x20; 77 | if ( fX ) f |= 8; 78 | } 79 | 80 | return f; 81 | } //materializeFlags 82 | 83 | uint16_t PSW() 84 | { 85 | materializeFlags(); 86 | #ifdef TARGET_BIG_ENDIAN 87 | return * ( (uint16_t *) & a ); 88 | #else 89 | return * ( (uint16_t *) & f ); 90 | #endif 91 | } //PSW 92 | 93 | void SetPSW( uint16_t x ) 94 | { 95 | #ifdef TARGET_BIG_ENDIAN 96 | * ( (uint16_t *) & a ) = x; 97 | #else 98 | * ( (uint16_t *) & f ) = x; 99 | #endif 100 | unmaterializeFlags(); 101 | } //SetPSW 102 | 103 | #ifdef TARGET_BIG_ENDIAN 104 | uint16_t B() const { return * ( (uint16_t *) & b ); } 105 | uint16_t D() const { return * ( (uint16_t *) & d ); } 106 | uint16_t H() const { return * ( (uint16_t *) & h ); } 107 | 108 | void SetB( uint16_t x ) { * ( (uint16_t *) & b ) = x; } 109 | void SetD( uint16_t x ) { * ( (uint16_t *) & d ) = x; } 110 | void SetH( uint16_t x ) { * ( (uint16_t *) & h ) = x; } 111 | #else 112 | uint16_t B() const { return * ( (uint16_t *) & c ); } 113 | uint16_t D() const { return * ( (uint16_t *) & e ); } 114 | uint16_t H() const { return * ( (uint16_t *) & l ); } 115 | 116 | void SetB( uint16_t x ) { * ( (uint16_t *) & c ) = x; } 117 | void SetD( uint16_t x ) { * ( (uint16_t *) & e ) = x; } 118 | void SetH( uint16_t x ) { * ( (uint16_t *) & l ) = x; } 119 | #endif 120 | 121 | void z80_increment_r() { /* reg.r++; */ } // 4.6% of runtime when the increment is enabled 122 | 123 | void unmaterializeFlags() 124 | { 125 | if ( fZ80Mode ) 126 | fWasSubtract = ( 0 != ( f & 2 ) ); 127 | else 128 | { 129 | f &= 0xd7; // set 0 bits 130 | f |= 0x2; // always 1 on 8080 131 | } 132 | 133 | fCarry = ( 0 != ( f & 1 ) ); 134 | fParityEven_Overflow = ( 0 != ( f & 4 ) ); 135 | fAuxCarry = ( 0 != ( f & 0x10 ) ); 136 | fZero = ( 0 != ( f & 0x40 ) ); 137 | fSign = ( 0 != ( f & 0x80 ) ); 138 | 139 | if ( fZ80Mode ) 140 | { 141 | fY = ( 0 != ( f & 0x20 ) ); 142 | fX = ( 0 != ( f & 8 ) ); 143 | } 144 | } //unmaterializeFlags 145 | 146 | uint16_t * rpAddress( uint8_t rp ) 147 | { 148 | // rp == register pair: 0 B, 1 D, 2 H, 3 SP 149 | assert( rp <= 3 ); 150 | return ( ( (uint16_t *) this ) + rp ); 151 | } //rpAddress 152 | 153 | uint16_t * rpAddressFromOp( uint8_t op ) 154 | { 155 | return (uint16_t *) ( ( (uint8_t *) this ) + ( ( op >> 3 ) & 6 ) ); 156 | } //rpAddressFromOp 157 | 158 | uint16_t * rpAddressFromLowOp( uint8_t op ) 159 | { 160 | assert( ! ( op & 0x88 ) ); // save masking with &6 if the high bits on each nibble are 0 161 | return (uint16_t *) ( ( (uint8_t *) this ) + ( op >> 3 ) ); 162 | } //rpAddressFromLowOp 163 | 164 | uint8_t * regOffset( uint8_t rm ) 165 | { 166 | assert( 6 != rm ); // that's a memory access, not a register. f can't be used directly. 167 | return ( ( ( uint8_t *) this ) + reg_offsets[ rm ] ); 168 | } //regOffset 169 | 170 | void clearHN() 171 | { 172 | fAuxCarry = false; 173 | fWasSubtract = false; 174 | } //clearHN 175 | 176 | bool getFlag( uint8_t x ) 177 | { 178 | assert( x <= 3 ); 179 | return * ( ( & fZero ) + x ); 180 | } //getFlag 181 | 182 | const char * renderFlags() 183 | { 184 | static char ac[10] = {0}; 185 | size_t next = 0; 186 | ac[ next++ ] = fSign ? 'S' : 's'; 187 | ac[ next++ ] = fZero ? 'Z' : 'z'; 188 | if ( fZ80Mode ) 189 | ac[ next++ ] = fY ? 'Y' : 'y'; 190 | 191 | if ( fZ80Mode ) 192 | ac[ next++ ] = fAuxCarry ? 'H' : 'h'; // half-carry; almost same meaning as aux-carry 193 | else 194 | ac[ next++ ] = fAuxCarry ? 'A' : 'a'; 195 | 196 | if ( fZ80Mode ) 197 | ac[ next++ ] = fX ? 'X' : 'x'; 198 | 199 | if ( fZ80Mode ) 200 | ac[ next++ ] = fParityEven_Overflow ? 'V' : 'v'; 201 | else 202 | ac[ next++ ] = fParityEven_Overflow ? 'P' : 'p'; 203 | 204 | if ( fZ80Mode ) 205 | ac[ next++ ] = fWasSubtract ? 'N' : 'n'; 206 | 207 | ac[ next++ ] = fCarry ? 'C' : 'c'; 208 | ac[ next ] = 0; 209 | 210 | return ac; 211 | } //renderFlags 212 | 213 | void z80_setIndex( uint8_t op, uint16_t val ) 214 | { 215 | if ( 0xdd == op ) 216 | ix = val; 217 | else 218 | iy = val; 219 | } //z80_setIndex 220 | 221 | uint16_t z80_getIndex( uint8_t op ) 222 | { 223 | assert( 0xdd == op || 0xfd == op ); 224 | 225 | if ( 0xdd == op ) 226 | return ix; 227 | return iy; 228 | } //z80_getIndex 229 | 230 | uint8_t z80_getIndexByte( uint8_t op, uint8_t hl ) 231 | { 232 | assert( 0xdd == op || 0xfd == op ); 233 | assert( 0 == hl || 1 == hl ); 234 | 235 | uint16_t result16 = z80_getIndex( op ); 236 | 237 | if ( 0 == hl ) 238 | return ( ( result16 >> 8 ) & 0xff ); 239 | return ( result16 & 0xff ); 240 | } //z80_getIndexByte 241 | 242 | uint8_t * z80_getIndexByteAddress( uint8_t op, uint8_t hl ) 243 | { 244 | assert( 0xdd == op || 0xfd == op ); 245 | assert( 0 == hl || 1 == hl ); 246 | 247 | uint8_t * pval; 248 | if ( 0xdd == op ) 249 | pval = (uint8_t *) & ix; 250 | else 251 | pval = (uint8_t *) & iy; 252 | 253 | #ifdef TARGET_BIG_ENDIAN 254 | if ( 1 == hl ) 255 | #else 256 | if ( 0 == hl ) 257 | #endif 258 | pval++; 259 | 260 | return pval; 261 | } //z80_getIndexByteAddress 262 | 263 | void z80_assignYX( uint8_t val ) 264 | { 265 | fY = ( 0 != ( val & 0x20 ) ); 266 | fX = ( 0 != ( val & 8 ) ); 267 | } //z80_assignYX 268 | 269 | void powerOn() 270 | { 271 | pc = 0; 272 | r = 1; 273 | i = 0; 274 | a = f = b = c = d = e = h = l = 0xff; 275 | sp = ix = iy = 0xffff; 276 | ap = fp = bp = cp = dp = ep = hp = lp = 0xff; 277 | fZero = fCarry = fParityEven_Overflow = fSign = fAuxCarry = fWasSubtract = fY = fX = 0; 278 | fINTE = false; 279 | } //powerOn 280 | }; 281 | 282 | extern uint8_t memory[ 65536 ]; 283 | extern registers reg; 284 | 285 | // callbacks when instructions are executed 286 | 287 | extern void x80_invoke_out( uint8_t x ); // called for the out instruction 288 | extern void x80_invoke_in( uint8_t x ); // called for the in instruction 289 | extern void x80_invoke_halt( void ); // called when the 8080 hlt (on Z80 halt) instruction is executed 290 | extern uint8_t x80_invoke_hook( void ); // called with the OPCODE_HOOK instruction is executed 291 | extern void x80_hard_exit( const char * pcerror, uint8_t arg1, uint8_t arg2 ); // called on failures to exit the app 292 | 293 | // emulator API 294 | 295 | extern uint16_t x80_emulate( uint16_t maxcycles ); // execute up to about maxcycles 296 | extern void x80_trace_instructions( bool trace ); // enable/disable tracing each instruction 297 | extern void x80_end_emulation(); // stop the emulation 298 | extern void x80_trace_state( void ); // trace the registers 299 | extern const char * x80_render_operation( uint16_t address ); // return a string with the disassembled instruction at address 300 | -------------------------------------------------------------------------------- /z80test/zexall.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidly/ntvcm/7ef441b24cb4e8b505e7e2673c1eb7ae9d7a7114/z80test/zexall.com -------------------------------------------------------------------------------- /z80test/zexdoc.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidly/ntvcm/7ef441b24cb4e8b505e7e2673c1eb7ae9d7a7114/z80test/zexdoc.com --------------------------------------------------------------------------------