├── .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
--------------------------------------------------------------------------------