The computers on which CHIP-8 originally ran had 16-key hexadecimal keyboards.
155 | The keys are mapped to a Qwerty keyboard according to the following table:
156 |
157 |
158 |
CHIP-8 Key
Actual Key
CHIP-8 Key
Actual Key
159 |
0
'X'
8
'S'
160 |
1
'1'
9
'D'
161 |
2
'2'
A
'Z'
162 |
3
'3'
B
'C'
163 |
4
'Q'
C
'4'
164 |
5
'W'
D
'R'
165 |
6
'E'
E
'F'
166 |
7
'A'
F
'V'
167 |
168 |
169 |
170 |
171 |
270 |
271 |
272 |
273 |
--------------------------------------------------------------------------------
/c8dasm.c:
--------------------------------------------------------------------------------
1 | /* CHIP-8 Disassembler.
2 |
3 |
4 | It choked on the [5-quirks][] test ROM in Timendus's suite:
5 |
6 | To test the `Bxnn`, there's a BE00 at #5AA that will jump to either #E98 or #E9C
7 | depending on the quirk (look for `jumpQuirk` in the original source).
8 |
9 | LD V0, #98 ; 6098 @ 5A6
10 | LD VE, #9C ; 6E9C @ 5A8
11 | JP V0, #E00 ; BE00 @ 5AA
12 | L5AC: db #A5, #D8, #F0, #65
13 |
14 | Either way, both paths jump back to a label `quirks-resume`, which is at #5AC,
15 | right after the BE00. The problem is that the disassembler isn't able to determine
16 | that #E98 and #E9C are reachable, and subsequently it can't tell that #5AC is
17 | reachable and disassemble anything after that.
18 |
19 | So there's now a `-r` command line option that will use the `c8_disasm_reachable()`
20 | function to tell us that an address is reachable. In our example you can use
21 | `-r 0xE98 -r 0xE9C`, which will tell the disassembler that #E98 and #E9C are reachable.
22 |
23 | [5-quirks]: https://github.com/Timendus/chip8-test-suite/raw/main/bin/5-quirks.ch8
24 |
25 | */
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 |
33 | #include "chip8.h"
34 |
35 | #define MAX_BRANCHES 256
36 |
37 | /* If you have a run of ZERO_RUNS or more 0x00 bytes in the data output,
38 | just skip it... */
39 | #define ZERO_RUNS 16
40 |
41 | #define REACHABLE(addr) (reachable[(addr) >> 3] & (1 << ((addr) & 0x07)))
42 | #define SET_REACHABLE(addr) reachable[(addr) >> 3] |= (1 << ((addr) & 0x07))
43 |
44 | #define TOUCHED(addr) (touched[(addr) >> 3] & (1 << ((addr) & 0x07)))
45 | #define TOUCH(addr) touched[(addr) >> 3] |= (1 << ((addr) & 0x07))
46 |
47 | #define IS_LABEL(addr) (labels[(addr) >> 3] & (1 << ((addr) & 0x07)))
48 | #define SET_LABEL(addr) labels[(addr) >> 3] |= (1 << ((addr) & 0x07))
49 |
50 | static uint16_t branches[MAX_BRANCHES], bsp;
51 | uint8_t labels[TOTAL_RAM/8];
52 |
53 | void c8_disasm_start() {
54 | bsp = 0;
55 | memset(labels, 0, sizeof labels);
56 | }
57 |
58 | void c8_disasm_reachable(uint16_t addr) {
59 | if(addr > TOTAL_RAM)
60 | return;
61 | branches[bsp++] = addr;
62 | SET_LABEL(addr);
63 | }
64 |
65 | void c8_disasm() {
66 | /* Some of the reported errors can conceivably happen
67 | if you try to disassembe a buggy program */
68 | uint16_t addr, max_addr = 0, run, run_end = 0;
69 | int odata = 0,
70 | out = 0;
71 |
72 | uint8_t reachable[TOTAL_RAM/8];
73 | uint8_t touched[TOTAL_RAM/8];
74 |
75 | memset(reachable, 0, sizeof reachable);
76 | memset(touched, 0, sizeof touched);
77 |
78 | /* Step 1: Determine which instructions are reachable.
79 | We run through the program instruction by instruction.
80 | If we encouter a branch, we push one path onto a stack and
81 | continue along the other. We continue until everything is
82 | marked as reachable and/or the stack is empty.
83 | Also, we mark branch destinations as labels to prettify the
84 | output - to have labels instead of adresses.
85 | */
86 | branches[bsp++] = PROG_OFFSET;
87 | while(bsp > 0) {
88 | addr = branches[--bsp];
89 |
90 | while(addr < TOTAL_RAM - 1 && !REACHABLE(addr)) {
91 |
92 | SET_REACHABLE(addr);
93 |
94 | uint16_t opcode = c8_opcode(addr);
95 | if(addr < PROG_OFFSET ) {
96 | /* Program ended up where it shouldn't; assumes the RAM is initialised to 0 */
97 | c8_message("error: bad jump: program at #%03X\n",addr);
98 | return;
99 | }
100 |
101 | addr += 2;
102 | if(addr >= TOTAL_RAM) {
103 | c8_message("error: program overflows RAM\n");
104 | return;
105 | }
106 |
107 | uint16_t nnn = opcode & 0x0FFF;
108 |
109 | if(opcode == 0x00EE) { /* RET */
110 | break;
111 | } else if(opcode == 0x00FD) { /* EXIT */
112 | break;
113 | } else if((opcode & 0xF000) == 0x1000) { /* JP addr */
114 | addr = nnn;
115 | assert(addr < TOTAL_RAM);
116 | SET_LABEL(addr);
117 | } else if((opcode & 0xF000) == 0x2000) { /* CALL addr */
118 | if(bsp == MAX_BRANCHES) {
119 | /* Basically, this program is too complex to disassemble,
120 | but you can increase MAX_BRANCHES to see if it helps. */
121 | c8_message("error: Too many branches to follow (%u)\n", bsp);
122 | return;
123 | }
124 | branches[bsp++] = addr; /* For the RET */
125 | addr = nnn;
126 | assert(addr < TOTAL_RAM);
127 | SET_LABEL(addr);
128 | } else if((opcode & 0xF000) == 0x3000 || (opcode & 0xF00F) == 0x5000) { /* SE */
129 | if(bsp == MAX_BRANCHES) {c8_message("error: Too many branches to follow (%u)\n", bsp);return;}
130 | branches[bsp++] = addr + 2;
131 | } else if((opcode & 0xF000) == 0x4000 || (opcode & 0xF00F) == 0x9000) { /* SNE */
132 | if(bsp == MAX_BRANCHES) {c8_message("error: Too many branches to follow (%u)\n", bsp);return;}
133 | branches[bsp++] = addr + 2;
134 | } else if((opcode & 0xF0FF) == 0xE09E) { /* SKP */
135 | if(bsp == MAX_BRANCHES) {c8_message("error: Too many branches to follow (%u)\n", bsp);return;}
136 | branches[bsp++] = addr + 2;
137 | } else if((opcode & 0xF0FF) == 0xE0A1) { /* SKNP */
138 | if(bsp == MAX_BRANCHES) {c8_message("error: Too many branches to follow (%u)\n", bsp); return;}
139 | branches[bsp++] = addr + 2;
140 | } else if((opcode & 0xF000) == 0xB000) {
141 | /* I don't think we can realistically disassemble this, because
142 | we can't know V0 without actually running the program. */
143 | break;
144 | } else if((opcode & 0xF000) == 0xA000) {
145 | /* Mark the address as touched so that it don't get removed by the code
146 | that hides the long runs of 0x00 bytes. */
147 | TOUCH(nnn);
148 | }
149 | }
150 | if(addr >= TOTAL_RAM - 1) {
151 | c8_message("error: program overflows RAM\n");
152 | return;
153 | }
154 | }
155 |
156 | /* Find the largest non-null address so that we don't write a bunch of
157 | unnecessary zeros at the end of our output */
158 | for(max_addr = TOTAL_RAM - 1; c8_get(max_addr) == 0; max_addr--);
159 |
160 | /* Step 2: Loop through all the reachable instructions and print them. */
161 | for(addr = PROG_OFFSET; addr < TOTAL_RAM; addr += REACHABLE(addr)?2:1) {
162 | /* The REACHABLE(addr)?2:1 above is to handle non-aligned instructions properly. */
163 | char buffer[64];
164 |
165 | if(!REACHABLE(addr)) {
166 | if(addr <= max_addr) {
167 | /* You've reached data that is non-null, but not code either. */
168 | if(addr < run_end)
169 | continue;
170 |
171 | /* Find out a run of 0x00 bytes that are not code (REACHABLE) and not
172 | data bytes that are referenced by a `LD I,nnn` (`Annn`) instruction (TOUCHED) */
173 | for(run = addr; run < TOTAL_RAM && !REACHABLE(run) && !TOUCHED(run) && !c8_get(run); run++);
174 |
175 | if(run - addr > ZERO_RUNS) {
176 | if(odata) {
177 | c8_message("\n");
178 | odata = 0;
179 | }
180 | c8_message(" ; skipped run of %u #00 bytes at #%04X...\n", run - addr, addr);
181 | c8_message("offset #%04X \n", run);
182 |
183 | run_end = run;
184 | continue;
185 | }
186 |
187 | /* Make sure `db` clauses to blocks touched by `LD I, nnn` start on new line: */
188 | if(TOUCHED(addr) && odata) {
189 | c8_message("\n");
190 | odata = 0;
191 | }
192 |
193 | if(!odata++) {
194 | c8_message("L%03X: db #%02X", addr, c8_get(addr));
195 | } else {
196 | c8_message(", #%02X", c8_get(addr));
197 | if(odata % 4 == 0) {
198 | c8_message("\n");
199 | odata = 0;
200 | }
201 | }
202 | }
203 | out = 0;
204 | continue;
205 | }
206 |
207 | uint16_t opcode = c8_opcode(addr);
208 |
209 | buffer[0] = '\0';
210 |
211 | uint8_t x = (opcode >> 8) & 0x0F;
212 | uint8_t y = (opcode >> 4) & 0x0F;
213 | uint8_t nibble = opcode & 0x0F;
214 | uint16_t nnn = opcode & 0x0FFF;
215 | uint8_t kk = opcode & 0xFF;
216 |
217 | switch(opcode & 0xF000) {
218 | case 0x0000:
219 | if(opcode == 0x00E0) sprintf(buffer,"CLS");
220 | else if(opcode == 0x00EE) sprintf(buffer,"RET");
221 | else if((opcode & 0xFFF0) == 0x00C0) sprintf(buffer,"SCD %d", nibble);
222 | else if(opcode == 0x00FB) sprintf(buffer,"SCR");
223 | else if(opcode == 0x00FC) sprintf(buffer,"SCL");
224 | else if(opcode == 0x00FD) sprintf(buffer,"EXIT");
225 | else if(opcode == 0x00FE) sprintf(buffer,"LOW");
226 | else if(opcode == 0x00FF) sprintf(buffer,"HIGH");
227 | else sprintf(buffer,"SYS #%03X", nnn);
228 | break;
229 | case 0x1000: sprintf(buffer,"JP L%03X", nnn); break;
230 | case 0x2000: sprintf(buffer,"CALL L%03X", nnn); break;
231 | case 0x3000: sprintf(buffer,"SE V%1X, %d", x, kk); break;
232 | case 0x4000: sprintf(buffer,"SNE V%1X, %d", x, kk); break;
233 | case 0x5000: sprintf(buffer,"SE V%1X, V%1X", x, y); break;
234 | case 0x6000: sprintf(buffer,"LD V%1X, %d", x, kk); break;
235 | case 0x7000: sprintf(buffer,"ADD V%1X, %d", x, kk); break;
236 | case 0x8000: {
237 | switch(nibble) {
238 | case 0x0: sprintf(buffer,"LD V%1X, V%1X", x, y); break;
239 | case 0x1: sprintf(buffer,"OR V%1X, V%1X", x, y); break;
240 | case 0x2: sprintf(buffer,"AND V%1X, V%1X", x, y); break;
241 | case 0x3: sprintf(buffer,"XOR V%1X, V%1X", x, y); break;
242 | case 0x4: sprintf(buffer,"ADD V%1X, V%1X", x, y); break;
243 | case 0x5: sprintf(buffer,"SUB V%1X, V%1X", x, y); break;
244 | case 0x7: sprintf(buffer,"SUBN V%1X, V%1X", x, y); break;
245 | case 0x6:
246 | if(x == y)
247 | sprintf(buffer,"SHR V%1X", x);
248 | else
249 | sprintf(buffer,"SHR V%1X, V%1X", x, y);
250 | break;
251 | case 0xE:
252 | if(x == y)
253 | sprintf(buffer,"SHL V%1X", x);
254 | else
255 | sprintf(buffer,"SHL V%1X, V%1X", x, y);
256 | break;
257 | }
258 | } break;
259 | case 0x9000: sprintf(buffer,"SNE V%1X, V%1X", x, y); break;
260 | case 0xA000:
261 | if(nnn < 0x200)
262 | sprintf(buffer,"LD I, #%03X", nnn);
263 | else
264 | sprintf(buffer,"LD I, L%03X", nnn);
265 | break;
266 | case 0xB000: sprintf(buffer,"JP V0, #%03X", nnn); break;
267 | case 0xC000: sprintf(buffer,"RND V%1X, #%02X", x, kk); break;
268 | case 0xD000: sprintf(buffer,"DRW V%1X, V%1X, %d", x, y, nibble); break;
269 | case 0xE000: {
270 | if(kk == 0x9E) {
271 | sprintf(buffer,"SKP V%1X", x);
272 | } else if(kk == 0xA1) {
273 | sprintf(buffer,"SKNP V%1X", x);
274 | }
275 | } break;
276 | case 0xF000: {
277 | switch(kk) {
278 | case 0x07: sprintf(buffer,"LD V%1X, DT", x); break;
279 | case 0x0A: sprintf(buffer,"KEY V%1X", x); break;
280 | case 0x15: sprintf(buffer,"DELAY V%1X", x); break;
281 | case 0x18: sprintf(buffer,"SOUND V%1X", x); break;
282 | case 0x1E: sprintf(buffer,"ADD I, V%1X", x); break;
283 | case 0x29: sprintf(buffer,"HEX V%1X", x); break;
284 | case 0x33: sprintf(buffer,"BCD V%1X", x); break;
285 | case 0x55: sprintf(buffer,"STOR V%1X", x); break;
286 | case 0x65: sprintf(buffer,"RSTR V%1X", x); break;
287 | case 0x30: sprintf(buffer,"HEXX V%1X", x); break;
288 | case 0x75: sprintf(buffer,"STORX V%1X", x); break;
289 | case 0x85: sprintf(buffer,"RSTRX V%1X", x); break;
290 | }
291 | } break;
292 | }
293 | if(!buffer[0]) {
294 | c8_message("error: Disassembler got confused at #%03X\n", addr);
295 | return;
296 | }
297 | if(IS_LABEL(addr) || TOUCHED(addr) || !out) {
298 | if(odata) c8_message("\n");
299 | c8_message("L%03X: %-20s ; %04X @ %03X\n", addr, buffer, opcode, addr);
300 | } else
301 | c8_message(" %-20s ; %04X @ %03X\n", buffer, opcode, addr);
302 | out = 1;
303 | odata = 0;
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CHIP-8 Interpreter, Assembler and Disassembler
2 |
3 | This package contains an interpreter for [CHIP-8][wikipedia] as well as a
4 | command-line assembler and disassembler.
5 |
6 | It also supports the SuperChip instructions.
7 |
8 | The syntax of the assembler and disassembler is based on the syntax described
9 | in [Cowgod's Chip-8 Technical Reference v1.0][cowgod], by Thomas P. Greene
10 |
11 | Frédéric Devernay's [SVision-8 website](http://devernay.free.fr/hacks/chip8/)
12 | has a wealth of information. He also has a collection of CHIP-8 games and
13 | programs in his [GAMES.zip](http://devernay.free.fr/hacks/chip8/GAMES.zip).
14 |
15 | ## Compilation and Usage
16 |
17 | * Linux: Type `make` from the shell.
18 | * Windows: The system was built and tested with the
19 | [MinGW](http://www.mingw.org/) tools. To compile it type `make` from the MSYS
20 | shell.
21 |
22 | To use the emulator:
23 |
24 | * Under Linux: Type `./chip8 game.ch8` where game.ch8 is the binary CHIP-8 file.
25 | * Under Windows: Type `chip8 game.ch8` or `chip8-gdi game.ch8` depending on
26 | which of the implementations (see below) you want to use.
27 |
28 | The assembler and disassemblers are simple command line applications and
29 | platform independent.
30 |
31 | To use the assembler, type
32 |
33 | $ ./c8asm -o file.c8h file.asm
34 |
35 | This will assemble `file.asm` into a binary `file.c8h`. If the `-o` is not
36 | specified it will default to `a.c8h`.
37 |
38 | To use the disassembler, run the command
39 |
40 | $ ./c8dasm a.ch8 > outfile.asm
41 |
42 | where `a.ch8` is the file you want to disassemble.
43 |
44 | ## Interpreter Implementations
45 |
46 | The core of the emulator is in `chip8.c`. The idea is that this core be
47 | platform independent and then hooks are provided for platform specific
48 | implementations.
49 |
50 | The API is described in `chip8.h`. The `docs` target in the Makefile generates
51 | HTML documentation from it.
52 |
53 | Two implementations are provided in this repository:
54 |
55 | 1. A SDL-based implentation () which is intended for
56 | portability, and
57 | 2. a native Windows implementation which is intended for small size and
58 | requires no third party dependencies.
59 |
60 | In both versions
61 |
62 | * `bmp.h` and `bmp.c` (together with the `fonts/` directory) is used to draw
63 | and manipulate the bitmap graphics. See also
64 | https://github.com/wernsey/bitmap
65 | * `render.c` implements the `init_game()`, `deinit_game()` and `render()`
66 | functions that forms the core of both implementations and demonstrates how
67 | the interpreter's API works.
68 |
69 | The `render()` function checks the keyboard and executes the interpreter a
70 | couple of times by calling `c8_step()` and redraws the screen if it changed.
71 | The SDL and Win32 frameworks were written in such a way that the `render()`
72 | function works with both with only a couple of minor modifications.
73 |
74 | The implementations feature a rudimentary debugger: Press F5 to pause a running
75 | game. The program counter and the current instruction will be displayed at the
76 | bottom of the screen, along with the values of the 16 Vx registers. Press F6 to
77 | step through the program to the next instruction and F8 to resume the program.
78 |
79 | The `Makefile` will build the SDL version by default, and build the GDI version
80 | under Windows.
81 |
82 | ### SDL Implementation
83 |
84 | The SDL-based implementation is intended for portability. The files `pocadv.c`
85 | and `pocadv.h` implement a wrapper around the SDL that contains the `main()`
86 | function, the SDL event loops and so on.
87 |
88 | The included `emscripten.mak` file is used to compile the SDL implementation to
89 | JavaScript with [Emscripten](http://emscripten.org/) for running the
90 | interpreter in a web browser. The `chip8.html` is a wrapper around the
91 | Emscripten-generated JavaScript. If you want to use this implementation:
92 |
93 | 1. You need to put your CHIP-8 binary file in a `./GAMES/` directory
94 | 2. Run `make -f emscripten.mak`
95 | 3. Change the `Module.arguments` variable in the JavaScript in `chip8.html`
96 | 4. Serve `chip8.html` in a web server.
97 |
98 | I built the emscripten version through the emscripten SDK installed
99 | according to the [installation instructions][emscripten-install]. I had
100 | some linker errors with Ubuntu's `emscripten` package that I couldn't
101 | resolve.
102 |
103 | [emscripten-install]: http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html#sdk-download-and-install
104 |
105 | ### Win32/GDI Implementation
106 |
107 | The native Windows version uses a simple hook around the Win32 GDI and requires
108 | no third party dependencies.
109 |
110 | `gdi.h` and `gdi.c` implements the native Windows code. It implements a
111 | `WinMain` function with the main Win32 events processing loop. It binds the
112 | window's GDI context to a `Bitmap` object so that a render function can draw
113 | onto it and fires off periodic `WM_PAINT` messages which calls the `render()`
114 | function to draw the screen.
115 |
116 | ## Implementation Notes
117 |
118 | I've consulted several sources for my implementation (see references below),
119 | and there were some discrepancies. This is how I handled them:
120 |
121 | * Regarding `2nnn`, [cowgod][] says the stack pointer is incremented first (i.e.
122 | `stack[++SP]`), but that skips `stack[0]`. My implementation does it the
123 | other way round.
124 | * ~~The `Fx55` and `Fx65` instructions doesn't change `I` in my implementation:~~
125 | * This is a known [quirk][langhoff].
126 | * The interpreter now provides `QUIRKS_MEM_CHIP8` to control this
127 | * I've read [David Winter's emulator][winter]'s documentation when I started, but I
128 | implemented things differently:
129 | * His emulator scrolls only 2 pixels if it is in low-res mode, but 4 pixels
130 | is consistent with [Octo][].
131 | * His emulator's `Dxy0` instruction apparently also works differently in
132 | lo-res mode.
133 | * ~~[instruction-draw][] says that images aren't generally wrapped, but
134 | [muller][] and [Octo][] seems to think differently.~~
135 | * This is alsp known [quirk][langhoff].
136 | * The interpreter now provides `QUIRKS_CLIPPING` to control this
137 | * According to [chip8-wiki][], the upper 256 bytes of RAM is used for the display, but it
138 | seems that modern interpreters don't do that. Besides, you'd need 1024 bytes
139 | to store the SCHIP's hi-res mode.
140 | * `hp48_flags` is not cleared between runs (See [octo-superchip]); I don't make any effort
141 | to persist them, though.
142 | * Apparently there are CHIP-8 interpreters out there that don't use the
143 | standard 64x32 and 128x64 resolutions, but I don't support those.
144 | * As far as I can tell, there is not much in terms of standard timings on
145 | CHIP-8 implementations. My implementation allows you to specify the speed as
146 | the number of instructions to execute per second (through the global variable
147 | `speed` in `render.c`). The value of 1200 instructions per second seems like
148 | a good value to start with.
149 |
150 | ## References and Links
151 |
152 | * [Wikipedia entry][wikipedia]
153 | * [Cowgod's Chip-8 Technical Reference v1.0][cowgod], by Thomas P. Greene,
154 | * [How to write an emulator (CHIP-8 interpreter)][muller] by Laurence Muller (archived)
155 | * [CHIP8 A CHIP8/SCHIP emulator Version 2.2.0][winter], by David Winter
156 | * [Chip 8 instruction set][chip8def], author unknown(?)
157 | * [Byte Magazine Volume 03 Number 12 - Life pp. 108-122. "An Easy
158 | Programming System,"][byte] by Joseph Weisbecker
159 | * [chip8.wikia.com][chip8-wiki]
160 | * Their page on the [Draw instruction][instruction-draw]
161 | * [Mastering CHIP-8][mikolay] by Matthew Mikolay
162 | * [Octo][], John Earnest
163 | * The [Octo SuperChip document][octo-superchip], by John Earnest
164 | * [codeslinger.co.uk](http://www.codeslinger.co.uk/pages/projects/chip8/primitive.html)
165 | * [CHIP‐8 Technical Reference](https://github.com/mattmikolay/chip-8/wiki/CHIP%E2%80%908-Technical-Reference), by Matthew Mikolay
166 | * [corax89' chip8-test-rom](https://github.com/corax89/chip8-test-rom)
167 | * [Timendus' chip8-test-suite][Timendus] was extremely useful to help clarify and fix the quirks.
168 | * Timendus' [Silicon8](https://github.com/Timendus/silicon8/) CHIP8 implementation
169 | * Tobias V. Langhoff's [Guide to making a CHIP-8 emulator][langhoff]
170 | * This one is very useful for explaining the various quirks
171 | * [Chip-8 on the COSMAC VIP: Drawing Sprites](https://web.archive.org/web/20200925222127if_/https://laurencescotford.com/chip-8-on-the-cosmac-vip-drawing-sprites/), by Laurence Scotford (archive link)
172 | * [CHIP-8 extensions and compatibility](https://chip-8.github.io/extensions/) -
173 | explains several of the variants out there
174 | *
175 | * The [load_quirk and shift_quirk](https://github.com/zaymat/super-chip8#load_quirk-and-shift_quirk)
176 | section of that README has another explaination of some of the
177 | quirks, along with a list of known games that need them.
178 |
179 | * has a collection of ROMs I used for testing
180 | * - Archive of CHIP8 programs.
181 | *
182 |
183 | [wikipedia]: https://en.wikipedia.org/wiki/CHIP-8
184 | [cowgod]: http://devernay.free.fr/hacks/chip8/C8TECH10.HTM
185 | [muller]: https://web.archive.org/web/20110426134039if_/http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/
186 | [winter]: http://devernay.free.fr/hacks/chip8/CHIP8.DOC
187 | [chip8def]: http://devernay.free.fr/hacks/chip8/chip8def.htm
188 | [byte]: https://archive.org/details/byte-magazine-1978-12
189 | [chip8-wiki]:
190 | [instruction-draw]: http://chip8.wikia.com/wiki/Instruction_Draw
191 | [mikolay]:
192 | [octo]: https://github.com/JohnEarnest/Octo
193 | [octo-superchip]: https://github.com/JohnEarnest/Octo/blob/gh-pages/docs/SuperChip.md
194 | [langhoff]: https://tobiasvl.github.io/blog/write-a-chip-8-emulator/
195 | [Timendus]: https://github.com/Timendus/chip8-test-suite
196 |
197 | ## License
198 |
199 | This code is licensed under the [Apache license version 2](http://www.apache.org/licenses/LICENSE-2.0):
200 |
201 | ```
202 | Copyright 2015-2016 Werner Stoop
203 |
204 | Licensed under the Apache License, Version 2.0 (the "License");
205 | you may not use this file except in compliance with the License.
206 | You may obtain a copy of the License at
207 |
208 | http://www.apache.org/licenses/LICENSE-2.0
209 |
210 | Unless required by applicable law or agreed to in writing, software
211 | distributed under the License is distributed on an "AS IS" BASIS,
212 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
213 | See the License for the specific language governing permissions and
214 | limitations under the License.
215 | ```
216 |
217 | ## TODO/Roadmap/Ideas
218 |
219 | * [ ] I really need to fix the "Display wait" quirk. See [Timendus][]'s `5-quirks.ch8` test.
220 | * [x] The quirks need to be in a flags variable so that they can be controlled at runtime
221 | * [x] The runtime should have a `-q` command line option to control the quirks
222 | * [x] The assembler needs an `include "file.asm"` directive.
223 | * [x] You need a way to specify how to do the include, because the assembler must be
224 | usable even if you're not loading the source from files. I suggest a function pointer
225 | that points to `c8_load_txt()` by default, but can be made to point elsewhere (or set to
226 | `NULL` and disable includes completely)
227 | * [x] I should consider a `text "hello"` directive in the assembler, that places a null
228 | terminated string in the bytecode. Users might be able to display the text at some point
229 | if you have the right sprites; [Octo][] does it.
230 | * [x] Allow for some hooks in the library to let the `SYS nnn` (`0nnn`) instructions break
231 | out into the environment outside.
232 | * It's meant as a bit of a joke, might be neat if you embed a CHIP-8 interpreter
233 | in another program and call out to it as a sort of scripting language.
234 | * [x] Command line option, like `-m addr=val`, that will set the byte at `addr` to `val` in the
235 | RAM before running the interpreter.
236 | * A immediate use case is for, example, [Timendus][]'s `5-quirks.ch8` test that allows you
237 | to write a value between 1 and 3 to `0x1FF` and then the program will bypass the initial
238 | menu and skip directly to the corresponding test. I imagine that while developing and
239 | debugging CHIP-8 programs it might be useful to have such a mechanism.
240 | * [x] Fix the assembler that doesn't do any bounds checks on `stepper->token`
241 | * [ ] Breakpoints in the debugger
242 | * [ ] ~~A `.map` file output by the assembler...~~
243 |
244 | Porting to the Amiga 500 might be an interesting challenge to get it truly portable:
245 | The Amiga's bus is word aligned, so if the program counter is ever an odd number then
246 | the system might crash when it tries to retrieve an instruction. Also, the Amiga is big
247 | endian, so that might reveal some problems as well.
248 |
249 | [XO-Chip compatibility](http://johnearnest.github.io/Octo/docs/XO-ChipSpecification.html) seems
250 | like something worth striving for. [Here](https://chip-8.github.io/extensions/#xo-chip)'s a
251 | short checklist of the changes. Also look at how [Octo](https://chip-8.github.io/extensions/#octo)
252 | modifies some instructions.
253 |
--------------------------------------------------------------------------------
/chip8.h:
--------------------------------------------------------------------------------
1 | /** chip8.h
2 | * =======
3 | *
4 | * ![toc]
5 | * ## Introduction
6 | *
7 | * This header provides the public API for the core of the CHIP-8 toolkit.
8 | *
9 | * The core of the CHIP-8 interpreter is written to be platform independent.
10 | * To use this core, one needs to use this API to create
11 | * a platform-specific _implementation_. This _implementation_ should do the
12 | * following:
13 | *
14 | * * Step through the instructions in the interpreter; see `c8_step()`.
15 | * * Draw the graphics; The resolution is obtained through `c8_resolution()`
16 | * and then the individual pixel states are read through `c8_get_pixel()`
17 | * * Tell the interpreter about the state of the keyboard; It should call
18 | * `c8_key_down()` and `c8_key_up()` when the state of the keyboard
19 | * changes.
20 | * * Tell the intepreter about every 60Hz timer tick; see `c8_60hz_tick()`.
21 | * * Play sound. Since the sound is just a buzzer, you may wish to skip this;
22 | * See `c8_sound()`.
23 | *
24 | * The header also includes an API for the CHIP-8 assembler and a disassembler.
25 | * The syntax is based on **Cowgod's Chip-8 Technical Reference v1.0**
26 | * by Thomas P. Greene available at
27 | *
28 | *
29 | * ## License
30 | * This code is licensed under the [Apache license version 2](http://www.apache.org/licenses/LICENSE-2.0):
31 | *
32 | * ```
33 | * Copyright 2015-2016 Werner Stoop
34 | *
35 | * Licensed under the Apache License, Version 2.0 (the "License");
36 | * you may not use this file except in compliance with the License.
37 | * You may obtain a copy of the License at
38 | *
39 | * http://www.apache.org/licenses/LICENSE-2.0
40 | *
41 | * Unless required by applicable law or agreed to in writing, software
42 | * distributed under the License is distributed on an "AS IS" BASIS,
43 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | * See the License for the specific language governing permissions and
45 | * limitations under the License.
46 | * ```
47 | */
48 |
49 | /** ## Definitions */
50 | /** `#define TOTAL_RAM 4096` \
51 | * Maximum addressable memory in the interpreter.
52 | */
53 | #define TOTAL_RAM 4096
54 |
55 | /** `#define PROG_OFFSET 512` \
56 | * Offset of the program in RAM. Should be 512, but
57 | * apparently there are some computers where this is `0x600` (See [wikipedia][]).
58 | */
59 | #define PROG_OFFSET 512
60 |
61 | /** `#define MAX_MESSAGE_TEXT 128` \
62 | * Size of the buffer used internally by `c8_message()`
63 | */
64 | #define MAX_MESSAGE_TEXT 128
65 |
66 | /** `typedef struct chip8_t`
67 | *
68 | * Structure to keep the interpreter's state and registers.
69 | *
70 | * It has these members:
71 | *
72 | * * `uint8_t V[16]` - CHIP-8 registers
73 | * * `uint8_t RAM[TOTAL_RAM]` - Interpreter RAM
74 | * * `uint16_t PC` - Program counter
75 | * * `uint16_t I` - Index register
76 | * * `uint8_t DT, ST` - Delay timer and sound timer
77 | * * `uint16_t stack[16]` - stack
78 | * * `uint8_t SP` - Stack pointer
79 | *
80 | */
81 | typedef struct {
82 | uint8_t V[16];
83 | uint8_t RAM[TOTAL_RAM];
84 | uint16_t PC;
85 | uint16_t I;
86 | uint8_t DT, ST;
87 | uint16_t stack[16];
88 | uint8_t SP;
89 | } chip8_t;
90 |
91 | extern chip8_t C8;
92 |
93 | /**
94 | * ## Quirks
95 | *
96 | * The definitions below determine how the Quirks, as defined in Timendus' [quirks-test][]
97 | * are handled.
98 | *
99 | * Detailed explainations of the issues can be found in [langhoff][].
100 | *
101 | * Use the function `c8_set_quirks()` to a combination of the following flags bitwise-OR'ed together:
102 | *
103 | * * `QUIRKS_VF_RESET` - Controls whether the AND/OR/XOR instructions reset the VF register to 0.
104 | * * The original COSMAC CHIP8 performed these directly in the ALU, so VF was
105 | * always affected, but later implementations didn't.
106 | * * See [here](https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#logical-and-arithmetic-instructions).
107 | * * `QUIRKS_MEM_CHIP8` - Controls whether the `I` register is incremented by the Fx55 and Fx65 instructions.
108 | * * The original COSMAC CHIP8 incremented the `I` register, but modern implementations don't.
109 | * * Don't use it if you're unsure, because that would be more compatible.
110 | * * See [here](https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#fx55-and-fx65-store-and-load-memory).
111 | * * `QUIRKS_SHIFT` - If it is not set, `Vy` is copied into `Vx` when performing the `8xy6` and `8xyE` shift instructions.
112 | * * The original CHIP8 behaved this way, but later implementations didn't.
113 | * * See [here](https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#8xy6-and-8xye-shift).
114 | * * (Note to self: The INVADERS.ch8 game I found somewhere is an example of where the shifting
115 | * needs to be on)
116 | * * `QUIRKS_JUMP` - The CHIP-48 and SUPER-CHIP implementations originally implemented the
117 | * instruction as Bxnn as a jump to `Vx + xnn` rather than as `V0 + xnn`
118 | * * (it may have been a mistake).
119 | * * See [here](https://tobiasvl.github.io/blog/write-a-chip-8-emulator/#bnnn-jump-with-offset)
120 | * * `QUIRKS_CLIPPING` - With clipping enabled, sprites off the edge of the screen are clipped.
121 | * If it is disabled, then those sprites are wrapped to the other side.
122 | * * This is associated with XO-CHIP
123 | *
124 | * In addition, these constants are defined for convenience:
125 | *
126 | * * `QUIRKS_DEFAULT` - The default setting: It enables `VF Reset`, `Shift` and `Clipping`.
127 | * * `QUIRKS_CHIP8` - Enables `VF Reset`, `Mem CHIP8`, `Disp Wait` and `Clipping` for the best compatibility with
128 | * the original CHIP8.
129 | * * `QUIRKS_SCHIP` - Enables `Clipping`, `Shift` and `Jump` for the best compatibility with SUPER-CHIP.
130 | *
131 | * [langhoff]: https://tobiasvl.github.io/blog/write-a-chip-8-emulator/
132 | * [quirks-test]: https://github.com/Timendus/chip8-test-suite/tree/main#quirks-test
133 | */
134 | #define QUIRKS_VF_RESET 0x01
135 | #define QUIRKS_MEM_CHIP8 0x02
136 | #define QUIRKS_DISP_WAIT 0x04
137 | #define QUIRKS_CLIPPING 0x08
138 | #define QUIRKS_SHIFT 0x10
139 | #define QUIRKS_JUMP 0x20
140 |
141 | #define QUIRKS_DEFAULT (QUIRKS_VF_RESET | QUIRKS_SHIFT | QUIRKS_CLIPPING)
142 | #define QUIRKS_CHIP8 (QUIRKS_VF_RESET | QUIRKS_MEM_CHIP8 | QUIRKS_DISP_WAIT | QUIRKS_CLIPPING)
143 | #define QUIRKS_SCHIP (QUIRKS_CLIPPING | QUIRKS_SHIFT | QUIRKS_JUMP)
144 |
145 | /**
146 | * `void c8_set_quirks(unsigned int q);` \
147 | */
148 | void c8_set_quirks(unsigned int q);
149 |
150 | /**
151 | * `unsigned int c8_get_quirks();` \
152 | */
153 | unsigned int c8_get_quirks();
154 |
155 | /**
156 | * ## Utilities
157 | *
158 | * `extern int c8_verbose;` \
159 | * Set to non-zero to turn on verbose mode.
160 | *
161 | * The higher the value, the more verbose the output.
162 | */
163 | extern int c8_verbose;
164 |
165 | /** ## Interpreter */
166 |
167 | /** `void c8_reset();` \
168 | * Resets the state of the interpreter so that a new program
169 | * can be executed.
170 | */
171 | void c8_reset();
172 |
173 | /** `void c8_step();` \
174 | * Steps through a single instruction in the interpreter.
175 | *
176 | * This function forms the core of the interpreter.
177 | */
178 | void c8_step();
179 |
180 | /** `int c8_ended();` \
181 | * Returns true if the interpreter has ended.
182 | *
183 | * The interpreter has ended if a **00FD** instruction has been encountered.
184 | *
185 | * The **00FD** instruction is actually SuperChip specific.
186 | */
187 | int c8_ended();
188 |
189 | /** `int c8_waitkey();` \
190 | * Returns true if the interpreter is waiting for keyboard input.
191 | *
192 | * The **Fx0A** instruction is the one that waits for a specific key to be pressed.
193 | */
194 | int c8_waitkey();
195 |
196 | /**
197 | * `typedef int (*c8_sys_hook_t)(unsigned int nnn);` \
198 | * `extern c8_sys_hook_t c8_sys_hook;` \
199 | *
200 | * If `c8_sys_hook` is not null, then the interpreter will call it when
201 | * it encounters a `SYS nnn` (`0nnn`) instruction, with `nnn` as a parameter.
202 | *
203 | * The function should return a non-zero value on success. If it returns
204 | * zero, the interpreter will halt.
205 | */
206 | typedef int (*c8_sys_hook_t)(unsigned int nnn);
207 |
208 | extern c8_sys_hook_t c8_sys_hook;
209 |
210 | /** ## Debugging */
211 |
212 | /** `uint8_t c8_get(uint16_t addr);` \
213 | * Gets the value of a byte at a specific address `addr` in
214 | * the interpreter's RAM.
215 | */
216 | uint8_t c8_get(uint16_t addr);
217 |
218 | /** `void c8_set(uint16_t addr, uint8_t byte);` \
219 | * Sets the value of the `byte` at a specific address `addr` in
220 | * the interpreter's RAM.
221 | */
222 | void c8_set(uint16_t addr, uint8_t byte);
223 |
224 | /** `uint16_t c8_opcode(uint16_t addr);` \
225 | * Gets the opcode at a specific address `addr` in the interpreter's RAM.
226 | */
227 | uint16_t c8_opcode(uint16_t addr);
228 |
229 | /** `uint16_t c8_get_pc();` \
230 | * Gets the current address pointed to by the interpreter's program
231 | * counter (PC).
232 | *
233 | */
234 | uint16_t c8_get_pc();
235 |
236 | /** `uint16_t c8_prog_size();` \
237 | * Gets the size of the program in the interpreter's RAM.
238 | *
239 | * It basically just search for the last non-zero byte in RAM.
240 | */
241 | uint16_t c8_prog_size();
242 |
243 | /** `uint8_t c8_get_reg(uint8_t r);` \
244 | * Gets the value of the register `Vr` where `0` <= `r` <= `F`.
245 | */
246 | uint8_t c8_get_reg(uint8_t r);
247 |
248 | /** `int (*c8_rand)();` \
249 | * Points to the function that should be used to generate
250 | * random numbers for the **Cxkk** instruction.
251 | *
252 | * The default value points to `rand()` in the standard library.
253 | * This implies that `srand()` should be called at the
254 | * start of the program.
255 | */
256 | extern int (*c8_rand)();
257 |
258 | /** ## Graphics
259 | * The _implementation_ should provide a platform specific way for the interpreter
260 | * core to draw its graphics.
261 | *
262 | */
263 |
264 | /** `int c8_screen_updated();` \
265 | * Returns true if the last instruction executed by `c8_step()` changed the graphics,
266 | * in which case the display should be updated.
267 | */
268 | int c8_screen_updated();
269 |
270 | /** `int c8_resolution(int *w, int *h);` \
271 | * Loads the current resolution of the interpreter into `w` and `h`.
272 | *
273 | * The interpreter will be in either the normal CHIP-8 64x32 resolution or
274 | * in the SuperChip-specific 128x64 "high" resolution mode.
275 | *
276 | * It returns 1 if the interpreter is in high resolution mode, 0 otherwise.
277 | */
278 | int c8_resolution(int *w, int *h);
279 |
280 | /** `int c8_get_pixel(int x, int y);` \
281 | * Gets the status of the pixel at (x,y).
282 | *
283 | * Returns 1 if the pixel is set - i.e. it should be drawn in the foreground colour.
284 | *
285 | * Returns 0 if the pixel is cleared - i.e. it should be drawn in the background colour.
286 | */
287 | int c8_get_pixel(int x, int y);
288 |
289 | /** ## Keyboard routines
290 | * The _implementation_ should use these functions to tell the interpreter
291 | * about changes in the keyboard state:
292 | */
293 |
294 | /** `void c8_key_down(uint8_t k);` \
295 | * Sets the state of key `k` to pressed.
296 | */
297 | void c8_key_down(uint8_t k);
298 |
299 | /** `void c8_key_up(uint8_t k);` \
300 | * Sets the state of key `k` to released.
301 | */
302 | void c8_key_up(uint8_t k);
303 |
304 | /** ## Timer and sound functions
305 | * CHIP-8 has a 60Hz timer that updates a delay timer and a sound timer
306 | * register. The _implementation_ needs to tell the interpreter about these
307 | * 60Hz ticks.
308 | */
309 |
310 | /** `void c8_60hz_tick();` \
311 | * Executes a CHIP-8 60Hz timer tick.
312 | *
313 | * This decrements the delay and sound timers if they are non-zero.
314 | *
315 | * The _implementation_ should call this function 60 times per second.
316 | */
317 | void c8_60hz_tick();
318 |
319 | /** `int c8_sound();` \
320 | * Returns true if the sound timer is non-zero and sound should be played.
321 | *
322 | * CHIP-8 sounds use a single tone over which programs have no control.
323 | */
324 | int c8_sound();
325 |
326 | /** ## I/O Routines
327 | * The toolkit provides several functions to save
328 | * and load CHIP-8 programs to and from disk.
329 | */
330 |
331 | /** `size_t c8_load_program(uint8_t program[], size_t n);` \
332 | * Loads a program's bytes (of length `n`) into the interpreter's RAM.
333 | * It returns the number of bytes loaded.
334 | */
335 | size_t c8_load_program(uint8_t program[], size_t n);
336 |
337 | /** `int c8_load_file(const char *fname);` \
338 | * Loads a CHIP-8 file from disk into the interpreter's RAM.
339 | *
340 | * Returns the number of bytes read, 0 on error.
341 | */
342 | int c8_load_file(const char *fname);
343 |
344 | /** `int c8_save_file(const char *fname);` \
345 | * Writes the contents of the interpreter's RAM to a file.
346 | *
347 | * This is typically done after `c8_assemble()` to write the
348 | * final program to disk.
349 | *
350 | * Returns the number of bytes written on success, 0 on failure.
351 | */
352 | int c8_save_file(const char *fname);
353 |
354 | /** `char *c8_load_txt(const char *fname);` \
355 | * Utility function that loads a text file.
356 | *
357 | * It `malloc()`s a buffer large enough to hold the entire file
358 | * that needs to be `free()`ed afterwards.
359 | *
360 | * Returns the buffer, or `NULL` on error.
361 | */
362 | char *c8_load_txt(const char *fname);
363 |
364 | /** ## Output and Error handling */
365 |
366 | /** `extern int (*c8_puts)(const char* s);` \
367 | * Pointer to a function that outputs text messages.
368 | *
369 | * The default implementation wraps around `fputs()`
370 | * and writes to `stdout`; change it if output needs
371 | * to be done differently.
372 | */
373 | extern int (*c8_puts)(const char* s);
374 |
375 | /** `int c8_message(const char *msg, ...);` \
376 | * Outputs a formatted message.
377 | *
378 | * It has the same symantics as `printf()` and
379 | * calls `c8_puts()` to output a message.
380 | *
381 | * Returns the value of the `c8_puts()` call.
382 | */
383 | int c8_message(const char *msg, ...);
384 |
385 | /** `extern char c8_message_text[];` \
386 | * The internal buffer used by `c8_message()`.
387 | */
388 | extern char c8_message_text[];
389 |
390 | /**
391 | * ## Assembler
392 | *
393 | */
394 |
395 | /** `int c8_assemble(const char *text);` \
396 | * Assembles a block of text into the interpreter's RAM.
397 | *
398 | * The assembled program can be written to a file using `c8_save_file()`.
399 | *
400 | * `c8_load_txt()` is provided as a utility function to load
401 | * a text file that can be assembled.
402 | *
403 | * See `asmmain.c` for an example of a program that uses this function.
404 | */
405 | int c8_assemble(const char *text);
406 |
407 | /**
408 | * `typedef char *(*c8_include_callback_t)(const char *fname);` \
409 | * `extern c8_include_callback_t c8_include_callback;` \
410 | *
411 | * Controls how `include` directives are handled by the assembler.
412 | *
413 | * The callback should return the text in the specified file. The
414 | * return value should be allocated on the heap, because the assembler
415 | * will call `free()` on it when it is done.
416 | *
417 | * `c8_include_callback` defaults to `c8_load_txt()`.
418 | */
419 | typedef char *(*c8_include_callback_t)(const char *fname);
420 | extern c8_include_callback_t c8_include_callback;
421 |
422 | /**
423 | * ## Disassembler
424 | *
425 | */
426 |
427 | /** `void c8_disasm_start();` \
428 | * Initializes the variables used by the disassembler to track its state.
429 | */
430 | void c8_disasm_start();
431 |
432 | /** `void c8_disasm_reachable(uint16_t addr)` \
433 | *
434 | * Marks an address in the program as reachable through a jump instruction.
435 | *
436 | * The disassembler follows the different branches in a program to determine
437 | * which instructions are reachable. It does this to determine which bytes in
438 | * the program are code and which are data. Unfortunately this does not work for
439 | * the `JP V0, nnn` (`Bnnn`) instruction, because it won't know what the value
440 | * in `V0` is (and `Bnnn`'s behaviour also depends on the `QUIRKS_JUMP` quirk).
441 | *
442 | * If you can determine the `Bnnn`'s intended destination, you could mark
443 | * that destination as reachable, and rerun the disassembler.
444 | */
445 | void c8_disasm_reachable(uint16_t addr);
446 |
447 | /** `void c8_disasm();` \
448 | * Disassembles the program currently in the interpreter's RAM.
449 | *
450 | * The output is written through `c8_puts()`.
451 | *
452 | * See `dasmmain.c` for an example of a program that uses this function.
453 | */
454 | void c8_disasm();
455 |
456 | /**
457 | * [wikipedia]: https://en.wikipedia.org/wiki/CHIP-8
458 | *
459 | */
460 |
--------------------------------------------------------------------------------
/render.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #if defined(SDL2) || defined(SDL) || defined(__EMSCRIPTEN__)
12 | # include "sdl/pocadv.h"
13 | #else
14 | # include
15 | # include "gdi/gdi.h"
16 | #endif
17 |
18 | #ifndef CRT_BLUR
19 | # define CRT_BLUR 0
20 | #endif
21 | #ifndef CRT_NOISE
22 | # define CRT_NOISE 0
23 | #endif
24 |
25 | #include "chip8.h"
26 | #include "bmp.h"
27 |
28 | /* number of instructions to execute per second */
29 | static int speed = 1200;
30 |
31 | /* Foreground color */
32 | static int fg_color = 0xAAAAFF;
33 |
34 | /* Background color */
35 | static int bg_color = 0x000055;
36 |
37 | /* Is the interpreter running? Set to 0 to enter "debug" mode */
38 | static int running = 1;
39 |
40 | static Bitmap *chip8_screen;
41 | static Bitmap *hud;
42 |
43 | /* These are the same keybindings [Octo][]'s */
44 | static unsigned int Key_Mapping[16] = {
45 | #if defined(SDL) || defined(SDL2)
46 | KCODEA(x,X),
47 | KCODE(1),
48 | KCODE(2),
49 | KCODE(3),
50 | KCODEA(q,Q),
51 | KCODEA(w,W),
52 | KCODEA(e,E),
53 | KCODEA(a,A),
54 | KCODEA(s,S),
55 | KCODEA(d,D),
56 | KCODEA(z,Z),
57 | KCODEA(c,C),
58 | KCODE(4),
59 | KCODEA(r,R),
60 | KCODEA(f,F),
61 | KCODEA(v,V)
62 | #else
63 | 0x58, /* '0' -> 'x' */
64 | 0x31, /* '1' -> '1' */
65 | 0x32, /* '2' -> '2' */
66 | 0x33, /* '3' -> '3' */
67 | 0x51, /* '4' -> 'q' */
68 | 0x57, /* '5' -> 'w' */
69 | 0x45, /* '6' -> 'e' */
70 | 0x41, /* '7' -> 'a' */
71 | 0x53, /* '8' -> 's' */
72 | 0x44, /* '9' -> 'd' */
73 | 0x5A, /* 'A' -> 'z' */
74 | 0x43, /* 'B' -> 'c' */
75 | 0x34, /* 'C' -> '4' */
76 | 0x52, /* 'D' -> 'r' */
77 | 0x46, /* 'E' -> 'f' */
78 | 0x56, /* 'F' -> 'v' */
79 | #endif
80 | };
81 |
82 | static void draw_screen();
83 |
84 | static void usage() {
85 | exit_error("Use these command line variables:\n"
86 | " -f fg : Foreground color\n"
87 | " -b bg : Background color\n"
88 | " -s spd : Specify the speed\n"
89 | " -d : Debug mode\n"
90 | " -v : increase verbosity\n"
91 | " -q quirks : sets the quirks mode\n"
92 | " `quirks` can be a comma separated combination\n"
93 | " of `none`, `vf`, `mem`, `disp`, `clip`, `shift`,\n"
94 | " `jump`, `default`, `chip8` and `schip`\n"
95 | " -a addr=val : Sets the byte in RAM at `addr` to\n"
96 | " the value `val` before executing.\n"
97 | " -h : Displays this help\n"
98 | );
99 | }
100 |
101 | #ifdef __EMSCRIPTEN__
102 | static int em_ready;
103 | void loaded_callback_func(const char *infile) {
104 | rlog("Loading %s...", infile);
105 | if(!c8_load_file(infile)) {
106 | exit_error("Unable to load '%s': %s\n", infile, strerror(errno));
107 | return;
108 | }
109 | em_ready = 1;
110 | }
111 | void error_callback_func(const char *s) {
112 | rerror("Error loading %s", s);
113 | }
114 | #endif
115 |
116 | /*
117 | * This is an example of how you can write a CHIP8 program that can access the
118 | * world outside by invoking the `SYS nnn` (`0nnn`) instruction
119 | *
120 | * ```
121 | * CLS
122 | * LD V0, #AB
123 | * SYS 2
124 | * LD V0, #CD
125 | * SYS 2
126 | * LD V0, #EF
127 | * SYS 2
128 | * LD I, string
129 | * SYS 1
130 | * EXIT
131 | * string: text "Hello World!"
132 | * ```
133 | */
134 | int example_sys_hook(unsigned int nnn) {
135 | switch(nnn) {
136 | case 1: {
137 | char *str = (char*)&C8.RAM[C8.I];
138 | rlog("console: %s", str);
139 | } break;
140 | case 2: {
141 | rlog("console: V0: %02X", C8.V[0]);
142 | } break;
143 | case 3: {
144 | rlog("console: Halting interpreter; VF: %02X", C8.V[0xF]);
145 | return 0;
146 | } break;
147 | default: break;
148 | }
149 | return 1;
150 | }
151 |
152 | void init_game(int argc, char *argv[]) {
153 |
154 | const char *infile = NULL;
155 |
156 | rlog("Initializing...");
157 |
158 | srand(time(NULL));
159 |
160 | c8_reset();
161 |
162 | fg_color = bm_byte_order(fg_color);
163 | bg_color = bm_byte_order(bg_color);
164 |
165 | int opt;
166 | while((opt = getopt(argc, argv, "f:b:s:dvhq:m:")) != -1) {
167 | switch(opt) {
168 | case 'v': c8_verbose++; break;
169 | case 'f': fg_color = bm_atoi(optarg); break;
170 | case 'b': bg_color = bm_atoi(optarg); break;
171 | case 's': speed = atoi(optarg); if(speed < 1) speed = 10; break;
172 | case 'd': running = 0; break;
173 | case 'q': {
174 | unsigned int quirks = 0;
175 | char *token = strtok(optarg, ",");
176 | while (token) {
177 | if(!strcmp(token, "none")) quirks = 0;
178 | else if(!strcmp(token, "vf")) quirks |= QUIRKS_VF_RESET;
179 | else if(!strcmp(token, "mem")) quirks |= QUIRKS_MEM_CHIP8;
180 | else if(!strcmp(token, "disp")) quirks |= QUIRKS_DISP_WAIT;
181 | else if(!strcmp(token, "clip")) quirks |= QUIRKS_CLIPPING;
182 | else if(!strcmp(token, "shift")) quirks |= QUIRKS_SHIFT;
183 | else if(!strcmp(token, "jump")) quirks |= QUIRKS_JUMP;
184 | else if(!strcmp(token, "default")) quirks |= QUIRKS_DEFAULT;
185 | else if(!strcmp(token, "chip8")) quirks |= QUIRKS_CHIP8;
186 | else if(!strcmp(token, "schip")) quirks |= QUIRKS_SCHIP;
187 | else rerror("warning: unknown quirk '%s'", token);
188 | token = strtok(NULL, ",");
189 | }
190 | c8_set_quirks(quirks);
191 | } break;
192 | case 'm': {
193 | int addr, val;
194 | char *token = strtok(optarg, ",");
195 | while (token) {
196 | char *delim = strchr(token, '=');
197 | if(!delim) {
198 | exit_error("error: bad field for -m; expected `addr=value`, got `%s`", token);
199 | }
200 |
201 | *delim = '\0';
202 | delim++;
203 | addr = strtol(token, NULL, 0);
204 | val = strtol(delim, NULL, 0);
205 | if(addr < 0 || addr >= TOTAL_RAM)
206 | exit_error("error: bad address for -m: 0 <= addr < %d", TOTAL_RAM);
207 | if(val < 0 || val >= 256)
208 | exit_error("error: bad address for -m: 0 <= val < 256");
209 |
210 | c8_set(addr, val);
211 |
212 | token = strtok(NULL, ",");
213 | }
214 | } break;
215 | case 'h': usage(); break;
216 | }
217 | }
218 |
219 | if(optind >= argc) {
220 | exit_error("You need to specify a CHIP-8 file.\n");
221 | }
222 | infile = argv[optind++];
223 |
224 | c8_sys_hook = example_sys_hook;
225 |
226 | #ifdef __EMSCRIPTEN__
227 | em_ready = 0;
228 | rlog("emscripten_wget retrieving %s", infile);
229 | //emscripten_wget(infile, infile);
230 | emscripten_async_wget(infile, infile, loaded_callback_func, error_callback_func);
231 | #else
232 | rlog("Loading %s...", infile);
233 | if(!c8_load_file(infile)) {
234 | exit_error("Unable to load '%s': %s\n", infile, strerror(errno));
235 | }
236 | #endif
237 |
238 | bm_set_color(screen, 0x202020);
239 | bm_clear(screen);
240 |
241 | chip8_screen = bm_create(128, 64);
242 |
243 | draw_screen();
244 |
245 | #ifdef __EMSCRIPTEN__
246 | /* I couldn't figure out why this is necessary on the emscripten port: */
247 | Key_Mapping[0] = KCODEA(x,X);
248 | Key_Mapping[1] = KCODE(1);
249 | Key_Mapping[2] = KCODE(2);
250 | Key_Mapping[3] = KCODE(3);
251 | Key_Mapping[4] = KCODEA(q,Q);
252 | Key_Mapping[5] = KCODEA(w,W);
253 | Key_Mapping[6] = KCODEA(e,E);
254 | Key_Mapping[7] = KCODEA(a,A);
255 | Key_Mapping[8] = KCODEA(s,S);
256 | Key_Mapping[9] = KCODEA(d,D);
257 | Key_Mapping[10] = KCODEA(z,Z);
258 | Key_Mapping[11] = KCODEA(c,C);
259 | Key_Mapping[12] = KCODE(4);
260 | Key_Mapping[13] = KCODEA(r,R);
261 | Key_Mapping[14] = KCODEA(f,F);
262 | Key_Mapping[15] = KCODEA(v,V);
263 | #endif
264 |
265 | hud = bm_create(128, 24);
266 | if(!hud)
267 | exit_error("unable to create HUD");
268 |
269 | rlog("Initialized.");
270 | }
271 |
272 | void deinit_game() {
273 | bm_free(hud);
274 | bm_free(chip8_screen);
275 | rlog("Done.");
276 | }
277 |
278 | #if CRT_BLUR
279 | static void add_bitmaps(Bitmap *b1, Bitmap *b2) {
280 | int x,y;
281 | assert(b1->w == b2->w && b1->h == b2->h);
282 | for(y = 0; y < b1->h; y++) {
283 | for(x = 0; x < b1->w; x++) {
284 | unsigned int c1 = bm_get(b1, x, y);
285 | unsigned int c2 = bm_get(b2, x, y);
286 | unsigned int c3 = bm_lerp(c1, c2, 0.6);
287 | bm_set(b1, x, y, c3);
288 | }
289 | }
290 | }
291 |
292 | static unsigned char oldscreen_buffer[SCREEN_WIDTH * SCREEN_HEIGHT * 4];
293 | static unsigned char plotscreen_buffer[SCREEN_WIDTH * SCREEN_HEIGHT * 4];
294 | #endif
295 | #if CRT_NOISE
296 | static long nz_seed = 0L;
297 | static long nz_rand() {
298 | nz_seed = nz_seed * 1103515245L + 12345L;
299 | return (nz_seed >> 16) & 0x7FFF;
300 | }
301 | static void nz_srand(long seed) {
302 | nz_seed = seed;
303 | }
304 | static unsigned int noise(int x, int y, unsigned int col_in) {
305 | unsigned char R, G, B;
306 | bm_get_rgb(col_in, &R, &G, &B);
307 | /*
308 | https://en.wikipedia.org/wiki/Linear_congruential_generator
309 | */
310 | int val = (int)(nz_rand() & 0x7) - 4;
311 | if(x & 0x01) val-=4;
312 | if(y & 0x02) val-=4;
313 |
314 | int iR = R + val, iG = G + val, iB = B + val;
315 | if(iR > 0xFF) iR = 0xFF;
316 | if(iR < 0) iR = 0;
317 | if(iG > 0xFF) iG = 0xFF;
318 | if(iG < 0) iG = 0;
319 | if(iB > 0xFF) iB = 0xFF;
320 | if(iB < 0) iB = 0;
321 | return bm_rgb(iR, iG, iB);
322 | }
323 | #endif
324 |
325 | static void chip8_to_bmp(Bitmap *sbmp) {
326 | int x, y, w, h;
327 |
328 | c8_resolution(&w, &h);
329 |
330 | assert(w <= bm_width(sbmp));
331 | assert(h <= bm_height(sbmp));
332 | //bm_bind_static(sbmp, chip8_screen_buffer, w, h);
333 |
334 | for(y = 0; y < h; y++) {
335 | for(x = 0; x < w; x++) {
336 | unsigned int c = c8_get_pixel(x,y) ? fg_color : bg_color;
337 | bm_set(sbmp, x, y, c);
338 | }
339 | }
340 | }
341 |
342 | static void draw_screen() {
343 | int w, h;
344 |
345 | chip8_to_bmp(chip8_screen);
346 | c8_resolution(&w, &h);
347 |
348 | #if CRT_BLUR
349 | /* FIXME: This won't work anymore on the new BMP API */
350 | Bitmap plotscreen;
351 | bm_bind_static(&plotscreen, plotscreen_buffer, SCREEN_WIDTH, SCREEN_HEIGHT);
352 |
353 | Bitmap oldscreen;
354 | bm_bind_static(&oldscreen, oldscreen_buffer, SCREEN_WIDTH, SCREEN_HEIGHT);
355 | memcpy(oldscreen.data, plotscreen.data, SCREEN_WIDTH * SCREEN_HEIGHT * 4);
356 |
357 | bm_smooth(&oldscreen);
358 | bm_smooth(&oldscreen);
359 | bm_blit_ex(screen, 0, 0, screen->w, screen->h, &chip8_screen, 0, 0, w, h, 0);
360 | add_bitmaps(screen, &oldscreen);
361 |
362 | float smooth_kernel[] = { 0.0, 0.1, 0.0,
363 | 0.1, 0.6, 0.1,
364 | 0.0, 0.1, 0.0};
365 | bm_apply_kernel(screen, 3, smooth_kernel);
366 | #else
367 | bm_blit_ex(screen, 0, 0, bm_width(screen), bm_height(screen), chip8_screen, 0, 0, w, h, 0);
368 | #endif
369 |
370 | #if CRT_NOISE
371 | int x, y;
372 | nz_srand(1234);
373 | for(y = 0; y < screen->h; y++) {
374 | for(x = 0; x < screen->w; x++) {
375 | unsigned int c = bm_get(screen, x, y);
376 | c = noise(x, y, c);
377 | bm_set(screen, x, y, c);
378 | }
379 | }
380 | #endif
381 |
382 | #if CRT_BLUR
383 | memcpy(plotscreen.data, screen->data, SCREEN_WIDTH * SCREEN_HEIGHT * 4);
384 | #endif
385 | }
386 |
387 | void bm_blit_blend(Bitmap *dst, int dx, int dy, Bitmap *src, int sx, int sy, int w, int h);
388 |
389 |
390 | void draw_hud() {
391 | int i;
392 |
393 | // Bitmap hud;
394 | // static unsigned char hud_buffer[128 * 24 * 4];
395 | // bm_bind_static(&hud, hud_buffer, 128, 24);
396 |
397 | uint16_t pc = c8_get_pc();
398 | uint16_t opcode = c8_opcode(pc);
399 | bm_set_color(hud, 0x202020);
400 | bm_clear(hud);
401 | bm_set_color(hud, 0xFFFFFF);
402 | bm_printf(hud, 1, 0, "%03X %04X", pc, opcode);
403 | for(i = 0; i < 16; i++) {
404 | bm_printf(hud, (i & 0x07) * 16, (i >> 3) * 8 + 8, "%02X", c8_get_reg(i));
405 | }
406 |
407 | bm_blit_blend(screen, 0, bm_height(screen) - 24, hud, 0, 0, bm_width(hud), bm_height(hud));
408 | }
409 |
410 | int render(double elapsedSeconds) {
411 | int i;
412 | static double timer = 0.0;
413 |
414 | #ifdef __EMSCRIPTEN__
415 | if(!em_ready) return 1;
416 | #endif
417 |
418 | int key_pressed = 0;
419 | for(i = 0; i < 16; i++) {
420 | int k = Key_Mapping[i];
421 | if(keys[k]) {
422 | key_pressed = 1;
423 | c8_key_down(i);
424 | #if !defined(NDEBUG) && 0
425 | rlog("key pressed: %X 0x%02X", i, k);
426 | #endif
427 | } else
428 | c8_key_up(i);
429 | }
430 |
431 | timer += elapsedSeconds;
432 | while(timer > 1.0/60.0) {
433 | c8_60hz_tick();
434 | timer -= 1.0/60.0;
435 | }
436 |
437 | if(running) {
438 | /* F5 breaks the program and enters debugging mode */
439 | if(keys[KCODE(F5)])
440 | running = 0;
441 |
442 | /* instructions per second * elapsed seconds = number of instructions to execute */
443 | int count = speed * elapsedSeconds;
444 | for(i = 0; i < count; i++) {
445 | if(c8_ended())
446 | return 0;
447 | else if(c8_waitkey() && !key_pressed)
448 | return 1;
449 |
450 | c8_step();
451 |
452 | if(c8_screen_updated())
453 | draw_screen();
454 | }
455 | } else {
456 | /* Debugging mode:
457 | F6 steps through the program
458 | F8 resumes
459 | */
460 | if(keys[KCODE(F8)]) {
461 | // bm_set_color(screen, 0x202020);
462 | // bm_fillrect(screen, 0, screen->h - 24, screen->w, screen->h);
463 | running = 1;
464 | return 1;
465 | }
466 | if(keys[KCODE(F6)]) {
467 | if(c8_ended())
468 | return 0;
469 | else if(c8_waitkey() && !key_pressed)
470 | return 1;
471 | c8_step();
472 | if(c8_screen_updated()) {
473 | draw_screen();
474 | }
475 | keys[KCODE(F6)] = 0;
476 | }
477 |
478 | draw_screen();
479 | draw_hud();
480 | }
481 |
482 | return 1;
483 | }
484 |
485 |
486 | void bm_blit_blend(Bitmap *dst, int dx, int dy, Bitmap *src, int sx, int sy, int w, int h) {
487 | int x,y, i, j;
488 |
489 | BmRect destClip = bm_get_clip(dst);
490 |
491 | if(sx < 0) {
492 | int delta = -sx;
493 | sx = 0;
494 | dx += delta;
495 | w -= delta;
496 | }
497 |
498 | if(dx < destClip.x0) {
499 | int delta = destClip.x0 - dx;
500 | sx += delta;
501 | w -= delta;
502 | dx = destClip.x0;
503 | }
504 |
505 | if(sx + w > bm_width(src)) {
506 | int delta = sx + w - bm_width(src);
507 | w -= delta;
508 | }
509 |
510 | if(dx + w > destClip.x1) {
511 | int delta = dx + w - destClip.x1;
512 | w -= delta;
513 | }
514 |
515 | if(sy < 0) {
516 | int delta = -sy;
517 | sy = 0;
518 | dy += delta;
519 | h -= delta;
520 | }
521 |
522 | if(dy < destClip.y0) {
523 | int delta = destClip.y0 - dy;
524 | sy += delta;
525 | h -= delta;
526 | dy = destClip.y0;
527 | }
528 |
529 | if(sy + h > bm_height(src)) {
530 | int delta = sy + h - bm_height(src);
531 | h -= delta;
532 | }
533 |
534 | if(dy + h > destClip.y1) {
535 | int delta = dy + h - destClip.y1;
536 | h -= delta;
537 | }
538 |
539 | if(w <= 0 || h <= 0)
540 | return;
541 | if(dx >= destClip.x1 || dx + w < destClip.x0)
542 | return;
543 | if(dy >= destClip.y1 || dy + h < destClip.y0)
544 | return;
545 | if(sx >= bm_width(src) || sx + w < 0)
546 | return;
547 | if(sy >= bm_height(src) || sy + h < 0)
548 | return;
549 |
550 | if(sx + w > bm_width(src)) {
551 | int delta = sx + w - bm_width(src);
552 | w -= delta;
553 | }
554 |
555 | if(sy + h > bm_height(src)) {
556 | int delta = sy + h - bm_height(src);
557 | h -= delta;
558 | }
559 |
560 | assert(dx >= 0 && dx + w <= destClip.x1);
561 | assert(dy >= 0 && dy + h <= destClip.y1);
562 | assert(sx >= 0 && sx + w <= bm_width(src));
563 | assert(sy >= 0 && sy + h <= bm_height(src));
564 |
565 | j = sy;
566 | for(y = dy; y < dy + h; y++) {
567 | i = sx;
568 | for(x = dx; x < dx + w; x++) {
569 | unsigned int c1 = (bm_get(src, i, j) >> 1) & 0x7F7F7F;
570 | unsigned int c2 = (bm_get(dst, x, y) >> 1) & 0x7F7F7F;
571 | bm_set(dst, x, y, c1 + c2);
572 | i++;
573 | }
574 | j++;
575 | }
576 | }
577 |
--------------------------------------------------------------------------------
/chip8.c:
--------------------------------------------------------------------------------
1 | /*
2 | Core of the CHIP-8 interpreter.
3 | This file should be kept platform independent. Everything that is
4 | platform dependent should be moved elsewhere.
5 |
6 |
7 | TODO: Apparently SuperChip 1.0 and 1.1 has a lot of caveats that
8 | I haven't addressed in my implementation
9 | https://chip-8.github.io/extensions/#super-chip-10
10 | */
11 |
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | #include "chip8.h"
21 |
22 |
23 | static unsigned int quirks = QUIRKS_DEFAULT;
24 |
25 | void c8_set_quirks(unsigned int q) {
26 | quirks = q;
27 | }
28 |
29 | unsigned int c8_get_quirks() {
30 | return quirks;
31 | }
32 |
33 | /* Where in RAM to load the font.
34 | The font should be in the first 512 bytes of RAM (see [2]),
35 | so FONT_OFFSET should be less than or equal to 0x1B0 */
36 | #define FONT_OFFSET 0x1B0
37 | #define HFONT_OFFSET 0x110
38 |
39 | int c8_verbose = 0;
40 |
41 | chip8_t C8;
42 |
43 | /* Display memory */
44 | static uint8_t pixels[1024];
45 |
46 | static int yield = 0, borked = 0;
47 |
48 | static int screen_updated; /* Screen updated */
49 | static int hi_res; /* Hi-res mode? */
50 |
51 | /* Keypad buffer */
52 | static uint16_t keys;
53 |
54 | /* HP48 flags for SuperChip Fx75 and Fx85 instructions */
55 | static uint8_t hp48_flags[16];
56 |
57 | /* Text output function */
58 | char c8_message_text[MAX_MESSAGE_TEXT];
59 | static int _puts_default(const char* s) {
60 | return fputs(s, stdout);
61 | }
62 | int (*c8_puts)(const char* s) = _puts_default;
63 |
64 | int (*c8_rand)() = rand;
65 |
66 | c8_sys_hook_t c8_sys_hook = NULL;
67 |
68 | /* Standard 4x5 font */
69 | static const uint8_t font[] = {
70 | /* '0' */ 0xF0, 0x90, 0x90, 0x90, 0xF0,
71 | /* '1' */ 0x20, 0x60, 0x20, 0x20, 0x70,
72 | /* '2' */ 0xF0, 0x10, 0xF0, 0x80, 0xF0,
73 | /* '3' */ 0xF0, 0x10, 0xF0, 0x10, 0xF0,
74 | /* '4' */ 0x90, 0x90, 0xF0, 0x10, 0x10,
75 | /* '5' */ 0xF0, 0x80, 0xF0, 0x10, 0xF0,
76 | /* '6' */ 0xF0, 0x80, 0xF0, 0x90, 0xF0,
77 | /* '7' */ 0xF0, 0x10, 0x20, 0x40, 0x40,
78 | /* '8' */ 0xF0, 0x90, 0xF0, 0x90, 0xF0,
79 | /* '9' */ 0xF0, 0x90, 0xF0, 0x10, 0xF0,
80 | /* 'A' */ 0xF0, 0x90, 0xF0, 0x90, 0x90,
81 | /* 'B' */ 0xE0, 0x90, 0xE0, 0x90, 0xE0,
82 | /* 'C' */ 0xF0, 0x80, 0x80, 0x80, 0xF0,
83 | /* 'D' */ 0xE0, 0x90, 0x90, 0x90, 0xE0,
84 | /* 'E' */ 0xF0, 0x80, 0xF0, 0x80, 0xF0,
85 | /* 'F' */ 0xF0, 0x80, 0xF0, 0x80, 0x80,
86 | };
87 |
88 | /* SuperChip hi-res 8x10 font. */
89 | static const uint8_t hfont[] = {
90 | /* '0' */ 0x7C, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7C, 0x00,
91 | /* '1' */ 0x08, 0x18, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3C, 0x00,
92 | /* '2' */ 0x7C, 0x82, 0x02, 0x02, 0x04, 0x18, 0x20, 0x40, 0xFE, 0x00,
93 | /* '3' */ 0x7C, 0x82, 0x02, 0x02, 0x3C, 0x02, 0x02, 0x82, 0x7C, 0x00,
94 | /* '4' */ 0x84, 0x84, 0x84, 0x84, 0xFE, 0x04, 0x04, 0x04, 0x04, 0x00,
95 | /* '5' */ 0xFE, 0x80, 0x80, 0x80, 0xFC, 0x02, 0x02, 0x82, 0x7C, 0x00,
96 | /* '6' */ 0x7C, 0x82, 0x80, 0x80, 0xFC, 0x82, 0x82, 0x82, 0x7C, 0x00,
97 | /* '7' */ 0xFE, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x20, 0x00,
98 | /* '8' */ 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x00,
99 | /* '9' */ 0x7C, 0x82, 0x82, 0x82, 0x7E, 0x02, 0x02, 0x82, 0x7C, 0x00,
100 | /* 'A' */ 0x10, 0x28, 0x44, 0x82, 0x82, 0xFE, 0x82, 0x82, 0x82, 0x00,
101 | /* 'B' */ 0xFC, 0x82, 0x82, 0x82, 0xFC, 0x82, 0x82, 0x82, 0xFC, 0x00,
102 | /* 'C' */ 0x7C, 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x82, 0x7C, 0x00,
103 | /* 'D' */ 0xFC, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0xFC, 0x00,
104 | /* 'E' */ 0xFE, 0x80, 0x80, 0x80, 0xF8, 0x80, 0x80, 0x80, 0xFE, 0x00,
105 | /* 'F' */ 0xFE, 0x80, 0x80, 0x80, 0xF8, 0x80, 0x80, 0x80, 0x80, 0x00,
106 | };
107 |
108 | void c8_reset() {
109 | memset(C8.V, 0, sizeof C8.V);
110 | memset(C8.RAM, 0, sizeof C8.RAM);
111 | C8.PC = PROG_OFFSET;
112 | C8.I = 0;
113 | C8.DT = 0;
114 | C8.ST = 0;
115 | C8.SP = 0;
116 | memset(C8.stack, 0, sizeof C8.stack);
117 |
118 | assert(FONT_OFFSET + sizeof font <= PROG_OFFSET);
119 | memcpy(C8.RAM + FONT_OFFSET, font, sizeof font);
120 | assert(HFONT_OFFSET + sizeof hfont <= FONT_OFFSET);
121 | memcpy(C8.RAM + HFONT_OFFSET, hfont, sizeof hfont);
122 |
123 | hi_res = 0;
124 | screen_updated = 0;
125 | yield = 0;
126 | borked = 0;
127 | }
128 |
129 | void c8_step() {
130 | assert(C8.PC < TOTAL_RAM);
131 |
132 | if(yield || borked) return;
133 |
134 | uint16_t opcode = C8.RAM[C8.PC] << 8 | C8.RAM[C8.PC+1];
135 | C8.PC += 2;
136 |
137 | uint8_t x = (opcode >> 8) & 0x0F;
138 | uint8_t y = (opcode >> 4) & 0x0F;
139 | uint8_t nibble = opcode & 0x0F;
140 | uint16_t nnn = opcode & 0x0FFF;
141 | uint8_t kk = opcode & 0xFF;
142 |
143 | int row, col;
144 |
145 | screen_updated = 0;
146 |
147 | switch(opcode & 0xF000) {
148 | case 0x0000:
149 | if(opcode == 0x00E0) {
150 | /* CLS */
151 | memset(pixels, 0, sizeof pixels);
152 | screen_updated = 1;
153 | } else if(opcode == 0x00EE) {
154 | /* RET */
155 | if(C8.SP == 0){
156 | /* You've got problems */
157 | borked = 1;
158 | return;
159 | }
160 | C8.PC = C8.stack[--C8.SP];
161 | } else if((opcode & 0xFFF0) == 0x00C0) {
162 | /* SCD nibble */
163 | c8_resolution(&col, &row);
164 | row--;
165 | col >>= 3;
166 | while(row - nibble >= 0) {
167 | memcpy(pixels + row * col, pixels + (row - nibble) * col, col);
168 | row--;
169 | }
170 | memset(pixels, 0x0, nibble * col);
171 | screen_updated = 1;
172 | } else if(opcode == 0x00FB) {
173 | /* SCR */
174 | c8_resolution(&col, &row);
175 | col >>= 3;
176 | for(y = 0; y < row; y++) {
177 | for(x = col - 1; x > 0; x--) {
178 | pixels[y * col + x] = (pixels[y * col + x] << 4) | (pixels[y * col + x - 1] >> 4);
179 | }
180 | pixels[y * col] <<= 4;
181 | }
182 | screen_updated = 1;
183 | } else if(opcode == 0x00FC) {
184 | /* SCL */
185 | c8_resolution(&col, &row);
186 | col >>= 3;
187 | for(y = 0; y < row; y++) {
188 | for(x = 0; x < col - 1; x++) {
189 | pixels[y * col + x] = (pixels[y * col + x] >> 4) | (pixels[y * col + x + 1] << 4);
190 | }
191 | pixels[y * col + x] >>= 4;
192 | }
193 | screen_updated = 1;
194 | } else if(opcode == 0x00FD) {
195 | /* EXIT */
196 | C8.PC -= 2; /* reset the PC to the 00FD */
197 | /* subsequent calls will encounter the 00FD again,
198 | and c8_ended() will return 1 */
199 | return;
200 | } else if(opcode == 0x00FE) {
201 | /* LOW */
202 | if(hi_res)
203 | screen_updated = 1;
204 | hi_res = 0;
205 | } else if(opcode == 0x00FF) {
206 | /* HIGH */
207 | if(!hi_res)
208 | screen_updated = 1;
209 | hi_res = 1;
210 | } else {
211 | /* SYS: If there's a hook, call it otherwise treat it as a no-op */
212 | if(c8_sys_hook) {
213 | int result = c8_sys_hook(nnn);
214 | if(!result)
215 | borked = 1;
216 | }
217 | }
218 | break;
219 | case 0x1000:
220 | /* JP nnn */
221 | C8.PC = nnn;
222 | break;
223 | case 0x2000:
224 | /* CALL nnn */
225 | if(C8.SP >= 16) return; /* See RET */
226 | C8.stack[C8.SP++] = C8.PC;
227 | C8.PC = nnn;
228 | break;
229 | case 0x3000:
230 | /* SE Vx, kk */
231 | if(C8.V[x] == kk) C8.PC += 2;
232 | break;
233 | case 0x4000:
234 | /* SNE Vx, kk */
235 | if(C8.V[x] != kk) C8.PC += 2;
236 | break;
237 | case 0x5000:
238 | /* SE Vx, Vy */
239 | if(C8.V[x] == C8.V[y]) C8.PC += 2;
240 | break;
241 | case 0x6000:
242 | /* LD Vx, kk */
243 | C8.V[x] = kk;
244 | break;
245 | case 0x7000:
246 | /* ADD Vx, kk */
247 | C8.V[x] += kk;
248 | break;
249 | case 0x8000: {
250 | uint16_t ans, carry;
251 | switch(nibble) {
252 | case 0x0:
253 | /* LD Vx, Vy */
254 | C8.V[x] = C8.V[y];
255 | break;
256 | case 0x1:
257 | /* OR Vx, Vy */
258 | C8.V[x] |= C8.V[y];
259 | if(quirks & QUIRKS_VF_RESET)
260 | C8.V[0xF] = 0;
261 | break;
262 | case 0x2:
263 | /* AND Vx, Vy */
264 | C8.V[x] &= C8.V[y];
265 | if(quirks & QUIRKS_VF_RESET)
266 | C8.V[0xF] = 0;
267 | break;
268 | case 0x3:
269 | /* XOR Vx, Vy */
270 | C8.V[x] ^= C8.V[y];
271 | if(quirks & QUIRKS_VF_RESET)
272 | C8.V[0xF] = 0;
273 | break;
274 | case 0x4:
275 | /* ADD Vx, Vy */
276 | ans = C8.V[x] + C8.V[y];
277 | C8.V[x] = ans & 0xFF;
278 | C8.V[0xF] = (ans > 255);
279 | break;
280 | case 0x5:
281 | /* SUB Vx, Vy */
282 | ans = C8.V[x] - C8.V[y];
283 | carry = (C8.V[x] > C8.V[y]);
284 | C8.V[x] = ans & 0xFF;
285 | C8.V[0xF] = carry;
286 | break;
287 | case 0x6:
288 | /* SHR Vx, Vy */
289 | if(!(quirks & QUIRKS_SHIFT))
290 | C8.V[x] = C8.V[y];
291 | carry = (C8.V[x] & 0x01);
292 | C8.V[x] >>= 1;
293 | C8.V[0xF] = carry;
294 | break;
295 | case 0x7:
296 | /* SUBN Vx, Vy */
297 | ans = C8.V[y] - C8.V[x];
298 | carry = (C8.V[y] > C8.V[x]);
299 | C8.V[x] = ans & 0xFF;
300 | C8.V[0xF] = carry;
301 | break;
302 | case 0xE:
303 | /* SHL Vx, Vy */
304 | if(!(quirks & QUIRKS_SHIFT))
305 | C8.V[x] = C8.V[y];
306 | carry = ((C8.V[x] & 0x80) != 0);
307 | C8.V[x] <<= 1;
308 | C8.V[0xF] = carry;
309 | break;
310 | }
311 | } break;
312 | case 0x9000:
313 | /* SNE Vx, Vy */
314 | if(C8.V[x] != C8.V[y]) C8.PC += 2;
315 | break;
316 | case 0xA000:
317 | /* LD I, nnn */
318 | C8.I = nnn;
319 | break;
320 | case 0xB000:
321 | /* JP V0, nnn */
322 | if(quirks & QUIRKS_JUMP)
323 | C8.PC = (nnn + C8.V[x]) & 0xFFF;
324 | else
325 | C8.PC = (nnn + C8.V[0]) & 0xFFF;
326 | break;
327 | case 0xC000:
328 | /* RND Vx, kk */
329 | C8.V[x] = c8_rand() & kk; /* FIXME: Better RNG? */
330 | break;
331 | case 0xD000: {
332 | /* DRW Vx, Vy, nibble */
333 | int mW, mH, W, H, p, q;
334 | int tx, ty, byte, bit, pix;
335 |
336 | /* TODO: [17] mentions that V[x] and V[y] gets modified by
337 | this instruction... */
338 |
339 | if(hi_res) {
340 | W = 128; H = 64; mW = 0x7F; mH = 0x3F;
341 | } else {
342 | W = 64; H = 32; mW = 0x3F; mH = 0x1F;
343 | }
344 |
345 | C8.V[0xF] = 0;
346 | if(nibble) {
347 | x = C8.V[x]; y = C8.V[y];
348 | x &= mW;
349 | y &= mH;
350 | for(q = 0; q < nibble; q++) {
351 | ty = (y + q);
352 | if((quirks & QUIRKS_CLIPPING) && (ty >= H))
353 | break;
354 |
355 | for(p = 0; p < 8; p++) {
356 | tx = (x + p);
357 | if((quirks & QUIRKS_CLIPPING) && (tx >= W))
358 | break;
359 | pix = (C8.RAM[C8.I + q] & (0x80 >> p)) != 0;
360 | if(pix) {
361 | tx &= mW;
362 | ty &= mH;
363 | byte = ty * W + tx;
364 | bit = 1 << (byte & 0x07);
365 | byte >>= 3;
366 | if(pixels[byte] & bit)
367 | C8.V[0x0F] = 1;
368 | pixels[byte] ^= bit;
369 | }
370 | }
371 | }
372 | } else {
373 | /* SCHIP mode has a 16x16 sprite if nibble == 0 */
374 | x = C8.V[x]; y = C8.V[y];
375 | x &= mW;
376 | y &= mH;
377 | for(q = 0; q < 16; q++) {
378 | ty = (y + q);
379 | if((quirks & QUIRKS_CLIPPING) && (ty >= H))
380 | break;
381 |
382 | for(p = 0; p < 16; p++) {
383 | tx = (x + p);
384 | if((quirks & QUIRKS_CLIPPING) && (tx >= W))
385 | break;
386 |
387 | if(p >= 8)
388 | pix = (C8.RAM[C8.I + (q * 2) + 1] & (0x80 >> (p & 0x07))) != 0;
389 | else
390 | pix = (C8.RAM[C8.I + (q * 2)] & (0x80 >> p)) != 0;
391 | if(pix) {
392 | byte = ty * W + tx;
393 | bit = 1 << (byte & 0x07);
394 | byte >>= 3;
395 | if(pixels[byte] & bit)
396 | C8.V[0x0F] = 1;
397 | pixels[byte] ^= bit;
398 | }
399 | }
400 | }
401 | }
402 | screen_updated = 1;
403 | if(quirks & QUIRKS_DISP_WAIT) {
404 | yield = 1;
405 | }
406 | } break;
407 | case 0xE000: {
408 | if(kk == 0x9E) {
409 | /* SKP Vx */
410 | if(keys & (1 << C8.V[x]))
411 | C8.PC += 2;
412 | } else if(kk == 0xA1) {
413 | /* SKNP Vx */
414 | if(!(keys & (1 << C8.V[x])))
415 | C8.PC += 2;
416 | }
417 | } break;
418 | case 0xF000: {
419 | switch(kk) {
420 | case 0x07:
421 | /* LD Vx, DT */
422 | C8.V[x] = C8.DT;
423 | break;
424 | case 0x0A: {
425 | /* LD Vx, K */
426 | if(!keys) {
427 | /* subsequent calls will encounter the Fx0A again */
428 | C8.PC -= 2;
429 | return;
430 | }
431 | for(y = 0; y < 0xF; y++) {
432 | if(keys & (1 << y)) {
433 | C8.V[x] = y;
434 | break;
435 | }
436 | }
437 | keys = 0;
438 | } break;
439 | case 0x15:
440 | /* LD DT, Vx */
441 | C8.DT = C8.V[x];
442 | break;
443 | case 0x18:
444 | /* LD ST, Vx */
445 | C8.ST = C8.V[x];
446 | break;
447 | case 0x1E:
448 | /* ADD I, Vx */
449 | C8.I += C8.V[x];
450 | /* According to [wikipedia][] the VF is set if I overflows. */
451 | if(C8.I > 0xFFF) {
452 | C8.V[0xF] = 1;
453 | C8.I &= 0xFFF;
454 | } else {
455 | C8.V[0xF] = 0;
456 | }
457 | break;
458 | case 0x29:
459 | /* LD F, Vx */
460 | C8.I = FONT_OFFSET + (C8.V[x] & 0x0F) * 5;
461 | break;
462 | case 0x30:
463 | /* LD HF, Vx - Load 8x10 hi-resolution font */
464 | C8.I = HFONT_OFFSET + (C8.V[x] & 0x0F) * 10;
465 | break;
466 | case 0x33:
467 | /* LD B, Vx */
468 | C8.RAM[C8.I] = (C8.V[x] / 100) % 10;
469 | C8.RAM[C8.I + 1] = (C8.V[x] / 10) % 10;
470 | C8.RAM[C8.I + 2] = C8.V[x] % 10;
471 | break;
472 | case 0x55:
473 | /* LD [I], Vx */
474 | if(C8.I + x > TOTAL_RAM)
475 | x = TOTAL_RAM - C8.I;
476 | assert(C8.I + x <= TOTAL_RAM);
477 | if(x >= 0)
478 | memcpy(C8.RAM + C8.I, C8.V, x+1);
479 | if(quirks & QUIRKS_MEM_CHIP8)
480 | C8.I += x + 1;
481 | break;
482 | case 0x65:
483 | /* LD Vx, [I] */
484 | if(C8.I + x > TOTAL_RAM)
485 | x = TOTAL_RAM - C8.I;
486 | assert(C8.I + x <= TOTAL_RAM);
487 | if(x >= 0)
488 | memcpy(C8.V, C8.RAM + C8.I, x+1);
489 | if(quirks & QUIRKS_MEM_CHIP8)
490 | C8.I += x + 1;
491 | break;
492 | case 0x75:
493 | /* LD R, Vx */
494 | assert(x <= sizeof hp48_flags);
495 | memcpy(hp48_flags, C8.V, x);
496 | break;
497 | case 0x85:
498 | /* LD Vx, R */
499 | assert(x <= sizeof hp48_flags);
500 | memcpy(C8.V, hp48_flags, x);
501 | break;
502 | }
503 | } break;
504 | }
505 | }
506 |
507 | int c8_ended() {
508 | /* Check whether the next instruction is 00FD */
509 | return borked || c8_opcode(C8.PC) == 0x00FD;
510 | }
511 | int c8_waitkey() {
512 | return (c8_opcode(C8.PC) & 0xF0FF) == 0xF00A;
513 | }
514 |
515 | uint8_t c8_get(uint16_t addr) {
516 | assert(addr < TOTAL_RAM);
517 | return C8.RAM[addr];
518 | }
519 |
520 | void c8_set(uint16_t addr, uint8_t byte) {
521 | assert(addr < TOTAL_RAM);
522 | C8.RAM[addr] = byte;
523 | }
524 |
525 | uint16_t c8_opcode(uint16_t addr) {
526 | assert(addr < TOTAL_RAM - 1);
527 | return C8.RAM[addr] << 8 | C8.RAM[addr+1];
528 | }
529 |
530 | uint16_t c8_get_pc() {
531 | return C8.PC;
532 | }
533 |
534 | uint16_t c8_prog_size() {
535 | uint16_t n;
536 | for(n = TOTAL_RAM - 1; n > PROG_OFFSET && C8.RAM[n] == 0; n--);
537 | if(++n & 0x1) // Fix for #4
538 | return n + 1;
539 | return n;
540 | }
541 |
542 | uint8_t c8_get_reg(uint8_t r) {
543 | if(r > 0xF) return 0;
544 | return C8.V[r];
545 | }
546 |
547 | int c8_screen_updated() {
548 | return screen_updated;
549 | }
550 |
551 | int c8_resolution(int *w, int *h) {
552 | if(!w || !h) return hi_res;
553 | if(hi_res) {
554 | *w = 128; *h = 64;
555 | } else {
556 | *w = 64; *h = 32;
557 | }
558 | return hi_res;
559 | }
560 |
561 | int c8_get_pixel(int x, int y) {
562 | int byte, bit, w, h;
563 | if(hi_res) {
564 | w = 128; h = 64;
565 | } else {
566 | w = 64; h = 32;
567 | }
568 | if(x < 0 || x >= w || y < 0 || y >= h) return 0;
569 | byte = y * w + x;
570 | bit = byte & 0x07;
571 | byte >>= 3;
572 | assert(byte < sizeof pixels);
573 | assert(bit < 8);
574 | return (pixels[byte] & (1 << bit)) != 0;
575 | }
576 |
577 | void c8_key_down(uint8_t k) {
578 | if(k > 0xF) return;
579 | keys |= 1 << k;
580 | }
581 |
582 | void c8_key_up(uint8_t k) {
583 | if(k > 0xF) return;
584 | keys &= ~(1 << k);
585 | }
586 |
587 | void c8_60hz_tick() {
588 | yield = 0;
589 | if(C8.DT > 0) C8.DT--;
590 | if(C8.ST > 0) C8.ST--;
591 | }
592 |
593 | int c8_sound() {
594 | return C8.ST > 0;
595 | }
596 |
597 | size_t c8_load_program(uint8_t program[], size_t n) {
598 | if(n + PROG_OFFSET > TOTAL_RAM)
599 | n = TOTAL_RAM - PROG_OFFSET;
600 | assert(n + PROG_OFFSET <= TOTAL_RAM);
601 | memcpy(C8.RAM + PROG_OFFSET, program, n);
602 | return n;
603 | }
604 |
605 | int c8_load_file(const char *fname) {
606 | FILE *f;
607 | size_t len, r;
608 | if(!(f = fopen(fname, "rb")))
609 | return 0;
610 | fseek(f, 0, SEEK_END);
611 | len = ftell(f);
612 | if(len == 0 || len + PROG_OFFSET > TOTAL_RAM) {
613 | fclose(f);
614 | return 0;
615 | }
616 | rewind(f);
617 | r = fread(C8.RAM + PROG_OFFSET, 1, len, f);
618 | fclose(f);
619 | if(r != len)
620 | return 0;
621 | return len;
622 | }
623 |
624 | char *c8_load_txt(const char *fname) {
625 | FILE *f;
626 | size_t len, r;
627 | char *bytes;
628 |
629 | if(!(f = fopen(fname, "rb")))
630 | return NULL;
631 |
632 | fseek(f, 0, SEEK_END);
633 | len = ftell(f);
634 | rewind(f);
635 |
636 | if(!(bytes = malloc(len+2)))
637 | return NULL;
638 | r = fread(bytes, 1, len, f);
639 |
640 | if(r != len) {
641 | free(bytes);
642 | return NULL;
643 | }
644 |
645 | fclose(f);
646 | bytes[len] = '\0';
647 |
648 | return bytes;
649 | }
650 |
651 | int c8_save_file(const char *fname) {
652 | uint16_t n = c8_prog_size();
653 | size_t len = n - PROG_OFFSET;
654 | FILE *f = fopen(fname, "wb");
655 | if(!f)
656 | return 0;
657 | if(fwrite(C8.RAM + PROG_OFFSET, 1, len, f) != len)
658 | return 0;
659 | fclose(f);
660 | return len;
661 | }
662 |
663 | int c8_message(const char *msg, ...) {
664 | if(msg) {
665 | va_list arg;
666 | va_start (arg, msg);
667 | vsnprintf (c8_message_text, MAX_MESSAGE_TEXT-1, msg, arg);
668 | va_end (arg);
669 | }
670 | if(c8_puts)
671 | return c8_puts(c8_message_text);
672 | return 0;
673 | }
674 |
675 | /**
676 | * [wikipedia]: https://en.wikipedia.org/wiki/CHIP-8
677 | *
678 | */
679 |
--------------------------------------------------------------------------------
/sdl/pocadv.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Pocket Adventure
3 | * ================
4 | */
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #include "pocadv.h"
17 |
18 | #if SCREEN_SCALE == 0
19 | /* This has happened to me more than once */
20 | # error "You set SCREEN_SCALE to 0 again, dummy!"
21 | #endif
22 |
23 | #define VSCREEN_WIDTH (SCREEN_WIDTH * (EPX_SCALE?2:1))
24 | #define VSCREEN_HEIGHT (SCREEN_HEIGHT * (EPX_SCALE?2:1))
25 | #define WINDOW_WIDTH (VSCREEN_WIDTH * SCREEN_SCALE)
26 | #define WINDOW_HEIGHT (VSCREEN_HEIGHT * SCREEN_SCALE)
27 |
28 | #ifndef USE_LOG_STREAM
29 | # define USE_LOG_STREAM 0
30 | #else
31 | # ifndef LOG_STREAM
32 | # define LOG_STREAM stderr
33 | # endif
34 | #endif
35 |
36 | #ifndef LOG_FILE_NAME
37 | # define LOG_FILE_NAME "sdl-game.log"
38 | #endif
39 |
40 | #ifndef SDL2
41 | static SDL_Surface *window;
42 | #else
43 | static SDL_Renderer *renderer = NULL;
44 | static SDL_Texture *texture = NULL;
45 | static SDL_Window *window;
46 | #endif
47 |
48 | Bitmap *screen;
49 | #ifndef SDL2
50 | Bitmap *vscreen;
51 | #endif
52 |
53 | #ifndef SDL2
54 | char keys[SDLK_LAST];
55 | #else
56 | char keys[SDL_NUM_SCANCODES];
57 | #endif
58 | static int pressed_key = 0;
59 |
60 | int mouse_x, mouse_y;
61 | static int mclick = 0, mdown = 0, mrelease = 0, mmove = 0;
62 |
63 | static Bitmap *cursor = NULL;
64 | static int cursor_hsx, cursor_hsy;
65 | static Bitmap *cursor_back = NULL;
66 |
67 | #if EPX_SCALE
68 | static Bitmap *scale_epx_i(Bitmap *in, Bitmap *out);
69 | static Bitmap *epx;
70 | #endif
71 |
72 | static int dodebug = 0;
73 | static double frameTimes[256];
74 | static Uint32 n_elapsed = 0;
75 |
76 | int quit = 0;
77 |
78 | /* This leaves a bit to be desired if I'm to
79 | support multi-touch on mobile eventually */
80 | int mouse_clicked() {
81 | return mclick;
82 | }
83 | int mouse_down() {
84 | return mdown;
85 | }
86 | int mouse_released() {
87 | return mrelease;
88 | }
89 | int mouse_moved() {
90 | return mmove;
91 | }
92 | int key_pressed() {
93 | return pressed_key;
94 | }
95 |
96 | int show_debug() {
97 | // #ifdef NDEBUG
98 | // return 0;
99 | // #else
100 | return dodebug;
101 | // #endif
102 | }
103 |
104 | /* Handle special keys */
105 | #ifdef SDL2
106 | static int handleKeys(SDL_Scancode key) {
107 | #else
108 | static int handleKeys(SDLKey key) {
109 | #endif
110 | switch(key) {
111 | #if !defined(__EMSCRIPTEN__) && ESCAPE_QUITS
112 | case KCODE(ESCAPE) : quit = 1; return 1;
113 | #endif
114 | /* TODO: F11 for fullscreen, etc. */
115 | #if !defined(__EMSCRIPTEN__)
116 | case KCODE(F10): dodebug = !dodebug; return 1;
117 | case KCODE(F12): {
118 | /* F12 for screenshots; Doesn't make sense in the browser. */
119 | char filename[128];
120 |
121 | #ifdef _MSC_VER
122 | time_t t = time(NULL);
123 | struct tm buf, *ptr = &buf;
124 | localtime_s(ptr, &t);
125 | #else
126 | time_t t;
127 | struct tm *ptr;
128 | t = time(NULL);
129 | ptr = localtime(&t);
130 | #endif
131 | strftime(filename, sizeof filename, "screen-%Y%m%d%H%M%S.bmp", ptr);
132 |
133 | bm_save(screen, filename);
134 | } return 1;
135 | #endif
136 | default : return 0;
137 | }
138 | }
139 |
140 | char *readfile(const char *fname) {
141 | FILEOBJ *f;
142 | long len,r;
143 | char *str;
144 |
145 | if(!(f = FOPEN(fname, "rb")))
146 | return NULL;
147 |
148 | FSEEK(f, 0, SEEK_END);
149 | len = (long)FTELL(f);
150 | REWIND(f);
151 |
152 | if(!(str = malloc(len+2)))
153 | return NULL;
154 | r = FREAD(str, 1, len, f);
155 |
156 | if(r != len) {
157 | free(str);
158 | return NULL;
159 | }
160 |
161 | FCLOSE(f);
162 | str[len] = '\0';
163 | return str;
164 | }
165 |
166 | void set_cursor(Bitmap *b, int hsx, int hsy) {
167 | cursor_hsx = hsx;
168 | cursor_hsy = hsy;
169 | cursor = b;
170 | int w = bm_width(b), h= bm_height(b);
171 | if(b) {
172 | if(!cursor_back)
173 | cursor_back = bm_create(w, h);
174 | else if(bm_width(cursor_back) != w || bm_height(cursor_back) != h) {
175 | bm_free(cursor_back);
176 | cursor_back = bm_create(w, h);
177 | }
178 | SDL_ShowCursor(SDL_DISABLE);
179 | } else {
180 | bm_free(cursor_back);
181 | cursor_back = NULL;
182 | SDL_ShowCursor(SDL_ENABLE);
183 | }
184 | }
185 |
186 | static const char *lastEvent = "---";
187 | static int finger_id = -1;
188 |
189 | static void handle_events() {
190 |
191 | SDL_Event event;
192 |
193 | while(SDL_PollEvent(&event)) {
194 | switch(event.type) {
195 | case SDL_KEYDOWN: {
196 | lastEvent = "Key Down"; finger_id=-1;
197 | #ifdef SDL2
198 | if(handleKeys(event.key.keysym.scancode))
199 | break;
200 | keys[event.key.keysym.scancode] = 1;
201 | pressed_key = event.key.keysym.sym;
202 | if(!(pressed_key & 0x40000000)) {
203 | if(event.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) {
204 | if(isalpha(pressed_key)) {
205 | pressed_key = toupper(pressed_key);
206 | } else {
207 | /* This would not work with different keyboard layouts */
208 | static const char *in = "`1234567890-=[],./;'\\";
209 | static const char *out = "~!@#$%^&*()_+{}<>?:\"|";
210 | char *p = strchr(in, pressed_key);
211 | if(p) {
212 | pressed_key = out[p-in];
213 | }
214 | }
215 | } else if (pressed_key == SDLK_DELETE) {
216 | // The Del key is a bit special...
217 | pressed_key = 0x40000000 | SDL_SCANCODE_DELETE;
218 | }
219 | }
220 | #else
221 | if(handleKeys(event.key.keysym.sym))
222 | break;
223 | keys[event.key.keysym.sym] = 1;
224 | pressed_key = event.key.keysym.sym;
225 | if(pressed_key > 0xFF) {
226 | pressed_key |= 0x40000000;
227 | } else if(event.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) {
228 | if(isalpha(pressed_key)) {
229 | pressed_key = toupper(pressed_key);
230 | } else {
231 | /* This would not work with different keyboard layouts */
232 | static const char *in = "`1234567890-=[],./;'\\";
233 | static const char *out = "~!@#$%^&*()_+{}<>?:\"|";
234 | char *p = strchr(in, pressed_key);
235 | if(p) {
236 | pressed_key = out[p-in];
237 | }
238 | }
239 | } else if (pressed_key == SDLK_DELETE) {
240 | pressed_key = 0x40000000 | SDLK_DELETE;
241 | }
242 | #endif
243 | } break;
244 | case SDL_KEYUP: {
245 | lastEvent = "Key Up";finger_id=-1;
246 | #ifdef SDL2
247 | keys[event.key.keysym.scancode] = 0;
248 | #else
249 | keys[event.key.keysym.sym] = 0;
250 | #endif
251 | } break;
252 | #ifndef ANDROID /* Ignore the mouse on android, that's what the touch events are for */
253 | case SDL_MOUSEBUTTONDOWN: {
254 | lastEvent = "Mouse Down";finger_id = 0;
255 | if(event.button.button != SDL_BUTTON_LEFT) break;
256 | mdown = 1;
257 | mrelease = 0;
258 | mclick = 1;
259 | } break;
260 | case SDL_MOUSEBUTTONUP: {
261 | lastEvent = "Mouse Up";finger_id = 0;
262 | if(event.button.button != SDL_BUTTON_LEFT) break;
263 | mdown = 0;
264 | mrelease = 1;
265 | } break;
266 | case SDL_MOUSEMOTION: {
267 | lastEvent = "Mouse Move";finger_id = 0;
268 | mouse_x = event.button.x * SCREEN_WIDTH / WINDOW_WIDTH;
269 | mouse_y = event.button.y * SCREEN_HEIGHT / WINDOW_HEIGHT;
270 | mmove = 1;
271 | } break;
272 | #endif
273 | #if defined(SDL2) && defined(ANDROID)
274 | case SDL_FINGERDOWN: {
275 | lastEvent = "Finger Down";finger_id=event.tfinger.fingerId;
276 | int x = (int)(event.tfinger.x * SCREEN_WIDTH), y = (int)(event.tfinger.y * SCREEN_HEIGHT);
277 | if(!pointer_click(x, y,finger_id)) {
278 | pointer_down(x, y,finger_id);
279 | }
280 | } break;
281 | case SDL_FINGERUP: {
282 | lastEvent = "Finger Up";finger_id=event.tfinger.fingerId;
283 | int x = (int)(event.tfinger.x * SCREEN_WIDTH), y = (int)(event.tfinger.y * SCREEN_HEIGHT);
284 | pointer_up(x, y,finger_id);
285 | } break;
286 | case SDL_FINGERMOTION: {
287 | lastEvent = "Finger Motion";finger_id=event.tfinger.fingerId;
288 | int x = (int)(event.tfinger.x * SCREEN_WIDTH), y = (int)(event.tfinger.y * SCREEN_HEIGHT);
289 | pointer_move(x, y,finger_id);
290 | } break;
291 | #endif
292 | case SDL_QUIT: {
293 | quit = 1;
294 | } break;
295 | }
296 | }
297 | }
298 |
299 | Bitmap *get_bmp(const char *filename) {
300 | #ifdef ANDROID
301 | SDL_RWops *file = SDL_RWFromFile(filename, "rb");
302 | Bitmap *bmp = bm_load_rw(file);
303 | SDL_RWclose(file);
304 | #else
305 | Bitmap *bmp = bm_load(filename);
306 | #endif
307 | return bmp;
308 | }
309 |
310 | static void draw_frame() {
311 | static Uint32 start = 0;
312 | static Uint32 elapsed = 0;
313 |
314 | elapsed = SDL_GetTicks() - start;
315 |
316 | /* It is technically possible for the game to run
317 | too fast, rendering the deltaTime useless */
318 | if(elapsed < 10)
319 | return;
320 |
321 | double deltaTime = elapsed / 1000.0;
322 | if(!render(deltaTime)) {
323 | quit = 1;
324 | }
325 |
326 | start = SDL_GetTicks();
327 |
328 | if(dodebug && n_elapsed > 0) {
329 | double sum = 0;
330 | int i, n = n_elapsed > 0xFF ? 0xFF : n_elapsed;
331 | for(i = 0; i < n; i++) sum += frameTimes[i];
332 | double avg = sum / n;
333 | double fps = 1.0 / avg;
334 | BmFont *save = bm_get_font(screen);
335 | bm_reset_font(screen);
336 | bm_set_color(screen, bm_atoi("red"));
337 | bm_fillrect(screen, 0, 0, 50, 10);
338 | bm_set_color(screen, bm_atoi("yellow"));
339 | bm_printf(screen, 1, 1, "%3.2f", fps);
340 | bm_set_font(screen, save);
341 | }
342 | frameTimes[(n_elapsed++) & 0xFF] = deltaTime;
343 |
344 | #if EPX_SCALE
345 | scale_epx_i(screen, epx);
346 | #endif
347 |
348 | mclick = 0;
349 | mrelease = 0;
350 | mmove = 0;
351 | pressed_key = 0;
352 | }
353 |
354 | #define USE_SDL_LOG 0
355 |
356 | static FILE *logfile = NULL;
357 |
358 | void rlog(const char *fmt, ...) {
359 | va_list arg;
360 | va_start(arg, fmt);
361 | #if defined(SDL2) && USE_SDL_LOG
362 | SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, arg);
363 | #else
364 | fputs("INFO: ", logfile);
365 | vfprintf(logfile, fmt, arg);
366 | fputc('\n', logfile);
367 | #endif
368 | va_end(arg);
369 | }
370 |
371 | void rerror(const char *fmt, ...) {
372 | va_list arg;
373 | va_start(arg, fmt);
374 | #if defined(SDL2) && USE_SDL_LOG
375 | SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, fmt, arg);
376 | #else
377 | fputs("ERROR: ", logfile);
378 | vfprintf(logfile, fmt, arg);
379 | fputc('\n', logfile);
380 | #endif
381 | va_end(arg);
382 | }
383 |
384 | void exit_error(const char *fmt, ...) {
385 | if(fmt) {
386 | va_list arg;
387 | va_start (arg, fmt);
388 | #if defined(SDL2) && USE_SDL_LOG
389 | SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, fmt, arg);
390 | #else
391 | fputs("ERROR: ", logfile);
392 | vfprintf (logfile, fmt, arg);
393 | #endif
394 | va_end (arg);
395 | }
396 | if(logfile != stdout && logfile != stderr)
397 | fclose(logfile);
398 | else {
399 | fflush(stdout);
400 | fflush(stderr);
401 | }
402 | exit(1);
403 | }
404 |
405 | static void do_iteration() {
406 | int cx = 0, cy = 0;
407 |
408 | #ifndef SDL2
409 | if(SDL_MUSTLOCK(window))
410 | SDL_LockSurface(window);
411 | bm_rebind(vscreen, window->pixels);
412 | handle_events();
413 |
414 | draw_frame();
415 |
416 | if(cursor) {
417 | cx = mouse_x - cursor_hsx;
418 | cy = mouse_y - cursor_hsy;
419 |
420 | int cw = bm_width(cursor), ch = bm_height(cursor);
421 |
422 | bm_blit(cursor_back, 0, 0, screen, cx, cy, cw, ch);
423 | bm_maskedblit(screen, cx, cy, cursor, 0, 0, cw, ch);
424 | }
425 |
426 | # if EPX_SCALE
427 | bm_blit_ex(vscreen, 0, 0, bm_width(vscreen), bm_height(vscreen), epx, 0, 0, bm_width(epx), bm_height(epx), 0);
428 | # else
429 | bm_blit_ex(vscreen, 0, 0, bm_width(vscreen), bm_height(vscreen), screen, 0, 0, bm_width(screen), bm_height(screen), 0);
430 | # endif
431 |
432 | if(SDL_MUSTLOCK(window))
433 | SDL_UnlockSurface(window);
434 | SDL_Flip(window);
435 | #else
436 | handle_events();
437 |
438 | draw_frame();
439 |
440 | if(cursor) {
441 | cx = mouse_x - cursor_hsx;
442 | cy = mouse_y - cursor_hsy;
443 | int cw = bm_width(cursor), ch = bm_height(cursor);
444 | bm_blit(cursor_back, 0, 0, screen, cx, cy, cw, ch);
445 | bm_maskedblit(screen, cx, cy, cursor, 0, 0, cw, ch);
446 | }
447 |
448 | # if EPX_SCALE
449 | SDL_UpdateTexture(texture, NULL, bm_data(epx), bm_width(epx)*4);
450 | # else
451 | SDL_UpdateTexture(texture, NULL, bm_raw_data(screen), bm_width(screen)*4);
452 | # endif
453 | SDL_RenderClear(renderer);
454 | SDL_RenderCopy(renderer, texture, NULL, NULL);
455 | SDL_RenderPresent(renderer);
456 | #endif
457 |
458 | if(cursor) {
459 | bm_maskedblit(screen, cx, cy, cursor_back, 0, 0, bm_width(cursor_back), bm_height(cursor_back));
460 | }
461 | }
462 |
463 | int main(int argc, char *argv[]) {
464 |
465 | #ifdef __EMSCRIPTEN__
466 | logfile = stdout;
467 | #else
468 | //logfile = fopen("pocadv.log", "w");
469 |
470 | # ifdef _MSC_VER
471 | errno_t err = fopen_s(&logfile, LOG_FILE_NAME, "w");
472 | if (err != 0)
473 | return 1;
474 | # elif USE_LOG_STREAM
475 | logfile = LOG_STREAM;
476 | # else
477 | logfile = fopen(LOG_FILE_NAME, "w");
478 | if (!logfile)
479 | return 1;
480 | # endif
481 | #endif
482 |
483 | rlog("%s: Application Running", WINDOW_CAPTION);
484 |
485 | srand((unsigned int)time(NULL));
486 |
487 | SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
488 |
489 | #ifdef SDL2
490 | window = SDL_CreateWindow(WINDOW_CAPTION " - SDL2",
491 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
492 | WINDOW_WIDTH, WINDOW_HEIGHT,
493 | SDL_WINDOW_SHOWN);
494 | if(!window) {
495 | rerror("%s","SDL_CreateWindow()");
496 | return 0;
497 | }
498 |
499 | renderer = SDL_CreateRenderer(window, -1, 0);
500 | if(!renderer) {
501 | rerror("%s","SDL_CreateRenderer()");
502 | return 1;
503 | }
504 |
505 | # if EPX_SCALE
506 | epx = bm_create(VSCREEN_WIDTH, VSCREEN_HEIGHT);
507 | screen = bm_create(SCREEN_WIDTH, SCREEN_HEIGHT);
508 | texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, VSCREEN_WIDTH, VSCREEN_HEIGHT);
509 | if(!texture) {
510 | rerror("%s","SDL_CreateTexture()");
511 | return 1;
512 | }
513 | # else
514 | screen = bm_create(SCREEN_WIDTH, SCREEN_HEIGHT);
515 | texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT);
516 | if(!texture) {
517 | rerror("%s","SDL_CreateTexture()");
518 | return 1;
519 | }
520 | # endif
521 |
522 | init_game(argc, argv);
523 | #else
524 | /* Using SDL 1.2 */
525 | SDL_WM_SetCaption(WINDOW_CAPTION " - SDL1.2", "game");
526 | window = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 32, SDL_SWSURFACE);
527 |
528 | screen = bm_create(SCREEN_WIDTH, SCREEN_HEIGHT);
529 | # if EPX_SCALE
530 | epx = bm_create(VSCREEN_WIDTH, VSCREEN_HEIGHT);
531 | # endif
532 | if(SDL_MUSTLOCK(window)) {
533 | SDL_LockSurface(window);
534 | vscreen = bm_bind(WINDOW_WIDTH, WINDOW_HEIGHT, window->pixels);
535 | init_game(argc, argv);
536 | SDL_UnlockSurface(window);
537 | } else {
538 | vscreen = bm_bind(WINDOW_WIDTH, WINDOW_HEIGHT, window->pixels);
539 | init_game(argc, argv);
540 | }
541 |
542 | SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
543 | #endif
544 |
545 | #ifdef TEST_SDL_LOCK_OPTS
546 | EM_ASM("SDL.defaults.copyOnLock = false; SDL.defaults.discardOnLock = true; SDL.defaults.opaqueFrontBuffer = false");
547 | #endif
548 |
549 | #ifdef __EMSCRIPTEN__
550 | emscripten_set_main_loop(do_iteration, 0, 1);
551 | #else
552 | rlog("%s: Entering main loop", WINDOW_CAPTION);
553 |
554 | while(!quit) {
555 | do_iteration();
556 | }
557 |
558 | deinit_game();
559 |
560 | #endif
561 | rlog("%s: Main loop stopped", WINDOW_CAPTION);
562 | #ifdef SDL2
563 | SDL_DestroyTexture(texture);
564 | SDL_DestroyRenderer(renderer);
565 | SDL_DestroyWindow(window);
566 | bm_free(screen);
567 | # if EPX_SCALE
568 | bm_free(epx);
569 | # endif
570 |
571 | #else
572 | bm_unbind(vscreen);
573 | bm_free(screen);
574 | # if EPX_SCALE
575 | bm_free(epx);
576 | # endif
577 |
578 | #endif
579 |
580 | SDL_Quit();
581 |
582 | rlog("%s","Application Done!\n");
583 | #if !USE_LOG_STREAM
584 | fclose(logfile);
585 | #endif
586 | return 0;
587 | }
588 |
589 | /* EPX 2x scaling */
590 | #if EPX_SCALE
591 | static Bitmap *scale_epx_i(Bitmap *in, Bitmap *out) {
592 | int x, y, mx = in->w, my = in->h;
593 | if(!out) return NULL;
594 | if(!in) return out;
595 | if(out->w < (mx << 1)) mx = (out->w - 1) >> 1;
596 | if(out->h < (my << 1)) my = (out->h - 1) >> 1;
597 | for(y = 0; y < my; y++) {
598 | for(x = 0; x < mx; x++) {
599 | unsigned int P = bm_get(in, x, y);
600 | unsigned int A = (y > 0) ? bm_get(in, x, y - 1) : P;
601 | unsigned int B = (x < in->w - 1) ? bm_get(in, x + 1, y) : P;
602 | unsigned int C = (x > 0) ? bm_get(in, x - 1, y) : P;
603 | unsigned int D = (y < in->h - 1) ? bm_get(in, x, y + 1) : P;
604 |
605 | unsigned int P1 = P, P2 = P, P3 = P, P4 = P;
606 |
607 | if(C == A && C != D && A != B) P1 = A;
608 | if(A == B && A != C && B != D) P2 = B;
609 | if(B == D && B != A && D != C) P4 = D;
610 | if(D == C && D != B && C != A) P3 = C;
611 |
612 | bm_set(out, (x << 1), (y << 1), P1);
613 | bm_set(out, (x << 1) + 1, (y << 1), P2);
614 | bm_set(out, (x << 1), (y << 1) + 1, P3);
615 | bm_set(out, (x << 1) + 1, (y << 1) + 1, P4);
616 | }
617 | }
618 | return out;
619 | }
620 | #endif
621 |
--------------------------------------------------------------------------------
/gdi/gdi.c:
--------------------------------------------------------------------------------
1 | /**
2 | * Win32 application that allows rendering directly to a GDI contexts
3 | * through my bitmap module.
4 | * (for simple games without any third party dependencies)
5 | *
6 | * ## References:
7 | *
8 | * * The bitmap module: https://github.com/wernsey/bitmap
9 | * * https://www.daniweb.com/software-development/cpp/code/241875/fast-animation-with-the-windows-gdi
10 | * * https://www-user.tu-chemnitz.de/~heha/petzold/ch14e.htm
11 | * * https://www-user.tu-chemnitz.de/~heha/petzold/ch15d.htm
12 | * * http://forums.codeguru.com/showthread.php?487633-32-bit-DIB-from-24-bit-bitmap
13 | * * HELLO_WIN.C example of the tcc C compiler
14 | */
15 |
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #ifndef WIN32_LEAN_AND_MEAN
23 | #define WIN32_LEAN_AND_MEAN
24 | #endif
25 |
26 | #define __STDC_WANT_LIB_EXT1__ 1
27 |
28 | #include
29 |
30 | #include "../bmp.h"
31 | #include "gdi.h"
32 |
33 | #define VSCREEN_WIDTH (SCREEN_WIDTH * (EPX_SCALE?2:1))
34 | #define VSCREEN_HEIGHT (SCREEN_HEIGHT * (EPX_SCALE?2:1))
35 | #define WINDOW_WIDTH (VSCREEN_WIDTH * SCREEN_SCALE)
36 | #define WINDOW_HEIGHT (VSCREEN_HEIGHT * SCREEN_SCALE)
37 |
38 | /* fflush() the log file after each call to rlog()?
39 | I only use it for those hard to debug crashes */
40 | #ifndef FLUSH
41 | # define FLUSH 0
42 | #endif
43 |
44 | #ifndef LOG_FILE_NAME
45 | # define LOG_FILE_NAME "gdi-game.log"
46 | #endif
47 |
48 | #ifndef USE_LOG_STREAM
49 | # define USE_LOG_STREAM 0
50 | #else
51 | # ifndef LOG_STREAM
52 | # define LOG_STREAM stderr
53 | # endif
54 | #endif
55 |
56 | static char szAppName[] = WINDOW_CAPTION;
57 | static char szTitle[] = WINDOW_CAPTION " - GDI";
58 |
59 | Bitmap *screen = NULL;
60 |
61 | static Bitmap *cursor = NULL;
62 | static int cursor_hsx, cursor_hsy;
63 | static Bitmap *cursor_back = NULL;
64 |
65 | /* Virtual-Key Codes here:
66 | https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx */
67 | #define MAX_KEYS 256
68 | char keys[MAX_KEYS];
69 | static int pressed_key = 0;
70 |
71 | int mouse_x, mouse_y;
72 | static int mclick = 0, mdown = 0, mrelease = 0, mmove = 0;
73 |
74 | int quit = 0;
75 |
76 | static int show_fps = 0;
77 | static double frameTimes[256];
78 | static unsigned int n_elapsed = 0;
79 |
80 | int show_debug() {
81 | return show_fps;
82 | }
83 |
84 | #if EPX_SCALE
85 | static Bitmap *scale_epx_i(Bitmap *in, Bitmap *out);
86 | #endif
87 |
88 | void clear_keys() {
89 | int i;
90 | for(i = 0; i < MAX_KEYS; i++) {
91 | keys[i] = 0;
92 | }
93 | }
94 | int mouse_clicked() {
95 | return mclick;
96 | }
97 | int mouse_down() {
98 | return mdown;
99 | }
100 | int mouse_released() {
101 | return mrelease;
102 | }
103 | int mouse_moved() {
104 | return mmove;
105 | }
106 | int key_pressed() {
107 | return pressed_key;
108 | }
109 |
110 | static FILE *logfile = NULL;
111 |
112 | void rlog(const char *fmt, ...) {
113 | va_list arg;
114 | va_start(arg, fmt);
115 | fputs("INFO: ", logfile);
116 | vfprintf(logfile, fmt, arg);
117 | fputc('\n', logfile);
118 | va_end(arg);
119 | #if FLUSH
120 | fflush(logfile);
121 | #endif
122 | }
123 |
124 | void rerror(const char *fmt, ...) {
125 | va_list arg;
126 | va_start(arg, fmt);
127 | fputs("ERROR: ", logfile);
128 | vfprintf(logfile, fmt, arg);
129 | fputc('\n', logfile);
130 | va_end(arg);
131 | fflush(logfile);
132 | }
133 |
134 | void exit_error(const char *msg, ...) {
135 | char message_text[256];
136 | if(msg) {
137 | va_list arg;
138 | va_start (arg, msg);
139 | vsnprintf (message_text, (sizeof message_text) - 1, msg, arg);
140 | message_text[(sizeof message_text) - 1] = '\0';
141 | va_end (arg);
142 | fputc('\n', logfile);
143 | } else {
144 | message_text[0] = '\0';
145 | }
146 | if (logfile) {
147 | fputs(message_text, logfile);
148 | }
149 | MessageBox(
150 | NULL,
151 | message_text,
152 | "Error",
153 | MB_ICONERROR | MB_OK
154 | );
155 | exit(1);
156 | }
157 |
158 | void set_cursor(Bitmap *b, int hsx, int hsy) {
159 | cursor_hsx = hsx;
160 | cursor_hsy = hsy;
161 | cursor = b;
162 | if(b) {
163 | int w = bm_width(b), h = bm_height(b);
164 | if(!cursor_back)
165 | cursor_back = bm_create(w, h);
166 | else if(bm_width(cursor_back) != w || bm_height(cursor_back) != h) {
167 | bm_free(cursor_back);
168 | cursor_back = bm_create(w, h);
169 | }
170 | ShowCursor(0);
171 | } else {
172 | bm_free(cursor_back);
173 | cursor_back = NULL;
174 | ShowCursor(1);
175 | }
176 | }
177 |
178 | /** WIN32 and GDI routines below this line *****************************************/
179 |
180 | static int split_cmd_line(char *cmdl, char *argv[], int max);
181 | static void FitWindow(HWND hWnd);
182 |
183 | LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
184 | {
185 | static HDC hdc, hdcMem;
186 | static HBITMAP hbmOld, hbmp;
187 |
188 | #if EPX_SCALE
189 | static Bitmap *epx = NULL;
190 | #endif
191 |
192 | #define MAX_ARGS 16
193 | static int argc = 0;
194 | static char *argv[MAX_ARGS];
195 | static LPTSTR cmdl;
196 |
197 | switch (message) {
198 |
199 | case WM_CREATE: {
200 | unsigned char *pixels;
201 |
202 | BITMAPINFO bmi;
203 | ZeroMemory(&bmi, sizeof bmi);
204 | bmi.bmiHeader.biSize = sizeof(BITMAPINFO);
205 | bmi.bmiHeader.biWidth = VSCREEN_WIDTH;
206 | bmi.bmiHeader.biHeight = -VSCREEN_HEIGHT; // Order pixels from top to bottom
207 | bmi.bmiHeader.biPlanes = 1;
208 | bmi.bmiHeader.biBitCount = 32; // last byte not used, 32 bit for alignment
209 | bmi.bmiHeader.biCompression = BI_RGB;
210 | bmi.bmiHeader.biSizeImage = 0;
211 | bmi.bmiHeader.biXPelsPerMeter = 0;
212 | bmi.bmiHeader.biYPelsPerMeter = 0;
213 | bmi.bmiHeader.biClrUsed = 0;
214 | bmi.bmiHeader.biClrImportant = 0;
215 | bmi.bmiColors[0].rgbBlue = 0;
216 | bmi.bmiColors[0].rgbGreen = 0;
217 | bmi.bmiColors[0].rgbRed = 0;
218 | bmi.bmiColors[0].rgbReserved = 0;
219 |
220 | hdc = GetDC( hwnd );
221 | hbmp = CreateDIBSection( hdc, &bmi, DIB_RGB_COLORS, (void**)&pixels, NULL, 0 );
222 | if (!hbmp) {
223 | exit_error("CreateDIBSection");
224 | return 0;
225 | }
226 |
227 | hdcMem = CreateCompatibleDC( hdc );
228 | hbmOld = (HBITMAP)SelectObject( hdcMem, hbmp );
229 |
230 | #if !EPX_SCALE
231 | screen = bm_bind(VSCREEN_WIDTH, VSCREEN_HEIGHT, pixels);
232 | #else
233 | screen = bm_create(SCREEN_WIDTH, SCREEN_HEIGHT);
234 | epx = bm_bind(VSCREEN_WIDTH, VSCREEN_HEIGHT, pixels);
235 | #endif
236 | bm_set_color(screen, bm_atoi("black"));
237 | bm_clear(screen);
238 |
239 | clear_keys();
240 |
241 | cmdl = _strdup(GetCommandLine());
242 | argc = split_cmd_line(cmdl, argv, MAX_ARGS);
243 |
244 | init_game(argc, argv);
245 |
246 | } break;
247 |
248 | case WM_DESTROY:
249 | quit = 1;
250 | deinit_game();
251 |
252 | free(cmdl);
253 |
254 | #if !EPX_SCALE
255 | bm_unbind(screen);
256 | #else
257 | bm_free(screen);
258 | bm_unbind(epx);
259 | #endif
260 | SelectObject( hdcMem, hbmOld );
261 | DeleteDC( hdc );
262 | screen = NULL;
263 | PostQuitMessage(0);
264 | break;
265 |
266 | case WM_RBUTTONUP:
267 | #if 0
268 | DestroyWindow(hwnd);
269 | #endif
270 | break;
271 |
272 | /* If you want text input, WM_CHAR is what you're looking for */
273 | case WM_CHAR:
274 | if (wParam < 128) {
275 | pressed_key = wParam;
276 | }
277 | break;
278 | case WM_SYSKEYDOWN:
279 | // TIL the F10 key doesn't go through the WM_KEYDOWN:
280 | // https://msdn.microsoft.com/en-us/library/windows/desktop/gg153546(v=vs.85).aspx
281 | if (wParam == VK_F10) {
282 | show_fps = !show_fps;
283 | } else return DefWindowProc(hwnd, message, wParam, lParam);
284 | break;
285 | case WM_KEYDOWN:
286 | if (wParam == VK_F12) {
287 |
288 | char filename[128];
289 | #ifdef _MSC_VER
290 | time_t t = time(NULL);
291 | struct tm buf, *ptr = &buf;
292 | localtime_s(ptr, &t);
293 | #else
294 | time_t t;
295 | struct tm *ptr;
296 | t = time(NULL);
297 | ptr = localtime(&t);
298 | #endif
299 | strftime(filename, sizeof filename, "screen-%Y%m%d%H%M%S.bmp", ptr);
300 | bm_save(screen, filename);
301 |
302 | } else if (ESCAPE_QUITS && VK_ESCAPE == wParam) {
303 | DestroyWindow(hwnd);
304 | } else if (wParam < MAX_KEYS) {
305 | keys[wParam] = 1;
306 | pressed_key = wParam | 0xFFFF0000;
307 | }
308 | break;
309 | case WM_KEYUP:
310 | if (wParam < MAX_KEYS) {
311 | keys[wParam] = 0;
312 | }
313 | break;
314 |
315 | case WM_LBUTTONDOWN:
316 | case WM_LBUTTONDBLCLK: /* ...all clicks treated equally */
317 | {
318 | mouse_x = LOWORD(lParam) * SCREEN_WIDTH / WINDOW_WIDTH;
319 | mouse_y = HIWORD(lParam) * SCREEN_HEIGHT / WINDOW_HEIGHT;
320 | mdown = 1;
321 | mrelease = 0;
322 | mclick = 1;
323 | } break;
324 |
325 | case WM_LBUTTONUP:
326 | {
327 | mdown = 0;
328 | mrelease = 1;
329 | } break;
330 | case WM_MOUSEMOVE:
331 | {
332 | mouse_x = LOWORD(lParam) * SCREEN_WIDTH / WINDOW_WIDTH;
333 | mouse_y = HIWORD(lParam) * SCREEN_HEIGHT / WINDOW_HEIGHT;
334 | mmove = 1;
335 | } break;
336 | case WM_PAINT:
337 | {
338 | if(!screen) break;
339 | PAINTSTRUCT ps;
340 | HDC hdc = BeginPaint( hwnd, &ps );
341 |
342 | int cx = 0, cy = 0;
343 | if(cursor) {
344 | cx = mouse_x - cursor_hsx;
345 | cy = mouse_y - cursor_hsy;
346 | int cw = bm_width(cursor);
347 | int ch = bm_height(cursor);
348 |
349 | bm_blit(cursor_back, 0, 0, screen, cx, cy, cw, ch);
350 | bm_maskedblit(screen, cx, cy, cursor, 0, 0, cw, ch);
351 | }
352 |
353 | #if EPX_SCALE
354 | scale_epx_i(screen, epx);
355 | #endif
356 |
357 | #if WINDOW_WIDTH == VSCREEN_WIDTH && WINDOW_HEIGHT == VSCREEN_HEIGHT
358 | BitBlt( hdc, 0, 0, VSCREEN_WIDTH, VSCREEN_HEIGHT, hdcMem, 0, 0, SRCCOPY );
359 | #else
360 | StretchBlt(hdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, hdcMem, 0, 0, VSCREEN_WIDTH, VSCREEN_HEIGHT, SRCCOPY);
361 | #endif
362 | EndPaint( hwnd, &ps );
363 |
364 | if(cursor) {
365 | bm_maskedblit(screen, cx, cy, cursor_back, 0, 0, bm_width(cursor_back), bm_height(cursor_back));
366 | }
367 |
368 | break;
369 | }
370 | /* Don't erase the background - it causes flickering
371 | http://stackoverflow.com/a/14153470/115589 */
372 | case WM_ERASEBKGND:
373 | return 1;
374 |
375 | default:
376 | return DefWindowProc(hwnd, message, wParam, lParam);
377 | }
378 | return 0;
379 | }
380 |
381 | int APIENTRY WinMain(
382 | HINSTANCE hInstance,
383 | HINSTANCE hPrevInstance,
384 | LPSTR lpCmdLine,
385 | int nCmdShow
386 | )
387 | {
388 | MSG msg;
389 | WNDCLASS wc;
390 | HWND hwnd;
391 | double elapsedSeconds = 0.0;
392 |
393 | #ifdef _MSC_VER
394 | errno_t err = fopen_s(&logfile, LOG_FILE_NAME, "w");
395 | if (err != 0) {
396 | exit_error("Unable to open log file `%s`");
397 | }
398 | #elif defined USE_LOG_STREAM
399 | logfile = LOG_STREAM;
400 | #else
401 | logfile = fopen(LOG_FILE_NAME, "w");
402 | if(!logfile) {
403 | exit_error("Unable to open log file `%s`");
404 | }
405 | #endif
406 |
407 | rlog("%s","GDI Framework: Application Running");
408 |
409 | ZeroMemory(&wc, sizeof wc);
410 | wc.hInstance = hInstance;
411 | wc.lpszClassName = szAppName;
412 | wc.lpfnWndProc = (WNDPROC)WndProc;
413 | wc.style = CS_DBLCLKS|CS_VREDRAW|CS_HREDRAW;
414 | wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
415 | wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
416 | wc.hCursor = LoadCursor(NULL, IDC_ARROW);
417 |
418 | if (FALSE == RegisterClass(&wc))
419 | return 0;
420 |
421 | hwnd = CreateWindow(
422 | szAppName,
423 | szTitle,
424 | //WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
425 | //WS_POPUP, // For no border/titlebar etc
426 | WS_CAPTION,
427 | CW_USEDEFAULT,
428 | CW_USEDEFAULT,
429 | WINDOW_WIDTH,
430 | WINDOW_HEIGHT,
431 | 0,
432 | 0,
433 | hInstance,
434 | 0);
435 |
436 | if (NULL == hwnd)
437 | return 0;
438 |
439 | FitWindow(hwnd);
440 |
441 | ShowWindow(hwnd , SW_SHOW);
442 |
443 | /* Todo: I didn't bother with higher resolution timers:
444 | https://msdn.microsoft.com/en-us/library/dn553408(v=vs.85).aspx */
445 |
446 | rlog("%s","GDI Framework: Entering main loop");
447 | quit = 0;
448 | for(;;) {
449 | clock_t startTime, endTime;
450 | startTime = clock();
451 | while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) {
452 | TranslateMessage(&msg);
453 | DispatchMessage(&msg);
454 | }
455 | if(quit) break;
456 |
457 | Sleep(1);
458 | if(elapsedSeconds > 1.0/FPS) {
459 |
460 | if(!render(elapsedSeconds)) {
461 | DestroyWindow(hwnd);
462 | } else {
463 | if(show_fps && n_elapsed > 0) {
464 | double sum = 0;
465 | int i, n = n_elapsed > 0xFF ? 0xFF : n_elapsed;
466 | for(i = 0; i < n; i++) sum += frameTimes[i];
467 | double avg = sum / n;
468 | double fps = 1.0 / avg;
469 | BmFont * f = bm_get_font(screen);
470 | bm_reset_font(screen);
471 | bm_set_color(screen, bm_atoi("red"));
472 | bm_fillrect(screen, 0, 0, 50, 10);
473 | bm_set_color(screen, bm_atoi("yellow"));
474 | bm_printf(screen, 1, 1, "%3.2f", fps);
475 | bm_set_font(screen, f);
476 | }
477 | frameTimes[(n_elapsed++) & 0xFF] = elapsedSeconds;
478 | }
479 |
480 | InvalidateRect(hwnd, 0, TRUE);
481 | elapsedSeconds = 0.0;
482 | pressed_key = 0;
483 | mrelease = 0;
484 | mmove = 0;
485 | mclick = 0;
486 | }
487 | endTime = clock();
488 | elapsedSeconds += (double)(endTime - startTime) / CLOCKS_PER_SEC;
489 | }
490 | rlog("%s","GDI Framework: Main loop stopped");
491 | rlog("%s","Application Done!\n");
492 |
493 | #if !USE_LOG_STREAM
494 | fclose(logfile);
495 | #endif
496 |
497 | return msg.wParam;
498 | }
499 |
500 | /* Make sure the client area fits; center the window in the process */
501 | static void FitWindow(HWND hwnd) {
502 | RECT rcClient, rwClient;
503 | POINT ptDiff;
504 | HWND hwndParent;
505 | RECT rcParent, rwParent;
506 | POINT ptPos;
507 |
508 | GetClientRect(hwnd, &rcClient);
509 | GetWindowRect(hwnd, &rwClient);
510 | ptDiff.x = (rwClient.right - rwClient.left) - rcClient.right;
511 | ptDiff.y = (rwClient.bottom - rwClient.top) - rcClient.bottom;
512 |
513 | hwndParent = GetParent(hwnd);
514 | if (NULL == hwndParent)
515 | hwndParent = GetDesktopWindow();
516 |
517 | GetWindowRect(hwndParent, &rwParent);
518 | GetClientRect(hwndParent, &rcParent);
519 |
520 | ptPos.x = rwParent.left + (rcParent.right - WINDOW_WIDTH) / 2;
521 | ptPos.y = rwParent.top + (rcParent.bottom - WINDOW_HEIGHT) / 2;
522 |
523 | MoveWindow(hwnd, ptPos.x, ptPos.y, WINDOW_WIDTH + ptDiff.x, WINDOW_HEIGHT + ptDiff.y, 0);
524 | }
525 |
526 | /*
527 | Alternative to CommandLineToArgvW().
528 | I used a compiler where shellapi.h was not available,
529 | so this function breaks it down according to the last set of rules in
530 | http://i1.blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx
531 |
532 | Only a long time after I wrote this did I discover that you can actually
533 | use __argc, __argv to access the commandline parameters...
534 |
535 | extern "C" int __stdcall WinMain( struct HINSTANCE__*, struct HINSTANCE__*, char*, int ) {
536 | return main( __argc, __argv );
537 | }
538 |
539 | http://www.testdeveloper.com/2010/03/16/a-few-ways-to-access-argc-and-argv-in-c/
540 |
541 | */
542 | static int split_cmd_line(char *cmdl, char *argv[], int max) {
543 |
544 | int argc = 0;
545 | char *p = cmdl, *q = p, *arg = p;
546 | int state = 1;
547 | while(state) {
548 | switch(state) {
549 | case 1:
550 | if(argc == max) return argc;
551 | if(!*p) {
552 | state = 0;
553 | } else if(isspace(*p)) {
554 | *q++ = *p++;
555 | } else if(*p == '\"') {
556 | state = 2;
557 | *q++ = *p++;
558 | arg = q;
559 | } else {
560 | state = 3;
561 | arg = q;
562 | *q++ = *p++;
563 | }
564 | break;
565 | case 2:
566 | if(!*p) {
567 | argv[argc++] = arg;
568 | *q++ = '\0';
569 | state = 0;
570 | } else if(*p == '\"') {
571 | if(p[1] == '\"') {
572 | state = 2;
573 | *q++ = *p;
574 | p+=2;
575 | } else {
576 | state = 1;
577 | argv[argc++] = arg;
578 | *q++ = '\0';
579 | p++;
580 | }
581 | } else {
582 | *q++ = *p++;
583 | }
584 | break;
585 | case 3:
586 | if(!*p) {
587 | state = 0;
588 | argv[argc++] = arg;
589 | *q++ = '\0';
590 | } else if(isspace(*p)) {
591 | state = 1;
592 | argv[argc++] = arg;
593 | *q++ = '\0';
594 | p++;
595 | } else {
596 | *q++ = *p++;
597 | }
598 | break;
599 | }
600 | }
601 | return argc;
602 | }
603 |
604 | /* EPX 2x scaling */
605 | #if EPX_SCALE
606 | static Bitmap *scale_epx_i(Bitmap *in, Bitmap *out) {
607 | int x, y, mx = in->w, my = in->h;
608 | if(!out) return NULL;
609 | if(!in) return out;
610 | if(out->w < (mx << 1)) mx = (out->w - 1) >> 1;
611 | if(out->h < (my << 1)) my = (out->h - 1) >> 1;
612 | for(y = 0; y < my; y++) {
613 | for(x = 0; x < mx; x++) {
614 | unsigned int P = bm_get(in, x, y);
615 | unsigned int A = (y > 0) ? bm_get(in, x, y - 1) : P;
616 | unsigned int B = (x < in->w - 1) ? bm_get(in, x + 1, y) : P;
617 | unsigned int C = (x > 0) ? bm_get(in, x - 1, y) : P;
618 | unsigned int D = (y < in->h - 1) ? bm_get(in, x, y + 1) : P;
619 |
620 | unsigned int P1 = P, P2 = P, P3 = P, P4 = P;
621 |
622 | if(C == A && C != D && A != B) P1 = A;
623 | if(A == B && A != C && B != D) P2 = B;
624 | if(B == D && B != A && D != C) P4 = D;
625 | if(D == C && D != B && C != A) P3 = C;
626 |
627 | bm_set(out, (x << 1), (y << 1), P1);
628 | bm_set(out, (x << 1) + 1, (y << 1), P2);
629 | bm_set(out, (x << 1), (y << 1) + 1, P3);
630 | bm_set(out, (x << 1) + 1, (y << 1) + 1, P4);
631 | }
632 | }
633 | return out;
634 | }
635 | #endif
636 |
637 | char *readfile(const char *fname) {
638 | FILE *f;
639 | size_t len, r;
640 | char *bytes;
641 |
642 | #ifdef _MSC_VER
643 | errno_t err = fopen_s(&f, fname, "rb");
644 | if(err != 0)
645 | return NULL;
646 | #else
647 | f = fopen(fname, "rb");
648 | if (!f)
649 | return NULL;
650 | #endif
651 |
652 | fseek(f, 0, SEEK_END);
653 | len = ftell(f);
654 | rewind(f);
655 |
656 | if(!(bytes = malloc(len+2)))
657 | return NULL;
658 | r = fread(bytes, 1, len, f);
659 |
660 | if(r != len) {
661 | free(bytes);
662 | return NULL;
663 | }
664 |
665 | fclose(f);
666 | bytes[len] = '\0';
667 |
668 | return bytes;
669 | }
670 |
671 | Bitmap *get_bmp(const char *filename) {
672 | return bm_load(filename);
673 | }
674 |
--------------------------------------------------------------------------------
/d.awk:
--------------------------------------------------------------------------------
1 | #! /usr/bin/awk -f
2 |
3 | ##
4 | # d.awk
5 | # =====
6 | #
7 | # Converts Markdown in C/C++-style code comments to HTML. \
8 | #
9 | #
10 | # The comments must have the `/** */` pattern. Every line in the comment
11 | # must start with a *. Like so:
12 | #
13 | # ```c
14 | # /**
15 | # * Markdown here...
16 | # */
17 | # ```
18 | #
19 | # Alternatively, three slashes can also be used: `/// Markdown here`
20 | #
21 | # ## Configuration Options
22 | #
23 | # You can set these in the BEGIN block below, or pass them to the script through the
24 | # `-v` command-line option:
25 | #
26 | # - `-vTitle="My Document Title"` to set the `` in the `` section of the HTML
27 | # - `-vStyleSheet=style.css` to use a separate CSS file as style sheet.
28 | # - `-vCss=n` with n either 0 to disable CSS or 1 to enable; Default = 1
29 | # - `-vTopLinks=1` to have links to the top of the doc next to headers.
30 | # - `-vMaxWidth=1080px` specifies the Maximum width of the HTML output.
31 | # - `-vPretty=0` disable syntax highlighting with Google's [code prettify][].
32 | # - `-vMermaid=0` disable [Mermaid][] diagrams.
33 | # - `-vMathjax=0` disable [MathJax][] mathematical rendering.
34 | # - `-vHideToCLevel=n` specifies the level of the ToC that should be collapsed by default.
35 | # - `-vLang=n` specifies the value of the `lang` attribute of the tag; Default = "en"
36 | # - `-vTables=0` disables support for [GitHub-style tables][github-tables]
37 | # - `-vclassic_underscore=1` words_with_underscores behave like old markdown where the
38 | # underscores in the word counts as emphasis. The default behaviour is to have
39 | # `words_like_this` not contain any emphasis.
40 | # - `-vNumberHeadings=1` to enable or disable section numbers in front of headings; Default = 1
41 | # - `-vNumberH1s=1`: if `NumberHeadings` is enabled, `
` headings are not numbered by
42 | # default (because the `
` would typically contain the document title). Use this to
43 | # number `
"
757 | for(c = 1; c <= NCols[r]; c++) {
758 | a = Align[c];
759 | if(a)
760 | s = s "<" t " align=\"" a "\">" scrub(Table[r,c]) "" t ">"
761 | else
762 | s = s "<" t ">" scrub(Table[r,c]) "" t ">"
763 | }
764 | s = s "
\n";
774 |
775 | p = match(st, /!\[toc[-+]?\]/);
776 | while(p) {
777 | if(substr(st,RSTART-1,1) == "\\") {
778 | r = r substr(st,1,RSTART-2) substr(st,RSTART,RLENGTH);
779 | st = substr(st,RSTART+RLENGTH);
780 | p = match(st, /!\[toc[-+]?\]/);
781 | continue;
782 | }
783 |
784 | ++n;
785 | dis = index(substr(st,RSTART,RLENGTH),"+");
786 | t = "\nContents\n" \
787 | tocBody "";
788 | t = t "\n
" tocBody "
"
789 | r = r substr(st,1,RSTART-1);
790 | r = r t;
791 | st = substr(st,RSTART+RLENGTH);
792 | p = match(st, /!\[toc[-+]?\]/);
793 | }
794 | return r st;
795 | }
796 | function fix_links(st, lt,ld,lr,url,img,res,rx,pos,pre) {
797 | do {
798 | pre = match(st, /<(pre|code)>/); # Don't substitute in
or blocks
799 | pos = match(st, /\[[^\]]+\]/);
800 | if(!pos)break;
801 | if(pre && pre < pos) {
802 | match(st, /<\/(pre|code)>/);
803 | res = res substr(st,1,RSTART+RLENGTH);
804 | st = substr(st, RSTART+RLENGTH+1);
805 | continue;
806 | }
807 | img=substr(st,RSTART-1,1)=="!";
808 | if(substr(st, RSTART-(img?2:1),1)=="\\") {
809 | res = res substr(st,1,RSTART-(img?3:2));
810 | if(img && substr(st,RSTART,RLENGTH)=="[toc]")res=res "\\";
811 | res = res substr(st,RSTART-(img?1:0),RLENGTH+(img?1:0));
812 | st = substr(st, RSTART + RLENGTH);
813 | continue;
814 | }
815 | res = res substr(st, 1, RSTART-(img?2:1));
816 | rx = substr(st, RSTART, RLENGTH);
817 | st = substr(st, RSTART+RLENGTH);
818 | if(match(st, /^[[:space:]]*\([^)]+\)/)) {
819 | lt = substr(rx, 2, length(rx) - 2);
820 | match(st, /\([^)]+\)/);
821 | url = substr(st, RSTART+1, RLENGTH-2);
822 | st = substr(st, RSTART+RLENGTH);
823 | ld = "";
824 | if(match(url,/[[:space:]]+["']/)) {
825 | ld = url;
826 | url = substr(url, 1, RSTART - 1);
827 | match(ld,/["']/);
828 | delim = substr(ld, RSTART, 1);
829 | if(match(ld,delim ".*" delim))
830 | ld = substr(ld, RSTART+1, RLENGTH-2);
831 | } else ld = "";
832 | if(img)
833 | res = res "";
834 | else
835 | res = res "" lt "";
836 | } else if(match(st, /^[[:space:]]*\[[^\]]*\]/)) {
837 | lt = substr(rx, 2, length(rx) - 2);
838 | match(st, /\[[^\]]*\]/);
839 | lr = trim(tolower(substr(st, RSTART+1, RLENGTH-2)));
840 | if(!lr) {
841 | lr = tolower(trim(lt));
842 | if(LinkDescs[lr]) lt = LinkDescs[lr];
843 | }
844 | st = substr(st, RSTART+RLENGTH);
845 | url = LinkUrls[lr];
846 | ld = LinkDescs[lr];
847 | if(img)
848 | res = res "";
849 | else if(url)
850 | res = res "" lt "";
851 | else
852 | res = res "[" lt "][" lr "]";
853 | } else
854 | res = res (img?"!":"") rx;
855 | } while(pos > 0);
856 | return res st;
857 | }
858 | function fix_footnotes(st, r,p,n,i,d,fn,fc) {
859 | p = match(st, /\[\^[^\]]+\]/);
860 | while(p) {
861 | if(substr(st,RSTART-2,1) == "\\") {
862 | r = r substr(st,1,RSTART-3) substr(st,RSTART,RLENGTH);
863 | st = substr(st,RSTART+RLENGTH);
864 | p = match(st, /\[\^[^\]]+\]/);
865 | continue;
866 | }
867 | r = r substr(st,1,RSTART-1);
868 | d = substr(st,RSTART+2,RLENGTH-3);
869 | n = tolower(d);
870 | st = substr(st,RSTART+RLENGTH);
871 | if(Footnote[tolower(n)]) {
872 | if(!fn[n]) fn[n] = ++fc;
873 | d = Footnote[n];
874 | } else {
875 | Footnote[n] = scrub(d);
876 | if(!fn[n]) fn[n] = ++fc;
877 | }
878 | footname[fc] = n;
879 | d = strip_tags(d);
880 | if(length(d) > 20) d = substr(d,1,20) "…";
881 | r = r "[" fn[n] "]";
882 | p = match(st, /\[\^[^\]]+\]/);
883 | }
884 | for(i=1;i<=fc;i++)
885 | footnotes = footnotes "