From 1022dc172b4c5d0f3ad913fbaf82d4745e32ba1c Mon Sep 17 00:00:00 2001 From: jvernet Date: Thu, 20 Jan 2022 17:45:54 +0100 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 364cfcbadeeb3f8a67035b15865c3664bd8b0905 Author: Andrea Odetti Date: Sun Jan 16 20:26:02 2022 +0000 Debugger: add some more flags & switches. Signed-off-by: Andrea Odetti commit 586f09439307794802e6c7b12c57d5e4682fa354 Author: Andrea Odetti Date: Sun Jan 16 19:17:57 2022 +0000 Update ImGui. Signed-off-by: Andrea Odetti commit 6c6409b625c71a85eceebf7709bc399be3c23d24 Merge: 26a72930 af814347 Author: Andrea Odetti Date: Sun Jan 16 11:16:39 2022 +0000 Merge remote-tracking branch 'upstream/master' commit af814347f08cb0911100ccbf20a005f23c7b7ab5 Author: tomcw Date: Sat Jan 15 18:01:41 2022 +0000 Support AN3 for IIe (#1018) (Fix IOUDIS for IIc - $C07E/F was backwards) commit 26a729309feba9778a317669f116aa508c2470c5 Merge: c70377fc d88ab119 Author: Andrea Date: Fri Jan 14 17:36:28 2022 +0000 Merge pull request #62 from audetto/fullspeed sa2: do not call VideoPresentScreen every Apple ][ frame when in full… commit d88ab1199d2b12339726ead5b17502a68e7d96d5 Author: Andrea Odetti Date: Tue Jan 11 18:07:57 2022 +0000 sa2: do not call VideoPresentScreen every Apple ][ frame when in full speed. But only call it after 16ms wall clock. Fixes https://github.com/audetto/AppleWin/issues/61 Signed-off-by: Andrea Odetti commit c70377fcafb95b69447480cb04cbc03ee46bffe8 Author: Andrea Odetti Date: Tue Jan 11 17:50:08 2022 +0000 ImGui: avoid non-reentrant call to VideoPresentScreen(). The debugger (which calls VideoPresentScreen) is execute in immediate mode from VideoPresentScreen. Is this a design problem? Signed-off-by: Andrea Odetti commit 2abca0c0b283a450dd84fd2579f60550da3ca1fd Author: Andrea Odetti Date: Mon Jan 10 15:38:25 2022 +0000 Update ImGui. Signed-off-by: Andrea Odetti commit 3348d4420e39bb74a06d7d1c70e47b99df5c73de Author: Andrea Odetti Date: Mon Jan 10 15:38:13 2022 +0000 SaveState: fix some issues in the order of things. Signed-off-by: Andrea Odetti commit 726b05cdbe8e20d8cc569b6bcdeac950504cb851 Author: Andrea Odetti Date: Mon Jan 10 12:00:18 2022 +0000 cpack: extract version number from resources. Signed-off-by: Andrea Odetti commit 0264538f4bb580976f36fd58417ef7d2678c0068 Merge: f5575668 5fbd8339 Author: Andrea Odetti Date: Sat Jan 8 08:57:04 2022 +0000 Merge remote-tracking branch 'upstream/master' commit 5fbd8339c10e3dd623d9613a47986ff95658fe6b Author: Andrea Date: Fri Jan 7 21:06:24 2022 +0000 Screenshot: reset file pointer to the end. (PR #1016) commit f557566824c8dc0cdcaceaf1f610a96e1b41f9f7 Merge: e8fe81c7 aa4af5e9 Author: Andrea Odetti Date: Thu Jan 6 18:55:38 2022 +0000 Merge remote-tracking branch 'upstream/master' commit aa4af5e9d568fb4438254d83ef68d925ca81a8a9 Author: michaelangel007 Date: Thu Jan 6 10:26:30 2022 -0800 Debugger: Fix DF E937 not checking for 0.0 correctly commit 65e4859b01a45e17e0051da37f4bd352134e4448 Author: michaelangel007 Date: Thu Jan 6 10:13:27 2022 -0800 Debugger: Cleanup DB DB2 DB4 DB8 DW DW2 DW4 DF DS commit ae214a1b19bd80930b311f1f1edcf61f104e50f8 Author: michaelangel007 Date: Thu Jan 6 10:11:33 2022 -0800 Debugger: Cleanup: Add note about magic number 200 commit 8667c0eb322e399073f08ab46db306e487cceec4 Author: michaelangel007 Date: Thu Jan 6 10:10:59 2022 -0800 Debugger: Cleanup: Add note about DISASM ID commit 182e48e82d127836270c4ef1b256f75934114300 Author: michaelangel007 Date: Thu Jan 6 10:10:09 2022 -0800 Debugger: Add: First pass of DF commit 32ee96fb8b68399307dd30bba30849480ce7975a Author: michaelangel007 Date: Thu Jan 6 08:24:55 2022 -0800 Debugger: Cleanup FormatNopcodeBytes() commit e8fe81c7cc1d94fca5723eb3dded3eb634442ff5 Merge: d0601d15 7c4855e6 Author: Andrea Odetti Date: Thu Jan 6 14:25:28 2022 +0000 Merge remote-tracking branch 'upstream/master' commit 7c4855e6313d084e4878947faf65c79f46d3d03a Author: michaelangel007 Date: Wed Jan 5 15:42:34 2022 -0800 Debugger: Cleanup DISASM_DISPLAY_ enums commit ca1bea3b97bbdb18b61969f2af3626fe881fad6a Author: michaelangel007 Date: Wed Jan 5 15:41:47 2022 -0800 Debugger: Add comment about DISK command commit 225b290e3e610bb855869c68cff542508cdc0469 Author: michaelangel007 Date: Wed Jan 5 15:41:18 2022 -0800 Debugger: Update wish list commit d0601d156dbfce07cc2079a2ea84db60136dc1ad Merge: c2a25530 545c79f1 Author: Andrea Odetti Date: Wed Jan 5 18:45:35 2022 +0000 Merge remote-tracking branch 'upstream/master' commit 545c79f132763f1ce2b702229746709d49cab5f1 Author: michaelangel007 Date: Wed Jan 5 08:55:17 2022 -0800 Debugger: Cleanup: Nopcode_e commit 7853c1cfcc1cbabaad5edd7c2d9e30bdc74751fa Author: michaelangel007 Date: Wed Jan 5 08:54:54 2022 -0800 Debugger: Cleanup commit 8d864c2ab6882b4c516a97363d1068768d0759db Author: michaelangel007 Date: Tue Jan 4 23:37:38 2022 -0800 Cleanup: getVideoScannerAddressTXT() and getVideoScannerAddressHGR() to make it easier to debug commit 98a4481c7d24a199b16a96af3685c3d662200eb2 Author: michaelangel007 Date: Tue Jan 4 23:37:06 2022 -0800 Debugger: 2.9.1.13 Added: CD now detects .. to change to the previous directory and chops the trailing sub-directory from the current path. commit e91c5c0494793156d053c49f6cfd742345218e60 Merge: 2a5e156f e14339e2 Author: michaelangel007 Date: Tue Jan 4 21:41:08 2022 -0800 Merged commit 2a5e156f5eb6e253398fa1c15936393c157ca743 Author: michaelangel007 Date: Tue Jan 4 21:40:23 2022 -0800 Debugger: 2.9.1.12 Added: New commands HGR0, HGR3, HGR4, HGR5 to see pages /usr/bin/bash0, 0, 0, respectively. commit e14339e282081d5ccace7c295f12516753a43893 Author: tomcw Date: Tue Jan 4 20:19:21 2022 +0000 Registry: fix support for legacy 'Harddisk Enable' key (#1015) commit 1613671c115bd71c94e53cce6173bf98ca44d81f Author: michaelangel007 Date: Tue Jan 4 11:29:18 2022 -0800 Debugger: Add note for 2.9.1.11 commit 5e70f7949ce367fccc54830266cd496d0485207a Author: michaelangel007 Date: Tue Jan 4 11:21:42 2022 -0800 Debugger: 2.9.1.11 Fixed: Right justify signed decimal values. commit f3c0e151ab4158f7344bc3fdcddb9e23801a1000 Author: michaelangel007 Date: Tue Jan 4 11:20:54 2022 -0800 Debugger: 2.9.1.10 Fixed: Immedate #80 was not showing -128 for the signed decimal value. commit 6b11e24a28caee983a1fa68dd68f8b2890559161 Author: michaelangel007 Date: Tue Jan 4 11:20:08 2022 -0800 Debugger: 2.9.1.9 Fixed: Immediate #0 was showing '#' prefix but not showing zero for the signed decimal value. Changed to show the signed decimal value only if non zero. commit 61d6ef58ee4dd2bca890df0b80cda90ab2567d06 Author: michaelangel007 Date: Tue Jan 4 11:17:14 2022 -0800 Debugger: Cleanup old cruft commit c2a255303d1049feea28adc062d1733b73571f03 Author: Andrea Odetti Date: Tue Jan 4 17:34:16 2022 +0000 Update ImGui. Signed-off-by: Andrea Odetti commit 4a5183552580fcd212148d59c0c5601c2b50d049 Merge: 1f1a0f0c f03c09db Author: Andrea Date: Tue Jan 4 17:33:27 2022 +0000 Merge pull request #56 from audetto/paths Separate Frame from Resource folder to support native MacOS port. commit 18b458109931c26b0834b2d8d549bf6e105348dd Author: michaelangel007 Date: Tue Jan 4 09:28:33 2022 -0800 Debugger: 2.9.1.8 Changed: Disassembly window now lists symbol labels and symbol target address from User2 in orange. commit 17686df3df49b2ff3ca93f24ec317a7fbee27f2f Author: michaelangel007 Date: Tue Jan 4 09:25:51 2022 -0800 Debugger: 2.9.1.7 Added: Extended SYM command to auto-generate symbol names when reverse engineering. NOTE: These symbols will be placed in User2. commit 1f5ca5d7e72273b9e57bab810ccd1a1b2d27e430 Author: michaelangel007 Date: Tue Jan 4 09:24:52 2022 -0800 Bugfix: Is user requested which symbol table but symbol not found, mark as not in any commit ba9388d1d8568b2f3291b627cec6da4ddfc98895 Author: michaelangel007 Date: Mon Jan 3 22:48:12 2022 -0800 Debugger: 2.9.1.6 Added: Branch instructions now show target address commit b3dc408195a4e3bb77b7a68b4352fffbe0110e8b Author: michaelangel007 Date: Mon Jan 3 22:47:25 2022 -0800 Debugger: Cleanup commit 65ab105d7612364298437fd517338e42282de042 Author: michaelangel007 Date: Mon Jan 3 22:18:12 2022 -0800 Debugger: 2.9.1.5 Added: Disassembly window now shows signed decimal values for immediate values. commit aaae1dd6a1a0ce01a0758b860e29f72ca750d531 Author: michaelangel007 Date: Mon Jan 3 22:15:41 2022 -0800 Debugger: 2.9.1.4 Changed: Show symbol warnings in Orange, and length of symbols in light blue commit 48e0fe3a8ee4ce39f72b7f6c9f6d57eb66b4e2d3 Author: michaelangel007 Date: Mon Jan 3 21:11:25 2022 -0800 Debugger: 2.9.1.3 Added: DB command now optionally supports = commit 3985ee9abc1a90b359128c54953ed65f77b741fa Author: michaelangel007 Date: Mon Jan 3 21:09:11 2022 -0800 Debugger: 2.9.1.2: Fixed: Off by one end address when deleting DisasmData_t commit 19b7d4332723036031dc2e4ad176bdfe744f59e6 Author: michaelangel007 Date: Mon Jan 3 21:07:49 2022 -0800 Debugger: 2.9.1.1: Added: X command now supports a range and will chop off the appropiate data sections. commit 561a145ab068abbfc18cc89a0d9d799a78f61455 Author: michaelangel007 Date: Mon Jan 3 21:05:37 2022 -0800 Debugger: Add _GetAutoSymbolName() helper commit f03c09db764578329e736f141e51b25626e7f35f Author: Andrea Odetti Date: Mon Jan 3 17:01:22 2022 +0000 Separate Frame from Resource folder to support native MacOS port. Fixes https://github.com/audetto/AppleWin/issues/55 Signed-off-by: Andrea Odetti commit 1f1a0f0c3fe0f0cde5ad965bef0ab4eb5215ac50 Author: Andrea Odetti Date: Mon Jan 3 16:32:29 2022 +0000 /proc/self/exe does not exist on MacOS. Fixes https://github.com/audetto/AppleWin/issues/54 Signed-off-by: Andrea Odetti commit fa6048087590882f6cb9458ef6c123eb923e0c65 Author: Andrea Odetti Date: Mon Jan 3 16:02:36 2022 +0000 Add support for screenshot in sa2 via Alt-Ins. Signed-off-by: Andrea Odetti commit 8de697f3f64ddc4f1a208029ebe9a93170f09f1e Author: Andrea Odetti Date: Mon Jan 3 16:01:36 2022 +0000 Clean NFrame::FrameRefreshStatus. Signed-off-by: Andrea Odetti commit dadba55b73d4a53a36e6bd6ba90838f1ca438329 Merge: 89d90317 d63e4065 Author: Andrea Odetti Date: Mon Jan 3 15:50:27 2022 +0000 Merge remote-tracking branch 'upstream/master' commit d63e406573ba7ee21b76d216563d209aaf7b59b1 Author: Andrea Date: Mon Jan 3 14:41:03 2022 +0000 Correct BMP creation on Linux (PR #1014) Enable BMP Header packing on all compilers. Add virtual function to FrameBase to select where to save screenshots. commit 89d9031752025aa0ad681a6b0e611232ddde4ad4 Merge: d6e9a58a 9006bcf3 Author: Andrea Date: Fri Dec 31 16:30:21 2021 +0000 Merge pull request #52 from audetto/cmake Use cmake native find_package for OpenGL. commit d6e9a58a15f45d0da75b507aa41b9a81b5fac0c1 Author: Andrea Odetti Date: Fri Dec 31 15:27:34 2021 +0000 applen: add some help with the most important F keys. Signed-off-by: Andrea Odetti commit b48893fab90ca73410e3e107c2120a1951c570f9 Merge: 89842321 a243efc7 Author: Andrea Odetti Date: Fri Dec 31 14:18:17 2021 +0000 Merge remote-tracking branch 'upstream/master' commit 89842321b8b490ab1732bb1e0263b86ad1641782 Author: Andrea Odetti Date: Fri Dec 31 14:18:02 2021 +0000 Update ImGui. Signed-off-by: Andrea Odetti commit 9006bcf354932505c66a6401376ad03dce58ed10 Author: Andrea Odetti Date: Fri Dec 31 14:06:34 2021 +0000 Use cmake native find_package for OpenGL. Could solve https://github.com/audetto/AppleWin/issues/39. Signed-off-by: Andrea Odetti commit a243efc74bf499e487ce0008adceb831da8fd890 Author: tomcw Date: Fri Dec 24 20:58:04 2021 +0000 Fix for cmd line -dcd not being honoured (regression) commit a727db7cc509cbd897942be24e76247b9d1c6ff4 Author: tomcw Date: Thu Dec 23 15:56:14 2021 +0000 Load save-state: . fix for II/II+ when VidHD's SHR is being updated (init memVidHD ptr). . refactor to consolidate all Mem* related initialisation. commit d865d5c252df448c8f66f2eee28f8acb86f7138e Author: Andrea Odetti Date: Sun Dec 19 20:26:41 2021 +0000 Update package version. Should really be dynamic. Signed-off-by: Andrea Odetti commit 9ec45b1dab7d8a298967b5a46f240bbcc2fefb0c Merge: e5456916 0f2d4b18 Author: Andrea Odetti Date: Sun Dec 19 20:18:37 2021 +0000 Merge remote-tracking branch 'upstream/master' commit 0f2d4b1878be4a5f313f4b01ff6e0a275124aa6b Author: tomcw Date: Sun Dec 19 19:28:50 2021 +0000 1.30.7.0: Update History.txt commit d342f3a17d94b0a8bc7886f9b1d06ee8f004a214 Author: tomcw Date: Sun Dec 19 19:26:07 2021 +0000 Fix for VS2008 commit e5456916fb743928c95f97df6371d5010f6478e7 Merge: d6aa4070 577ffccf Author: Andrea Odetti Date: Sun Dec 19 15:06:02 2021 +0000 Merge remote-tracking branch 'upstream/master' commit 577ffccf8f131648f244df758e40fe57b4c9e3af Author: TomCh Date: Sun Dec 19 14:17:51 2021 +0000 VidHD: Support SHR for Apple II/II+ models (#997, PR #1013) . Support aux writes for II/II+ (6502 emulation, not 65C02) . Extend VidHD save-state for II/II+ aux memory commit ff65a9f6bf98adc0a208d666e20c20b3cab5f9f3 Author: tomcw Date: Sat Dec 18 21:31:17 2021 +0000 Save-state: remove ASSERT when saving LC for //e or Enhanced //e commit d6aa407076e96dbd16d2519a044655c9d670d23e Author: Andrea Odetti Date: Sat Dec 18 17:58:19 2021 +0000 Fix a couple of warnings / Pi issues. Signed-off-by: Andrea Odetti commit 86122aa5f5712ea1a4a490c1256ab97ca17cc074 Author: Andrea Odetti Date: Sat Dec 18 16:45:44 2021 +0000 Update ImGui. Signed-off-by: Andrea Odetti commit 913081649efbec476e52ddeb18086890d6d32804 Author: Andrea Odetti Date: Sat Dec 18 16:43:23 2021 +0000 Unify exceptions. Signed-off-by: Andrea Odetti commit 9590d7c4f95cc6aa1719f06f08e7441892e02cff Merge: 151ef943 47f72188 Author: Andrea Odetti Date: Sat Dec 18 16:38:39 2021 +0000 Merge remote-tracking branch 'upstream/master' commit 47f721882ea7d397ff6ae72e121587c2b48ea5f7 Author: Andrea Date: Sat Dec 18 16:37:28 2021 +0000 throw std::runtime_error instead of std::string (PR #1011) + add more information about location of yaml parser error. commit 151ef943826ebccec3e4f8638f8651bf65cabaf4 Merge: c758e688 651b57fd Author: Andrea Odetti Date: Sun Dec 12 19:55:51 2021 +0000 Merge remote-tracking branch 'upstream/master' commit c758e6884c43607d42d2f118b25ecb0148eae23d Author: Andrea Odetti Date: Sun Dec 12 19:40:41 2021 +0000 qapple: patch qapple.pro. Qt Creator is better at debugging qapple. Signed-off-by: Andrea Odetti commit b80f7c5f508206df40850f7138e3d97103536a2b Author: Andrea Odetti Date: Sun Dec 12 19:15:40 2021 +0000 QApple: fix VidHD. Signed-off-by: Andrea Odetti commit 28e5703ab5c6982cbfd03ca93ee1754e260168c4 Author: Andrea Odetti Date: Sun Dec 12 18:31:53 2021 +0000 Update ImGui. Signed-off-by: Andrea Odetti commit 651b57fdaed23c755eba6f453da55fa669f0141d Author: tomcw Date: Sun Dec 12 11:53:37 2021 +0000 HDD: fix for slowdown during r/w block operations . firmware does busy-wait RMW (rol abs,x) . the IORead then IOWrite was flipping the LED status each access, with a huge performance penalty! commit 19aadbfb8df421cbdd89dc5a8214259d77570adb Author: tomcw Date: Sat Dec 11 20:27:39 2021 +0000 Add a top-level catch-handler for std::string commit 6dd4390c0b145d2dedf4641290b83ed21e2be3d8 Author: Andrea Odetti Date: Sat Dec 11 20:26:38 2021 +0000 libretro: update cards. Signed-off-by: Andrea Odetti commit abe7d664c52fd29e79286d24e440cdb87c92c3ca Author: Andrea Odetti Date: Sat Dec 11 20:08:47 2021 +0000 Update from AW. Signed-off-by: Andrea Odetti commit b5b2958be1241963ddc49c3801175345a23a6278 Merge: 9d8d1110 408f5fe4 Author: Andrea Odetti Date: Sat Dec 11 20:08:34 2021 +0000 Merge remote-tracking branch 'upstream/master' # Conflicts: # source/CardManager.cpp commit 9d8d1110abca689ab39fc9dab73f9af9108dedf6 Merge: 0ae58c58 49d0b753 Author: Andrea Date: Sat Dec 11 20:00:12 2021 +0000 Merge pull request #51 from audetto/vidhd Vidhd commit 408f5fe42f840ae4a067eb1f924ad4544f494e0e Author: tomcw Date: Sat Dec 11 19:30:54 2021 +0000 Fix for loading save-state - eg. where 'AS-S2_End Credits' & 'OMT-PAL' lost their precise frame cycle commit a07ba4ade0f517ceb64dbdf3451e929e79fe647e Author: Andrea Date: Sat Dec 11 18:05:06 2021 +0000 Set SHR alpha channel to 255 for consistency with other video modes. (PR #1010) commit 1670e0dbc092429b1e5024b9cc6762203e167496 Author: tomcw Date: Sat Dec 11 17:57:35 2021 +0000 Fix for AppleWin-Test: . don't load floppy/harddisk images (in Registry's Config) if also loading a save-state via cmd-line. . done to prevent MessageBox alerts when deleted disk images can't be found. Info: Registry contains refs to disk images, but on test clean-up, the images get deleted. commit 49d0b7535df2d12a132c72d20d8a674823641a93 Author: Andrea Odetti Date: Sat Dec 11 14:25:37 2021 +0000 Set SHR Alpha to 255 for maximum compatibility. Signed-off-by: Andrea Odetti commit aa7f3277d16f71e89940de52c46e19126abe811e Author: Andrea Odetti Date: Sat Dec 11 17:14:09 2021 +0000 SDL: reset hardware in Begin, not Initialize. Signed-off-by: Andrea Odetti commit 177c9fec05fc443373a5b7998952bea12a488616 Author: Andrea Date: Sat Dec 11 15:26:09 2021 +0000 Make Slot 0 more consistent with other slots. (PR #1006) commit 010d3525d38d6576348dbe3ea7c2d5a40d478ae9 Author: tomcw Date: Sat Dec 11 15:11:04 2021 +0000 HDD read: error if reading block to ROM (#1007) commit 2de04b35c5196531ff9caf1b189c008a6ae0b66a Author: tomcw Date: Sat Dec 11 14:32:00 2021 +0000 Help: Update debugger info for 'brk' cmd commit 0ac210b13285e8434b908202171e2852cd068516 Author: tomcw Date: Sat Dec 11 13:41:34 2021 +0000 Debugger: Extend 'brk' cmd: . brk all Fix 'brk' cmd for invalid opcodes of length 2 & 3 commit 1ab0d56558e96d980c7de46735dff852561d8928 Author: tomcw Date: Fri Dec 10 19:37:58 2021 +0000 HDD: remove magic numbers & add GH ref (#1007) commit bb6fa803359aec0828a09b75db03bf06949b07e0 Author: tomcw Date: Thu Dec 9 21:22:13 2021 +0000 HDD r/w: error if r/w overlaps with I/O memory . break to debugger if single-stepping HDD write: support memory accesses across 64KiB boundary commit c594d6e101f7f4c4a71705ad7f257454b25a3ae8 Author: tomcw Date: Tue Dec 7 21:59:05 2021 +0000 VidHD: Fix SHIFT+PRINTSCRN for 320x200 commit 36e1c5a135a871e1a1f0b72102afd770af8f65cb Author: Andrea Odetti Date: Sat Dec 4 13:36:33 2021 +0000 Move ExpansionCard utility to separate file. Signed-off-by: Andrea Odetti commit b74025597932f6a097bbeb4fb6bdd7f87134548b Author: Andrea Odetti Date: Sat Dec 4 12:47:09 2021 +0000 Fix VidHD card insertion. Signed-off-by: Andrea Odetti commit ad6ca9c94037ecf3287612f70bd21366b8131f8b Author: Andrea Odetti Date: Sat Dec 4 12:29:57 2021 +0000 Move Initialise/Destroy Emulator to LinuxFrame. Signed-off-by: Andrea Odetti commit 6e422740338b6ec8dfab703c95d2ae951b0a3003 Merge: 9439b1b4 3128fdb4 Author: Andrea Odetti Date: Fri Dec 3 19:42:53 2021 +0000 Uthernet2 as a Card. Signed-off-by: Andrea Odetti commit 9439b1b4005343fb9a963d1e9c26214edc611665 Author: Andrea Odetti Date: Fri Dec 3 18:46:23 2021 +0000 Incorporate VidHD fro AW. Signed-off-by: Andrea Odetti commit 79668e49e19b21df3240858ed32ca40ee4f5b129 Merge: 0ae58c58 443545b0 Author: Andrea Odetti Date: Fri Dec 3 10:15:27 2021 +0000 Merge remote-tracking branch 'upstream/master' into hd # Conflicts: # source/Card.h commit 443545b0f6d31349eb365956ab948561e889cc15 Author: TomCh Date: Tue Nov 30 21:41:02 2021 +0000 Support SHR video modes with a VidHD card (#997, PR #1004) Support VidHD in slot 3 (via Config GUI or '-s3 vidhd') for SHR video modes only. - AppleWin window is slightly enlarged when VidHD card is inserted. Support IIgs 320x200 (and fill mode) and 640x200 video modes. Debugger: add 'shr' command to view video CUI: Allow user to specify width & height (for full-screen); and allow separate x,y scaling in full-screen mode. commit 0ae58c58155a81dcbaca7557387cdf2c64b87a27 Author: Andrea Odetti Date: Tue Nov 30 09:23:33 2021 +0000 libretro: include DiscControl in save state. Signed-off-by: Andrea Odetti commit a6eea044afc2395167aafc1d9e3014ea354d25ef Author: Andrea Odetti Date: Mon Nov 29 20:46:16 2021 +0000 Update 3rd party repos. Signed-off-by: Andrea Odetti commit 36ab6a8b4fabca3377efe917b7e0280e798dbcc1 Author: Andrea Odetti Date: Mon Nov 29 09:59:34 2021 +0000 cmake changes: 1) always build libappleii as static 2) expose OpenGL as a cmake variable commit 1f1ef6c8a395413d1bdb386f60e4ee7c4abe079d Author: Andrea Odetti Date: Sun Nov 28 19:27:56 2021 +0000 libretro: remove poor function. Signed-off-by: Andrea Odetti commit 97c3472e6e854cc16f04b30f0211da0b9d453b58 Author: Andrea Odetti Date: Sun Nov 28 19:18:38 2021 +0000 libretro: support relative paths in m3u playlists. Signed-off-by: Andrea Odetti commit e08363f141a79581c2517ff6393993a06d003452 Author: Andrea Odetti Date: Sun Nov 28 18:52:40 2021 +0000 libretro: ignore RetroArch request to re-insert the previous image. The idea is that on a multigame floppy, only the first is bootable. This seems the most common use case. Signed-off-by: Andrea Odetti commit 24a135e042c0a39277d2e47d7d50aa76ce325bb9 Author: Andrea Odetti Date: Sun Nov 28 18:51:42 2021 +0000 libretro: add m3u as supported extension. Signed-off-by: Andrea Odetti commit 76317c2df1b16c479c4aedbae21898a590b7624c Author: Andrea Odetti Date: Sun Nov 28 18:49:17 2021 +0000 libwindows: a very tiny step towards compilation of libretro with VS. Signed-off-by: Andrea Odetti commit d46c90a82ce52d59f766da102016ea8a9e3e25cb Author: Andrea Odetti Date: Sun Nov 28 14:52:58 2021 +0000 libretro: cosmetic changes. Signed-off-by: Andrea Odetti commit a16b2b2d35e0dc734aa6b6d3f40773e7b14551e9 Author: Andrea Odetti Date: Sun Nov 28 14:29:16 2021 +0000 More elegant serialisation data structure. Signed-off-by: Andrea Odetti commit 02740cee805177cfbe0833382f53e7246d386912 Author: Andrea Odetti Date: Sun Nov 28 14:12:55 2021 +0000 libretro: handle m3u format as gamepath. Implement retro_set_initial_image. Signed-off-by: Andrea Odetti commit 30dbfad59cd508937b893374b9f0568feef5ee4c Author: Andrea Odetti Date: Sun Nov 28 13:24:25 2021 +0000 libretro: implement retro_reset(). Signed-off-by: Andrea Odetti commit 7037e20c0f4d44ca6278eadea2922ede8c1cf846 Author: Andrea Odetti Date: Sat Nov 27 21:27:46 2021 +0000 libretro: implement save/load state. Signed-off-by: Andrea Odetti commit 741849a0266bdaea30dadc45770203d1f538fe76 Author: Andrea Odetti Date: Sat Nov 27 13:45:34 2021 +0000 Implement 2nd version of the Disc Control interface. Signed-off-by: Andrea Odetti commit 117502b77425fd87094a17c3d4e868215b020f88 Author: Andrea Odetti Date: Sat Nov 27 11:39:55 2021 +0000 Implement base Disc Control interface. Signed-off-by: Andrea Odetti commit cca65baf97def3b4468f5ce9c894d8cec8124d57 Author: Andrea Odetti Date: Sat Nov 27 11:38:40 2021 +0000 Reduce verbosity of key logger. Signed-off-by: Andrea Odetti commit eb74e285853a605ff747f4f4f5bc5cdc45410fa8 Author: Andrea Odetti Date: Sat Nov 27 10:12:38 2021 +0000 libretro: fix meaning of memory ids. Signed-off-by: Andrea Odetti commit 34a0baa65ec0a1737512f1d1aa1b61535e3838bd Author: Andrea Odetti Date: Fri Nov 26 21:09:16 2021 +0000 Update 3rd party repos. Signed-off-by: Andrea Odetti commit fad9da0ec293c254ab872f069d73f8c41c2b3f51 Author: Andrea Odetti Date: Fri Nov 26 20:54:28 2021 +0000 Fix for recent AW. Signed-off-by: Andrea Odetti commit f61d2073e586df21a9b12bd19525064e82e96c6a Merge: f0805997 0b2104cc Author: Andrea Odetti Date: Fri Nov 26 20:51:34 2021 +0000 Merge remote-tracking branch 'upstream/master' commit 0b2104cc8929d382cdb39fc8d55cff41adc62d6b Author: Andrea Date: Thu Nov 25 20:23:21 2021 +0000 Card::Save/LoadSnapshot (PR #1003) Add 2 virtual methods to Card to load and save snapshots. commit f080599718251ae9078c5aebb80bdfd42ec55977 Author: Andrea Odetti Date: Wed Nov 24 18:02:27 2021 +0000 libretro: support mouse retropad. commit 6175dc8a55462e78c3abedd06ebac56c552a7584 Author: Andrea Odetti Date: Wed Nov 24 14:08:14 2021 +0000 Add support for memory / achievements. Signed-off-by: Andrea Odetti commit 8fd124e3ae8e78bf939a57ed273826e3ce34898f Author: Andrea Odetti Date: Wed Nov 24 14:01:37 2021 +0000 Enable printf warnings. on retro_log_cb. Signed-off-by: Andrea Odetti commit 759cb5ac64d000ffbacd0071f84b19402c88a0a6 Author: Andrea Odetti Date: Mon Nov 22 19:04:48 2021 +0000 libretro: ensure game is released *before* shutdown. Signed-off-by: Andrea Odetti commit ea828795c3b4eb664a0bd7d766648262fcf7684f Merge: 7c81aafa abc63143 Author: Andrea Odetti Date: Mon Nov 22 18:58:44 2021 +0000 Merge remote-tracking branch 'upstream/master' commit 7c81aafad3d11d179a321bc4dc55c76ec92ab53c Author: Andrea Odetti Date: Mon Nov 22 18:53:39 2021 +0000 libretro: controllers are inserted after the Game has loaded. Signed-off-by: Andrea Odetti commit 0493cade10b730dc1c3f6f1eea5ebe9e596bceef Merge: 8da9f845 68291641 Author: Andrea Date: Mon Nov 22 18:50:08 2021 +0000 Merge pull request #48 from audetto/cmake Cmake: add ability to select frontends commit 682916415c5a1bf5a7ff9b118a6f84f541b91c25 Author: Andrea Odetti Date: Sun Nov 21 20:20:37 2021 +0000 Add libretro.h to the git repo. To ease libretro compilation. Signed-off-by: Andrea Odetti commit 7839aa0330efc1ce15b718542d30d6c65f8d9dbd Author: Andrea Odetti Date: Sun Nov 21 20:09:57 2021 +0000 Add ability to select which frontend to build. cmake .. -DBUILD_SA2=ON or use cmake-gui. Signed-off-by: Andrea Odetti commit 3128fdb4493f0bd8db3e2bde5ad18dc96a44c063 Author: Andrea Odetti Date: Sun Nov 14 16:06:08 2021 +0000 Ensure the Plug-And-Play works as much as possible. Reset all the IO handlers every time a card is inserted / removed. Signed-off-by: Andrea Odetti commit 3c09d7557377b6308fab4a91f2e3cfca2acb29a9 Author: Andrea Odetti Date: Sun Nov 14 16:05:28 2021 +0000 Rewrite the Uthernet2 as Card. Signed-off-by: Andrea Odetti commit abc631432d06c8b2d075247ff031a9ba5d46307f Author: tomcw Date: Mon Nov 15 20:26:53 2021 +0000 HDD: fix edge-case for old save-state m_buf_ptr commit 01f89f88eb1150f528b29d266815fb81ed2525fc Author: TomCh Date: Sun Nov 14 17:40:15 2021 +0000 Joystick: Remove buttonlatch & BUTTONTIME (#1002) --- AppleWinExpress2008.vcproj | 8 + AppleWinExpress2019.vcxproj | 2 + AppleWinExpress2019.vcxproj.filters | 6 + CMakeLists.txt | 57 +- bin/History.txt | 17 + docs/Debugger_Changelog.txt | 36 +- docs/Debugger_Wishlist.txt | 31 + help/CommandLine.html | 2 + help/cfg-config.html | 9 +- help/cfg-input.html | 4 +- help/dbg-breakpoints.html | 22 +- help/img/config.png | Bin 35144 -> 35890 bytes help/savestate.html | 1 + linux.md | 15 +- resource/Applewin.rc | 38 +- resource/resource.h | 3 +- resource/version.h | 2 +- source/AY8910.cpp | 8 +- source/CMakeLists.txt | 69 +- source/CPU.cpp | 2 +- source/CPU/cpu_general.inl | 5 +- source/Card.cpp | 40 + source/Card.h | 11 + source/CardManager.cpp | 87 +- source/CardManager.h | 12 +- source/CmdLine.cpp | 39 +- source/CmdLine.h | 8 + source/Common.h | 5 + source/Configuration/PageConfig.cpp | 26 +- source/Configuration/PropertySheetHelper.cpp | 2 + source/Debugger/Debug.cpp | 214 +- source/Debugger/Debug.h | 3 + source/Debugger/Debugger_Assembler.cpp | 1 + source/Debugger/Debugger_Assembler.h | 1 + source/Debugger/Debugger_Color.cpp | 1 + source/Debugger/Debugger_Color.h | 1 + source/Debugger/Debugger_Commands.cpp | 14 +- source/Debugger/Debugger_Console.h | 4 +- source/Debugger/Debugger_Disassembler.cpp | 264 +- source/Debugger/Debugger_DisassemblerData.cpp | 253 +- source/Debugger/Debugger_Display.cpp | 55 +- source/Debugger/Debugger_Help.cpp | 12 +- source/Debugger/Debugger_Symbols.cpp | 49 +- source/Debugger/Debugger_Types.h | 70 +- source/Disk.cpp | 20 +- source/Disk.h | 4 +- source/DiskFormatTrack.cpp | 2 +- source/FourPlay.cpp | 4 +- source/FourPlay.h | 4 +- source/FrameBase.cpp | 4 +- source/FrameBase.h | 6 +- source/Harddisk.cpp | 76 +- source/Harddisk.h | 4 +- source/Joystick.cpp | 74 +- source/Joystick.h | 1 - source/LanguageCard.cpp | 78 +- source/LanguageCard.h | 33 +- source/Memory.cpp | 195 +- source/Memory.h | 16 +- source/Mockingboard.cpp | 16 +- source/MouseInterface.cpp | 16 +- source/MouseInterface.h | 4 +- source/NTSC.cpp | 126 +- source/ParallelPrinter.cpp | 14 +- source/ParallelPrinter.h | 2 +- source/RGBMonitor.cpp | 2 +- source/SAM.cpp | 4 +- source/SAM.h | 4 +- source/SNESMAX.cpp | 4 +- source/SNESMAX.h | 4 +- source/SSI263.cpp | 2 +- source/SaveState.cpp | 162 +- source/SerialComms.cpp | 12 +- source/SerialComms.h | 4 +- source/StdAfx.h | 2 +- source/Tfe/tfe.cpp | 15 +- source/Tfe/tfe.h | 2 +- source/Utilities.cpp | 22 +- source/Utilities.h | 2 +- source/VidHD.cpp | 243 + source/VidHD.h | 48 + source/Video.cpp | 94 +- source/Video.h | 47 +- source/Windows/AppleWin.cpp | 66 +- source/Windows/Win32Frame.cpp | 43 +- source/Windows/Win32Frame.h | 14 +- source/Windows/WinFrame.cpp | 63 +- source/YamlHelper.cpp | 86 +- source/YamlHelper.h | 14 +- source/Z80VICE/z80.cpp | 4 +- source/frontends/common2/CMakeLists.txt | 3 + source/frontends/common2/commonframe.cpp | 86 +- source/frontends/common2/commonframe.h | 10 +- source/frontends/common2/fileregistry.cpp | 28 +- source/frontends/common2/fileregistry.h | 1 + source/frontends/common2/gnuframe.cpp | 99 + source/frontends/common2/gnuframe.h | 22 + source/frontends/common2/programoptions.cpp | 6 - source/frontends/common2/utils.cpp | 144 +- source/frontends/common2/utils.h | 6 +- source/frontends/libretro/CMakeLists.txt | 103 +- source/frontends/libretro/analog.cpp | 7 +- source/frontends/libretro/analog.h | 2 +- source/frontends/libretro/buffer.h | 45 + source/frontends/libretro/diskcontrol.cpp | 289 ++ source/frontends/libretro/diskcontrol.h | 51 + source/frontends/libretro/environment.cpp | 2 - source/frontends/libretro/environment.h | 6 +- source/frontends/libretro/game.cpp | 100 +- source/frontends/libretro/game.h | 20 +- source/frontends/libretro/joypad.cpp | 7 +- source/frontends/libretro/joypad.h | 2 +- source/frontends/libretro/joypadbase.cpp | 15 +- source/frontends/libretro/joypadbase.h | 16 +- .../libretro-common/include/libretro.h | 3890 +++++++++++++++++ source/frontends/libretro/libretro.cpp | 228 +- source/frontends/libretro/mouse.cpp | 21 + source/frontends/libretro/mouse.h | 23 + source/frontends/libretro/rdirectsound.cpp | 2 +- source/frontends/libretro/retroframe.cpp | 6 +- source/frontends/libretro/retroframe.h | 5 +- source/frontends/libretro/retroregistry.cpp | 20 +- source/frontends/libretro/serialisation.cpp | 114 + source/frontends/libretro/serialisation.h | 16 + source/frontends/ncurses/CMakeLists.txt | 10 +- source/frontends/ncurses/main.cpp | 21 +- source/frontends/ncurses/nframe.cpp | 44 +- source/frontends/ncurses/nframe.h | 6 +- source/frontends/qt/CMakeLists.txt | 1 + source/frontends/qt/QHexView | 2 +- source/frontends/qt/options.cpp | 8 + source/frontends/qt/options.h | 1 + source/frontends/qt/preferences.cpp | 5 + source/frontends/qt/preferences.ui | 45 +- source/frontends/qt/qapple.cpp | 80 +- source/frontends/qt/qapple.pro | 5 +- source/frontends/qt/qdirectsound.cpp | 2 +- source/frontends/qt/qtframe.cpp | 11 +- source/frontends/qt/qtframe.h | 3 +- source/frontends/qt/qvideo.cpp | 5 +- source/frontends/qt/qvideo.h | 3 + source/frontends/sdl/CMakeLists.txt | 39 +- source/frontends/sdl/imgui/imgui | 2 +- source/frontends/sdl/imgui/imgui_club | 2 +- source/frontends/sdl/imgui/sdlimguiframe.cpp | 61 +- source/frontends/sdl/imgui/sdlimguiframe.h | 5 +- source/frontends/sdl/imgui/sdlsettings.cpp | 63 +- source/frontends/sdl/imgui/sdlsettings.h | 4 +- source/frontends/sdl/imgui/settingshelper.cpp | 134 +- source/frontends/sdl/imgui/settingshelper.h | 6 +- source/frontends/sdl/main.cpp | 16 +- source/frontends/sdl/processfile.cpp | 4 +- .../sdl/renderer/sdlrendererframe.cpp | 9 +- .../frontends/sdl/renderer/sdlrendererframe.h | 7 +- source/frontends/sdl/sdirectsound.cpp | 2 +- source/frontends/sdl/sdlframe.cpp | 35 +- source/frontends/sdl/sdlframe.h | 7 +- source/linux/build.sh | 4 +- source/linux/context.cpp | 81 + source/linux/context.h | 3 + source/linux/duplicates/FourPlay.cpp | 17 +- source/linux/duplicates/Joystick.cpp | 22 +- source/linux/duplicates/SNESMAX.cpp | 19 +- source/linux/duplicates/SerialComms.cpp | 9 +- source/linux/keyboard.cpp | 2 +- source/linux/keyboard.h | 2 - source/linux/libwindows/CMakeLists.txt | 45 + .../linux/{windows => libwindows}/dmusicc.cpp | 4 +- .../linux/{windows => libwindows}/dmusicc.h | 6 +- .../linux/{windows => libwindows}/dsound.cpp | 3 +- source/linux/{windows => libwindows}/dsound.h | 8 +- .../files.cpp => libwindows/fileapi.cpp} | 2 +- .../{windows/files.h => libwindows/fileapi.h} | 4 +- source/linux/{windows => libwindows}/gdi.h | 0 .../linux/{windows => libwindows}/guiddef.cpp | 4 +- .../linux/{windows => libwindows}/guiddef.h | 2 +- source/linux/{windows => libwindows}/misc.cpp | 2 +- source/linux/{windows => libwindows}/misc.h | 4 +- source/linux/{windows => libwindows}/mmreg.h | 2 +- .../linux/{windows => libwindows}/mmsystem.h | 0 .../stringcb.cpp => libwindows/strsafe.cpp} | 2 +- .../stringcb.h => libwindows/strsafe.h} | 2 +- .../time.cpp => libwindows/timeapi.cpp} | 2 +- .../{windows/time.h => libwindows/timeapi.h} | 2 +- .../linux/{windows => libwindows}/winbase.cpp | 8 +- .../linux/{windows => libwindows}/winbase.h | 4 +- .../linux/{windows => libwindows}/wincompat.h | 0 source/linux/libwindows/windows.h | 20 + .../linux/{windows => libwindows}/winerror.h | 2 +- .../{windows/bitmap.h => libwindows/wingdi.h} | 2 +- .../handles.cpp => libwindows/winhandles.cpp} | 2 +- .../handles.h => libwindows/winhandles.h} | 2 +- .../linux/{windows => libwindows}/winnls.cpp | 2 +- source/linux/{windows => libwindows}/winnls.h | 2 +- .../strings.cpp => libwindows/winstrings.cpp} | 2 +- .../strings.h => libwindows/winstrings.h} | 2 +- .../linux/{windows => libwindows}/winuser.cpp | 11 +- .../linux/{windows => libwindows}/winuser.h | 2 +- source/linux/linuxframe.cpp | 38 +- source/linux/linuxframe.h | 13 +- source/linux/linuxinterface.h | 3 +- source/linux/network/uthernet2.cpp | 1514 ++++--- source/linux/network/uthernet2.h | 103 +- source/linux/paddle.h | 2 +- source/linux/registry.cpp | 3 +- source/linux/registry.h | 2 +- source/linux/win.h | 21 - test/TestCPU6502/CMakeLists.txt | 3 + test/TestCPU6502/TestCPU6502.cpp | 1 + test/TestCPU6502/stdafx.h | 2 +- 210 files changed, 8727 insertions(+), 2506 deletions(-) create mode 100644 source/VidHD.cpp create mode 100644 source/VidHD.h create mode 100644 source/frontends/common2/gnuframe.cpp create mode 100644 source/frontends/common2/gnuframe.h create mode 100644 source/frontends/libretro/buffer.h create mode 100644 source/frontends/libretro/diskcontrol.cpp create mode 100644 source/frontends/libretro/diskcontrol.h create mode 100644 source/frontends/libretro/libretro-common/include/libretro.h create mode 100644 source/frontends/libretro/mouse.cpp create mode 100644 source/frontends/libretro/mouse.h create mode 100644 source/frontends/libretro/serialisation.cpp create mode 100644 source/frontends/libretro/serialisation.h create mode 100644 source/linux/libwindows/CMakeLists.txt rename source/linux/{windows => libwindows}/dmusicc.cpp (81%) rename source/linux/{windows => libwindows}/dmusicc.h (74%) rename source/linux/{windows => libwindows}/dsound.cpp (98%) rename source/linux/{windows => libwindows}/dsound.h (96%) rename source/linux/{windows/files.cpp => libwindows/fileapi.cpp} (99%) rename source/linux/{windows/files.h => libwindows/fileapi.h} (96%) rename source/linux/{windows => libwindows}/gdi.h (100%) rename source/linux/{windows => libwindows}/guiddef.cpp (81%) rename source/linux/{windows => libwindows}/guiddef.h (92%) rename source/linux/{windows => libwindows}/misc.cpp (74%) rename source/linux/{windows => libwindows}/misc.h (93%) rename source/linux/{windows => libwindows}/mmreg.h (87%) rename source/linux/{windows => libwindows}/mmsystem.h (100%) rename source/linux/{windows/stringcb.cpp => libwindows/strsafe.cpp} (91%) rename source/linux/{windows/stringcb.h => libwindows/strsafe.h} (85%) rename source/linux/{windows/time.cpp => libwindows/timeapi.cpp} (98%) rename source/linux/{windows/time.h => libwindows/timeapi.h} (94%) rename source/linux/{windows => libwindows}/winbase.cpp (92%) rename source/linux/{windows => libwindows}/winbase.h (96%) rename source/linux/{windows => libwindows}/wincompat.h (100%) create mode 100644 source/linux/libwindows/windows.h rename source/linux/{windows => libwindows}/winerror.h (95%) rename source/linux/{windows/bitmap.h => libwindows/wingdi.h} (86%) rename source/linux/{windows/handles.cpp => libwindows/winhandles.cpp} (82%) rename source/linux/{windows/handles.h => libwindows/winhandles.h} (93%) rename source/linux/{windows => libwindows}/winnls.cpp (96%) rename source/linux/{windows => libwindows}/winnls.h (87%) rename source/linux/{windows/strings.cpp => libwindows/winstrings.cpp} (95%) rename source/linux/{windows/strings.h => libwindows/winstrings.h} (96%) rename source/linux/{windows => libwindows}/winuser.cpp (72%) rename source/linux/{windows => libwindows}/winuser.h (97%) delete mode 100644 source/linux/win.h diff --git a/AppleWinExpress2008.vcproj b/AppleWinExpress2008.vcproj index 908467674..ec9d92d4f 100644 --- a/AppleWinExpress2008.vcproj +++ b/AppleWinExpress2008.vcproj @@ -1001,6 +1001,14 @@ RelativePath=".\source\Video.h" > + + + + + @@ -247,6 +248,7 @@ + diff --git a/AppleWinExpress2019.vcxproj.filters b/AppleWinExpress2019.vcxproj.filters index 49c6034d5..26c96c5dd 100644 --- a/AppleWinExpress2019.vcxproj.filters +++ b/AppleWinExpress2019.vcxproj.filters @@ -241,6 +241,9 @@ Source Files\Emulator + + Source Files\Video + @@ -555,6 +558,9 @@ Source Files\Emulator + + Source Files\Video + diff --git a/CMakeLists.txt b/CMakeLists.txt index 837fad273..fe84696af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,19 +2,26 @@ cmake_minimum_required(VERSION 3.13) project(applewin HOMEPAGE_URL "https://github.com/audetto/AppleWin") +option(BUILD_APPLEN "build ncurses frontend") +option(BUILD_QAPPLE "build Qt5 frontend") +option(BUILD_SA2 "build SDL2 frontend") +option(BUILD_LIBRETRO "build libretro core") + +if (NOT (BUILD_APPLEN OR BUILD_QAPPLE OR BUILD_SA2 OR BUILD_LIBRETRO)) + message(NOTICE "Building everything by default") + set(BUILD_APPLEN ON) + set(BUILD_QAPPLE ON) + set(BUILD_SA2 ON) + set(BUILD_LIBRETRO ON) +endif() + set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_compile_options(-Werror=return-type) -if(${APPLE}) - MESSAGE("MACOSX DETECTED") - SET(MACOSX TRUE) - add_compile_options(-Werror=return-type -Werror=format -Wno-error=format-overflow -Wno-error=format-truncation) -endif() - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - add_compile_options(-Werror=format -Wno-error=format-overflow -Wno-error=format-truncation) + add_compile_options(-Werror=format -Wno-error=format-overflow -Wno-error=format-truncation -Wno-psabi) endif() MESSAGE("CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") @@ -35,7 +42,7 @@ execute_process(COMMAND uname -n if(${UNAME} STREQUAL raspberrypi) # it is too slow and might cause out of memory issues # more forensic is required - MESSAGE("Raspberry Pi detected: IPO disabled") + MESSAGE(NOTICE "Raspberry Pi detected: IPO disabled") else() include(CheckIPOSupported) check_ipo_supported() @@ -45,18 +52,34 @@ endif() include_directories(source) add_subdirectory(source) -add_subdirectory(source/frontends/common2) -if(NOT(${APPLE})) - add_subdirectory(source/frontends/ncurses) -endif() -add_subdirectory(source/frontends/qt) -add_subdirectory(source/frontends/sdl) -if(NOT($APPLE})) -add_subdirectory(source/frontends/libretro) +add_subdirectory(source/linux/libwindows) add_subdirectory(test/TestCPU6502) + +if (BUILD_LIBRETRO OR BUILD_APPLEN OR BUILD_SA2) + add_subdirectory(source/frontends/common2) +endif() + +if (BUILD_APPLEN) + add_subdirectory(source/frontends/ncurses) endif() -set(CPACK_PACKAGE_VERSION "1.30.6.0") +if (BUILD_QAPPLE) + add_subdirectory(source/frontends/qt) +endif() + +if (BUILD_SA2) + add_subdirectory(source/frontends/sdl) +endif() + +if (BUILD_LIBRETRO) + add_subdirectory(source/frontends/libretro) +endif() + +file(STRINGS resource/version.h VERSION_FILE LIMIT_COUNT 1) +string(REGEX MATCH "#define APPLEWIN_VERSION (.*)" _ ${VERSION_FILE}) +string(REPLACE "," "." VERSION ${CMAKE_MATCH_1}) + +set(CPACK_PACKAGE_VERSION ${VERSION}) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Apple ][ emulator for Linux") set(CPACK_PACKAGE_CONTACT "audetto ") diff --git a/bin/History.txt b/bin/History.txt index 7d65cb919..c615a02cd 100644 --- a/bin/History.txt +++ b/bin/History.txt @@ -8,6 +8,23 @@ https://github.com/AppleWin/AppleWin/issues/new Tom Charlesworth +1.30.7.0 - 19 Dec 2021 +---------------------- +. [Bug #997] Support VidHD card for IIgs Super Hi-Res (SHR) video modes, eg. for Total Replay box art. + - Support VidHD in slot 3 (via Config GUI or '-s3 vidhd'). + - Only partial VidHD support - ie. just for SHR video modes, but SHR is supported for all Apple II models. + - NB. AppleWin's window is slightly enlarged when VidHD card is inserted. + - Command line: Allow user to specify width & height (for full-screen); and allow separate x,y scaling in full-screen mode. + - eg. for 4:3 aspect ratio on monitors that support it: -no-full-screen -fs-width=1600 -fs-height=1200 + - Debugger: add 'shr' command to view video. +. [Change #1007] HDD: Fail if r/w access touches $Cnnn I/O space + - Debugger: On a HDD r/w failure, execution will break, and the debugger will show a stop reason message. + - Also fix HDD write wrapping at 64KiB boundary. +. [Change #996] Adapt HDD firmware to be slot-independent. + - Internal only, since currently no support to specify HDD Controller card in another slot. +. Debugger: Add 'brk all ' command to break on any BRK or invalid opcode. + + 1.30.6.0 - 30 Oct 2021 ---------------------- . [Bug #993] Fix Mousecard not persisting after a machine's hardware changes. (Regression at 1.30.5.0) diff --git a/docs/Debugger_Changelog.txt b/docs/Debugger_Changelog.txt index b2ca5a724..3e1db1aa2 100644 --- a/docs/Debugger_Changelog.txt +++ b/docs/Debugger_Changelog.txt @@ -1,5 +1,39 @@ /* - +2.9.1.13 Added: CD now detects ".." to change to the previous directory and chops the trailing sub-directory from the current path. + It worked before but would clutter up the current directory with a trailing "..\". +2.9.1.12 Added: New commands HGR0, HGR3, HGR4, HGR5 to see pseudo pages $00, $60, $80, $A0 respectively. +2.9.1.11 Fixed: Right justify signed decimal values. + Example: + U 300 + 300:A9 80 A9 81 A9 FF A9 00 A9 01 A9 7E A9 7F + Will display as: + LDA #$80 #-128 + LDA #$81 #-127 + LDA #$FF #-1 + LDA #$00 + LDA #$01 #+1 + LDA #$7E #+126 + LDA #$7F #+127 +2.9.1.10 Fixed: Immedate #80 was not showing -128 for the signed decimal value. +2.9.1.9 Fixed: Immediate #0 was showing '#' prefix but not showing zero for the signed decimal value. Changed to show the signed decimal value only if non zero. +2.9.1.8 Changed: Disassembly window now lists symbol labels and symbol target address from User2 in orange. + Example: + U 300 + SYM @ = 303 + 300: 20 03 03 +2.9.1.7 Added: Extended SYM command to auto-generate symbol names when reverse engineering. NOTE: These symbols will be placed in User2. + Example: + SYM @ = 800 // Alias for: SYM _0800 = 0800 +2.9.1.6 Added: Branch instructions now show target address. +2.9.1.5 Added: Disassembly window now shows signed decimal values for immediate values. +2.9.1.4 Changed: Show symbol warnings in Orange, and length of symbols in light blue. +2.9.1.3 Added: DB commanoptionally supports = + DB HGR = 2000:3FFF +2.9.1.2 Fixed: Off by one end address when deleting DisasmData_t +2.9.1.1 Added: X command now supports a range and will chop off the appropiate data sections. + DB 2000:2005 + X 2002:2003 +Released post 1.30.7.0 2.9.1.0 Added: Bookmarks now have their own indicator (a number with a box around it) and replace the ":" seperator. Updated Debug_Font.bmp diff --git a/docs/Debugger_Wishlist.txt b/docs/Debugger_Wishlist.txt index cc18bbbab..6ef84424e 100644 --- a/docs/Debugger_Wishlist.txt +++ b/docs/Debugger_Wishlist.txt @@ -1,6 +1,37 @@ Requests (Wishlist): ==================== +* HOME/CLS Clear Screen + +* DT Define Target + Default disassembly: + D7AF:A9 13 LDA #$13 + D7B1:A0 E9 LDY #$E9 + Correct disassembly: + DT D7AF:B7B2 + D7AF:A9 13 LDA #CON.ONE + +* DF Define FAC + +* Cleanup Applesoft listing + * DB + * DA + * DF + +* Read/Write Track/Sector + DISK 2 READ 11 F 800 + DISK 2 WRITE 11 F 800 + +* Save/Load memory to mounted DOS3.3 disk + DSAVE "LOGO.HGR",2000:3FFF,6,1 + DLOAD "LOGO.HGR" + +* Save/load memory to mounted ProDOS disk/hard drive + PREFIX + PSAVE "LOGO.HGR",2000:3FFF + PLOAD "LOGO.HGR" + * [ ] HELP BPM on read/write Nail down syntax: BPM A7 = R diff --git a/help/CommandLine.html b/help/CommandLine.html index 9945171ef..760e54385 100644 --- a/help/CommandLine.html +++ b/help/CommandLine.html @@ -48,6 +48,8 @@

Command line

Remove the SSC card from slot 2.

-s3 empty
Remove the Uthernet card from slot 3.

+ -s3 vidhd
+ Insert a VidHD card into slot 3.

-s5 diskii
Insert a 2nd Disk II controller card into slot 5.

-s6 empty
diff --git a/help/cfg-config.html b/help/cfg-config.html index df364682f..69a9d5497 100644 --- a/help/cfg-config.html +++ b/help/cfg-config.html @@ -8,7 +8,7 @@ link="#008000" vlink="#008000">

Configuration Settings


- Configuration settings Model:
@@ -64,6 +64,13 @@

Configuration Settings

When in full-screen mode, show floppy (activity and track) and harddisk status (activity); keyboard caps-lock status and if emulation is paused.

+ VidHD in slot 3
+ Insert a VidHD card into slot 3 (which can co-exist with an 80-column card in the Apple //e's AUX slot).
+ Allows all Apple II models to support the IIgs' Super Hi-Res (SHR) video modes and is supported by eg. Total Replay.
+
+ +
+ Serial Port:
This option will remap the emulated Apple's serial port to your PC's serial port (or TCP port 1977).
See Super Serial Card for more details.
diff --git a/help/cfg-input.html b/help/cfg-input.html index fa12e146f..4503cff31 100644 --- a/help/cfg-input.html +++ b/help/cfg-input.html @@ -42,7 +42,7 @@

Input Settings

On real hardware this card allows up to 4 Atari 9-pin joysticks to be connected.
Under emulation, the first 2 Windows-detected controllers will be used, and then for joysticks 3 and 4, use keys: ESDF+ZX and IJKL+NM. Note these keys will also be readable from the keyboard.
  • The card can be configured in slots 3, 4 or 5. -
  • Since it only uses the slot's DEVICE SELECT space ($C0Bx for slot 3) then it can co-exist with 80-column cards in the Apple //e's AUX slot. NB. For a real PAL Apple //e, then a slot riser card is required for it to fit.
    +
  • Since it only uses the slot's DEVICE SELECT space ($C0Bx for slot 3) then it can co-exist with an 80-column card in the Apple //e's AUX slot. NB. For a real PAL Apple //e, then a slot riser card is required for it to fit.
    See Lukazi's 4Play card and 4Play card software blogs for more information.

    @@ -50,7 +50,7 @@

    Input Settings

    On real hardware this card allows up to 2 SNES controllers to be connected and all 12 buttons can be read.
    Under emulation, the first 2 Windows-detected controllers will be used, ideally with 12 (or more) buttons eg. Logitech F310, PlayStation Dualshock 4, DualSense. Note that for some controllers (eg. 8BitDo NES30 Pro) the buttons need remapping, so use the command line switches -snes-max-alt-joy1 or -snes-max-alt-joy2 to remap.
  • The card can be configured in slots 3, 4 or 5. -
  • Since it only uses the slot's DEVICE SELECT space ($C0Bx for slot 3) then it can co-exist with 80-column cards in the Apple //e's AUX slot. NB. This card is small, so no slot riser card is required.
    +
  • Since it only uses the slot's DEVICE SELECT space ($C0Bx for slot 3) then it can co-exist with an 80-column card in the Apple //e's AUX slot. NB. This card is small, so no slot riser card is required.
    See Lukazi's SNES MAX blog for more information.


    diff --git a/help/dbg-breakpoints.html b/help/dbg-breakpoints.html index f9eded3bb..b05f61df9 100644 --- a/help/dbg-breakpoints.html +++ b/help/dbg-breakpoints.html @@ -148,7 +148,7 @@

    Breakpoints

    -

    BRK [1|2|3] [on|off]

    +

    BRK [0|1|2|3|all] [on|off]

    Break on BRK or Invalid 1-3 byte opcodes

    @@ -404,7 +404,23 @@

    Notes:

    BRK ON

    -

    Break execution when the opcode to be executed is $00 or BRK.

    +

    Break execution when the opcode to be executed is $00 (ie. BRK).

    + + + + +

    BRK ALL ON

    + + +

    Break execution when the opcode to be executed is $00 (ie. BRK) or any Invalid opcode.

    + + + + +

    BRK 0 OFF

    + + +

    Disable breaking execution when the opcode to be executed is $00 (ie. BRK).

    @@ -412,7 +428,7 @@

    Notes:

    BRKOP 6C

    -

    Break execution when the opcode to be executed is $6C or JMP (ABS).

    +

    Break execution when the opcode to be executed is $6C (ie. JMP (ABS)).

    diff --git a/help/img/config.png b/help/img/config.png index d35785ca6248f7b4de30e5263bd98bad88b860d5..ef045c367a4c3693cf6cb13583efde1332db4058 100644 GIT binary patch literal 35890 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 z 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 N002ovPDHLkV1mfGF&_W` literal 35144 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 P00000NkvXXu0mjfmSd+n diff --git a/help/savestate.html b/help/savestate.html index ae8035bce..4b5c35e3e 100644 --- a/help/savestate.html +++ b/help/savestate.html @@ -34,6 +34,7 @@

    Save-state Files

  • No-Slot clock
  • Uthernet card
  • 4Play & SNES MAX joystick cards
  • +
  • VidHD card
  • The following are not yet persisted to the file:
      diff --git a/linux.md b/linux.md index 87c3547a4..48cb17a2e 100644 --- a/linux.md +++ b/linux.md @@ -97,9 +97,6 @@ Video works, but the vertical flip is done in software. Audio (speaker) works. -Must be manually configured: -``cmake -DLIBRETRO_COMMON_PATH=/path/to/libretro-common`` - Easiest way to run from the ``build`` folder: ``retroarch -L source/frontends/libretro/applewin_libretro.so ../bin/MASTER.DSK`` @@ -120,6 +117,18 @@ cmake -DCMAKE_BUILD_TYPE=RELEASE .. make ``` +### Frontend selection + +There are 4 `cmake` variables to selectively enable frontends: `BUILD_APPLEN`, `BUILD_QAPPLE`, `BUILD_SA2` and `BUILD_LIBRETRO`. + +Usage: + +``` +cmake -DBUILD_SA2=ON -DBUILD_LIBRETRO=ON .. +``` + +or use `cmake-gui` (if none is selected, they are all built). + ### Fedora On Fedora 35, from a fresh installation, install all packages from [fedora.list.txt](source/linux/fedora.list.txt). diff --git a/resource/Applewin.rc b/resource/Applewin.rc index 968fa3f81..dc7c5687a 100644 --- a/resource/Applewin.rc +++ b/resource/Applewin.rc @@ -84,7 +84,7 @@ IDB_DEBUG_FONT_7X8 BITMAP "Debug_Font.bmp" // Dialog // -IDD_PROPPAGE_CONFIG DIALOGEX 0, 0, 210, 209 +IDD_PROPPAGE_CONFIG DIALOGEX 0, 0, 210, 240 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_CAPTION | WS_SYSMENU CAPTION "Configuration" FONT 8, "MS Shell Dlg", 0, 0, 0x0 @@ -92,30 +92,32 @@ BEGIN LTEXT "&Model:",IDC_STATIC,5,7,40,8 COMBOBOX IDC_COMPUTER,45,5,91,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP CONTROL "Confirm reboot",IDC_CHECK_CONFIRM_REBOOT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,142,8,62,10 - GROUPBOX "Video",IDC_STATIC,5,22,200,56 + GROUPBOX "Video",IDC_STATIC,5,22,200,74 LTEXT "Mo&de:",IDC_STATIC,12,33,33,8 COMBOBOX IDC_VIDEOTYPE,33,30,103,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "Monochrome &Color...",IDC_MONOCOLOR,12,46,80,14 CONTROL "50% Scan lines",IDC_CHECK_HALF_SCAN_LINES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,142,33,62,10 CONTROL "Vertical blend",IDC_CHECK_VERTICAL_BLEND,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,142,48,62,10 - CONTROL "Full-Screen: Show drive/keyboard status",IDC_CHECK_FS_SHOW_SUBUNIT_STATUS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,64,140,10 - LTEXT "&Serial Port:",IDC_STATIC,5,89,40,8 - COMBOBOX IDC_SERIALPORT,45,87,90,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "&Ethernet Settings...",IDC_ETHERNET,4,105,80,14 - GROUPBOX "Emulation Speed Control",IDC_STATIC,5,130,200,85 + CONTROL "Full-Screen: Show drive/keyboard status",IDC_CHECK_FS_SHOW_SUBUNIT_STATUS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,64,140,10 + CONTROL "VidHD in slot 3",IDC_CHECK_VIDHD_IN_SLOT3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,78,140,10 + LTEXT "&Serial Port:",IDC_STATIC,5,108,40,8 + COMBOBOX IDC_SERIALPORT,45,106,90,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "&Ethernet Settings...",IDC_ETHERNET,4,124,80,14 + GROUPBOX "Emulation Speed Control",IDC_STATIC,5,149,200,85 CONTROL "Use &Authentic Machine Speed",IDC_AUTHENTIC_SPEED, - "Button",BS_AUTORADIOBUTTON,15,141,115,10 - CONTROL "Select C&ustom Speed (in MHz)",IDC_CUSTOM_SPEED,"Button",BS_AUTORADIOBUTTON,15,153,115,10 - CONTROL "Generic2",IDC_SLIDER_CPU_SPEED,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,25,164,160,15 - CTEXT "0.5",IDC_0_5_MHz,23,180,20,10 - CTEXT "1.0",IDC_1_0_MHz,59,180,20,10 - CTEXT "2.0",IDC_2_0_MHz,96,180,20,10 - RTEXT "Fastest",IDC_MAX_MHz,150,180,29,10 - PUSHBUTTON "&Benchmark Emulator",IDC_BENCHMARK,15,194,85,15 - CONTROL "50Hz video",IDC_CHECK_50HZ_VIDEO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,142,141,51,10 + "Button",BS_AUTORADIOBUTTON,15,160,115,10 + CONTROL "Select C&ustom Speed (in MHz)",IDC_CUSTOM_SPEED,"Button",BS_AUTORADIOBUTTON,15,172,115,10 + CONTROL "Generic2",IDC_SLIDER_CPU_SPEED,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,25,183,160,15 + CTEXT "0.5",IDC_0_5_MHz,23,199,20,10 + CTEXT "1.0",IDC_1_0_MHz,59,199,20,10 + CTEXT "2.0",IDC_2_0_MHz,96,199,20,10 + RTEXT "Fastest",IDC_MAX_MHz,150,199,29,10 + PUSHBUTTON "&Benchmark Emulator",IDC_BENCHMARK,15,213,85,15 + CONTROL "50Hz video",IDC_CHECK_50HZ_VIDEO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,142,160,51,10 END -IDD_PROPPAGE_INPUT DIALOGEX 0, 0, 211, 240 +IDD_PROPPAGE_INPUT DIALOGEX 0, 0, 210, 240 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_CAPTION | WS_SYSMENU CAPTION "Input" FONT 8, "MS Shell Dlg", 0, 0, 0x0 @@ -172,7 +174,7 @@ BEGIN CONTROL "No sound cards",IDC_SOUNDCARD_DISABLE,"Button",BS_AUTORADIOBUTTON,10,175,78,10 END -IDD_PROPPAGE_DISK DIALOGEX 0, 0, 211, 188 +IDD_PROPPAGE_DISK DIALOGEX 0, 0, 210, 240 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_CAPTION | WS_SYSMENU CAPTION "Disk" FONT 8, "MS Shell Dlg", 0, 0, 0x0 diff --git a/resource/resource.h b/resource/resource.h index ae5a2158b..819b10115 100644 --- a/resource/resource.h +++ b/resource/resource.h @@ -70,7 +70,6 @@ #define IDC_PHASOR_ENABLE 1029 #define IDC_SAM_ENABLE 1030 #define IDC_SOUNDCARD_DISABLE 1031 - #define IDC_TFE_SETTINGS_ENABLE_T 1032 #define IDC_TFE_SETTINGS_ENABLE 1033 #define IDC_TFE_SETTINGS_INTERFACE_T 1034 @@ -99,7 +98,6 @@ #define IDC_PRINTER_FILTER_UNPRINTABLE 1057 #define IDC_PRINTER_APPEND 1058 #define IDC_SPIN_PRINTER_IDLE 1059 - #define IDC_CHECK_HALF_SCAN_LINES 1060 #define IDC_GPL_TEXT 1061 #define IDC_GPL_BORDER 1063 @@ -120,6 +118,7 @@ #define IDC_COMBO_DISK2_SLOT5 1086 #define IDC_FOURPLAY_CONFIG 1087 #define IDC_SNESMAX_CONFIG 1088 +#define IDC_CHECK_VIDHD_IN_SLOT3 1089 #define IDM_EXIT 40001 #define IDM_HELP 40002 #define IDM_ABOUT 40003 diff --git a/resource/version.h b/resource/version.h index 0f2eb642f..c10f8046c 100644 --- a/resource/version.h +++ b/resource/version.h @@ -1,4 +1,4 @@ -#define APPLEWIN_VERSION 1,30,6,0 +#define APPLEWIN_VERSION 1,30,7,0 #define xstr(a) str(a) #define str(a) #a diff --git a/source/AY8910.cpp b/source/AY8910.cpp index 589adf625..c4633f197 100644 --- a/source/AY8910.cpp +++ b/source/AY8910.cpp @@ -1105,7 +1105,7 @@ bool CAY8910::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, const std::string& su { std::string unit = std::string(SS_YAML_KEY_AY8910) + suffix; if (!yamlLoadHelper.GetSubMap(unit)) - throw std::string("Card: Expected key: ") + unit; + throw std::runtime_error("Card: Expected key: " + unit); ay_tone_tick[0] = yamlLoadHelper.LoadUint(SS_YAML_KEY_TONE0_TICK); ay_tone_tick[1] = yamlLoadHelper.LoadUint(SS_YAML_KEY_TONE1_TICK); @@ -1130,7 +1130,7 @@ bool CAY8910::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, const std::string& su env_counter = yamlLoadHelper.LoadUint(SS_YAML_KEY_ENV_COUNTER); if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_REGISTERS)) - throw std::string("Card: Expected key: ") + SS_YAML_KEY_REGISTERS; + throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_REGISTERS); USHORT period = (USHORT) yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_TONE0_PERIOD); sound_ay_registers[0] = period & 0xff; @@ -1174,7 +1174,7 @@ bool CAY8910::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, const std::string& su unsigned int _val = 0; if(4 != sscanf_s(value.c_str(), SS_YAML_VALUE_CHANGE_FORMAT, &_tstates, &_ofs, &_reg, &_val)) - throw std::string("Card: AY8910: Failed to scanf change list"); + throw std::runtime_error("Card: AY8910: Failed to scanf change list"); ay_change[ay_change_count].tstates = _tstates; ay_change[ay_change_count].ofs = _ofs; @@ -1183,7 +1183,7 @@ bool CAY8910::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, const std::string& su ay_change_count++; if (ay_change_count > AY_CHANGE_MAX) - throw std::string("Card: AY8910: Too many changes"); + throw std::runtime_error("Card: AY8910: Too many changes"); } yamlLoadHelper.PopMap(); diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 0e99c7d6d..ac6912af2 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,17 +1,16 @@ include(FindPkgConfig) +include(FindZLIB) -MESSAGE("***SOURCE CMAKE***") - -pkg_check_modules(YAML REQUIRED yaml-0.1) -pkg_check_modules(MINIZIP REQUIRED minizip) -pkg_check_modules(ZLIB REQUIRED zlib) -pkg_check_modules(SLIRP slirp) +find_package(ZLIB REQUIRED) +pkg_search_module(YAML REQUIRED yaml-0.1) +pkg_search_module(MINIZIP REQUIRED minizip) +pkg_search_module(SLIRP slirp) if ("${SLIRP_FOUND}" STREQUAL "") message(WARNING "'libslirp' not found. Will use 'libpcap' instead") endif() -pkg_check_modules(PCAP libpcap) +pkg_search_module(PCAP libpcap) if ("${PCAP_FOUND}" STREQUAL "") # old versions of pcap do not work with pkg-config # this is necessary on Rapsberri Pi OS @@ -49,6 +48,7 @@ set(SOURCE_FILES Debugger/Debugger_Commands.cpp Debugger/Util_MemoryTextFile.cpp + VidHD.cpp SSI263.cpp Speaker.cpp SoundCore.cpp @@ -88,19 +88,6 @@ set(SOURCE_FILES Configuration/PropertySheetHelper.cpp - linux/windows/handles.cpp - linux/windows/files.cpp - linux/windows/time.cpp - linux/windows/stringcb.cpp - linux/windows/strings.cpp - linux/windows/misc.cpp - linux/windows/winbase.cpp - linux/windows/winuser.cpp - linux/windows/dsound.cpp - linux/windows/guiddef.cpp - linux/windows/dmusicc.cpp - linux/windows/winnls.cpp - linux/resources.cpp linux/benchmark.cpp linux/paddle.cpp @@ -130,6 +117,7 @@ set(SOURCE_FILES ) set(HEADER_FILES + VidHD.cpp SSI263.h SSI263Phonemes.h Speaker.h @@ -195,23 +183,6 @@ set(HEADER_FILES Configuration/PropertySheetHelper.h - linux/windows/handles.h - linux/windows/files.h - linux/windows/bitmap.h - linux/windows/time.h - linux/windows/stringcb.h - linux/windows/strings.h - linux/windows/misc.h - linux/windows/winbase.h - linux/windows/winuser.h - linux/windows/dsound.h - linux/windows/guiddef.h - linux/windows/dmusicc.h - linux/windows/winnls.h - linux/windows/gdi.h - linux/windows/mmreg.h - linux/windows/mmsystem.h - linux/resources.h linux/linuxinterface.h linux/benchmark.h @@ -225,7 +196,6 @@ set(HEADER_FILES linux/network/tfe2.h linux/network/slirp2.h linux/network/registers.h - linux/win.h Z80VICE/z80.h Z80VICE/z80mem.h @@ -233,16 +203,8 @@ set(HEADER_FILES Z80VICE/daa.h ) -if (${APPLE}) -# Applle's linker does not like the link-time interface of libappleii -# we need link everything at once - message("Mac OS X detected, libappleii as static library") - set(LIBRARY_TYPE "STATIC") -else() - set(LIBRARY_TYPE "SHARED") -endif() - -add_library(appleii "${LIBRARY_TYPE}" +# we used to generate a shared object, but it turns out there are more cons than pros +add_library(appleii STATIC ${SOURCE_FILES} ${HEADER_FILES} ) @@ -253,7 +215,6 @@ target_include_directories(appleii PRIVATE ${PCAP_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ${SLIRP_INCLUDE_DIRS} - ${ZLIB_INCLUDE_DIRS} Debugger ) @@ -267,7 +228,11 @@ target_link_libraries(appleii PRIVATE ${MINIZIP_LIBRARIES} ${PCAP_LIBRARIES} ${SLIRP_LIBRARIES} - ${ZLIB_LIBRARIES} + ZLIB::ZLIB + ) + +target_link_libraries(appleii PUBLIC + windows ) target_link_directories(appleii PRIVATE @@ -275,7 +240,6 @@ target_link_directories(appleii PRIVATE ${MINIZIP_LIBRARY_DIRS} ${PCAP_LIBRARY_DIRS} ${SLIRP_LIBRARY_DIRS} - ${ZLIB_LIBRARY_DIRS} ) target_compile_options(appleii PUBLIC @@ -288,6 +252,3 @@ add_custom_command( ) configure_file(linux/config.h.in linux/config.h) - -install(TARGETS appleii - DESTINATION lib) diff --git a/source/CPU.cpp b/source/CPU.cpp index cd39707b0..648e04356 100644 --- a/source/CPU.cpp +++ b/source/CPU.cpp @@ -810,7 +810,7 @@ void CpuLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) eCpuType cpu; if (cpuType == SS_YAML_VALUE_6502) cpu = CPU_6502; else if (cpuType == SS_YAML_VALUE_65C02) cpu = CPU_65C02; - else throw std::string("Load: Unknown main CPU type"); + else throw std::runtime_error("Load: Unknown main CPU type"); SetMainCpu(cpu); regs.a = (BYTE) yamlLoadHelper.LoadUint(SS_YAML_KEY_REGA); diff --git a/source/CPU/cpu_general.inl b/source/CPU/cpu_general.inl index 451ba4768..bc9586d2c 100644 --- a/source/CPU/cpu_general.inl +++ b/source/CPU/cpu_general.inl @@ -87,8 +87,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA else { \ memdirty[addr >> 8] = 0xFF; \ LPBYTE page = memwrite[addr >> 8]; \ - if (page) \ + if (page) { \ *(page+(addr & 0xFF)) = (BYTE)(a); \ + if (memVidHD) /* GH#997 */\ + *(memVidHD + addr) = (BYTE)(a); \ + } \ else if ((addr & 0xF000) == 0xC000) \ IOWrite[(addr>>4) & 0xFF](regs.pc,addr,1,(BYTE)(a),uExecutedCycles);\ } \ diff --git a/source/Card.cpp b/source/Card.cpp index 78e8de414..723b7ed7c 100644 --- a/source/Card.cpp +++ b/source/Card.cpp @@ -70,3 +70,43 @@ void DummyCard::Update(const ULONG nExecutedCycles) break; } } + +void DummyCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) +{ + switch (QueryType()) + { + case CT_GenericPrinter: + Printer_SaveSnapshot(yamlSaveHelper, m_slot); + break; + case CT_MockingboardC: + MB_SaveSnapshot(yamlSaveHelper, m_slot); + break; + case CT_Phasor: + Phasor_SaveSnapshot(yamlSaveHelper, m_slot); + break; + case CT_Z80: + Z80_SaveSnapshot(yamlSaveHelper, m_slot); + break; + case CT_Uthernet: + tfe_SaveSnapshot(yamlSaveHelper, m_slot); + break; + } +} + +bool DummyCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) +{ + switch (QueryType()) + { + case CT_GenericPrinter: + return Printer_LoadSnapshot(yamlLoadHelper, m_slot, version); + case CT_MockingboardC: + return MB_LoadSnapshot(yamlLoadHelper, m_slot, version); + case CT_Phasor: + return Phasor_LoadSnapshot(yamlLoadHelper, m_slot, version); + case CT_Z80: + return Z80_LoadSnapshot(yamlLoadHelper, m_slot, version); + case CT_Uthernet: + return tfe_LoadSnapshot(yamlLoadHelper, m_slot, version); + } + return false; +} diff --git a/source/Card.h b/source/Card.h index 96e903af7..225ba8047 100644 --- a/source/Card.h +++ b/source/Card.h @@ -23,11 +23,15 @@ enum SS_CARDTYPE CT_Saturn128K, // Saturn 128K (but may be populated with less RAM, in multiples of 16K) CT_FourPlay, // 4 port Atari 2600 style digital joystick card CT_SNESMAX, // 2 port Nintendo NES/SNES controller serial interface card + CT_VidHD, CT_Uthernet2, }; enum SLOTS { SLOT0=0, SLOT1, SLOT2, SLOT3, SLOT4, SLOT5, SLOT6, SLOT7, NUM_SLOTS, SLOT_AUX }; +class YamlSaveHelper; +class YamlLoadHelper; + class Card { public: @@ -38,6 +42,9 @@ class Card virtual void Init(void) = 0; virtual void Reset(const bool powerCycle) = 0; virtual void Update(const ULONG nExecutedCycles) = 0; + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper) = 0; + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) = 0; + SS_CARDTYPE QueryType(void) { return m_type; } protected: @@ -59,6 +66,8 @@ class EmptyCard : public Card virtual void Init(void) {} virtual void Reset(const bool powerCycle) {} virtual void Update(const ULONG nExecutedCycles) {} + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper) {} + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { _ASSERT(0); return false; } }; // @@ -73,4 +82,6 @@ class DummyCard : public Card // For cards that currently can't be instantiated virtual void Init(void) {} virtual void Reset(const bool powerCycle) {} virtual void Update(const ULONG nExecutedCycles); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); }; diff --git a/source/CardManager.cpp b/source/CardManager.cpp index 7e26af72c..846630e25 100644 --- a/source/CardManager.cpp +++ b/source/CardManager.cpp @@ -40,6 +40,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "SAM.h" #include "SerialComms.h" #include "SNESMAX.h" +#include "VidHD.h" +#include "linux/network/uthernet2.h" +#include "LanguageCard.h" +#include "Memory.h" void CardManager::InsertInternal(UINT slot, SS_CARDTYPE type) { @@ -91,7 +95,7 @@ void CardManager::InsertInternal(UINT slot, SS_CARDTYPE type) m_slot[slot] = new DummyCard(type, slot); break; case CT_Uthernet2: - m_slot[slot] = new DummyCard(type, slot); + m_slot[slot] = new Uthernet2(slot); break; case CT_FourPlay: m_slot[slot] = new FourPlayCard(slot); @@ -99,20 +103,26 @@ void CardManager::InsertInternal(UINT slot, SS_CARDTYPE type) case CT_SNESMAX: m_slot[slot] = new SNESMAXCard(slot); break; + case CT_VidHD: + m_slot[slot] = new VidHDCard(slot); + break; case CT_LanguageCard: + _ASSERT(m_pLanguageCard == NULL); + if (m_pLanguageCard) break; // Only support one language card + m_slot[slot] = m_pLanguageCard = LanguageCardSlot0::create(slot); + break; case CT_Saturn128K: - { - if (slot != 0) - { - _ASSERT(0); - break; - } - } - m_slot[slot] = new DummyCard(type, slot); + _ASSERT(m_pLanguageCard == NULL); + if (m_pLanguageCard) break; // Only support one language card + m_slot[slot] = m_pLanguageCard = new Saturn128K(slot, Saturn128K::GetSaturnMemorySize()); + break; + case CT_LanguageCardIIe: + _ASSERT(m_pLanguageCard == NULL); + if (m_pLanguageCard) break; // Only support one language card + m_slot[slot] = m_pLanguageCard = LanguageCardUnit::create(slot); break; - case CT_LanguageCardIIe: // not a card default: _ASSERT(0); break; @@ -131,14 +141,27 @@ void CardManager::Insert(UINT slot, SS_CARDTYPE type, bool updateRegistry/*=true void CardManager::RemoveInternal(UINT slot) { - if (m_slot[slot] && m_slot[slot]->QueryType() == CT_MouseInterface) - m_pMouseCard = NULL; // NB. object deleted below: delete m_slot[slot] - - if (m_slot[slot] && m_slot[slot]->QueryType() == CT_SSC) - m_pSSC = NULL; // NB. object deleted below: delete m_slot[slot] + if (m_slot[slot]) + { + // NB. object deleted below: delete m_slot[slot] + switch (m_slot[slot]->QueryType()) + { + case CT_MouseInterface: + m_pMouseCard = NULL; + break; + case CT_SSC: + m_pSSC = NULL; + break; + case CT_LanguageCard: + case CT_Saturn128K: + case CT_LanguageCardIIe: + m_pLanguageCard = NULL; + break; + } - delete m_slot[slot]; - m_slot[slot] = NULL; + delete m_slot[slot]; + m_slot[slot] = NULL; + } } void CardManager::Remove(UINT slot, bool updateRegistry/*=true*/) @@ -194,7 +217,10 @@ void CardManager::RemoveAux(void) void CardManager::InitializeIO(LPBYTE pCxRomPeripheral) { - for (UINT i = 0; i < NUM_SLOTS; ++i) + // if it is a //e then SLOT0 must be CT_LanguageCardIIe (and the other way round) + _ASSERT(IsApple2PlusOrClone(GetApple2Type()) != (m_slot[SLOT0]->QueryType() == CT_LanguageCardIIe)); + + for (UINT i = SLOT0; i < NUM_SLOTS; ++i) { if (m_slot[i]) { @@ -205,11 +231,22 @@ void CardManager::InitializeIO(LPBYTE pCxRomPeripheral) void CardManager::Update(const ULONG nExecutedCycles) { - for (UINT i = 0; i < NUM_SLOTS; ++i) - { - if (m_slot[i]) - { - m_slot[i]->Update(nExecutedCycles); - } - } + for (UINT i = SLOT0; i < NUM_SLOTS; ++i) + { + if (m_slot[i]) + { + m_slot[i]->Update(nExecutedCycles); + } + } +} + +void CardManager::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) +{ + for (UINT i = SLOT0; i < NUM_SLOTS; ++i) + { + if (m_slot[i]) + { + m_slot[i]->SaveSnapshot(yamlSaveHelper); + } + } } diff --git a/source/CardManager.h b/source/CardManager.h index f59ce6c5b..0e09ec592 100644 --- a/source/CardManager.h +++ b/source/CardManager.h @@ -9,7 +9,8 @@ class CardManager public: CardManager(void) : m_pMouseCard(NULL), - m_pSSC(NULL) + m_pSSC(NULL), + m_pLanguageCard(NULL) { InsertInternal(SLOT0, CT_Empty); InsertInternal(SLOT1, CT_GenericPrinter); @@ -33,14 +34,11 @@ class CardManager SS_CARDTYPE QuerySlot(UINT slot) { _ASSERT(slotQueryType(); } Card& GetRef(UINT slot) { - SS_CARDTYPE t=QuerySlot(slot); - _ASSERT((t==CT_GenericHDD || t==CT_SSC || t==CT_MouseInterface || t==CT_Disk2 || t == CT_FourPlay || t == CT_SNESMAX || t == CT_SAM) && m_slot[slot]); + _ASSERT(m_slot[slot]); return *m_slot[slot]; } Card* GetObj(UINT slot) { - SS_CARDTYPE t=QuerySlot(slot); - _ASSERT(t == CT_GenericHDD || t==CT_SSC || t==CT_MouseInterface || t==CT_Disk2 || t == CT_FourPlay || t == CT_SNESMAX || t == CT_SAM); return m_slot[slot]; } @@ -57,8 +55,11 @@ class CardManager class CSuperSerialCard* GetSSC(void) { return m_pSSC; } bool IsSSCInstalled(void) { return m_pSSC != NULL; } + class LanguageCardUnit* GetLanguageCard(void) { return m_pLanguageCard; } + void InitializeIO(LPBYTE pCxRomPeripheral); void Update(const ULONG nExecutedCycles); + void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); private: void InsertInternal(UINT slot, SS_CARDTYPE type); @@ -71,4 +72,5 @@ class CardManager Disk2CardManager m_disk2CardMgr; class CMouseInterface* m_pMouseCard; class CSuperSerialCard* m_pSSC; + class LanguageCardUnit* m_pLanguageCard; }; diff --git a/source/CmdLine.cpp b/source/CmdLine.cpp index 2dbccffe0..d39a8baec 100644 --- a/source/CmdLine.cpp +++ b/source/CmdLine.cpp @@ -37,8 +37,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Joystick.h" #include "SoundCore.h" #include "ParallelPrinter.h" -#include "CardManager.h" -#include "SerialComms.h" #include "Interface.h" CmdLine g_cmdLine; @@ -169,6 +167,13 @@ bool ProcessCmdLine(LPSTR lpCmdLine) g_cmdLine.bSlotEmpty[slot] = true; if (strcmp(lpCmdLine, "diskii") == 0) g_cmdLine.slotInsert[slot] = CT_Disk2; + if (strcmp(lpCmdLine, "vidhd") == 0) + { + if (slot == SLOT3) + g_cmdLine.slotInsert[slot] = CT_VidHD; + else + LogFileOutput("VidHD currently only supported in slot 3\n"); + } } else if (lpCmdLine[3] == 'd' && (lpCmdLine[4] == '1' || lpCmdLine[4] == '2')) // -s[1..7]d[1|2] { @@ -208,6 +213,19 @@ bool ProcessCmdLine(LPSTR lpCmdLine) { g_cmdLine.setFullScreen = 0; } +#define CMD_FS_WIDTH "-fs-width=" + else if (strncmp(lpCmdLine, CMD_FS_WIDTH, sizeof(CMD_FS_WIDTH)-1) == 0) + { + if (g_cmdLine.setFullScreen < 0) // Not yet been specified on cmd line? + g_cmdLine.setFullScreen = 1; // Implicity set full-screen. NB. Can be overridden by "-no-full-screen" + + LPSTR lpTmp = lpCmdLine + sizeof(CMD_FS_WIDTH)-1; + { + g_cmdLine.userSpecifiedWidth = atoi(lpTmp); + if (!g_cmdLine.userSpecifiedWidth) + LogFileOutput("Invalid cmd-line parameter for -fs-width=x switch\n"); + } + } #define CMD_FS_HEIGHT "-fs-height=" else if (strncmp(lpCmdLine, CMD_FS_HEIGHT, sizeof(CMD_FS_HEIGHT)-1) == 0) { @@ -215,24 +233,16 @@ bool ProcessCmdLine(LPSTR lpCmdLine) g_cmdLine.setFullScreen = 1; // Implicity set full-screen. NB. Can be overridden by "-no-full-screen" LPSTR lpTmp = lpCmdLine + sizeof(CMD_FS_HEIGHT)-1; - bool bRes = false; - UINT bestWidth=0, bestHeight=0; if (strcmp(lpTmp, "best") == 0) { - bRes = GetFrame().GetBestDisplayResolutionForFullScreen(bestWidth, bestHeight); + g_cmdLine.bestFullScreenResolution = true; } else { - UINT userSpecifiedHeight = atoi(lpTmp); - if (userSpecifiedHeight) - bRes = GetFrame().GetBestDisplayResolutionForFullScreen(bestWidth, bestHeight, userSpecifiedHeight); - else + g_cmdLine.userSpecifiedHeight = atoi(lpTmp); + if (!g_cmdLine.userSpecifiedHeight) LogFileOutput("Invalid cmd-line parameter for -fs-height=x switch\n"); } - if (bRes) - LogFileOutput("Best resolution for -fs-height=x switch: Width=%d, Height=%d\n", bestWidth, bestHeight); - else - LogFileOutput("Failed to set parameter for -fs-height=x switch\n"); } else if (strcmp(lpCmdLine, "-no-di") == 0) { @@ -396,8 +406,7 @@ bool ProcessCmdLine(LPSTR lpCmdLine) } else if ((strcmp(lpCmdLine, "-dcd") == 0) || (strcmp(lpCmdLine, "-modem") == 0)) // GH#386 { - if (GetCardMgr().IsSSCInstalled()) - GetCardMgr().GetSSC()->SupportDCD(true); + g_cmdLine.supportDCD = true; } else if (strcmp(lpCmdLine, "-alt-enter=toggle-full-screen") == 0) // GH#556 { diff --git a/source/CmdLine.h b/source/CmdLine.h index 2e1034cd8..4a935e4f0 100644 --- a/source/CmdLine.h +++ b/source/CmdLine.h @@ -20,6 +20,7 @@ struct CmdLine bRemoveNoSlotClock = false; snesMaxAltControllerType[0] = false; snesMaxAltControllerType[1] = false; + supportDCD = false; szImageName_harddisk[HARDDISK_1] = NULL; szImageName_harddisk[HARDDISK_2] = NULL; szSnapshotName = NULL; @@ -35,6 +36,9 @@ struct CmdLine rgbCard = RGB_Videocard_e::Apple; rgbCardForegroundColor = 15; rgbCardBackgroundColor = 0; + bestFullScreenResolution = false; + userSpecifiedWidth = 0; + userSpecifiedHeight = 0; for (UINT i = 0; i < NUM_SLOTS; i++) { @@ -56,6 +60,7 @@ struct CmdLine bool bSwapButtons0and1; bool bRemoveNoSlotClock; bool snesMaxAltControllerType[2]; + bool supportDCD; SS_CARDTYPE slotInsert[NUM_SLOTS]; LPCSTR szImageName_drive[NUM_SLOTS][NUM_DRIVES]; bool driveConnected[NUM_SLOTS][NUM_DRIVES]; @@ -74,6 +79,9 @@ struct CmdLine int rgbCardForegroundColor; int rgbCardBackgroundColor; std::string strCurrentDir; + bool bestFullScreenResolution; + UINT userSpecifiedWidth; + UINT userSpecifiedHeight; }; bool ProcessCmdLine(LPSTR lpCmdLine); diff --git a/source/Common.h b/source/Common.h index 123495298..8df91b2ab 100644 --- a/source/Common.h +++ b/source/Common.h @@ -220,6 +220,11 @@ inline bool IsAppleIIeOrAbove(eApple2Type type) // Apple //e,Enhanced//e,//c or return !IsApple2PlusOrClone(type); } +inline bool IsAppleIIc(eApple2Type type) // Apple //c +{ + return (type & A2TYPE_APPLE2C) != 0; +} + extern eApple2Type g_Apple2Type; inline bool IsEnhancedIIE(void) { diff --git a/source/Configuration/PageConfig.cpp b/source/Configuration/PageConfig.cpp index 4db1efa83..996c56fe0 100644 --- a/source/Configuration/PageConfig.cpp +++ b/source/Configuration/PageConfig.cpp @@ -89,18 +89,18 @@ INT_PTR CPageConfig::DlgProcInternal(HWND hWnd, UINT message, WPARAM wparam, LPA switch (LOWORD(wparam)) { case IDC_AUTHENTIC_SPEED: // Authentic Machine Speed - SendDlgItemMessage(hWnd,IDC_SLIDER_CPU_SPEED,TBM_SETPOS,1,SPEED_NORMAL); - EnableTrackbar(hWnd,0); + SendDlgItemMessage(hWnd, IDC_SLIDER_CPU_SPEED, TBM_SETPOS, 1, SPEED_NORMAL); + EnableTrackbar(hWnd, 0); break; case IDC_CUSTOM_SPEED: // Select Custom Speed - SetFocus(GetDlgItem(hWnd,IDC_SLIDER_CPU_SPEED)); - EnableTrackbar(hWnd,1); + SetFocus(GetDlgItem(hWnd, IDC_SLIDER_CPU_SPEED)); + EnableTrackbar(hWnd, 1); break; case IDC_SLIDER_CPU_SPEED: // CPU speed slider - CheckRadioButton(hWnd,IDC_AUTHENTIC_SPEED,IDC_CUSTOM_SPEED,IDC_CUSTOM_SPEED); - EnableTrackbar(hWnd,1); + CheckRadioButton(hWnd, IDC_AUTHENTIC_SPEED, IDC_CUSTOM_SPEED, IDC_CUSTOM_SPEED); + EnableTrackbar(hWnd, 1); break; case IDC_BENCHMARK: @@ -113,6 +113,7 @@ INT_PTR CPageConfig::DlgProcInternal(HWND hWnd, UINT message, WPARAM wparam, LPA case IDC_ETHERNET: ui_tfe_settings_dialog(hWnd); m_PropertySheetHelper.GetConfigNew().m_Slot[SLOT3] = m_PageConfigTfe.m_tfe_enabled ? CT_Uthernet : CT_Empty; + InitOptions(hWnd); break; case IDC_MONOCOLOR: @@ -127,6 +128,14 @@ INT_PTR CPageConfig::DlgProcInternal(HWND hWnd, UINT message, WPARAM wparam, LPA // Checked in DlgOK() break; + case IDC_CHECK_VIDHD_IN_SLOT3: + { + const UINT newState = IsDlgButtonChecked(hWnd, IDC_CHECK_VIDHD_IN_SLOT3) ? 1 : 0; + m_PropertySheetHelper.GetConfigNew().m_Slot[SLOT3] = newState ? CT_VidHD : CT_Empty; + InitOptions(hWnd); + } + break; + case IDC_COMPUTER: if(HIWORD(wparam) == CBN_SELCHANGE) { @@ -374,6 +383,11 @@ void CPageConfig::InitOptions(HWND hWnd) const SS_CARDTYPE slot3 = m_PropertySheetHelper.GetConfigNew().m_Slot[SLOT3]; const BOOL enableUthernetDialog = slot3 == CT_Empty || slot3 == CT_Uthernet; EnableWindow(GetDlgItem(hWnd, IDC_ETHERNET), enableUthernetDialog); + + const bool bIsSlot3VidHD = slot3 == CT_VidHD; + CheckDlgButton(hWnd, IDC_CHECK_VIDHD_IN_SLOT3, bIsSlot3VidHD ? BST_CHECKED : BST_UNCHECKED); + const BOOL enableVidHD = slot3 == CT_Empty || bIsSlot3VidHD; + EnableWindow(GetDlgItem(hWnd, IDC_CHECK_VIDHD_IN_SLOT3), enableVidHD); } // Config->Computer: Menu item to eApple2Type diff --git a/source/Configuration/PropertySheetHelper.cpp b/source/Configuration/PropertySheetHelper.cpp index 217d79d3e..426c05973 100644 --- a/source/Configuration/PropertySheetHelper.cpp +++ b/source/Configuration/PropertySheetHelper.cpp @@ -548,6 +548,8 @@ std::string CPropertySheetHelper::GetCardName(const SS_CARDTYPE CardType) return "4Play"; case CT_SNESMAX: return "SNES MAX"; + case CT_VidHD: + return "VidHD"; default: return "Unknown"; } diff --git a/source/Debugger/Debug.cpp b/source/Debugger/Debug.cpp index 3e68efabc..6e05af2f3 100644 --- a/source/Debugger/Debug.cpp +++ b/source/Debugger/Debug.cpp @@ -51,7 +51,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define ALLOW_INPUT_LOWERCASE 1 // See /docs/Debugger_Changelog.txt for full details - const int DEBUGGER_VERSION = MAKE_VERSION(2,9,1,0); + const int DEBUGGER_VERSION = MAKE_VERSION(2,9,1,13); // Public _________________________________________________________________________________________ @@ -68,6 +68,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA int g_nDebugBreakOnInvalid = 0; // Bit Flags of Invalid Opcode to break on: // iOpcodeType = AM_IMPLIED (BRK), AM_1, AM_2, AM_3 int g_iDebugBreakOnOpcode = 0; bool g_bDebugBreakOnInterrupt = false; + static int g_iDebugBreakOnDmaToOrFromIoMemory = 0; + static WORD g_uDebugBreakOnDmaIoMemoryAddr = 0; static int g_bDebugBreakpointHit = 0; // See: BreakpointHit_t @@ -837,10 +839,9 @@ Update_t CmdProfile (int nArgs) //=========================================================================== // iOpcodeType = AM_IMPLIED (BRK), AM_1, AM_2, AM_3 -static int IsDebugBreakOnInvalid( int iOpcodeType ) +static bool IsDebugBreakOnInvalid(int iOpcodeType) { - g_bDebugBreakpointHit |= ((g_nDebugBreakOnInvalid >> iOpcodeType) & 1) ? BP_HIT_INVALID : 0; - return g_bDebugBreakpointHit; + return ((g_nDebugBreakOnInvalid >> iOpcodeType) & 1) ? true : false; } // iOpcodeType = AM_IMPLIED (BRK), AM_1, AM_2, AM_3 @@ -873,14 +874,15 @@ Update_t CmdBreakInvalid (int nArgs) // Breakpoint IFF Full-speed! // Cases: // 0. CMD // display // 1a. CMD # // display - // 1b. CMD ON | OFF //set + // 1b. CMD ON | OFF // set // 1c. CMD ? // error // 2a. CMD # ON | OFF // set - // 2b. CMD # ? // error + // 2b. CMD ALL ON | OFF // set all + // 2c. CMD # ? // error TCHAR sText[ CONSOLE_WIDTH ]; bool bValidParam = true; - int iParamArg = nArgs; + int iParamArg = nArgs; // last arg is the 'ON' / 'OFF' param int iParam; int nFound = FindParam( g_aArgs[ iParamArg ].sArg, MATCH_EXACT, iParam, _PARAM_GENERAL_BEGIN, _PARAM_GENERAL_END ); @@ -892,14 +894,17 @@ Update_t CmdBreakInvalid (int nArgs) // Breakpoint IFF Full-speed! if (iParam == PARAM_OFF) nActive = 0; else + { bValidParam = false; + nFound = 0; + } } else bValidParam = false; if (nArgs == 1) { - if (! nFound) // bValidParam) // case 1a or 1c + if (! nFound) { if ((iType < AM_IMPLIED) || (iType > AM_3)) goto _Help; @@ -920,14 +925,22 @@ Update_t CmdBreakInvalid (int nArgs) // Breakpoint IFF Full-speed! ConsoleBufferPushFormat( sText, TEXT("Enter debugger on INVALID %1X opcode: %s"), iType, g_aParameters[ iParam ].m_sName ); return ConsoleUpdate(); } - else + else if (nArgs == 2) { - if (! bValidParam) // case 2b + int iParam1; + if (FindParam(g_aArgs[1].sArg, MATCH_EXACT, iParam1, PARAM_ALL, PARAM_ALL)) // case 2b + { + for (iType = 0; iType <= AM_3; iType++) + SetDebugBreakOnInvalid(iType, nActive); + ConsoleBufferPushFormat(sText, TEXT("Enter debugger on BRK opcode and INVALID opcodes: %s"), g_aParameters[iParam].m_sName); + return ConsoleUpdate(); + } + else if (! bValidParam) // case 2c { goto _Help; } - else // case 2a (or not 2b ;-) + else // case 2a { if ((iType < 0) || (iType > AM_3)) goto _Help; @@ -1251,6 +1264,20 @@ void ClearTempBreakpoints () } } +//=========================================================================== +static int CheckBreakpointsDmaToOrFromIoMemory(void) +{ + int res = g_iDebugBreakOnDmaToOrFromIoMemory; + g_iDebugBreakOnDmaToOrFromIoMemory = 0; + return res; +} + +void DebuggerBreakOnDmaToOrFromIoMemory(WORD addr, bool isDmaToMemory) +{ + g_iDebugBreakOnDmaToOrFromIoMemory = isDmaToMemory ? BP_DMA_TO_IO_MEM : BP_DMA_FROM_IO_MEM; + g_uDebugBreakOnDmaIoMemoryAddr = addr; +} + //=========================================================================== Update_t CmdBreakpoint (int nArgs) { @@ -3303,6 +3330,18 @@ Update_t CmdFlag (int nArgs) // Disk ___________________________________________________________________________________________ + +// Usage: +// DISK # EJECT // Unmount disk +// DISK INFO +// DISK # PROTECT # // Write-protect disk on/off +// DISK # "" // Mount filename as floppy disk +// TODO: +// DISK # READ // Read Track/Sector(s) +// DISK # READ Addr:Addr // Read Track/Sector(s) +// DISK # WRITE Addr:Addr // Write Track/Sector(s) +// Examples: +// DISK 2 INFO Update_t CmdDisk ( int nArgs) { if (! nArgs) @@ -3322,7 +3361,7 @@ Update_t CmdDisk ( int nArgs) if (nArgs > 2) return HelpLastCommand(); - char buffer[200] = ""; + char buffer[200] = ""; // HACK: Magic number TODO: Should be MAX_CONSOLE_WIDTH*2 ConsoleBufferPushFormat(buffer, "FW%2d: D%d at T$%s, phase $%s, offset $%X, mask $%02X, extraCycles %.2f, %s", diskCard.GetCurrentFirmware(), diskCard.GetCurrentDrive() + 1, @@ -3718,7 +3757,10 @@ Update_t CmdConfigGetDebugDir (int nArgs) return ConsoleUpdate(); } -// "CD" +// Usage: +// CD "" +// CD ".." +// Note: Subdirectory MUST be quoted with double quotes. //=========================================================================== Update_t CmdConfigSetDebugDir (int nArgs) { @@ -3753,8 +3795,71 @@ Update_t CmdConfigSetDebugDir (int nArgs) } else // Relative { - // TODO: Support ".." - currently just appends (which still works) - sPath = g_sCurrentDir + g_aArgs[1].sArg; // TODO: debugger dir has no ` CONSOLE_COLOR_ESCAPE_CHAR ?!?! + std::string SAME_DIR( "." ); SAME_DIR += PATH_SEPARATOR; + std::string UP_DIR ( ".."); UP_DIR += PATH_SEPARATOR; + std::string sNewPath( g_aArgs[1].sArg ); + + // if new path doesn't have a trailing slash, append one + if (*(sNewPath.rbegin()) != PATH_SEPARATOR) + sNewPath += PATH_SEPARATOR; + + // Support ".." and various permutations + // cd "..\" + // cd "abc\..\def\" + // + // 1. find next slash in newpath + // 2. subdir = newpath.substr() + // 3. if subdir == "..\" + // reverse find slash in g_sCurrentDir + // g_sCurrentDir = g_sCurrentDir.substr() + // else + // g_sCurrentDir += subdir + size_t iPrevSeparator = 0; + size_t iPathSeparator = 0; + + while ((iPathSeparator = sNewPath.find( PATH_SEPARATOR, iPrevSeparator )) != std::string::npos) + { +#if _DEBUG + char zDebug[128]; + sprintf( zDebug, "Prev: %d\n", iPrevSeparator ); OutputDebugStringA( zDebug ); + sprintf( zDebug, "Next: %d\n", iPathSeparator ); OutputDebugStringA( zDebug ); + sprintf( zDebug, "%s\n", sNewPath.c_str() ); OutputDebugStringA( zDebug ); + sprintf( zDebug, "%*s%s\n", iPathSeparator, "", "^"); OutputDebugStringA( zDebug ); +#endif + + std::string sSubDir = sNewPath.substr( iPrevSeparator, iPathSeparator - iPrevSeparator + 1 ); + const size_t nSubDirLen = sSubDir.size(); + + if ((nSubDirLen == 2) && (sSubDir == SAME_DIR)) // Same directory ".\" in the subpath? + { + // Intentional: Nothing to do + } + else + if ((nSubDirLen == 3) && (sSubDir == UP_DIR)) // Up directory "..\" in the subpath? + { + size_t nCurrentLen = g_sCurrentDir.size(); + size_t nLastSeperator = g_sCurrentDir.rfind( '\\', nCurrentLen - 2 ); + + if (nLastSeperator != std::string::npos) + { +#if _DEBUG + sprintf( zDebug, "Last: %d\n", nLastSeperator ); OutputDebugStringA( zDebug ); + sprintf( zDebug, "%s\n", g_sCurrentDir.c_str() ); OutputDebugStringA( zDebug ); + sprintf( zDebug, "%*s%s\n", nLastSeperator, "", "^"); OutputDebugStringA( zDebug ); +#endif + std::string sCurrentDir = g_sCurrentDir.substr( 0, nLastSeperator + 1 ); // Path always has trailing slash so include it + g_sCurrentDir = sCurrentDir; + } + } + else + g_sCurrentDir += sSubDir; + + iPathSeparator++; // start next search past path separator + iPrevSeparator = iPathSeparator; + } + + // TODO: debugger dir has no ` CONSOLE_COLOR_ESCAPE_CHAR ?!?! + sPath = g_sCurrentDir; } if ( SetCurrentImageDir( sPath ) ) @@ -6443,8 +6548,12 @@ Update_t CmdCyclesReset(int /*nArgs*/) enum ViewVideoPage_t { VIEW_PAGE_X, // current page + VIEW_PAGE_0, // Pseudo VIEW_PAGE_1, - VIEW_PAGE_2 + VIEW_PAGE_2, + VIEW_PAGE_3, // Pseudo + VIEW_PAGE_4, // Pseudo + VIEW_PAGE_5 // Pseudo }; Update_t _ViewOutput( ViewVideoPage_t iPage, int bVideoModeFlags ) @@ -6455,8 +6564,12 @@ Update_t _ViewOutput( ViewVideoPage_t iPage, int bVideoModeFlags ) bVideoModeFlags |= !GetVideo().VideoGetSWPAGE2() ? 0 : VF_PAGE2; bVideoModeFlags |= !GetVideo().VideoGetSWMIXED() ? 0 : VF_MIXED; break; // Page Current & current MIXED state - case VIEW_PAGE_1: bVideoModeFlags |= 0; break; // Page 1 - case VIEW_PAGE_2: bVideoModeFlags |= VF_PAGE2; break; // Page 2 + case VIEW_PAGE_0: bVideoModeFlags |= VF_PAGE0; break; // Pseudo Page 0 ($0000) + case VIEW_PAGE_1: bVideoModeFlags |= 0 ; break; // Hardware Page 1 ($2000), NOTE: VF_HIRES will be passed in + case VIEW_PAGE_2: bVideoModeFlags |= VF_PAGE2; break; // Hardware Page 2 ($4000) + case VIEW_PAGE_3: bVideoModeFlags |= VF_PAGE3; break; // Pseudo Page 3 ($6000) + case VIEW_PAGE_4: bVideoModeFlags |= VF_PAGE4; break; // Pseudo Page 4 ($8000) + case VIEW_PAGE_5: bVideoModeFlags |= VF_PAGE5; break; // Pseudo Page 5 ($A000) default: _ASSERT(0); break; @@ -6524,6 +6637,10 @@ Update_t _ViewOutput( ViewVideoPage_t iPage, int bVideoModeFlags ) { return _ViewOutput( VIEW_PAGE_X, VF_HIRES ); } + Update_t CmdViewOutput_HGR0 (int nArgs) + { + return _ViewOutput( VIEW_PAGE_0, VF_HIRES ); // Pseudo page ($0000) + } Update_t CmdViewOutput_HGR1 (int nArgs) { return _ViewOutput( VIEW_PAGE_1, VF_HIRES ); @@ -6532,6 +6649,18 @@ Update_t _ViewOutput( ViewVideoPage_t iPage, int bVideoModeFlags ) { return _ViewOutput( VIEW_PAGE_2, VF_HIRES ); } + Update_t CmdViewOutput_HGR3 (int nArgs) + { + return _ViewOutput( VIEW_PAGE_3, VF_HIRES ); // Pseudo page ($6000) + } + Update_t CmdViewOutput_HGR4 (int nArgs) + { + return _ViewOutput( VIEW_PAGE_4, VF_HIRES ); // Pseudo page ($8000) + } + Update_t CmdViewOutput_HGR5 (int nArgs) + { + return _ViewOutput( VIEW_PAGE_5, VF_HIRES ); // Pseudo page ($A000) + } // Double Hi-Res Update_t CmdViewOutput_DHGRX (int nArgs) { @@ -6545,6 +6674,11 @@ Update_t _ViewOutput( ViewVideoPage_t iPage, int bVideoModeFlags ) { return _ViewOutput( VIEW_PAGE_2, VF_HIRES | VF_DHIRES | VF_80COL ); } +// Super Hi-Res + Update_t CmdViewOutput_SHR(int nArgs) + { + return _ViewOutput( VIEW_PAGE_1, VF_SHR ); + } // Watches ________________________________________________________________________________________ @@ -8182,12 +8316,41 @@ void DebugExitDebugger () static void CheckBreakOpcode( int iOpcode ) { if (iOpcode == 0x00) // BRK - IsDebugBreakOnInvalid( AM_IMPLIED ); + g_bDebugBreakpointHit |= IsDebugBreakOnInvalid(AM_IMPLIED) ? BP_HIT_INVALID : 0; if (g_aOpcodes[iOpcode].sMnemonic[0] >= 'a') // All 6502/65C02 undocumented opcodes mnemonics are lowercase strings! { - // TODO: Translate g_aOpcodes[iOpcode].nAddressMode into {AM_1, AM_2, AM_3} - IsDebugBreakOnInvalid( AM_1 ); + // Translate g_aOpcodes[iOpcode].nAddressMode into {AM_1, AM_2, AM_3} + int iOpcodeType = AM_1; + switch (g_aOpcodes[iOpcode].nAddressMode) + { + case AM_1: // Invalid 1 Byte + case AM_IMPLIED: + iOpcodeType = AM_1; + break; + case AM_2: // Invalid 2 Bytes + case AM_M: // 4 #Immediate + case AM_Z: // 6 Zeropage + case AM_ZX: // 9 Zeropage, X + case AM_ZY: // 10 Zeropage, Y + case AM_R: // 11 Relative + case AM_IZX: // 12 Indexed (Zeropage Indirect, X) + case AM_NZY: // 14 Indirect (Zeropage) Indexed, Y + case AM_NZ: // 15 Indirect (Zeropage) + iOpcodeType = AM_2; + break; + case AM_3: // Invalid 3 Bytes + case AM_A: // 5 $Absolute + case AM_AX: // 7 Absolute, X + case AM_AY: // 8 Absolute, Y + case AM_IAX: // 13 Indexed (Absolute Indirect, X) + case AM_NA: // 16 Indirect (Absolute) i.e. JMP + iOpcodeType = AM_3; + break; + default: + _ASSERT(0); + } + g_bDebugBreakpointHit |= IsDebugBreakOnInvalid(iOpcodeType) ? BP_HIT_INVALID : 0; } // User wants to enter debugger on specific opcode? (NB. Can't be BRK) @@ -8302,7 +8465,7 @@ void DebugContinueStepping(const bool bCallerWillUpdateDisplay/*=false*/) g_bDebugBreakpointHit |= BP_HIT_INTERRUPT; } - g_bDebugBreakpointHit |= CheckBreakpointsIO() | CheckBreakpointsReg(); + g_bDebugBreakpointHit |= CheckBreakpointsIO() | CheckBreakpointsReg() | CheckBreakpointsDmaToOrFromIoMemory(); } if (regs.pc == g_nDebugStepUntil || g_bDebugBreakpointHit) @@ -8329,6 +8492,10 @@ void DebugContinueStepping(const bool bCallerWillUpdateDisplay/*=false*/) pszStopReason = TEXT("PC reads from floating bus or I/O memory"); else if (g_bDebugBreakpointHit & BP_HIT_INTERRUPT) sprintf_s(szStopMessage, sizeof(szStopMessage), "Interrupt occurred at $%04X", g_LBR); + else if (g_bDebugBreakpointHit & BP_DMA_TO_IO_MEM) + sprintf_s(szStopMessage, sizeof(szStopMessage), "HDD DMA to I/O memory or ROM $%04X", g_uDebugBreakOnDmaIoMemoryAddr); + else if (g_bDebugBreakpointHit & BP_DMA_FROM_IO_MEM) + sprintf_s(szStopMessage, sizeof(szStopMessage), "HDD DMA from I/O memory $%04X", g_uDebugBreakOnDmaIoMemoryAddr); else pszStopReason = TEXT("Unknown!"); @@ -8744,6 +8911,9 @@ void DebuggerProcessKey( int keycode ) // Normally any key press takes us out of "Viewing Apple Output" g_nAppMode // VK_F# are already processed, so we can't use them to cycle next video g_nAppMode // if ((g_nAppMode != MODE_LOGO) && (g_nAppMode != MODE_DEBUG)) + + GetVideo().ClearSHRResidue(); // Clear the framebuffer to remove any SHR residue in the borders + DebugVideoMode::Instance().Reset(); UpdateDisplay( UPDATE_ALL ); // 1 return; diff --git a/source/Debugger/Debug.h b/source/Debugger/Debug.h index 5ff94c1fa..fa292e4ae 100644 --- a/source/Debugger/Debug.h +++ b/source/Debugger/Debug.h @@ -39,6 +39,8 @@ , BP_HIT_MEMW = (1 << 5) , BP_HIT_PC_READ_FLOATING_BUS_OR_IO_MEM = (1 << 6) , BP_HIT_INTERRUPT = (1 << 7) + , BP_DMA_TO_IO_MEM = (1 << 8) + , BP_DMA_FROM_IO_MEM = (1 << 9) }; extern int g_nBreakpoints; @@ -181,3 +183,4 @@ void DebuggerMouseClick( int x, int y ); bool IsDebugSteppingAtFullSpeed(void); + void DebuggerBreakOnDmaToOrFromIoMemory(WORD addr, bool isDmaToMemory); diff --git a/source/Debugger/Debugger_Assembler.cpp b/source/Debugger/Debugger_Assembler.cpp index c86483e1d..6578ec986 100644 --- a/source/Debugger/Debugger_Assembler.cpp +++ b/source/Debugger/Debugger_Assembler.cpp @@ -509,6 +509,7 @@ int _6502_GetOpmodeOpbyte ( const int nBaseAddress, int & iOpmode_, int & nOpby case NOP_BYTE_2: nOpbyte_ = 2; iOpmode_ = AM_M; break; case NOP_BYTE_4: nOpbyte_ = 4; iOpmode_ = AM_M; break; case NOP_BYTE_8: nOpbyte_ = 8; iOpmode_ = AM_M; break; + case NOP_FAC : nOpbyte_ = 5; iOpmode_ = AM_M; break; case NOP_WORD_1: nOpbyte_ = 2; iOpmode_ = AM_M; break; case NOP_WORD_2: nOpbyte_ = 4; iOpmode_ = AM_M; break; case NOP_WORD_4: nOpbyte_ = 8; iOpmode_ = AM_M; break; diff --git a/source/Debugger/Debugger_Assembler.h b/source/Debugger/Debugger_Assembler.h index 56dddcb48..16fa7d2ae 100644 --- a/source/Debugger/Debugger_Assembler.h +++ b/source/Debugger/Debugger_Assembler.h @@ -100,6 +100,7 @@ ,NUM_ASM_W_DIRECTIVES }; + // NOTE: Keep in sync! AsmCustomDirective_e g_aAssemblerDirectives enum AsmCustomDirective_e { ASM_DEFINE_BYTE diff --git a/source/Debugger/Debugger_Color.cpp b/source/Debugger/Debugger_Color.cpp index 2ef90c117..3db6714b1 100644 --- a/source/Debugger/Debugger_Color.cpp +++ b/source/Debugger/Debugger_Color.cpp @@ -95,6 +95,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA G8, // FG_DISASM_SYMBOL C8, // FG_DISASM_CHAR G8, // FG_DISASM_BRANCH + COLOR_CUSTOM_01, // FG_DISASM_SINT8 C3, // BG_INFO (C4, C2 too dark) C3, // BG_INFO_WATCH diff --git a/source/Debugger/Debugger_Color.h b/source/Debugger/Debugger_Color.h index 22a1757c5..9cef06cfd 100644 --- a/source/Debugger/Debugger_Color.h +++ b/source/Debugger/Debugger_Color.h @@ -89,6 +89,7 @@ /*ZZZ*/ , FG_DISASM_SYMBOL // Green HOME , FG_DISASM_CHAR // Cyan 'c' , FG_DISASM_BRANCH // Green ^ = v + , FG_DISASM_SINT8 // Lite Blue , BG_INFO // Cyan Regs/Stack/BP/Watch/ZP , BG_INFO_WATCH // Cyan diff --git a/source/Debugger/Debugger_Commands.cpp b/source/Debugger/Debugger_Commands.cpp index ac056080f..7aef5b76d 100644 --- a/source/Debugger/Debugger_Commands.cpp +++ b/source/Debugger/Debugger_Commands.cpp @@ -140,7 +140,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA {TEXT("DW2") , CmdDisasmDataDefWord2 , CMD_DEFINE_DATA_WORD2, "Define address array, display 2 words/line" }, {TEXT("DW4") , CmdDisasmDataDefWord4 , CMD_DEFINE_DATA_WORD4, "Define address array, display 4 words/line" }, {TEXT("ASC") , CmdDisasmDataDefString , CMD_DEFINE_DATA_STR , "Define text string" }, // 2.7.0.26 Changed: DS to ASC because DS is used as "Define Space" assembler directive -// {TEXT("DF") , CmdDisasmDataDefFloat , CMD_DEFINE_DATA_FLOAT, "Define AppleSoft (packed) Float" }, + {TEXT("DF") , CmdDisasmDataDefFloat , CMD_DEFINE_DATA_FLOAT, "Define AppleSoft (packed) Float" }, // {TEXT("DFX") , CmdDisasmDataDefFloatUnpack , CMD_DEFINE_DATA_FLOAT2,"Define AppleSoft (unpacked) Float" }, // with symbol lookup // {TEXT("DA<>") , CmdDisasmDataDefAddress8HL , CMD_DEFINE_ADDR_8_HL , "Define split array of addresses, high byte section followed by low byte section" }, @@ -148,7 +148,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // {TEXT("DA<") , CmdDisasmDataDefAddress8H , CMD_DEFINE_ADDR_BYTE_H , "Define array of high byte addresses" }, // {TEXT("DB>") , CmdDisasmDataDefAddress8L , CMD_DEFINE_ADDR_BYTE_L , "Define array of low byte addresses" } {TEXT("DA") , CmdDisasmDataDefAddress16 , CMD_DEFINE_ADDR_WORD , "Define array of word addresses" }, -// TODO: Rename config cmd: DISASM +// TODO: Rename config cmd: DISASM or ID (Interactive Disassembly) // {TEXT("UA") , CmdDisasmDataSmart , CMD_SMART_DISASSEMBLE, "Analyze opcodes to determine if code or data" }, // Disk {TEXT("DISK") , CmdDisk , CMD_DISK , "Access Disk Drive Functions" }, @@ -262,11 +262,16 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA {TEXT("DGR1") , CmdViewOutput_DGR1 , CMD_VIEW_DGR1 , "View Double lo-res Page 1" }, {TEXT("DGR2") , CmdViewOutput_DGR2 , CMD_VIEW_DGR2 , "View Double lo-res Page 2" }, {TEXT("HGR") , CmdViewOutput_HGRX , CMD_VIEW_HGRX , "View Hi-res (current page)" }, - {TEXT("HGR1") , CmdViewOutput_HGR1 , CMD_VIEW_HGR1 , "View Hi-res Page 1" }, - {TEXT("HGR2") , CmdViewOutput_HGR2 , CMD_VIEW_HGR2 , "View Hi-res Page 2" }, + {TEXT("HGR0") , CmdViewOutput_HGR0 , CMD_VIEW_HGR0 , "View pseudo Hi-res Page 0 ($0000)" }, + {TEXT("HGR1") , CmdViewOutput_HGR1 , CMD_VIEW_HGR1 , "View Hi-res Page 1 ($2000)" }, + {TEXT("HGR2") , CmdViewOutput_HGR2 , CMD_VIEW_HGR2 , "View Hi-res Page 2 ($4000)" }, + {TEXT("HGR3") , CmdViewOutput_HGR3 , CMD_VIEW_HGR3 , "View pseudo Hi-res Page 3 ($6000)" }, + {TEXT("HGR4") , CmdViewOutput_HGR4 , CMD_VIEW_HGR4 , "View pseudo Hi-res Page 4 ($8000)" }, + {TEXT("HGR5") , CmdViewOutput_HGR5 , CMD_VIEW_HGR5 , "View pseudo Hi-res Page 5 ($A000)" }, {TEXT("DHGR") , CmdViewOutput_DHGRX , CMD_VIEW_DHGRX , "View Double Hi-res (current page)" }, {TEXT("DHGR1") , CmdViewOutput_DHGR1 , CMD_VIEW_DHGR1 , "View Double Hi-res Page 1" }, {TEXT("DHGR2") , CmdViewOutput_DHGR2 , CMD_VIEW_DHGR2 , "View Double Hi-res Page 2" }, + {TEXT("SHR") , CmdViewOutput_SHR , CMD_VIEW_SHR , "View Super Hi-res" }, // Watch {TEXT("W") , CmdWatch , CMD_WATCH , "Alias for WA (Watch Add)" }, {TEXT("WA") , CmdWatchAdd , CMD_WATCH_ADD , "Add/Update address or symbol to watch" }, @@ -455,6 +460,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA {TEXT("SAVE") , NULL, PARAM_SAVE }, {TEXT("START") , NULL, PARAM_START }, // benchmark {TEXT("STOP") , NULL, PARAM_STOP }, // benchmark + {TEXT("ALL") , NULL, PARAM_ALL }, // Help Categories {"*" , NULL, PARAM_WILDSTAR }, {"BOOKMARKS" , NULL, PARAM_CAT_BOOKMARKS }, diff --git a/source/Debugger/Debugger_Console.h b/source/Debugger/Debugger_Console.h index 4f4e193bf..2ac3e4d49 100644 --- a/source/Debugger/Debugger_Console.h +++ b/source/Debugger/Debugger_Console.h @@ -68,12 +68,12 @@ #define CHC_ARG_MAND "`7" // < > #define CHC_ARG_OPT "`4" // [ ] #define CHC_ARG_SEP "`9" // | grey - #define CHC_NUM_DEC "`6" // cyan looks better then yellow (_SearchMemoryDisplay), S D000:FFFF A9 00, PROFILE, HELP BP + #define CHC_NUM_DEC "`:" // Lite Blue looks better then yellow (_SearchMemoryDisplay), S D000:FFFF A9 00, PROFILE, HELP BP #define CHC_NUM_HEX "`3" #define CHC_SYMBOL "`2" // Symbols #define CHC_ADDRESS "`8" // Hex Address #define CHC_ERROR "`1" // Red - #define CHC_WARNING "`5" // Purple + #define CHC_WARNING "`8" // Orange #define CHC_INFO "`3" // Yellow #define CHC_STRING "`6" // #define CHC_EXAMPLE "`:" diff --git a/source/Debugger/Debugger_Disassembler.cpp b/source/Debugger/Debugger_Disassembler.cpp index 8649ac09f..6c8e12981 100644 --- a/source/Debugger/Debugger_Disassembler.cpp +++ b/source/Debugger/Debugger_Disassembler.cpp @@ -205,7 +205,7 @@ int GetDisassemblyLine(WORD nBaseAddress, DisasmLine_t& line_) if ((iOpmode == AM_AY) || (iOpmode == AM_ZY) || (iOpmode == AM_NZY)) line_.bTargetY = true; // ,Y - unsigned int nMinBytesLen = (MAX_OPCODES * (2 + g_bConfigDisasmOpcodeSpaces)); // 2 char for byte (or 3 with space) + unsigned int nMinBytesLen = (DISASM_DISPLAY_MAX_OPCODES * (2 + g_bConfigDisasmOpcodeSpaces)); // 2 char for byte (or 3 with space) int bDisasmFormatFlags = 0; @@ -243,41 +243,39 @@ int GetDisassemblyLine(WORD nBaseAddress, DisasmLine_t& line_) bDisasmFormatFlags |= DISASM_FORMAT_BRANCH; if (nTarget < nBaseAddress) - { sprintf(line_.sBranch, "%s", g_sConfigBranchIndicatorUp[g_iConfigDisasmBranchType]); - } else - if (nTarget > nBaseAddress) - { - sprintf(line_.sBranch, "%s", g_sConfigBranchIndicatorDown[g_iConfigDisasmBranchType]); - } - else - { - sprintf(line_.sBranch, "%s", g_sConfigBranchIndicatorEqual[g_iConfigDisasmBranchType]); - } + if (nTarget > nBaseAddress) + sprintf(line_.sBranch, "%s", g_sConfigBranchIndicatorDown[g_iConfigDisasmBranchType]); + else + sprintf(line_.sBranch, "%s", g_sConfigBranchIndicatorEqual[g_iConfigDisasmBranchType]); + + bDisasmFormatFlags |= DISASM_FORMAT_TARGET_POINTER; + if (g_iConfigDisasmTargets & DISASM_TARGET_ADDR) + sprintf(line_.sTargetPointer, "%04X", nTarget & 0xFFFF); } // intentional re-test AM_R ... -// if ((iOpmode >= AM_A) && (iOpmode <= AM_NA)) - if ((iOpmode == AM_A) || // Absolute - (iOpmode == AM_Z) || // Zeropage - (iOpmode == AM_AX) || // Absolute, X - (iOpmode == AM_AY) || // Absolute, Y - (iOpmode == AM_ZX) || // Zeropage, X - (iOpmode == AM_ZY) || // Zeropage, Y - (iOpmode == AM_R) || // Relative +// if ((iOpmode >= AM_A ) && (iOpmode <= AM_NA)) + if ((iOpmode == AM_A ) || // Absolute + (iOpmode == AM_Z ) || // Zeropage + (iOpmode == AM_AX ) || // Absolute, X + (iOpmode == AM_AY ) || // Absolute, Y + (iOpmode == AM_ZX ) || // Zeropage, X + (iOpmode == AM_ZY ) || // Zeropage, Y + (iOpmode == AM_R ) || // Relative (iOpmode == AM_IZX) || // Indexed (Zeropage Indirect, X) (iOpmode == AM_IAX) || // Indexed (Absolute Indirect, X) (iOpmode == AM_NZY) || // Indirect (Zeropage) Index, Y - (iOpmode == AM_NZ) || // Indirect (Zeropage) - (iOpmode == AM_NA)) //(Indirect Absolute) + (iOpmode == AM_NZ ) || // Indirect (Zeropage) + (iOpmode == AM_NA )) //(Indirect Absolute) { line_.nTarget = nTarget; const char* pTarget = NULL; const char* pSymbol = 0; - pSymbol = FindSymbolFromAddress(nTarget); + pSymbol = FindSymbolFromAddress(nTarget, &line_.iTargetTable); // Data Assembler if (pData && (!pData->bSymbolLookup)) @@ -292,7 +290,7 @@ int GetDisassemblyLine(WORD nBaseAddress, DisasmLine_t& line_) if (!(bDisasmFormatFlags & DISASM_FORMAT_SYMBOL)) { - pSymbol = FindSymbolFromAddress(nTarget - 1); + pSymbol = FindSymbolFromAddress(nTarget - 1, &line_.iTargetTable); if (pSymbol) { bDisasmFormatFlags |= DISASM_FORMAT_SYMBOL; @@ -314,7 +312,7 @@ int GetDisassemblyLine(WORD nBaseAddress, DisasmLine_t& line_) // nSecondTarget = g_bDebugConfig_DisasmMatchSymbolOffsetMinus1First ? nTarget+1 : nTarget-1; if (!(bDisasmFormatFlags & DISASM_FORMAT_SYMBOL) || pData) { - pSymbol = FindSymbolFromAddress(nTarget + 1); + pSymbol = FindSymbolFromAddress(nTarget + 1,&line_.iTargetTable); if (pSymbol) { bDisasmFormatFlags |= DISASM_FORMAT_SYMBOL; @@ -390,22 +388,28 @@ int GetDisassemblyLine(WORD nBaseAddress, DisasmLine_t& line_) if (iOpmode == AM_M) { // sprintf( sTarget, g_aOpmodes[ iOpmode ]._sFormat, (unsigned)nTarget ); - sprintf(line_.sTarget, "%02X", (unsigned)nTarget); + sprintf(line_.sTarget , "%02X", (unsigned)nTarget); - if (iOpmode == AM_M) - { - bDisasmFormatFlags |= DISASM_FORMAT_CHAR; - line_.nImmediate = (BYTE)nTarget; - unsigned _char = FormatCharTxtCtrl(FormatCharTxtHigh(line_.nImmediate, NULL), NULL); + if (nTarget == 0) + line_.sImmediateSignedDec[0] = 0; // nothing + else + if (nTarget < 128) + sprintf(line_.sImmediateSignedDec, "+%d" , nTarget ); + else + if (nTarget >= 128) + sprintf(line_.sImmediateSignedDec, "-%d" , (~nTarget + 1) & 0xFF ); - sprintf(line_.sImmediate, "%c", _char); + bDisasmFormatFlags |= DISASM_FORMAT_CHAR; + line_.nImmediate = (BYTE)nTarget; + unsigned _char = FormatCharTxtCtrl(FormatCharTxtHigh(line_.nImmediate, NULL), NULL); + + sprintf(line_.sImmediate, "%c", _char); #if OLD_CONSOLE_COLOR - if (ConsoleColorIsEscapeMeta(_char)) - sprintf(line_.sImmediate, "%c%c", _char, _char); - else - sprintf(line_.sImmediate, "%c", _char); + if (ConsoleColorIsEscapeMeta(_char)) + sprintf(line_.sImmediate, "%c%c", _char, _char); + else + sprintf(line_.sImmediate, "%c", _char); #endif - } } } @@ -444,8 +448,8 @@ void FormatOpcodeBytes(WORD nBaseAddress, DisasmLine_t& line_) char* pDst = line_.sOpCodes; int nMaxOpBytes = nOpbyte; - if (nMaxOpBytes > MAX_OPCODES) // 2.8.0.0 fix // TODO: FIX: show max 8 bytes for HEX - nMaxOpBytes = MAX_OPCODES; + if (nMaxOpBytes > DISASM_DISPLAY_MAX_OPCODES) // 2.8.0.0 fix // TODO: FIX: show max 8 bytes for HEX + nMaxOpBytes = DISASM_DISPLAY_MAX_OPCODES; for (int iByte = 0; iByte < nMaxOpBytes; iByte++) { @@ -462,6 +466,45 @@ void FormatOpcodeBytes(WORD nBaseAddress, DisasmLine_t& line_) } } +struct FAC_t +{ + uint8_t negative; + int8_t exponent; + uint32_t mantissa; + + bool isZero; +}; + +void FAC_Unpack(WORD nAddress, FAC_t& fac_) +{ + BYTE e0 = *(LPBYTE)(mem + nAddress + 0); + BYTE m1 = *(LPBYTE)(mem + nAddress + 1); + BYTE m2 = *(LPBYTE)(mem + nAddress + 2); + BYTE m3 = *(LPBYTE)(mem + nAddress + 3); + BYTE m4 = *(LPBYTE)(mem + nAddress + 4); + + // sign + // EB82:A5 9D SIGN LDA FAC + // EB84:F0 09 BEQ SIGN3 ; zero + // EB86:A5 A2 SIGN1 LDA FAC.SIGN + // EB88:2A SIGN2 ROL + // EB89:A9 FF LDA #$FF ; negative + // EB8B:B0 02 BCS SIGN3 + // EB8D:A9 01 LDA #$01 ; positive + // EB8F:60 SIGN3 + + fac_.exponent = e0 - 0x80; + fac_.negative =(m1 & 0x80) >> 7; // EBAF:46 A2 ABS LSR FAC.SIGN + fac_.mantissa = 0 + | ((m1 | 0x80) << 24) // implicit 1.0, EB12: ORA #$80, STA FAC+1 + | ((m2 ) << 16) + | ((m3 ) << 8) + | ((m4 ) << 0); + + fac_.isZero = (e0 == 0); // TODO: need to check mantissa? +} + + // Formats Target string with bytes,words, string, etc... //=========================================================================== void FormatNopcodeBytes(WORD nBaseAddress, DisasmLine_t& line_) @@ -469,85 +512,106 @@ void FormatNopcodeBytes(WORD nBaseAddress, DisasmLine_t& line_) char* pDst = line_.sTarget; const char* pSrc = 0; DWORD nStartAddress = line_.pDisasmData->nStartAddress; - DWORD nEndAddress = line_.pDisasmData->nEndAddress; + DWORD nEndAddress = line_.pDisasmData->nEndAddress; // int nDataLen = nEndAddress - nStartAddress + 1 ; int nDisplayLen = nEndAddress - nBaseAddress + 1; // *inclusive* KEEP IN SYNC: _CmdDefineByteRange() CmdDisasmDataList() _6502_GetOpmodeOpbyte() FormatNopcodeBytes() int len = nDisplayLen; for (int iByte = 0; iByte < line_.nOpbyte; ) { - BYTE nTarget8 = *(LPBYTE)(mem + nBaseAddress + iByte); + BYTE nTarget8 = *(LPBYTE)(mem + nBaseAddress + iByte); WORD nTarget16 = *(LPWORD)(mem + nBaseAddress + iByte); switch (line_.iNoptype) { - case NOP_BYTE_1: - case NOP_BYTE_2: - case NOP_BYTE_4: - case NOP_BYTE_8: - sprintf(pDst, "%02X", nTarget8); // sBytes+strlen(sBytes) - pDst += 2; - iByte++; - if (line_.iNoptype == NOP_BYTE_1) - if (iByte < line_.nOpbyte) + case NOP_BYTE_1: + case NOP_BYTE_2: + case NOP_BYTE_4: + case NOP_BYTE_8: + sprintf(pDst, "%02X", nTarget8); // sBytes+strlen(sBytes) + pDst += 2; + iByte++; + if (line_.iNoptype == NOP_BYTE_1) + if (iByte < line_.nOpbyte) + { + *pDst++ = ','; + } + break; + + case NOP_FAC: + { + FAC_t fac; + FAC_Unpack( nBaseAddress, fac ); + const char aSign[2] = { '+', '-' }; + if (fac.isZero) + sprintf( pDst, "0" ); + else { - *pDst++ = ','; + double f = fac.mantissa * pow( 2.0, fac.exponent - 32 ); + //sprintf( "s%1X m%04X e%02X", fac.negative, fac.mantissa, fac.exponent ); + sprintf( pDst, "%c%f", aSign[ fac.negative ], f ); } - break; - case NOP_WORD_1: - case NOP_WORD_2: - case NOP_WORD_4: - sprintf(pDst, "%04X", nTarget16); // sBytes+strlen(sBytes) - pDst += 4; - iByte += 2; - if (iByte < line_.nOpbyte) - { - *pDst++ = ','; + iByte += 5; + break; } - break; - case NOP_ADDRESS: - // Nothing to do, already handled :-) - iByte += 2; - break; - case NOP_STRING_APPLESOFT: - iByte = line_.nOpbyte; - strncpy(pDst, (const char*)(mem + nBaseAddress), iByte); - pDst += iByte; - *pDst = 0; - case NOP_STRING_APPLE: - iByte = line_.nOpbyte; // handle all bytes of text - pSrc = (const char*)mem + nStartAddress; - - if (len > (MAX_IMMEDIATE_LEN - 2)) // does "text" fit? - { - if (len > MAX_IMMEDIATE_LEN) // no; need extra characters for ellipsis? - len = (MAX_IMMEDIATE_LEN - 3); // ellipsis = true - - // DISPLAY: text_longer_18... - FormatCharCopy(pDst, pSrc, len); // BUG: #251 v2.8.0.7: ASC #:# with null byte doesn't mark up properly - if (nDisplayLen > len) // ellipsis + case NOP_WORD_1: + case NOP_WORD_2: + case NOP_WORD_4: + sprintf(pDst, "%04X", nTarget16); // sBytes+strlen(sBytes) + pDst += 4; + iByte += 2; + if (iByte < line_.nOpbyte) { - *pDst++ = '.'; - *pDst++ = '.'; - *pDst++ = '.'; + *pDst++ = ','; + } + break; + + case NOP_ADDRESS: + // Nothing to do, already handled :-) + iByte += 2; + break; + + case NOP_STRING_APPLESOFT: + iByte = line_.nOpbyte; + strncpy(pDst, (const char*)(mem + nBaseAddress), iByte); + pDst += iByte; + *pDst = 0; + case NOP_STRING_APPLE: + iByte = line_.nOpbyte; // handle all bytes of text + pSrc = (const char*)mem + nStartAddress; + + if (len > (DISASM_DISPLAY_MAX_IMMEDIATE_LEN - 2)) // does "text" fit? + { + if (len > DISASM_DISPLAY_MAX_IMMEDIATE_LEN) // no; need extra characters for ellipsis? + len = (DISASM_DISPLAY_MAX_IMMEDIATE_LEN - 3); // ellipsis = true + + // DISPLAY: text_longer_18... + FormatCharCopy(pDst, pSrc, len); // BUG: #251 v2.8.0.7: ASC #:# with null byte doesn't mark up properly + + if (nDisplayLen > len) // ellipsis + { + *pDst++ = '.'; + *pDst++ = '.'; + *pDst++ = '.'; + } + } + else { // DISPLAY: "max_18_char" + *pDst++ = '"'; + pDst = FormatCharCopy(pDst, pSrc, len); // BUG: #251 v2.8.0.7: ASC #:# with null byte doesn't mark up properly + *pDst++ = '"'; } - } - else { // DISPLAY: "max_18_char" - *pDst++ = '"'; - pDst = FormatCharCopy(pDst, pSrc, len); // BUG: #251 v2.8.0.7: ASC #:# with null byte doesn't mark up properly - *pDst++ = '"'; - } - *pDst = 0; - break; - default: -#if _DEBUG // Unhandled data disassembly! - int* FATAL = 0; - *FATAL = 0xDEADC0DE; -#endif - iByte++; - break; + *pDst = 0; + break; + + default: + #if _DEBUG // Unhandled data disassembly! + int* FATAL = 0; + *FATAL = 0xDEADC0DE; + #endif + iByte++; + break; } } } diff --git a/source/Debugger/Debugger_DisassemblerData.cpp b/source/Debugger/Debugger_DisassemblerData.cpp index 24cec4754..6ff7ddd96 100644 --- a/source/Debugger/Debugger_DisassemblerData.cpp +++ b/source/Debugger/Debugger_DisassemblerData.cpp @@ -32,8 +32,41 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // __ Debugger Interface ____________________________________________________________________________ +void _GetAutoSymbolName ( const Nopcode_e &nopcode, const WORD nStartAddress, char *pSymbolName ) +{ + switch (nopcode) + { + case NOP_ADDRESS: + sprintf( pSymbolName, "A_%04X", nStartAddress ); // DA range + break; + + case NOP_FAC: + sprintf( pSymbolName, "F_%04X", nStartAddress ); // DF range + + case NOP_STRING_ASCII: + case NOP_STRING_APPLE: + sprintf( pSymbolName, "T_%04X", nStartAddress ); // ASC range + break; + + case NOP_WORD_1: + case NOP_WORD_2: + case NOP_WORD_4: + sprintf( pSymbolName, "W_%04X", nStartAddress ); // DW range + break; + + case NOP_BYTE_1: + case NOP_BYTE_2: + case NOP_BYTE_4: + case NOP_BYTE_8: + default: + sprintf( pSymbolName, "B_%04X", nStartAddress ); // DB range + break; + } +} + +// @param tData_ Filled out with range data //=========================================================================== -WORD _CmdDefineByteRange(int nArgs,int iArg,DisasmData_t & tData_) +WORD _GetDataRange (int nArgs, int iArg, DisasmData_t& tData_) { WORD nAddress = 0; WORD nAddress2 = 0; @@ -46,12 +79,14 @@ WORD _CmdDefineByteRange(int nArgs,int iArg,DisasmData_t & tData_) // DB symbol // bool bisAddress ... - if( nArgs < 1 ) + if (nArgs < 1) { nAddress = g_nDisasmCurAddress; } else { + // DB foo = 300 // nArgs == 3 + RangeType_t eRange = Range_Get( nAddress, nAddress2, iArg); if ((eRange == RANGE_HAS_END) || (eRange == RANGE_HAS_LEN)) @@ -61,8 +96,16 @@ WORD _CmdDefineByteRange(int nArgs,int iArg,DisasmData_t & tData_) } else { - if( nArgs > 1 ) - nAddress = g_aArgs[ 2 ].nValue; + if (nArgs > 1) + { + // 2.9.1.1 Add: Support for equal sign, also make it optional for command DB + // DB FOO 300 + // DB FOO = 300 + if (g_aArgs[2].bType == TOKEN_EQUAL) + nAddress = g_aArgs[ 3 ].nValue; + else + nAddress = g_aArgs[ 2 ].nValue; + } else nAddress = g_aArgs[ 1 ].nValue; } @@ -78,10 +121,23 @@ WORD _CmdDefineByteRange(int nArgs,int iArg,DisasmData_t & tData_) tData_.nStartAddress = nAddress; tData_.nEndAddress = nAddress + nLen; -// tData_.nArraySize = 0; + + return nAddress; +} + +//=========================================================================== +WORD _CmdDefineByteRange(int nArgs,int iArg,DisasmData_t & tData_) +{ + WORD nAddress = 0; + WORD nAddress2 = 0; + WORD nEnd = 0; + int nLen = 0; + + nAddress = _GetDataRange(nArgs,iArg,tData_); const char *pSymbolName = ""; char aSymbolName[ MAX_SYMBOLS_LEN+1 ]; + SymbolTable_Index_e eSymbolTable = SYMBOLS_ASSEMBLY; bool bAutoDefineName = false; // 2.7.0.34 @@ -106,24 +162,39 @@ WORD _CmdDefineByteRange(int nArgs,int iArg,DisasmData_t & tData_) // Old name: auto define D_# DB $XX // Example 'DB' or 'DW' with 1 arg // DB 801 + Nopcode_e nopcode = NOP_BYTE_1; + + bool isFloat = (g_iCommand == CMD_DEFINE_DATA_FLOAT); + if( isFloat ) + nopcode = NOP_FAC; + + bool isString = (g_iCommand == CMD_DEFINE_DATA_STR); + if( isString ) + nopcode = NOP_STRING_ASCII; + + bool isWord1 = (g_iCommand == CMD_DEFINE_DATA_WORD1); + if( isWord1 ) + nopcode = NOP_WORD_1; + + bool isAddr = (g_iCommand == CMD_DEFINE_ADDR_WORD); + if( isAddr ) + nopcode = NOP_ADDRESS; + if( bAutoDefineName ) { - if( g_iCommand == CMD_DEFINE_DATA_STR ) - sprintf( aSymbolName, "T_%04X", tData_.nStartAddress ); // ASC range - else - if( g_iCommand == CMD_DEFINE_DATA_WORD1 ) - sprintf( aSymbolName, "W_%04X", tData_.nStartAddress ); // DW range - else - sprintf( aSymbolName, "B_%04X", tData_.nStartAddress ); // DB range - + _GetAutoSymbolName( nopcode, tData_.nStartAddress , aSymbolName ); pSymbolName = aSymbolName; } // bRemoveSymbol = false // use arg[2] // bUpdateSymbol = true // add the symbol to the table - SymbolUpdate( eSymbolTable, pSymbolName, nAddress, false, true ); + SymbolUpdate( eSymbolTable, pSymbolName, nAddress, false, true ); // TODO: Note: need to call ConsoleUpdate(), as may print symbol has been updated + + // NOTE: We don't set the type here + // tData_.eElementType = nopcode; + // As that is done by the caller. strcpy_s( tData_.sSymbol, sizeof(tData_.sSymbol), pSymbolName ); @@ -131,18 +202,38 @@ WORD _CmdDefineByteRange(int nArgs,int iArg,DisasmData_t & tData_) } // Undefine Data +// +// X +// X addr +// X addr:addr +// +// Example: +// DB hgr 2000:3FFF +// U 1FFF +// X 1FFF:2001 //=========================================================================== Update_t CmdDisasmDataDefCode (int nArgs) { // treat memory (bytes) as code - if (! ((nArgs <= 2) || (nArgs == 4))) + if (! ((nArgs <= 2) || (nArgs == 4)) && (nArgs != 3)) { return Help_Arg_1( CMD_DISASM_CODE ); } - DisasmData_t tData; - int iArg = 2; - WORD nAddress = _CmdDefineByteRange( nArgs, iArg, tData ); + DisasmData_t tData; // X 1FFF:2001 + WORD nAddress = 0; // 8191 + int iArg = 0; + + if (nArgs == 3) + { + iArg = 1; // #### : #### + nAddress = _GetDataRange( nArgs, iArg, tData ); + } + else + { + iArg = 2; + nAddress = _GetDataRange( nArgs, iArg, tData ); + } // Need to iterate through all blocks // DB TEST1 300:320 @@ -150,18 +241,72 @@ Update_t CmdDisasmDataDefCode (int nArgs) // DB TEST3 320:340 // X TEST1 - DisasmData_t *pData = Disassembly_IsDataAddress( nAddress ); - if( pData ) + /* + // Edge cases: + U 1FFF + DB 2000:2005 + X 1FFF:2001 // Chop 2 head + X 2004:2006 // Chop 2 tail + X 1FFF:2006 // Chop entire + + U 1FFF + DB 2000:2000 + X 2000:2000 // Chop entire + + U 1FFF + DB 2000:2002 + X 2001:2001 // Chop middle + + U 1FFF + DB 2000:2005 + X 2002:2003 // Chop middle + */ + while (nAddress <= tData.nEndAddress) { - // TODO: Do we need to split the data !? - //Disassembly_DelData( tData ); - pData->iDirective = _NOP_REMOVED; + DisasmData_t *pData = Disassembly_IsDataAddress( nAddress ); + if( pData ) + { + if (( nAddress <= pData->nStartAddress) + && (tData.nEndAddress >= pData->nEndAddress )) + { + // remove entire + Disassembly_DelData( *pData ); + } + else + if (( nAddress <= pData->nStartAddress) + && (tData.nStartAddress < pData->nEndAddress )) + { + // head + pData->nStartAddress = nAddress+1; + } + else + if (( nAddress > pData->nStartAddress) + && (tData.nEndAddress >= pData->nEndAddress )) + { + // tail + pData->nEndAddress = nAddress-1; + } + else + { + // middle + SymbolTable_Index_e eSymbolTable = SYMBOLS_ASSEMBLY; - // TODO: Remove symbol 'D_FA62' from symbol table! - } - else - { - Disassembly_DelData( tData ); + DisasmData_t tSplit = *pData; + pData->nEndAddress = nAddress - 1; + + const char *pSymbolName = tSplit.sSymbol; + + tSplit.nStartAddress = tData.nEndAddress + 1; // nAddress + 1; + _GetAutoSymbolName( pData->eElementType, tSplit.nStartAddress, tSplit.sSymbol ); + + SymbolUpdate( eSymbolTable, pSymbolName, tSplit.nStartAddress, false, true ); + Disassembly_AddData( tSplit ); + } + + // TODO: Remove symbol 'D_FA62' from symbol table! + } + + nAddress++; } return UPDATE_DISASM | ConsoleUpdate(); @@ -225,6 +370,11 @@ Update_t CmdDisasmDataList (int nArgs) } // Common code + + +// TODO: merge _CmdDisasmDataDefByteX() and _CmdDisasmDataDefWordX +// add params( iDirective, iOpcode ) to allow ASM_DEFINE_FLOAT, NOP_FAC + //=========================================================================== Update_t _CmdDisasmDataDefByteX (int nArgs) { @@ -335,6 +485,13 @@ Update_t CmdDisasmDataDefAddress8L (int nArgs) return UPDATE_DISASM; } +// Command: DA +// Description: Markup data as an address (i.e. table of function pointers) +// Usage: +// DA +// Example: +// DA D000 +// DA D000:D0B1 //=========================================================================== Update_t CmdDisasmDataDefAddress16 (int nArgs) { @@ -368,46 +525,72 @@ Update_t CmdDisasmDataDefAddress16 (int nArgs) return UPDATE_DISASM | ConsoleUpdate(); } -// DB +// Command: DB +// Usage: +// DB Update_t CmdDisasmDataDefByte1 ( int nArgs ) { g_aArgs[0].nValue = NOP_BYTE_1; return _CmdDisasmDataDefByteX( nArgs ); } -// DB2 +// Command: DB2 +// Usage: +// DB2 Update_t CmdDisasmDataDefByte2 ( int nArgs ) { g_aArgs[0].nValue = NOP_BYTE_2; return _CmdDisasmDataDefByteX( nArgs ); } +// Command: DB4 +// Usage: +// DB4 Update_t CmdDisasmDataDefByte4 ( int nArgs ) { g_aArgs[0].nValue = NOP_BYTE_4; return _CmdDisasmDataDefByteX( nArgs ); } +// Command DB8 +// Usage: +// DB8 Update_t CmdDisasmDataDefByte8 ( int nArgs ) { g_aArgs[0].nValue = NOP_BYTE_8; return _CmdDisasmDataDefByteX( nArgs ); } -// DW +// Command: DF +// Usage: +// DF +Update_t CmdDisasmDataDefFloat(int nArgs) +{ + g_aArgs[0].nValue = NOP_FAC; + return _CmdDisasmDataDefByteX( nArgs ); +} + +// Command: DW +// Usage: +// DW Update_t CmdDisasmDataDefWord1 ( int nArgs ) { g_aArgs[0].nValue = NOP_WORD_1; return _CmdDisasmDataDefWordX( nArgs ); } -// DW2 +// Command: DW2 +// Usage: +// DW2 Update_t CmdDisasmDataDefWord2 ( int nArgs ) { g_aArgs[0].nValue = NOP_WORD_2; return _CmdDisasmDataDefWordX( nArgs ); } +// Command: DW4 +// Usage: +// DW4 Update_t CmdDisasmDataDefWord4 ( int nArgs ) { g_aArgs[0].nValue = NOP_WORD_4; @@ -415,6 +598,8 @@ Update_t CmdDisasmDataDefWord4 ( int nArgs ) } // Command: DS +// Usage: +// DS // ASC range Auto-define T_#### where # is the address Update_t CmdDisasmDataDefString ( int nArgs ) { @@ -545,9 +730,11 @@ void Disassembly_DelData( DisasmData_t tData) { if (pData->iDirective != _NOP_REMOVED) { - if ((nAddress >= pData->nStartAddress) && (nAddress < pData->nEndAddress)) + if ((nAddress >= pData->nStartAddress) && (nAddress <= pData->nEndAddress)) { pData->iDirective = _NOP_REMOVED; + + // TODO: delete from vector? } } pData++; diff --git a/source/Debugger/Debugger_Display.cpp b/source/Debugger/Debugger_Display.cpp index 70d7a9ec3..8d2a02a1a 100644 --- a/source/Debugger/Debugger_Display.cpp +++ b/source/Debugger/Debugger_Display.cpp @@ -38,6 +38,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../CPU.h" #include "../Windows/Win32Frame.h" #include "../LanguageCard.h" +#include "../CardManager.h" #include "../Memory.h" #include "../Mockingboard.h" #include "../NTSC.h" @@ -639,8 +640,8 @@ void StretchBltMemToFrameDC(void) int nViewportCX, nViewportCY; win32Frame.GetViewportCXCY(nViewportCX, nViewportCY); - int xdest = win32Frame.IsFullScreen() ? win32Frame.GetFullScreenOffsetX() : 0; - int ydest = win32Frame.IsFullScreen() ? win32Frame.GetFullScreenOffsetY() : 0; + int xdest = win32Frame.IsFullScreen() ? win32Frame.GetFullScreenOffsetX() : GetVideo().GetFrameBufferCentringOffsetX() * win32Frame.GetViewportScale(); + int ydest = win32Frame.IsFullScreen() ? win32Frame.GetFullScreenOffsetY() : GetVideo().GetFrameBufferCentringOffsetY() * win32Frame.GetViewportScale(); int wdest = nViewportCX; int hdest = nViewportCY; @@ -738,6 +739,7 @@ static void PrintGlyph( const int xDst, const int yDst, const int glyph ) // Manual print of character. A lot faster than BitBlt, which must be avoided. int index_src = (CONSOLE_FONT_BITMAP_HEIGHT - 1 - ySrc) * CONSOLE_FONT_NUM_CHARS_PER_ROW * CONSOLE_FONT_GRID_X + xSrc; // font bitmap int index_dst = (DISPLAY_HEIGHT - 1 - yDst) * DEBUG_VIRTUAL_TEXT_WIDTH * CONSOLE_FONT_GRID_X + xDst; // debugger bitmap + for (int yy = 0; yy < CONSOLE_FONT_GRID_Y; yy++) { for (int xx = 0; xx < CONSOLE_FONT_GRID_X; xx++) @@ -1328,7 +1330,9 @@ WORD DrawDisassemblyLine ( int iLine, const WORD nBaseAddress ) int iOpmode; int nOpbyte; DisasmLine_t line; - const char* pSymbol = FindSymbolFromAddress( nBaseAddress ); + + int iTable = NUM_SYMBOL_TABLES; + const char* pSymbol = FindSymbolFromAddress( nBaseAddress, &iTable ); const char* pMnemonic = NULL; // Data Disassembler @@ -1365,9 +1369,7 @@ WORD DrawDisassemblyLine ( int iLine, const WORD nBaseAddress ) // int X_BRANCH = 46 * nDefaultFontWidth; float aTabs[ _NUM_TAB_STOPS ] = -// { 6, 16, 26, 41, 46, 49 }; // 6, 17, 26, 40, 46 #if USE_APPLE_FONT -// { 5, 14, 20, 40, 46, 49 }; // xxxx:xx xx xx LABELxxxxxx MNEMONIC 'E' = // 0 45 14 26 { 5, 14, 26, 41, 48, 49 }; @@ -1513,7 +1515,7 @@ WORD DrawDisassemblyLine ( int iLine, const WORD nBaseAddress ) } - // Address Seperator + // Address Seperator if (! bCursorLine) DebuggerSetColorFG( DebuggerGetColor( FG_DISASM_OPERATOR ) ); @@ -1555,7 +1557,12 @@ WORD DrawDisassemblyLine ( int iLine, const WORD nBaseAddress ) if (pSymbol) { if (! bCursorLine) - DebuggerSetColorFG( DebuggerGetColor( FG_DISASM_SYMBOL ) ); + { + if (iTable == SYMBOLS_USER_2) + DebuggerSetColorFG( DebuggerGetColor( FG_INFO_ADDRESS ) ); // Show user symbols 2 in different color for organization when reverse engineering. Table 1 = known, Table 2 = unknown. + else + DebuggerSetColorFG( DebuggerGetColor( FG_DISASM_SYMBOL ) ); + } PrintTextCursorX( pSymbol, linerect ); } @@ -1604,7 +1611,10 @@ WORD DrawDisassemblyLine ( int iLine, const WORD nBaseAddress ) { if (bDisasmFormatFlags & DISASM_FORMAT_SYMBOL) { - DebuggerSetColorFG( DebuggerGetColor( FG_DISASM_SYMBOL ) ); + if (line.iTargetTable == SYMBOLS_USER_2) + DebuggerSetColorFG( DebuggerGetColor( FG_INFO_ADDRESS ) ); // Show user symbols 2 in different color for organization when reverse engineering. Table 1 = known, Table 2 = unknown. + else + DebuggerSetColorFG( DebuggerGetColor( FG_DISASM_SYMBOL ) ); } else { @@ -1628,13 +1638,13 @@ WORD DrawDisassemblyLine ( int iLine, const WORD nBaseAddress ) // SYM COPY.FAC.TO.ARG.ROUNDED = EB63 // If opcodes aren't showing then length can be longer! // FormatOpcodeBytes() uses 3 chars/MAX_OPCODES. i.e. "## " - int nMaxLen = MAX_TARGET_LEN; + int nMaxLen = DISASM_DISPLAY_MAX_TARGET_LEN; // 2.8.0.8: Bug #227: AppleSoft symbol: COPY.FAC.TO.ARG.ROUNDED overflows into registers if ( !g_bConfigDisasmAddressView ) nMaxLen += 4; if ( !g_bConfigDisasmOpcodesView ) - nMaxLen += (MAX_OPCODES*3); + nMaxLen += (DISASM_DISPLAY_MAX_OPCODES*3); // 2.9.0.9 Continuation of 2.8.0.8: Fix overflowing disassembly pane for long symbols int nOverflow = 0; @@ -1780,6 +1790,29 @@ WORD DrawDisassemblyLine ( int iLine, const WORD nBaseAddress ) } } + // 2.9.1.4: Print decimal for immediate values + if (line.bTargetImmediate) + { + linerect.left = (int) aTabs[ TS_IMMEDIATE ]; + + if( line.nImmediate ) + { + /* + 300:A9 80 A9 81 A9 FF A9 00 A9 01 A9 7E A9 7F + */ + + // Right justify to target ADDR:## + size_t len = strlen( line.sImmediateSignedDec ); + linerect.left += (2 + (4 - len)) * nDefaultFontWidth; + + DebuggerSetColorFG( DebuggerGetColor( FG_INFO_OPERATOR )); + PrintTextCursorX( "#", linerect ); + + DebuggerSetColorFG( DebuggerGetColor( FG_DISASM_SINT8 )); + PrintTextCursorX( line.sImmediateSignedDec, linerect); + } + } + // Immediate Char if (bDisasmFormatFlags & DISASM_FORMAT_CHAR) { @@ -2407,7 +2440,7 @@ void _DrawSoftSwitchLanguageCardBank( RECT & rect, const int iBankDisplay, int b int iActiveBank = -1; char sText[ 4 ] = "?"; // Default to RAMWORKS if (GetCurrentExpansionMemType() == CT_RamWorksIII) { sText[0] = 'r'; iActiveBank = GetRamWorksActiveBank(); } - if (GetCurrentExpansionMemType() == CT_Saturn128K) { sText[0] = 's'; iActiveBank = GetLanguageCard()->GetActiveBank(); } + if (GetCurrentExpansionMemType() == CT_Saturn128K) { sText[0] = 's'; iActiveBank = GetCardMgr().GetLanguageCard()->GetActiveBank(); } if (iActiveBank >= 0) { diff --git a/source/Debugger/Debugger_Help.cpp b/source/Debugger/Debugger_Help.cpp index bc4c24792..f2b065920 100644 --- a/source/Debugger/Debugger_Help.cpp +++ b/source/Debugger/Debugger_Help.cpp @@ -984,7 +984,7 @@ Update_t CmdHelpSpecific (int nArgs) break; // Breakpoints case CMD_BREAK_INVALID: - ConsoleColorizePrintFormat( sTemp, sText, TEXT(" Usage: [%s%s | %s%s] | [ # | # %s%s | # %s%s ]") + ConsoleColorizePrintFormat( sTemp, sText, TEXT(" Usage: [%s%s | %s%s] | [# | # %s%s | # %s%s]") , CHC_COMMAND , g_aParameters[ PARAM_ON ].m_sName , CHC_COMMAND @@ -994,6 +994,16 @@ Update_t CmdHelpSpecific (int nArgs) , CHC_COMMAND , g_aParameters[ PARAM_OFF ].m_sName ); + ConsoleColorizePrintFormat(sTemp, sText, TEXT(" Usage: [%s%s %s%s | %s%s %s%s]") + , CHC_COMMAND + , g_aParameters[PARAM_ALL].m_sName + , CHC_COMMAND + , g_aParameters[PARAM_ON].m_sName + , CHC_COMMAND + , g_aParameters[PARAM_ALL].m_sName + , CHC_COMMAND + , g_aParameters[PARAM_OFF].m_sName + ); ConsoleColorizePrint( sTemp, TEXT("Where: # is 0=BRK, 1=Invalid Opcode_1, 2=Invalid Opcode_2, 3=Invalid Opcode_3")); break; // case CMD_BREAK_OPCODE: diff --git a/source/Debugger/Debugger_Symbols.cpp b/source/Debugger/Debugger_Symbols.cpp index 636a5e617..a5f531453 100644 --- a/source/Debugger/Debugger_Symbols.cpp +++ b/source/Debugger/Debugger_Symbols.cpp @@ -140,11 +140,17 @@ int GetSymbolTableFromCommand() return (g_iCommand - CMD_SYMBOLS_ROM); } +// @param iTable_ Which symbol table the symbol is in if any. If none will be NUM_SYMBOL_TABLES //=========================================================================== const char* FindSymbolFromAddress (WORD nAddress, int * iTable_ ) { // Bugfix/User feature: User symbols should be searched first int iTable = NUM_SYMBOL_TABLES; + if (iTable_) + { + *iTable_ = iTable; + } + while (iTable-- > 0) { if (! g_aSymbols[iTable].size()) @@ -273,7 +279,7 @@ Update_t CmdSymbols (int nArgs) //=========================================================================== Update_t CmdSymbolsClear (int nArgs) { - SymbolTable_Index_e eSymbolTable = SYMBOLS_USER_1; + SymbolTable_Index_e eSymbolTable = SYMBOLS_USER_1; _CmdSymbolsClear( eSymbolTable ); return (UPDATE_DISASM | UPDATE_SYMBOLS); } @@ -557,7 +563,7 @@ int ParseSymbolTable(const std::string & pPathFileName, SymbolTable_Index_e eSym char sText[ CONSOLE_WIDTH * 3 ]; bool bFileDisplayed = false; - const int nMaxLen = MIN(MAX_TARGET_LEN,MAX_SYMBOLS_LEN); + const int nMaxLen = MIN(DISASM_DISPLAY_MAX_TARGET_LEN, MAX_SYMBOLS_LEN); int nSymbolsLoaded = 0; @@ -648,12 +654,17 @@ int ParseSymbolTable(const std::string & pPathFileName, SymbolTable_Index_e eSym int nLen = strlen( sName ); if (nLen > nMaxLen) { - ConsolePrintFormat( sText, " %sWarn.: %s%s (%d > %d)" + ConsolePrintFormat( sText, " %sWarn.: %s%s %s(%s%d %s> %s%d%s)" , CHC_WARNING , CHC_SYMBOL , sName + , CHC_ARG_SEP + , CHC_NUM_DEC , nLen + , CHC_ARG_SEP + , CHC_NUM_DEC , nMaxLen + , CHC_ARG_SEP ); ConsoleUpdate(); // Flush buffered output so we don't ask the user to pause } @@ -898,25 +909,45 @@ void SymbolUpdate( SymbolTable_Index_e eSymbolTable, const char *pSymbolName, WO } } - +// Syntax: +// sym ! +// sym ~ +// sym = +// sym @ = +// NOTE: Listing of the symbols is handled via returning UPDATE_NOTHING which is triggered by: +// sym * //=========================================================================== Update_t _CmdSymbolsUpdate( int nArgs, int bSymbolTables ) { bool bRemoveSymbol = false; bool bUpdateSymbol = false; - if ((nArgs == 2) && - ((g_aArgs[ 1 ].eToken == TOKEN_EXCLAMATION) || (g_aArgs[1].eToken == TOKEN_TILDE)) ) + TCHAR *pSymbolName = g_aArgs[1].sArg; + WORD nAddress = g_aArgs[3].nValue; + + if ((nArgs == 2) + && ((g_aArgs[ 1 ].eToken == TOKEN_EXCLAMATION) || (g_aArgs[1].eToken == TOKEN_TILDE)) ) bRemoveSymbol = true; if ((nArgs == 3) && (g_aArgs[ 2 ].eToken == TOKEN_EQUAL )) bUpdateSymbol = true; - if (bRemoveSymbol || bUpdateSymbol) + // 2.9.1.7 Added: QoL for automatic symbol names + if ((nArgs == 2) + && (g_aArgRaw[ 1 ].eToken == TOKEN_AT ) // NOTE: @ is parsed and evaluated and NOT in the cooked args + && (g_aArgs [ 1 ].eToken == TOKEN_EQUAL)) { - TCHAR *pSymbolName = g_aArgs[1].sArg; - WORD nAddress = g_aArgs[3].nValue; + if (bSymbolTables == SYMBOL_TABLE_USER_1) + bSymbolTables = SYMBOL_TABLE_USER_2; // Autogenerated symbol names go in table 2 for organization when reverse engineering. Table 1 = known, Table 2 = unknown. + + nAddress = g_aArgs[2].nValue; + sprintf( g_aArgs[1].sArg, "_%04X", nAddress ); // Autogenerated symbol name + bUpdateSymbol = true; + } + + if (bRemoveSymbol || bUpdateSymbol) + { int iTable = _GetSymbolTableFromFlag( bSymbolTables ); SymbolUpdate( (SymbolTable_Index_e) iTable, pSymbolName, nAddress, bRemoveSymbol, bUpdateSymbol ); return ConsoleUpdate(); diff --git a/source/Debugger/Debugger_Types.h b/source/Debugger/Debugger_Types.h index 8ac67eb2f..3e79fc6eb 100644 --- a/source/Debugger/Debugger_Types.h +++ b/source/Debugger/Debugger_Types.h @@ -398,6 +398,7 @@ , CMD_DEFINE_DATA_WORD2 , CMD_DEFINE_DATA_WORD4 , CMD_DEFINE_DATA_STR + , CMD_DEFINE_DATA_FLOAT// FAC Packed // , CMD_DEFINE_DATA_FACP // FAC Packed // , CMD_DEFINE_DATA_FACU // FAC Unpacked // , CMD_DATA_DEFINE_ADDR_BYTE_L // DB< address symbol @@ -504,11 +505,16 @@ , CMD_VIEW_DGR1 , CMD_VIEW_DGR2 , CMD_VIEW_HGRX + , CMD_VIEW_HGR0 , CMD_VIEW_HGR1 , CMD_VIEW_HGR2 + , CMD_VIEW_HGR3 + , CMD_VIEW_HGR4 + , CMD_VIEW_HGR5 , CMD_VIEW_DHGRX , CMD_VIEW_DHGR1 , CMD_VIEW_DHGR2 + , CMD_VIEW_SHR // Watch , CMD_WATCH // TODO: Deprecated ? , CMD_WATCH_ADD @@ -585,6 +591,8 @@ Update_t CmdDisasmDataDefByte4 (int nArgs); Update_t CmdDisasmDataDefByte8 (int nArgs); + Update_t CmdDisasmDataDefFloat (int nArgs); + Update_t CmdDisasmDataDefWord1 (int nArgs); Update_t CmdDisasmDataDefWord2 (int nArgs); Update_t CmdDisasmDataDefWord4 (int nArgs); @@ -765,11 +773,16 @@ Update_t CmdViewOutput_DGR2 (int nArgs); Update_t CmdViewOutput_HGRX (int nArgs); + Update_t CmdViewOutput_HGR0 (int nArgs); Update_t CmdViewOutput_HGR1 (int nArgs); Update_t CmdViewOutput_HGR2 (int nArgs); + Update_t CmdViewOutput_HGR3 (int nArgs); + Update_t CmdViewOutput_HGR4 (int nArgs); + Update_t CmdViewOutput_HGR5 (int nArgs); Update_t CmdViewOutput_DHGRX (int nArgs); Update_t CmdViewOutput_DHGR1 (int nArgs); Update_t CmdViewOutput_DHGR2 (int nArgs); + Update_t CmdViewOutput_SHR (int nArgs); // Watch Update_t CmdWatch (int nArgs); Update_t CmdWatchAdd (int nArgs); @@ -842,20 +855,20 @@ enum Nopcode_e { _NOP_REMOVED - ,NOP_BYTE_1 // 1 bytes/line - ,NOP_BYTE_2 // 2 bytes/line - ,NOP_BYTE_4 // 4 bytes/line - ,NOP_BYTE_8 // 8 bytes/line - ,NOP_WORD_1 // 1 words/line = 2 bytes (no symbol lookup) - ,NOP_WORD_2 // 2 words/line = 4 bytes - ,NOP_WORD_4 // 4 words/line = 8 bytes - ,NOP_ADDRESS// 1 word/line = 2 bytes (with symbol lookup) - ,NOP_HEX // hex string =16 bytes - ,NOP_CHAR // char string // TODO: FIXME: needed?? - ,NOP_STRING_ASCII // Low Ascii - ,NOP_STRING_APPLE // High Ascii + ,NOP_BYTE_1 // 1 bytes/line + ,NOP_BYTE_2 // 2 bytes/line + ,NOP_BYTE_4 // 4 bytes/line + ,NOP_BYTE_8 // 8 bytes/line + ,NOP_WORD_1 // 1 words/line = 2 bytes (no symbol lookup) + ,NOP_WORD_2 // 2 words/line = 4 bytes + ,NOP_WORD_4 // 4 words/line = 8 bytes + ,NOP_ADDRESS // 1 word/line = 2 bytes (with symbol lookup) + ,NOP_HEX // hex string =16 bytes + ,NOP_CHAR // char string // TODO: FIXME: needed?? + ,NOP_STRING_ASCII // Low Ascii + ,NOP_STRING_APPLE // High Ascii ,NOP_STRING_APPLESOFT // Mixed Low/High - ,NOP_FAC + ,NOP_FAC // Applesoft Floating-Point Format (5 bytes), i.e. $F069 = 0x81490FDAA2 = pi/2 ,NOP_SPRITE ,NUM_NOPCODE_TYPES }; @@ -867,7 +880,7 @@ char sSymbol[ MAX_SYMBOLS_LEN+1 ]; Nopcode_e eElementType ; // eElementType -> iNoptype - int iDirective ; // iDirective -> iNopcode + int iDirective ; // iDirective -> iNopcode ASC DA DB DF DW etc. WORD nStartAddress; // link to block [start,end) WORD nEndAddress ; @@ -919,13 +932,13 @@ , NUM_DISASM_TARGET_TYPES }; - enum DisasmDisplay_e // TODO: Prefix enums with DISASM_DISPLAY_ + enum DisasmDisplay_e { - MAX_ADDRESS_LEN = 40, - MAX_OPCODES = 3, // only display 3 opcode bytes -- See FormatOpcodeBytes() // TODO: FIX when showing data hex - CHARS_FOR_ADDRESS = 8, // 4 digits + end-of-string + padding - MAX_IMMEDIATE_LEN = 20, // Data Disassembly - MAX_TARGET_LEN = MAX_IMMEDIATE_LEN, // Debugger Display: pTarget = line.sTarget + DISASM_DISPLAY_MAX_ADDRESS_LEN = 40, + DISASM_DISPLAY_MAX_OPCODES = 3, // only display 3 opcode bytes -- See FormatOpcodeBytes() // TODO: FIX when showing data hex + DISASM_DISPLAY_CHARS_FOR_ADDRESS = 8, // 4 digits + end-of-string + padding + DISASM_DISPLAY_MAX_IMMEDIATE_LEN = 20, // Data Disassembly + DISASM_DISPLAY_MAX_TARGET_LEN = DISASM_DISPLAY_MAX_IMMEDIATE_LEN, // Debugger Display: pTarget = line.sTarget }; struct DisasmLine_t @@ -934,8 +947,8 @@ short iOpmode; int nOpbyte; - char sAddress [ CHARS_FOR_ADDRESS ]; - char sOpCodes [(MAX_OPCODES*3)+1]; + char sAddress [ DISASM_DISPLAY_CHARS_FOR_ADDRESS ]; + char sOpCodes [(DISASM_DISPLAY_MAX_OPCODES*3)+1]; // Added for Data Disassembler char sLabel [ MAX_SYMBOLS_LEN+1 ]; // label is a symbol @@ -949,15 +962,17 @@ const DisasmData_t* pDisasmData; // If != NULL then bytes are marked up as data // int nTarget; // address -> string - char sTarget [ MAX_ADDRESS_LEN ]; + char sTarget [ DISASM_DISPLAY_MAX_ADDRESS_LEN ]; - char sTargetOffset[ CHARS_FOR_ADDRESS ]; // +/- 255, realistically +/-1 + char sTargetOffset[ DISASM_DISPLAY_CHARS_FOR_ADDRESS ]; // +/- 255, realistically +/-1 int nTargetOffset; - char sTargetPointer[ CHARS_FOR_ADDRESS ]; - char sTargetValue [ CHARS_FOR_ADDRESS ]; -// char sTargetAddress[ CHARS_FOR_ADDRESS ]; + char sTargetPointer[ DISASM_DISPLAY_CHARS_FOR_ADDRESS ]; + char sTargetValue [ DISASM_DISPLAY_CHARS_FOR_ADDRESS ]; + + int iTargetTable; // Which symbol table this appears in if any. See: SYMBOLS_USER_2, DrawDisassemblyLine(), GetDisassemblyLine(), FindSymbolFromAddress() + char sImmediateSignedDec[ 6 ]; // "-128" .. "+127" char sImmediate[ 4 ]; // 'c' char nImmediate; char sBranch [ 4 ]; // ^ @@ -1403,6 +1418,7 @@ const DisasmData_t* pDisasmData; // If != NULL then bytes are marked up as data , PARAM_SAVE , PARAM_START , PARAM_STOP + , PARAM_ALL , _PARAM_GENERAL_END , PARAM_GENERAL_NUM = _PARAM_GENERAL_END - _PARAM_GENERAL_BEGIN diff --git a/source/Disk.cpp b/source/Disk.cpp index d4e73e6f0..58c74ce1b 100644 --- a/source/Disk.cpp +++ b/source/Disk.cpp @@ -60,6 +60,9 @@ const BYTE Disk2InterfaceCard::m_T00S00Pattern[] = {0xD5,0xAA,0x96,0xAA,0xAA,0xA Disk2InterfaceCard::Disk2InterfaceCard(UINT slot) : Card(CT_Disk2, slot) { + if (m_slot != 5 && m_slot != 6) // fixme + throw std::runtime_error("Card: wrong slot"); + ResetSwitches(); m_floppyLatch = 0; @@ -2027,7 +2030,7 @@ void Disk2InterfaceCard::SaveSnapshotDriveUnit(YamlSaveHelper& yamlSaveHelper, U SaveSnapshotFloppy(yamlSaveHelper, unit); } -void Disk2InterfaceCard::SaveSnapshot(class YamlSaveHelper& yamlSaveHelper) +void Disk2InterfaceCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) { YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION); @@ -2092,7 +2095,7 @@ bool Disk2InterfaceCard::LoadSnapshotFloppy(YamlLoadHelper& yamlLoadHelper, UINT m_floppyDrive[unit].m_disk.m_extraCycles = yamlLoadHelper.LoadDouble(SS_YAML_KEY_EXTRA_CYCLES); if (m_floppyDrive[unit].m_disk.m_bitCount && (m_floppyDrive[unit].m_disk.m_bitOffset >= m_floppyDrive[unit].m_disk.m_bitCount)) - throw std::string("Disk image: bitOffset >= bitCount"); + throw std::runtime_error("Disk image: bitOffset >= bitCount"); if (ImageIsWOZ(m_floppyDrive[unit].m_disk.m_imagehandle)) UpdateBitStreamOffsets(m_floppyDrive[unit].m_disk); // overwrites m_byte, inits m_bitMask @@ -2113,7 +2116,7 @@ bool Disk2InterfaceCard::LoadSnapshotDriveUnitv3(YamlLoadHelper& yamlLoadHelper, std::string disk2UnitName = std::string(SS_YAML_KEY_DISK2UNIT) + (unit == DRIVE_1 ? std::string("0") : std::string("1")); if (!yamlLoadHelper.GetSubMap(disk2UnitName)) - throw std::string("Card: Expected key: ") + disk2UnitName; + throw std::runtime_error("Card: Expected key: " + disk2UnitName); bool bImageError = LoadSnapshotFloppy(yamlLoadHelper, unit, version, track); @@ -2134,10 +2137,10 @@ bool Disk2InterfaceCard::LoadSnapshotDriveUnitv4(YamlLoadHelper& yamlLoadHelper, std::string disk2UnitName = std::string(SS_YAML_KEY_DISK2UNIT) + (unit == DRIVE_1 ? std::string("0") : std::string("1")); if (!yamlLoadHelper.GetSubMap(disk2UnitName)) - throw std::string("Card: Expected key: ") + disk2UnitName; + throw std::runtime_error("Card: Expected key: " + disk2UnitName); if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_FLOPPY)) - throw std::string("Card: Expected key: ") + SS_YAML_KEY_FLOPPY; + throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_FLOPPY); bool bImageError = LoadSnapshotFloppy(yamlLoadHelper, unit, version, track); @@ -2193,13 +2196,10 @@ void Disk2InterfaceCard::LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, U } } -bool Disk2InterfaceCard::LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool Disk2InterfaceCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { - if (slot != 5 && slot != 6) // fixme - throw std::string("Card: wrong slot"); - if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); m_currDrive = yamlLoadHelper.LoadUint(SS_YAML_KEY_CURRENT_DRIVE); m_magnetStates = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASES); diff --git a/source/Disk.h b/source/Disk.h index 65224e4c4..172118f6d 100644 --- a/source/Disk.h +++ b/source/Disk.h @@ -166,8 +166,8 @@ class Disk2InterfaceCard : public Card bool IsDriveConnected(int drive) { return m_floppyDrive[drive].m_isConnected; } static std::string GetSnapshotCardName(void); - void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); - bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); void LoadLastDiskImage(const int drive); void SaveLastDiskImage(const int drive); diff --git a/source/DiskFormatTrack.cpp b/source/DiskFormatTrack.cpp index 8da9f20e8..774d89a3f 100644 --- a/source/DiskFormatTrack.cpp +++ b/source/DiskFormatTrack.cpp @@ -357,7 +357,7 @@ void FormatTrack::SaveSnapshot(class YamlSaveHelper& yamlSaveHelper) void FormatTrack::LoadSnapshot(class YamlLoadHelper& yamlLoadHelper) { if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_FORMAT_TRACK)) - throw std::string("Card: Expected key: ") + std::string(SS_YAML_KEY_FORMAT_TRACK); + throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_FORMAT_TRACK); m_bmWrittenSectorAddrFields = yamlLoadHelper.LoadUint(SS_YAML_KEY_WRITTEN_SECTOR_ADDR_FIELDS); m_WriteTrackStartIndex = yamlLoadHelper.LoadUint(SS_YAML_KEY_WRITE_TRACK_START_IDX); diff --git a/source/FourPlay.cpp b/source/FourPlay.cpp index 39465ff27..82e908469 100644 --- a/source/FourPlay.cpp +++ b/source/FourPlay.cpp @@ -146,10 +146,10 @@ void FourPlayCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) // NB. No state for this card } -bool FourPlayCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool FourPlayCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); return true; } diff --git a/source/FourPlay.h b/source/FourPlay.h index 81e09c729..51de24e2f 100644 --- a/source/FourPlay.h +++ b/source/FourPlay.h @@ -20,8 +20,8 @@ class FourPlayCard : public Card static BYTE __stdcall IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles); static std::string GetSnapshotCardName(void); - void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); - bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); static const UINT JOYSTICKSTATIONARY = 0x20; diff --git a/source/FrameBase.cpp b/source/FrameBase.cpp index b0b7d89db..57dc13262 100644 --- a/source/FrameBase.cpp +++ b/source/FrameBase.cpp @@ -121,9 +121,9 @@ void FrameBase::Video_SaveScreenShot(const Video::VideoScreenShot_e ScreenShotTy void FrameBase::Util_MakeScreenShotFileName(TCHAR* pFinalFileName_, DWORD chars) { const std::string sPrefixScreenShotFileName = "AppleWin_ScreenShot"; - // TODO: g_sScreenshotDir const std::string pPrefixFileName = !g_pLastDiskImageName.empty() ? g_pLastDiskImageName : sPrefixScreenShotFileName; - StringCbPrintf(pFinalFileName_, chars, TEXT("%s_%09d.bmp"), pPrefixFileName.c_str(), g_nLastScreenShot); + const std::string folder = Video_GetScreenShotFolder(); + StringCbPrintf(pFinalFileName_, chars, TEXT("%s%s_%09d.bmp"), folder.c_str(), pPrefixFileName.c_str(), g_nLastScreenShot); } // Returns TRUE if file exists, else FALSE diff --git a/source/FrameBase.h b/source/FrameBase.h index bcf267ef3..74a61257c 100644 --- a/source/FrameBase.h +++ b/source/FrameBase.h @@ -15,7 +15,7 @@ class FrameBase BOOL g_bMultiMon; bool g_bFreshReset; - virtual void Initialize(void) = 0; + virtual void Initialize(bool resetVideoState) = 0; virtual void Destroy(void) = 0; virtual void FrameDrawDiskLEDS() = 0; @@ -26,13 +26,14 @@ class FrameBase virtual void FrameSetCursorPosByMousePos() = 0; virtual void SetFullScreenShowSubunitStatus(bool bShow) = 0; - virtual bool GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& bestHeight, UINT userSpecifiedHeight = 0) = 0; + virtual bool GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& bestHeight, UINT userSpecifiedWidth=0, UINT userSpecifiedHeight=0) = 0; virtual int SetViewportScale(int nNewScale, bool bForce = false) = 0; virtual void SetAltEnterToggleFullScreen(bool mode) = 0; virtual void SetLoadedSaveStateFlag(const bool bFlag) = 0; virtual void VideoPresentScreen(void) = 0; + virtual void ResizeWindow(void) = 0; // this function has the same interface as MessageBox in windows.h virtual int FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType) = 0; @@ -55,6 +56,7 @@ class FrameBase void VideoRedrawScreenAfterFullSpeed(DWORD dwCyclesThisFrame); void Video_RedrawAndTakeScreenShot(const char* pScreenshotFilename); + virtual std::string Video_GetScreenShotFolder() = 0; void Video_TakeScreenShot(const Video::VideoScreenShot_e ScreenShotType); void Video_SaveScreenShot(const Video::VideoScreenShot_e ScreenShotType, const TCHAR* pScreenShotFileName); void SetDisplayPrintScreenFileName(bool state) { g_bDisplayPrintScreenFileName = state; } diff --git a/source/Harddisk.cpp b/source/Harddisk.cpp index a0eadba5d..fb834f505 100644 --- a/source/Harddisk.cpp +++ b/source/Harddisk.cpp @@ -39,6 +39,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "SaveState.h" #include "YamlHelper.h" +#include "Debugger/Debug.h" #include "../resource/resource.h" /* @@ -129,6 +130,9 @@ Overview HarddiskInterfaceCard::HarddiskInterfaceCard(UINT slot) : Card(CT_GenericHDD, slot) { + if (m_slot != SLOT7) // fixme + throw std::runtime_error("Card: wrong slot"); + m_unitNum = HARDDISK_1 << 7; // b7=unit // The HDD interface has a single Command register for both drives: @@ -515,6 +519,16 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY { memdirty[dstAddr >> 8] = 0xFF; LPBYTE page = memwrite[dstAddr >> 8]; + if (!page) // I/O space or ROM + { + if (g_nAppMode == MODE_STEPPING) + DebuggerBreakOnDmaToOrFromIoMemory(dstAddr, true); // GH#1007 + //else // Show MessageBox? + + pCard->m_notBusyCycle = 0; // DMA complete + bRes = false; + break; + } // handle both page-aligned & non-page aligned destinations UINT size = PAGE_SIZE - (dstAddr & 0xff); @@ -527,7 +541,8 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY remaining -= size; } } - else + + if (!bRes) { pHDD->m_error = 1; r = DEVICE_IO_ERROR; @@ -541,7 +556,7 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY break; case 0x02: //write { - pHDD->m_status_next = DISK_STATUS_WRITE; + pHDD->m_status_next = DISK_STATUS_WRITE; // or DISK_STATUS_PROT if we ever enable write-protect on HDD bool bRes = true; const bool bAppendBlocks = (pHDD->m_diskblock * HD_BLOCK_SIZE) >= ImageGetImageSize(pHDD->m_imagehandle); @@ -560,7 +575,32 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY } } - memcpy(pHDD->m_buf, mem + pHDD->m_memblock, HD_BLOCK_SIZE); + // Trap and error on any accesses that overlap with I/O memory (GH#1007) + if ((pHDD->m_memblock < APPLE_IO_BEGIN && ((pHDD->m_memblock + HD_BLOCK_SIZE - 1) >= APPLE_IO_BEGIN)) // 1) Starts before I/O, but ends in I/O memory + || ((pHDD->m_memblock >> 12) == (APPLE_IO_BEGIN >> 12))) // 2) Starts in I/O memory + { + WORD dstAddr = ((pHDD->m_memblock >> 12) == (APPLE_IO_BEGIN >> 12)) ? pHDD->m_memblock : APPLE_IO_BEGIN; + + if (g_nAppMode == MODE_STEPPING) + DebuggerBreakOnDmaToOrFromIoMemory(dstAddr, false); + //else // Show MessageBox? + + pCard->m_notBusyCycle = 0; // DMA complete + bRes = false; + } + else + { + if (pHDD->m_memblock <= (MEMORY_LENGTH - HD_BLOCK_SIZE)) + { + memcpy(pHDD->m_buf, mem + pHDD->m_memblock, HD_BLOCK_SIZE); + } + else // wraps on 64KiB boundary (GH#1007) + { + const UINT size = MEMORY_LENGTH - pHDD->m_memblock; + memcpy(pHDD->m_buf, mem + pHDD->m_memblock, size); + memcpy(pHDD->m_buf + size, mem, HD_BLOCK_SIZE - size); + } + } if (bRes) bRes = ImageWriteBlock(pHDD->m_imagehandle, pHDD->m_diskblock, pHDD->m_buf); @@ -579,7 +619,7 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY } break; case 0x03: //format - pHDD->m_status_next = DISK_STATUS_WRITE; + pHDD->m_status_next = DISK_STATUS_WRITE; // or DISK_STATUS_PROT if we ever enable write-protect on HDD break; } } @@ -644,10 +684,17 @@ BYTE __stdcall HarddiskInterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, B HardDiskDrive* pHDD = &(pCard->m_hardDiskDrive[pCard->m_unitNum >> 7]); // bit7 = drive select BYTE r = DEVICE_OK; - pHDD->m_status_next = DISK_STATUS_PROT; // TODO: FIXME: If we ever enable write-protect on HD then need to change to something else ... switch (addr & 0xF) { + case 0x0: // r/o: status + case 0x1: // r/o: execute + case 0x8: // r/o: legacy next-data port + // Writing to these 3 read-only registers is a no-op. + // NB. Don't change m_status_next, as UpdateLightStatus() has a huge performance cost! + // Firmware has a busy-wait loop doing "rol hd_status,x" + // - this RMW opcode does an IORead() then an IOWrite(), and the loop iterates ~100 times! + break; case 0x2: pCard->m_command = d; break; @@ -776,7 +823,7 @@ bool HarddiskInterfaceCard::LoadSnapshotHDDUnit(YamlLoadHelper& yamlLoadHelper, { std::string hddUnitName = std::string(SS_YAML_KEY_HDDUNIT) + (unit == HARDDISK_1 ? std::string("0") : std::string("1")); if (!yamlLoadHelper.GetSubMap(hddUnitName)) - throw std::string("Card: Expected key: ") + hddUnitName; + throw std::runtime_error("Card: Expected key: " + hddUnitName); m_hardDiskDrive[unit].m_fullname.clear(); m_hardDiskDrive[unit].m_imagename.clear(); @@ -792,9 +839,11 @@ bool HarddiskInterfaceCard::LoadSnapshotHDDUnit(YamlLoadHelper& yamlLoadHelper, Disk_Status_e diskStatusNext = (Disk_Status_e) yamlLoadHelper.LoadUint(SS_YAML_KEY_STATUS_NEXT); Disk_Status_e diskStatusPrev = (Disk_Status_e) yamlLoadHelper.LoadUint(SS_YAML_KEY_STATUS_PREV); m_hardDiskDrive[unit].m_buf_ptr = yamlLoadHelper.LoadUint(SS_YAML_KEY_BUF_PTR); + if (m_hardDiskDrive[unit].m_buf_ptr >= sizeof(m_hardDiskDrive[unit].m_buf)) // pre-v3 save-states would leave m_buf_ptr==0x200 after reading a block + m_hardDiskDrive[unit].m_buf_ptr = sizeof(m_hardDiskDrive[unit].m_buf) - 1; if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_BUF)) - throw hddUnitName + std::string(": Missing: ") + std::string(SS_YAML_KEY_BUF); + throw std::runtime_error(hddUnitName + ": Missing: " + SS_YAML_KEY_BUF); yamlLoadHelper.LoadMemory(m_hardDiskDrive[unit].m_buf, HD_BLOCK_SIZE); yamlLoadHelper.PopMap(); @@ -836,16 +885,13 @@ bool HarddiskInterfaceCard::LoadSnapshotHDDUnit(YamlLoadHelper& yamlLoadHelper, return bResSelectImage; } -bool HarddiskInterfaceCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version, const std::string& strSaveStatePath) +bool HarddiskInterfaceCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { - if (slot != SLOT7) // fixme - throw std::string("Card: wrong slot"); - if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); - if (version <= 2 && (regs.pc >> 8) == (0xC0|slot)) - throw std::string("HDD card: 6502 is running old HDD firmware"); + if (version <= 2 && (regs.pc >> 8) == (0xC0|m_slot)) + throw std::runtime_error("HDD card: 6502 is running old HDD firmware"); m_unitNum = yamlLoadHelper.LoadUint(SS_YAML_KEY_CURRENT_UNIT); // b7=unit m_command = yamlLoadHelper.LoadUint(SS_YAML_KEY_COMMAND); @@ -864,7 +910,7 @@ bool HarddiskInterfaceCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT sl bool bResSelectImage2 = LoadSnapshotHDDUnit(yamlLoadHelper, HARDDISK_2); if (!bResSelectImage1 && !bResSelectImage2) - RegSaveString(TEXT(REG_PREFS), TEXT(REGVALUE_PREF_HDV_START_DIR), 1, strSaveStatePath); + RegSaveString(TEXT(REG_PREFS), TEXT(REGVALUE_PREF_HDV_START_DIR), 1, Snapshot_GetPath()); GetFrame().FrameRefreshStatus(DRAW_LEDS | DRAW_DISK_STATUS); diff --git a/source/Harddisk.h b/source/Harddisk.h index 695b0f6bd..d66b1ee86 100644 --- a/source/Harddisk.h +++ b/source/Harddisk.h @@ -104,8 +104,8 @@ class HarddiskInterfaceCard : public Card bool ImageSwap(void); static std::string GetSnapshotCardName(void); - void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); - bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version, const std::string& strSaveStatePath); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); static BYTE __stdcall IORead(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles); static BYTE __stdcall IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles); diff --git a/source/Joystick.cpp b/source/Joystick.cpp index ecba035f4..844794875 100644 --- a/source/Joystick.cpp +++ b/source/Joystick.cpp @@ -48,8 +48,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Configuration/PropertySheet.h" -#define BUTTONTIME 5000 // This is the latch (debounce) time in usecs for the joystick buttons - enum {DEVICE_NONE=0, DEVICE_JOYSTICK, DEVICE_KEYBOARD, DEVICE_MOUSE, DEVICE_JOYSTICK_THUMBSTICK2}; // Indexed by joytype[n] @@ -86,7 +84,6 @@ static POINT keyvalue[9] = {{PDL_MIN,PDL_MAX}, {PDL_CENTRAL,PDL_MAX}, {PDL {PDL_MIN,PDL_CENTRAL},{PDL_CENTRAL,PDL_CENTRAL},{PDL_MAX,PDL_CENTRAL}, {PDL_MIN,PDL_MIN}, {PDL_CENTRAL,PDL_MIN}, {PDL_MAX,PDL_MIN}}; -static int buttonlatch[3] = {0,0,0}; static BOOL joybutton[3] = {0,0,0}; static int joyshrx[2] = {8,8}; @@ -133,12 +130,6 @@ void CheckJoystick0() JOYINFO info; if (joyGetPos(JOYSTICKID1,&info) == JOYERR_NOERROR) { - if ((info.wButtons & JOY_BUTTON1) && !joybutton[0]) - buttonlatch[0] = BUTTONTIME; - if ((info.wButtons & JOY_BUTTON2) && !joybutton[1] && - (joyinfo[joytype[1]] == DEVICE_NONE) // Only consider 2nd button if NOT emulating a 2nd Apple joystick - ) - buttonlatch[1] = BUTTONTIME; joybutton[0] = ((info.wButtons & JOY_BUTTON1) != 0); if (joyinfo[joytype[1]] == DEVICE_NONE) // Only consider 2nd button if NOT emulating a 2nd Apple joystick joybutton[1] = ((info.wButtons & JOY_BUTTON2) != 0); @@ -180,13 +171,6 @@ void CheckJoystick1() result = joyGetPos(JOYSTICKID2, &info); if (result == JOYERR_NOERROR) { - if ((info.wButtons & JOY_BUTTON1) && !joybutton[2]) - { - buttonlatch[2] = BUTTONTIME; - if(joyinfo[joytype[1]] != DEVICE_NONE) - buttonlatch[1] = BUTTONTIME; // Re-map this button when emulating a 2nd Apple joystick - } - joybutton[2] = ((info.wButtons & JOY_BUTTON1) != 0); if(joyinfo[joytype[1]] != DEVICE_NONE) joybutton[1] = ((info.wButtons & JOY_BUTTON1) != 0); // Re-map this button when emulating a 2nd Apple joystick @@ -412,30 +396,7 @@ BOOL JoyProcessKey(int virtkey, bool extended, bool down, bool autorep) // - if (virtkey == VK_NUMPAD0) - { - if(down) - { - if(joyinfo[joytype[1]] != DEVICE_KEYBOARD) - { - buttonlatch[0] = BUTTONTIME; - } - else if(joyinfo[joytype[1]] != DEVICE_NONE) - { - buttonlatch[2] = BUTTONTIME; - buttonlatch[1] = BUTTONTIME; // Re-map this button when emulating a 2nd Apple joystick - } - } - } - else if (virtkey == VK_DECIMAL) - { - if(down) - { - if(joyinfo[joytype[1]] != DEVICE_KEYBOARD) - buttonlatch[1] = BUTTONTIME; - } - } - else if ((down && !autorep) || (GetPropertySheet().GetJoystickCenteringControl() == JOYSTICK_MODE_CENTERING)) + if ((down && !autorep) || (GetPropertySheet().GetJoystickCenteringControl() == JOYSTICK_MODE_CENTERING)) { int xkeys = 0; int ykeys = 0; @@ -524,10 +485,9 @@ BYTE __stdcall JoyportReadButton(WORD address, ULONG nExecutedCycles) switch (address) { case 0x61: - pressed = (buttonlatch[0] || joybutton[0] || setbutton[0] /*|| keydown[JK_OPENAPPLE]*/); + pressed = (joybutton[0] || setbutton[0] /*|| keydown[JK_OPENAPPLE]*/); if(joyinfo[joytype[1]] != DEVICE_KEYBOARD) // BUG? joytype[1] should be [0] ? pressed = (pressed || keydown[JK_BUTTON0]); - buttonlatch[0] = 0; break; case 0x62: // Left or Up @@ -568,8 +528,7 @@ BYTE __stdcall JoyportReadButton(WORD address, ULONG nExecutedCycles) static BOOL CheckButton0Pressed(void) { - BOOL pressed = buttonlatch[0] || - joybutton[0] || + BOOL pressed = joybutton[0] || setbutton[0] || keydown[JK_OPENAPPLE]; @@ -581,8 +540,7 @@ static BOOL CheckButton0Pressed(void) static BOOL CheckButton1Pressed(void) { - BOOL pressed = buttonlatch[1] || - joybutton[1] || + BOOL pressed = joybutton[1] || setbutton[1] || keydown[JK_CLOSEDAPPLE]; @@ -617,7 +575,6 @@ BYTE __stdcall JoyReadButton(WORD pc, WORD address, BYTE, BYTE, ULONG nExecutedC { pressed = !swapButtons0and1 ? CheckButton0Pressed() : CheckButton1Pressed(); const UINT button0 = !swapButtons0and1 ? 0 : 1; - buttonlatch[button0] = 0; DoAutofire(button0, pressed); } break; @@ -626,7 +583,6 @@ BYTE __stdcall JoyReadButton(WORD pc, WORD address, BYTE, BYTE, ULONG nExecutedC { pressed = !swapButtons0and1 ? CheckButton1Pressed() : CheckButton0Pressed(); const UINT button1 = !swapButtons0and1 ? 1 : 0; - buttonlatch[button1] = 0; DoAutofire(button1, pressed); } break; @@ -640,10 +596,9 @@ BYTE __stdcall JoyReadButton(WORD pc, WORD address, BYTE, BYTE, ULONG nExecutedC } else { - pressed = (buttonlatch[2] || joybutton[2] || setbutton[2]); + pressed = (joybutton[2] || setbutton[2]); DoAutofire(2, pressed); } - buttonlatch[2] = 0; break; } @@ -737,9 +692,6 @@ void JoySetButton(eBUTTON number, eBUTTONSTATE down) } setbutton[number] = down; - - if (down) - buttonlatch[number] = BUTTONTIME; } //=========================================================================== @@ -836,22 +788,6 @@ void JoySetPosition(int xvalue, int xrange, int yvalue, int yrange) //=========================================================================== -// Update the latch (debounce) time for each button -void JoyUpdateButtonLatch(const UINT nExecutionPeriodUsec) -{ - for (UINT i=0; i<3; i++) - { - if (buttonlatch[i]) - { - buttonlatch[i] -= nExecutionPeriodUsec; - if (buttonlatch[i] < 0) - buttonlatch[i] = 0; - } - } -} - -//=========================================================================== - BOOL JoyUsingMouse() { return (joyinfo[joytype[0]] == DEVICE_MOUSE) || (joyinfo[joytype[1]] == DEVICE_MOUSE); diff --git a/source/Joystick.h b/source/Joystick.h index 06fa0ae58..1a8ded420 100644 --- a/source/Joystick.h +++ b/source/Joystick.h @@ -14,7 +14,6 @@ void JoyReset(); void JoySetButton(eBUTTON,eBUTTONSTATE); BOOL JoySetEmulationType(HWND,DWORD,int, const bool bMousecardActive); void JoySetPosition(int,int,int,int); -void JoyUpdateButtonLatch(const UINT nExecutionPeriodUsec); BOOL JoyUsingMouse(); BOOL JoyUsingKeyboard(); BOOL JoyUsingKeyboardCursors(); diff --git a/source/LanguageCard.cpp b/source/LanguageCard.cpp index bcb8b1610..1c8a57e54 100644 --- a/source/LanguageCard.cpp +++ b/source/LanguageCard.cpp @@ -38,10 +38,18 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA const UINT LanguageCardUnit::kMemModeInitialState = MF_BANK2 | MF_WRITERAM; // !INTCXROM -LanguageCardUnit::LanguageCardUnit(SS_CARDTYPE type/*=CT_LanguageCardIIe*/) : - Card(type, kSlot0), +LanguageCardUnit * LanguageCardUnit::create(UINT slot) +{ + return new LanguageCardUnit(CT_LanguageCardIIe, slot); +} + +LanguageCardUnit::LanguageCardUnit(SS_CARDTYPE type, UINT slot) : + Card(type, slot), m_uLastRamWrite(0) { + if (m_slot != LanguageCardUnit::kSlot0) + throw std::runtime_error("Language Card: wrong slot"); // throws to the top-level catch-handler, and shuts down + SetMemMainLanguageCard(NULL, true); } @@ -52,12 +60,13 @@ LanguageCardUnit::~LanguageCardUnit(void) void LanguageCardUnit::InitializeIO(LPBYTE pCxRomPeripheral) { - RegisterIoHandler(kSlot0, &LanguageCardUnit::IO, &LanguageCardUnit::IO, NULL, NULL, this, NULL); + RegisterIoHandler(m_slot, &LanguageCardUnit::IO, &LanguageCardUnit::IO, NULL, NULL, this, NULL); } BYTE __stdcall LanguageCardUnit::IO(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULONG nExecutedCycles) { - LanguageCardUnit* pLC = (LanguageCardUnit*) MemGetSlotParameters(kSlot0); + UINT uSlot = ((uAddr & 0xff) >> 4) - 8; + LanguageCardUnit* pLC = (LanguageCardUnit*) MemGetSlotParameters(uSlot); DWORD memmode = GetMemMode(); DWORD lastmemmode = memmode; @@ -136,8 +145,13 @@ bool LanguageCardUnit::IsOpcodeRMWabs(WORD addr) //------------------------------------- -LanguageCardSlot0::LanguageCardSlot0(SS_CARDTYPE type/*=CT_LanguageCard*/) - : LanguageCardUnit(type) +LanguageCardSlot0 * LanguageCardSlot0::create(UINT slot) +{ + return new LanguageCardSlot0(CT_LanguageCard, slot); +} + +LanguageCardSlot0::LanguageCardSlot0(SS_CARDTYPE type, UINT slot) + : LanguageCardUnit(type, slot) { m_pMemory = new BYTE[kMemBankSize]; SetMemMainLanguageCard(m_pMemory); @@ -152,7 +166,6 @@ LanguageCardSlot0::~LanguageCardSlot0(void) // static const UINT kUNIT_LANGUAGECARD_VER = 1; -static const UINT kSLOT_LANGUAGECARD = LanguageCardUnit::kSlot0; #define SS_YAML_VALUE_CARD_LANGUAGECARD "Language Card" @@ -194,7 +207,7 @@ void LanguageCardSlot0::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) return; // No Language Card support for //e and above } - YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), kSLOT_LANGUAGECARD, kUNIT_LANGUAGECARD_VER); + YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_LANGUAGECARD_VER); YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); SaveLCState(yamlSaveHelper); @@ -205,13 +218,10 @@ void LanguageCardSlot0::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) } } -bool LanguageCardSlot0::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool LanguageCardSlot0::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { - if (slot != kSLOT_LANGUAGECARD) - throw std::string("Card: wrong slot"); - if (version != kUNIT_LANGUAGECARD_VER) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); // "State" LoadLCState(yamlLoadHelper); @@ -222,7 +232,7 @@ bool LanguageCardSlot0::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, } if (!yamlLoadHelper.GetSubMap(GetSnapshotMemStructName())) - throw std::string("Memory: Missing map name: " + GetSnapshotMemStructName()); + throw std::runtime_error("Memory: Missing map name: " + GetSnapshotMemStructName()); yamlLoadHelper.LoadMemory(m_pMemory, kMemBankSize); @@ -235,8 +245,10 @@ bool LanguageCardSlot0::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, //------------------------------------- -Saturn128K::Saturn128K(UINT banks) - : LanguageCardSlot0(CT_Saturn128K) +UINT Saturn128K::g_uSaturnBanksFromCmdLine = 0; + +Saturn128K::Saturn128K(UINT slot, UINT banks) + : LanguageCardSlot0(CT_Saturn128K, slot) { m_uSaturnTotalBanks = (banks == 0) ? kMaxSaturnBanks : banks; m_uSaturnActiveBank = 0; @@ -266,11 +278,6 @@ Saturn128K::~Saturn128K(void) } } -void Saturn128K::SetMemorySize(UINT banks) -{ - m_uSaturnTotalBanks = banks; -} - UINT Saturn128K::GetActiveBank(void) { return m_uSaturnActiveBank; @@ -278,7 +285,7 @@ UINT Saturn128K::GetActiveBank(void) void Saturn128K::InitializeIO(LPBYTE pCxRomPeripheral) { - RegisterIoHandler(kSlot0, &Saturn128K::IO, &Saturn128K::IO, NULL, NULL, this, NULL); + RegisterIoHandler(m_slot, &Saturn128K::IO, &Saturn128K::IO, NULL, NULL, this, NULL); } BYTE __stdcall Saturn128K::IO(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULONG nExecutedCycles) @@ -302,7 +309,8 @@ BYTE __stdcall Saturn128K::IO(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULO 1110 $C0NE select 16K Bank 7 1111 $C0NF select 16K Bank 8 */ - Saturn128K* pLC = (Saturn128K*) MemGetSlotParameters(kSlot0); + UINT uSlot = ((uAddr & 0xff) >> 4) - 8; + Saturn128K* pLC = (Saturn128K*) MemGetSlotParameters(uSlot); _ASSERT(pLC->m_uSaturnTotalBanks); if (!pLC->m_uSaturnTotalBanks) @@ -365,7 +373,6 @@ BYTE __stdcall Saturn128K::IO(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULO // static const UINT kUNIT_SATURN_VER = 1; -static const UINT kSLOT_SATURN = LanguageCardUnit::kSlot0; #define SS_YAML_VALUE_CARD_SATURN128 "Saturn 128" @@ -393,7 +400,7 @@ void Saturn128K::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) return; // No Saturn support for //e and above } - YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), kSLOT_SATURN, kUNIT_SATURN_VER); + YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_SATURN_VER); YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); SaveLCState(yamlSaveHelper); @@ -409,13 +416,10 @@ void Saturn128K::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) } } -bool Saturn128K::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool Saturn128K::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { - if (slot != kSLOT_SATURN) // fixme - throw std::string("Card: wrong slot"); - if (version != kUNIT_SATURN_VER) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); // "State" LoadLCState(yamlLoadHelper); @@ -424,7 +428,7 @@ bool Saturn128K::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT ve UINT activeBank = yamlLoadHelper.LoadUint(SS_YAML_KEY_ACTIVE_SATURN_BANK); if (numBanks < 1 || numBanks > kMaxSaturnBanks || activeBank >= numBanks) - throw std::string(SS_YAML_KEY_UNIT ": Bad Saturn card state"); + throw std::runtime_error(SS_YAML_KEY_UNIT ": Bad Saturn card state"); m_uSaturnTotalBanks = numBanks; m_uSaturnActiveBank = activeBank; @@ -444,7 +448,7 @@ bool Saturn128K::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT ve std::string memName = GetSnapshotMemStructName() + szBank; if (!yamlLoadHelper.GetSubMap(memName)) - throw std::string("Memory: Missing map name: " + memName); + throw std::runtime_error("Memory: Missing map name: " + memName); yamlLoadHelper.LoadMemory(m_aSaturnBanks[uBank], kMemBankSize); @@ -457,3 +461,13 @@ bool Saturn128K::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT ve return true; } + +void Saturn128K::SetSaturnMemorySize(UINT banks) +{ + g_uSaturnBanksFromCmdLine = banks; +} + +UINT Saturn128K::GetSaturnMemorySize() +{ + return g_uSaturnBanksFromCmdLine; +} diff --git a/source/LanguageCard.h b/source/LanguageCard.h index 6ded0d1fc..ca2bac75a 100644 --- a/source/LanguageCard.h +++ b/source/LanguageCard.h @@ -9,7 +9,9 @@ class LanguageCardUnit : public Card { public: - LanguageCardUnit(SS_CARDTYPE type = CT_LanguageCardIIe); + // in modern C++ this could be a 2nd constructor + static LanguageCardUnit * create(UINT slot); + virtual ~LanguageCardUnit(void); virtual void Init(void) {} @@ -18,10 +20,9 @@ class LanguageCardUnit : public Card virtual void InitializeIO(LPBYTE pCxRomPeripheral); - virtual void SetMemorySize(UINT banks) {} // No-op for //e and slot-0 16K LC virtual UINT GetActiveBank(void) { return 0; } // Always 0 as only 1x 16K bank - virtual void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper) { _ASSERT(0); } // Not used for //e - virtual bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) { _ASSERT(0); return false; } // Not used for //e + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper) { } // A no-op for //e - called from CardManager::SaveSnapshot() + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { _ASSERT(0); return false; } // Not used for //e BOOL GetLastRamWrite(void) { return m_uLastRamWrite; } void SetLastRamWrite(BOOL count) { m_uLastRamWrite = count; } @@ -33,6 +34,9 @@ class LanguageCardUnit : public Card static const UINT kMemModeInitialState; static const UINT kSlot0 = SLOT0; +protected: + LanguageCardUnit(SS_CARDTYPE type, UINT slot); + private: UINT m_uLastRamWrite; }; @@ -44,16 +48,19 @@ class LanguageCardUnit : public Card class LanguageCardSlot0 : public LanguageCardUnit { public: - LanguageCardSlot0(SS_CARDTYPE = CT_LanguageCard); + // in modern C++ this could be a 2nd constructor + static LanguageCardSlot0 * create(UINT slot); + virtual ~LanguageCardSlot0(void); - virtual void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); - virtual bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); static const UINT kMemBankSize = 16*1024; static std::string GetSnapshotCardName(void); protected: + LanguageCardSlot0(SS_CARDTYPE type, UINT slot); void SaveLCState(class YamlSaveHelper& yamlSaveHelper); void LoadLCState(class YamlLoadHelper& yamlLoadHelper); @@ -70,14 +77,16 @@ class LanguageCardSlot0 : public LanguageCardUnit class Saturn128K : public LanguageCardSlot0 { public: - Saturn128K(UINT banks); + Saturn128K(UINT slot, UINT banks); virtual ~Saturn128K(void); virtual void InitializeIO(LPBYTE pCxRomPeripheral); - virtual void SetMemorySize(UINT banks); virtual UINT GetActiveBank(void); - virtual void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); - virtual bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); + + static UINT GetSaturnMemorySize(); + static void SetSaturnMemorySize(UINT banks); static BYTE __stdcall IO(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULONG nExecutedCycles); @@ -88,6 +97,8 @@ class Saturn128K : public LanguageCardSlot0 private: std::string GetSnapshotMemStructName(void); + static UINT g_uSaturnBanksFromCmdLine; + UINT m_uSaturnTotalBanks; // Will be > 0 if Saturn card is installed UINT m_uSaturnActiveBank; // Saturn 128K Language Card Bank 0 .. 7 LPBYTE m_aSaturnBanks[kMaxSaturnBanks]; diff --git a/source/Memory.cpp b/source/Memory.cpp index 21ffc2d00..83db7f011 100644 --- a/source/Memory.cpp +++ b/source/Memory.cpp @@ -56,6 +56,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Tape.h" #include "Tfe/tfe.h" #include "RGBMonitor.h" +#include "VidHD.h" #include "z80emu.h" #include "Z80VICE/z80.h" @@ -122,6 +123,10 @@ VIDEO SOFT SWITCHES $C00D W 80COLON Turn on 80 column display $C00E W ALTCHARSETOFF Turn off alternate characters $C00F W ALTCHARSETON Turn on alternate characters + $C022 R/W SCREENCOLOR [IIgs] text foreground and background colors (also VidHD) + $C029 R/W NEWVIDEO [IIgs] Select new video modes (also VidHD) + $C034 R/W BORDERCOLOR [IIgs] b3:0 are border color (also VidHD) + $C035 R/W SHADOW [IIgs] auxmem-to-bank-E1 shadowing (also VidHD) $C050 R/W TEXTOFF Select graphics mode $C051 R/W TEXTON Select text mode $C052 R/W MIXEDOFF Use full screen for graphics @@ -138,7 +143,7 @@ SOFT SWITCH STATUS FLAGS $C011 R7 BSRBANK2 1=bank2 available 0=bank1 available $C012 R7 BSRREADRAM 1=BSR active for read 0=$D000-$FFFF active (BSR = Bank Switch RAM) $C013 R7 RAMRD 0=main $0200-$BFFF active reads 1=aux active - $C014 R7 RAMWRT 0=main $0200-$BFFF active writes 1=aux writes + $C014 R7 RAMWRT 0=main $0200-$BFFF active writes 1=aux active $C015 R7 INTCXROM 1=main $C100-$CFFF ROM active 0=slot active $C016 R7 ALTZP 1=aux $0000-$1FF+auxBSR 0=main available $C017 R7 SLOTC3ROM 1=slot $C3 ROM active 0=main $C3 ROM active @@ -234,8 +239,9 @@ static BOOL modechanging = 0; // An Optimisation: means delay calling Upda static UINT memrompages = 1; +LPBYTE memVidHD = NULL; // For Apple II/II+ writes to aux mem (on VidHD card). memVidHD = memaux or NULL (depends on //e soft-switches) + static CNoSlotClock* g_NoSlotClock = new CNoSlotClock; -static LanguageCardUnit* g_pLanguageCard = NULL; // For all Apple II, //e and above #ifdef RAMWORKS static UINT g_uMaxExPages = 1; // user requested ram pages (default to 1 aux bank: so total = 128KB) @@ -255,7 +261,6 @@ BYTE __stdcall IO_Annunciator(WORD programcounter, WORD address, BYTE write, BYT static SS_CARDTYPE g_MemTypeAppleII = CT_Empty; static SS_CARDTYPE g_MemTypeAppleIIPlus = CT_LanguageCard; // Keep a copy so it's not lost if machine type changes, eg: A][ -> A//e -> A][ static SS_CARDTYPE g_MemTypeAppleIIe = CT_Extended80Col; // Keep a copy so it's not lost if machine type changes, eg: A//e -> A][ -> A//e -static UINT g_uSaturnBanksFromCmdLine = 0; const UINT CxRomSize = 4 * 1024; @@ -303,7 +308,7 @@ void SetExpansionMemType(const SS_CARDTYPE type) } else // Apple //e or above { - newSlot0Card = CT_Empty; // NB. No slot0 for //e + newSlot0Card = CT_LanguageCardIIe; // NB. No slot0 for //e newSlotAuxCard = CT_Extended80Col; } @@ -331,21 +336,24 @@ void SetExpansionMemType(const SS_CARDTYPE type) void CreateLanguageCard(void) { - delete g_pLanguageCard; - g_pLanguageCard = NULL; - + SS_CARDTYPE slot0CardType = GetCardMgr().QuerySlot(SLOT0); if (IsApple2PlusOrClone(GetApple2Type())) { - if (GetCardMgr().QuerySlot(SLOT0) == CT_Saturn128K) - g_pLanguageCard = new Saturn128K(g_uSaturnBanksFromCmdLine); - else if (GetCardMgr().QuerySlot(SLOT0) == CT_LanguageCard) - g_pLanguageCard = new LanguageCardSlot0; - else - g_pLanguageCard = NULL; + switch (slot0CardType) { + case CT_Empty: // OK + case CT_Saturn128K: // OK + case CT_LanguageCard: // OK + break; + default: // Anything else is invalid + GetCardMgr().Remove(SLOT0); + break; + } } else { - g_pLanguageCard = new LanguageCardUnit; + // only ever a CT_LanguageCardIIe for a //e + if (slot0CardType != CT_LanguageCardIIe) + GetCardMgr().Insert(SLOT0, CT_LanguageCardIIe); } } @@ -369,24 +377,19 @@ UINT GetRamWorksActiveBank(void) return g_uActiveBank; } -void SetSaturnMemorySize(UINT banks) -{ - g_uSaturnBanksFromCmdLine = banks; -} - // static BOOL GetLastRamWrite(void) { - if (g_pLanguageCard) - return g_pLanguageCard->GetLastRamWrite(); + if (GetCardMgr().GetLanguageCard()) + return GetCardMgr().GetLanguageCard()->GetLastRamWrite(); return 0; } static void SetLastRamWrite(BOOL count) { - if (g_pLanguageCard) - g_pLanguageCard->SetLastRamWrite(count); + if (GetCardMgr().GetLanguageCard()) + GetCardMgr().GetLanguageCard()->SetLastRamWrite(count); } // @@ -399,12 +402,6 @@ void SetMemMainLanguageCard(LPBYTE ptr, bool bMemMain /*=false*/) g_pMemMainLanguageCard = ptr; } -LanguageCardUnit* GetLanguageCard(void) -{ - _ASSERT(g_pLanguageCard); - return g_pLanguageCard; -} - LPBYTE GetCxRomPeripheral(void) { return pCxRomPeripheral; // Can be NULL if at MODE_LOGO @@ -470,6 +467,12 @@ static BYTE __stdcall IORead_C02x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG static BYTE __stdcall IOWrite_C02x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles) { + if (GetCardMgr().QuerySlot(SLOT3) == CT_VidHD) + { + if (addr == 0xC022 || addr == 0xC029) + GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles); + } + return TapeWrite(pc, addr, bWrite, d, nExecutedCycles); // $C020 TAPEOUT } @@ -482,6 +485,13 @@ static BYTE __stdcall IORead_C03x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG static BYTE __stdcall IOWrite_C03x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles) { + if (GetCardMgr().QuerySlot(SLOT3) == CT_VidHD) + { + // NB. Writes to $C03x addresses will still toggle the speaker, even with a VidHD present + if (addr == 0xC034 || addr == 0xC035) + GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles); + } + return SpkrToggle(pc, addr, bWrite, d, nExecutedCycles); } @@ -499,6 +509,24 @@ static BYTE __stdcall IOWrite_C04x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULON //------------------------------------- +static BYTE __stdcall IOReadWrite_ANx(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles) +{ + // $C058..C05F + _ASSERT((addr & 0xf) >= 8); + + if (IsAppleIIeOrAbove(GetApple2Type())) + { + if (!IsAppleIIc(GetApple2Type()) || /* IsIIc && */ SW_IOUDIS) + GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles); // Apple //e or (//c && IOUDIS) + } + + if (IsAppleIIc(GetApple2Type())) // No ANx lines for //c + return 0; + + // GH#1018 - AN3 line is still present on //e (with $C05E/F also toggling DHIRES) + return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); +} + static BYTE __stdcall IORead_C05x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles) { switch (addr & 0xf) @@ -511,18 +539,7 @@ static BYTE __stdcall IORead_C05x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG case 0x5: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles); case 0x6: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles); case 0x7: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles); - case 0x8: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0x9: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xA: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xB: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xC: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xD: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xE: // fall through... - case 0xF: if (IsApple2PlusOrClone(GetApple2Type())) - IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - else - return (!SW_IOUDIS) ? GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles) - : IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); + default: return IOReadWrite_ANx(pc, addr, bWrite, d, nExecutedCycles); // $C058..C05F } return 0; @@ -540,21 +557,8 @@ static BYTE __stdcall IOWrite_C05x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULON case 0x5: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles); case 0x6: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles); case 0x7: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles); - case 0x8: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0x9: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xA: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xB: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xC: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xD: return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - case 0xE: // fall through... - case 0xF: if (IsApple2PlusOrClone(GetApple2Type())) - IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); - else - return (!SW_IOUDIS) ? GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles) - : IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles); + default: return IOReadWrite_ANx(pc, addr, bWrite, d, nExecutedCycles); // $C058..C05F } - - return 0; } //------------------------------------- @@ -649,12 +653,12 @@ static BYTE __stdcall IOWrite_C07x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULON case 0xC: return IO_Null(pc, addr, bWrite, d, nExecutedCycles); case 0xD: return IO_Null(pc, addr, bWrite, d, nExecutedCycles); case 0xE: if (IS_APPLE2C()) - SetMemMode(memmode & ~MF_IOUDIS); // disable IOU access for addresses $C058 to $C05F; enable access to DHIRES switch + SetMemMode(memmode | MF_IOUDIS); // On: disable IOU access for addresses $C058 to $C05F; enable access to DHIRES switch else return IO_Null(pc, addr, bWrite, d, nExecutedCycles); break; case 0xF: if (IS_APPLE2C()) - SetMemMode(memmode | MF_IOUDIS); // enable IOU access for addresses $C058 to $C05F; disable access to DHIRES switch + SetMemMode(memmode & ~MF_IOUDIS); // Off: enable IOU access for addresses $C058 to $C05F; disable access to DHIRES switch else return IO_Null(pc, addr, bWrite, d, nExecutedCycles); break; @@ -716,8 +720,6 @@ BYTE __stdcall IO_Annunciator(WORD programcounter, WORD address, BYTE write, BYT // . $C2B5: LDA $C05D (CLRAN2) ;SETUP // . $C2B8: LDA $C05F (CLRAN3) ; ANNUNCIATORS - // NB. AN3: For //e & //c these locations are now used to enabled/disabled DHIRES - g_Annunciator[(address>>1) & 3] = (address&1) ? true : false; if (address >= 0xC058 && address <= 0xC05B) @@ -1288,9 +1290,6 @@ void MemDestroy() RWpages[0]=NULL; #endif - delete g_pLanguageCard; - g_pLanguageCard = NULL; - memaux = NULL; memmain = NULL; memdirty = NULL; @@ -1490,7 +1489,7 @@ bool MemIsAddrCodeMemory(const USHORT addr) void MemInitialize() { // ALLOCATE MEMORY FOR THE APPLE MEMORY IMAGE AND ASSOCIATED DATA STRUCTURES - memaux = ALIGNED_ALLOC(_6502_MEM_LEN); + memaux = ALIGNED_ALLOC(_6502_MEM_LEN); // NB. alloc even if model is Apple II/II+, since it's used by VidHD card memmain = ALIGNED_ALLOC(_6502_MEM_LEN); memimage = ALIGNED_ALLOC(_6502_MEM_LEN); @@ -1719,18 +1718,22 @@ void MemInitializeIO(void) { InitIoHandlers(); - if (g_pLanguageCard) - g_pLanguageCard->InitializeIO(NULL); - else - RegisterIoHandler(LanguageCardUnit::kSlot0, IO_Null, IO_Null, NULL, NULL, NULL, NULL); - GetCardMgr().InitializeIO(pCxRomPeripheral); } // Called by: // . Snapshot_LoadState_v2() -void MemInitializeCardSlotAndExpansionRomFromSnapshot(void) +void MemInitializeFromSnapshot(void) { + MemInitializeROM(); + MemInitializeCustomROM(); + MemInitializeCustomF8ROM(); + MemInitializeIO(); + + // + // Card and Expansion ROM + // + // Remove all the cards' ROMs at $Csnn if internal ROM is enabled if (IsAppleIIeOrAbove(GetApple2Type()) && SW_INTCXROM) IoHandlerCardsOut(); @@ -1738,13 +1741,27 @@ void MemInitializeCardSlotAndExpansionRomFromSnapshot(void) // Potentially init a card's expansion ROM const UINT uSlot = g_uPeripheralRomSlot; - if (ExpansionRom[uSlot] == NULL) - return; + if (ExpansionRom[uSlot] != NULL) + { + _ASSERT(g_eExpansionRomType == eExpRomPeripheral); + + memcpy(pCxRomPeripheral + 0x800, ExpansionRom[uSlot], FIRMWARE_EXPANSION_SIZE); + // NB. Copied to /mem/ by UpdatePaging(TRUE) + } + + MemUpdatePaging(TRUE); - _ASSERT(g_eExpansionRomType == eExpRomPeripheral); + // + // VidHD + // - memcpy(pCxRomPeripheral+0x800, ExpansionRom[uSlot], FIRMWARE_EXPANSION_SIZE); - // NB. Copied to /mem/ by UpdatePaging(TRUE) + memVidHD = NULL; + + if (IsApple2PlusOrClone(GetApple2Type()) && (GetCardMgr().QuerySlot(SLOT3) == CT_VidHD)) + { + VidHDCard* vidHD = dynamic_cast(GetCardMgr().GetObj(SLOT3)); + memVidHD = vidHD->IsWriteAux() ? memaux : NULL; + } } inline DWORD getRandomTime() @@ -1776,6 +1793,8 @@ void MemReset() memset(memdirty, 0, 0x100); + memVidHD = NULL; + // int iByte; @@ -1974,7 +1993,7 @@ BYTE __stdcall MemSetPaging(WORD programcounter, WORD address, BYTE write, BYTE #endif // DETERMINE THE NEW MEMORY PAGING MODE. - if (!IS_APPLE2) + if (IsAppleIIeOrAbove(GetApple2Type())) { switch (address) { @@ -2007,6 +2026,15 @@ BYTE __stdcall MemSetPaging(WORD programcounter, WORD address, BYTE write, BYTE #endif } } + else // Apple ][,][+,][J-Plus or clone ][,][+ + { + if (GetCardMgr().QuerySlot(SLOT3) == CT_VidHD) + { + VidHDCard* vidHD = dynamic_cast(GetCardMgr().GetObj(SLOT3)); + vidHD->VideoIOWrite(programcounter, address, write, value, nExecutedCycles); + memVidHD = vidHD->IsWriteAux() ? memaux : NULL; + } + } if (IsCopamBase64A(GetApple2Type())) { @@ -2294,7 +2322,7 @@ bool MemLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT unitVersion) // if (!yamlLoadHelper.GetSubMap( MemGetSnapshotMainMemStructName() )) - throw std::string("Card: Expected key: ") + MemGetSnapshotMainMemStructName(); + throw std::runtime_error("Card: Expected key: " + MemGetSnapshotMainMemStructName()); memset(memmain+0xC000, 0, LanguageCardSlot0::kMemBankSize); // Clear it, as high 16K may not be in the save-state's "Main Memory" (eg. the case of II+ Saturn replacing //e LC) @@ -2372,25 +2400,25 @@ static void MemLoadSnapshotAuxCommon(YamlLoadHelper& yamlLoadHelper, const std:: { type = CT_80Col; if (numAuxBanks != 0 || activeAuxBank != 0) - throw std::string(SS_YAML_KEY_UNIT ": AuxSlot: Bad aux slot card state"); + throw std::runtime_error(SS_YAML_KEY_UNIT ": AuxSlot: Bad aux slot card state"); } else if (card == SS_YAML_VALUE_CARD_EXTENDED80COL) { type = CT_Extended80Col; if (numAuxBanks != 1 || activeAuxBank != 0) - throw std::string(SS_YAML_KEY_UNIT ": AuxSlot: Bad aux slot card state"); + throw std::runtime_error(SS_YAML_KEY_UNIT ": AuxSlot: Bad aux slot card state"); } else if (card == SS_YAML_VALUE_CARD_RAMWORKSIII) { type = CT_RamWorksIII; if (numAuxBanks < 2 || numAuxBanks > 0x7F || (activeAuxBank+1) > numAuxBanks) - throw std::string(SS_YAML_KEY_UNIT ": AuxSlot: Bad aux slot card state"); + throw std::runtime_error(SS_YAML_KEY_UNIT ": AuxSlot: Bad aux slot card state"); } else { // todo: support empty slot type = CT_Empty; - throw std::string(SS_YAML_KEY_UNIT ": AuxSlot: Unknown card: " + card); + throw std::runtime_error(SS_YAML_KEY_UNIT ": AuxSlot: Unknown card: " + card); } g_uMaxExPages = numAuxBanks; @@ -2412,14 +2440,13 @@ static void MemLoadSnapshotAuxCommon(YamlLoadHelper& yamlLoadHelper, const std:: std::string auxMemName = MemGetSnapshotAuxMemStructName() + szBank; if (!yamlLoadHelper.GetSubMap(auxMemName)) - throw std::string("Memory: Missing map name: " + auxMemName); + throw std::runtime_error("Memory: Missing map name: " + auxMemName); yamlLoadHelper.LoadMemory(pBank, _6502_MEM_LEN); yamlLoadHelper.PopMap(); } - GetCardMgr().Remove(SLOT0); GetCardMgr().InsertAux(type); memaux = RWpages[g_uActiveBank]; @@ -2438,7 +2465,7 @@ static void MemLoadSnapshotAuxVer2(YamlLoadHelper& yamlLoadHelper) UINT cardVersion = yamlLoadHelper.LoadUint(SS_YAML_KEY_VERSION); if (!yamlLoadHelper.GetSubMap(std::string(SS_YAML_KEY_STATE))) - throw std::string(SS_YAML_KEY_UNIT ": Expected sub-map name: " SS_YAML_KEY_STATE); + throw std::runtime_error(SS_YAML_KEY_UNIT ": Expected sub-map name: " SS_YAML_KEY_STATE); MemLoadSnapshotAuxCommon(yamlLoadHelper, card); @@ -2448,7 +2475,7 @@ static void MemLoadSnapshotAuxVer2(YamlLoadHelper& yamlLoadHelper) bool MemLoadSnapshotAux(YamlLoadHelper& yamlLoadHelper, UINT unitVersion) { if (unitVersion < 1 || unitVersion > kUNIT_AUXSLOT_VER) - throw std::string(SS_YAML_KEY_UNIT ": AuxSlot: Version mismatch"); + throw std::runtime_error(SS_YAML_KEY_UNIT ": AuxSlot: Version mismatch"); if (unitVersion == 1) MemLoadSnapshotAuxVer1(yamlLoadHelper); diff --git a/source/Memory.h b/source/Memory.h index c20a8cf16..b1c1297c7 100644 --- a/source/Memory.h +++ b/source/Memory.h @@ -15,7 +15,7 @@ #define MF_SLOTC3ROM 0x00000100 #define MF_INTCXROM 0x00000200 #define MF_WRITERAM 0x00000400 // Language Card RAM is Write Enabled -#define MF_IOUDIS 0x00000800 // Disable IOU access for addresses $C058 to $C05F; enable access to DHIRES switch (0=on) (Enhanced //e only) +#define MF_IOUDIS 0x00000800 // Disable IOU access for addresses $C058 to $C05F; enable access to DHIRES switch (0=on) (//c only) #define MF_ALTROM0 0x00001000 // Use alternate ROM for $D000 to $FFFF. Two bits for up to 4 pages #define MF_ALTROM1 0x00002000 // Use alternate ROM, second bit to have four pages #define MF_IMAGEMASK 0x000003F7 @@ -25,13 +25,18 @@ enum { // Note: All are in bytes! + TEXT_PAGE1_BEGIN = 0x0400, + APPLE_SLOT_SIZE = 0x0100, // 1 page = $Cx00 .. $CxFF (slot 1 .. 7) + APPLE_IO_BEGIN = 0xC000, APPLE_SLOT_BEGIN = 0xC100, // each slot has 1 page reserved for it APPLE_SLOT_END = 0xC7FF, // FIRMWARE_EXPANSION_SIZE = 0x0800, // 8 pages = $C800 .. $CFFF FIRMWARE_EXPANSION_BEGIN = 0xC800, // [C800,CFFF) - FIRMWARE_EXPANSION_END = 0xCFFF // + FIRMWARE_EXPANSION_END = 0xCFFF, // + + MEMORY_LENGTH = 0x10000 }; enum MemoryInitPattern_e @@ -54,6 +59,7 @@ extern iofunction IOWrite[256]; extern LPBYTE memwrite[0x100]; extern LPBYTE mem; extern LPBYTE memdirty; +extern LPBYTE memVidHD; #ifdef RAMWORKS const UINT kMaxExMemoryBanks = 127; // 127 * aux mem(64K) + main mem(64K) = 8MB @@ -78,7 +84,7 @@ void MemInitializeROM(void); void MemInitializeCustomROM(void); void MemInitializeCustomF8ROM(void); void MemInitializeIO(void); -void MemInitializeCardSlotAndExpansionRomFromSnapshot(void); +void MemInitializeFromSnapshot(void); BYTE MemReadFloatingBus(const ULONG uExecutedCycles); BYTE MemReadFloatingBus(const BYTE highbit, const ULONG uExecutedCycles); void MemReset (); @@ -104,15 +110,11 @@ BYTE __stdcall MemSetPaging(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExec BYTE __stdcall IO_F8xx(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles); -enum SS_CARDTYPE; void SetExpansionMemType(const SS_CARDTYPE type); SS_CARDTYPE GetCurrentExpansionMemType(void); -void CreateLanguageCard(void); void SetRamWorksMemorySize(UINT pages); UINT GetRamWorksActiveBank(void); -void SetSaturnMemorySize(UINT banks); void SetMemMainLanguageCard(LPBYTE ptr, bool bMemMain=false); -class LanguageCardUnit* GetLanguageCard(void); LPBYTE GetCxRomPeripheral(void); diff --git a/source/Mockingboard.cpp b/source/Mockingboard.cpp index d5b08886e..a35d8e1ef 100644 --- a/source/Mockingboard.cpp +++ b/source/Mockingboard.cpp @@ -1930,7 +1930,7 @@ void MB_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, const UINT uSlot) static void LoadSnapshotSY6522(YamlLoadHelper& yamlLoadHelper, SY6522& sy6522, UINT version) { if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_SY6522)) - throw std::string("Card: Expected key: ") + std::string(SS_YAML_KEY_SY6522); + throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_SY6522); sy6522.ORB = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_ORB); sy6522.ORA = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_ORA); @@ -1968,10 +1968,10 @@ static void LoadSnapshotSY6522(YamlLoadHelper& yamlLoadHelper, SY6522& sy6522, U bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) { if (slot != 4 && slot != 5) // fixme - throw std::string("Card: wrong slot"); + throw std::runtime_error("Card: wrong slot"); if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); AY8910UpdateSetCycles(); @@ -1987,7 +1987,7 @@ bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) char szNum[2] = {char('0' + i), 0}; std::string unit = std::string(SS_YAML_KEY_MB_UNIT) + std::string(szNum); if (!yamlLoadHelper.GetSubMap(unit)) - throw std::string("Card: Expected key: ") + std::string(unit); + throw std::runtime_error("Card: Expected key: " + unit); LoadSnapshotSY6522(yamlLoadHelper, pMB->sy6522, version); UpdateIFR(pMB, 0, pMB->sy6522.IFR); // Assert any pending IRQs (GH#677) @@ -2053,7 +2053,7 @@ bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) void Phasor_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, const UINT uSlot) { if (uSlot != 4) - throw std::string("Card: Phasor only supported in slot-4"); + throw std::runtime_error("Card: Phasor only supported in slot-4"); UINT nDeviceNum = 0; SY6522_AY8910* pMB = &g_MB[0]; // fixme: Phasor uses MB's slot4(2x6522), slot4(2xSSI263), but slot4+5(4xAY8910) @@ -2091,10 +2091,10 @@ void Phasor_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, const UINT uSlot) bool Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) { if (slot != 4) // fixme - throw std::string("Card: wrong slot"); + throw std::runtime_error("Card: wrong slot"); if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); if (version < 6) yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR); // Consume redundant data @@ -2123,7 +2123,7 @@ bool Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version char szNum[2] = {char('0' + i), 0}; std::string unit = std::string(SS_YAML_KEY_MB_UNIT) + std::string(szNum); if (!yamlLoadHelper.GetSubMap(unit)) - throw std::string("Card: Expected key: ") + std::string(unit); + throw std::runtime_error("Card: Expected key: " + unit); LoadSnapshotSY6522(yamlLoadHelper, pMB->sy6522, version); UpdateIFR(pMB, 0, pMB->sy6522.IFR); // Assert any pending IRQs (GH#677) diff --git a/source/MouseInterface.cpp b/source/MouseInterface.cpp index 3aeff3736..b902be4ac 100644 --- a/source/MouseInterface.cpp +++ b/source/MouseInterface.cpp @@ -138,6 +138,9 @@ CMouseInterface::CMouseInterface(UINT slot) : m_pSlotRom(NULL), m_syncEvent(slot, 0, SyncEventCallback) // use slot# as "unique" id for MouseInterfaces { + if (m_slot != 4) // fixme + throw std::runtime_error("Card: wrong slot"); + m_6821.SetListenerB( this, M6821_Listener_B ); m_6821.SetListenerA( this, M6821_Listener_A ); @@ -651,7 +654,7 @@ void CMouseInterface::SaveSnapshotMC6821(YamlSaveHelper& yamlSaveHelper, std::st yamlSaveHelper.SaveUint(SS_YAML_KEY_IB, byIB); } -void CMouseInterface::SaveSnapshot(class YamlSaveHelper& yamlSaveHelper) +void CMouseInterface::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) { // if (!m_bActive) // return; @@ -692,7 +695,7 @@ void CMouseInterface::SaveSnapshot(class YamlSaveHelper& yamlSaveHelper) void CMouseInterface::LoadSnapshotMC6821(YamlLoadHelper& yamlLoadHelper, std::string key) { if (!yamlLoadHelper.GetSubMap(key)) - throw std::string("Card: Expected key: ") + key; + throw std::runtime_error("Card: Expected key: " + key); mc6821_t mc6821; mc6821.pra = yamlLoadHelper.LoadUint(SS_YAML_KEY_PRA); @@ -710,13 +713,10 @@ void CMouseInterface::LoadSnapshotMC6821(YamlLoadHelper& yamlLoadHelper, std::st yamlLoadHelper.PopMap(); } -bool CMouseInterface::LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool CMouseInterface::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { - if (slot != 4) // fixme - throw std::string("Card: wrong slot"); - if (version != 1) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); LoadSnapshotMC6821(yamlLoadHelper, SS_YAML_KEY_MC6821); @@ -726,7 +726,7 @@ bool CMouseInterface::LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT sl m_by6821A = yamlLoadHelper.LoadUint(SS_YAML_KEY_6821A); if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_BUFF)) - throw std::string("Card: Expected key: " SS_YAML_KEY_BUFF); + throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_BUFF); yamlLoadHelper.LoadMemory(m_byBuff, sizeof(m_byBuff)); yamlLoadHelper.PopMap(); diff --git a/source/MouseInterface.h b/source/MouseInterface.h index a427efc0f..fef998ae4 100644 --- a/source/MouseInterface.h +++ b/source/MouseInterface.h @@ -44,8 +44,8 @@ class CMouseInterface : public Card } static std::string GetSnapshotCardName(void); - void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); - bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); protected: void InitializeROM(void); diff --git a/source/NTSC.cpp b/source/NTSC.cpp index 0eaccfeba..eda0cc56f 100644 --- a/source/NTSC.cpp +++ b/source/NTSC.cpp @@ -27,6 +27,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Memory.h" // MemGetMainPtr(), MemGetAuxPtr(), MemGetAnnunciator() #include "Interface.h" // GetFrameBuffer() #include "RGBMonitor.h" + #include "VidHD.h" #include "NTSC_CharSet.h" @@ -100,11 +101,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define VIDEO_SCANNER_HORZ_START 25 // first displayable horz scanner index #define VIDEO_SCANNER_Y_MIXED 160 // num scanlins for mixed graphics + text #define VIDEO_SCANNER_Y_DISPLAY 192 // max displayable scanlines + #define VIDEO_SCANNER_Y_DISPLAY_IIGS 200 - // these are initialized in NTSC_VideoInit + // These 3 vars are initialized in NTSC_VideoInit() static bgra_t* g_pVideoAddress = 0; - static bgra_t *g_pScanLines[VIDEO_SCANNER_Y_DISPLAY*2]; // To maintain the 280x192 aspect ratio for 560px width, we double every scan line -> 560x384 - static UINT g_kFrameBufferWidth; + // To maintain the 280x192 aspect ratio for 560px width, we double every scan line -> 560x384 + // NB. For IIgs SHR, the 320x200 is again doubled (to 640x400), but this gives a ~16:9 ratio, when 4:3 is probably required (ie. stretch height from 200 to 240) + static bgra_t* g_pScanLines[VIDEO_SCANNER_Y_DISPLAY_IIGS * 2]; + static UINT g_kFrameBufferWidth = 0; static unsigned short (*g_pHorzClockOffset)[VIDEO_SCANNER_MAX_HORZ] = 0; @@ -348,6 +352,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA static void updateScreenText80RGB ( long cycles6502 ); static void updateScreenDoubleHires80Simplified(long cycles6502); static void updateScreenDoubleHires80RGB(long cycles6502); + static void updateScreenSHR(long cycles6502); //=========================================================================== static void set_csbits() @@ -712,13 +717,37 @@ inline void updateVideoScannerHorzEOL() } } +inline void updateVideoScannerHorzEOL_SHR() +{ + if (VIDEO_SCANNER_MAX_HORZ == ++g_nVideoClockHorz) + { + g_nVideoClockHorz = 0; + + if (++g_nVideoClockVert == g_videoScannerMaxVert) + { + g_nVideoClockVert = 0; + } + + if (g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY_IIGS) + { + updateVideoScannerAddress(); + } + } +} + //=========================================================================== inline void updateVideoScannerAddress() { if (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED && GetVideo().GetVideoRefreshRate() == VR_50HZ) // GH#763 g_nColorBurstPixels = 0; // instantaneously kill color-burst! - g_pVideoAddress = g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY ? g_pScanLines[2*g_nVideoClockVert] : g_pScanLines[0]; + if (g_pFuncUpdateGraphicsScreen == updateScreenSHR) + { + g_pVideoAddress = g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY_IIGS ? g_pScanLines[2 * g_nVideoClockVert] : g_pScanLines[0]; + return; + } + + g_pVideoAddress = g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY ? g_pScanLines[2 * g_nVideoClockVert] : g_pScanLines[0]; // Adjust, as these video styles have 2x 14M pixels of pre-render // NB. For VT_COLOR_MONITOR_NTSC, also check color-burst so that TEXT and MIXED(HGR+TEXT) render the TEXT at the same offset (GH#341) @@ -740,6 +769,9 @@ inline void updateVideoScannerAddress() g_pVideoAddress -= 1; } + // Centre the older //e video modes when running with a VidHD + g_pVideoAddress += GetVideo().GetFrameBufferCentringValue(); + g_nColorPhaseNTSC = INITIAL_COLOR_PHASE; g_nLastColumnPixelNTSC = 0; g_nSignalBitsNTSC = 0; @@ -748,19 +780,22 @@ inline void updateVideoScannerAddress() //=========================================================================== INLINE uint16_t getVideoScannerAddressTXT() { - return (g_aClockVertOffsetsTXT[g_nVideoClockVert/8] + - g_pHorzClockOffset [g_nVideoClockVert/64][g_nVideoClockHorz] + (g_nTextPage * 0x400)); + uint16_t nAddress = (g_aClockVertOffsetsTXT[g_nVideoClockVert/8] + + g_pHorzClockOffset [g_nVideoClockVert/64][g_nVideoClockHorz] + + (g_nTextPage * 0x400)); + return nAddress; } //=========================================================================== INLINE uint16_t getVideoScannerAddressHGR() { // NB. For both A2 and //e use APPLE_IIE_HORZ_CLOCK_OFFSET - see VideoGetScannerAddress() where only TEXT mode adds $1000 - return (g_aClockVertOffsetsHGR[g_nVideoClockVert ] + - APPLE_IIE_HORZ_CLOCK_OFFSET[g_nVideoClockVert/64][g_nVideoClockHorz] + (g_nHiresPage * 0x2000)); + uint16_t nAddress = (g_aClockVertOffsetsHGR[g_nVideoClockVert ] + + APPLE_IIE_HORZ_CLOCK_OFFSET[g_nVideoClockVert/64][g_nVideoClockHorz] + + (g_nHiresPage * 0x2000)); + return nAddress; } - // Non-Inline _________________________________________________________ // Build the 4 phase chroma lookup table @@ -1709,6 +1744,42 @@ void updateScreenText80RGB(long cycles6502) } } +//=========================================================================== +void updateScreenSHR(long cycles6502) +{ + for (; cycles6502 > 0; --cycles6502) + { + // 2 pixels per byte in 320-pixel mode = 160 bytes/scanline + // 4 pixels per byte in 640-pixel mode = 160 bytes/scanline + const UINT kBytesPerScanline = 160; + const UINT kBytesPerCycle = 4; + uint16_t addr = 0x2000 + kBytesPerScanline * g_nVideoClockVert + kBytesPerCycle * (g_nVideoClockHorz - VIDEO_SCANNER_HORZ_START); + + if (g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY_IIGS) + { + if (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_START) + { + uint32_t* pAux = (uint32_t*) MemGetAuxPtr(addr); // 8 pixels (320 mode) / 16 pixels (640 mode) + uint32_t a = pAux[0]; + + uint8_t* pControl = MemGetAuxPtr(0x9D00 + g_nVideoClockVert); // scan-line control byte + uint8_t c = pControl[0]; + + bool is640Mode = c & 0x80; + bool isColorFillMode = c & 0x20; + UINT paletteSelectCode = c & 0xf; + const UINT kColorsPerPalette = 16; + const UINT kColorSize = 2; + uint16_t addrPalette = 0x9E00 + paletteSelectCode * kColorsPerPalette * kColorSize; + + VidHDCard::UpdateSHRCell(is640Mode, isColorFillMode, addrPalette, g_pVideoAddress, a); + g_pVideoAddress += 16; + } + } + updateVideoScannerHorzEOL_SHR(); + } +} + // Functions (Public) _____________________________________________________________________________ //=========================================================================== @@ -1803,6 +1874,19 @@ void NTSC_SetVideoTextMode( int cols ) //=========================================================================== void NTSC_SetVideoMode( uint32_t uVideoModeFlags, bool bDelay/*=false*/ ) { + if (uVideoModeFlags & VF_SHR) + { + g_pFuncUpdateGraphicsScreen = updateScreenSHR; + g_pFuncUpdateTextScreen = updateScreenSHR; + return; + } + + if (g_pFuncUpdateGraphicsScreen == updateScreenSHR && !(uVideoModeFlags & VF_SHR)) + { + // Was SHR mode, so clear the framebuffer to remove any SHR residue in the borders + GetVideo().ClearFrameBuffer(); + } + if (bDelay && !g_bFullSpeed) { // (GH#670) NB. if g_bFullSpeed then NTSC_VideoUpdateCycles() won't be called on the next 6502 opcode. @@ -1812,7 +1896,6 @@ void NTSC_SetVideoMode( uint32_t uVideoModeFlags, bool bDelay/*=false*/ ) return; } - g_nVideoMixed = uVideoModeFlags & VF_MIXED; g_nVideoCharSet = GetVideo().VideoGetSWAltCharSet() ? 1 : 0; @@ -1831,6 +1914,26 @@ void NTSC_SetVideoMode( uint32_t uVideoModeFlags, bool bDelay/*=false*/ ) } } + if( uVideoModeFlags & VF_PAGE0) // Pseudo page ($0000) + { + g_nHiresPage = 0; + } + + if( uVideoModeFlags & VF_PAGE3) // Pseudo page ($6000) + { + g_nHiresPage = 3; + } + + if( uVideoModeFlags & VF_PAGE4) // Pseudo page ($8000) + { + g_nHiresPage = 4; + } + + if( uVideoModeFlags & VF_PAGE5) // Pseudo page ($A000) + { + g_nHiresPage = 5; + } + if (GetVideo().GetVideoRefreshRate() == VR_50HZ && g_pVideoAddress) // GH#763 / NB. g_pVideoAddress==NULL when called via VideoResetState() { if (uVideoModeFlags & VF_TEXT) @@ -2104,7 +2207,7 @@ void NTSC_VideoInit( uint8_t* pFramebuffer ) // wsVideoInit g_kFrameBufferWidth = GetVideo().GetFrameBufferWidth(); - for (int y = 0; y < (VIDEO_SCANNER_Y_DISPLAY*2); y++) + for (int y = 0; y < (VIDEO_SCANNER_Y_DISPLAY_IIGS*2); y++) { uint32_t offset = sizeof(bgra_t) * GetVideo().GetFrameBufferWidth() * ((GetVideo().GetFrameBufferHeight() - 1) - y - GetVideo().GetFrameBufferBorderHeight()) @@ -2325,7 +2428,6 @@ static bool CheckVideoTables2( eApple2Type type, uint32_t mode ) GetVideo().SetVideoMode(mode); - g_dwCyclesThisFrame = 0; g_nVideoClockHorz = g_nVideoClockVert = 0; for (DWORD cycles=0; cycles kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); return true; } diff --git a/source/SAM.h b/source/SAM.h index 2aa713bb5..9fdfdf305 100644 --- a/source/SAM.h +++ b/source/SAM.h @@ -20,8 +20,8 @@ class SAMCard : public Card static BYTE __stdcall IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles); static std::string GetSnapshotCardName(void); - void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); - bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); private: // no state diff --git a/source/SNESMAX.cpp b/source/SNESMAX.cpp index baa0b8282..ede7f12bd 100644 --- a/source/SNESMAX.cpp +++ b/source/SNESMAX.cpp @@ -227,10 +227,10 @@ void SNESMAXCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) yamlSaveHelper.SaveUint(SS_YAML_KEY_BUTTON_INDEX, m_buttonIndex); } -bool SNESMAXCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool SNESMAXCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); m_buttonIndex = yamlLoadHelper.LoadUint(SS_YAML_KEY_BUTTON_INDEX); diff --git a/source/SNESMAX.h b/source/SNESMAX.h index d7e0b49d2..6722d1927 100644 --- a/source/SNESMAX.h +++ b/source/SNESMAX.h @@ -28,8 +28,8 @@ class SNESMAXCard : public Card static BYTE __stdcall IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles); static std::string GetSnapshotCardName(void); - void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); - bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); private: UINT m_buttonIndex; diff --git a/source/SSI263.cpp b/source/SSI263.cpp index d56dd54f1..7859e894c 100644 --- a/source/SSI263.cpp +++ b/source/SSI263.cpp @@ -868,7 +868,7 @@ void SSI263::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) void SSI263::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT device, PHASOR_MODE mode, UINT version) { if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_SSI263)) - throw std::string("Card: Expected key: ") + std::string(SS_YAML_KEY_SSI263); + throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_SSI263); m_durationPhoneme = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_DUR_PHON); m_inflection = yamlLoadHelper.LoadUint(SS_YAML_KEY_SSI263_REG_INF); diff --git a/source/SaveState.cpp b/source/SaveState.cpp index 776dd81ed..9e37f59a6 100644 --- a/source/SaveState.cpp +++ b/source/SaveState.cpp @@ -50,6 +50,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "SNESMAX.h" #include "Speaker.h" #include "Speech.h" +#include "VidHD.h" #include "z80emu.h" #include "Configuration/Config.h" @@ -221,7 +222,7 @@ static eApple2Type ParseApple2Type(std::string type) else if (type == SS_YAML_VALUE_TK30002E) return A2TYPE_TK30002E; else if (type == SS_YAML_VALUE_BASE64A) return A2TYPE_BASE64A; - throw std::string("Load: Unknown Apple2 type"); + throw std::runtime_error("Load: Unknown Apple2 type"); } static std::string GetApple2TypeAsString(void) @@ -240,7 +241,7 @@ static std::string GetApple2TypeAsString(void) case A2TYPE_TK30002E: return SS_YAML_VALUE_TK30002E; case A2TYPE_BASE64A: return SS_YAML_VALUE_BASE64A; default: - throw std::string("Save: Unknown Apple2 type"); + throw std::runtime_error("Save: Unknown Apple2 type"); } } @@ -250,10 +251,10 @@ static UINT ParseFileHdr(void) { std::string scalar; if (!yamlHelper.GetScalar(scalar)) - throw std::string(SS_YAML_KEY_FILEHDR ": Failed to find scalar"); + throw std::runtime_error(SS_YAML_KEY_FILEHDR ": Failed to find scalar"); if (scalar != SS_YAML_KEY_FILEHDR) - throw std::string("Failed to find file header"); + throw std::runtime_error("Failed to find file header"); yamlHelper.GetMapStartEvent(); @@ -265,7 +266,7 @@ static UINT ParseFileHdr(void) if (value != SS_YAML_VALUE_AWSS) { //printf("%s: Bad tag (%s) - expected %s\n", SS_YAML_KEY_FILEHDR, value.c_str(), SS_YAML_VALUE_AWSS); - throw std::string(SS_YAML_KEY_FILEHDR ": Bad tag"); + throw std::runtime_error(SS_YAML_KEY_FILEHDR ": Bad tag"); } return yamlLoadHelper.LoadUint(SS_YAML_KEY_VERSION); @@ -276,7 +277,7 @@ static UINT ParseFileHdr(void) static void ParseUnitApple2(YamlLoadHelper& yamlLoadHelper, UINT version) { if (version == 0 || version > UNIT_APPLE2_VER) - throw std::string(SS_YAML_KEY_UNIT ": Apple2: Version mismatch"); + throw std::runtime_error(SS_YAML_KEY_UNIT ": Apple2: Version mismatch"); std::string model = yamlLoadHelper.LoadString(SS_YAML_KEY_MODEL); SetApple2Type( ParseApple2Type(model) ); // NB. Sets default main CPU type @@ -288,10 +289,6 @@ static void ParseUnitApple2(YamlLoadHelper& yamlLoadHelper, UINT version) SpkrLoadSnapshot(yamlLoadHelper); GetVideo().VideoLoadSnapshot(yamlLoadHelper, version); MemLoadSnapshot(yamlLoadHelper, version); - - // g_Apple2Type may've changed: so redraw frame (title, buttons, leds, etc) - GetVideo().VideoReinitialize(true); // g_CharsetType changed - GetFrame().FrameUpdateApple2Type(); // Calls VideoRedrawScreen() before the aux mem has been loaded (so if DHGR is enabled, then aux mem will be zeros at this stage) } //--- @@ -299,9 +296,7 @@ static void ParseUnitApple2(YamlLoadHelper& yamlLoadHelper, UINT version) static void ParseSlots(YamlLoadHelper& yamlLoadHelper, UINT unitVersion) { if (unitVersion != UNIT_SLOTS_VER) - throw std::string(SS_YAML_KEY_UNIT ": Slots: Version mismatch"); - - bool cardInserted[NUM_SLOTS] = {}; + throw std::runtime_error(SS_YAML_KEY_UNIT ": Slots: Version mismatch"); while (1) { @@ -312,7 +307,7 @@ static void ParseSlots(YamlLoadHelper& yamlLoadHelper, UINT unitVersion) const int slot = strtoul(scalar.c_str(), NULL, 10); // NB. aux slot supported as a different "unit" // NB. slot-0 only supported for Apple II or II+ (or similar clones) if (slot < SLOT0 || slot > SLOT7) - throw std::string("Slots: Invalid slot #: ") + scalar; + throw std::runtime_error("Slots: Invalid slot #: " + scalar); yamlLoadHelper.GetSubMap(scalar); @@ -320,7 +315,7 @@ static void ParseSlots(YamlLoadHelper& yamlLoadHelper, UINT unitVersion) UINT cardVersion = yamlLoadHelper.LoadUint(SS_YAML_KEY_VERSION); if (!yamlLoadHelper.GetSubMap(std::string(SS_YAML_KEY_STATE), true)) // NB. For some cards, State can be null - throw std::string(SS_YAML_KEY_UNIT ": Expected sub-map name: " SS_YAML_KEY_STATE); + throw std::runtime_error(SS_YAML_KEY_UNIT ": Expected sub-map name: " SS_YAML_KEY_STATE); SS_CARDTYPE type = CT_Empty; bool bRes = false; @@ -328,107 +323,83 @@ static void ParseSlots(YamlLoadHelper& yamlLoadHelper, UINT unitVersion) if (card == Printer_GetSnapshotCardName()) { type = CT_GenericPrinter; - GetCardMgr().Insert(slot, type); - bRes = Printer_LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == CSuperSerialCard::GetSnapshotCardName()) { type = CT_SSC; - GetCardMgr().Insert(slot, type); - bRes = dynamic_cast(GetCardMgr().GetRef(slot)).LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == CMouseInterface::GetSnapshotCardName()) { type = CT_MouseInterface; - GetCardMgr().Insert(slot, type); - bRes = dynamic_cast(GetCardMgr().GetRef(slot)).LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == Z80_GetSnapshotCardName()) { type = CT_Z80; - GetCardMgr().Insert(slot, type); - bRes = Z80_LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == MB_GetSnapshotCardName()) { type = CT_MockingboardC; - GetCardMgr().Insert(slot, type); - bRes = MB_LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == Phasor_GetSnapshotCardName()) { type = CT_Phasor; - GetCardMgr().Insert(slot, type); - bRes = Phasor_LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == SAMCard::GetSnapshotCardName()) { type = CT_SAM; - GetCardMgr().Insert(slot, type); - bRes = dynamic_cast(GetCardMgr().GetRef(slot)).LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == Disk2InterfaceCard::GetSnapshotCardName()) { type = CT_Disk2; - GetCardMgr().Insert(slot, type); - bRes = dynamic_cast(GetCardMgr().GetRef(slot)).LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == HarddiskInterfaceCard::GetSnapshotCardName()) { type = CT_GenericHDD; - GetCardMgr().Insert(slot, type); - bRes = dynamic_cast(GetCardMgr().GetRef(slot)).LoadSnapshot(yamlLoadHelper, slot, cardVersion, g_strSaveStatePath); } else if (card == tfe_GetSnapshotCardName()) { type = CT_Uthernet; - GetCardMgr().Insert(slot, type); - tfe_LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == LanguageCardSlot0::GetSnapshotCardName()) { type = CT_LanguageCard; - SetExpansionMemType(type); // calls GetCardMgr().Insert() & InsertAux() - CreateLanguageCard(); - bRes = GetLanguageCard()->LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == Saturn128K::GetSnapshotCardName()) { type = CT_Saturn128K; - SetExpansionMemType(type); // calls GetCardMgr().Insert() & InsertAux() - CreateLanguageCard(); - bRes = GetLanguageCard()->LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == FourPlayCard::GetSnapshotCardName()) { type = CT_FourPlay; - GetCardMgr().Insert(slot, type); - bRes = dynamic_cast(GetCardMgr().GetRef(slot)).LoadSnapshot(yamlLoadHelper, slot, cardVersion); } else if (card == SNESMAXCard::GetSnapshotCardName()) { type = CT_SNESMAX; - GetCardMgr().Insert(slot, type); - bRes = dynamic_cast(GetCardMgr().GetRef(slot)).LoadSnapshot(yamlLoadHelper, slot, cardVersion); + } + else if (card == VidHDCard::GetSnapshotCardName()) + { + type = CT_VidHD; } else { - throw std::string("Slots: Unknown card: " + card); // todo: don't throw - just ignore & continue + throw std::runtime_error("Slots: Unknown card: " + card); // todo: don't throw - just ignore & continue } - cardInserted[slot] = true; + if (slot == 0) + { + SetExpansionMemType(type); // calls GetCardMgr().Insert() & InsertAux() + } + else + { + GetCardMgr().Insert(slot, type); + } + + bRes = GetCardMgr().GetRef(slot).LoadSnapshot(yamlLoadHelper, cardVersion); yamlLoadHelper.PopMap(); yamlLoadHelper.PopMap(); } - // Save-state may not contain any info about empty slots, so ensure they are set to empty - for (UINT slot = SLOT0; slot < NUM_SLOTS; slot++) - { - if (cardInserted[slot]) - continue; - GetCardMgr().Remove(slot); - } } //--- @@ -443,7 +414,7 @@ static void ParseUnit(void) UINT unitVersion = yamlLoadHelper.LoadUint(SS_YAML_KEY_VERSION); if (!yamlLoadHelper.GetSubMap(std::string(SS_YAML_KEY_STATE))) - throw std::string(SS_YAML_KEY_UNIT ": Expected sub-map name: " SS_YAML_KEY_STATE); + throw std::runtime_error(SS_YAML_KEY_UNIT ": Expected sub-map name: " SS_YAML_KEY_STATE); if (unit == GetSnapshotUnitApple2Name()) { @@ -467,7 +438,7 @@ static void ParseUnit(void) } else { - throw std::string(SS_YAML_KEY_UNIT ": Unknown type: " ) + unit; + throw std::runtime_error(SS_YAML_KEY_UNIT ": Unknown type: " + unit); } } @@ -481,10 +452,10 @@ static void Snapshot_LoadState_v2(void) try { if (!yamlHelper.InitParser( g_strSaveStatePathname.c_str() )) - throw std::string("Failed to initialize parser or open file"); + throw std::runtime_error("Failed to initialize parser or open file: " + g_strSaveStatePathname); if (ParseFileHdr() != SS_FILE_VER) - throw std::string("Version mismatch"); + throw std::runtime_error("Version mismatch"); // @@ -500,6 +471,7 @@ static void Snapshot_LoadState_v2(void) GetPravets().Reset(); KeybReset(); + GetVideo().SetVidHD(false); // Set true later only if VidHDCard is instantiated GetVideo().VideoResetState(); GetVideo().SetVideoRefreshRate(VR_60HZ); // Default to 60Hz as older save-states won't contain refresh rate MB_InitializeForLoadingSnapshot(); // GH#609 @@ -513,7 +485,7 @@ static void Snapshot_LoadState_v2(void) if (scalar == SS_YAML_KEY_UNIT) ParseUnit(); else - throw std::string("Unknown top-level scalar: " + scalar); + throw std::runtime_error("Unknown top-level scalar: " + scalar); } MB_SetCumulativeCycles(); @@ -527,22 +499,22 @@ static void Snapshot_LoadState_v2(void) const CConfigNeedingRestart configNew = CConfigNeedingRestart::Create(); GetPropertySheet().ApplyNewConfigFromSnapshot(configNew); // Saves new state to Registry (not slot/cards though) - MemInitializeROM(); - MemInitializeCustomROM(); - MemInitializeCustomF8ROM(); - MemInitializeIO(); - MemInitializeCardSlotAndExpansionRomFromSnapshot(); - - MemUpdatePaging(TRUE); + MemInitializeFromSnapshot(); DebugReset(); if (g_nAppMode == MODE_DEBUG) DebugDisplay(TRUE); + + frame.Initialize(false); // don't reset the video state + frame.ResizeWindow(); + + // g_Apple2Type may've changed: so reload button bitmaps & redraw frame (title, buttons, leds, etc) + frame.FrameUpdateApple2Type(); // NB. Calls VideoRedrawScreen() } - catch(std::string szMessage) + catch(const std::exception & szMessage) { frame.FrameMessageBox( - szMessage.c_str(), + szMessage.what(), TEXT("Load State"), MB_ICONEXCLAMATION | MB_SETFOREGROUND); @@ -603,55 +575,7 @@ void Snapshot_SaveState(void) yamlSaveHelper.UnitHdr(GetSnapshotUnitSlotsName(), UNIT_SLOTS_VER); YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); - if (GetCardMgr().QuerySlot(SLOT0) != CT_Empty && IsApple2PlusOrClone(GetApple2Type())) - GetLanguageCard()->SaveSnapshot(yamlSaveHelper); // Language Card or Saturn 128K - - if (GetCardMgr().QuerySlot(SLOT1) == CT_GenericPrinter) - Printer_SaveSnapshot(yamlSaveHelper); - - if (GetCardMgr().QuerySlot(SLOT2) == CT_SSC) - dynamic_cast(GetCardMgr().GetRef(SLOT2)).SaveSnapshot(yamlSaveHelper); - - if (GetCardMgr().QuerySlot(SLOT3) == CT_Uthernet) - tfe_SaveSnapshot(yamlSaveHelper); - - if (GetCardMgr().QuerySlot(SLOT4) == CT_MouseInterface) - dynamic_cast(GetCardMgr().GetRef(SLOT4)).SaveSnapshot(yamlSaveHelper); - - if (GetCardMgr().QuerySlot(SLOT4) == CT_Z80) - Z80_SaveSnapshot(yamlSaveHelper, SLOT4); - - if (GetCardMgr().QuerySlot(SLOT5) == CT_Z80) - Z80_SaveSnapshot(yamlSaveHelper, SLOT5); - - if (GetCardMgr().QuerySlot(SLOT4) == CT_MockingboardC) - MB_SaveSnapshot(yamlSaveHelper, SLOT4); - - if (GetCardMgr().QuerySlot(SLOT5) == CT_MockingboardC) - MB_SaveSnapshot(yamlSaveHelper, SLOT5); - - if (GetCardMgr().QuerySlot(SLOT4) == CT_Phasor) - Phasor_SaveSnapshot(yamlSaveHelper, SLOT4); - - if (GetCardMgr().QuerySlot(SLOT5) == CT_SAM) - dynamic_cast(GetCardMgr().GetRef(SLOT5)).SaveSnapshot(yamlSaveHelper); - - if (GetCardMgr().QuerySlot(SLOT5) == CT_Disk2) - dynamic_cast(GetCardMgr().GetRef(SLOT5)).SaveSnapshot(yamlSaveHelper); - - if (GetCardMgr().QuerySlot(SLOT6) == CT_Disk2) - dynamic_cast(GetCardMgr().GetRef(SLOT6)).SaveSnapshot(yamlSaveHelper); - - if (GetCardMgr().QuerySlot(SLOT7) == CT_GenericHDD) - dynamic_cast(GetCardMgr().GetRef(SLOT7)).SaveSnapshot(yamlSaveHelper); - - for (UINT slot = SLOT3; slot <= SLOT5; slot++) - { - if (GetCardMgr().QuerySlot(slot) == CT_FourPlay) - dynamic_cast(GetCardMgr().GetRef(slot)).SaveSnapshot(yamlSaveHelper); - else if (GetCardMgr().QuerySlot(slot) == CT_SNESMAX) - dynamic_cast(GetCardMgr().GetRef(slot)).SaveSnapshot(yamlSaveHelper); - } + GetCardMgr().SaveSnapshot(yamlSaveHelper); } // Miscellaneous @@ -663,10 +587,10 @@ void Snapshot_SaveState(void) NoSlotClockSaveSnapshot(yamlSaveHelper); } } - catch(std::string szMessage) + catch(const std::exception & szMessage) { GetFrame().FrameMessageBox( - szMessage.c_str(), + szMessage.what(), TEXT("Save State"), MB_ICONEXCLAMATION | MB_SETFOREGROUND); } diff --git a/source/SerialComms.cpp b/source/SerialComms.cpp index b372ba0d7..ed49f66a2 100644 --- a/source/SerialComms.cpp +++ b/source/SerialComms.cpp @@ -71,6 +71,9 @@ CSuperSerialCard::CSuperSerialCard(UINT slot) : m_bCfgSupportDCD(false), m_pExpansionRom(NULL) { + if (m_slot != 2) // fixme + throw std::runtime_error("Card: wrong slot"); + m_dwSerialPortItem = 0; m_hCommHandle = INVALID_HANDLE_VALUE; @@ -1463,7 +1466,7 @@ void CSuperSerialCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) void CSuperSerialCard::LoadSnapshotDIPSW(YamlLoadHelper& yamlLoadHelper, std::string key, SSC_DIPSW& dipsw) { if (!yamlLoadHelper.GetSubMap(key)) - throw std::string("Card: Expected key: " + key); + throw std::runtime_error("Card: Expected key: " + key); dipsw.uBaudRate = yamlLoadHelper.LoadUint(SS_YAML_KEY_BAUDRATE); dipsw.eFirmwareMode = (eFWMODE) yamlLoadHelper.LoadUint(SS_YAML_KEY_FWMODE); @@ -1476,13 +1479,10 @@ void CSuperSerialCard::LoadSnapshotDIPSW(YamlLoadHelper& yamlLoadHelper, std::st yamlLoadHelper.PopMap(); } -bool CSuperSerialCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool CSuperSerialCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { - if (slot != 2) // fixme - throw std::string("Card: wrong slot"); - if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); LoadSnapshotDIPSW(yamlLoadHelper, SS_YAML_KEY_DIPSWDEFAULT, m_DIPSWDefault); LoadSnapshotDIPSW(yamlLoadHelper, SS_YAML_KEY_DIPSWCURRENT, m_DIPSWCurrent); diff --git a/source/SerialComms.h b/source/SerialComms.h index b53fd55cd..9f83fa671 100644 --- a/source/SerialComms.h +++ b/source/SerialComms.h @@ -37,8 +37,8 @@ class CSuperSerialCard : public Card void CommDestroy(); void CommSetSerialPort(DWORD dwNewSerialPortItem); static std::string GetSnapshotCardName(void); - void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); - bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); char* GetSerialPortChoices(); DWORD GetSerialPort() { return m_dwSerialPortItem; } // Drop-down list item diff --git a/source/StdAfx.h b/source/StdAfx.h index 972d7c6dc..d32863bad 100644 --- a/source/StdAfx.h +++ b/source/StdAfx.h @@ -57,7 +57,7 @@ typedef UINT64 uint64_t; #include #include -#include "linux/win.h" +#include "windows.h" //#define USE_SPEECH_API diff --git a/source/Tfe/tfe.cpp b/source/Tfe/tfe.cpp index 840296e08..95a059478 100644 --- a/source/Tfe/tfe.cpp +++ b/source/Tfe/tfe.cpp @@ -48,8 +48,6 @@ typedef unsigned int UINT; #include "../Registry.h" #include "../YamlHelper.h" -static UINT g_slot = SLOT3; - /**/ /** #define TFE_DEBUG_DUMP 1 **/ @@ -1430,7 +1428,6 @@ return ret; void tfe_InitializeIO(LPBYTE pCxRomPeripheral, UINT slot) { - g_slot = slot; RegisterIoHandler(slot, TfeIo, TfeIo, TfeIoCxxx, TfeIoCxxx, NULL, NULL); } @@ -1481,9 +1478,9 @@ std::string tfe_GetSnapshotCardName(void) return name; } -void tfe_SaveSnapshot(class YamlSaveHelper& yamlSaveHelper) +void tfe_SaveSnapshot(class YamlSaveHelper& yamlSaveHelper, const UINT uSlot) { - YamlSaveHelper::Slot slot(yamlSaveHelper, tfe_GetSnapshotCardName(), g_slot, kUNIT_VERSION); + YamlSaveHelper::Slot slot(yamlSaveHelper, tfe_GetSnapshotCardName(), uSlot, kUNIT_VERSION); YamlSaveHelper::Label unit(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); @@ -1510,10 +1507,10 @@ void tfe_SaveSnapshot(class YamlSaveHelper& yamlSaveHelper) bool tfe_LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) { if (slot != SLOT3) // fixme - throw std::string("Card: wrong slot"); + throw std::runtime_error("Card: wrong slot"); if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); tfe_enabled = yamlLoadHelper.LoadBool(SS_YAML_KEY_ENABLED) ? true : false; set_tfe_interface(yamlLoadHelper.LoadString(SS_YAML_KEY_NETWORK_INTERFACE)); @@ -1525,14 +1522,14 @@ bool tfe_LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT vers rx_buffer = yamlLoadHelper.LoadUint(SS_YAML_KEY_RX_BUFFER); if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_CS8900A_REGS)) - throw std::string("Card: Expected key: ") + SS_YAML_KEY_CS8900A_REGS; + throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_CS8900A_REGS); memset(tfe, 0, TFE_COUNT_IO_REGISTER); yamlLoadHelper.LoadMemory(tfe, TFE_COUNT_IO_REGISTER); yamlLoadHelper.PopMap(); if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_PACKETPAGE_REGS)) - throw std::string("Card: Expected key: ") + SS_YAML_KEY_PACKETPAGE_REGS; + throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_PACKETPAGE_REGS); memset(tfe_packetpage, 0, MAX_PACKETPAGE_ARRAY); yamlLoadHelper.LoadMemory(tfe_packetpage, MAX_PACKETPAGE_ARRAY); diff --git a/source/Tfe/tfe.h b/source/Tfe/tfe.h index 8c478129a..8e6d493e7 100644 --- a/source/Tfe/tfe.h +++ b/source/Tfe/tfe.h @@ -52,7 +52,7 @@ void tfe_InitializeIO(LPBYTE pCxRomPeripheral, UINT slot); void tfe_SetRegistryInterface(UINT slot, const std::string& name); std::string tfe_GetSnapshotCardName(void); -void tfe_SaveSnapshot(class YamlSaveHelper& yamlSaveHelper); +void tfe_SaveSnapshot(class YamlSaveHelper& yamlSaveHelper, const UINT uSlot); bool tfe_LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version); /* diff --git a/source/Utilities.cpp b/source/Utilities.cpp index ff1bcbd26..b9296b2ac 100644 --- a/source/Utilities.cpp +++ b/source/Utilities.cpp @@ -88,8 +88,11 @@ static void LoadConfigOldJoystick_v1(const UINT uJoyNum) JoySetJoyType(uJoyNum, uNewJoyType); } -//Reads configuration from the registry entries -void LoadConfiguration(void) +// Reads configuration from the registry entries +// +// NB. loadImages=false if loading a save-state from cmd-line afterwards +// - Registry images may have been deleted from disk, so avoid the MessageBox +void LoadConfiguration(bool loadImages) { DWORD dwComputerType = 0; eApple2Type apple2Type = A2TYPE_APPLE2EENHANCED; @@ -292,8 +295,8 @@ void LoadConfiguration(void) GetCardMgr().Insert(SLOT4, (SS_CARDTYPE)dwTmp); else if (slot == SLOT5 && REGLOAD(TEXT(REGVALUE_SLOT5), &dwTmp)) GetCardMgr().Insert(SLOT5, (SS_CARDTYPE)dwTmp); - else if (slot == SLOT7 && REGLOAD(TEXT(REGVALUE_HDD_ENABLED), &dwTmp)) - GetCardMgr().Insert(SLOT7, (SS_CARDTYPE)dwTmp); + else if (slot == SLOT7 && REGLOAD(TEXT(REGVALUE_HDD_ENABLED), &dwTmp) && dwTmp == 1) // GH#1015 + GetCardMgr().Insert(SLOT7, CT_GenericHDD); } } @@ -311,7 +314,7 @@ void LoadConfiguration(void) GetCurrentDirectory(sizeof(szFilename), szFilename); SetCurrentImageDir(szFilename); - if (GetCardMgr().QuerySlot(SLOT7) == CT_GenericHDD) + if (loadImages && GetCardMgr().QuerySlot(SLOT7) == CT_GenericHDD) { dynamic_cast(GetCardMgr().GetRef(SLOT7)).LoadLastDiskImage(HARDDISK_1); dynamic_cast(GetCardMgr().GetRef(SLOT7)).LoadLastDiskImage(HARDDISK_2); @@ -325,7 +328,8 @@ void LoadConfiguration(void) GetCurrentDirectory(sizeof(szFilename), szFilename); SetCurrentImageDir(szFilename); - GetCardMgr().GetDisk2CardMgr().LoadLastDiskImage(); + if (loadImages) + GetCardMgr().GetDisk2CardMgr().LoadLastDiskImage(); // Do this after populating the slots with Disk II controller(s) DWORD dwEnhanceDisk; @@ -543,6 +547,8 @@ void ResetMachineState() GetCardMgr().GetDisk2CardMgr().Reset(true); if (GetCardMgr().QuerySlot(SLOT7) == CT_GenericHDD) GetCardMgr().GetRef(SLOT7).Reset(true); + if (GetCardMgr().QuerySlot(SLOT3) == CT_VidHD) + GetCardMgr().GetRef(SLOT3).Reset(true); g_bFullSpeed = 0; // Might've hit reset in middle of InternalCpuExecute() - so beep may get (partially) muted MemReset(); // calls CpuInitialize(), CNoSlotClock.Reset() @@ -597,7 +603,9 @@ void CtrlReset() GetPravets().Reset(); GetCardMgr().GetDisk2CardMgr().Reset(); if (GetCardMgr().QuerySlot(SLOT7) == CT_GenericHDD) - GetCardMgr().GetRef(SLOT7).Reset(true); + GetCardMgr().GetRef(SLOT7).Reset(false); + if (GetCardMgr().QuerySlot(SLOT3) == CT_VidHD) + GetCardMgr().GetRef(SLOT3).Reset(false); KeybReset(); if (GetCardMgr().IsSSCInstalled()) GetCardMgr().GetSSC()->CommReset(); diff --git a/source/Utilities.h b/source/Utilities.h index 14ce50795..460e83664 100644 --- a/source/Utilities.h +++ b/source/Utilities.h @@ -4,7 +4,7 @@ #include "Harddisk.h" -void LoadConfiguration(); +void LoadConfiguration(bool loadImages); void InsertFloppyDisks(const UINT slot, LPCSTR szImageName_drive[NUM_DRIVES], bool driveConnected[NUM_DRIVES], bool& bBoot); void InsertHardDisks(LPCSTR szImageName_harddisk[NUM_HARDDISKS], bool& bBoot); void GetAppleWindowTitle(); diff --git a/source/VidHD.cpp b/source/VidHD.cpp new file mode 100644 index 000000000..1eb8e7582 --- /dev/null +++ b/source/VidHD.cpp @@ -0,0 +1,243 @@ +/* + AppleWin : An Apple //e emulator for Windows + + Copyright (C) 1994-1996, Michael O'Brien + Copyright (C) 1999-2001, Oliver Schmidt + Copyright (C) 2002-2005, Tom Charlesworth + Copyright (C) 2006-2021, Tom Charlesworth, Michael Pohoreski + + AppleWin is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + AppleWin is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with AppleWin; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + Emulate a VidHD card (Blue Shift Inc) + * Partial support only * + + Allows any Apple II to support the IIgs' 320x200 and 640x200 256-colour Super High-Res video modes. + + NB. Not supported yet: + . Apple II/II+ support for //e video modes (TEXT80, DGR, DHGR). + . IIgs SCREEN & BORDER COLOR. + . IIgs B&W DHGR. + . The VidHD extended text modes 80x45, 120x67, 240x135 (and setting FG/BG colours). + . Enable/disable VidHD soft-switch. + + Implementation notes: + . II/II+ + . Mirrors the 80STORE/PAGE2/AUXREAD/AUXWRITE switches to VidHD. + . Reuses 'memaux' that's for the //e models. + . AUXWRITE=1: writes occur to both main & memaux. + . 80STORE=1 && PAGE2=1: same as AUXWRITE=1 (but should be changed to *only* allow writes to aux's TEXT1 & HGR2 areas). + . Only 6502 (not 65C02) emulation supports this dual write to main & memaux (via the 'memVidHD' pointer): + - So a II/II+ with a 65C02 won't correctly support VidHD cards. + - And a //e with a 6502 will incur a slight overhead to test 'memVidHD' pointer (which is always NULL for //e's). + . VidHD card's save-state includes VidHD's aux mem ($400-$9FFF). + . //e with 1KiB 80-Col card: AppleWin doesn't support this - so currently out of scope. +*/ + +#include "StdAfx.h" + +#include "Core.h" +#include "Memory.h" +#include "NTSC.h" +#include "Video.h" +#include "VidHD.h" +#include "YamlHelper.h" + +void VidHDCard::Reset(const bool powerCycle) +{ + m_NEWVIDEO = 0; + GetVideo().SetVideoMode(GetVideo().GetVideoMode() & ~VF_SHR); +} + +void VidHDCard::InitializeIO(LPBYTE pCxRomPeripheral) +{ + RegisterIoHandler(m_slot, IO_Null, IO_Null, &VidHDCard::IORead, IO_Null, this, NULL); +} + +BYTE __stdcall VidHDCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles) +{ + // Return magic bytes (from the VidHD firmware) for VidHD detection + switch (addr & 0xff) + { + case 0: return 0x24; + case 1: return 0xEA; + case 2: return 0x4C; + } + return IO_Null(pc, addr, bWrite, value, nExecutedCycles); +} + +// NB. VidHD has no support for reading the IIgs video registers (from an earlier Apple II) +void VidHDCard::VideoIOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles) +{ + switch (addr & 0xff) + { + case 0x00: m_memMode &= ~MF_80STORE; break; + case 0x01: m_memMode |= MF_80STORE; break; + case 0x02: m_memMode &= ~MF_AUXREAD; break; + case 0x03: m_memMode |= MF_AUXREAD; break; + case 0x04: m_memMode &= ~MF_AUXWRITE; break; + case 0x05: m_memMode |= MF_AUXWRITE; break; + case 0x54: m_memMode &= ~MF_PAGE2; break; + case 0x55: m_memMode |= MF_PAGE2; break; + // IIgs registers + case 0x22: m_SCREENCOLOR = value; break; + case 0x29: m_NEWVIDEO = value; break; + case 0x34: m_BORDERCOLOR = value; break; + case 0x35: m_SHADOW = value; break; + } +} + +bool VidHDCard::IsWriteAux(void) +{ + return (m_memMode & MF_AUXWRITE) || // Write to aux: $200-$BFFF + ((m_memMode & MF_80STORE) && (m_memMode & MF_PAGE2)); // Write to aux: $400-$7FF and $2000-$3FFF +} + +//=========================================================================== + +#pragma pack(push) +#pragma pack(1) // Ensure struct is packed +struct Color +{ + USHORT blue : 4; + USHORT green : 4; + USHORT red : 4; + USHORT reserved : 4; +}; +#pragma pack(pop) + +bgra_t ConvertIIgs2RGB(Color color) +{ + bgra_t rgb = { 0 }; + rgb.r = color.red * 16; + rgb.g = color.green * 16; + rgb.b = color.blue * 16; + rgb.a = ALPHA; + return rgb; +} + +void VidHDCard::UpdateSHRCell(bool is640Mode, bool isColorFillMode, uint16_t addrPalette, bgra_t* pVideoAddress, uint32_t a) +{ + _ASSERT(!is640Mode); // to do: test this mode + + Color* palette = (Color*) MemGetAuxPtr(addrPalette); + + for (UINT i = 0; i < 4; i++) + { + if (!is640Mode) // 320 mode + { + BYTE pixel1 = (a >> 4) & 0xf; + bgra_t color1 = ConvertIIgs2RGB(palette[pixel1]); + if (isColorFillMode && pixel1 == 0) color1 = *(pVideoAddress - 1); + *pVideoAddress++ = color1; + *pVideoAddress++ = color1; + + BYTE pixel2 = a & 0xf; + bgra_t color2 = ConvertIIgs2RGB(palette[pixel2]); + if (isColorFillMode && pixel2 == 0) color2 = color1; + *pVideoAddress++ = color2; + *pVideoAddress++ = color2; + } + else // 640 mode - see IIgs Hardware Ref, Pg.96, Table4-21 'Color Selection in 640 mode' + { + BYTE pixel1 = (a >> 6) & 0x3; + bgra_t color1 = ConvertIIgs2RGB(palette[0x8 + pixel1]); + *pVideoAddress++ = color1; + + BYTE pixel2 = (a >> 4) & 0x3; + bgra_t color2 = ConvertIIgs2RGB(palette[0xC + pixel2]); + *pVideoAddress++ = color2; + + BYTE pixel3 = (a >> 2) & 0x3; + bgra_t color3 = ConvertIIgs2RGB(palette[0x0 + pixel3]); + *pVideoAddress++ = color3; + + BYTE pixel4 = a & 0x3; + bgra_t color4 = ConvertIIgs2RGB(palette[0x4 + pixel4]); + *pVideoAddress++ = color4; + } + + a >>= 8; + } +} + +//=========================================================================== + +static const UINT kUNIT_VERSION = 1; + +#define SS_YAML_KEY_MEMORYMODE "Memory Mode" +#define SS_YAML_KEY_SCREEN_COLOR "Screen Color" +#define SS_YAML_KEY_NEW_VIDEO "New Video" +#define SS_YAML_KEY_BORDER_COLOR "Border Color" +#define SS_YAML_KEY_SHADOW "Shadow" + +std::string VidHDCard::GetSnapshotCardName(void) +{ + static const std::string name("VidHD"); + return name; +} + +static std::string MemGetSnapshotAuxMemStructName(void) +{ + static const std::string name("Auxiliary Memory Bank"); + return name; +} + +void VidHDCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) +{ + YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION); + + YamlSaveHelper::Label unit(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); + yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_MEMORYMODE, m_memMode); + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SCREEN_COLOR, m_SCREENCOLOR); + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_NEW_VIDEO, m_NEWVIDEO); + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_BORDER_COLOR, m_BORDERCOLOR); + yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SHADOW, m_SHADOW); + + if (IsApple2PlusOrClone(GetApple2Type())) // Save aux mem for II/II+ + { + // Save [$400-$9FFF] + YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", MemGetSnapshotAuxMemStructName().c_str()); + + LPBYTE pMemBase = MemGetBankPtr(1); + yamlSaveHelper.SaveMemory(pMemBase, (SHR_MEMORY_END + 1) - TEXT_PAGE1_BEGIN, TEXT_PAGE1_BEGIN); + } +} + +bool VidHDCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) +{ + if (version < 1 || version > kUNIT_VERSION) + throw std::runtime_error("Card: wrong version"); + + m_memMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_MEMORYMODE); + m_SCREENCOLOR = yamlLoadHelper.LoadUint(SS_YAML_KEY_SCREEN_COLOR); + m_NEWVIDEO = yamlLoadHelper.LoadUint(SS_YAML_KEY_NEW_VIDEO); + m_BORDERCOLOR = yamlLoadHelper.LoadUint(SS_YAML_KEY_BORDER_COLOR); + m_SHADOW = yamlLoadHelper.LoadUint(SS_YAML_KEY_SHADOW); + + if (IsApple2PlusOrClone(GetApple2Type())) // Load aux mem for II/II+ + { + // Load [$400-$9FFF] + if (!yamlLoadHelper.GetSubMap(MemGetSnapshotAuxMemStructName())) + throw std::runtime_error("Memory: Missing map name: " + MemGetSnapshotAuxMemStructName()); + + LPBYTE pMemBase = MemGetBankPtr(1); + yamlLoadHelper.LoadMemory(pMemBase, (SHR_MEMORY_END + 1) - TEXT_PAGE1_BEGIN, TEXT_PAGE1_BEGIN); + + yamlLoadHelper.PopMap(); + } + + return true; +} diff --git a/source/VidHD.h b/source/VidHD.h new file mode 100644 index 000000000..733eb2ec4 --- /dev/null +++ b/source/VidHD.h @@ -0,0 +1,48 @@ +#pragma once + +#include "Card.h" +#include "Interface.h" + +class VidHDCard : public Card +{ +public: + VidHDCard(UINT slot) : + Card(CT_VidHD, slot) + { + m_memMode = 0; + m_SCREENCOLOR = 0; + m_NEWVIDEO = 0; + m_BORDERCOLOR = 0; + m_SHADOW = 0; + + GetVideo().SetVidHD(true); + } + virtual ~VidHDCard(void) {} + + virtual void Init(void) {} + virtual void Reset(const bool powerCycle); + virtual void Update(const ULONG nExecutedCycles) {} + virtual void InitializeIO(LPBYTE pCxRomPeripheral); + + static BYTE __stdcall IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles); + + void VideoIOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles); + + bool IsSHR(void) { return (m_NEWVIDEO & 0xC0) == 0xC0; } // 11000000 = Enable SHR(b7) | Linearize SHR video memory(b6) + bool IsDHGRBlackAndWhite(void) { return (m_NEWVIDEO & (1 << 5)) ? true : false; } + bool IsWriteAux(void); + + static void UpdateSHRCell(bool is640Mode, bool isColorFillMode, uint16_t addrPalette, bgra_t* pVideoAddress, uint32_t a); + + static std::string GetSnapshotCardName(void); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); + +private: + static const UINT SHR_MEMORY_END = 0x9FFF; + UINT m_memMode; // Only used by II/II+ + BYTE m_SCREENCOLOR; + BYTE m_NEWVIDEO; + BYTE m_BORDERCOLOR; + BYTE m_SHADOW; +}; diff --git a/source/Video.cpp b/source/Video.cpp index f096cd31e..adebe2c86 100644 --- a/source/Video.cpp +++ b/source/Video.cpp @@ -29,6 +29,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "StdAfx.h" #include "Video.h" +#include "CardManager.h" #include "Core.h" #include "CPU.h" #include "Log.h" @@ -36,6 +37,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Registry.h" #include "NTSC.h" #include "RGBMonitor.h" +#include "VidHD.h" #include "YamlHelper.h" #define SW_80COL (g_uVideoMode & VF_80COL) @@ -45,6 +47,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define SW_MIXED (g_uVideoMode & VF_MIXED) #define SW_PAGE2 (g_uVideoMode & VF_PAGE2) #define SW_TEXT (g_uVideoMode & VF_TEXT) +#define SW_SHR (g_uVideoMode & VF_SHR) //------------------------------------- @@ -91,14 +94,12 @@ const char* const Video::g_apVideoModeDesc[NUM_VIDEO_MODES] = UINT Video::GetFrameBufferBorderlessWidth(void) { - static const UINT uFrameBufferBorderlessW = 560; // 560 = Double Hi-Res - return uFrameBufferBorderlessW; + return HasVidHD() ? kVideoWidthIIgs : kVideoWidthII; } UINT Video::GetFrameBufferBorderlessHeight(void) { - static const UINT uFrameBufferBorderlessH = 384; // 384 = Double Scan Line - return uFrameBufferBorderlessH; + return HasVidHD() ? kVideoHeightIIgs : kVideoHeightII; } // NB. These border areas are not visible (... and these border areas are unrelated to the 3D border below) @@ -124,6 +125,29 @@ UINT Video::GetFrameBufferHeight(void) return GetFrameBufferBorderlessHeight() + 2 * GetFrameBufferBorderHeight(); } +UINT Video::GetFrameBufferCentringOffsetX(void) +{ + return HasVidHD() ? ((kVideoWidthIIgs - kVideoWidthII) / 2) : 0; +} + +UINT Video::GetFrameBufferCentringOffsetY(void) +{ + return HasVidHD() ? ((kVideoHeightIIgs - kVideoHeightII) / 2) : 0; +} + +int Video::GetFrameBufferCentringValue(void) +{ + int value = 0; + + if (HasVidHD()) + { + value -= GetFrameBufferCentringOffsetY() * GetFrameBufferWidth(); + value += GetFrameBufferCentringOffsetX(); + } + + return value; +} + //=========================================================================== void Video::VideoReinitialize(bool bInitVideoScannerAddress) @@ -151,12 +175,15 @@ void Video::VideoResetState(void) //=========================================================================== -BYTE Video::VideoSetMode(WORD, WORD address, BYTE write, BYTE, ULONG uExecutedCycles) +BYTE Video::VideoSetMode(WORD pc, WORD address, BYTE write, BYTE d, ULONG uExecutedCycles) { - address &= 0xFF; - const uint32_t oldVideoMode = g_uVideoMode; + VidHDCard* vidHD = NULL; + if (GetCardMgr().QuerySlot(SLOT3) == CT_VidHD) + vidHD = dynamic_cast(GetCardMgr().GetObj(SLOT3)); + + address &= 0xFF; switch (address) { case 0x00: g_uVideoMode &= ~VF_80STORE; break; @@ -165,6 +192,10 @@ BYTE Video::VideoSetMode(WORD, WORD address, BYTE write, BYTE, ULONG uExecutedCy case 0x0D: if (!IS_APPLE2){g_uVideoMode |= VF_80COL; NTSC_SetVideoTextMode(80);}; break; case 0x0E: if (!IS_APPLE2) g_nAltCharSetOffset = 0; break; // Alternate char set off case 0x0F: if (!IS_APPLE2) g_nAltCharSetOffset = 256; break; // Alternate char set on + case 0x22: if (vidHD) vidHD->VideoIOWrite(pc, address, write, d, uExecutedCycles); break; // VidHD IIgs video mode register + case 0x29: if (vidHD) vidHD->VideoIOWrite(pc, address, write, d, uExecutedCycles); break; // VidHD IIgs video mode register + case 0x34: if (vidHD) vidHD->VideoIOWrite(pc, address, write, d, uExecutedCycles); break; // VidHD IIgs video mode register + case 0x35: if (vidHD) vidHD->VideoIOWrite(pc, address, write, d, uExecutedCycles); break; // VidHD IIgs video mode register case 0x50: g_uVideoMode &= ~VF_TEXT; break; case 0x51: g_uVideoMode |= VF_TEXT; break; case 0x52: g_uVideoMode &= ~VF_MIXED; break; @@ -177,6 +208,11 @@ BYTE Video::VideoSetMode(WORD, WORD address, BYTE write, BYTE, ULONG uExecutedCy case 0x5F: if (!IS_APPLE2) g_uVideoMode &= ~VF_DHIRES; break; } + if (vidHD && vidHD->IsSHR()) + g_uVideoMode |= VF_SHR; + else + g_uVideoMode &= ~VF_SHR; + if (!IS_APPLE2) RGB_SetVideoMode(address); @@ -185,7 +221,7 @@ BYTE Video::VideoSetMode(WORD, WORD address, BYTE write, BYTE, ULONG uExecutedCy if ((oldVideoMode ^ g_uVideoMode) & (VF_TEXT|VF_MIXED)) delay = true; - NTSC_SetVideoMode( g_uVideoMode, delay ); + NTSC_SetVideoMode(g_uVideoMode, delay); return MemReadFloatingBus(uExecutedCycles); } @@ -473,7 +509,7 @@ void Video::Video_SetBitmapHeader(WinBmpHeader_t *pBmp, int nWidth, int nHeight, void Video::Video_MakeScreenShot(FILE *pFile, const VideoScreenShot_e ScreenShotType) { - WinBmpHeader_t *pBmp = &g_tBmpHeader; + WinBmpHeader_t bmp, *pBmp = &bmp; Video_SetBitmapHeader( pBmp, @@ -482,8 +518,13 @@ void Video::Video_MakeScreenShot(FILE *pFile, const VideoScreenShot_e ScreenShot 32 ); - char sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize54[ sizeof( WinBmpHeader_t ) == (14 + 40) ]; +#define EXPECTED_BMP_HEADER_SIZE (14 + 40) +#ifdef _MSC_VER + char sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize54[ sizeof( WinBmpHeader_t ) == EXPECTED_BMP_HEADER_SIZE]; /**/ sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize54[0]=0; +#else + static_assert(sizeof( WinBmpHeader_t ) == EXPECTED_BMP_HEADER_SIZE, "BadWinBmpHeaderPackingSize"); +#endif // Write Header fwrite( pBmp, sizeof( WinBmpHeader_t ), 1, pFile ); @@ -512,7 +553,7 @@ void Video::Video_MakeScreenShot(FILE *pFile, const VideoScreenShot_e ScreenShot { pSrc += GetFrameBufferWidth(); // Start on odd scanline (otherwise for 50% scanline mode get an all black image!) - uint32_t aScanLine[ 280 ]; + uint32_t aScanLine[kVideoWidthIIgs / 2]; // Big enough to contain both a 280 or 320 line uint32_t *pDst; // 50% Half Scan Line clears every odd scanline. @@ -539,6 +580,12 @@ void Video::Video_MakeScreenShot(FILE *pFile, const VideoScreenShot_e ScreenShot pSrc += GetFrameBufferWidth(); } } + + // re-write the Header to include the file size (otherwise "file" does not recognise it) + pBmp->nSizeFile = ftell(pFile); + rewind(pFile); + fwrite( pBmp, sizeof( WinBmpHeader_t ), 1, pFile ); + fseek(pFile, 0, SEEK_END); } //=========================================================================== @@ -754,16 +801,18 @@ const char* Video::VideoGetAppWindowTitle(void) return apVideoMonitorModeDesc[ GetVideoRefreshRate() == VR_60HZ ? 0 : 1 ]; // NTSC or PAL } - -void Video::Initialize(uint8_t* frameBuffer) +void Video::Initialize(uint8_t* frameBuffer, bool resetState) { SetFrameBuffer(frameBuffer); - // RESET THE VIDEO MODE SWITCHES AND THE CHARACTER SET OFFSET - VideoResetState(); + if (resetState) + { + // RESET THE VIDEO MODE SWITCHES AND THE CHARACTER SET OFFSET + VideoResetState(); + } // DRAW THE SOURCE IMAGE INTO THE SOURCE BIT BUFFER - memset(GetFrameBuffer(), 0, GetFrameBufferWidth() * GetFrameBufferHeight() * sizeof(bgra_t)); + ClearFrameBuffer(); // CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE FRAME BUFFER NTSC_VideoInit(GetFrameBuffer()); @@ -796,3 +845,16 @@ void Video::VideoRefreshBuffer(uint32_t uRedrawWholeScreenVideoMode, bool bRedra NTSC_VideoRedrawWholeScreen(); } } + +void Video::ClearFrameBuffer(void) +{ + UINT32* frameBuffer = (UINT32*)GetFrameBuffer(); + std::fill(frameBuffer, frameBuffer + GetFrameBufferWidth() * GetFrameBufferHeight(), OPAQUE_BLACK); +} + +// Called when entering debugger, and after viewing Apple II video screen from debugger +void Video::ClearSHRResidue(void) +{ + ClearFrameBuffer(); + GetFrame().VideoPresentScreen(); +} diff --git a/source/Video.h b/source/Video.h index acde39156..f88ec6c10 100644 --- a/source/Video.h +++ b/source/Video.h @@ -54,8 +54,13 @@ enum VideoFlag_e VF_HIRES = 0x00000004, VF_80STORE= 0x00000008, VF_MIXED = 0x00000010, - VF_PAGE2 = 0x00000020, - VF_TEXT = 0x00000040 + VF_PAGE2 = 0x00000020, // Text or Hires + VF_TEXT = 0x00000040, + VF_SHR = 0x00000080, // For VidHD's support for IIgs SHR video modes + VF_PAGE0 = 0x00000100, // Pseudo Page $00 (Poorman's heatmap) + VF_PAGE3 = 0x00000200, // Pseudo Page $60 (Poorman's heatmap) + VF_PAGE4 = 0x00000400, // Pseudo Page $80 (Poorman's heatmap) + VF_PAGE5 = 0x00000800, // Pseudo Page $A0 (Poorman's heatmap) }; enum AppleFont_e @@ -81,13 +86,9 @@ enum AppleFont_e APPLE_FONT_Y_APPLE_40COL = 512, // ][ }; -#ifdef _MSC_VER - // turn of MSVC struct member padding - #pragma pack(push,1) - #define PACKED -#else - #define PACKED // TODO: FIXME: gcc/clang __attribute__ -#endif + +// turn on struct member padding +#pragma pack(push,1) // TODO: Replace with WinGDI.h / RGBQUAD struct bgra_t @@ -173,10 +174,7 @@ struct WinBmpHeader4_t uint32_t nBlueGamma ; // 0x76 0x04 }; -#ifdef _MSC_VER - #pragma pack(pop) -#endif - +#pragma pack(pop) // class Video @@ -193,9 +191,12 @@ class Video g_nMonochromeRGB = RGB(0xC0,0xC0,0xC0); g_videoRomSize = 0; g_videoRomRockerSwitch = false; + m_hasVidHD = false; } - void Initialize(uint8_t* frameBuffer); // Do not call directly. Call FrameBase::Initialize() + ~Video(void){} + + void Initialize(uint8_t* frameBuffer, bool resetState); // Do not call directly. Call FrameBase::Initialize() void Destroy(void); // Call FrameBase::Destroy() uint8_t* GetFrameBuffer(void) { return g_pFramebufferbits; } @@ -207,6 +208,9 @@ class Video UINT GetFrameBufferBorderHeight(void); UINT GetFrameBufferWidth(void); UINT GetFrameBufferHeight(void); + UINT GetFrameBufferCentringOffsetX(void); + UINT GetFrameBufferCentringOffsetY(void); + int GetFrameBufferCentringValue(void); COLORREF GetMonochromeRGB(void) { return g_nMonochromeRGB; } void SetMonochromeRGB(COLORREF colorRef) { g_nMonochromeRGB = colorRef; } @@ -214,6 +218,8 @@ class Video void VideoReinitialize(bool bInitVideoScannerAddress); void VideoResetState(void); void VideoRefreshBuffer(uint32_t uRedrawWholeScreenVideoMode, bool bRedrawWholeScreen); + void ClearFrameBuffer(void); + void ClearSHRResidue(void); enum VideoScanner_e {VS_FullAddr, VS_PartialAddrV, VS_PartialAddrH}; WORD VideoGetScannerAddress(DWORD nCycles, VideoScanner_e videoScannerAddr = VS_FullAddr); @@ -268,6 +274,9 @@ class Video const char* VideoGetAppWindowTitle(void); const char* GetVideoChoices(void) { return g_aVideoChoices; } + bool HasVidHD(void) { return m_hasVidHD; } + void SetVidHD(bool hasVidHD) { m_hasVidHD = hasVidHD; } + static const UINT kVideoRomSize2K = 1024*2; static const UINT kVideoRomSize4K = kVideoRomSize2K*2; @@ -275,7 +284,6 @@ class Video uint8_t *g_pFramebufferbits; private: - void SetFrameBuffer(uint8_t* frameBuffer) { g_pFramebufferbits = frameBuffer; } std::string VideoGetSnapshotStructName(void); @@ -285,8 +293,7 @@ class Video VideoStyle_e g_eVideoStyle; bool g_bVideoScannerNTSC; // NTSC video scanning (or PAL) COLORREF g_nMonochromeRGB; // saved to Registry - - WinBmpHeader_t g_tBmpHeader; + bool m_hasVidHD; static const int kVDisplayableScanLines = 192; // max displayable scanlines @@ -309,4 +316,10 @@ class Video static const char m_szModeDesc7[]; static const char m_szModeDesc8[]; static const char* const g_apVideoModeDesc[NUM_VIDEO_MODES]; + + static const UINT kVideoHeightII = 192*2; + static const UINT kVideoHeightIIgs = 200*2; + + static const UINT kVideoWidthII = 280*2; + static const UINT kVideoWidthIIgs = 320*2; }; diff --git a/source/Windows/AppleWin.cpp b/source/Windows/AppleWin.cpp index 794e03f09..0caf82968 100644 --- a/source/Windows/AppleWin.cpp +++ b/source/Windows/AppleWin.cpp @@ -42,8 +42,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Registry.h" #include "Riff.h" #include "SaveState.h" +#include "SerialComms.h" #include "SoundCore.h" #include "Speaker.h" +#include "LanguageCard.h" #ifdef USE_SPEECH_API #include "Speech.h" #endif @@ -218,7 +220,6 @@ static void ContinueExecution(void) g_dwCyclesThisFrame += uActualCyclesExecuted; GetCardMgr().Update(uActualCyclesExecuted); - JoyUpdateButtonLatch(nExecutionPeriodUsec); // Button latch time is independent of CPU clock frequency // @@ -569,11 +570,7 @@ int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int) } while (g_bRestart); } - catch(std::runtime_error exception) - { - ExceptionHandler(exception.what()); - } - catch(std::exception exception) + catch(const std::exception & exception) { ExceptionHandler(exception.what()); } @@ -668,13 +665,18 @@ static void OneTimeInitialization(HINSTANCE passinstance) // DO INITIALIZATION THAT MUST BE REPEATED FOR A RESTART static void RepeatInitialization(void) { + GetVideo().SetVidHD(false); // Set true later only if VidHDCard is instantiated ResetToLogoMode(); // NB. g_OldAppleWinVersion needed by LoadConfiguration() -> Config_Load_Video() const bool bShowAboutDlg = CheckOldAppleWinVersion(); // Post: g_OldAppleWinVersion - LoadConfiguration(); - LogFileOutput("Main: LoadConfiguration()\n"); + // Load configuration from Registry + { + bool loadImages = g_cmdLine.szSnapshotName == NULL; // don't load floppy/harddisk images if a snapshot is to be loaded later on + LoadConfiguration(loadImages); + LogFileOutput("Main: LoadConfiguration()\n"); + } if (g_cmdLine.model != A2TYPE_MAX) SetApple2Type(g_cmdLine.model); @@ -709,7 +711,7 @@ static void RepeatInitialization(void) #endif if (g_cmdLine.uSaturnBanks) { - SetSaturnMemorySize(g_cmdLine.uSaturnBanks); // Set number of banks before constructing Saturn card + Saturn128K::SetSaturnMemorySize(g_cmdLine.uSaturnBanks); // Set number of banks before constructing Saturn card SetExpansionMemType(CT_Saturn128K); g_cmdLine.uSaturnBanks = 0; // Don't reapply after a restart } @@ -732,13 +734,6 @@ static void RepeatInitialization(void) JoyInitialize(); LogFileOutput("Main: JoyInitialize()\n"); - GetFrame().Initialize(); // g_pFramebufferinfo been created now & COM init'ed - LogFileOutput("Main: VideoInitialize()\n"); - - LogFileOutput("Main: FrameCreateWindow() - pre\n"); - Win32Frame::GetWin32Frame().FrameCreateWindow(); // GetFrame().g_hFrameWindow is now valid - LogFileOutput("Main: FrameCreateWindow() - post\n"); - // Init palette color VideoSwitchVideocardPalette(RGB_GetVideocard(), GetVideo().GetVideoType()); @@ -754,6 +749,16 @@ static void RepeatInitialization(void) if (g_cmdLine.bSlotEmpty[SLOT6]) GetCardMgr().Remove(SLOT6); + if (g_cmdLine.supportDCD && GetCardMgr().IsSSCInstalled()) + { + GetCardMgr().GetSSC()->SupportDCD(true); + } + + if (g_cmdLine.slotInsert[SLOT3] != CT_Empty && g_cmdLine.slotInsert[SLOT3] == CT_VidHD) // For now just support VidHD in slot 3 + { + GetCardMgr().Insert(SLOT3, g_cmdLine.slotInsert[SLOT3]); + } + if (g_cmdLine.slotInsert[SLOT5] != CT_Empty) { if (GetCardMgr().QuerySlot(SLOT4) == CT_MockingboardC && g_cmdLine.slotInsert[SLOT5] != CT_MockingboardC) // Currently MB occupies slot4+5 when enabled @@ -765,6 +770,35 @@ static void RepeatInitialization(void) GetCardMgr().Insert(SLOT5, g_cmdLine.slotInsert[SLOT5]); } + // Create window after inserting/removing VidHD card (as it affects width & height) + { + Win32Frame::GetWin32Frame().SetViewportScale(Win32Frame::GetWin32Frame().GetViewportScale(), true); + + GetFrame().Initialize(true); // g_pFramebufferinfo been created now & COM init'ed + LogFileOutput("Main: VideoInitialize()\n"); + + LogFileOutput("Main: FrameCreateWindow() - pre\n"); + Win32Frame::GetWin32Frame().FrameCreateWindow(); // GetFrame().g_hFrameWindow is now valid + LogFileOutput("Main: FrameCreateWindow() - post\n"); + } + + // Set best W,H resolution after inserting/removing VidHD card + if (g_cmdLine.bestFullScreenResolution || g_cmdLine.userSpecifiedWidth || g_cmdLine.userSpecifiedHeight) + { + bool res = false; + UINT bestWidth = 0, bestHeight = 0; + + if (g_cmdLine.bestFullScreenResolution) + res = GetFrame().GetBestDisplayResolutionForFullScreen(bestWidth, bestHeight); + else + res = GetFrame().GetBestDisplayResolutionForFullScreen(bestWidth, bestHeight, g_cmdLine.userSpecifiedWidth, g_cmdLine.userSpecifiedHeight); + + if (res) + LogFileOutput("Best resolution for -fs-height/height=x switch(es): Width=%d, Height=%d\n", bestWidth, bestHeight); + else + LogFileOutput("Failed to set parameter for -fs-width/height=x switch(es)\n"); + } + // Pre: may need g_hFrameWindow for MessageBox errors // Post: may enable HDD, required for MemInitialize()->MemInitializeIO() { diff --git a/source/Windows/Win32Frame.cpp b/source/Windows/Win32Frame.cpp index 5c0925234..7e37a06d0 100644 --- a/source/Windows/Win32Frame.cpp +++ b/source/Windows/Win32Frame.cpp @@ -33,16 +33,12 @@ Win32Frame::Win32Frame() g_bFrameActive = false; g_windowMinimized = false; g_bFullScreen_ShowSubunitStatus = true; - g_win_fullscreen_scale = 1; g_win_fullscreen_offsetx = 0; g_win_fullscreen_offsety = 0; m_bestWidthForFullScreen = 0; m_bestHeightForFullScreen = 0; m_changedDisplaySettings = false; - g_nViewportCX = GetVideo().GetFrameBufferBorderlessWidth() * kDEFAULT_VIEWPORT_SCALE; - g_nViewportCY = GetVideo().GetFrameBufferBorderlessHeight() * kDEFAULT_VIEWPORT_SCALE; - g_nViewportScale = kDEFAULT_VIEWPORT_SCALE; // saved REGSAVE g_nMaxViewportScale = kDEFAULT_VIEWPORT_SCALE; // Max scale in Windowed mode with borders, buttons etc (full-screen may be +1) btnfacebrush = (HBRUSH)0; @@ -52,8 +48,6 @@ Win32Frame::Win32Frame() buttonactive = -1; buttondown = -1; buttonover = -1; - buttonx = BUTTONX; // NB. macro uses g_nViewportCX - buttony = BUTTONY; g_hFrameDC = (HDC)0; memset(&framerect, 0, sizeof(framerect)); @@ -76,9 +70,12 @@ Win32Frame::Win32Frame() g_eStatusDrive1 = DISK_STATUS_OFF; g_eStatusDrive2 = DISK_STATUS_OFF; + + // Set g_nViewportScale, g_nViewportCX, g_nViewportCY & buttonx, buttony + SetViewportScale(kDEFAULT_VIEWPORT_SCALE, true); } -void Win32Frame::videoCreateDIBSection(Video & video) +void Win32Frame::VideoCreateDIBSection(bool resetVideoState) { // CREATE THE DEVICE CONTEXT HWND window = GetDesktopWindow(); @@ -91,7 +88,10 @@ void Win32Frame::videoCreateDIBSection(Video & video) // CREATE THE FRAME BUFFER DIB SECTION if (g_hDeviceBitmap) + { DeleteObject(g_hDeviceBitmap); + GetVideo().Destroy(); + } uint8_t* pFramebufferbits; @@ -102,29 +102,33 @@ void Win32Frame::videoCreateDIBSection(Video & video) (LPVOID*)&pFramebufferbits, 0, 0 ); SelectObject(g_hDeviceDC, g_hDeviceBitmap); - video.Initialize(pFramebufferbits); + GetVideo().Initialize(pFramebufferbits, resetVideoState); } -void Win32Frame::Initialize(void) +void Win32Frame::Initialize(bool resetVideoState) { - // LOAD THE LOGO - g_hLogoBitmap = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_APPLEWIN)); + if (g_hLogoBitmap == NULL) + { + // LOAD THE LOGO + g_hLogoBitmap = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_APPLEWIN)); + } + + if (g_pFramebufferinfo) + delete[] g_pFramebufferinfo; // CREATE A BITMAPINFO STRUCTURE FOR THE FRAME BUFFER g_pFramebufferinfo = (LPBITMAPINFO) new BYTE[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)]; - Video & video = GetVideo(); - memset(g_pFramebufferinfo, 0, sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)); g_pFramebufferinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - g_pFramebufferinfo->bmiHeader.biWidth = video.GetFrameBufferWidth(); - g_pFramebufferinfo->bmiHeader.biHeight = video.GetFrameBufferHeight(); + g_pFramebufferinfo->bmiHeader.biWidth = GetVideo().GetFrameBufferWidth(); + g_pFramebufferinfo->bmiHeader.biHeight = GetVideo().GetFrameBufferHeight(); g_pFramebufferinfo->bmiHeader.biPlanes = 1; g_pFramebufferinfo->bmiHeader.biBitCount = 32; g_pFramebufferinfo->bmiHeader.biCompression = BI_RGB; g_pFramebufferinfo->bmiHeader.biClrUsed = 0; - videoCreateDIBSection(video); + VideoCreateDIBSection(resetVideoState); #if 0 DDInit(); // For WaitForVerticalBlank() @@ -297,7 +301,6 @@ void Win32Frame::Benchmark(void) DWORD executedcycles = CpuExecute(103, true); cycles -= executedcycles; GetCardMgr().GetDisk2CardMgr().Update(executedcycles); - JoyUpdateButtonLatch(executedcycles); } } if (cycle & 1) @@ -617,3 +620,9 @@ BYTE* Win32Frame::GetResource(WORD id, LPCSTR lpType, DWORD dwExpectedSize) return pResource; } + +std::string Win32Frame::Video_GetScreenShotFolder() +{ + // save in current folder + return ""; +} diff --git a/source/Windows/Win32Frame.h b/source/Windows/Win32Frame.h index cbce057ed..2708075b0 100644 --- a/source/Windows/Win32Frame.h +++ b/source/Windows/Win32Frame.h @@ -25,7 +25,8 @@ class Video; class Win32Frame : public FrameBase { public: - Win32Frame(); + Win32Frame(void); + virtual ~Win32Frame(void){} static Win32Frame& GetWin32Frame(); static LRESULT CALLBACK FrameWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); @@ -38,21 +39,24 @@ class Win32Frame : public FrameBase virtual void FrameSetCursorPosByMousePos(); virtual void SetFullScreenShowSubunitStatus(bool bShow); - virtual bool GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& bestHeight, UINT userSpecifiedHeight = 0); + virtual bool GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& bestHeight, UINT userSpecifiedWidth=0, UINT userSpecifiedHeight=0); virtual int SetViewportScale(int nNewScale, bool bForce = false); virtual void SetAltEnterToggleFullScreen(bool mode); virtual void SetLoadedSaveStateFlag(const bool bFlag); - virtual void Initialize(void); + virtual void Initialize(bool resetVideoState); virtual void Destroy(void); virtual void VideoPresentScreen(void); + virtual void ResizeWindow(void); virtual int FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType); virtual void GetBitmap(LPCSTR lpBitmapName, LONG cb, LPVOID lpvBits); virtual BYTE* GetResource(WORD id, LPCSTR lpType, DWORD expectedSize); virtual void Restart(); + virtual std::string Video_GetScreenShotFolder(); + bool GetFullScreenShowSubunitStatus(void); int GetFullScreenOffsetX(void); int GetFullScreenOffsetY(void); @@ -64,6 +68,7 @@ class Win32Frame : public FrameBase UINT Get3DBorderHeight(void); int GetViewportScale(void); void GetViewportCXCY(int& nViewportCX, int& nViewportCY); + void SetFullScreenViewportScale(int nNewXScale, int nNewYScale); void ApplyVideoModeChange(void); @@ -76,7 +81,7 @@ class Win32Frame : public FrameBase static BOOL CALLBACK DDEnumProc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext); LRESULT WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - void videoCreateDIBSection(Video& video); + void VideoCreateDIBSection(bool resetVideoState); void VideoDrawLogoBitmap(HDC hDstDC, int xoff, int yoff, int srcw, int srch, int scale); bool DDInit(void); void DDUninit(void); @@ -129,7 +134,6 @@ class Win32Frame : public FrameBase bool g_windowMinimized; std::string driveTooltip; bool g_bFullScreen_ShowSubunitStatus; - FULLSCREEN_SCALE_TYPE g_win_fullscreen_scale; int g_win_fullscreen_offsetx; int g_win_fullscreen_offsety; UINT m_bestWidthForFullScreen; diff --git a/source/Windows/WinFrame.cpp b/source/Windows/WinFrame.cpp index 8c3c2a40a..e78abbea7 100644 --- a/source/Windows/WinFrame.cpp +++ b/source/Windows/WinFrame.cpp @@ -463,11 +463,20 @@ void Win32Frame::DrawFrameWindow (bool bPaintingWindow/*=false*/) DrawButton(dc,iButton); } - if (g_nViewportScale == 2) + if (g_nViewportScale == 2 || GetVideo().HasVidHD()) { - int x = buttonx + 1; - int y = buttony + BUTTONS*BUTTONCY + 36; // 36 = height of StatusArea - RECT rect = {x, y, x+45, y+BUTTONS*BUTTONCY+22}; + const int x = buttonx + 1; + const int y = buttony + BUTTONS * BUTTONCY + 36; // 36 = height of StatusArea + RECT rect = { x, y, x + BUTTONCX, y + BUTTONS * BUTTONCY + 22 }; + + if (GetVideo().HasVidHD()) + { + if (g_nViewportScale == 1) + rect.bottom += 14; + else + rect.bottom += 32; + } + int res = FillRect(dc, &rect, btnfacebrush); } } @@ -1933,6 +1942,7 @@ void Win32Frame::ProcessButtonClick(int button, bool bFromButtonUI /*=false*/) } else // MODE_RUNNING, MODE_LOGO, MODE_PAUSED { + GetVideo().ClearSHRResidue(); // Clear the framebuffer to remove any SHR residue in the borders DebugBegin(); } break; @@ -2149,13 +2159,14 @@ void Win32Frame::SetFullScreenMode(void) scalex = width / GetVideo().GetFrameBufferBorderlessWidth(); scaley = height / GetVideo().GetFrameBufferBorderlessHeight(); - g_win_fullscreen_scale = (scalex <= scaley) ? scalex : scaley; - g_win_fullscreen_offsetx = ((int)width - (int)(g_win_fullscreen_scale * GetVideo().GetFrameBufferBorderlessWidth())) / 2; - g_win_fullscreen_offsety = ((int)height - (int)(g_win_fullscreen_scale * GetVideo().GetFrameBufferBorderlessHeight())) / 2; + // NB. Separate x,y scaling is OK in full-screen mode + // . eg. SHR 640x400 (scalex=2, scaley=3) => 1280x1200, which roughly gives a 4:3 aspect ratio for a resolution of 1600x1200 + g_win_fullscreen_offsetx = ((int)width - (int)(scalex * GetVideo().GetFrameBufferBorderlessWidth())) / 2; + g_win_fullscreen_offsety = ((int)height - (int)(scaley * GetVideo().GetFrameBufferBorderlessHeight())) / 2; SetWindowPos(g_hFrameWindow, NULL, left, top, (int)width, (int)height, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); g_bIsFullScreen = true; - SetViewportScale(g_win_fullscreen_scale, true); + SetFullScreenViewportScale(scalex, scaley); buttonx = GetFullScreenOffsetX() + g_nViewportCX + VIEWPORTX*2; buttony = GetFullScreenOffsetY(); @@ -2187,7 +2198,6 @@ void Win32Frame::SetNormalMode(void) g_win_fullscreen_offsetx = 0; g_win_fullscreen_offsety = 0; - g_win_fullscreen_scale = 1; SetWindowLong(g_hFrameWindow, GWL_STYLE, g_main_window_saved_style); SetWindowLong(g_hFrameWindow, GWL_EXSTYLE, g_main_window_saved_exstyle); SetWindowPos(g_hFrameWindow, NULL, @@ -2249,9 +2259,22 @@ int Win32Frame::SetViewportScale(int nNewScale, bool bForce /*=false*/) g_nViewportCX = g_nViewportScale * GetVideo().GetFrameBufferBorderlessWidth(); g_nViewportCY = g_nViewportScale * GetVideo().GetFrameBufferBorderlessHeight(); + buttonx = BUTTONX; // NB. macro uses g_nViewportCX + buttony = BUTTONY; + return nNewScale; } +void Win32Frame::SetFullScreenViewportScale(int nNewXScale, int nNewYScale) +{ + g_nViewportScale = MIN(nNewXScale, nNewYScale); // Not needed in FS mode + g_nViewportCX = nNewXScale * GetVideo().GetFrameBufferBorderlessWidth(); + g_nViewportCY = nNewYScale * GetVideo().GetFrameBufferBorderlessHeight(); + + buttonx = BUTTONX; // NB. macro uses g_nViewportCX + buttony = BUTTONY; +} + void Win32Frame::SetupTooltipControls(void) { TOOLINFO toolinfo; @@ -2303,6 +2326,12 @@ void Win32Frame::GetWidthHeight(int& nWidth, int& nHeight) #endif } +// Window frame's border size has changed (eg. VidHD added/removed) +void Win32Frame::ResizeWindow(void) +{ + FrameResizeWindow(GetViewportScale()); +} + void Win32Frame::FrameResizeWindow(int nNewScale) { int nOldWidth, nOldHeight; @@ -2314,11 +2343,6 @@ void Win32Frame::FrameResizeWindow(int nNewScale) int nXPos = framerect.left; int nYPos = framerect.top; - // - - buttonx = g_nViewportCX + VIEWPORTX*2; - buttony = 0; - // Invalidate old rect region { RECT irect; @@ -2697,7 +2721,7 @@ void Win32Frame::FrameUpdateApple2Type(void) DrawFrameWindow(); } -bool Win32Frame::GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& bestHeight, UINT userSpecifiedHeight /*= 0*/) +bool Win32Frame::GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& bestHeight, UINT userSpecifiedWidth/*=0*/, UINT userSpecifiedHeight/*=0*/) { m_bestWidthForFullScreen = 0; m_bestHeightForFullScreen = 0; @@ -2734,10 +2758,17 @@ bool Win32Frame::GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& be if (vecDisplayResolutions.size() == 0) return false; - // Pick least width (such that it's wide enough to scale) + // Pick user-specific width if it exists + // Else pick least width (such that it's wide enough to scale) UINT width = (UINT)-1; for (VEC_PAIR::iterator it = vecDisplayResolutions.begin(); it!= vecDisplayResolutions.end(); ++it) { + if (it->first == userSpecifiedWidth) + { + width = userSpecifiedWidth; + break; + } + if (width > it->first) { UINT scaleFactor = it->second / GetVideo().GetFrameBufferBorderlessHeight(); diff --git a/source/YamlHelper.cpp b/source/YamlHelper.cpp index 8bfb1737d..e0f74966d 100644 --- a/source/YamlHelper.cpp +++ b/source/YamlHelper.cpp @@ -26,6 +26,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "YamlHelper.h" #include "Log.h" +#include + int YamlHelper::InitParser(const char* pPathname) { m_hFile = fopen(pPathname, "r"); @@ -61,10 +63,14 @@ void YamlHelper::GetNextEvent(void) yaml_event_delete(&m_newEvent); if (!yaml_parser_parse(&m_parser, &m_newEvent)) { - std::string error = std::string("Save-state parser error: "); - if (m_parser.problem != NULL) error += std::string(m_parser.problem); - else error += std::string("unknown"); - throw error; + std::ostringstream error("Save-state parser error: "); + if (m_parser.problem != NULL) + error << m_parser.problem; + else + error << "unknown"; + error << " @ " << m_parser.problem_mark.line << ":" << m_parser.problem_mark.column; + + throw std::runtime_error(error.str()); } } @@ -109,7 +115,7 @@ void YamlHelper::GetMapStartEvent(void) if (m_newEvent.type != YAML_MAPPING_START_EVENT) { //printf("Unexpected yaml event (%d)\n", m_newEvent.type); - throw std::string("Unexpected yaml event"); + throw std::runtime_error("Unexpected yaml event"); } } @@ -142,7 +148,7 @@ int YamlHelper::ParseMap(MapYaml& mapYaml) mapYaml[pKey] = mapValue; res = ParseMap(*mapValue.subMap); if (!res) - throw std::string("ParseMap: premature end of file during map parsing"); + throw std::runtime_error("ParseMap: premature end of file during map parsing"); bKey = true; // possibly more key,value pairs in this map } break; @@ -168,7 +174,7 @@ int YamlHelper::ParseMap(MapYaml& mapYaml) break; case YAML_SEQUENCE_START_EVENT: case YAML_SEQUENCE_END_EVENT: - throw std::string("ParseMap: Sequence event unsupported"); + throw std::runtime_error("ParseMap: Sequence event unsupported"); } } @@ -192,7 +198,7 @@ std::string YamlHelper::GetMapValue(MapYaml& mapYaml, const std::string& key, bo return value; } -bool YamlHelper::GetSubMap(MapYaml** mapYaml, const std::string& key, const bool canBeNull = false) +bool YamlHelper::GetSubMap(MapYaml** mapYaml, const std::string& key, const bool canBeNull/*=false*/) { MapYaml::const_iterator iter = (*mapYaml)->find(key); if (iter == (*mapYaml)->end() || (!canBeNull && iter->second.subMap == NULL)) @@ -241,7 +247,7 @@ void YamlHelper::MakeAsciiToHexTable(void) m_AsciiToHex[i] = i - 'a' + 0xA; } -UINT YamlHelper::LoadMemory(MapYaml& mapYaml, const LPBYTE pMemBase, const size_t kAddrSpaceSize) +UINT YamlHelper::LoadMemory(MapYaml& mapYaml, const LPBYTE pMemBase, const size_t kAddrSpaceSize, const UINT offset) { UINT bytes = 0; @@ -249,29 +255,29 @@ UINT YamlHelper::LoadMemory(MapYaml& mapYaml, const LPBYTE pMemBase, const size_ { const char* pKey = it->first.c_str(); UINT addr = strtoul(pKey, NULL, 16); - if (addr >= kAddrSpaceSize) - throw std::string("Memory: line address too big: " + it->first); + if (addr >= (kAddrSpaceSize + offset)) + throw std::runtime_error("Memory: line address too big: " + it->first); LPBYTE pDst = (LPBYTE) (pMemBase + addr); - const LPBYTE pDstEnd = (LPBYTE) (pMemBase + kAddrSpaceSize); + const LPBYTE pDstEnd = (LPBYTE) (pMemBase + kAddrSpaceSize + offset); if (it->second.subMap) - throw std::string("Memory: unexpected sub-map"); + throw std::runtime_error("Memory: unexpected sub-map"); const char* pValue = it->second.value.c_str(); size_t len = strlen(pValue); if (len & 1) - throw std::string("Memory: hex data must be an even number of nibbles on line address: " + it->first); + throw std::runtime_error("Memory: hex data must be an even number of nibbles on line address: " + it->first); for (UINT i = 0; i= pDstEnd) - throw std::string("Memory: hex data overflowed address space on line address: " + it->first); + throw std::runtime_error("Memory: hex data overflowed address space on line address: " + it->first); BYTE ah = m_AsciiToHex[ (BYTE)(*pValue++) ]; BYTE al = m_AsciiToHex[ (BYTE)(*pValue++) ]; if ((ah | al) & 0x80) - throw std::string("Memory: hex data contains illegal character on line address: " + it->first); + throw std::runtime_error("Memory: hex data contains illegal character on line address: " + it->first); *pDst++ = (ah<<4) | al; bytes++; @@ -292,7 +298,7 @@ INT YamlLoadHelper::LoadInt(const std::string key) if (value == "") { m_bDoGetMapRemainder = false; - throw std::string(m_currentMapName + ": Missing: " + key); + throw std::runtime_error(m_currentMapName + ": Missing: " + key); } return strtol(value.c_str(), NULL, 0); } @@ -304,7 +310,7 @@ UINT YamlLoadHelper::LoadUint(const std::string key) if (value == "") { m_bDoGetMapRemainder = false; - throw std::string(m_currentMapName + ": Missing: " + key); + throw std::runtime_error(m_currentMapName + ": Missing: " + key); } return strtoul(value.c_str(), NULL, 0); } @@ -316,7 +322,7 @@ UINT64 YamlLoadHelper::LoadUint64(const std::string key) if (value == "") { m_bDoGetMapRemainder = false; - throw std::string(m_currentMapName + ": Missing: " + key); + throw std::runtime_error(m_currentMapName + ": Missing: " + key); } return _strtoui64(value.c_str(), NULL, 0); } @@ -330,7 +336,7 @@ bool YamlLoadHelper::LoadBool(const std::string key) else if (value == "false") return false; m_bDoGetMapRemainder = false; - throw std::string(m_currentMapName + ": Missing: " + key); + throw std::runtime_error(m_currentMapName + ": Missing: " + key); } std::string YamlLoadHelper::LoadString_NoThrow(const std::string& key, bool& bFound) @@ -346,7 +352,7 @@ std::string YamlLoadHelper::LoadString(const std::string& key) if (!bFound) { m_bDoGetMapRemainder = false; - throw std::string(m_currentMapName + ": Missing: " + key); + throw std::runtime_error(m_currentMapName + ": Missing: " + key); } return value; } @@ -358,7 +364,7 @@ float YamlLoadHelper::LoadFloat(const std::string& key) if (value == "") { m_bDoGetMapRemainder = false; - throw std::string(m_currentMapName + ": Missing: " + key); + throw std::runtime_error(m_currentMapName + ": Missing: " + key); } #if (_MSC_VER >= 1900) return strtof(value.c_str(), NULL); // MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015 version 14.0) @@ -374,20 +380,20 @@ double YamlLoadHelper::LoadDouble(const std::string& key) if (value == "") { m_bDoGetMapRemainder = false; - throw std::string(m_currentMapName + ": Missing: " + key); + throw std::runtime_error(m_currentMapName + ": Missing: " + key); } return strtod(value.c_str(), NULL); } -void YamlLoadHelper::LoadMemory(const LPBYTE pMemBase, const size_t size) +void YamlLoadHelper::LoadMemory(const LPBYTE pMemBase, const size_t size, const UINT offset/*=0*/) { - m_yamlHelper.LoadMemory(*m_pMapYaml, pMemBase, size); + m_yamlHelper.LoadMemory(*m_pMapYaml, pMemBase, size, offset); } -void YamlLoadHelper::LoadMemory(std::vector& memory, const size_t size) +void YamlLoadHelper::LoadMemory(std::vector& memory, const size_t size, const UINT offset/*=0*/) { memory.reserve(size); // expand (but don't shrink) vector's capacity (NB. vector's size doesn't change) - const UINT bytes = m_yamlHelper.LoadMemory(*m_pMapYaml, &memory[0], size); + const UINT bytes = m_yamlHelper.LoadMemory(*m_pMapYaml, &memory[0], size, offset); memory.resize(bytes); // resize so that vector contains /bytes/ elements - so that size() gives correct value. } @@ -465,7 +471,7 @@ void YamlSaveHelper::SaveString(const char* key, const char* value) { int size = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, value, -1, NULL, 0); if (size == 0) - throw std::string("Unable to convert to unicode: ") + std::string(value); + throw std::runtime_error("Unable to convert to unicode: " + std::string(value)); if (size > m_wcStrSize) { delete[] m_pWcStr; @@ -474,7 +480,7 @@ void YamlSaveHelper::SaveString(const char* key, const char* value) } int res = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, value, -1, m_pWcStr, m_wcStrSize); if (!res) - throw std::string("Unable to convert to unicode: ") + std::string(value); + throw std::runtime_error("Unable to convert to unicode: " + std::string(value)); } // 2) unicode -> UTF-8 @@ -482,7 +488,7 @@ void YamlSaveHelper::SaveString(const char* key, const char* value) // NB. WC_ERR_INVALID_CHARS only defined when WIN_VER >= 0x600 - but stdafx.h defines it as 0x500 int size = WideCharToMultiByte(CP_UTF8, 0/*WC_ERR_INVALID_CHARS*/, m_pWcStr, -1, NULL, 0, NULL, NULL); if (size == 0) - throw std::string("Unable to convert to UTF-8: ") + std::string(value); + throw std::runtime_error("Unable to convert to UTF-8: " + std::string(value)); if (size > m_mbStrSize) { delete[] m_pMbStr; @@ -491,7 +497,7 @@ void YamlSaveHelper::SaveString(const char* key, const char* value) } int res = WideCharToMultiByte(CP_UTF8, 0/*WC_ERR_INVALID_CHARS*/, m_pWcStr, -1, m_pMbStr, m_mbStrSize, NULL, NULL); if (!res) - throw std::string("Unable to convert to UTF-8: ") + std::string(value); + throw std::runtime_error("Unable to convert to UTF-8: " + std::string(value)); } Save("%s: %s\n", key, m_pMbStr); @@ -513,10 +519,10 @@ void YamlSaveHelper::SaveDouble(const char* key, double value) } // Pre: uMemSize must be multiple of 8 -void YamlSaveHelper::SaveMemory(const LPBYTE pMemBase, const UINT uMemSize) +void YamlSaveHelper::SaveMemory(const LPBYTE pMemBase, const UINT uMemSize, const UINT offset/*=0*/) { if (uMemSize & 7) - throw std::string("Memory: size must be multiple of 8"); + throw std::runtime_error("Memory: size must be multiple of 8"); const UINT kIndent = m_indent; @@ -526,23 +532,23 @@ void YamlSaveHelper::SaveMemory(const LPBYTE pMemBase, const UINT uMemSize) size_t lineSize = kIndent+6+2*kStride+2; // "AAAA: 00010203...3F\n\00" = 6+ 2*64 +2 char* const pLine = new char [lineSize]; - for(DWORD dwOffset = 0x0000; dwOffset < uMemSize; dwOffset+=kStride) + for (DWORD addr = offset; addr < (uMemSize + offset); addr += kStride) { char* pDst = pLine; for (UINT i=0; i>12)&0xf ]; - *pDst++ = szHex[ (dwOffset>>8)&0xf ]; - *pDst++ = szHex[ (dwOffset>>4)&0xf ]; - *pDst++ = szHex[ dwOffset&0xf ]; + *pDst++ = szHex[ (addr>>12)&0xf ]; + *pDst++ = szHex[ (addr>>8)&0xf ]; + *pDst++ = szHex[ (addr>>4)&0xf ]; + *pDst++ = szHex[ addr&0xf ]; *pDst++ = ':'; *pDst++ = ' '; - LPBYTE pMem = pMemBase + dwOffset; + LPBYTE pMem = pMemBase + addr; for (UINT i=0; i= uMemSize) // Support short final line (still multiple of 8 bytes) + if (addr + i >= (uMemSize + offset)) // Support short final line (still multiple of 8 bytes) { lineSize = lineSize - 2*kStride + 2*i; break; diff --git a/source/YamlHelper.h b/source/YamlHelper.h index da18d6088..f577dc4f8 100644 --- a/source/YamlHelper.h +++ b/source/YamlHelper.h @@ -49,8 +49,8 @@ friend class YamlLoadHelper; // YamlLoadHelper can access YamlHelper's private m void GetNextEvent(void); int ParseMap(MapYaml& mapYaml); std::string GetMapValue(MapYaml& mapYaml, const std::string &key, bool& bFound); - UINT LoadMemory(MapYaml& mapYaml, const LPBYTE pMemBase, const size_t kAddrSpaceSize); - bool GetSubMap(MapYaml** mapYaml, const std::string &key, const bool canBeNull /*= false*/); + UINT LoadMemory(MapYaml& mapYaml, const LPBYTE pMemBase, const size_t kAddrSpaceSize, const UINT offset); + bool GetSubMap(MapYaml** mapYaml, const std::string &key, const bool canBeNull=false); void GetMapRemainder(std::string& mapName, MapYaml& mapYaml); void MakeAsciiToHexTable(void); @@ -82,7 +82,7 @@ class YamlLoadHelper if (!m_yamlHelper.ParseMap(yamlHelper.m_mapYaml)) { m_bDoGetMapRemainder = false; - throw std::string(m_currentMapName + ": Failed to parse map"); + throw std::runtime_error(m_currentMapName + ": Failed to parse map"); } } @@ -100,8 +100,8 @@ class YamlLoadHelper std::string LoadString(const std::string& key); float LoadFloat(const std::string & key); double LoadDouble(const std::string & key); - void LoadMemory(const LPBYTE pMemBase, const size_t size); - void LoadMemory(std::vector& memory, const size_t size); + void LoadMemory(const LPBYTE pMemBase, const size_t size, const UINT offset=0); + void LoadMemory(std::vector& memory, const size_t size, const UINT offset=0); bool GetSubMap(const std::string & key, const bool canBeNull=false) { @@ -184,7 +184,7 @@ class YamlSaveHelper // - at this point any old file will have been truncated to zero if(m_hFile == NULL) - throw std::string("Save error"); + throw std::runtime_error("Save error"); _tzset(); time_t ltime; @@ -228,7 +228,7 @@ class YamlSaveHelper void SaveString(const char* key, const std::string & value); void SaveFloat(const char* key, float value); void SaveDouble(const char* key, double value); - void SaveMemory(const LPBYTE pMemBase, const UINT uMemSize); + void SaveMemory(const LPBYTE pMemBase, const UINT uMemSize, const UINT offset=0); class Label { diff --git a/source/Z80VICE/z80.cpp b/source/Z80VICE/z80.cpp index e058c3a51..62026d743 100644 --- a/source/Z80VICE/z80.cpp +++ b/source/Z80VICE/z80.cpp @@ -6525,10 +6525,10 @@ void Z80_SaveSnapshot(class YamlSaveHelper& yamlSaveHelper, const UINT uSlot) bool Z80_LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT uSlot, UINT version) { if (uSlot != 4 && uSlot != 5) // fixme - throw std::string("Card: wrong slot"); + throw std::runtime_error("Card: wrong slot"); if (version != 1) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); reg_a = yamlLoadHelper.LoadUint(SS_YAML_KEY_REGA); reg_b = yamlLoadHelper.LoadUint(SS_YAML_KEY_REGB); diff --git a/source/frontends/common2/CMakeLists.txt b/source/frontends/common2/CMakeLists.txt index f55bd1653..49869f975 100644 --- a/source/frontends/common2/CMakeLists.txt +++ b/source/frontends/common2/CMakeLists.txt @@ -2,6 +2,7 @@ include(GNUInstallDirs) set(SOURCE_FILES commonframe.cpp + gnuframe.cpp fileregistry.cpp ptreeregistry.cpp programoptions.cpp @@ -12,6 +13,7 @@ set(SOURCE_FILES set(HEADER_FILES commonframe.h + gnuframe.h fileregistry.h ptreeregistry.h programoptions.h @@ -37,6 +39,7 @@ target_include_directories(common2 PRIVATE target_link_libraries(common2 PRIVATE Boost::program_options appleii + windows ) file(RELATIVE_PATH ROOT_PATH ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}) diff --git a/source/frontends/common2/commonframe.cpp b/source/frontends/common2/commonframe.cpp index 3917a7246..92a6dc59b 100644 --- a/source/frontends/common2/commonframe.cpp +++ b/source/frontends/common2/commonframe.cpp @@ -6,94 +6,16 @@ #include #include #include -#include #include "Log.h" -#include "Core.h" -#include "config.h" - -namespace -{ - - bool dirExists(const std::string & folder) - { - struct stat stdbuf; - - if (stat(folder.c_str(), &stdbuf) == 0 && S_ISDIR(stdbuf.st_mode)) - { - return true; - } - else - { - return false; - } - } - - std::string getResourcePath(const std::string & target) - { - std::vector paths; - - char self[1024] = {0}; - const int ch = readlink("/proc/self/exe", self, sizeof(self)); - if (ch != -1) - { - const char * path = dirname(self); - - // case 1: run from the build folder - paths.emplace_back(std::string(path) + '/'+ ROOT_PATH); - // case 2: run from the installation folder - paths.emplace_back(std::string(path) + '/'+ SHARE_PATH); - } - - // case 3: use the source folder - paths.emplace_back(CMAKE_SOURCE_DIR); - - for (const std::string & path : paths) - { - char * real = realpath(path.c_str(), nullptr); - if (real) - { - const std::string resourcePath = std::string(real) + target; - free(real); - if (dirExists(resourcePath)) - { - return resourcePath; - } - } - } - - throw std::runtime_error("Cannot found the resource path: " + target); - } - -} +#include "SaveState.h" namespace common2 { - CommonFrame::CommonFrame() - : myResourcePath(getResourcePath("/resource/")) - { - // should this go down to LinuxFrame (maybe Initialisation?) - g_sProgramDir = getResourcePath("/bin/"); - } - - void CommonFrame::Initialize() - { - InitialiseEmulator(); - LinuxFrame::Initialize(); - } - - void CommonFrame::Destroy() - { - LinuxFrame::Destroy(); - myResource.clear(); - DestroyEmulator(); - } - - void CommonFrame::Restart() + void CommonFrame::LoadSnapshot() { - Destroy(); - Initialize(); + Snapshot_LoadState(); } BYTE* CommonFrame::GetResource(WORD id, LPCSTR lpType, DWORD expectedSize) @@ -101,7 +23,7 @@ namespace common2 myResource.clear(); const std::string & filename = getResourceName(id); - const std::string path = myResourcePath + filename; + const std::string path = getResourcePath(filename); const int fd = open(path.c_str(), O_RDONLY); diff --git a/source/frontends/common2/commonframe.h b/source/frontends/common2/commonframe.h index 102391df7..d52bfeab4 100644 --- a/source/frontends/common2/commonframe.h +++ b/source/frontends/common2/commonframe.h @@ -10,18 +10,14 @@ namespace common2 class CommonFrame : public LinuxFrame { public: - CommonFrame(); - - void Initialize() override; - void Destroy() override; - void Restart() override; - BYTE* GetResource(WORD id, LPCSTR lpType, DWORD expectedSize) override; + virtual void LoadSnapshot(); protected: + virtual std::string getResourcePath(const std::string & filename) = 0; + static std::string getBitmapFilename(const std::string & resource); - const std::string myResourcePath; std::vector myResource; }; diff --git a/source/frontends/common2/fileregistry.cpp b/source/frontends/common2/fileregistry.cpp index 6a70fbb03..9bae51a1c 100644 --- a/source/frontends/common2/fileregistry.cpp +++ b/source/frontends/common2/fileregistry.cpp @@ -5,7 +5,7 @@ #include "frontends/common2/programoptions.h" #include "Log.h" -#include "linux/windows/files.h" +#include "windows.h" #include "frontends/qt/applicationname.h" #include @@ -28,17 +28,6 @@ namespace std::replace(value.begin(), value.end(), '_', ' '); } - std::string getHomeDir() - { - const char* homeDir = getenv("HOME"); - if (!homeDir) - { - throw std::runtime_error("${HOME} not set, cannot locate configuration file"); - } - - return std::string(homeDir); - } - class Configuration : public common2::PTreeRegistry { public: @@ -94,9 +83,20 @@ namespace namespace common2 { + std::string GetHomeDir() + { + const char* homeDir = getenv("HOME"); + if (!homeDir) + { + throw std::runtime_error("${HOME} not set, cannot locate configuration file"); + } + + return std::string(homeDir); + } + std::string GetConfigFile(const std::string & filename) { - const std::string dir = getHomeDir() + "/.applewin"; + const std::string dir = GetHomeDir() + "/.applewin"; const int status = mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); if (!status || (errno == EEXIST)) { @@ -112,7 +112,7 @@ namespace common2 std::shared_ptr CreateFileRegistry(const EmulatorOptions & options) { - const std::string homeDir = getHomeDir(); + const std::string homeDir = GetHomeDir(); std::string filename; bool saveOnExit; diff --git a/source/frontends/common2/fileregistry.h b/source/frontends/common2/fileregistry.h index 4fc73bde2..04ad1ab1e 100644 --- a/source/frontends/common2/fileregistry.h +++ b/source/frontends/common2/fileregistry.h @@ -12,5 +12,6 @@ namespace common2 std::string GetConfigFile(const std::string & filename); std::shared_ptr CreateFileRegistry(const EmulatorOptions & options); + std::string GetHomeDir(); } diff --git a/source/frontends/common2/gnuframe.cpp b/source/frontends/common2/gnuframe.cpp new file mode 100644 index 000000000..dead08364 --- /dev/null +++ b/source/frontends/common2/gnuframe.cpp @@ -0,0 +1,99 @@ +#include "StdAfx.h" +#include "frontends/common2/gnuframe.h" +#include "frontends/common2/fileregistry.h" + +#include +#include +#include + +#ifdef __APPLE__ +#include "mach-o/dyld.h" +#endif + +#include "Core.h" +#include "config.h" + +namespace +{ + + bool dirExists(const std::string & folder) + { + struct stat stdbuf; + + if (stat(folder.c_str(), &stdbuf) == 0 && S_ISDIR(stdbuf.st_mode)) + { + return true; + } + else + { + return false; + } + } + + std::string getResourceFolder(const std::string & target) + { + std::vector paths; + + char self[1024] = {0}; + +#ifdef __APPLE__ + uint32_t size = sizeof(self); + const int ch = _NSGetExecutablePath(self, &size); +#else + const int ch = readlink("/proc/self/exe", self, sizeof(self)); +#endif + + if (ch != -1) + { + const char * path = dirname(self); + + // case 1: run from the build folder + paths.emplace_back(std::string(path) + '/'+ ROOT_PATH); + // case 2: run from the installation folder + paths.emplace_back(std::string(path) + '/'+ SHARE_PATH); + } + + // case 3: use the source folder + paths.emplace_back(CMAKE_SOURCE_DIR); + + for (const std::string & path : paths) + { + char * real = realpath(path.c_str(), nullptr); + if (real) + { + const std::string resourcePath = std::string(real) + target; + free(real); + if (dirExists(resourcePath)) + { + return resourcePath; + } + } + } + + throw std::runtime_error("Cannot found the resource path: " + target); + } + +} + +namespace common2 +{ + + GNUFrame::GNUFrame() + : myHomeDir(GetHomeDir()) + , myResourceFolder(getResourceFolder("/resource/")) + { + // should this go down to LinuxFrame (maybe Initialisation?) + g_sProgramDir = getResourceFolder("/bin/"); + } + + std::string GNUFrame::getResourcePath(const std::string & filename) + { + return myResourceFolder + filename; + } + + std::string GNUFrame::Video_GetScreenShotFolder() + { + return myHomeDir + "/Pictures/"; + } + +} diff --git a/source/frontends/common2/gnuframe.h b/source/frontends/common2/gnuframe.h new file mode 100644 index 000000000..3d64640a7 --- /dev/null +++ b/source/frontends/common2/gnuframe.h @@ -0,0 +1,22 @@ +#pragma once + +#include "frontends/common2/commonframe.h" +#include + +namespace common2 +{ + + class GNUFrame : public virtual CommonFrame + { + public: + GNUFrame(); + + std::string Video_GetScreenShotFolder() override; + std::string getResourcePath(const std::string & filename) override; + + private: + const std::string myHomeDir; + const std::string myResourceFolder; + }; + +} diff --git a/source/frontends/common2/programoptions.cpp b/source/frontends/common2/programoptions.cpp index 90c4ab4bd..84184ae6c 100644 --- a/source/frontends/common2/programoptions.cpp +++ b/source/frontends/common2/programoptions.cpp @@ -1,5 +1,4 @@ #include "frontends/common2/programoptions.h" -#include "frontends/common2/utils.h" #include "frontends/common2/fileregistry.h" #include "linux/version.h" #include "linux/paddle.h" @@ -227,11 +226,6 @@ namespace common2 bool bBoot; InsertFloppyDisks(SLOT6, szImageName_drive, driveConnected, bBoot); - if (!options.snapshotFilename.empty()) - { - setSnapshotFilename(options.snapshotFilename, options.loadSnapshot); - } - if (!options.customRom.empty()) { CloseHandle(g_hCustomRom); diff --git a/source/frontends/common2/utils.cpp b/source/frontends/common2/utils.cpp index 7c7d62c4b..f5dc167dc 100644 --- a/source/frontends/common2/utils.cpp +++ b/source/frontends/common2/utils.cpp @@ -2,34 +2,8 @@ #include "frontends/common2/utils.h" #include "frontends/common2/programoptions.h" -#include "linux/network/uthernet2.h" - -#include "SaveState.h" - -#include "Common.h" -#include "CardManager.h" -#include "Core.h" -#include "Disk.h" -#include "Mockingboard.h" -#include "SoundCore.h" -#include "Harddisk.h" -#include "Speaker.h" -#include "Log.h" -#include "CPU.h" -#include "Memory.h" -#include "LanguageCard.h" -#include "MouseInterface.h" -#include "ParallelPrinter.h" -#include "Video.h" -#include "NTSC.h" #include "SaveState.h" -#include "RGBMonitor.h" -#include "Riff.h" #include "Registry.h" -#include "Utilities.h" -#include "Interface.h" -#include "Debugger/Debug.h" -#include "Tfe/tfe.h" #include #include @@ -37,115 +11,29 @@ namespace common2 { - void setSnapshotFilename(const std::string & filename, const bool load) + void setSnapshotFilename(const std::string & filename) { - // same logic as qapple - // setting chdir allows to load relative disks from the snapshot file (tests?) - // but if the snapshot file itself is relative, it wont work after a chdir - // so we convert to absolute first - char * absPath = realpath(filename.c_str(), nullptr); - if (absPath) + if (!filename.empty()) { - char * temp = strdup(absPath); - const char * dir = dirname(temp); - // dir points inside temp! - chdir(dir); - Snapshot_SetFilename(absPath); - - free(temp); - free(absPath); - - if (load) + // same logic as qapple + // setting chdir allows to load relative disks from the snapshot file (tests?) + // but if the snapshot file itself is relative, it wont work after a chdir + // so we convert to absolute first + char * absPath = realpath(filename.c_str(), nullptr); + if (absPath) { - Snapshot_LoadState(); + char * temp = strdup(absPath); + const char * dir = dirname(temp); + // dir points inside temp! + chdir(dir); + Snapshot_SetFilename(absPath); + + free(temp); + free(absPath); } } } - void LoadUthernet2() - { - CardManager & cardManager = GetCardMgr(); - for (UINT slot = SLOT1; slot < NUM_SLOTS; slot++) - { - if (cardManager.QuerySlot(slot) == CT_Uthernet2) - { - // AppleWin does not know anything about this - registerUthernet2(slot); - break; - } - } - } - - void InitialiseEmulator() - { -#ifdef RIFF_SPKR - RiffInitWriteFile("/tmp/Spkr.wav", SPKR_SAMPLE_RATE, 1); -#endif -#ifdef RIFF_MB - RiffInitWriteFile("/tmp/Mockingboard.wav", 44100, 2); -#endif - - g_nAppMode = MODE_RUNNING; - LogFileOutput("Initialisation\n"); - - g_bFullSpeed = false; - - LoadConfiguration(); - LoadUthernet2(); - SetCurrentCLK6502(); - GetAppleWindowTitle(); - GetFrame().FrameRefreshStatus(DRAW_LEDS | DRAW_BUTTON_DRIVES | DRAW_DISK_STATUS); - - DSInit(); - MB_Initialize(); - SpkrInitialize(); - - MemInitialize(); - - CardManager & cardManager = GetCardMgr(); - cardManager.GetDisk2CardMgr().Reset(); - if (cardManager.QuerySlot(SLOT7) == CT_GenericHDD) - { - dynamic_cast(cardManager.GetRef(SLOT7)).Reset(true); - } - - Snapshot_Startup(); - - DebugInitialize(); - } - - void DestroyEmulator() - { - CardManager & cardManager = GetCardMgr(); - - Snapshot_Shutdown(); - CMouseInterface* pMouseCard = cardManager.GetMouseCard(); - if (pMouseCard) - { - pMouseCard->Reset(); - } - MemDestroy(); - - SpkrDestroy(); - MB_Destroy(); - DSUninit(); - - unRegisterUthernet2(); - tfe_shutdown(); - - if (cardManager.QuerySlot(SLOT7) == CT_GenericHDD) - { - dynamic_cast(cardManager.GetRef(SLOT7)).Destroy(); - } - - PrintDestroy(); - CpuDestroy(); - DebugDestroy(); - - GetCardMgr().GetDisk2CardMgr().Destroy(); - RiffFinishWriteFile(); - } - void loadGeometryFromRegistry(const std::string §ion, Geometry & geometry) { if (geometry.empty) // otherwise it was user provided diff --git a/source/frontends/common2/utils.h b/source/frontends/common2/utils.h index 5986d9d9d..193e921d2 100644 --- a/source/frontends/common2/utils.h +++ b/source/frontends/common2/utils.h @@ -6,11 +6,7 @@ namespace common2 { struct Geometry; - void setSnapshotFilename(const std::string & filename, const bool load); - - // Do not call directly. Used in CommonFrame - void InitialiseEmulator(); - void DestroyEmulator(); + void setSnapshotFilename(const std::string & filename); void loadGeometryFromRegistry(const std::string §ion, Geometry & geometry); void saveGeometryToRegistry(const std::string §ion, const Geometry & geometry); diff --git a/source/frontends/libretro/CMakeLists.txt b/source/frontends/libretro/CMakeLists.txt index 165879f42..77c48a9fc 100644 --- a/source/frontends/libretro/CMakeLists.txt +++ b/source/frontends/libretro/CMakeLists.txt @@ -1,52 +1,51 @@ -# there is no simple package for libretro-common -# this requires user input -set(LIBRETRO_COMMON_PATH NONE CACHE PATH "path to libretro-common") - -if (EXISTS ${LIBRETRO_COMMON_PATH}/include/libretro.h) - message("Using LIBRETRO_COMMON_PATH=${LIBRETRO_COMMON_PATH}") - - set(SOURCE_FILES - environment.cpp - libretro.cpp - rdirectsound.cpp - game.cpp - joypadbase.cpp - joypad.cpp - analog.cpp - rdirectsound.cpp - retroregistry.cpp - retroframe.cpp - ) - - set(HEADER_FILES - environment.h - rdirectsound.h - game.h - joypadbase.h - joypad.h - analog.h - rdirectsound.h - retroregistry.h - retroframe.h - ) - - add_library(applewin_libretro SHARED - ${SOURCE_FILES} - ${HEADER_FILES} - ) - - target_include_directories(applewin_libretro PRIVATE - ${LIBRETRO_COMMON_PATH}/include - ) - - target_link_libraries(applewin_libretro PRIVATE - appleii - common2 - ) - - # just call it "applewin_libretro.so" as per libretro standard - set_target_properties(applewin_libretro PROPERTIES PREFIX "") - -else() - message(WARNING "Bad LIBRETRO_COMMON_PATH=${LIBRETRO_COMMON_PATH}, skipping 'libretro' core") -endif() +set(SOURCE_FILES + environment.cpp + libretro.cpp + rdirectsound.cpp + game.cpp + joypadbase.cpp + joypad.cpp + mouse.cpp + analog.cpp + rdirectsound.cpp + retroregistry.cpp + retroframe.cpp + diskcontrol.cpp + serialisation.cpp + ) + +set(HEADER_FILES + libretro-common/include/libretro.h + environment.h + rdirectsound.h + game.h + joypadbase.h + joypad.h + mouse.h + analog.h + rdirectsound.h + retroregistry.h + retroframe.h + diskcontrol.h + serialisation.h + ) + +add_library(applewin_libretro SHARED + ${SOURCE_FILES} + ${HEADER_FILES} + ) + +target_compile_features(applewin_libretro PUBLIC cxx_std_17) + +target_include_directories(applewin_libretro PRIVATE + libretro-common/include + ) + +target_link_libraries(applewin_libretro PRIVATE + appleii + common2 + windows + ) + +# just call it "applewin_libretro.so" as per libretro standard +set_target_properties(applewin_libretro PROPERTIES PREFIX "") diff --git a/source/frontends/libretro/analog.cpp b/source/frontends/libretro/analog.cpp index dc5d0863f..a22564756 100644 --- a/source/frontends/libretro/analog.cpp +++ b/source/frontends/libretro/analog.cpp @@ -9,8 +9,9 @@ namespace ra2 { - Analog::Analog() - : myAxisCodes(2) + Analog::Analog(unsigned device) + : JoypadBase(device) + , myAxisCodes(2) { myAxisCodes[0] = std::make_pair(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X); myAxisCodes[1] = std::make_pair(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y); @@ -19,7 +20,7 @@ namespace ra2 double Analog::getAxis(int i) const { const auto & code = myAxisCodes[i]; - const int value = input_state_cb(0, RETRO_DEVICE_ANALOG, code.first, code.second); + const int value = input_state_cb(0, myDevice, code.first, code.second); const double axis = 2.0 * double(value - AXIS_MIN) / double(AXIS_MAX - AXIS_MIN) - 1.0; return axis; } diff --git a/source/frontends/libretro/analog.h b/source/frontends/libretro/analog.h index b0c2200e6..0dee42a22 100644 --- a/source/frontends/libretro/analog.h +++ b/source/frontends/libretro/analog.h @@ -10,7 +10,7 @@ namespace ra2 class Analog : public JoypadBase { public: - Analog(); + Analog(unsigned device); double getAxis(int i) const override; diff --git a/source/frontends/libretro/buffer.h b/source/frontends/libretro/buffer.h new file mode 100644 index 000000000..f464f8cc0 --- /dev/null +++ b/source/frontends/libretro/buffer.h @@ -0,0 +1,45 @@ +#include +#include + +namespace ra2 +{ + + template // so it works with both char * and const char * + class Buffer + { + public: + Buffer(C * const begin, size_t const size) : myPtr(begin), myEnd(begin + size) + { + + } + + template + T & get() + { + C * c = myPtr; + advance(sizeof(T)); + return *reinterpret_cast(c); + } + + void get(size_t const size, C * & begin, C * & end) + { + begin = myPtr; + advance(size); + end = myPtr; + } + + private: + C * myPtr; + C * const myEnd; + + void advance(size_t const size) + { + if (myPtr + size > myEnd) + { + throw std::runtime_error("Buffer: out of bounds"); + } + myPtr += size; + } + }; + +} diff --git a/source/frontends/libretro/diskcontrol.cpp b/source/frontends/libretro/diskcontrol.cpp new file mode 100644 index 000000000..a987d2658 --- /dev/null +++ b/source/frontends/libretro/diskcontrol.cpp @@ -0,0 +1,289 @@ +#include "StdAfx.h" +#include "frontends/libretro/diskcontrol.h" +#include "frontends/libretro/environment.h" + +#include "Core.h" +#include "CardManager.h" +#include "Disk.h" +#include "Harddisk.h" + +#include + +namespace ra2 +{ + + DiskControl::DiskControl() : myEjected(false), myIndex(0) + { + + } + + bool DiskControl::insertDisk(const std::string & filename) + { + if (insertFloppyDisk(filename)) + { + myIndex = 0; + myImages.clear(); + myImages.push_back(filename); + myEjected = false; + return true; + } + + return insertHardDisk(filename); + } + + bool DiskControl::insertPlaylist(const std::string & filename) + { + const std::filesystem::path path(filename); + std::ifstream playlist(path); + if (!playlist) + { + return false; + } + + myImages.clear(); + const std::filesystem::path parent = path.parent_path(); + + std::string line; + while (std::getline(playlist, line)) + { + // should we trim initial spaces? + if (!line.empty() && line[0] != '#') + { + std::filesystem::path image(line); + if (image.is_relative()) + { + image = parent / image; + } + myImages.push_back(image); + } + } + + // if we have an initial disk image, let's try to honour it + if (!ourInitialPath.empty() && ourInitialIndex < myImages.size() && myImages[ourInitialIndex] == ourInitialPath) + { + myIndex = ourInitialIndex; + // do we need to reset for next time? + ourInitialPath.clear(); + ourInitialIndex = 0; + } + else + { + // insert the first image + myIndex = 0; + } + + // this is safe even if myImages is empty + myEjected = true; + return setEjectedState(false); + } + + bool DiskControl::insertFloppyDisk(const std::string & filename) const + { + CardManager & cardManager = GetCardMgr(); + + Disk2InterfaceCard * disk2Card = dynamic_cast(cardManager.GetObj(SLOT6)); + if (disk2Card) + { + const ImageError_e error = disk2Card->InsertDisk(DRIVE_1, filename.c_str(), IMAGE_FORCE_WRITE_PROTECTED, IMAGE_DONT_CREATE); + + if (error == eIMAGE_ERROR_NONE) + { + return true; + } + } + + return false; + } + + bool DiskControl::insertHardDisk(const std::string & filename) const + { + CardManager & cardManager = GetCardMgr(); + + if (cardManager.QuerySlot(SLOT7) != CT_GenericHDD) + { + cardManager.Insert(SLOT7, CT_GenericHDD); + } + + HarddiskInterfaceCard * harddiskCard = dynamic_cast(cardManager.GetObj(SLOT7)); + if (harddiskCard) + { + BOOL bRes = harddiskCard->Insert(HARDDISK_1, filename); + return bRes == TRUE; + } + + return false; + } + + bool DiskControl::getEjectedState() const + { + return myEjected; + } + + bool DiskControl::setEjectedState(bool ejected) + { + if (myEjected == ejected) + { + return true; // or false? + } + + CardManager & cardManager = GetCardMgr(); + Disk2InterfaceCard * disk2Card = dynamic_cast(cardManager.GetObj(SLOT6)); + + bool result = false; + if (disk2Card && myIndex < myImages.size()) + { + if (ejected) + { + disk2Card->EjectDisk(DRIVE_1); + result = true; + myEjected = ejected; + } + else + { + // inserted + result = insertFloppyDisk(myImages[myIndex]); + myEjected = !result; + ra2::log_cb(RETRO_LOG_INFO, "Insert new disk: %s -> %d\n", myImages[myIndex].c_str(), result); + } + } + + return result; + } + + size_t DiskControl::getImageIndex() const + { + return myIndex; + } + + bool DiskControl::setImageIndex(size_t index) + { + if (myEjected) + { + myIndex = index; + return true; + } + else + { + return false; + } + } + + size_t DiskControl::getNumImages() const + { + return myImages.size(); + } + + bool DiskControl::replaceImageIndex(size_t index, const std::string & path) + { + if (myEjected && myIndex < myImages.size()) + { + myImages[index] = path; + return true; + } + else + { + return false; + } + } + + bool DiskControl::removeImageIndex(size_t index) + { + if (myEjected && myIndex < myImages.size()) + { + myImages.erase(myImages.begin() + index); + if (myImages.empty() || myIndex == index) + { + myIndex = myImages.size(); + } + else if (myIndex > index) + { + --myIndex; + } + return true; + } + else + { + return false; + } + } + + bool DiskControl::addImageIndex() + { + myImages.push_back(std::string()); + return true; + } + + bool DiskControl::getImagePath(unsigned index, char *path, size_t len) const + { + if (index < myImages.size()) + { + strncpy(path, myImages[index].c_str(), len); + path[len - 1] = 0; + return true; + } + else + { + return false; + } + } + + bool DiskControl::getImageLabel(unsigned index, char *label, size_t len) const + { + if (index < myImages.size()) + { + const std::string filename = myImages[index].filename(); + strncpy(label, filename.c_str(), len); + label[len - 1] = 0; + return true; + } + else + { + return false; + } + } + + void DiskControl::serialise(Buffer & buffer) const + { + buffer.get() = myEjected; + buffer.get() = myIndex; + buffer.get() = myImages.size(); + + for (std::string const & image : myImages) + { + size_t const size = image.size(); + buffer.get() = size; + char * begin, * end; + buffer.get(size, begin, end); + memcpy(begin, image.data(), end - begin); + } + } + + void DiskControl::deserialise(Buffer & buffer) + { + myEjected = buffer.get(); + myIndex = buffer.get(); + size_t const numberOfImages = buffer.get(); + myImages.clear(); + myImages.resize(numberOfImages); + + for (size_t i = 0; i < numberOfImages; ++i) + { + size_t const size = buffer.get(); + char const * begin, * end; + buffer.get(size, begin, end); + myImages[i].assign(begin, end); + } + } + + unsigned DiskControl::ourInitialIndex = 0; + std::string DiskControl::ourInitialPath; + void DiskControl::setInitialPath(unsigned index, const char *path) + { + if (path && *path) + { + ourInitialIndex = index; + ourInitialPath = path; + } + } + +} diff --git a/source/frontends/libretro/diskcontrol.h b/source/frontends/libretro/diskcontrol.h new file mode 100644 index 000000000..6e95459f1 --- /dev/null +++ b/source/frontends/libretro/diskcontrol.h @@ -0,0 +1,51 @@ +#include "frontends/libretro/buffer.h" +#include +#include +#include + +namespace ra2 +{ + + class DiskControl + { + public: + DiskControl(); + + bool getEjectedState() const; + bool setEjectedState(bool state); + + size_t getImageIndex() const; + bool setImageIndex(size_t index); + + size_t getNumImages() const; + + bool replaceImageIndex(size_t index, const std::string & path); + bool removeImageIndex(size_t index); + bool addImageIndex(); + + // these 2 functions update the images for the Disc Control Interface + bool insertDisk(const std::string & filename); + bool insertPlaylist(const std::string & filename); + + bool getImagePath(unsigned index, char *path, size_t len) const; + bool getImageLabel(unsigned index, char *label, size_t len) const; + + static void setInitialPath(unsigned index, const char *path); + + void serialise(Buffer & buffer) const; + void deserialise(Buffer & buffer); + + private: + std::vector myImages; + + bool myEjected; + size_t myIndex; + + bool insertFloppyDisk(const std::string & filename) const; + bool insertHardDisk(const std::string & filename) const; + + static unsigned ourInitialIndex; + static std::string ourInitialPath; + }; + +} diff --git a/source/frontends/libretro/environment.cpp b/source/frontends/libretro/environment.cpp index d437fbf5b..7b1380f09 100644 --- a/source/frontends/libretro/environment.cpp +++ b/source/frontends/libretro/environment.cpp @@ -25,8 +25,6 @@ namespace ra2 retro_audio_sample_t audio_cb; retro_audio_sample_batch_t audio_batch_cb; - std::string retro_base_directory; - void display_message(const std::string & message) { retro_message rmsg; diff --git a/source/frontends/libretro/environment.h b/source/frontends/libretro/environment.h index fd65f1d3d..40760f851 100644 --- a/source/frontends/libretro/environment.h +++ b/source/frontends/libretro/environment.h @@ -5,9 +5,9 @@ namespace ra2 { - void fallback_log(enum retro_log_level level, const char *fmt, ...); + void fallback_log(enum retro_log_level level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); extern retro_log_callback logging; - extern retro_log_printf_t log_cb; + extern retro_log_printf_t log_cb __attribute__((format(printf, 2, 3))); extern retro_input_poll_t input_poll_cb; extern retro_input_state_t input_state_cb; extern retro_environment_t environ_cb; @@ -15,8 +15,6 @@ namespace ra2 extern retro_audio_sample_t audio_cb; extern retro_audio_sample_batch_t audio_batch_cb; - extern std::string retro_base_directory; - #define MAX_PADS 1 void display_message(const std::string & message); diff --git a/source/frontends/libretro/game.cpp b/source/frontends/libretro/game.cpp index a9b268347..2bf680572 100644 --- a/source/frontends/libretro/game.cpp +++ b/source/frontends/libretro/game.cpp @@ -1,8 +1,6 @@ #include "StdAfx.h" #include "frontends/libretro/game.h" #include "frontends/libretro/retroregistry.h" -#include "frontends/libretro/joypad.h" -#include "frontends/libretro/analog.h" #include "frontends/libretro/retroframe.h" #include "Common.h" @@ -14,6 +12,7 @@ #include "CPU.h" #include "NTSC.h" #include "Utilities.h" +#include "Interface.h" #include "linux/keyboard.h" #include "linux/registry.h" @@ -23,46 +22,6 @@ #include "libretro.h" -namespace -{ - - bool insertDisk(const std::string & filename) - { - if (filename.empty()) - { - return false; - } - - CardManager & cardManager = GetCardMgr(); - - Disk2InterfaceCard * disk2Card = dynamic_cast(cardManager.GetObj(SLOT6)); - if (disk2Card) - { - const ImageError_e error = disk2Card->InsertDisk(DRIVE_1, filename.c_str(), IMAGE_FORCE_WRITE_PROTECTED, IMAGE_DONT_CREATE); - - if (error == eIMAGE_ERROR_NONE) - { - return true; - } - } - - if (cardManager.QuerySlot(SLOT7) != CT_GenericHDD) - { - cardManager.Insert(SLOT7, CT_GenericHDD); - } - - HarddiskInterfaceCard * harddiskCard = dynamic_cast(cardManager.GetObj(SLOT7)); - if (harddiskCard) - { - BOOL bRes = harddiskCard->Insert(HARDDISK_1, filename); - return bRes == TRUE; - } - - return false; - } - -} - namespace ra2 { @@ -76,25 +35,20 @@ namespace ra2 myRegistryContext.reset(new RegistryContext(CreateRetroRegistry())); myFrame.reset(new ra2::RetroFrame()); - std::shared_ptr paddle; - switch (ourInputDevices[0]) - { - case RETRO_DEVICE_NONE: - break; - case RETRO_DEVICE_JOYPAD: - paddle.reset(new Joypad); - Paddle::setSquaring(false); - break; - case RETRO_DEVICE_ANALOG: - paddle.reset(new Analog); - Paddle::setSquaring(true); - break; - default: - break; - } + SetFrame(myFrame); + myFrame->Begin(); + + Video & video = GetVideo(); + // should the user be allowed to tweak 0.75 + myMouse[0] = {0.0, 0.75 / video.GetFrameBufferBorderlessWidth(), RETRO_DEVICE_ID_MOUSE_X}; + myMouse[1] = {0.0, 0.75 / video.GetFrameBufferBorderlessHeight(), RETRO_DEVICE_ID_MOUSE_Y}; + } - myInitialisation.reset(new Initialisation(myFrame, paddle)); - myFrame->Initialize(); + Game::~Game() + { + myFrame->End(); + myFrame.reset(); + SetFrame(myFrame); } retro_usec_t Game::ourFrameTime = 0; @@ -119,6 +73,7 @@ namespace ra2 { input_poll_cb(); keyboardEmulation(); + mouseEmulation(); } void Game::keyboardCallback(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers) @@ -231,7 +186,7 @@ namespace ra2 if (ch) { addKeyToBuffer(ch); - log_cb(RETRO_LOG_INFO, "RA2: %s - %02x\n", __FUNCTION__, ch); + // log_cb(RETRO_LOG_INFO, "RA2: %s - %02x\n", __FUNCTION__, ch); } } @@ -288,16 +243,31 @@ namespace ra2 } } - bool Game::loadGame(const std::string & path) + void Game::mouseEmulation() { - const bool ok = insertDisk(path); - return ok; + for (auto & mouse : myMouse) + { + const int16_t x = input_state_cb(0, RETRO_DEVICE_MOUSE, 0, mouse.id); + mouse.position += x * mouse.multiplier; + mouse.position = std::min(1.0, std::max(mouse.position, -1.0)); + } + } + + double Game::getMousePosition(int i) const + { + return myMouse[i].position; } bool Game::loadSnapshot(const std::string & path) { - common2::setSnapshotFilename(path, true); + common2::setSnapshotFilename(path); + myFrame->LoadSnapshot(); return true; } + DiskControl & Game::getDiskControl() + { + return myDiskControl; + } + } diff --git a/source/frontends/libretro/game.h b/source/frontends/libretro/game.h index 419b2f360..d7dbcedc5 100644 --- a/source/frontends/libretro/game.h +++ b/source/frontends/libretro/game.h @@ -2,6 +2,7 @@ #include "frontends/common2/speed.h" #include "frontends/libretro/environment.h" +#include "frontends/libretro/diskcontrol.h" #include "linux/context.h" @@ -17,8 +18,8 @@ namespace ra2 { public: Game(); + ~Game(); - bool loadGame(const std::string & path); bool loadSnapshot(const std::string & path); void executeOneFrame(); @@ -26,6 +27,10 @@ namespace ra2 void drawVideoBuffer(); + double getMousePosition(int i) const; + + DiskControl & getDiskControl(); + static void keyboardCallback(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers); static void frameTimeCallback(retro_usec_t usec); @@ -38,14 +43,25 @@ namespace ra2 std::shared_ptr myLoggerContext; std::shared_ptr myRegistryContext; std::shared_ptr myFrame; - std::shared_ptr myInitialisation; common2::Speed mySpeed; // fixed speed std::vector myButtonStates; + struct MousePosition_t + { + double position; // -1 to 1 + double multiplier; + unsigned id; + }; + + MousePosition_t myMouse[2]; + + DiskControl myDiskControl; + bool checkButtonPressed(unsigned id); void keyboardEmulation(); + void mouseEmulation(); static void processKeyDown(unsigned keycode, uint32_t character, uint16_t key_modifiers); static void processKeyUp(unsigned keycode, uint32_t character, uint16_t key_modifiers); diff --git a/source/frontends/libretro/joypad.cpp b/source/frontends/libretro/joypad.cpp index f78d1e458..d8a58fa0b 100644 --- a/source/frontends/libretro/joypad.cpp +++ b/source/frontends/libretro/joypad.cpp @@ -6,8 +6,9 @@ namespace ra2 { - Joypad::Joypad() - : myAxisCodes(2) + Joypad::Joypad(unsigned device) + : JoypadBase(device) + , myAxisCodes(2) { myAxisCodes[0][RETRO_DEVICE_ID_JOYPAD_LEFT] = -1.0; myAxisCodes[0][RETRO_DEVICE_ID_JOYPAD_RIGHT] = 1.0; @@ -19,7 +20,7 @@ namespace ra2 { for (const auto & axis : myAxisCodes[i]) { - const int value = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, axis.first); + const int value = input_state_cb(0, myDevice, 0, axis.first); if (value) { return axis.second; diff --git a/source/frontends/libretro/joypad.h b/source/frontends/libretro/joypad.h index ee82fa314..8c0dda08c 100644 --- a/source/frontends/libretro/joypad.h +++ b/source/frontends/libretro/joypad.h @@ -11,7 +11,7 @@ namespace ra2 class Joypad : public JoypadBase { public: - Joypad(); + Joypad(unsigned device); double getAxis(int i) const override; diff --git a/source/frontends/libretro/joypadbase.cpp b/source/frontends/libretro/joypadbase.cpp index f474f62b5..10ceb1c58 100644 --- a/source/frontends/libretro/joypadbase.cpp +++ b/source/frontends/libretro/joypadbase.cpp @@ -6,17 +6,20 @@ namespace ra2 { - JoypadBase::JoypadBase() - : myButtonCodes(2) + ControllerBase::ControllerBase(unsigned device, const std::vector & buttonCodes) + : myDevice(device), myButtonCodes(buttonCodes) { - myButtonCodes[0] = RETRO_DEVICE_ID_JOYPAD_A; - myButtonCodes[1] = RETRO_DEVICE_ID_JOYPAD_B; } - bool JoypadBase::getButton(int i) const + bool ControllerBase::getButton(int i) const { - const int value = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, myButtonCodes[i]); + const int value = input_state_cb(0, myDevice, 0, myButtonCodes[i]); return value != 0; } + JoypadBase::JoypadBase(unsigned device) : ControllerBase(device, {RETRO_DEVICE_ID_JOYPAD_A, RETRO_DEVICE_ID_JOYPAD_B}) + { + + } + } diff --git a/source/frontends/libretro/joypadbase.h b/source/frontends/libretro/joypadbase.h index 4e5cf325d..5ad8720f6 100644 --- a/source/frontends/libretro/joypadbase.h +++ b/source/frontends/libretro/joypadbase.h @@ -8,15 +8,25 @@ namespace ra2 { - class JoypadBase : public Paddle + class ControllerBase : public Paddle { public: - JoypadBase(); + ControllerBase(unsigned device, const std::vector & buttonCodes); bool getButton(int i) const override; + protected: + const unsigned myDevice; + private: - std::vector myButtonCodes; + const std::vector myButtonCodes; + }; + + class JoypadBase : public ControllerBase + { + public: + JoypadBase(unsigned device); }; + } diff --git a/source/frontends/libretro/libretro-common/include/libretro.h b/source/frontends/libretro/libretro-common/include/libretro.h new file mode 100644 index 000000000..2f1748370 --- /dev/null +++ b/source/frontends/libretro/libretro-common/include/libretro.h @@ -0,0 +1,3890 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this libretro API header (libretro.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef LIBRETRO_H__ +#define LIBRETRO_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __cplusplus +#if defined(_MSC_VER) && _MSC_VER < 1800 && !defined(SN_TARGET_PS3) +/* Hack applied for MSVC when compiling in C89 mode + * as it isn't C99-compliant. */ +#define bool unsigned char +#define true 1 +#define false 0 +#else +#include +#endif +#endif + +#ifndef RETRO_CALLCONV +# if defined(__GNUC__) && defined(__i386__) && !defined(__x86_64__) +# define RETRO_CALLCONV __attribute__((cdecl)) +# elif defined(_MSC_VER) && defined(_M_X86) && !defined(_M_X64) +# define RETRO_CALLCONV __cdecl +# else +# define RETRO_CALLCONV /* all other platforms only have one calling convention each */ +# endif +#endif + +#ifndef RETRO_API +# if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) +# ifdef RETRO_IMPORT_SYMBOLS +# ifdef __GNUC__ +# define RETRO_API RETRO_CALLCONV __attribute__((__dllimport__)) +# else +# define RETRO_API RETRO_CALLCONV __declspec(dllimport) +# endif +# else +# ifdef __GNUC__ +# define RETRO_API RETRO_CALLCONV __attribute__((__dllexport__)) +# else +# define RETRO_API RETRO_CALLCONV __declspec(dllexport) +# endif +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define RETRO_API RETRO_CALLCONV __attribute__((__visibility__("default"))) +# else +# define RETRO_API RETRO_CALLCONV +# endif +# endif +#endif + +/* Used for checking API/ABI mismatches that can break libretro + * implementations. + * It is not incremented for compatible changes to the API. + */ +#define RETRO_API_VERSION 1 + +/* + * Libretro's fundamental device abstractions. + * + * Libretro's input system consists of some standardized device types, + * such as a joypad (with/without analog), mouse, keyboard, lightgun + * and a pointer. + * + * The functionality of these devices are fixed, and individual cores + * map their own concept of a controller to libretro's abstractions. + * This makes it possible for frontends to map the abstract types to a + * real input device, and not having to worry about binding input + * correctly to arbitrary controller layouts. + */ + +#define RETRO_DEVICE_TYPE_SHIFT 8 +#define RETRO_DEVICE_MASK ((1 << RETRO_DEVICE_TYPE_SHIFT) - 1) +#define RETRO_DEVICE_SUBCLASS(base, id) (((id + 1) << RETRO_DEVICE_TYPE_SHIFT) | base) + +/* Input disabled. */ +#define RETRO_DEVICE_NONE 0 + +/* The JOYPAD is called RetroPad. It is essentially a Super Nintendo + * controller, but with additional L2/R2/L3/R3 buttons, similar to a + * PS1 DualShock. */ +#define RETRO_DEVICE_JOYPAD 1 + +/* The mouse is a simple mouse, similar to Super Nintendo's mouse. + * X and Y coordinates are reported relatively to last poll (poll callback). + * It is up to the libretro implementation to keep track of where the mouse + * pointer is supposed to be on the screen. + * The frontend must make sure not to interfere with its own hardware + * mouse pointer. + */ +#define RETRO_DEVICE_MOUSE 2 + +/* KEYBOARD device lets one poll for raw key pressed. + * It is poll based, so input callback will return with the current + * pressed state. + * For event/text based keyboard input, see + * RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. + */ +#define RETRO_DEVICE_KEYBOARD 3 + +/* LIGHTGUN device is similar to Guncon-2 for PlayStation 2. + * It reports X/Y coordinates in screen space (similar to the pointer) + * in the range [-0x8000, 0x7fff] in both axes, with zero being center and + * -0x8000 being out of bounds. + * As well as reporting on/off screen state. It features a trigger, + * start/select buttons, auxiliary action buttons and a + * directional pad. A forced off-screen shot can be requested for + * auto-reloading function in some games. + */ +#define RETRO_DEVICE_LIGHTGUN 4 + +/* The ANALOG device is an extension to JOYPAD (RetroPad). + * Similar to DualShock2 it adds two analog sticks and all buttons can + * be analog. This is treated as a separate device type as it returns + * axis values in the full analog range of [-0x7fff, 0x7fff], + * although some devices may return -0x8000. + * Positive X axis is right. Positive Y axis is down. + * Buttons are returned in the range [0, 0x7fff]. + * Only use ANALOG type when polling for analog values. + */ +#define RETRO_DEVICE_ANALOG 5 + +/* Abstracts the concept of a pointing mechanism, e.g. touch. + * This allows libretro to query in absolute coordinates where on the + * screen a mouse (or something similar) is being placed. + * For a touch centric device, coordinates reported are the coordinates + * of the press. + * + * Coordinates in X and Y are reported as: + * [-0x7fff, 0x7fff]: -0x7fff corresponds to the far left/top of the screen, + * and 0x7fff corresponds to the far right/bottom of the screen. + * The "screen" is here defined as area that is passed to the frontend and + * later displayed on the monitor. + * + * The frontend is free to scale/resize this screen as it sees fit, however, + * (X, Y) = (-0x7fff, -0x7fff) will correspond to the top-left pixel of the + * game image, etc. + * + * To check if the pointer coordinates are valid (e.g. a touch display + * actually being touched), PRESSED returns 1 or 0. + * + * If using a mouse on a desktop, PRESSED will usually correspond to the + * left mouse button, but this is a frontend decision. + * PRESSED will only return 1 if the pointer is inside the game screen. + * + * For multi-touch, the index variable can be used to successively query + * more presses. + * If index = 0 returns true for _PRESSED, coordinates can be extracted + * with _X, _Y for index = 0. One can then query _PRESSED, _X, _Y with + * index = 1, and so on. + * Eventually _PRESSED will return false for an index. No further presses + * are registered at this point. */ +#define RETRO_DEVICE_POINTER 6 + +/* Buttons for the RetroPad (JOYPAD). + * The placement of these is equivalent to placements on the + * Super Nintendo controller. + * L2/R2/L3/R3 buttons correspond to the PS1 DualShock. + * Also used as id values for RETRO_DEVICE_INDEX_ANALOG_BUTTON */ +#define RETRO_DEVICE_ID_JOYPAD_B 0 +#define RETRO_DEVICE_ID_JOYPAD_Y 1 +#define RETRO_DEVICE_ID_JOYPAD_SELECT 2 +#define RETRO_DEVICE_ID_JOYPAD_START 3 +#define RETRO_DEVICE_ID_JOYPAD_UP 4 +#define RETRO_DEVICE_ID_JOYPAD_DOWN 5 +#define RETRO_DEVICE_ID_JOYPAD_LEFT 6 +#define RETRO_DEVICE_ID_JOYPAD_RIGHT 7 +#define RETRO_DEVICE_ID_JOYPAD_A 8 +#define RETRO_DEVICE_ID_JOYPAD_X 9 +#define RETRO_DEVICE_ID_JOYPAD_L 10 +#define RETRO_DEVICE_ID_JOYPAD_R 11 +#define RETRO_DEVICE_ID_JOYPAD_L2 12 +#define RETRO_DEVICE_ID_JOYPAD_R2 13 +#define RETRO_DEVICE_ID_JOYPAD_L3 14 +#define RETRO_DEVICE_ID_JOYPAD_R3 15 + +#define RETRO_DEVICE_ID_JOYPAD_MASK 256 + +/* Index / Id values for ANALOG device. */ +#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0 +#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1 +#define RETRO_DEVICE_INDEX_ANALOG_BUTTON 2 +#define RETRO_DEVICE_ID_ANALOG_X 0 +#define RETRO_DEVICE_ID_ANALOG_Y 1 + +/* Id values for MOUSE. */ +#define RETRO_DEVICE_ID_MOUSE_X 0 +#define RETRO_DEVICE_ID_MOUSE_Y 1 +#define RETRO_DEVICE_ID_MOUSE_LEFT 2 +#define RETRO_DEVICE_ID_MOUSE_RIGHT 3 +#define RETRO_DEVICE_ID_MOUSE_WHEELUP 4 +#define RETRO_DEVICE_ID_MOUSE_WHEELDOWN 5 +#define RETRO_DEVICE_ID_MOUSE_MIDDLE 6 +#define RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP 7 +#define RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN 8 +#define RETRO_DEVICE_ID_MOUSE_BUTTON_4 9 +#define RETRO_DEVICE_ID_MOUSE_BUTTON_5 10 + +/* Id values for LIGHTGUN. */ +#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X 13 /*Absolute Position*/ +#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y 14 /*Absolute*/ +#define RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN 15 /*Status Check*/ +#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2 +#define RETRO_DEVICE_ID_LIGHTGUN_RELOAD 16 /*Forced off-screen shot*/ +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_A 3 +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_B 4 +#define RETRO_DEVICE_ID_LIGHTGUN_START 6 +#define RETRO_DEVICE_ID_LIGHTGUN_SELECT 7 +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_C 8 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP 9 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN 10 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT 11 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT 12 +/* deprecated */ +#define RETRO_DEVICE_ID_LIGHTGUN_X 0 /*Relative Position*/ +#define RETRO_DEVICE_ID_LIGHTGUN_Y 1 /*Relative*/ +#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3 /*Use Aux:A*/ +#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4 /*Use Aux:B*/ +#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5 /*Use Start*/ + +/* Id values for POINTER. */ +#define RETRO_DEVICE_ID_POINTER_X 0 +#define RETRO_DEVICE_ID_POINTER_Y 1 +#define RETRO_DEVICE_ID_POINTER_PRESSED 2 +#define RETRO_DEVICE_ID_POINTER_COUNT 3 + +/* Returned from retro_get_region(). */ +#define RETRO_REGION_NTSC 0 +#define RETRO_REGION_PAL 1 + +/* Id values for LANGUAGE */ +enum retro_language +{ + RETRO_LANGUAGE_ENGLISH = 0, + RETRO_LANGUAGE_JAPANESE = 1, + RETRO_LANGUAGE_FRENCH = 2, + RETRO_LANGUAGE_SPANISH = 3, + RETRO_LANGUAGE_GERMAN = 4, + RETRO_LANGUAGE_ITALIAN = 5, + RETRO_LANGUAGE_DUTCH = 6, + RETRO_LANGUAGE_PORTUGUESE_BRAZIL = 7, + RETRO_LANGUAGE_PORTUGUESE_PORTUGAL = 8, + RETRO_LANGUAGE_RUSSIAN = 9, + RETRO_LANGUAGE_KOREAN = 10, + RETRO_LANGUAGE_CHINESE_TRADITIONAL = 11, + RETRO_LANGUAGE_CHINESE_SIMPLIFIED = 12, + RETRO_LANGUAGE_ESPERANTO = 13, + RETRO_LANGUAGE_POLISH = 14, + RETRO_LANGUAGE_VIETNAMESE = 15, + RETRO_LANGUAGE_ARABIC = 16, + RETRO_LANGUAGE_GREEK = 17, + RETRO_LANGUAGE_TURKISH = 18, + RETRO_LANGUAGE_SLOVAK = 19, + RETRO_LANGUAGE_PERSIAN = 20, + RETRO_LANGUAGE_HEBREW = 21, + RETRO_LANGUAGE_ASTURIAN = 22, + RETRO_LANGUAGE_FINNISH = 23, + RETRO_LANGUAGE_LAST, + + /* Ensure sizeof(enum) == sizeof(int) */ + RETRO_LANGUAGE_DUMMY = INT_MAX +}; + +/* Passed to retro_get_memory_data/size(). + * If the memory type doesn't apply to the + * implementation NULL/0 can be returned. + */ +#define RETRO_MEMORY_MASK 0xff + +/* Regular save RAM. This RAM is usually found on a game cartridge, + * backed up by a battery. + * If save game data is too complex for a single memory buffer, + * the SAVE_DIRECTORY (preferably) or SYSTEM_DIRECTORY environment + * callback can be used. */ +#define RETRO_MEMORY_SAVE_RAM 0 + +/* Some games have a built-in clock to keep track of time. + * This memory is usually just a couple of bytes to keep track of time. + */ +#define RETRO_MEMORY_RTC 1 + +/* System ram lets a frontend peek into a game systems main RAM. */ +#define RETRO_MEMORY_SYSTEM_RAM 2 + +/* Video ram lets a frontend peek into a game systems video RAM (VRAM). */ +#define RETRO_MEMORY_VIDEO_RAM 3 + +/* Keysyms used for ID in input state callback when polling RETRO_KEYBOARD. */ +enum retro_key +{ + RETROK_UNKNOWN = 0, + RETROK_FIRST = 0, + RETROK_BACKSPACE = 8, + RETROK_TAB = 9, + RETROK_CLEAR = 12, + RETROK_RETURN = 13, + RETROK_PAUSE = 19, + RETROK_ESCAPE = 27, + RETROK_SPACE = 32, + RETROK_EXCLAIM = 33, + RETROK_QUOTEDBL = 34, + RETROK_HASH = 35, + RETROK_DOLLAR = 36, + RETROK_AMPERSAND = 38, + RETROK_QUOTE = 39, + RETROK_LEFTPAREN = 40, + RETROK_RIGHTPAREN = 41, + RETROK_ASTERISK = 42, + RETROK_PLUS = 43, + RETROK_COMMA = 44, + RETROK_MINUS = 45, + RETROK_PERIOD = 46, + RETROK_SLASH = 47, + RETROK_0 = 48, + RETROK_1 = 49, + RETROK_2 = 50, + RETROK_3 = 51, + RETROK_4 = 52, + RETROK_5 = 53, + RETROK_6 = 54, + RETROK_7 = 55, + RETROK_8 = 56, + RETROK_9 = 57, + RETROK_COLON = 58, + RETROK_SEMICOLON = 59, + RETROK_LESS = 60, + RETROK_EQUALS = 61, + RETROK_GREATER = 62, + RETROK_QUESTION = 63, + RETROK_AT = 64, + RETROK_LEFTBRACKET = 91, + RETROK_BACKSLASH = 92, + RETROK_RIGHTBRACKET = 93, + RETROK_CARET = 94, + RETROK_UNDERSCORE = 95, + RETROK_BACKQUOTE = 96, + RETROK_a = 97, + RETROK_b = 98, + RETROK_c = 99, + RETROK_d = 100, + RETROK_e = 101, + RETROK_f = 102, + RETROK_g = 103, + RETROK_h = 104, + RETROK_i = 105, + RETROK_j = 106, + RETROK_k = 107, + RETROK_l = 108, + RETROK_m = 109, + RETROK_n = 110, + RETROK_o = 111, + RETROK_p = 112, + RETROK_q = 113, + RETROK_r = 114, + RETROK_s = 115, + RETROK_t = 116, + RETROK_u = 117, + RETROK_v = 118, + RETROK_w = 119, + RETROK_x = 120, + RETROK_y = 121, + RETROK_z = 122, + RETROK_LEFTBRACE = 123, + RETROK_BAR = 124, + RETROK_RIGHTBRACE = 125, + RETROK_TILDE = 126, + RETROK_DELETE = 127, + + RETROK_KP0 = 256, + RETROK_KP1 = 257, + RETROK_KP2 = 258, + RETROK_KP3 = 259, + RETROK_KP4 = 260, + RETROK_KP5 = 261, + RETROK_KP6 = 262, + RETROK_KP7 = 263, + RETROK_KP8 = 264, + RETROK_KP9 = 265, + RETROK_KP_PERIOD = 266, + RETROK_KP_DIVIDE = 267, + RETROK_KP_MULTIPLY = 268, + RETROK_KP_MINUS = 269, + RETROK_KP_PLUS = 270, + RETROK_KP_ENTER = 271, + RETROK_KP_EQUALS = 272, + + RETROK_UP = 273, + RETROK_DOWN = 274, + RETROK_RIGHT = 275, + RETROK_LEFT = 276, + RETROK_INSERT = 277, + RETROK_HOME = 278, + RETROK_END = 279, + RETROK_PAGEUP = 280, + RETROK_PAGEDOWN = 281, + + RETROK_F1 = 282, + RETROK_F2 = 283, + RETROK_F3 = 284, + RETROK_F4 = 285, + RETROK_F5 = 286, + RETROK_F6 = 287, + RETROK_F7 = 288, + RETROK_F8 = 289, + RETROK_F9 = 290, + RETROK_F10 = 291, + RETROK_F11 = 292, + RETROK_F12 = 293, + RETROK_F13 = 294, + RETROK_F14 = 295, + RETROK_F15 = 296, + + RETROK_NUMLOCK = 300, + RETROK_CAPSLOCK = 301, + RETROK_SCROLLOCK = 302, + RETROK_RSHIFT = 303, + RETROK_LSHIFT = 304, + RETROK_RCTRL = 305, + RETROK_LCTRL = 306, + RETROK_RALT = 307, + RETROK_LALT = 308, + RETROK_RMETA = 309, + RETROK_LMETA = 310, + RETROK_LSUPER = 311, + RETROK_RSUPER = 312, + RETROK_MODE = 313, + RETROK_COMPOSE = 314, + + RETROK_HELP = 315, + RETROK_PRINT = 316, + RETROK_SYSREQ = 317, + RETROK_BREAK = 318, + RETROK_MENU = 319, + RETROK_POWER = 320, + RETROK_EURO = 321, + RETROK_UNDO = 322, + RETROK_OEM_102 = 323, + + RETROK_LAST, + + RETROK_DUMMY = INT_MAX /* Ensure sizeof(enum) == sizeof(int) */ +}; + +enum retro_mod +{ + RETROKMOD_NONE = 0x0000, + + RETROKMOD_SHIFT = 0x01, + RETROKMOD_CTRL = 0x02, + RETROKMOD_ALT = 0x04, + RETROKMOD_META = 0x08, + + RETROKMOD_NUMLOCK = 0x10, + RETROKMOD_CAPSLOCK = 0x20, + RETROKMOD_SCROLLOCK = 0x40, + + RETROKMOD_DUMMY = INT_MAX /* Ensure sizeof(enum) == sizeof(int) */ +}; + +/* If set, this call is not part of the public libretro API yet. It can + * change or be removed at any time. */ +#define RETRO_ENVIRONMENT_EXPERIMENTAL 0x10000 +/* Environment callback to be used internally in frontend. */ +#define RETRO_ENVIRONMENT_PRIVATE 0x20000 + +/* Environment commands. */ +#define RETRO_ENVIRONMENT_SET_ROTATION 1 /* const unsigned * -- + * Sets screen rotation of graphics. + * Valid values are 0, 1, 2, 3, which rotates screen by 0, 90, 180, + * 270 degrees counter-clockwise respectively. + */ +#define RETRO_ENVIRONMENT_GET_OVERSCAN 2 /* bool * -- + * NOTE: As of 2019 this callback is considered deprecated in favor of + * using core options to manage overscan in a more nuanced, core-specific way. + * + * Boolean value whether or not the implementation should use overscan, + * or crop away overscan. + */ +#define RETRO_ENVIRONMENT_GET_CAN_DUPE 3 /* bool * -- + * Boolean value whether or not frontend supports frame duping, + * passing NULL to video frame callback. + */ + + /* Environ 4, 5 are no longer supported (GET_VARIABLE / SET_VARIABLES), + * and reserved to avoid possible ABI clash. + */ + +#define RETRO_ENVIRONMENT_SET_MESSAGE 6 /* const struct retro_message * -- + * Sets a message to be displayed in implementation-specific manner + * for a certain amount of 'frames'. + * Should not be used for trivial messages, which should simply be + * logged via RETRO_ENVIRONMENT_GET_LOG_INTERFACE (or as a + * fallback, stderr). + */ +#define RETRO_ENVIRONMENT_SHUTDOWN 7 /* N/A (NULL) -- + * Requests the frontend to shutdown. + * Should only be used if game has a specific + * way to shutdown the game from a menu item or similar. + */ +#define RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL 8 + /* const unsigned * -- + * Gives a hint to the frontend how demanding this implementation + * is on a system. E.g. reporting a level of 2 means + * this implementation should run decently on all frontends + * of level 2 and up. + * + * It can be used by the frontend to potentially warn + * about too demanding implementations. + * + * The levels are "floating". + * + * This function can be called on a per-game basis, + * as certain games an implementation can play might be + * particularly demanding. + * If called, it should be called in retro_load_game(). + */ +#define RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY 9 + /* const char ** -- + * Returns the "system" directory of the frontend. + * This directory can be used to store system specific + * content such as BIOSes, configuration data, etc. + * The returned value can be NULL. + * If so, no such directory is defined, + * and it's up to the implementation to find a suitable directory. + * + * NOTE: Some cores used this folder also for "save" data such as + * memory cards, etc, for lack of a better place to put it. + * This is now discouraged, and if possible, cores should try to + * use the new GET_SAVE_DIRECTORY. + */ +#define RETRO_ENVIRONMENT_SET_PIXEL_FORMAT 10 + /* const enum retro_pixel_format * -- + * Sets the internal pixel format used by the implementation. + * The default pixel format is RETRO_PIXEL_FORMAT_0RGB1555. + * This pixel format however, is deprecated (see enum retro_pixel_format). + * If the call returns false, the frontend does not support this pixel + * format. + * + * This function should be called inside retro_load_game() or + * retro_get_system_av_info(). + */ +#define RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS 11 + /* const struct retro_input_descriptor * -- + * Sets an array of retro_input_descriptors. + * It is up to the frontend to present this in a usable way. + * The array is terminated by retro_input_descriptor::description + * being set to NULL. + * This function can be called at any time, but it is recommended + * to call it as early as possible. + */ +#define RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK 12 + /* const struct retro_keyboard_callback * -- + * Sets a callback function used to notify core about keyboard events. + */ +#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE 13 + /* const struct retro_disk_control_callback * -- + * Sets an interface which frontend can use to eject and insert + * disk images. + * This is used for games which consist of multiple images and + * must be manually swapped out by the user (e.g. PSX). + */ +#define RETRO_ENVIRONMENT_SET_HW_RENDER 14 + /* struct retro_hw_render_callback * -- + * Sets an interface to let a libretro core render with + * hardware acceleration. + * Should be called in retro_load_game(). + * If successful, libretro cores will be able to render to a + * frontend-provided framebuffer. + * The size of this framebuffer will be at least as large as + * max_width/max_height provided in get_av_info(). + * If HW rendering is used, pass only RETRO_HW_FRAME_BUFFER_VALID or + * NULL to retro_video_refresh_t. + */ +#define RETRO_ENVIRONMENT_GET_VARIABLE 15 + /* struct retro_variable * -- + * Interface to acquire user-defined information from environment + * that cannot feasibly be supported in a multi-system way. + * 'key' should be set to a key which has already been set by + * SET_VARIABLES. + * 'data' will be set to a value or NULL. + */ +#define RETRO_ENVIRONMENT_SET_VARIABLES 16 + /* const struct retro_variable * -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterward it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * + * 'data' points to an array of retro_variable structs + * terminated by a { NULL, NULL } element. + * retro_variable::key should be namespaced to not collide + * with other implementations' keys. E.g. A core called + * 'foo' should use keys named as 'foo_option'. + * retro_variable::value should contain a human readable + * description of the key as well as a '|' delimited list + * of expected values. + * + * The number of possible options should be very limited, + * i.e. it should be feasible to cycle through options + * without a keyboard. + * + * First entry should be treated as a default. + * + * Example entry: + * { "foo_option", "Speed hack coprocessor X; false|true" } + * + * Text before first ';' is description. This ';' must be + * followed by a space, and followed by a list of possible + * values split up with '|'. + * + * Only strings are operated on. The possible values will + * generally be displayed and stored as-is by the frontend. + */ +#define RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE 17 + /* bool * -- + * Result is set to true if some variables are updated by + * frontend since last call to RETRO_ENVIRONMENT_GET_VARIABLE. + * Variables should be queried with GET_VARIABLE. + */ +#define RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME 18 + /* const bool * -- + * If true, the libretro implementation supports calls to + * retro_load_game() with NULL as argument. + * Used by cores which can run without particular game data. + * This should be called within retro_set_environment() only. + */ +#define RETRO_ENVIRONMENT_GET_LIBRETRO_PATH 19 + /* const char ** -- + * Retrieves the absolute path from where this libretro + * implementation was loaded. + * NULL is returned if the libretro was loaded statically + * (i.e. linked statically to frontend), or if the path cannot be + * determined. + * Mostly useful in cooperation with SET_SUPPORT_NO_GAME as assets can + * be loaded without ugly hacks. + */ + + /* Environment 20 was an obsolete version of SET_AUDIO_CALLBACK. + * It was not used by any known core at the time, + * and was removed from the API. */ +#define RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK 21 + /* const struct retro_frame_time_callback * -- + * Lets the core know how much time has passed since last + * invocation of retro_run(). + * The frontend can tamper with the timing to fake fast-forward, + * slow-motion, frame stepping, etc. + * In this case the delta time will use the reference value + * in frame_time_callback.. + */ +#define RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK 22 + /* const struct retro_audio_callback * -- + * Sets an interface which is used to notify a libretro core about audio + * being available for writing. + * The callback can be called from any thread, so a core using this must + * have a thread safe audio implementation. + * It is intended for games where audio and video are completely + * asynchronous and audio can be generated on the fly. + * This interface is not recommended for use with emulators which have + * highly synchronous audio. + * + * The callback only notifies about writability; the libretro core still + * has to call the normal audio callbacks + * to write audio. The audio callbacks must be called from within the + * notification callback. + * The amount of audio data to write is up to the implementation. + * Generally, the audio callback will be called continously in a loop. + * + * Due to thread safety guarantees and lack of sync between audio and + * video, a frontend can selectively disallow this interface based on + * internal configuration. A core using this interface must also + * implement the "normal" audio interface. + * + * A libretro core using SET_AUDIO_CALLBACK should also make use of + * SET_FRAME_TIME_CALLBACK. + */ +#define RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE 23 + /* struct retro_rumble_interface * -- + * Gets an interface which is used by a libretro core to set + * state of rumble motors in controllers. + * A strong and weak motor is supported, and they can be + * controlled indepedently. + * Should be called from either retro_init() or retro_load_game(). + * Should not be called from retro_set_environment(). + * Returns false if rumble functionality is unavailable. + */ +#define RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES 24 + /* uint64_t * -- + * Gets a bitmask telling which device type are expected to be + * handled properly in a call to retro_input_state_t. + * Devices which are not handled or recognized always return + * 0 in retro_input_state_t. + * Example bitmask: caps = (1 << RETRO_DEVICE_JOYPAD) | (1 << RETRO_DEVICE_ANALOG). + * Should only be called in retro_run(). + */ +#define RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE (25 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_sensor_interface * -- + * Gets access to the sensor interface. + * The purpose of this interface is to allow + * setting state related to sensors such as polling rate, + * enabling/disable it entirely, etc. + * Reading sensor state is done via the normal + * input_state_callback API. + */ +#define RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE (26 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_camera_callback * -- + * Gets an interface to a video camera driver. + * A libretro core can use this interface to get access to a + * video camera. + * New video frames are delivered in a callback in same + * thread as retro_run(). + * + * GET_CAMERA_INTERFACE should be called in retro_load_game(). + * + * Depending on the camera implementation used, camera frames + * will be delivered as a raw framebuffer, + * or as an OpenGL texture directly. + * + * The core has to tell the frontend here which types of + * buffers can be handled properly. + * An OpenGL texture can only be handled when using a + * libretro GL core (SET_HW_RENDER). + * It is recommended to use a libretro GL core when + * using camera interface. + * + * The camera is not started automatically. The retrieved start/stop + * functions must be used to explicitly + * start and stop the camera driver. + */ +#define RETRO_ENVIRONMENT_GET_LOG_INTERFACE 27 + /* struct retro_log_callback * -- + * Gets an interface for logging. This is useful for + * logging in a cross-platform way + * as certain platforms cannot use stderr for logging. + * It also allows the frontend to + * show logging information in a more suitable way. + * If this interface is not used, libretro cores should + * log to stderr as desired. + */ +#define RETRO_ENVIRONMENT_GET_PERF_INTERFACE 28 + /* struct retro_perf_callback * -- + * Gets an interface for performance counters. This is useful + * for performance logging in a cross-platform way and for detecting + * architecture-specific features, such as SIMD support. + */ +#define RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE 29 + /* struct retro_location_callback * -- + * Gets access to the location interface. + * The purpose of this interface is to be able to retrieve + * location-based information from the host device, + * such as current latitude / longitude. + */ +#define RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY 30 /* Old name, kept for compatibility. */ +#define RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY 30 + /* const char ** -- + * Returns the "core assets" directory of the frontend. + * This directory can be used to store specific assets that the + * core relies upon, such as art assets, + * input data, etc etc. + * The returned value can be NULL. + * If so, no such directory is defined, + * and it's up to the implementation to find a suitable directory. + */ +#define RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY 31 + /* const char ** -- + * Returns the "save" directory of the frontend, unless there is no + * save directory available. The save directory should be used to + * store SRAM, memory cards, high scores, etc, if the libretro core + * cannot use the regular memory interface (retro_get_memory_data()). + * + * If the frontend cannot designate a save directory, it will return + * NULL to indicate that the core should attempt to operate without a + * save directory set. + * + * NOTE: early libretro cores used the system directory for save + * files. Cores that need to be backwards-compatible can still check + * GET_SYSTEM_DIRECTORY. + */ +#define RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO 32 + /* const struct retro_system_av_info * -- + * Sets a new av_info structure. This can only be called from + * within retro_run(). + * This should *only* be used if the core is completely altering the + * internal resolutions, aspect ratios, timings, sampling rate, etc. + * Calling this can require a full reinitialization of video/audio + * drivers in the frontend, + * + * so it is important to call it very sparingly, and usually only with + * the users explicit consent. + * An eventual driver reinitialize will happen so that video and + * audio callbacks + * happening after this call within the same retro_run() call will + * target the newly initialized driver. + * + * This callback makes it possible to support configurable resolutions + * in games, which can be useful to + * avoid setting the "worst case" in max_width/max_height. + * + * ***HIGHLY RECOMMENDED*** Do not call this callback every time + * resolution changes in an emulator core if it's + * expected to be a temporary change, for the reasons of possible + * driver reinitialization. + * This call is not a free pass for not trying to provide + * correct values in retro_get_system_av_info(). If you need to change + * things like aspect ratio or nominal width/height, + * use RETRO_ENVIRONMENT_SET_GEOMETRY, which is a softer variant + * of SET_SYSTEM_AV_INFO. + * + * If this returns false, the frontend does not acknowledge a + * changed av_info struct. + */ +#define RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK 33 + /* const struct retro_get_proc_address_interface * -- + * Allows a libretro core to announce support for the + * get_proc_address() interface. + * This interface allows for a standard way to extend libretro where + * use of environment calls are too indirect, + * e.g. for cases where the frontend wants to call directly into the core. + * + * If a core wants to expose this interface, SET_PROC_ADDRESS_CALLBACK + * **MUST** be called from within retro_set_environment(). + */ +#define RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO 34 + /* const struct retro_subsystem_info * -- + * This environment call introduces the concept of libretro "subsystems". + * A subsystem is a variant of a libretro core which supports + * different kinds of games. + * The purpose of this is to support e.g. emulators which might + * have special needs, e.g. Super Nintendo's Super GameBoy, Sufami Turbo. + * It can also be used to pick among subsystems in an explicit way + * if the libretro implementation is a multi-system emulator itself. + * + * Loading a game via a subsystem is done with retro_load_game_special(), + * and this environment call allows a libretro core to expose which + * subsystems are supported for use with retro_load_game_special(). + * A core passes an array of retro_game_special_info which is terminated + * with a zeroed out retro_game_special_info struct. + * + * If a core wants to use this functionality, SET_SUBSYSTEM_INFO + * **MUST** be called from within retro_set_environment(). + */ +#define RETRO_ENVIRONMENT_SET_CONTROLLER_INFO 35 + /* const struct retro_controller_info * -- + * This environment call lets a libretro core tell the frontend + * which controller subclasses are recognized in calls to + * retro_set_controller_port_device(). + * + * Some emulators such as Super Nintendo support multiple lightgun + * types which must be specifically selected from. It is therefore + * sometimes necessary for a frontend to be able to tell the core + * about a special kind of input device which is not specifcally + * provided by the Libretro API. + * + * In order for a frontend to understand the workings of those devices, + * they must be defined as a specialized subclass of the generic device + * types already defined in the libretro API. + * + * The core must pass an array of const struct retro_controller_info which + * is terminated with a blanked out struct. Each element of the + * retro_controller_info struct corresponds to the ascending port index + * that is passed to retro_set_controller_port_device() when that function + * is called to indicate to the core that the frontend has changed the + * active device subclass. SEE ALSO: retro_set_controller_port_device() + * + * The ascending input port indexes provided by the core in the struct + * are generally presented by frontends as ascending User # or Player #, + * such as Player 1, Player 2, Player 3, etc. Which device subclasses are + * supported can vary per input port. + * + * The first inner element of each entry in the retro_controller_info array + * is a retro_controller_description struct that specifies the names and + * codes of all device subclasses that are available for the corresponding + * User or Player, beginning with the generic Libretro device that the + * subclasses are derived from. The second inner element of each entry is the + * total number of subclasses that are listed in the retro_controller_description. + * + * NOTE: Even if special device types are set in the libretro core, + * libretro should only poll input based on the base input device types. + */ +#define RETRO_ENVIRONMENT_SET_MEMORY_MAPS (36 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* const struct retro_memory_map * -- + * This environment call lets a libretro core tell the frontend + * about the memory maps this core emulates. + * This can be used to implement, for example, cheats in a core-agnostic way. + * + * Should only be used by emulators; it doesn't make much sense for + * anything else. + * It is recommended to expose all relevant pointers through + * retro_get_memory_* as well. + * + * Can be called from retro_init and retro_load_game. + */ +#define RETRO_ENVIRONMENT_SET_GEOMETRY 37 + /* const struct retro_game_geometry * -- + * This environment call is similar to SET_SYSTEM_AV_INFO for changing + * video parameters, but provides a guarantee that drivers will not be + * reinitialized. + * This can only be called from within retro_run(). + * + * The purpose of this call is to allow a core to alter nominal + * width/heights as well as aspect ratios on-the-fly, which can be + * useful for some emulators to change in run-time. + * + * max_width/max_height arguments are ignored and cannot be changed + * with this call as this could potentially require a reinitialization or a + * non-constant time operation. + * If max_width/max_height are to be changed, SET_SYSTEM_AV_INFO is required. + * + * A frontend must guarantee that this environment call completes in + * constant time. + */ +#define RETRO_ENVIRONMENT_GET_USERNAME 38 + /* const char ** + * Returns the specified username of the frontend, if specified by the user. + * This username can be used as a nickname for a core that has online facilities + * or any other mode where personalization of the user is desirable. + * The returned value can be NULL. + * If this environ callback is used by a core that requires a valid username, + * a default username should be specified by the core. + */ +#define RETRO_ENVIRONMENT_GET_LANGUAGE 39 + /* unsigned * -- + * Returns the specified language of the frontend, if specified by the user. + * It can be used by the core for localization purposes. + */ +#define RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER (40 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_framebuffer * -- + * Returns a preallocated framebuffer which the core can use for rendering + * the frame into when not using SET_HW_RENDER. + * The framebuffer returned from this call must not be used + * after the current call to retro_run() returns. + * + * The goal of this call is to allow zero-copy behavior where a core + * can render directly into video memory, avoiding extra bandwidth cost by copying + * memory from core to video memory. + * + * If this call succeeds and the core renders into it, + * the framebuffer pointer and pitch can be passed to retro_video_refresh_t. + * If the buffer from GET_CURRENT_SOFTWARE_FRAMEBUFFER is to be used, + * the core must pass the exact + * same pointer as returned by GET_CURRENT_SOFTWARE_FRAMEBUFFER; + * i.e. passing a pointer which is offset from the + * buffer is undefined. The width, height and pitch parameters + * must also match exactly to the values obtained from GET_CURRENT_SOFTWARE_FRAMEBUFFER. + * + * It is possible for a frontend to return a different pixel format + * than the one used in SET_PIXEL_FORMAT. This can happen if the frontend + * needs to perform conversion. + * + * It is still valid for a core to render to a different buffer + * even if GET_CURRENT_SOFTWARE_FRAMEBUFFER succeeds. + * + * A frontend must make sure that the pointer obtained from this function is + * writeable (and readable). + */ +#define RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE (41 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* const struct retro_hw_render_interface ** -- + * Returns an API specific rendering interface for accessing API specific data. + * Not all HW rendering APIs support or need this. + * The contents of the returned pointer is specific to the rendering API + * being used. See the various headers like libretro_vulkan.h, etc. + * + * GET_HW_RENDER_INTERFACE cannot be called before context_reset has been called. + * Similarly, after context_destroyed callback returns, + * the contents of the HW_RENDER_INTERFACE are invalidated. + */ +#define RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS (42 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* const bool * -- + * If true, the libretro implementation supports achievements + * either via memory descriptors set with RETRO_ENVIRONMENT_SET_MEMORY_MAPS + * or via retro_get_memory_data/retro_get_memory_size. + * + * This must be called before the first call to retro_run. + */ +#define RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE (43 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* const struct retro_hw_render_context_negotiation_interface * -- + * Sets an interface which lets the libretro core negotiate with frontend how a context is created. + * The semantics of this interface depends on which API is used in SET_HW_RENDER earlier. + * This interface will be used when the frontend is trying to create a HW rendering context, + * so it will be used after SET_HW_RENDER, but before the context_reset callback. + */ +#define RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS 44 + /* uint64_t * -- + * Sets quirk flags associated with serialization. The frontend will zero any flags it doesn't + * recognize or support. Should be set in either retro_init or retro_load_game, but not both. + */ +#define RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT (44 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* N/A (null) * -- + * The frontend will try to use a 'shared' hardware context (mostly applicable + * to OpenGL) when a hardware context is being set up. + * + * Returns true if the frontend supports shared hardware contexts and false + * if the frontend does not support shared hardware contexts. + * + * This will do nothing on its own until SET_HW_RENDER env callbacks are + * being used. + */ +#define RETRO_ENVIRONMENT_GET_VFS_INTERFACE (45 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_vfs_interface_info * -- + * Gets access to the VFS interface. + * VFS presence needs to be queried prior to load_game or any + * get_system/save/other_directory being called to let front end know + * core supports VFS before it starts handing out paths. + * It is recomended to do so in retro_set_environment + */ +#define RETRO_ENVIRONMENT_GET_LED_INTERFACE (46 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_led_interface * -- + * Gets an interface which is used by a libretro core to set + * state of LEDs. + */ +#define RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE (47 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* int * -- + * Tells the core if the frontend wants audio or video. + * If disabled, the frontend will discard the audio or video, + * so the core may decide to skip generating a frame or generating audio. + * This is mainly used for increasing performance. + * Bit 0 (value 1): Enable Video + * Bit 1 (value 2): Enable Audio + * Bit 2 (value 4): Use Fast Savestates. + * Bit 3 (value 8): Hard Disable Audio + * Other bits are reserved for future use and will default to zero. + * If video is disabled: + * * The frontend wants the core to not generate any video, + * including presenting frames via hardware acceleration. + * * The frontend's video frame callback will do nothing. + * * After running the frame, the video output of the next frame should be + * no different than if video was enabled, and saving and loading state + * should have no issues. + * If audio is disabled: + * * The frontend wants the core to not generate any audio. + * * The frontend's audio callbacks will do nothing. + * * After running the frame, the audio output of the next frame should be + * no different than if audio was enabled, and saving and loading state + * should have no issues. + * Fast Savestates: + * * Guaranteed to be created by the same binary that will load them. + * * Will not be written to or read from the disk. + * * Suggest that the core assumes loading state will succeed. + * * Suggest that the core updates its memory buffers in-place if possible. + * * Suggest that the core skips clearing memory. + * * Suggest that the core skips resetting the system. + * * Suggest that the core may skip validation steps. + * Hard Disable Audio: + * * Used for a secondary core when running ahead. + * * Indicates that the frontend will never need audio from the core. + * * Suggests that the core may stop synthesizing audio, but this should not + * compromise emulation accuracy. + * * Audio output for the next frame does not matter, and the frontend will + * never need an accurate audio state in the future. + * * State will never be saved when using Hard Disable Audio. + */ +#define RETRO_ENVIRONMENT_GET_MIDI_INTERFACE (48 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_midi_interface ** -- + * Returns a MIDI interface that can be used for raw data I/O. + */ + +#define RETRO_ENVIRONMENT_GET_FASTFORWARDING (49 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* bool * -- + * Boolean value that indicates whether or not the frontend is in + * fastforwarding mode. + */ + +#define RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE (50 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* float * -- + * Float value that lets us know what target refresh rate + * is curently in use by the frontend. + * + * The core can use the returned value to set an ideal + * refresh rate/framerate. + */ + +#define RETRO_ENVIRONMENT_GET_INPUT_BITMASKS (51 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* bool * -- + * Boolean value that indicates whether or not the frontend supports + * input bitmasks being returned by retro_input_state_t. The advantage + * of this is that retro_input_state_t has to be only called once to + * grab all button states instead of multiple times. + * + * If it returns true, you can pass RETRO_DEVICE_ID_JOYPAD_MASK as 'id' + * to retro_input_state_t (make sure 'device' is set to RETRO_DEVICE_JOYPAD). + * It will return a bitmask of all the digital buttons. + */ + +#define RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION 52 + /* unsigned * -- + * Unsigned value is the API version number of the core options + * interface supported by the frontend. If callback return false, + * API version is assumed to be 0. + * + * In legacy code, core options are set by passing an array of + * retro_variable structs to RETRO_ENVIRONMENT_SET_VARIABLES. + * This may be still be done regardless of the core options + * interface version. + * + * If version is >= 1 however, core options may instead be set by + * passing an array of retro_core_option_definition structs to + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS, or a 2D array of + * retro_core_option_definition structs to RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL. + * This allows the core to additionally set option sublabel information + * and/or provide localisation support. + * + * If version is >= 2, core options may instead be set by passing + * a retro_core_options_v2 struct to RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, + * or an array of retro_core_options_v2 structs to + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL. This allows the core + * to additionally set optional core option category information + * for frontends with core option category support. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS 53 + /* const struct retro_core_option_definition ** -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION + * returns an API version of >= 1. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * + * 'data' points to an array of retro_core_option_definition structs + * terminated by a { NULL, NULL, NULL, {{0}}, NULL } element. + * retro_core_option_definition::key should be namespaced to not collide + * with other implementations' keys. e.g. A core called + * 'foo' should use keys named as 'foo_option'. + * retro_core_option_definition::desc should contain a human readable + * description of the key. + * retro_core_option_definition::info should contain any additional human + * readable information text that a typical user may need to + * understand the functionality of the option. + * retro_core_option_definition::values is an array of retro_core_option_value + * structs terminated by a { NULL, NULL } element. + * > retro_core_option_definition::values[index].value is an expected option + * value. + * > retro_core_option_definition::values[index].label is a human readable + * label used when displaying the value on screen. If NULL, + * the value itself is used. + * retro_core_option_definition::default_value is the default core option + * setting. It must match one of the expected option values in the + * retro_core_option_definition::values array. If it does not, or the + * default value is NULL, the first entry in the + * retro_core_option_definition::values array is treated as the default. + * + * The number of possible option values should be very limited, + * and must be less than RETRO_NUM_CORE_OPTION_VALUES_MAX. + * i.e. it should be feasible to cycle through options + * without a keyboard. + * + * Example entry: + * { + * "foo_option", + * "Speed hack coprocessor X", + * "Provides increased performance at the expense of reduced accuracy", + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * }, + * "false" + * } + * + * Only strings are operated on. The possible values will + * generally be displayed and stored as-is by the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL 54 + /* const struct retro_core_options_intl * -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION + * returns an API version of >= 1. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * + * This is fundamentally the same as RETRO_ENVIRONMENT_SET_CORE_OPTIONS, + * with the addition of localisation support. The description of the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS callback should be consulted + * for further details. + * + * 'data' points to a retro_core_options_intl struct. + * + * retro_core_options_intl::us is a pointer to an array of + * retro_core_option_definition structs defining the US English + * core options implementation. It must point to a valid array. + * + * retro_core_options_intl::local is a pointer to an array of + * retro_core_option_definition structs defining core options for + * the current frontend language. It may be NULL (in which case + * retro_core_options_intl::us is used by the frontend). Any items + * missing from this array will be read from retro_core_options_intl::us + * instead. + * + * NOTE: Default core option values are always taken from the + * retro_core_options_intl::us array. Any default values in + * retro_core_options_intl::local array will be ignored. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY 55 + /* struct retro_core_option_display * -- + * + * Allows an implementation to signal the environment to show + * or hide a variable when displaying core options. This is + * considered a *suggestion*. The frontend is free to ignore + * this callback, and its implementation not considered mandatory. + * + * 'data' points to a retro_core_option_display struct + * + * retro_core_option_display::key is a variable identifier + * which has already been set by SET_VARIABLES/SET_CORE_OPTIONS. + * + * retro_core_option_display::visible is a boolean, specifying + * whether variable should be displayed + * + * Note that all core option variables will be set visible by + * default when calling SET_VARIABLES/SET_CORE_OPTIONS. + */ + +#define RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER 56 + /* unsigned * -- + * + * Allows an implementation to ask frontend preferred hardware + * context to use. Core should use this information to deal + * with what specific context to request with SET_HW_RENDER. + * + * 'data' points to an unsigned variable + */ + +#define RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION 57 + /* unsigned * -- + * Unsigned value is the API version number of the disk control + * interface supported by the frontend. If callback return false, + * API version is assumed to be 0. + * + * In legacy code, the disk control interface is defined by passing + * a struct of type retro_disk_control_callback to + * RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE. + * This may be still be done regardless of the disk control + * interface version. + * + * If version is >= 1 however, the disk control interface may + * instead be defined by passing a struct of type + * retro_disk_control_ext_callback to + * RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE. + * This allows the core to provide additional information about + * disk images to the frontend and/or enables extra + * disk control functionality by the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE 58 + /* const struct retro_disk_control_ext_callback * -- + * Sets an interface which frontend can use to eject and insert + * disk images, and also obtain information about individual + * disk image files registered by the core. + * This is used for games which consist of multiple images and + * must be manually swapped out by the user (e.g. PSX, floppy disk + * based systems). + */ + +#define RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION 59 + /* unsigned * -- + * Unsigned value is the API version number of the message + * interface supported by the frontend. If callback returns + * false, API version is assumed to be 0. + * + * In legacy code, messages may be displayed in an + * implementation-specific manner by passing a struct + * of type retro_message to RETRO_ENVIRONMENT_SET_MESSAGE. + * This may be still be done regardless of the message + * interface version. + * + * If version is >= 1 however, messages may instead be + * displayed by passing a struct of type retro_message_ext + * to RETRO_ENVIRONMENT_SET_MESSAGE_EXT. This allows the + * core to specify message logging level, priority and + * destination (OSD, logging interface or both). + */ + +#define RETRO_ENVIRONMENT_SET_MESSAGE_EXT 60 + /* const struct retro_message_ext * -- + * Sets a message to be displayed in an implementation-specific + * manner for a certain amount of 'frames'. Additionally allows + * the core to specify message logging level, priority and + * destination (OSD, logging interface or both). + * Should not be used for trivial messages, which should simply be + * logged via RETRO_ENVIRONMENT_GET_LOG_INTERFACE (or as a + * fallback, stderr). + */ + +#define RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS 61 + /* unsigned * -- + * Unsigned value is the number of active input devices + * provided by the frontend. This may change between + * frames, but will remain constant for the duration + * of each frame. + * If callback returns true, a core need not poll any + * input device with an index greater than or equal to + * the number of active devices. + * If callback returns false, the number of active input + * devices is unknown. In this case, all input devices + * should be considered active. + */ + +#define RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK 62 + /* const struct retro_audio_buffer_status_callback * -- + * Lets the core know the occupancy level of the frontend + * audio buffer. Can be used by a core to attempt frame + * skipping in order to avoid buffer under-runs. + * A core may pass NULL to disable buffer status reporting + * in the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY 63 + /* const unsigned * -- + * Sets minimum frontend audio latency in milliseconds. + * Resultant audio latency may be larger than set value, + * or smaller if a hardware limit is encountered. A frontend + * is expected to honour requests up to 512 ms. + * + * - If value is less than current frontend + * audio latency, callback has no effect + * - If value is zero, default frontend audio + * latency is set + * + * May be used by a core to increase audio latency and + * therefore decrease the probability of buffer under-runs + * (crackling) when performing 'intensive' operations. + * A core utilising RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK + * to implement audio-buffer-based frame skipping may achieve + * optimal results by setting the audio latency to a 'high' + * (typically 6x or 8x) integer multiple of the expected + * frame time. + * + * WARNING: This can only be called from within retro_run(). + * Calling this can require a full reinitialization of audio + * drivers in the frontend, so it is important to call it very + * sparingly, and usually only with the users explicit consent. + * An eventual driver reinitialize will happen so that audio + * callbacks happening after this call within the same retro_run() + * call will target the newly initialized driver. + */ + +#define RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE 64 + /* const struct retro_fastforwarding_override * -- + * Used by a libretro core to override the current + * fastforwarding mode of the frontend. + * If NULL is passed to this function, the frontend + * will return true if fastforwarding override + * functionality is supported (no change in + * fastforwarding state will occur in this case). + */ + +#define RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE 65 + /* const struct retro_system_content_info_override * -- + * Allows an implementation to override 'global' content + * info parameters reported by retro_get_system_info(). + * Overrides also affect subsystem content info parameters + * set via RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO. + * This function must be called inside retro_set_environment(). + * If callback returns false, content info overrides + * are unsupported by the frontend, and will be ignored. + * If callback returns true, extended game info may be + * retrieved by calling RETRO_ENVIRONMENT_GET_GAME_INFO_EXT + * in retro_load_game() or retro_load_game_special(). + * + * 'data' points to an array of retro_system_content_info_override + * structs terminated by a { NULL, false, false } element. + * If 'data' is NULL, no changes will be made to the frontend; + * a core may therefore pass NULL in order to test whether + * the RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE and + * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT callbacks are supported + * by the frontend. + * + * For struct member descriptions, see the definition of + * struct retro_system_content_info_override. + * + * Example: + * + * - struct retro_system_info: + * { + * "My Core", // library_name + * "v1.0", // library_version + * "m3u|md|cue|iso|chd|sms|gg|sg", // valid_extensions + * true, // need_fullpath + * false // block_extract + * } + * + * - Array of struct retro_system_content_info_override: + * { + * { + * "md|sms|gg", // extensions + * false, // need_fullpath + * true // persistent_data + * }, + * { + * "sg", // extensions + * false, // need_fullpath + * false // persistent_data + * }, + * { NULL, false, false } + * } + * + * Result: + * - Files of type m3u, cue, iso, chd will not be + * loaded by the frontend. Frontend will pass a + * valid path to the core, and core will handle + * loading internally + * - Files of type md, sms, gg will be loaded by + * the frontend. A valid memory buffer will be + * passed to the core. This memory buffer will + * remain valid until retro_deinit() returns + * - Files of type sg will be loaded by the frontend. + * A valid memory buffer will be passed to the core. + * This memory buffer will remain valid until + * retro_load_game() (or retro_load_game_special()) + * returns + * + * NOTE: If an extension is listed multiple times in + * an array of retro_system_content_info_override + * structs, only the first instance will be registered + */ + +#define RETRO_ENVIRONMENT_GET_GAME_INFO_EXT 66 + /* const struct retro_game_info_ext ** -- + * Allows an implementation to fetch extended game + * information, providing additional content path + * and memory buffer status details. + * This function may only be called inside + * retro_load_game() or retro_load_game_special(). + * If callback returns false, extended game information + * is unsupported by the frontend. In this case, only + * regular retro_game_info will be available. + * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT is guaranteed + * to return true if RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE + * returns true. + * + * 'data' points to an array of retro_game_info_ext structs. + * + * For struct member descriptions, see the definition of + * struct retro_game_info_ext. + * + * - If function is called inside retro_load_game(), + * the retro_game_info_ext array is guaranteed to + * have a size of 1 - i.e. the returned pointer may + * be used to access directly the members of the + * first retro_game_info_ext struct, for example: + * + * struct retro_game_info_ext *game_info_ext; + * if (environ_cb(RETRO_ENVIRONMENT_GET_GAME_INFO_EXT, &game_info_ext)) + * printf("Content Directory: %s\n", game_info_ext->dir); + * + * - If the function is called inside retro_load_game_special(), + * the retro_game_info_ext array is guaranteed to have a + * size equal to the num_info argument passed to + * retro_load_game_special() + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 67 + /* const struct retro_core_options_v2 * -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION + * returns an API version of >= 2. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API + * version of >= 2, this callback is guaranteed to succeed + * (i.e. callback return value does not indicate success) + * If callback returns true, frontend has core option category + * support. + * If callback returns false, frontend does not have core option + * category support. + * + * 'data' points to a retro_core_options_v2 struct, containing + * of two pointers: + * - retro_core_options_v2::categories is an array of + * retro_core_option_v2_category structs terminated by a + * { NULL, NULL, NULL } element. If retro_core_options_v2::categories + * is NULL, all core options will have no category and will be shown + * at the top level of the frontend core option interface. If frontend + * does not have core option category support, categories array will + * be ignored. + * - retro_core_options_v2::definitions is an array of + * retro_core_option_v2_definition structs terminated by a + * { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL } + * element. + * + * >> retro_core_option_v2_category notes: + * + * - retro_core_option_v2_category::key should contain string + * that uniquely identifies the core option category. Valid + * key characters are [a-z, A-Z, 0-9, _, -] + * Namespace collisions with other implementations' category + * keys are permitted. + * - retro_core_option_v2_category::desc should contain a human + * readable description of the category key. + * - retro_core_option_v2_category::info should contain any + * additional human readable information text that a typical + * user may need to understand the nature of the core option + * category. + * + * Example entry: + * { + * "advanced_settings", + * "Advanced", + * "Options affecting low-level emulation performance and accuracy." + * } + * + * >> retro_core_option_v2_definition notes: + * + * - retro_core_option_v2_definition::key should be namespaced to not + * collide with other implementations' keys. e.g. A core called + * 'foo' should use keys named as 'foo_option'. Valid key characters + * are [a-z, A-Z, 0-9, _, -]. + * - retro_core_option_v2_definition::desc should contain a human readable + * description of the key. Will be used when the frontend does not + * have core option category support. Examples: "Aspect Ratio" or + * "Video > Aspect Ratio". + * - retro_core_option_v2_definition::desc_categorized should contain a + * human readable description of the key, which will be used when + * frontend has core option category support. Example: "Aspect Ratio", + * where associated retro_core_option_v2_category::desc is "Video". + * If empty or NULL, the string specified by + * retro_core_option_v2_definition::desc will be used instead. + * retro_core_option_v2_definition::desc_categorized will be ignored + * if retro_core_option_v2_definition::category_key is empty or NULL. + * - retro_core_option_v2_definition::info should contain any additional + * human readable information text that a typical user may need to + * understand the functionality of the option. + * - retro_core_option_v2_definition::info_categorized should contain + * any additional human readable information text that a typical user + * may need to understand the functionality of the option, and will be + * used when frontend has core option category support. This is provided + * to accommodate the case where info text references an option by + * name/desc, and the desc/desc_categorized text for that option differ. + * If empty or NULL, the string specified by + * retro_core_option_v2_definition::info will be used instead. + * retro_core_option_v2_definition::info_categorized will be ignored + * if retro_core_option_v2_definition::category_key is empty or NULL. + * - retro_core_option_v2_definition::category_key should contain a + * category identifier (e.g. "video" or "audio") that will be + * assigned to the core option if frontend has core option category + * support. A categorized option will be shown in a subsection/ + * submenu of the frontend core option interface. If key is empty + * or NULL, or if key does not match one of the + * retro_core_option_v2_category::key values in the associated + * retro_core_option_v2_category array, option will have no category + * and will be shown at the top level of the frontend core option + * interface. + * - retro_core_option_v2_definition::values is an array of + * retro_core_option_value structs terminated by a { NULL, NULL } + * element. + * --> retro_core_option_v2_definition::values[index].value is an + * expected option value. + * --> retro_core_option_v2_definition::values[index].label is a + * human readable label used when displaying the value on screen. + * If NULL, the value itself is used. + * - retro_core_option_v2_definition::default_value is the default + * core option setting. It must match one of the expected option + * values in the retro_core_option_v2_definition::values array. If + * it does not, or the default value is NULL, the first entry in the + * retro_core_option_v2_definition::values array is treated as the + * default. + * + * The number of possible option values should be very limited, + * and must be less than RETRO_NUM_CORE_OPTION_VALUES_MAX. + * i.e. it should be feasible to cycle through options + * without a keyboard. + * + * Example entries: + * + * - Uncategorized: + * + * { + * "foo_option", + * "Speed hack coprocessor X", + * NULL, + * "Provides increased performance at the expense of reduced accuracy.", + * NULL, + * NULL, + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * }, + * "false" + * } + * + * - Categorized: + * + * { + * "foo_option", + * "Advanced > Speed hack coprocessor X", + * "Speed hack coprocessor X", + * "Setting 'Advanced > Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy", + * "Setting 'Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy", + * "advanced_settings", + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * }, + * "false" + * } + * + * Only strings are operated on. The possible values will + * generally be displayed and stored as-is by the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL 68 + /* const struct retro_core_options_v2_intl * -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION + * returns an API version of >= 2. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API + * version of >= 2, this callback is guaranteed to succeed + * (i.e. callback return value does not indicate success) + * If callback returns true, frontend has core option category + * support. + * If callback returns false, frontend does not have core option + * category support. + * + * This is fundamentally the same as RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, + * with the addition of localisation support. The description of the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 callback should be consulted + * for further details. + * + * 'data' points to a retro_core_options_v2_intl struct. + * + * - retro_core_options_v2_intl::us is a pointer to a + * retro_core_options_v2 struct defining the US English + * core options implementation. It must point to a valid struct. + * + * - retro_core_options_v2_intl::local is a pointer to a + * retro_core_options_v2 struct defining core options for + * the current frontend language. It may be NULL (in which case + * retro_core_options_v2_intl::us is used by the frontend). Any items + * missing from this struct will be read from + * retro_core_options_v2_intl::us instead. + * + * NOTE: Default core option values are always taken from the + * retro_core_options_v2_intl::us struct. Any default values in + * the retro_core_options_v2_intl::local struct will be ignored. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK 69 + /* const struct retro_core_options_update_display_callback * -- + * Allows a frontend to signal that a core must update + * the visibility of any dynamically hidden core options, + * and enables the frontend to detect visibility changes. + * Used by the frontend to update the menu display status + * of core options without requiring a call of retro_run(). + * Must be called in retro_set_environment(). + */ + +#define RETRO_ENVIRONMENT_SET_VARIABLE 70 + /* const struct retro_variable * -- + * Allows an implementation to notify the frontend + * that a core option value has changed. + * + * retro_variable::key and retro_variable::value + * must match strings that have been set previously + * via one of the following: + * + * - RETRO_ENVIRONMENT_SET_VARIABLES + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 + * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL + * + * After changing a core option value via this + * callback, RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE + * will return true. + * + * If data is NULL, no changes will be registered + * and the callback will return true; an + * implementation may therefore pass NULL in order + * to test whether the callback is supported. + */ + +#define RETRO_ENVIRONMENT_GET_THROTTLE_STATE (71 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_throttle_state * -- + * Allows an implementation to get details on the actual rate + * the frontend is attempting to call retro_run(). + */ + +/* VFS functionality */ + +/* File paths: + * File paths passed as parameters when using this API shall be well formed UNIX-style, + * using "/" (unquoted forward slash) as directory separator regardless of the platform's native separator. + * Paths shall also include at least one forward slash ("game.bin" is an invalid path, use "./game.bin" instead). + * Other than the directory separator, cores shall not make assumptions about path format: + * "C:/path/game.bin", "http://example.com/game.bin", "#game/game.bin", "./game.bin" (without quotes) are all valid paths. + * Cores may replace the basename or remove path components from the end, and/or add new components; + * however, cores shall not append "./", "../" or multiple consecutive forward slashes ("//") to paths they request to front end. + * The frontend is encouraged to make such paths work as well as it can, but is allowed to give up if the core alters paths too much. + * Frontends are encouraged, but not required, to support native file system paths (modulo replacing the directory separator, if applicable). + * Cores are allowed to try using them, but must remain functional if the front rejects such requests. + * Cores are encouraged to use the libretro-common filestream functions for file I/O, + * as they seamlessly integrate with VFS, deal with directory separator replacement as appropriate + * and provide platform-specific fallbacks in cases where front ends do not support VFS. */ + +/* Opaque file handle + * Introduced in VFS API v1 */ +struct retro_vfs_file_handle; + +/* Opaque directory handle + * Introduced in VFS API v3 */ +struct retro_vfs_dir_handle; + +/* File open flags + * Introduced in VFS API v1 */ +#define RETRO_VFS_FILE_ACCESS_READ (1 << 0) /* Read only mode */ +#define RETRO_VFS_FILE_ACCESS_WRITE (1 << 1) /* Write only mode, discard contents and overwrites existing file unless RETRO_VFS_FILE_ACCESS_UPDATE is also specified */ +#define RETRO_VFS_FILE_ACCESS_READ_WRITE (RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE) /* Read-write mode, discard contents and overwrites existing file unless RETRO_VFS_FILE_ACCESS_UPDATE is also specified*/ +#define RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING (1 << 2) /* Prevents discarding content of existing files opened for writing */ + +/* These are only hints. The frontend may choose to ignore them. Other than RAM/CPU/etc use, + and how they react to unlikely external interference (for example someone else writing to that file, + or the file's server going down), behavior will not change. */ +#define RETRO_VFS_FILE_ACCESS_HINT_NONE (0) +/* Indicate that the file will be accessed many times. The frontend should aggressively cache everything. */ +#define RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS (1 << 0) + +/* Seek positions */ +#define RETRO_VFS_SEEK_POSITION_START 0 +#define RETRO_VFS_SEEK_POSITION_CURRENT 1 +#define RETRO_VFS_SEEK_POSITION_END 2 + +/* stat() result flags + * Introduced in VFS API v3 */ +#define RETRO_VFS_STAT_IS_VALID (1 << 0) +#define RETRO_VFS_STAT_IS_DIRECTORY (1 << 1) +#define RETRO_VFS_STAT_IS_CHARACTER_SPECIAL (1 << 2) + +/* Get path from opaque handle. Returns the exact same path passed to file_open when getting the handle + * Introduced in VFS API v1 */ +typedef const char *(RETRO_CALLCONV *retro_vfs_get_path_t)(struct retro_vfs_file_handle *stream); + +/* Open a file for reading or writing. If path points to a directory, this will + * fail. Returns the opaque file handle, or NULL for error. + * Introduced in VFS API v1 */ +typedef struct retro_vfs_file_handle *(RETRO_CALLCONV *retro_vfs_open_t)(const char *path, unsigned mode, unsigned hints); + +/* Close the file and release its resources. Must be called if open_file returns non-NULL. Returns 0 on success, -1 on failure. + * Whether the call succeeds ot not, the handle passed as parameter becomes invalid and should no longer be used. + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_close_t)(struct retro_vfs_file_handle *stream); + +/* Return the size of the file in bytes, or -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_size_t)(struct retro_vfs_file_handle *stream); + +/* Truncate file to specified size. Returns 0 on success or -1 on error + * Introduced in VFS API v2 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_truncate_t)(struct retro_vfs_file_handle *stream, int64_t length); + +/* Get the current read / write position for the file. Returns -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_tell_t)(struct retro_vfs_file_handle *stream); + +/* Set the current read/write position for the file. Returns the new position, -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_seek_t)(struct retro_vfs_file_handle *stream, int64_t offset, int seek_position); + +/* Read data from a file. Returns the number of bytes read, or -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_read_t)(struct retro_vfs_file_handle *stream, void *s, uint64_t len); + +/* Write data to a file. Returns the number of bytes written, or -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_write_t)(struct retro_vfs_file_handle *stream, const void *s, uint64_t len); + +/* Flush pending writes to file, if using buffered IO. Returns 0 on sucess, or -1 on failure. + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_flush_t)(struct retro_vfs_file_handle *stream); + +/* Delete the specified file. Returns 0 on success, -1 on failure + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_remove_t)(const char *path); + +/* Rename the specified file. Returns 0 on success, -1 on failure + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_rename_t)(const char *old_path, const char *new_path); + +/* Stat the specified file. Retruns a bitmask of RETRO_VFS_STAT_* flags, none are set if path was not valid. + * Additionally stores file size in given variable, unless NULL is given. + * Introduced in VFS API v3 */ +typedef int (RETRO_CALLCONV *retro_vfs_stat_t)(const char *path, int32_t *size); + +/* Create the specified directory. Returns 0 on success, -1 on unknown failure, -2 if already exists. + * Introduced in VFS API v3 */ +typedef int (RETRO_CALLCONV *retro_vfs_mkdir_t)(const char *dir); + +/* Open the specified directory for listing. Returns the opaque dir handle, or NULL for error. + * Support for the include_hidden argument may vary depending on the platform. + * Introduced in VFS API v3 */ +typedef struct retro_vfs_dir_handle *(RETRO_CALLCONV *retro_vfs_opendir_t)(const char *dir, bool include_hidden); + +/* Read the directory entry at the current position, and move the read pointer to the next position. + * Returns true on success, false if already on the last entry. + * Introduced in VFS API v3 */ +typedef bool (RETRO_CALLCONV *retro_vfs_readdir_t)(struct retro_vfs_dir_handle *dirstream); + +/* Get the name of the last entry read. Returns a string on success, or NULL for error. + * The returned string pointer is valid until the next call to readdir or closedir. + * Introduced in VFS API v3 */ +typedef const char *(RETRO_CALLCONV *retro_vfs_dirent_get_name_t)(struct retro_vfs_dir_handle *dirstream); + +/* Check if the last entry read was a directory. Returns true if it was, false otherwise (or on error). + * Introduced in VFS API v3 */ +typedef bool (RETRO_CALLCONV *retro_vfs_dirent_is_dir_t)(struct retro_vfs_dir_handle *dirstream); + +/* Close the directory and release its resources. Must be called if opendir returns non-NULL. Returns 0 on success, -1 on failure. + * Whether the call succeeds ot not, the handle passed as parameter becomes invalid and should no longer be used. + * Introduced in VFS API v3 */ +typedef int (RETRO_CALLCONV *retro_vfs_closedir_t)(struct retro_vfs_dir_handle *dirstream); + +struct retro_vfs_interface +{ + /* VFS API v1 */ + retro_vfs_get_path_t get_path; + retro_vfs_open_t open; + retro_vfs_close_t close; + retro_vfs_size_t size; + retro_vfs_tell_t tell; + retro_vfs_seek_t seek; + retro_vfs_read_t read; + retro_vfs_write_t write; + retro_vfs_flush_t flush; + retro_vfs_remove_t remove; + retro_vfs_rename_t rename; + /* VFS API v2 */ + retro_vfs_truncate_t truncate; + /* VFS API v3 */ + retro_vfs_stat_t stat; + retro_vfs_mkdir_t mkdir; + retro_vfs_opendir_t opendir; + retro_vfs_readdir_t readdir; + retro_vfs_dirent_get_name_t dirent_get_name; + retro_vfs_dirent_is_dir_t dirent_is_dir; + retro_vfs_closedir_t closedir; +}; + +struct retro_vfs_interface_info +{ + /* Set by core: should this be higher than the version the front end supports, + * front end will return false in the RETRO_ENVIRONMENT_GET_VFS_INTERFACE call + * Introduced in VFS API v1 */ + uint32_t required_interface_version; + + /* Frontend writes interface pointer here. The frontend also sets the actual + * version, must be at least required_interface_version. + * Introduced in VFS API v1 */ + struct retro_vfs_interface *iface; +}; + +enum retro_hw_render_interface_type +{ + RETRO_HW_RENDER_INTERFACE_VULKAN = 0, + RETRO_HW_RENDER_INTERFACE_D3D9 = 1, + RETRO_HW_RENDER_INTERFACE_D3D10 = 2, + RETRO_HW_RENDER_INTERFACE_D3D11 = 3, + RETRO_HW_RENDER_INTERFACE_D3D12 = 4, + RETRO_HW_RENDER_INTERFACE_GSKIT_PS2 = 5, + RETRO_HW_RENDER_INTERFACE_DUMMY = INT_MAX +}; + +/* Base struct. All retro_hw_render_interface_* types + * contain at least these fields. */ +struct retro_hw_render_interface +{ + enum retro_hw_render_interface_type interface_type; + unsigned interface_version; +}; + +typedef void (RETRO_CALLCONV *retro_set_led_state_t)(int led, int state); +struct retro_led_interface +{ + retro_set_led_state_t set_led_state; +}; + +/* Retrieves the current state of the MIDI input. + * Returns true if it's enabled, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_input_enabled_t)(void); + +/* Retrieves the current state of the MIDI output. + * Returns true if it's enabled, false otherwise */ +typedef bool (RETRO_CALLCONV *retro_midi_output_enabled_t)(void); + +/* Reads next byte from the input stream. + * Returns true if byte is read, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_read_t)(uint8_t *byte); + +/* Writes byte to the output stream. + * 'delta_time' is in microseconds and represent time elapsed since previous write. + * Returns true if byte is written, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_write_t)(uint8_t byte, uint32_t delta_time); + +/* Flushes previously written data. + * Returns true if successful, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_flush_t)(void); + +struct retro_midi_interface +{ + retro_midi_input_enabled_t input_enabled; + retro_midi_output_enabled_t output_enabled; + retro_midi_read_t read; + retro_midi_write_t write; + retro_midi_flush_t flush; +}; + +enum retro_hw_render_context_negotiation_interface_type +{ + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN = 0, + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_DUMMY = INT_MAX +}; + +/* Base struct. All retro_hw_render_context_negotiation_interface_* types + * contain at least these fields. */ +struct retro_hw_render_context_negotiation_interface +{ + enum retro_hw_render_context_negotiation_interface_type interface_type; + unsigned interface_version; +}; + +/* Serialized state is incomplete in some way. Set if serialization is + * usable in typical end-user cases but should not be relied upon to + * implement frame-sensitive frontend features such as netplay or + * rerecording. */ +#define RETRO_SERIALIZATION_QUIRK_INCOMPLETE (1 << 0) +/* The core must spend some time initializing before serialization is + * supported. retro_serialize() will initially fail; retro_unserialize() + * and retro_serialize_size() may or may not work correctly either. */ +#define RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE (1 << 1) +/* Serialization size may change within a session. */ +#define RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE (1 << 2) +/* Set by the frontend to acknowledge that it supports variable-sized + * states. */ +#define RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE (1 << 3) +/* Serialized state can only be loaded during the same session. */ +#define RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION (1 << 4) +/* Serialized state cannot be loaded on an architecture with a different + * endianness from the one it was saved on. */ +#define RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT (1 << 5) +/* Serialized state cannot be loaded on a different platform from the one it + * was saved on for reasons other than endianness, such as word size + * dependence */ +#define RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT (1 << 6) + +#define RETRO_MEMDESC_CONST (1 << 0) /* The frontend will never change this memory area once retro_load_game has returned. */ +#define RETRO_MEMDESC_BIGENDIAN (1 << 1) /* The memory area contains big endian data. Default is little endian. */ +#define RETRO_MEMDESC_SYSTEM_RAM (1 << 2) /* The memory area is system RAM. This is main RAM of the gaming system. */ +#define RETRO_MEMDESC_SAVE_RAM (1 << 3) /* The memory area is save RAM. This RAM is usually found on a game cartridge, backed up by a battery. */ +#define RETRO_MEMDESC_VIDEO_RAM (1 << 4) /* The memory area is video RAM (VRAM) */ +#define RETRO_MEMDESC_ALIGN_2 (1 << 16) /* All memory access in this area is aligned to their own size, or 2, whichever is smaller. */ +#define RETRO_MEMDESC_ALIGN_4 (2 << 16) +#define RETRO_MEMDESC_ALIGN_8 (3 << 16) +#define RETRO_MEMDESC_MINSIZE_2 (1 << 24) /* All memory in this region is accessed at least 2 bytes at the time. */ +#define RETRO_MEMDESC_MINSIZE_4 (2 << 24) +#define RETRO_MEMDESC_MINSIZE_8 (3 << 24) +struct retro_memory_descriptor +{ + uint64_t flags; + + /* Pointer to the start of the relevant ROM or RAM chip. + * It's strongly recommended to use 'offset' if possible, rather than + * doing math on the pointer. + * + * If the same byte is mapped my multiple descriptors, their descriptors + * must have the same pointer. + * If 'start' does not point to the first byte in the pointer, put the + * difference in 'offset' instead. + * + * May be NULL if there's nothing usable here (e.g. hardware registers and + * open bus). No flags should be set if the pointer is NULL. + * It's recommended to minimize the number of descriptors if possible, + * but not mandatory. */ + void *ptr; + size_t offset; + + /* This is the location in the emulated address space + * where the mapping starts. */ + size_t start; + + /* Which bits must be same as in 'start' for this mapping to apply. + * The first memory descriptor to claim a certain byte is the one + * that applies. + * A bit which is set in 'start' must also be set in this. + * Can be zero, in which case each byte is assumed mapped exactly once. + * In this case, 'len' must be a power of two. */ + size_t select; + + /* If this is nonzero, the set bits are assumed not connected to the + * memory chip's address pins. */ + size_t disconnect; + + /* This one tells the size of the current memory area. + * If, after start+disconnect are applied, the address is higher than + * this, the highest bit of the address is cleared. + * + * If the address is still too high, the next highest bit is cleared. + * Can be zero, in which case it's assumed to be infinite (as limited + * by 'select' and 'disconnect'). */ + size_t len; + + /* To go from emulated address to physical address, the following + * order applies: + * Subtract 'start', pick off 'disconnect', apply 'len', add 'offset'. */ + + /* The address space name must consist of only a-zA-Z0-9_-, + * should be as short as feasible (maximum length is 8 plus the NUL), + * and may not be any other address space plus one or more 0-9A-F + * at the end. + * However, multiple memory descriptors for the same address space is + * allowed, and the address space name can be empty. NULL is treated + * as empty. + * + * Address space names are case sensitive, but avoid lowercase if possible. + * The same pointer may exist in multiple address spaces. + * + * Examples: + * blank+blank - valid (multiple things may be mapped in the same namespace) + * 'Sp'+'Sp' - valid (multiple things may be mapped in the same namespace) + * 'A'+'B' - valid (neither is a prefix of each other) + * 'S'+blank - valid ('S' is not in 0-9A-F) + * 'a'+blank - valid ('a' is not in 0-9A-F) + * 'a'+'A' - valid (neither is a prefix of each other) + * 'AR'+blank - valid ('R' is not in 0-9A-F) + * 'ARB'+blank - valid (the B can't be part of the address either, because + * there is no namespace 'AR') + * blank+'B' - not valid, because it's ambigous which address space B1234 + * would refer to. + * The length can't be used for that purpose; the frontend may want + * to append arbitrary data to an address, without a separator. */ + const char *addrspace; + + /* TODO: When finalizing this one, add a description field, which should be + * "WRAM" or something roughly equally long. */ + + /* TODO: When finalizing this one, replace 'select' with 'limit', which tells + * which bits can vary and still refer to the same address (limit = ~select). + * TODO: limit? range? vary? something else? */ + + /* TODO: When finalizing this one, if 'len' is above what 'select' (or + * 'limit') allows, it's bankswitched. Bankswitched data must have both 'len' + * and 'select' != 0, and the mappings don't tell how the system switches the + * banks. */ + + /* TODO: When finalizing this one, fix the 'len' bit removal order. + * For len=0x1800, pointer 0x1C00 should go to 0x1400, not 0x0C00. + * Algorithm: Take bits highest to lowest, but if it goes above len, clear + * the most recent addition and continue on the next bit. + * TODO: Can the above be optimized? Is "remove the lowest bit set in both + * pointer and 'len'" equivalent? */ + + /* TODO: Some emulators (MAME?) emulate big endian systems by only accessing + * the emulated memory in 32-bit chunks, native endian. But that's nothing + * compared to Darek Mihocka + * (section Emulation 103 - Nearly Free Byte Reversal) - he flips the ENTIRE + * RAM backwards! I'll want to represent both of those, via some flags. + * + * I suspect MAME either didn't think of that idea, or don't want the #ifdef. + * Not sure which, nor do I really care. */ + + /* TODO: Some of those flags are unused and/or don't really make sense. Clean + * them up. */ +}; + +/* The frontend may use the largest value of 'start'+'select' in a + * certain namespace to infer the size of the address space. + * + * If the address space is larger than that, a mapping with .ptr=NULL + * should be at the end of the array, with .select set to all ones for + * as long as the address space is big. + * + * Sample descriptors (minus .ptr, and RETRO_MEMFLAG_ on the flags): + * SNES WRAM: + * .start=0x7E0000, .len=0x20000 + * (Note that this must be mapped before the ROM in most cases; some of the + * ROM mappers + * try to claim $7E0000, or at least $7E8000.) + * SNES SPC700 RAM: + * .addrspace="S", .len=0x10000 + * SNES WRAM mirrors: + * .flags=MIRROR, .start=0x000000, .select=0xC0E000, .len=0x2000 + * .flags=MIRROR, .start=0x800000, .select=0xC0E000, .len=0x2000 + * SNES WRAM mirrors, alternate equivalent descriptor: + * .flags=MIRROR, .select=0x40E000, .disconnect=~0x1FFF + * (Various similar constructions can be created by combining parts of + * the above two.) + * SNES LoROM (512KB, mirrored a couple of times): + * .flags=CONST, .start=0x008000, .select=0x408000, .disconnect=0x8000, .len=512*1024 + * .flags=CONST, .start=0x400000, .select=0x400000, .disconnect=0x8000, .len=512*1024 + * SNES HiROM (4MB): + * .flags=CONST, .start=0x400000, .select=0x400000, .len=4*1024*1024 + * .flags=CONST, .offset=0x8000, .start=0x008000, .select=0x408000, .len=4*1024*1024 + * SNES ExHiROM (8MB): + * .flags=CONST, .offset=0, .start=0xC00000, .select=0xC00000, .len=4*1024*1024 + * .flags=CONST, .offset=4*1024*1024, .start=0x400000, .select=0xC00000, .len=4*1024*1024 + * .flags=CONST, .offset=0x8000, .start=0x808000, .select=0xC08000, .len=4*1024*1024 + * .flags=CONST, .offset=4*1024*1024+0x8000, .start=0x008000, .select=0xC08000, .len=4*1024*1024 + * Clarify the size of the address space: + * .ptr=NULL, .select=0xFFFFFF + * .len can be implied by .select in many of them, but was included for clarity. + */ + +struct retro_memory_map +{ + const struct retro_memory_descriptor *descriptors; + unsigned num_descriptors; +}; + +struct retro_controller_description +{ + /* Human-readable description of the controller. Even if using a generic + * input device type, this can be set to the particular device type the + * core uses. */ + const char *desc; + + /* Device type passed to retro_set_controller_port_device(). If the device + * type is a sub-class of a generic input device type, use the + * RETRO_DEVICE_SUBCLASS macro to create an ID. + * + * E.g. RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1). */ + unsigned id; +}; + +struct retro_controller_info +{ + const struct retro_controller_description *types; + unsigned num_types; +}; + +struct retro_subsystem_memory_info +{ + /* The extension associated with a memory type, e.g. "psram". */ + const char *extension; + + /* The memory type for retro_get_memory(). This should be at + * least 0x100 to avoid conflict with standardized + * libretro memory types. */ + unsigned type; +}; + +struct retro_subsystem_rom_info +{ + /* Describes what the content is (SGB BIOS, GB ROM, etc). */ + const char *desc; + + /* Same definition as retro_get_system_info(). */ + const char *valid_extensions; + + /* Same definition as retro_get_system_info(). */ + bool need_fullpath; + + /* Same definition as retro_get_system_info(). */ + bool block_extract; + + /* This is set if the content is required to load a game. + * If this is set to false, a zeroed-out retro_game_info can be passed. */ + bool required; + + /* Content can have multiple associated persistent + * memory types (retro_get_memory()). */ + const struct retro_subsystem_memory_info *memory; + unsigned num_memory; +}; + +struct retro_subsystem_info +{ + /* Human-readable string of the subsystem type, e.g. "Super GameBoy" */ + const char *desc; + + /* A computer friendly short string identifier for the subsystem type. + * This name must be [a-z]. + * E.g. if desc is "Super GameBoy", this can be "sgb". + * This identifier can be used for command-line interfaces, etc. + */ + const char *ident; + + /* Infos for each content file. The first entry is assumed to be the + * "most significant" content for frontend purposes. + * E.g. with Super GameBoy, the first content should be the GameBoy ROM, + * as it is the most "significant" content to a user. + * If a frontend creates new file paths based on the content used + * (e.g. savestates), it should use the path for the first ROM to do so. */ + const struct retro_subsystem_rom_info *roms; + + /* Number of content files associated with a subsystem. */ + unsigned num_roms; + + /* The type passed to retro_load_game_special(). */ + unsigned id; +}; + +typedef void (RETRO_CALLCONV *retro_proc_address_t)(void); + +/* libretro API extension functions: + * (None here so far). + * + * Get a symbol from a libretro core. + * Cores should only return symbols which are actual + * extensions to the libretro API. + * + * Frontends should not use this to obtain symbols to standard + * libretro entry points (static linking or dlsym). + * + * The symbol name must be equal to the function name, + * e.g. if void retro_foo(void); exists, the symbol must be called "retro_foo". + * The returned function pointer must be cast to the corresponding type. + */ +typedef retro_proc_address_t (RETRO_CALLCONV *retro_get_proc_address_t)(const char *sym); + +struct retro_get_proc_address_interface +{ + retro_get_proc_address_t get_proc_address; +}; + +enum retro_log_level +{ + RETRO_LOG_DEBUG = 0, + RETRO_LOG_INFO, + RETRO_LOG_WARN, + RETRO_LOG_ERROR, + + RETRO_LOG_DUMMY = INT_MAX +}; + +/* Logging function. Takes log level argument as well. */ +typedef void (RETRO_CALLCONV *retro_log_printf_t)(enum retro_log_level level, + const char *fmt, ...); + +struct retro_log_callback +{ + retro_log_printf_t log; +}; + +/* Performance related functions */ + +/* ID values for SIMD CPU features */ +#define RETRO_SIMD_SSE (1 << 0) +#define RETRO_SIMD_SSE2 (1 << 1) +#define RETRO_SIMD_VMX (1 << 2) +#define RETRO_SIMD_VMX128 (1 << 3) +#define RETRO_SIMD_AVX (1 << 4) +#define RETRO_SIMD_NEON (1 << 5) +#define RETRO_SIMD_SSE3 (1 << 6) +#define RETRO_SIMD_SSSE3 (1 << 7) +#define RETRO_SIMD_MMX (1 << 8) +#define RETRO_SIMD_MMXEXT (1 << 9) +#define RETRO_SIMD_SSE4 (1 << 10) +#define RETRO_SIMD_SSE42 (1 << 11) +#define RETRO_SIMD_AVX2 (1 << 12) +#define RETRO_SIMD_VFPU (1 << 13) +#define RETRO_SIMD_PS (1 << 14) +#define RETRO_SIMD_AES (1 << 15) +#define RETRO_SIMD_VFPV3 (1 << 16) +#define RETRO_SIMD_VFPV4 (1 << 17) +#define RETRO_SIMD_POPCNT (1 << 18) +#define RETRO_SIMD_MOVBE (1 << 19) +#define RETRO_SIMD_CMOV (1 << 20) +#define RETRO_SIMD_ASIMD (1 << 21) + +typedef uint64_t retro_perf_tick_t; +typedef int64_t retro_time_t; + +struct retro_perf_counter +{ + const char *ident; + retro_perf_tick_t start; + retro_perf_tick_t total; + retro_perf_tick_t call_cnt; + + bool registered; +}; + +/* Returns current time in microseconds. + * Tries to use the most accurate timer available. + */ +typedef retro_time_t (RETRO_CALLCONV *retro_perf_get_time_usec_t)(void); + +/* A simple counter. Usually nanoseconds, but can also be CPU cycles. + * Can be used directly if desired (when creating a more sophisticated + * performance counter system). + * */ +typedef retro_perf_tick_t (RETRO_CALLCONV *retro_perf_get_counter_t)(void); + +/* Returns a bit-mask of detected CPU features (RETRO_SIMD_*). */ +typedef uint64_t (RETRO_CALLCONV *retro_get_cpu_features_t)(void); + +/* Asks frontend to log and/or display the state of performance counters. + * Performance counters can always be poked into manually as well. + */ +typedef void (RETRO_CALLCONV *retro_perf_log_t)(void); + +/* Register a performance counter. + * ident field must be set with a discrete value and other values in + * retro_perf_counter must be 0. + * Registering can be called multiple times. To avoid calling to + * frontend redundantly, you can check registered field first. */ +typedef void (RETRO_CALLCONV *retro_perf_register_t)(struct retro_perf_counter *counter); + +/* Starts a registered counter. */ +typedef void (RETRO_CALLCONV *retro_perf_start_t)(struct retro_perf_counter *counter); + +/* Stops a registered counter. */ +typedef void (RETRO_CALLCONV *retro_perf_stop_t)(struct retro_perf_counter *counter); + +/* For convenience it can be useful to wrap register, start and stop in macros. + * E.g.: + * #ifdef LOG_PERFORMANCE + * #define RETRO_PERFORMANCE_INIT(perf_cb, name) static struct retro_perf_counter name = {#name}; if (!name.registered) perf_cb.perf_register(&(name)) + * #define RETRO_PERFORMANCE_START(perf_cb, name) perf_cb.perf_start(&(name)) + * #define RETRO_PERFORMANCE_STOP(perf_cb, name) perf_cb.perf_stop(&(name)) + * #else + * ... Blank macros ... + * #endif + * + * These can then be used mid-functions around code snippets. + * + * extern struct retro_perf_callback perf_cb; * Somewhere in the core. + * + * void do_some_heavy_work(void) + * { + * RETRO_PERFORMANCE_INIT(cb, work_1; + * RETRO_PERFORMANCE_START(cb, work_1); + * heavy_work_1(); + * RETRO_PERFORMANCE_STOP(cb, work_1); + * + * RETRO_PERFORMANCE_INIT(cb, work_2); + * RETRO_PERFORMANCE_START(cb, work_2); + * heavy_work_2(); + * RETRO_PERFORMANCE_STOP(cb, work_2); + * } + * + * void retro_deinit(void) + * { + * perf_cb.perf_log(); * Log all perf counters here for example. + * } + */ + +struct retro_perf_callback +{ + retro_perf_get_time_usec_t get_time_usec; + retro_get_cpu_features_t get_cpu_features; + + retro_perf_get_counter_t get_perf_counter; + retro_perf_register_t perf_register; + retro_perf_start_t perf_start; + retro_perf_stop_t perf_stop; + retro_perf_log_t perf_log; +}; + +/* FIXME: Document the sensor API and work out behavior. + * It will be marked as experimental until then. + */ +enum retro_sensor_action +{ + RETRO_SENSOR_ACCELEROMETER_ENABLE = 0, + RETRO_SENSOR_ACCELEROMETER_DISABLE, + RETRO_SENSOR_GYROSCOPE_ENABLE, + RETRO_SENSOR_GYROSCOPE_DISABLE, + RETRO_SENSOR_ILLUMINANCE_ENABLE, + RETRO_SENSOR_ILLUMINANCE_DISABLE, + + RETRO_SENSOR_DUMMY = INT_MAX +}; + +/* Id values for SENSOR types. */ +#define RETRO_SENSOR_ACCELEROMETER_X 0 +#define RETRO_SENSOR_ACCELEROMETER_Y 1 +#define RETRO_SENSOR_ACCELEROMETER_Z 2 +#define RETRO_SENSOR_GYROSCOPE_X 3 +#define RETRO_SENSOR_GYROSCOPE_Y 4 +#define RETRO_SENSOR_GYROSCOPE_Z 5 +#define RETRO_SENSOR_ILLUMINANCE 6 + +typedef bool (RETRO_CALLCONV *retro_set_sensor_state_t)(unsigned port, + enum retro_sensor_action action, unsigned rate); + +typedef float (RETRO_CALLCONV *retro_sensor_get_input_t)(unsigned port, unsigned id); + +struct retro_sensor_interface +{ + retro_set_sensor_state_t set_sensor_state; + retro_sensor_get_input_t get_sensor_input; +}; + +enum retro_camera_buffer +{ + RETRO_CAMERA_BUFFER_OPENGL_TEXTURE = 0, + RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER, + + RETRO_CAMERA_BUFFER_DUMMY = INT_MAX +}; + +/* Starts the camera driver. Can only be called in retro_run(). */ +typedef bool (RETRO_CALLCONV *retro_camera_start_t)(void); + +/* Stops the camera driver. Can only be called in retro_run(). */ +typedef void (RETRO_CALLCONV *retro_camera_stop_t)(void); + +/* Callback which signals when the camera driver is initialized + * and/or deinitialized. + * retro_camera_start_t can be called in initialized callback. + */ +typedef void (RETRO_CALLCONV *retro_camera_lifetime_status_t)(void); + +/* A callback for raw framebuffer data. buffer points to an XRGB8888 buffer. + * Width, height and pitch are similar to retro_video_refresh_t. + * First pixel is top-left origin. + */ +typedef void (RETRO_CALLCONV *retro_camera_frame_raw_framebuffer_t)(const uint32_t *buffer, + unsigned width, unsigned height, size_t pitch); + +/* A callback for when OpenGL textures are used. + * + * texture_id is a texture owned by camera driver. + * Its state or content should be considered immutable, except for things like + * texture filtering and clamping. + * + * texture_target is the texture target for the GL texture. + * These can include e.g. GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE, and possibly + * more depending on extensions. + * + * affine points to a packed 3x3 column-major matrix used to apply an affine + * transform to texture coordinates. (affine_matrix * vec3(coord_x, coord_y, 1.0)) + * After transform, normalized texture coord (0, 0) should be bottom-left + * and (1, 1) should be top-right (or (width, height) for RECTANGLE). + * + * GL-specific typedefs are avoided here to avoid relying on gl.h in + * the API definition. + */ +typedef void (RETRO_CALLCONV *retro_camera_frame_opengl_texture_t)(unsigned texture_id, + unsigned texture_target, const float *affine); + +struct retro_camera_callback +{ + /* Set by libretro core. + * Example bitmask: caps = (1 << RETRO_CAMERA_BUFFER_OPENGL_TEXTURE) | (1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER). + */ + uint64_t caps; + + /* Desired resolution for camera. Is only used as a hint. */ + unsigned width; + unsigned height; + + /* Set by frontend. */ + retro_camera_start_t start; + retro_camera_stop_t stop; + + /* Set by libretro core if raw framebuffer callbacks will be used. */ + retro_camera_frame_raw_framebuffer_t frame_raw_framebuffer; + + /* Set by libretro core if OpenGL texture callbacks will be used. */ + retro_camera_frame_opengl_texture_t frame_opengl_texture; + + /* Set by libretro core. Called after camera driver is initialized and + * ready to be started. + * Can be NULL, in which this callback is not called. + */ + retro_camera_lifetime_status_t initialized; + + /* Set by libretro core. Called right before camera driver is + * deinitialized. + * Can be NULL, in which this callback is not called. + */ + retro_camera_lifetime_status_t deinitialized; +}; + +/* Sets the interval of time and/or distance at which to update/poll + * location-based data. + * + * To ensure compatibility with all location-based implementations, + * values for both interval_ms and interval_distance should be provided. + * + * interval_ms is the interval expressed in milliseconds. + * interval_distance is the distance interval expressed in meters. + */ +typedef void (RETRO_CALLCONV *retro_location_set_interval_t)(unsigned interval_ms, + unsigned interval_distance); + +/* Start location services. The device will start listening for changes to the + * current location at regular intervals (which are defined with + * retro_location_set_interval_t). */ +typedef bool (RETRO_CALLCONV *retro_location_start_t)(void); + +/* Stop location services. The device will stop listening for changes + * to the current location. */ +typedef void (RETRO_CALLCONV *retro_location_stop_t)(void); + +/* Get the position of the current location. Will set parameters to + * 0 if no new location update has happened since the last time. */ +typedef bool (RETRO_CALLCONV *retro_location_get_position_t)(double *lat, double *lon, + double *horiz_accuracy, double *vert_accuracy); + +/* Callback which signals when the location driver is initialized + * and/or deinitialized. + * retro_location_start_t can be called in initialized callback. + */ +typedef void (RETRO_CALLCONV *retro_location_lifetime_status_t)(void); + +struct retro_location_callback +{ + retro_location_start_t start; + retro_location_stop_t stop; + retro_location_get_position_t get_position; + retro_location_set_interval_t set_interval; + + retro_location_lifetime_status_t initialized; + retro_location_lifetime_status_t deinitialized; +}; + +enum retro_rumble_effect +{ + RETRO_RUMBLE_STRONG = 0, + RETRO_RUMBLE_WEAK = 1, + + RETRO_RUMBLE_DUMMY = INT_MAX +}; + +/* Sets rumble state for joypad plugged in port 'port'. + * Rumble effects are controlled independently, + * and setting e.g. strong rumble does not override weak rumble. + * Strength has a range of [0, 0xffff]. + * + * Returns true if rumble state request was honored. + * Calling this before first retro_run() is likely to return false. */ +typedef bool (RETRO_CALLCONV *retro_set_rumble_state_t)(unsigned port, + enum retro_rumble_effect effect, uint16_t strength); + +struct retro_rumble_interface +{ + retro_set_rumble_state_t set_rumble_state; +}; + +/* Notifies libretro that audio data should be written. */ +typedef void (RETRO_CALLCONV *retro_audio_callback_t)(void); + +/* True: Audio driver in frontend is active, and callback is + * expected to be called regularily. + * False: Audio driver in frontend is paused or inactive. + * Audio callback will not be called until set_state has been + * called with true. + * Initial state is false (inactive). + */ +typedef void (RETRO_CALLCONV *retro_audio_set_state_callback_t)(bool enabled); + +struct retro_audio_callback +{ + retro_audio_callback_t callback; + retro_audio_set_state_callback_t set_state; +}; + +/* Notifies a libretro core of time spent since last invocation + * of retro_run() in microseconds. + * + * It will be called right before retro_run() every frame. + * The frontend can tamper with timing to support cases like + * fast-forward, slow-motion and framestepping. + * + * In those scenarios the reference frame time value will be used. */ +typedef int64_t retro_usec_t; +typedef void (RETRO_CALLCONV *retro_frame_time_callback_t)(retro_usec_t usec); +struct retro_frame_time_callback +{ + retro_frame_time_callback_t callback; + /* Represents the time of one frame. It is computed as + * 1000000 / fps, but the implementation will resolve the + * rounding to ensure that framestepping, etc is exact. */ + retro_usec_t reference; +}; + +/* Notifies a libretro core of the current occupancy + * level of the frontend audio buffer. + * + * - active: 'true' if audio buffer is currently + * in use. Will be 'false' if audio is + * disabled in the frontend + * + * - occupancy: Given as a value in the range [0,100], + * corresponding to the occupancy percentage + * of the audio buffer + * + * - underrun_likely: 'true' if the frontend expects an + * audio buffer underrun during the + * next frame (indicates that a core + * should attempt frame skipping) + * + * It will be called right before retro_run() every frame. */ +typedef void (RETRO_CALLCONV *retro_audio_buffer_status_callback_t)( + bool active, unsigned occupancy, bool underrun_likely); +struct retro_audio_buffer_status_callback +{ + retro_audio_buffer_status_callback_t callback; +}; + +/* Pass this to retro_video_refresh_t if rendering to hardware. + * Passing NULL to retro_video_refresh_t is still a frame dupe as normal. + * */ +#define RETRO_HW_FRAME_BUFFER_VALID ((void*)-1) + +/* Invalidates the current HW context. + * Any GL state is lost, and must not be deinitialized explicitly. + * If explicit deinitialization is desired by the libretro core, + * it should implement context_destroy callback. + * If called, all GPU resources must be reinitialized. + * Usually called when frontend reinits video driver. + * Also called first time video driver is initialized, + * allowing libretro core to initialize resources. + */ +typedef void (RETRO_CALLCONV *retro_hw_context_reset_t)(void); + +/* Gets current framebuffer which is to be rendered to. + * Could change every frame potentially. + */ +typedef uintptr_t (RETRO_CALLCONV *retro_hw_get_current_framebuffer_t)(void); + +/* Get a symbol from HW context. */ +typedef retro_proc_address_t (RETRO_CALLCONV *retro_hw_get_proc_address_t)(const char *sym); + +enum retro_hw_context_type +{ + RETRO_HW_CONTEXT_NONE = 0, + /* OpenGL 2.x. Driver can choose to use latest compatibility context. */ + RETRO_HW_CONTEXT_OPENGL = 1, + /* OpenGL ES 2.0. */ + RETRO_HW_CONTEXT_OPENGLES2 = 2, + /* Modern desktop core GL context. Use version_major/ + * version_minor fields to set GL version. */ + RETRO_HW_CONTEXT_OPENGL_CORE = 3, + /* OpenGL ES 3.0 */ + RETRO_HW_CONTEXT_OPENGLES3 = 4, + /* OpenGL ES 3.1+. Set version_major/version_minor. For GLES2 and GLES3, + * use the corresponding enums directly. */ + RETRO_HW_CONTEXT_OPENGLES_VERSION = 5, + + /* Vulkan, see RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE. */ + RETRO_HW_CONTEXT_VULKAN = 6, + + /* Direct3D, set version_major to select the type of interface + * returned by RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE */ + RETRO_HW_CONTEXT_DIRECT3D = 7, + + RETRO_HW_CONTEXT_DUMMY = INT_MAX +}; + +struct retro_hw_render_callback +{ + /* Which API to use. Set by libretro core. */ + enum retro_hw_context_type context_type; + + /* Called when a context has been created or when it has been reset. + * An OpenGL context is only valid after context_reset() has been called. + * + * When context_reset is called, OpenGL resources in the libretro + * implementation are guaranteed to be invalid. + * + * It is possible that context_reset is called multiple times during an + * application lifecycle. + * If context_reset is called without any notification (context_destroy), + * the OpenGL context was lost and resources should just be recreated + * without any attempt to "free" old resources. + */ + retro_hw_context_reset_t context_reset; + + /* Set by frontend. + * TODO: This is rather obsolete. The frontend should not + * be providing preallocated framebuffers. */ + retro_hw_get_current_framebuffer_t get_current_framebuffer; + + /* Set by frontend. + * Can return all relevant functions, including glClear on Windows. */ + retro_hw_get_proc_address_t get_proc_address; + + /* Set if render buffers should have depth component attached. + * TODO: Obsolete. */ + bool depth; + + /* Set if stencil buffers should be attached. + * TODO: Obsolete. */ + bool stencil; + + /* If depth and stencil are true, a packed 24/8 buffer will be added. + * Only attaching stencil is invalid and will be ignored. */ + + /* Use conventional bottom-left origin convention. If false, + * standard libretro top-left origin semantics are used. + * TODO: Move to GL specific interface. */ + bool bottom_left_origin; + + /* Major version number for core GL context or GLES 3.1+. */ + unsigned version_major; + + /* Minor version number for core GL context or GLES 3.1+. */ + unsigned version_minor; + + /* If this is true, the frontend will go very far to avoid + * resetting context in scenarios like toggling fullscreen, etc. + * TODO: Obsolete? Maybe frontend should just always assume this ... + */ + bool cache_context; + + /* The reset callback might still be called in extreme situations + * such as if the context is lost beyond recovery. + * + * For optimal stability, set this to false, and allow context to be + * reset at any time. + */ + + /* A callback to be called before the context is destroyed in a + * controlled way by the frontend. */ + retro_hw_context_reset_t context_destroy; + + /* OpenGL resources can be deinitialized cleanly at this step. + * context_destroy can be set to NULL, in which resources will + * just be destroyed without any notification. + * + * Even when context_destroy is non-NULL, it is possible that + * context_reset is called without any destroy notification. + * This happens if context is lost by external factors (such as + * notified by GL_ARB_robustness). + * + * In this case, the context is assumed to be already dead, + * and the libretro implementation must not try to free any OpenGL + * resources in the subsequent context_reset. + */ + + /* Creates a debug context. */ + bool debug_context; +}; + +/* Callback type passed in RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. + * Called by the frontend in response to keyboard events. + * down is set if the key is being pressed, or false if it is being released. + * keycode is the RETROK value of the char. + * character is the text character of the pressed key. (UTF-32). + * key_modifiers is a set of RETROKMOD values or'ed together. + * + * The pressed/keycode state can be indepedent of the character. + * It is also possible that multiple characters are generated from a + * single keypress. + * Keycode events should be treated separately from character events. + * However, when possible, the frontend should try to synchronize these. + * If only a character is posted, keycode should be RETROK_UNKNOWN. + * + * Similarily if only a keycode event is generated with no corresponding + * character, character should be 0. + */ +typedef void (RETRO_CALLCONV *retro_keyboard_event_t)(bool down, unsigned keycode, + uint32_t character, uint16_t key_modifiers); + +struct retro_keyboard_callback +{ + retro_keyboard_event_t callback; +}; + +/* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE & + * RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE. + * Should be set for implementations which can swap out multiple disk + * images in runtime. + * + * If the implementation can do this automatically, it should strive to do so. + * However, there are cases where the user must manually do so. + * + * Overview: To swap a disk image, eject the disk image with + * set_eject_state(true). + * Set the disk index with set_image_index(index). Insert the disk again + * with set_eject_state(false). + */ + +/* If ejected is true, "ejects" the virtual disk tray. + * When ejected, the disk image index can be set. + */ +typedef bool (RETRO_CALLCONV *retro_set_eject_state_t)(bool ejected); + +/* Gets current eject state. The initial state is 'not ejected'. */ +typedef bool (RETRO_CALLCONV *retro_get_eject_state_t)(void); + +/* Gets current disk index. First disk is index 0. + * If return value is >= get_num_images(), no disk is currently inserted. + */ +typedef unsigned (RETRO_CALLCONV *retro_get_image_index_t)(void); + +/* Sets image index. Can only be called when disk is ejected. + * The implementation supports setting "no disk" by using an + * index >= get_num_images(). + */ +typedef bool (RETRO_CALLCONV *retro_set_image_index_t)(unsigned index); + +/* Gets total number of images which are available to use. */ +typedef unsigned (RETRO_CALLCONV *retro_get_num_images_t)(void); + +struct retro_game_info; + +/* Replaces the disk image associated with index. + * Arguments to pass in info have same requirements as retro_load_game(). + * Virtual disk tray must be ejected when calling this. + * + * Replacing a disk image with info = NULL will remove the disk image + * from the internal list. + * As a result, calls to get_image_index() can change. + * + * E.g. replace_image_index(1, NULL), and previous get_image_index() + * returned 4 before. + * Index 1 will be removed, and the new index is 3. + */ +typedef bool (RETRO_CALLCONV *retro_replace_image_index_t)(unsigned index, + const struct retro_game_info *info); + +/* Adds a new valid index (get_num_images()) to the internal disk list. + * This will increment subsequent return values from get_num_images() by 1. + * This image index cannot be used until a disk image has been set + * with replace_image_index. */ +typedef bool (RETRO_CALLCONV *retro_add_image_index_t)(void); + +/* Sets initial image to insert in drive when calling + * core_load_game(). + * Since we cannot pass the initial index when loading + * content (this would require a major API change), this + * is set by the frontend *before* calling the core's + * retro_load_game()/retro_load_game_special() implementation. + * A core should therefore cache the index/path values and handle + * them inside retro_load_game()/retro_load_game_special(). + * - If 'index' is invalid (index >= get_num_images()), the + * core should ignore the set value and instead use 0 + * - 'path' is used purely for error checking - i.e. when + * content is loaded, the core should verify that the + * disk specified by 'index' has the specified file path. + * This is to guard against auto selecting the wrong image + * if (for example) the user should modify an existing M3U + * playlist. We have to let the core handle this because + * set_initial_image() must be called before loading content, + * i.e. the frontend cannot access image paths in advance + * and thus cannot perform the error check itself. + * If set path and content path do not match, the core should + * ignore the set 'index' value and instead use 0 + * Returns 'false' if index or 'path' are invalid, or core + * does not support this functionality + */ +typedef bool (RETRO_CALLCONV *retro_set_initial_image_t)(unsigned index, const char *path); + +/* Fetches the path of the specified disk image file. + * Returns 'false' if index is invalid (index >= get_num_images()) + * or path is otherwise unavailable. + */ +typedef bool (RETRO_CALLCONV *retro_get_image_path_t)(unsigned index, char *path, size_t len); + +/* Fetches a core-provided 'label' for the specified disk + * image file. In the simplest case this may be a file name + * (without extension), but for cores with more complex + * content requirements information may be provided to + * facilitate user disk swapping - for example, a core + * running floppy-disk-based content may uniquely label + * save disks, data disks, level disks, etc. with names + * corresponding to in-game disk change prompts (so the + * frontend can provide better user guidance than a 'dumb' + * disk index value). + * Returns 'false' if index is invalid (index >= get_num_images()) + * or label is otherwise unavailable. + */ +typedef bool (RETRO_CALLCONV *retro_get_image_label_t)(unsigned index, char *label, size_t len); + +struct retro_disk_control_callback +{ + retro_set_eject_state_t set_eject_state; + retro_get_eject_state_t get_eject_state; + + retro_get_image_index_t get_image_index; + retro_set_image_index_t set_image_index; + retro_get_num_images_t get_num_images; + + retro_replace_image_index_t replace_image_index; + retro_add_image_index_t add_image_index; +}; + +struct retro_disk_control_ext_callback +{ + retro_set_eject_state_t set_eject_state; + retro_get_eject_state_t get_eject_state; + + retro_get_image_index_t get_image_index; + retro_set_image_index_t set_image_index; + retro_get_num_images_t get_num_images; + + retro_replace_image_index_t replace_image_index; + retro_add_image_index_t add_image_index; + + /* NOTE: Frontend will only attempt to record/restore + * last used disk index if both set_initial_image() + * and get_image_path() are implemented */ + retro_set_initial_image_t set_initial_image; /* Optional - may be NULL */ + + retro_get_image_path_t get_image_path; /* Optional - may be NULL */ + retro_get_image_label_t get_image_label; /* Optional - may be NULL */ +}; + +enum retro_pixel_format +{ + /* 0RGB1555, native endian. + * 0 bit must be set to 0. + * This pixel format is default for compatibility concerns only. + * If a 15/16-bit pixel format is desired, consider using RGB565. */ + RETRO_PIXEL_FORMAT_0RGB1555 = 0, + + /* XRGB8888, native endian. + * X bits are ignored. */ + RETRO_PIXEL_FORMAT_XRGB8888 = 1, + + /* RGB565, native endian. + * This pixel format is the recommended format to use if a 15/16-bit + * format is desired as it is the pixel format that is typically + * available on a wide range of low-power devices. + * + * It is also natively supported in APIs like OpenGL ES. */ + RETRO_PIXEL_FORMAT_RGB565 = 2, + + /* Ensure sizeof() == sizeof(int). */ + RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX +}; + +struct retro_message +{ + const char *msg; /* Message to be displayed. */ + unsigned frames; /* Duration in frames of message. */ +}; + +enum retro_message_target +{ + RETRO_MESSAGE_TARGET_ALL = 0, + RETRO_MESSAGE_TARGET_OSD, + RETRO_MESSAGE_TARGET_LOG +}; + +enum retro_message_type +{ + RETRO_MESSAGE_TYPE_NOTIFICATION = 0, + RETRO_MESSAGE_TYPE_NOTIFICATION_ALT, + RETRO_MESSAGE_TYPE_STATUS, + RETRO_MESSAGE_TYPE_PROGRESS +}; + +struct retro_message_ext +{ + /* Message string to be displayed/logged */ + const char *msg; + /* Duration (in ms) of message when targeting the OSD */ + unsigned duration; + /* Message priority when targeting the OSD + * > When multiple concurrent messages are sent to + * the frontend and the frontend does not have the + * capacity to display them all, messages with the + * *highest* priority value should be shown + * > There is no upper limit to a message priority + * value (within the bounds of the unsigned data type) + * > In the reference frontend (RetroArch), the same + * priority values are used for frontend-generated + * notifications, which are typically assigned values + * between 0 and 3 depending upon importance */ + unsigned priority; + /* Message logging level (info, warn, error, etc.) */ + enum retro_log_level level; + /* Message destination: OSD, logging interface or both */ + enum retro_message_target target; + /* Message 'type' when targeting the OSD + * > RETRO_MESSAGE_TYPE_NOTIFICATION: Specifies that a + * message should be handled in identical fashion to + * a standard frontend-generated notification + * > RETRO_MESSAGE_TYPE_NOTIFICATION_ALT: Specifies that + * message is a notification that requires user attention + * or action, but that it should be displayed in a manner + * that differs from standard frontend-generated notifications. + * This would typically correspond to messages that should be + * displayed immediately (independently from any internal + * frontend message queue), and/or which should be visually + * distinguishable from frontend-generated notifications. + * For example, a core may wish to inform the user of + * information related to a disk-change event. It is + * expected that the frontend itself may provide a + * notification in this case; if the core sends a + * message of type RETRO_MESSAGE_TYPE_NOTIFICATION, an + * uncomfortable 'double-notification' may occur. A message + * of RETRO_MESSAGE_TYPE_NOTIFICATION_ALT should therefore + * be presented such that visual conflict with regular + * notifications does not occur + * > RETRO_MESSAGE_TYPE_STATUS: Indicates that message + * is not a standard notification. This typically + * corresponds to 'status' indicators, such as a core's + * internal FPS, which are intended to be displayed + * either permanently while a core is running, or in + * a manner that does not suggest user attention or action + * is required. 'Status' type messages should therefore be + * displayed in a different on-screen location and in a manner + * easily distinguishable from both standard frontend-generated + * notifications and messages of type RETRO_MESSAGE_TYPE_NOTIFICATION_ALT + * > RETRO_MESSAGE_TYPE_PROGRESS: Indicates that message reports + * the progress of an internal core task. For example, in cases + * where a core itself handles the loading of content from a file, + * this may correspond to the percentage of the file that has been + * read. Alternatively, an audio/video playback core may use a + * message of type RETRO_MESSAGE_TYPE_PROGRESS to display the current + * playback position as a percentage of the runtime. 'Progress' type + * messages should therefore be displayed as a literal progress bar, + * where: + * - 'retro_message_ext.msg' is the progress bar title/label + * - 'retro_message_ext.progress' determines the length of + * the progress bar + * NOTE: Message type is a *hint*, and may be ignored + * by the frontend. If a frontend lacks support for + * displaying messages via alternate means than standard + * frontend-generated notifications, it will treat *all* + * messages as having the type RETRO_MESSAGE_TYPE_NOTIFICATION */ + enum retro_message_type type; + /* Task progress when targeting the OSD and message is + * of type RETRO_MESSAGE_TYPE_PROGRESS + * > -1: Unmetered/indeterminate + * > 0-100: Current progress percentage + * NOTE: Since message type is a hint, a frontend may ignore + * progress values. Where relevant, a core should therefore + * include progress percentage within the message string, + * such that the message intent remains clear when displayed + * as a standard frontend-generated notification */ + int8_t progress; +}; + +/* Describes how the libretro implementation maps a libretro input bind + * to its internal input system through a human readable string. + * This string can be used to better let a user configure input. */ +struct retro_input_descriptor +{ + /* Associates given parameters with a description. */ + unsigned port; + unsigned device; + unsigned index; + unsigned id; + + /* Human readable description for parameters. + * The pointer must remain valid until + * retro_unload_game() is called. */ + const char *description; +}; + +struct retro_system_info +{ + /* All pointers are owned by libretro implementation, and pointers must + * remain valid until it is unloaded. */ + + const char *library_name; /* Descriptive name of library. Should not + * contain any version numbers, etc. */ + const char *library_version; /* Descriptive version of core. */ + + const char *valid_extensions; /* A string listing probably content + * extensions the core will be able to + * load, separated with pipe. + * I.e. "bin|rom|iso". + * Typically used for a GUI to filter + * out extensions. */ + + /* Libretro cores that need to have direct access to their content + * files, including cores which use the path of the content files to + * determine the paths of other files, should set need_fullpath to true. + * + * Cores should strive for setting need_fullpath to false, + * as it allows the frontend to perform patching, etc. + * + * If need_fullpath is true and retro_load_game() is called: + * - retro_game_info::path is guaranteed to have a valid path + * - retro_game_info::data and retro_game_info::size are invalid + * + * If need_fullpath is false and retro_load_game() is called: + * - retro_game_info::path may be NULL + * - retro_game_info::data and retro_game_info::size are guaranteed + * to be valid + * + * See also: + * - RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY + * - RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY + */ + bool need_fullpath; + + /* If true, the frontend is not allowed to extract any archives before + * loading the real content. + * Necessary for certain libretro implementations that load games + * from zipped archives. */ + bool block_extract; +}; + +/* Defines overrides which modify frontend handling of + * specific content file types. + * An array of retro_system_content_info_override is + * passed to RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE + * NOTE: In the following descriptions, references to + * retro_load_game() may be replaced with + * retro_load_game_special() */ +struct retro_system_content_info_override +{ + /* A list of file extensions for which the override + * should apply, delimited by a 'pipe' character + * (e.g. "md|sms|gg") + * Permitted file extensions are limited to those + * included in retro_system_info::valid_extensions + * and/or retro_subsystem_rom_info::valid_extensions */ + const char *extensions; + + /* Overrides the need_fullpath value set in + * retro_system_info and/or retro_subsystem_rom_info. + * To reiterate: + * + * If need_fullpath is true and retro_load_game() is called: + * - retro_game_info::path is guaranteed to contain a valid + * path to an existent file + * - retro_game_info::data and retro_game_info::size are invalid + * + * If need_fullpath is false and retro_load_game() is called: + * - retro_game_info::path may be NULL + * - retro_game_info::data and retro_game_info::size are guaranteed + * to be valid + * + * In addition: + * + * If need_fullpath is true and retro_load_game() is called: + * - retro_game_info_ext::full_path is guaranteed to contain a valid + * path to an existent file + * - retro_game_info_ext::archive_path may be NULL + * - retro_game_info_ext::archive_file may be NULL + * - retro_game_info_ext::dir is guaranteed to contain a valid path + * to the directory in which the content file exists + * - retro_game_info_ext::name is guaranteed to contain the + * basename of the content file, without extension + * - retro_game_info_ext::ext is guaranteed to contain the + * extension of the content file in lower case format + * - retro_game_info_ext::data and retro_game_info_ext::size + * are invalid + * + * If need_fullpath is false and retro_load_game() is called: + * - If retro_game_info_ext::file_in_archive is false: + * - retro_game_info_ext::full_path is guaranteed to contain + * a valid path to an existent file + * - retro_game_info_ext::archive_path may be NULL + * - retro_game_info_ext::archive_file may be NULL + * - retro_game_info_ext::dir is guaranteed to contain a + * valid path to the directory in which the content file exists + * - retro_game_info_ext::name is guaranteed to contain the + * basename of the content file, without extension + * - retro_game_info_ext::ext is guaranteed to contain the + * extension of the content file in lower case format + * - If retro_game_info_ext::file_in_archive is true: + * - retro_game_info_ext::full_path may be NULL + * - retro_game_info_ext::archive_path is guaranteed to + * contain a valid path to an existent compressed file + * inside which the content file is located + * - retro_game_info_ext::archive_file is guaranteed to + * contain a valid path to an existent content file + * inside the compressed file referred to by + * retro_game_info_ext::archive_path + * e.g. for a compressed file '/path/to/foo.zip' + * containing 'bar.sfc' + * > retro_game_info_ext::archive_path will be '/path/to/foo.zip' + * > retro_game_info_ext::archive_file will be 'bar.sfc' + * - retro_game_info_ext::dir is guaranteed to contain a + * valid path to the directory in which the compressed file + * (containing the content file) exists + * - retro_game_info_ext::name is guaranteed to contain + * EITHER + * 1) the basename of the compressed file (containing + * the content file), without extension + * OR + * 2) the basename of the content file inside the + * compressed file, without extension + * In either case, a core should consider 'name' to + * be the canonical name/ID of the the content file + * - retro_game_info_ext::ext is guaranteed to contain the + * extension of the content file inside the compressed file, + * in lower case format + * - retro_game_info_ext::data and retro_game_info_ext::size are + * guaranteed to be valid */ + bool need_fullpath; + + /* If need_fullpath is false, specifies whether the content + * data buffer available in retro_load_game() is 'persistent' + * + * If persistent_data is false and retro_load_game() is called: + * - retro_game_info::data and retro_game_info::size + * are valid only until retro_load_game() returns + * - retro_game_info_ext::data and retro_game_info_ext::size + * are valid only until retro_load_game() returns + * + * If persistent_data is true and retro_load_game() is called: + * - retro_game_info::data and retro_game_info::size + * are valid until retro_deinit() returns + * - retro_game_info_ext::data and retro_game_info_ext::size + * are valid until retro_deinit() returns */ + bool persistent_data; +}; + +/* Similar to retro_game_info, but provides extended + * information about the source content file and + * game memory buffer status. + * And array of retro_game_info_ext is returned by + * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT + * NOTE: In the following descriptions, references to + * retro_load_game() may be replaced with + * retro_load_game_special() */ +struct retro_game_info_ext +{ + /* - If file_in_archive is false, contains a valid + * path to an existent content file (UTF-8 encoded) + * - If file_in_archive is true, may be NULL */ + const char *full_path; + + /* - If file_in_archive is false, may be NULL + * - If file_in_archive is true, contains a valid path + * to an existent compressed file inside which the + * content file is located (UTF-8 encoded) */ + const char *archive_path; + + /* - If file_in_archive is false, may be NULL + * - If file_in_archive is true, contain a valid path + * to an existent content file inside the compressed + * file referred to by archive_path (UTF-8 encoded) + * e.g. for a compressed file '/path/to/foo.zip' + * containing 'bar.sfc' + * > archive_path will be '/path/to/foo.zip' + * > archive_file will be 'bar.sfc' */ + const char *archive_file; + + /* - If file_in_archive is false, contains a valid path + * to the directory in which the content file exists + * (UTF-8 encoded) + * - If file_in_archive is true, contains a valid path + * to the directory in which the compressed file + * (containing the content file) exists (UTF-8 encoded) */ + const char *dir; + + /* Contains the canonical name/ID of the content file + * (UTF-8 encoded). Intended for use when identifying + * 'complementary' content named after the loaded file - + * i.e. companion data of a different format (a CD image + * required by a ROM), texture packs, internally handled + * save files, etc. + * - If file_in_archive is false, contains the basename + * of the content file, without extension + * - If file_in_archive is true, then string is + * implementation specific. A frontend may choose to + * set a name value of: + * EITHER + * 1) the basename of the compressed file (containing + * the content file), without extension + * OR + * 2) the basename of the content file inside the + * compressed file, without extension + * RetroArch sets the 'name' value according to (1). + * A frontend that supports routine loading of + * content from archives containing multiple unrelated + * content files may set the 'name' value according + * to (2). */ + const char *name; + + /* - If file_in_archive is false, contains the extension + * of the content file in lower case format + * - If file_in_archive is true, contains the extension + * of the content file inside the compressed file, + * in lower case format */ + const char *ext; + + /* String of implementation specific meta-data. */ + const char *meta; + + /* Memory buffer of loaded game content. Will be NULL: + * IF + * - retro_system_info::need_fullpath is true and + * retro_system_content_info_override::need_fullpath + * is unset + * OR + * - retro_system_content_info_override::need_fullpath + * is true */ + const void *data; + + /* Size of game content memory buffer, in bytes */ + size_t size; + + /* True if loaded content file is inside a compressed + * archive */ + bool file_in_archive; + + /* - If data is NULL, value is unset/ignored + * - If data is non-NULL: + * - If persistent_data is false, data and size are + * valid only until retro_load_game() returns + * - If persistent_data is true, data and size are + * are valid until retro_deinit() returns */ + bool persistent_data; +}; + +struct retro_game_geometry +{ + unsigned base_width; /* Nominal video width of game. */ + unsigned base_height; /* Nominal video height of game. */ + unsigned max_width; /* Maximum possible width of game. */ + unsigned max_height; /* Maximum possible height of game. */ + + float aspect_ratio; /* Nominal aspect ratio of game. If + * aspect_ratio is <= 0.0, an aspect ratio + * of base_width / base_height is assumed. + * A frontend could override this setting, + * if desired. */ +}; + +struct retro_system_timing +{ + double fps; /* FPS of video content. */ + double sample_rate; /* Sampling rate of audio. */ +}; + +struct retro_system_av_info +{ + struct retro_game_geometry geometry; + struct retro_system_timing timing; +}; + +struct retro_variable +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. + * If NULL, obtains the complete environment string if more + * complex parsing is necessary. + * The environment string is formatted as key-value pairs + * delimited by semicolons as so: + * "key1=value1;key2=value2;..." + */ + const char *key; + + /* Value to be obtained. If key does not exist, it is set to NULL. */ + const char *value; +}; + +struct retro_core_option_display +{ + /* Variable to configure in RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY */ + const char *key; + + /* Specifies whether variable should be displayed + * when presenting core options to the user */ + bool visible; +}; + +/* Maximum number of values permitted for a core option + * > Note: We have to set a maximum value due the limitations + * of the C language - i.e. it is not possible to create an + * array of structs each containing a variable sized array, + * so the retro_core_option_definition values array must + * have a fixed size. The size limit of 128 is a balancing + * act - it needs to be large enough to support all 'sane' + * core options, but setting it too large may impact low memory + * platforms. In practise, if a core option has more than + * 128 values then the implementation is likely flawed. + * To quote the above API reference: + * "The number of possible options should be very limited + * i.e. it should be feasible to cycle through options + * without a keyboard." + */ +#define RETRO_NUM_CORE_OPTION_VALUES_MAX 128 + +struct retro_core_option_value +{ + /* Expected option value */ + const char *value; + + /* Human-readable value label. If NULL, value itself + * will be displayed by the frontend */ + const char *label; +}; + +struct retro_core_option_definition +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. */ + const char *key; + + /* Human-readable core option description (used as menu label) */ + const char *desc; + + /* Human-readable core option information (used as menu sublabel) */ + const char *info; + + /* Array of retro_core_option_value structs, terminated by NULL */ + struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX]; + + /* Default core option value. Must match one of the values + * in the retro_core_option_value array, otherwise will be + * ignored */ + const char *default_value; +}; + +struct retro_core_options_intl +{ + /* Pointer to an array of retro_core_option_definition structs + * - US English implementation + * - Must point to a valid array */ + struct retro_core_option_definition *us; + + /* Pointer to an array of retro_core_option_definition structs + * - Implementation for current frontend language + * - May be NULL */ + struct retro_core_option_definition *local; +}; + +struct retro_core_option_v2_category +{ + /* Variable uniquely identifying the + * option category. Valid key characters + * are [a-z, A-Z, 0-9, _, -] */ + const char *key; + + /* Human-readable category description + * > Used as category menu label when + * frontend has core option category + * support */ + const char *desc; + + /* Human-readable category information + * > Used as category menu sublabel when + * frontend has core option category + * support + * > Optional (may be NULL or an empty + * string) */ + const char *info; +}; + +struct retro_core_option_v2_definition +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. + * Valid key characters are [a-z, A-Z, 0-9, _, -] */ + const char *key; + + /* Human-readable core option description + * > Used as menu label when frontend does + * not have core option category support + * e.g. "Video > Aspect Ratio" */ + const char *desc; + + /* Human-readable core option description + * > Used as menu label when frontend has + * core option category support + * e.g. "Aspect Ratio", where associated + * retro_core_option_v2_category::desc + * is "Video" + * > If empty or NULL, the string specified by + * desc will be used as the menu label + * > Will be ignored (and may be set to NULL) + * if category_key is empty or NULL */ + const char *desc_categorized; + + /* Human-readable core option information + * > Used as menu sublabel */ + const char *info; + + /* Human-readable core option information + * > Used as menu sublabel when frontend + * has core option category support + * (e.g. may be required when info text + * references an option by name/desc, + * and the desc/desc_categorized text + * for that option differ) + * > If empty or NULL, the string specified by + * info will be used as the menu sublabel + * > Will be ignored (and may be set to NULL) + * if category_key is empty or NULL */ + const char *info_categorized; + + /* Variable specifying category (e.g. "video", + * "audio") that will be assigned to the option + * if frontend has core option category support. + * > Categorized options will be displayed in a + * subsection/submenu of the frontend core + * option interface + * > Specified string must match one of the + * retro_core_option_v2_category::key values + * in the associated retro_core_option_v2_category + * array; If no match is not found, specified + * string will be considered as NULL + * > If specified string is empty or NULL, option will + * have no category and will be shown at the top + * level of the frontend core option interface */ + const char *category_key; + + /* Array of retro_core_option_value structs, terminated by NULL */ + struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX]; + + /* Default core option value. Must match one of the values + * in the retro_core_option_value array, otherwise will be + * ignored */ + const char *default_value; +}; + +struct retro_core_options_v2 +{ + /* Array of retro_core_option_v2_category structs, + * terminated by NULL + * > If NULL, all entries in definitions array + * will have no category and will be shown at + * the top level of the frontend core option + * interface + * > Will be ignored if frontend does not have + * core option category support */ + struct retro_core_option_v2_category *categories; + + /* Array of retro_core_option_v2_definition structs, + * terminated by NULL */ + struct retro_core_option_v2_definition *definitions; +}; + +struct retro_core_options_v2_intl +{ + /* Pointer to a retro_core_options_v2 struct + * > US English implementation + * > Must point to a valid struct */ + struct retro_core_options_v2 *us; + + /* Pointer to a retro_core_options_v2 struct + * - Implementation for current frontend language + * - May be NULL */ + struct retro_core_options_v2 *local; +}; + +/* Used by the frontend to monitor changes in core option + * visibility. May be called each time any core option + * value is set via the frontend. + * - On each invocation, the core must update the visibility + * of any dynamically hidden options using the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY environment + * callback. + * - On the first invocation, returns 'true' if the visibility + * of any core option has changed since the last call of + * retro_load_game() or retro_load_game_special(). + * - On each subsequent invocation, returns 'true' if the + * visibility of any core option has changed since the last + * time the function was called. */ +typedef bool (RETRO_CALLCONV *retro_core_options_update_display_callback_t)(void); +struct retro_core_options_update_display_callback +{ + retro_core_options_update_display_callback_t callback; +}; + +struct retro_game_info +{ + const char *path; /* Path to game, UTF-8 encoded. + * Sometimes used as a reference for building other paths. + * May be NULL if game was loaded from stdin or similar, + * but in this case some cores will be unable to load `data`. + * So, it is preferable to fabricate something here instead + * of passing NULL, which will help more cores to succeed. + * retro_system_info::need_fullpath requires + * that this path is valid. */ + const void *data; /* Memory buffer of loaded game. Will be NULL + * if need_fullpath was set. */ + size_t size; /* Size of memory buffer. */ + const char *meta; /* String of implementation specific meta-data. */ +}; + +#define RETRO_MEMORY_ACCESS_WRITE (1 << 0) + /* The core will write to the buffer provided by retro_framebuffer::data. */ +#define RETRO_MEMORY_ACCESS_READ (1 << 1) + /* The core will read from retro_framebuffer::data. */ +#define RETRO_MEMORY_TYPE_CACHED (1 << 0) + /* The memory in data is cached. + * If not cached, random writes and/or reading from the buffer is expected to be very slow. */ +struct retro_framebuffer +{ + void *data; /* The framebuffer which the core can render into. + Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. + The initial contents of data are unspecified. */ + unsigned width; /* The framebuffer width used by the core. Set by core. */ + unsigned height; /* The framebuffer height used by the core. Set by core. */ + size_t pitch; /* The number of bytes between the beginning of a scanline, + and beginning of the next scanline. + Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. */ + enum retro_pixel_format format; /* The pixel format the core must use to render into data. + This format could differ from the format used in + SET_PIXEL_FORMAT. + Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. */ + + unsigned access_flags; /* How the core will access the memory in the framebuffer. + RETRO_MEMORY_ACCESS_* flags. + Set by core. */ + unsigned memory_flags; /* Flags telling core how the memory has been mapped. + RETRO_MEMORY_TYPE_* flags. + Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. */ +}; + +/* Used by a libretro core to override the current + * fastforwarding mode of the frontend */ +struct retro_fastforwarding_override +{ + /* Specifies the runtime speed multiplier that + * will be applied when 'fastforward' is true. + * For example, a value of 5.0 when running 60 FPS + * content will cap the fast-forward rate at 300 FPS. + * Note that the target multiplier may not be achieved + * if the host hardware has insufficient processing + * power. + * Setting a value of 0.0 (or greater than 0.0 but + * less than 1.0) will result in an uncapped + * fast-forward rate (limited only by hardware + * capacity). + * If the value is negative, it will be ignored + * (i.e. the frontend will use a runtime speed + * multiplier of its own choosing) */ + float ratio; + + /* If true, fastforwarding mode will be enabled. + * If false, fastforwarding mode will be disabled. */ + bool fastforward; + + /* If true, and if supported by the frontend, an + * on-screen notification will be displayed while + * 'fastforward' is true. + * If false, and if supported by the frontend, any + * on-screen fast-forward notifications will be + * suppressed */ + bool notification; + + /* If true, the core will have sole control over + * when fastforwarding mode is enabled/disabled; + * the frontend will not be able to change the + * state set by 'fastforward' until either + * 'inhibit_toggle' is set to false, or the core + * is unloaded */ + bool inhibit_toggle; +}; + +/* During normal operation. Rate will be equal to the core's internal FPS. */ +#define RETRO_THROTTLE_NONE 0 + +/* While paused or stepping single frames. Rate will be 0. */ +#define RETRO_THROTTLE_FRAME_STEPPING 1 + +/* During fast forwarding. + * Rate will be 0 if not specifically limited to a maximum speed. */ +#define RETRO_THROTTLE_FAST_FORWARD 2 + +/* During slow motion. Rate will be less than the core's internal FPS. */ +#define RETRO_THROTTLE_SLOW_MOTION 3 + +/* While rewinding recorded save states. Rate can vary depending on the rewind + * speed or be 0 if the frontend is not aiming for a specific rate. */ +#define RETRO_THROTTLE_REWINDING 4 + +/* While vsync is active in the video driver and the target refresh rate is + * lower than the core's internal FPS. Rate is the target refresh rate. */ +#define RETRO_THROTTLE_VSYNC 5 + +/* When the frontend does not throttle in any way. Rate will be 0. + * An example could be if no vsync or audio output is active. */ +#define RETRO_THROTTLE_UNBLOCKED 6 + +struct retro_throttle_state +{ + /* The current throttling mode. Should be one of the values above. */ + unsigned mode; + + /* How many times per second the frontend aims to call retro_run. + * Depending on the mode, it can be 0 if there is no known fixed rate. + * This won't be accurate if the total processing time of the core and + * the frontend is longer than what is available for one frame. */ + float rate; +}; + +/* Callbacks */ + +/* Environment callback. Gives implementations a way of performing + * uncommon tasks. Extensible. */ +typedef bool (RETRO_CALLCONV *retro_environment_t)(unsigned cmd, void *data); + +/* Render a frame. Pixel format is 15-bit 0RGB1555 native endian + * unless changed (see RETRO_ENVIRONMENT_SET_PIXEL_FORMAT). + * + * Width and height specify dimensions of buffer. + * Pitch specifices length in bytes between two lines in buffer. + * + * For performance reasons, it is highly recommended to have a frame + * that is packed in memory, i.e. pitch == width * byte_per_pixel. + * Certain graphic APIs, such as OpenGL ES, do not like textures + * that are not packed in memory. + */ +typedef void (RETRO_CALLCONV *retro_video_refresh_t)(const void *data, unsigned width, + unsigned height, size_t pitch); + +/* Renders a single audio frame. Should only be used if implementation + * generates a single sample at a time. + * Format is signed 16-bit native endian. + */ +typedef void (RETRO_CALLCONV *retro_audio_sample_t)(int16_t left, int16_t right); + +/* Renders multiple audio frames in one go. + * + * One frame is defined as a sample of left and right channels, interleaved. + * I.e. int16_t buf[4] = { l, r, l, r }; would be 2 frames. + * Only one of the audio callbacks must ever be used. + */ +typedef size_t (RETRO_CALLCONV *retro_audio_sample_batch_t)(const int16_t *data, + size_t frames); + +/* Polls input. */ +typedef void (RETRO_CALLCONV *retro_input_poll_t)(void); + +/* Queries for input for player 'port'. device will be masked with + * RETRO_DEVICE_MASK. + * + * Specialization of devices such as RETRO_DEVICE_JOYPAD_MULTITAP that + * have been set with retro_set_controller_port_device() + * will still use the higher level RETRO_DEVICE_JOYPAD to request input. + */ +typedef int16_t (RETRO_CALLCONV *retro_input_state_t)(unsigned port, unsigned device, + unsigned index, unsigned id); + +/* Sets callbacks. retro_set_environment() is guaranteed to be called + * before retro_init(). + * + * The rest of the set_* functions are guaranteed to have been called + * before the first call to retro_run() is made. */ +RETRO_API void retro_set_environment(retro_environment_t); +RETRO_API void retro_set_video_refresh(retro_video_refresh_t); +RETRO_API void retro_set_audio_sample(retro_audio_sample_t); +RETRO_API void retro_set_audio_sample_batch(retro_audio_sample_batch_t); +RETRO_API void retro_set_input_poll(retro_input_poll_t); +RETRO_API void retro_set_input_state(retro_input_state_t); + +/* Library global initialization/deinitialization. */ +RETRO_API void retro_init(void); +RETRO_API void retro_deinit(void); + +/* Must return RETRO_API_VERSION. Used to validate ABI compatibility + * when the API is revised. */ +RETRO_API unsigned retro_api_version(void); + +/* Gets statically known system info. Pointers provided in *info + * must be statically allocated. + * Can be called at any time, even before retro_init(). */ +RETRO_API void retro_get_system_info(struct retro_system_info *info); + +/* Gets information about system audio/video timings and geometry. + * Can be called only after retro_load_game() has successfully completed. + * NOTE: The implementation of this function might not initialize every + * variable if needed. + * E.g. geom.aspect_ratio might not be initialized if core doesn't + * desire a particular aspect ratio. */ +RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info); + +/* Sets device to be used for player 'port'. + * By default, RETRO_DEVICE_JOYPAD is assumed to be plugged into all + * available ports. + * Setting a particular device type is not a guarantee that libretro cores + * will only poll input based on that particular device type. It is only a + * hint to the libretro core when a core cannot automatically detect the + * appropriate input device type on its own. It is also relevant when a + * core can change its behavior depending on device type. + * + * As part of the core's implementation of retro_set_controller_port_device, + * the core should call RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS to notify the + * frontend if the descriptions for any controls have changed as a + * result of changing the device type. + */ +RETRO_API void retro_set_controller_port_device(unsigned port, unsigned device); + +/* Resets the current game. */ +RETRO_API void retro_reset(void); + +/* Runs the game for one video frame. + * During retro_run(), input_poll callback must be called at least once. + * + * If a frame is not rendered for reasons where a game "dropped" a frame, + * this still counts as a frame, and retro_run() should explicitly dupe + * a frame if GET_CAN_DUPE returns true. + * In this case, the video callback can take a NULL argument for data. + */ +RETRO_API void retro_run(void); + +/* Returns the amount of data the implementation requires to serialize + * internal state (save states). + * Between calls to retro_load_game() and retro_unload_game(), the + * returned size is never allowed to be larger than a previous returned + * value, to ensure that the frontend can allocate a save state buffer once. + */ +RETRO_API size_t retro_serialize_size(void); + +/* Serializes internal state. If failed, or size is lower than + * retro_serialize_size(), it should return false, true otherwise. */ +RETRO_API bool retro_serialize(void *data, size_t size); +RETRO_API bool retro_unserialize(const void *data, size_t size); + +RETRO_API void retro_cheat_reset(void); +RETRO_API void retro_cheat_set(unsigned index, bool enabled, const char *code); + +/* Loads a game. + * Return true to indicate successful loading and false to indicate load failure. + */ +RETRO_API bool retro_load_game(const struct retro_game_info *game); + +/* Loads a "special" kind of game. Should not be used, + * except in extreme cases. */ +RETRO_API bool retro_load_game_special( + unsigned game_type, + const struct retro_game_info *info, size_t num_info +); + +/* Unloads the currently loaded game. Called before retro_deinit(void). */ +RETRO_API void retro_unload_game(void); + +/* Gets region of game. */ +RETRO_API unsigned retro_get_region(void); + +/* Gets region of memory. */ +RETRO_API void *retro_get_memory_data(unsigned id); +RETRO_API size_t retro_get_memory_size(unsigned id); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/frontends/libretro/libretro.cpp b/source/frontends/libretro/libretro.cpp index 5a2fb8f1b..e2ea02bf7 100644 --- a/source/frontends/libretro/libretro.cpp +++ b/source/frontends/libretro/libretro.cpp @@ -6,6 +6,9 @@ #include "Common.h" #include "Video.h" #include "Interface.h" +#include "Memory.h" +#include "Utilities.h" +#include "Debugger/DebugDefs.h" #include "linux/version.h" #include "linux/paddle.h" @@ -14,6 +17,10 @@ #include "frontends/libretro/environment.h" #include "frontends/libretro/rdirectsound.h" #include "frontends/libretro/retroregistry.h" +#include "frontends/libretro/joypad.h" +#include "frontends/libretro/analog.h" +#include "frontends/libretro/mouse.h" +#include "frontends/libretro/serialisation.h" namespace { @@ -29,26 +36,86 @@ namespace return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); } + bool retro_set_eject_state(bool ejected) + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, ejected); + return ourGame->getDiskControl().setEjectedState(ejected); + } + + bool retro_get_eject_state() + { + return ourGame->getDiskControl().getEjectedState(); + } + + unsigned retro_get_image_index() + { + return ourGame->getDiskControl().getImageIndex(); + } + + bool retro_set_image_index(unsigned index) + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, index); + return ourGame->getDiskControl().setImageIndex(index); + } + + unsigned retro_get_num_images() + { + return ourGame->getDiskControl().getNumImages(); + } + + bool retro_replace_image_index(unsigned index, const struct retro_game_info *info) + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%s)\n", __FUNCTION__, info->path); + if (info->path) + { + return ourGame->getDiskControl().replaceImageIndex(index, info->path); + } + else + { + return ourGame->getDiskControl().removeImageIndex(index); + } + } + + bool retro_add_image_index() + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); + return ourGame->getDiskControl().addImageIndex(); + } + + bool retro_set_initial_image(unsigned index, const char *path) + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d) = %s\n", __FUNCTION__, index, path); + ra2::DiskControl::setInitialPath(index, path); + return true; + } + + bool retro_get_image_path(unsigned index, char *path, size_t len) + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, index); + return ourGame->getDiskControl().getImagePath(index, path, len); + } + + bool retro_get_image_label(unsigned index, char *label, size_t len) + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, index); + return ourGame->getDiskControl().getImageLabel(index, label, len); + } + } void retro_init(void) { ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); - const char *dir = NULL; - if (ra2::environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) - { - ra2::retro_base_directory = dir; - } } void retro_deinit(void) { + ourGame.reset(); ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); } unsigned retro_api_version(void) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); return RETRO_API_VERSION; } @@ -58,6 +125,27 @@ void retro_set_controller_port_device(unsigned port, unsigned device) if (port == 0) { ra2::Game::ourInputDevices[port] = device; + + switch (device) + { + case RETRO_DEVICE_NONE: + Paddle::instance.reset(); + break; + case RETRO_DEVICE_JOYPAD: + Paddle::instance.reset(new ra2::Joypad(device)); + Paddle::setSquaring(false); + break; + case RETRO_DEVICE_ANALOG: + Paddle::instance.reset(new ra2::Analog(device)); + Paddle::setSquaring(true); + break; + case RETRO_DEVICE_MOUSE: + Paddle::instance.reset(new ra2::Mouse(device, &ourGame)); + Paddle::setSquaring(false); + break; + default: + break; + } } } @@ -70,10 +158,9 @@ void retro_get_system_info(retro_system_info *info) info->library_name = "AppleWin"; info->library_version = version.c_str(); info->need_fullpath = true; - info->valid_extensions = "bin|do|dsk|nib|po|gz|woz|zip|2mg|2img|iie|apl|hdv|yaml"; + info->valid_extensions = "bin|do|dsk|nib|po|gz|woz|zip|2mg|2img|iie|apl|hdv|yaml|m3u"; } - void retro_get_system_av_info(retro_system_av_info *info) { ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); @@ -103,7 +190,8 @@ void retro_set_environment(retro_environment_t cb) static const struct retro_controller_description controllers[] = { { "Standard Joypad", RETRO_DEVICE_JOYPAD }, - { "Analog Joypad", RETRO_DEVICE_ANALOG } + { "Analog Joypad", RETRO_DEVICE_ANALOG }, + { "Mouse", RETRO_DEVICE_MOUSE }, }; static const struct retro_controller_info ports[] = @@ -123,36 +211,61 @@ void retro_set_environment(retro_environment_t cb) retro_frame_time_callback timeCallback = {&ra2::Game::frameTimeCallback, 1000000 / ra2::Game::FPS}; cb(RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK, &timeCallback); + // see retro_get_memory_data() below + bool achievements = true; + cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); + + unsigned dciVersion = 0; + if (cb(RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION, &dciVersion) && (dciVersion >= 1)) + { + retro_disk_control_ext_callback diskControlExtCallback = { + &retro_set_eject_state, &retro_get_eject_state, + &retro_get_image_index, &retro_set_image_index, + &retro_get_num_images, &retro_replace_image_index, + &retro_add_image_index, nullptr, + &retro_get_image_path, &retro_get_image_label + }; + // intentionally skip retro_set_initial_image + // as we do always want to restart with the first disk of a playlist + // which (normally) is the only bootable floppy of a game + cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE, &diskControlExtCallback); + } + else + { + retro_disk_control_callback diskControlCallback = { + &retro_set_eject_state, &retro_get_eject_state, + &retro_get_image_index, &retro_set_image_index, + &retro_get_num_images, &retro_replace_image_index, + &retro_add_image_index + }; + cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &diskControlCallback); + } + ra2::SetupRetroVariables(); } void retro_set_audio_sample(retro_audio_sample_t cb) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); ra2::audio_cb = cb; } void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); ra2::audio_batch_cb = cb; } void retro_set_input_poll(retro_input_poll_t cb) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); ra2::input_poll_cb = cb; } void retro_set_input_state(retro_input_state_t cb) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); ra2::input_state_cb = cb; } void retro_set_video_refresh(retro_video_refresh_t cb) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); ra2::video_cb = cb; } @@ -182,36 +295,45 @@ bool retro_load_game(const retro_game_info *info) std::unique_ptr game(new ra2::Game()); const std::string snapshotEnding = ".aws.yaml"; - const std::string gamePath = info->path; + const std::string playlistEnding = ".m3u"; bool ok; - if (endsWith(gamePath, snapshotEnding)) + + if (info->path && *info->path) { - ok = game->loadSnapshot(gamePath); + const std::string gamePath = info->path; + if (endsWith(gamePath, snapshotEnding)) + { + ok = game->loadSnapshot(gamePath); + } + else if (endsWith(gamePath, playlistEnding)) + { + ok = game->getDiskControl().insertPlaylist(gamePath); + } + else + { + ok = game->getDiskControl().insertDisk(gamePath); + } } else { - ok = game->loadGame(gamePath); + ok = false; } - ra2::log_cb(RETRO_LOG_INFO, "Game path: %s:%d\n", info->path, ok); + ra2::log_cb(RETRO_LOG_INFO, "Game path: %s -> %d\n", info->path, ok); if (ok) { ra2::display_message("Enable Game Focus Mode for better keyboard handling"); + std::swap(ourGame, game); } - std::swap(ourGame, game); return ok; } catch (const std::exception & e) { ra2::log_cb(RETRO_LOG_INFO, "Exception: %s\n", e.what()); } - catch (const std::string & s) - { - ra2::log_cb(RETRO_LOG_INFO, "Exception: %s\n", s.c_str()); - } return false; } @@ -224,13 +346,13 @@ void retro_unload_game(void) unsigned retro_get_region(void) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); return RETRO_REGION_NTSC; } void retro_reset(void) { ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); + ResetMachineState(); } bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num) @@ -241,20 +363,44 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t retro_serialize_size(void) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); - return 0; + try + { + const size_t size = ra2::RetroSerialisation::getSize(); + return size; + } + catch(const std::exception& e) + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s - %s\n", __FUNCTION__, e.what()); + return 0; + } } bool retro_serialize(void *data, size_t size) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); - return false; + try + { + ra2::RetroSerialisation::serialise(data, size, ourGame->getDiskControl()); + return true; + } + catch(const std::exception& e) + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s - %s\n", __FUNCTION__, e.what()); + return false; + } } bool retro_unserialize(const void *data, size_t size) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); - return false; + try + { + ra2::RetroSerialisation::deserialise(data, size, ourGame->getDiskControl()); + return true; + } + catch(const std::exception& e) + { + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s - %s\n", __FUNCTION__, e.what()); + return false; + } } void retro_cheat_reset(void) @@ -269,12 +415,24 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code) void *retro_get_memory_data(unsigned id) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); - return nullptr; + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, id); + switch (id & RETRO_MEMORY_MASK) + { + case RETRO_MEMORY_SYSTEM_RAM: + return MemGetBankPtr(0); + default: + return nullptr; + }; } size_t retro_get_memory_size(unsigned id) { - ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); - return 0; + ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, id); + switch (id & RETRO_MEMORY_MASK) + { + case RETRO_MEMORY_SYSTEM_RAM: + return _6502_MEM_LEN; + default: + return 0; + }; } diff --git a/source/frontends/libretro/mouse.cpp b/source/frontends/libretro/mouse.cpp new file mode 100644 index 000000000..ed26de4e9 --- /dev/null +++ b/source/frontends/libretro/mouse.cpp @@ -0,0 +1,21 @@ +#include "frontends/libretro/mouse.h" +#include "frontends/libretro/environment.h" +#include "frontends/libretro/game.h" + +#include "libretro.h" + +namespace ra2 +{ + + Mouse::Mouse(unsigned device, const std::unique_ptr * game) + : ControllerBase(device, {RETRO_DEVICE_ID_MOUSE_LEFT, RETRO_DEVICE_ID_MOUSE_RIGHT}) + , myGame(game) + { + } + + double Mouse::getAxis(int i) const + { + return *myGame ? (*myGame)->getMousePosition(i) : 0.0; + } + +} diff --git a/source/frontends/libretro/mouse.h b/source/frontends/libretro/mouse.h new file mode 100644 index 000000000..aba2c83d0 --- /dev/null +++ b/source/frontends/libretro/mouse.h @@ -0,0 +1,23 @@ +#pragma once + +#include "frontends/libretro/joypadbase.h" + +#include + +namespace ra2 +{ + + class Game; + + class Mouse : public ControllerBase + { + public: + Mouse(unsigned device, const std::unique_ptr * game); + + double getAxis(int i) const override; + + private: + const std::unique_ptr * myGame; + }; + +} diff --git a/source/frontends/libretro/rdirectsound.cpp b/source/frontends/libretro/rdirectsound.cpp index 3d52a7ffc..bb02a6e3b 100644 --- a/source/frontends/libretro/rdirectsound.cpp +++ b/source/frontends/libretro/rdirectsound.cpp @@ -1,7 +1,7 @@ #include "frontends/libretro/rdirectsound.h" #include "frontends/libretro/environment.h" -#include "linux/windows/dsound.h" +#include "windows.h" #include "linux/linuxinterface.h" #include diff --git a/source/frontends/libretro/retroframe.cpp b/source/frontends/libretro/retroframe.cpp index 3b857387a..02ea04d8a 100644 --- a/source/frontends/libretro/retroframe.cpp +++ b/source/frontends/libretro/retroframe.cpp @@ -106,9 +106,9 @@ namespace ra2 video_cb(myVideoBuffer.data() + myOffset, myBorderlessWidth, myBorderlessHeight, myPitch); } - void RetroFrame::Initialize() + void RetroFrame::Initialize(bool resetVideoState) { - CommonFrame::Initialize(); + CommonFrame::Initialize(resetVideoState); FrameRefreshStatus(DRAW_TITLE); Video & video = GetVideo(); @@ -139,7 +139,7 @@ namespace ra2 void RetroFrame::GetBitmap(LPCSTR lpBitmapName, LONG cb, LPVOID lpvBits) { const std::string filename = getBitmapFilename(lpBitmapName); - const std::string path = myResourcePath + filename; + const std::string path = getResourcePath(filename); std::vector buffer; readFileToBuffer(path, buffer); diff --git a/source/frontends/libretro/retroframe.h b/source/frontends/libretro/retroframe.h index 376dd34b0..a77985dcb 100644 --- a/source/frontends/libretro/retroframe.h +++ b/source/frontends/libretro/retroframe.h @@ -1,6 +1,7 @@ #pragma once #include "frontends/common2/commonframe.h" +#include "frontends/common2/gnuframe.h" #include #include @@ -8,14 +9,14 @@ namespace ra2 { - class RetroFrame : public common2::CommonFrame + class RetroFrame : public virtual common2::CommonFrame, public common2::GNUFrame { public: RetroFrame(); void VideoPresentScreen() override; void FrameRefreshStatus(int drawflags) override; - void Initialize() override; + void Initialize(bool resetVideoState) override; void Destroy() override; int FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType) override; void GetBitmap(LPCSTR lpBitmapName, LONG cb, LPVOID lpvBits) override; diff --git a/source/frontends/libretro/retroregistry.cpp b/source/frontends/libretro/retroregistry.cpp index 30b65fd99..45faf2123 100644 --- a/source/frontends/libretro/retroregistry.cpp +++ b/source/frontends/libretro/retroregistry.cpp @@ -46,11 +46,21 @@ namespace {"TK3000 //e", A2TYPE_TK30002E}, } }, + { + "slot3", + "Card in slot 3", + "Configuration\\Slot 3", + REGVALUE_CARD_TYPE, + { + {"Empty", CT_Empty}, + {"Video HD", CT_VidHD}, + } + }, { "slot4", "Card in slot 4", - REG_CONFIG, - REGVALUE_SLOT4, + "Configuration\\Slot 4", + REGVALUE_CARD_TYPE, { {"Empty", CT_Empty}, {"Mouse", CT_MouseInterface}, @@ -61,8 +71,8 @@ namespace { "slot5", "Card in slot 5", - REG_CONFIG, - REGVALUE_SLOT5, + "Configuration\\Slot 5", + REGVALUE_CARD_TYPE, { {"Empty", CT_Empty}, {"CP/M", CT_Z80}, @@ -96,7 +106,7 @@ namespace { if (i > 0) { - ss << "|"; + ss << "|"; } ss << var.values[i].first; } diff --git a/source/frontends/libretro/serialisation.cpp b/source/frontends/libretro/serialisation.cpp new file mode 100644 index 000000000..0ceadb7da --- /dev/null +++ b/source/frontends/libretro/serialisation.cpp @@ -0,0 +1,114 @@ +#include "StdAfx.h" +#include "SaveState.h" + +#include "frontends/libretro/serialisation.h" +#include "frontends/libretro/environment.h" +#include "frontends/libretro/diskcontrol.h" + +#include +#include + +namespace +{ + class AutoFile + { + public: + AutoFile(); + ~AutoFile(); + + const std::string & getFilename() const; // only if true + + protected: + std::string myFilename; + }; + + AutoFile::AutoFile() + { + // massive race condition, but without changes to AW, little can we do here + const char * tmp = std::tmpnam(nullptr); + if (!tmp) + { + throw std::runtime_error("Cannot create temporary file"); + } + myFilename = tmp; + } + + AutoFile::~AutoFile() + { + std::remove(myFilename.c_str()); + } + + const std::string & AutoFile::getFilename() const + { + return myFilename; + } + + void saveToFile(const std::string & filename) // cannot be null! + { + Snapshot_SetFilename(filename); + Snapshot_SaveState(); + } + +} + +namespace ra2 +{ + + size_t RetroSerialisation::getSize() + { + AutoFile autoFile; + std::string const & filename = autoFile.getFilename(); + saveToFile(filename); + std::ifstream ifs(filename, std::ios::binary | std::ios::ate); + + const size_t fileSize = ifs.tellg(); + // we add a buffer to include a few things + // DiscControl images + // various sizes + // small variations in AW yaml format + const size_t buffer = 4096; + return fileSize + buffer; + } + + void RetroSerialisation::serialise(void * data, size_t size, const DiskControl & diskControl) + { + Buffer buffer(reinterpret_cast(data), size); + diskControl.serialise(buffer); + + AutoFile autoFile; + std::string const & filename = autoFile.getFilename(); + saveToFile(filename); + std::ifstream ifs(filename, std::ios::binary | std::ios::ate); + + size_t const fileSize = ifs.tellg(); + buffer.get() = fileSize; + + char * begin, * end; + buffer.get(fileSize, begin, end); + + ifs.seekg(0, std::ios::beg); + ifs.read(begin, end - begin); + } + + void RetroSerialisation::deserialise(const void * data, size_t size, DiskControl & diskControl) + { + Buffer buffer(reinterpret_cast(data), size); + diskControl.deserialise(buffer); + + const size_t fileSize = buffer.get(); + + AutoFile autoFile; + std::string const & filename = autoFile.getFilename(); + // do not remove the {} scope below! it ensures the file is flushed + { + char const * begin, * end; + buffer.get(fileSize, begin, end); + std::ofstream ofs(filename, std::ios::binary); + ofs.write(begin, end - begin); + } + + Snapshot_SetFilename(filename); + Snapshot_LoadState(); + } + +} diff --git a/source/frontends/libretro/serialisation.h b/source/frontends/libretro/serialisation.h new file mode 100644 index 000000000..c222b8523 --- /dev/null +++ b/source/frontends/libretro/serialisation.h @@ -0,0 +1,16 @@ +#include + +namespace ra2 +{ + + class DiskControl; + + class RetroSerialisation + { + public: + static size_t getSize(); + static void serialise(void * data, size_t size, const DiskControl & diskControl); + static void deserialise(const void * data, size_t size, DiskControl & diskControl); + }; + +} diff --git a/source/frontends/ncurses/CMakeLists.txt b/source/frontends/ncurses/CMakeLists.txt index c65fe9ddd..0e8895def 100644 --- a/source/frontends/ncurses/CMakeLists.txt +++ b/source/frontends/ncurses/CMakeLists.txt @@ -1,4 +1,5 @@ include(FindPkgConfig) +include(FindCurses) set(SOURCE_FILES main.cpp @@ -22,22 +23,23 @@ add_executable(applen ${HEADER_FILES} ) -pkg_search_module(NCURSESW REQUIRED ncursesw) +find_package(Curses REQUIRED) +# pkg_search_module(NCURSESW REQUIRED ncursesw) pkg_search_module(LIBEVDEV REQUIRED libevdev) target_include_directories(applen PRIVATE - ${NCURSESW_INCLUDE_DIRS} + ${CURSES_INCLUDE_DIRS} ${LIBEVDEV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ) target_compile_options(applen PRIVATE - ${NCURSESW_CFLAGS_OTHER} + ${CURSES_CFLAGS} ${LIBEVDEV_CFLAGS_OTHER} ) target_link_libraries(applen PRIVATE - ${NCURSESW_LIBRARIES} + ${CURSES_LIBRARIES} ${LIBEVDEV_LIBRARIES} appleii common2 diff --git a/source/frontends/ncurses/main.cpp b/source/frontends/ncurses/main.cpp index 56e078e1d..3f46346b8 100644 --- a/source/frontends/ncurses/main.cpp +++ b/source/frontends/ncurses/main.cpp @@ -151,8 +151,14 @@ namespace const std::shared_ptr frame(new na2::NFrame(paddle)); const Initialisation init(frame, paddle); - applyOptions(options); - frame->Initialize(); + common2::applyOptions(options); + frame->Begin(); + + common2::setSnapshotFilename(options.snapshotFilename); + if (options.loadSnapshot) + { + frame->LoadSnapshot(); + } na2::SetCtrlCHandler(options.headless); @@ -165,6 +171,7 @@ namespace { EnterMessageLoop(options, frame); } + frame->End(); return 0; } @@ -182,14 +189,4 @@ int main(int argc, const char * argv []) std::cerr << e.what() << std::endl; return 1; } - catch (const std::string & e) - { - std::cerr << e << std::endl; - return 1; - } - catch (int e) - { - std::cerr << "Exit process called: " << e << std::endl; - return e; - } } diff --git a/source/frontends/ncurses/nframe.cpp b/source/frontends/ncurses/nframe.cpp index c2231a9f9..048fc8d51 100644 --- a/source/frontends/ncurses/nframe.cpp +++ b/source/frontends/ncurses/nframe.cpp @@ -6,6 +6,9 @@ #include "Interface.h" #include "Memory.h" #include "Log.h" +#include "Core.h" +#include "CardManager.h" +#include "Disk.h" #include #include @@ -47,9 +50,9 @@ namespace na2 // so we can run headless } - void NFrame::Initialize() + void NFrame::Initialize(bool resetVideoState) { - CommonFrame::Initialize(); + CommonFrame::Initialize(resetVideoState); myTextFlashCounter = 0; myTextFlashState = 0; myAsciiArt.reset(new ASCIIArt()); @@ -107,11 +110,9 @@ namespace na2 keypad(myFrame.get(), true); wrefresh(myFrame.get()); - myStatus.reset(newwin(4, width, 1 + myRows + 1, left), delwin); - box(myStatus.get(), 0 , 0); - wrefresh(myStatus.get()); + myStatus.reset(newwin(8, width, 1 + myRows + 1, left), delwin); + FrameRefreshStatus(DRAW_LEDS | DRAW_BUTTON_DRIVES | DRAW_DISK_STATUS); } - } WINDOW * NFrame::GetWindow() @@ -196,6 +197,37 @@ namespace na2 wrefresh(myFrame.get()); } + void NFrame::FrameRefreshStatus(int /* drawflags */) + { + werase(myStatus.get()); + box(myStatus.get(), 0 , 0); + + int row = 0; + + CardManager& cardManager = GetCardMgr(); + if (cardManager.QuerySlot(SLOT6) == CT_Disk2) + { + Disk2InterfaceCard& disk2 = dynamic_cast(cardManager.GetRef(SLOT6)); + const size_t maximumWidth = myColumns - 6; // 6 is the width of "S6D1: " + for (UINT i = DRIVE_1; i <= DRIVE_2; ++i) + { + const std::string name = disk2.GetBaseName(i).substr(0, maximumWidth); + mvwprintw(myStatus.get(), ++row, 1, "S6D%d: %s", 1 + i, name.c_str()); + } + } + else + { + row += DRIVE_2 - DRIVE_1 + 1; + } + + ++row; + + mvwprintw(myStatus.get(), ++row, 1, "F2: ResetMachine / Shift-F2: CtrlReset"); + mvwprintw(myStatus.get(), ++row, 1, "F3: Exit / F5: Swap"); + mvwprintw(myStatus.get(), ++row, 1, "F11: Load State / F12: Save State"); + wrefresh(myStatus.get()); + } + void NFrame::VideoUpdateFlash() { ++myTextFlashCounter; diff --git a/source/frontends/ncurses/nframe.h b/source/frontends/ncurses/nframe.h index fdfb912cb..1a96546cf 100644 --- a/source/frontends/ncurses/nframe.h +++ b/source/frontends/ncurses/nframe.h @@ -1,6 +1,7 @@ #pragma once #include "frontends/common2/commonframe.h" +#include "frontends/common2/gnuframe.h" #include #include @@ -13,7 +14,7 @@ namespace na2 class EvDevPaddle; struct NCurses; - class NFrame : public common2::CommonFrame + class NFrame : public virtual common2::CommonFrame, public common2::GNUFrame { public: NFrame(const std::shared_ptr & paddle); @@ -21,10 +22,11 @@ namespace na2 WINDOW * GetWindow(); WINDOW * GetStatus(); - void Initialize() override; + void Initialize(bool resetVideoState) override; void Destroy() override; void VideoPresentScreen() override; int FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType) override; + void FrameRefreshStatus(int drawflags) override; void ProcessEvDev(); diff --git a/source/frontends/qt/CMakeLists.txt b/source/frontends/qt/CMakeLists.txt index e3bc4926d..3ca93b762 100644 --- a/source/frontends/qt/CMakeLists.txt +++ b/source/frontends/qt/CMakeLists.txt @@ -54,6 +54,7 @@ target_link_libraries(qapple PRIVATE Qt5::Multimedia appleii qhexview-lib + windows ) install(TARGETS qapple diff --git a/source/frontends/qt/QHexView b/source/frontends/qt/QHexView index 44dedf500..b3c0e4a8b 160000 --- a/source/frontends/qt/QHexView +++ b/source/frontends/qt/QHexView @@ -1 +1 @@ -Subproject commit 44dedf500f056bad3b8b7ff6f7338a9a09cce1f4 +Subproject commit b3c0e4a8b7318f3c6ed8a29a75694b376ba16cf1 diff --git a/source/frontends/qt/options.cpp b/source/frontends/qt/options.cpp index b3fa362cc..31d7da258 100644 --- a/source/frontends/qt/options.cpp +++ b/source/frontends/qt/options.cpp @@ -60,6 +60,9 @@ namespace void insertHD(HarddiskInterfaceCard * pHarddiskCard, const QString & filename, const int disk) { + if (!pHarddiskCard) + return; + if (filename.isEmpty()) { pHarddiskCard->Unplug(disk); @@ -200,6 +203,7 @@ void getAppleWinPreferences(PreferenceData & data) } data.enhancedSpeed = pDisk2Card && pDisk2Card->GetEnhanceDisk(); + data.cardInSlot3 = cardManager.QuerySlot(SLOT3); data.cardInSlot4 = cardManager.QuerySlot(SLOT4); data.cardInSlot5 = cardManager.QuerySlot(SLOT5); data.hdInSlot7 = pHarddiskCard; @@ -257,6 +261,10 @@ void setAppleWinPreferences(const std::shared_ptr & frame, const Prefer SetMainCpu(cpu); REGSAVE(TEXT(REGVALUE_CPU_TYPE), cpu); } + if (currentData.cardInSlot3 != newData.cardInSlot3) + { + SetSlot(SLOT3, newData.cardInSlot3); + } if (currentData.cardInSlot4 != newData.cardInSlot4) { SetSlot(SLOT4, newData.cardInSlot4); diff --git a/source/frontends/qt/options.h b/source/frontends/qt/options.h index 24058f5e0..986c20288 100644 --- a/source/frontends/qt/options.h +++ b/source/frontends/qt/options.h @@ -32,6 +32,7 @@ struct PreferenceData GlobalOptions options; eApple2Type apple2Type; + SS_CARDTYPE cardInSlot3; SS_CARDTYPE cardInSlot4; SS_CARDTYPE cardInSlot5; bool hdInSlot7; diff --git a/source/frontends/qt/preferences.cpp b/source/frontends/qt/preferences.cpp index 34d602ec4..7ec7d091d 100644 --- a/source/frontends/qt/preferences.cpp +++ b/source/frontends/qt/preferences.cpp @@ -121,6 +121,7 @@ namespace const std::vector computerTypes = {A2TYPE_APPLE2, A2TYPE_APPLE2PLUS, A2TYPE_APPLE2JPLUS, A2TYPE_APPLE2E, A2TYPE_APPLE2EENHANCED, A2TYPE_PRAVETS82, A2TYPE_PRAVETS8M, A2TYPE_PRAVETS8A, A2TYPE_BASE64A, A2TYPE_TK30002E}; + const std::vector cardsInSlot3 = {CT_Empty, CT_Uthernet, CT_Uthernet2, CT_VidHD}; const std::vector cardsInSlot4 = {CT_Empty, CT_MouseInterface, CT_MockingboardC, CT_Phasor}; const std::vector cardsInSlot5 = {CT_Empty, CT_Z80, CT_MockingboardC, CT_SAM}; @@ -219,6 +220,9 @@ void Preferences::setData(const PreferenceData & data) const int apple2Index = getIndexInList(computerTypes, data.apple2Type, 2); ui->apple2Type->setCurrentIndex(apple2Index); + const int slot3Index = getIndexInList(cardsInSlot3, data.cardInSlot3, 0); + ui->slot3_combo->setCurrentIndex(slot3Index); + const int slot4Index = getIndexInList(cardsInSlot4, data.cardInSlot4, 0); ui->slot4_combo->setCurrentIndex(slot4Index); @@ -266,6 +270,7 @@ PreferenceData Preferences::getData() const data.mockingboardVolume = ui->mb_volume->maximum() - ui->mb_volume->value(); data.enhancedSpeed = ui->enhanced_speed->isChecked(); data.apple2Type = computerTypes[ui->apple2Type->currentIndex()]; + data.cardInSlot3 = cardsInSlot3[ui->slot3_combo->currentIndex()]; data.cardInSlot4 = cardsInSlot4[ui->slot4_combo->currentIndex()]; data.cardInSlot5 = cardsInSlot5[ui->slot5_combo->currentIndex()]; data.hdInSlot7 = ui->hd_7->isChecked(); diff --git a/source/frontends/qt/preferences.ui b/source/frontends/qt/preferences.ui index b34cba4d5..0675162c6 100644 --- a/source/frontends/qt/preferences.ui +++ b/source/frontends/qt/preferences.ui @@ -7,7 +7,7 @@ 0 0 648 - 319 + 335 @@ -40,28 +40,28 @@ Slots - + Slot 4 - + Slot 5 - + Slot 7 - + Hard Disk @@ -118,7 +118,7 @@ - + @@ -142,7 +142,7 @@ - + @@ -166,6 +166,37 @@ + + + + Slot 3 + + + + + + + + Empty + + + + + Uthernet + + + + + Uthernet II + + + + + Video HD + + + + diff --git a/source/frontends/qt/qapple.cpp b/source/frontends/qt/qapple.cpp index 557e00ace..9de150572 100644 --- a/source/frontends/qt/qapple.cpp +++ b/source/frontends/qt/qapple.cpp @@ -10,19 +10,13 @@ #include "Harddisk.h" #include "Log.h" #include "CPU.h" -#include "Memory.h" #include "LanguageCard.h" -#include "Mockingboard.h" -#include "MouseInterface.h" -#include "ParallelPrinter.h" #include "Video.h" -#include "SoundCore.h" #include "NTSC.h" #include "SaveState.h" #include "Speaker.h" #include "Riff.h" #include "RGBMonitor.h" -#include "Utilities.h" #include "linux/benchmark.h" #include "linux/version.h" @@ -76,72 +70,6 @@ namespace * */ - void loadEmulator(const std::shared_ptr & frame, const GlobalOptions & options) - { - LoadConfiguration(); - - // ResetDefaultMachineMemTypes(); - - switch (options.slot0Card) { - case 1: // Language Card - SetExpansionMemType(CT_LanguageCard); - break; - case 2: // Saturn 64 - SetSaturnMemorySize(Saturn128K::kMaxSaturnBanks / 2); - SetExpansionMemType(CT_Saturn128K); - break; - case 3: // Saturn 128 - SetSaturnMemorySize(Saturn128K::kMaxSaturnBanks); - SetExpansionMemType(CT_Saturn128K); - break; - case 4: // RamWorks - SetRamWorksMemorySize(options.ramWorksMemorySize); - SetExpansionMemType(CT_RamWorksIII); - break; - } - - DSInit(); - MB_Initialize(); - SpkrInitialize(); - MemInitialize(); - frame->Initialize(); - - CardManager & cardManager = GetCardMgr(); - cardManager.GetDisk2CardMgr().Reset(); - if (cardManager.QuerySlot(SLOT7) == CT_GenericHDD) - { - dynamic_cast(cardManager.GetRef(SLOT7)).Reset(true); - } - } - - void unloadEmulator(const std::shared_ptr & frame) - { - CardManager & cardManager = GetCardMgr(); - - CMouseInterface* pMouseCard = cardManager.GetMouseCard(); - if (pMouseCard) - { - pMouseCard->Reset(); - } - if (cardManager.QuerySlot(SLOT7) == CT_GenericHDD) - { - dynamic_cast(cardManager.GetRef(SLOT7)).Destroy(); - } - PrintDestroy(); - MemDestroy(); - SpkrDestroy(); - frame->Destroy(); - MB_Destroy(); - DSUninit(); - CpuDestroy(); - - cardManager.GetDisk2CardMgr().Destroy(); - LogDone(); - RiffFinishWriteFile(); - - QDirectSound::stop(); - } - qint64 emulatorTimeInMS() { const double timeInSeconds = g_nCumulativeCycles / g_fCurrentCLK6502; @@ -206,7 +134,7 @@ QApple::QApple(QWidget *parent) : on_actionPause_triggered(); initialiseEmulator(); - loadEmulator(myFrame, myOptions); + myFrame->Begin(); setAcceptDrops(true); } @@ -219,7 +147,8 @@ QApple::~QApple() void QApple::closeEvent(QCloseEvent * event) { stopTimer(); - unloadEmulator(myFrame); + myFrame->End(); + QDirectSound::stop(); QSettings settings; settings.setValue("QApple/window/geometry", saveGeometry().toBase64()); @@ -367,8 +296,7 @@ void QApple::on_actionReboot_triggered() emit endEmulator(); mySaveStateLabel->clear(); - unloadEmulator(myFrame); - loadEmulator(myFrame, myOptions); + myFrame->Restart(); myFrame->VideoPresentScreen(); } diff --git a/source/frontends/qt/qapple.pro b/source/frontends/qt/qapple.pro index 33c9b8257..4e159fd08 100644 --- a/source/frontends/qt/qapple.pro +++ b/source/frontends/qt/qapple.pro @@ -48,10 +48,13 @@ RESOURCES += \ LIBS += -L$$PWD/../../../build/source -lappleii LIBS += -L$$PWD/../../../build/source/frontends/common2 -lcommon2 +LIBS += -L$$PWD/../../../build/source/linux/libwindows -lwindows LIBS += -L$$PWD/../../../build/source/frontends/qt/QHexView -lqhexview-lib -LIBS += -levdev +LIBS += -levdev -lminizip -lz -lyaml -lslirp -lpcap INCLUDEPATH += $$PWD/../../../source +INCLUDEPATH += $$PWD/../../../source/linux/libwindows INCLUDEPATH += $$PWD/QHexView +INCLUDEPATH += /usr/include/minizip QMAKE_RPATHDIR += $ORIGIN/../.. diff --git a/source/frontends/qt/qdirectsound.cpp b/source/frontends/qt/qdirectsound.cpp index 72d05bc23..333295ced 100644 --- a/source/frontends/qt/qdirectsound.cpp +++ b/source/frontends/qt/qdirectsound.cpp @@ -2,7 +2,7 @@ #include "loggingcategory.h" #include "qdirectsound.h" -#include "linux/windows/dsound.h" +#include "windows.h" #include "linux/linuxinterface.h" #include #include diff --git a/source/frontends/qt/qtframe.cpp b/source/frontends/qt/qtframe.cpp index 305de1b9c..9d759a070 100644 --- a/source/frontends/qt/qtframe.cpp +++ b/source/frontends/qt/qtframe.cpp @@ -11,6 +11,7 @@ #include #include #include +#include QtFrame::QtFrame(Emulator * emulator, QMdiSubWindow * window) : myEmulator(emulator), myWindow(window), myForceRepaint(false) { @@ -36,9 +37,9 @@ void QtFrame::FrameRefreshStatus(int drawflags) } } -void QtFrame::Initialize() +void QtFrame::Initialize(bool resetVideoState) { - LinuxFrame::Initialize(); + LinuxFrame::Initialize(resetVideoState); FrameRefreshStatus(DRAW_TITLE); myEmulator->loadVideoSettings(); myEmulator->displayLogo(); @@ -143,6 +144,12 @@ BYTE* QtFrame::GetResource(WORD id, LPCSTR lpType, DWORD expectedSize) return reinterpret_cast(myResource.data()); } +std::string QtFrame::Video_GetScreenShotFolder() +{ + const QString pictures = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + return pictures.toStdString() + "/"; +} + void SingleStep(bool /* bReinit */) { diff --git a/source/frontends/qt/qtframe.h b/source/frontends/qt/qtframe.h index d0c5cf1a7..82b49f791 100644 --- a/source/frontends/qt/qtframe.h +++ b/source/frontends/qt/qtframe.h @@ -16,12 +16,13 @@ class QtFrame : public LinuxFrame void VideoPresentScreen() override; void FrameRefreshStatus(int drawflags) override; - void Initialize() override; + void Initialize(bool resetVideoState) override; void Destroy() override; int FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType) override; void GetBitmap(LPCSTR lpBitmapName, LONG cb, LPVOID lpvBits) override; BYTE* GetResource(WORD id, LPCSTR lpType, DWORD expectedSize) override; + std::string Video_GetScreenShotFolder() override; void SetForceRepaint(const bool force); void SetZoom(const int x); diff --git a/source/frontends/qt/qvideo.cpp b/source/frontends/qt/qvideo.cpp index 76cc7149d..3848d4527 100644 --- a/source/frontends/qt/qvideo.cpp +++ b/source/frontends/qt/qvideo.cpp @@ -31,6 +31,9 @@ void QVideo::loadVideoSettings() myWidth = video.GetFrameBufferWidth(); myHeight = video.GetFrameBufferHeight(); + myLogoX = mySX + video.GetFrameBufferCentringOffsetX(); + myLogoY = mySY + video.GetFrameBufferCentringOffsetY(); + myFrameBuffer = video.GetFrameBuffer(); } @@ -58,7 +61,7 @@ void QVideo::displayLogo() QImage frameBuffer = getScreenImage(); QPainter painter(&frameBuffer); - painter.drawImage(mySX, mySY, myLogo); + painter.drawImage(myLogoX, myLogoY, myLogo); } void QVideo::paintEvent(QPaintEvent *) diff --git a/source/frontends/qt/qvideo.h b/source/frontends/qt/qvideo.h index 1f7deec26..e815c907d 100644 --- a/source/frontends/qt/qvideo.h +++ b/source/frontends/qt/qvideo.h @@ -41,6 +41,9 @@ public slots: int myWidth; int myHeight; + int myLogoX; + int myLogoY; + quint8 * myFrameBuffer; QImage getScreenImage() const; diff --git a/source/frontends/sdl/CMakeLists.txt b/source/frontends/sdl/CMakeLists.txt index 7374a9891..3d75a3e61 100644 --- a/source/frontends/sdl/CMakeLists.txt +++ b/source/frontends/sdl/CMakeLists.txt @@ -1,6 +1,5 @@ include(FindPkgConfig) - -MESSAGE("***SDL CMAKE***") +include(FindOpenGL) add_executable(sa2) @@ -8,24 +7,19 @@ set(IMGUI_PATH "imgui/imgui") set(IMGUI_CLUB_PATH "imgui/imgui_club") find_package(SDL2 REQUIRED) +# we should use find_package, but Ubuntu does not provide it for SDL2_image pkg_search_module(SDL2_IMAGE REQUIRED SDL2_image) -# the default is to use GLES -set (SA2_USE_OPENGL 0) - -if (${APPLE}) - MESSAGE("MACOSX OPENGL SEARCH") - set (SA2_USE_OPENGL 1) -endif() - -if (${SA2_USE_OPENGL}) - if(NOT(${APPLE})) - pkg_search_module(GLES2 REQUIRED opengl) +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + # only OpenGL supported on MacOS + set (SA2_OPENGL ON) else() - find_package(opengl REQUIRED opengl) - MESSAGE("OPENGL INCLUDE DIRS $(OPENGL_INCLUDE_DIR)}") + option(SA2_OPENGL "Prefer OpenGL over OpenGL ES" OFF) endif() + +if (SA2_OPENGL) + find_package(OpenGL REQUIRED) else() pkg_search_module(GLES2 REQUIRED glesv2) target_compile_definitions(sa2 PRIVATE @@ -59,27 +53,18 @@ target_compile_features(sa2 PUBLIC cxx_std_17) target_include_directories(sa2 PRIVATE ${SDL2_INCLUDE_DIRS} ${SDL2_IMAGE_INCLUDE_DIRS} - ${OPENGL_INCLUDE_DIRS} + ${GLES2_INCLUDE_DIRS} ) -if(NOT(${APPLE})) target_link_libraries(sa2 PRIVATE ${SDL2_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} ${GLES2_LIBRARIES} + OpenGL::GL appleii common2 + windows ) -else() -target_link_libraries(sa2 PRIVATE - ${SDL2_LIBRARIES} - ${SDL2_IMAGE_LIBRARIES} - ${OPENGL_LIBRARIES} - ${YAML_LINK_LIBRARIES} - appleii - common2 - ) -endif() target_link_directories(sa2 PRIVATE ${SDL2_LIBRARY_DIRS} diff --git a/source/frontends/sdl/imgui/imgui b/source/frontends/sdl/imgui/imgui index 19c3773cc..f84c93d83 160000 --- a/source/frontends/sdl/imgui/imgui +++ b/source/frontends/sdl/imgui/imgui @@ -1 +1 @@ -Subproject commit 19c3773cc09fffdf950c3e88273f0a15adad1176 +Subproject commit f84c93d8347617326952dfb1dbb921c53df071cc diff --git a/source/frontends/sdl/imgui/imgui_club b/source/frontends/sdl/imgui/imgui_club index 02e679b7f..450b9cf35 160000 --- a/source/frontends/sdl/imgui/imgui_club +++ b/source/frontends/sdl/imgui/imgui_club @@ -1 +1 @@ -Subproject commit 02e679b7f4cfb01f9480dcbcac59552879f96573 +Subproject commit 450b9cf352c51530b9e23fb14b83891c2555b032 diff --git a/source/frontends/sdl/imgui/sdlimguiframe.cpp b/source/frontends/sdl/imgui/sdlimguiframe.cpp index d0462ab2a..8978b296b 100644 --- a/source/frontends/sdl/imgui/sdlimguiframe.cpp +++ b/source/frontends/sdl/imgui/sdlimguiframe.cpp @@ -36,6 +36,7 @@ namespace sa2 SDLImGuiFrame::SDLImGuiFrame(const common2::EmulatorOptions & options) : SDLFrame(options) + , myPresenting(false) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SA2_CONTEXT_FLAGS); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SA2_CONTEXT_PROFILE_MASK); @@ -96,9 +97,25 @@ namespace sa2 ImGui::StyleColorsDark(); ImGui_ImplSDL2_InitForOpenGL(myWindow.get(), myGLContext); - ImGui_ImplOpenGL3_Init(); + myDeadTopZone = 0; + myTexture = 0; + } + + SDLImGuiFrame::~SDLImGuiFrame() + { + glDeleteTextures(1, &myTexture); + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + SDL_GL_DeleteContext(myGLContext); + } + + void SDLImGuiFrame::Initialize(bool resetVideoState) + { + SDLFrame::Initialize(resetVideoState); + glDeleteTextures(1, &myTexture); glGenTextures(1, &myTexture); Video & video = GetVideo(); @@ -114,17 +131,6 @@ namespace sa2 myOffset = (width * borderHeight + borderWidth) * sizeof(bgra_t); allocateTexture(myTexture, myBorderlessWidth, myBorderlessHeight); - - myDeadTopZone = 0; - } - - SDLImGuiFrame::~SDLImGuiFrame() - { - glDeleteTextures(1, &myTexture); - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplSDL2_Shutdown(); - ImGui::DestroyContext(); - SDL_GL_DeleteContext(myGLContext); } void SDLImGuiFrame::UpdateTexture() @@ -182,18 +188,25 @@ namespace sa2 void SDLImGuiFrame::VideoPresentScreen() { - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL2_NewFrame(myWindow.get()); - ImGui::NewFrame(); - - // "this" is a bit circular - mySettings.show(this); - DrawAppleVideo(); - - ImGui::Render(); - ClearBackground(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - SDL_GL_SwapWindow(myWindow.get()); + // this is NOT REENTRANT + // the debugger (executed via mySettings.show(this)) might call it recursively + if (!myPresenting) + { + myPresenting = true; + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + // "this" is a bit circular + mySettings.show(this); + DrawAppleVideo(); + + ImGui::Render(); + ClearBackground(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(myWindow.get()); + myPresenting = false; + } } void SDLImGuiFrame::ProcessSingleEvent(const SDL_Event & event, bool & quit) diff --git a/source/frontends/sdl/imgui/sdlimguiframe.h b/source/frontends/sdl/imgui/sdlimguiframe.h index b18472f59..f9d740b80 100644 --- a/source/frontends/sdl/imgui/sdlimguiframe.h +++ b/source/frontends/sdl/imgui/sdlimguiframe.h @@ -3,6 +3,7 @@ #include "frontends/sdl/sdlframe.h" #include "frontends/sdl/imgui/sdlsettings.h" #include "frontends/sdl/imgui/glselector.h" +#include "frontends/common2/gnuframe.h" namespace common2 { @@ -12,7 +13,7 @@ namespace common2 namespace sa2 { - class SDLImGuiFrame : public SDLFrame + class SDLImGuiFrame : public SDLFrame, public common2::GNUFrame { public: SDLImGuiFrame(const common2::EmulatorOptions & options); @@ -21,6 +22,7 @@ namespace sa2 void VideoPresentScreen() override; void ResetSpeed() override; + void Initialize(bool resetVideoState) override; protected: @@ -39,6 +41,7 @@ namespace sa2 size_t myBorderlessHeight; int myDeadTopZone; // for mouse position + bool myPresenting; // VideoPresentScreen() is NOT REENTRANT SDL_GLContext myGLContext; ImTextureID myTexture; diff --git a/source/frontends/sdl/imgui/sdlsettings.cpp b/source/frontends/sdl/imgui/sdlsettings.cpp index e68101760..e10e9704a 100644 --- a/source/frontends/sdl/imgui/sdlsettings.cpp +++ b/source/frontends/sdl/imgui/sdlsettings.cpp @@ -18,6 +18,8 @@ #include "Registry.h" #include "Utilities.h" #include "Memory.h" +#include "ParallelPrinter.h" +#include "SaveState.h" #include "Debugger/Debugger_Types.h" @@ -48,6 +50,18 @@ namespace } } + void printBoolean(const char * label, const bool value, const char * falseValue, const char * trueValue) + { + int elem = value; + const char * name = value ? trueValue : falseValue; + ImGui::SliderInt(label, &elem, 0, 1, name); + } + + void printOnOff(const char * label, const bool value) + { + printBoolean(label, value, "OFF", "ON"); + } + char getPrintableChar(const uint8_t x) { if (x >= 0x20 && x <= 0x7e) @@ -233,6 +247,9 @@ namespace sa2 } ImGui::LabelText("Clock", "%15.2f Hz", g_fCurrentCLK6502); + ImGui::Separator(); + ImGui::LabelText("Printer", "%s", Printer_GetFilename().c_str()); + ImGui::LabelText("Save state", "%s", Snapshot_GetPathname().c_str()); ImGui::Separator(); if (frame->HardwareChanged()) @@ -306,7 +323,7 @@ namespace sa2 const bool isSelected = card == current; if (ImGui::Selectable(getCardName(card).c_str(), isSelected)) { - insertCard(slot, card); + insertCard(slot, card, frame); } if (isSelected) { @@ -330,7 +347,7 @@ namespace sa2 const bool isSelected = card == expansion; if (ImGui::Selectable(getCardName(card).c_str(), isSelected)) { - SetExpansionMemType(card); + setExpansionCard(card); } if (isSelected) { @@ -1013,6 +1030,40 @@ namespace sa2 } } + void ImGuiSettings::drawAnnunciators() + { + ImGui::TextUnformatted("Annunciators"); + ImGui::Separator(); + ImGui::BeginDisabled(); + for (UINT i = 0; i < 4; ++i) + { + char buffer[20]; + sprintf(buffer, "Ann %d", i); + printOnOff(buffer, MemGetAnnunciator(i)); + } + ImGui::EndDisabled(); + } + + void ImGuiSettings::drawSwitches() + { + Video & video = GetVideo(); + ImGui::TextUnformatted("Switches C0xx"); + ImGui::Separator(); + ImGui::BeginDisabled(); + printBoolean("50", video.VideoGetSWTEXT(), "GR", "TEXT"); + printBoolean("52", video.VideoGetSWMIXED(), "FULL", "MIX"); + printBoolean("54", video.VideoGetSWPAGE2(), "PAGE 1", "PAGE 2"); + printBoolean("56", video.VideoGetSWHIRES(), "RES LO", "RES HI"); + printBoolean("5E", !video.VideoGetSWDHIRES(), "DHGR", "HGR"); + ImGui::Separator(); + printBoolean("00", video.VideoGetSW80STORE(), "80sto 0", "80sto 1"); + printBoolean("02", GetMemMode() & MF_AUXREAD, "R m", "R x"); + printBoolean("02", GetMemMode() & MF_AUXWRITE, "W m", "R x"); + printBoolean("0C", video.VideoGetSW80COL(), "Col 40", "Col 80"); + printBoolean("0E", video.VideoGetSWAltCharSet(), "ASC", "MOUS"); + ImGui::EndDisabled(); + } + void ImGuiSettings::drawConsole() { const ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_ScrollY; @@ -1134,7 +1185,7 @@ namespace sa2 } // this is about right to contain the fixed-size register child - const ImVec2 registerTextSize = ImGui::CalcTextSize("012345678901"); + const ImVec2 registerTextSize = ImGui::CalcTextSize("Annunciators012345"); ImGui::BeginChild("Disasm", ImVec2(-registerTextSize.x, 0)); drawDisassemblyTable(frame); @@ -1142,8 +1193,12 @@ namespace sa2 ImGui::SameLine(); - ImGui::BeginChild("Registers"); + ImGui::BeginChild("Flags"); drawRegisters(); + ImGui::Dummy(ImVec2(0.0f, 20.0f)); + drawAnnunciators(); + ImGui::Dummy(ImVec2(0.0f, 20.0f)); + drawSwitches(); ImGui::EndChild(); ImGui::EndTabItem(); diff --git a/source/frontends/sdl/imgui/sdlsettings.h b/source/frontends/sdl/imgui/sdlsettings.h index 7e2f13279..65a54f62c 100644 --- a/source/frontends/sdl/imgui/sdlsettings.h +++ b/source/frontends/sdl/imgui/sdlsettings.h @@ -48,8 +48,10 @@ namespace sa2 void showAboutWindow(); void drawDisassemblyTable(SDLFrame * frame); - void drawRegisters(); void drawConsole(); + void drawRegisters(); + void drawAnnunciators(); + void drawSwitches(); void debuggerCommand(SDLFrame * frame, const char * s); }; diff --git a/source/frontends/sdl/imgui/settingshelper.cpp b/source/frontends/sdl/imgui/settingshelper.cpp index df42e7333..f9af8fd34 100644 --- a/source/frontends/sdl/imgui/settingshelper.cpp +++ b/source/frontends/sdl/imgui/settingshelper.cpp @@ -3,6 +3,8 @@ #include "Registry.h" #include "Harddisk.h" #include "Core.h" +#include "Memory.h" +#include "Interface.h" #include "Debugger/Debug.h" #include "Tfe/tfe.h" @@ -11,65 +13,67 @@ #include "frontends/sdl/imgui/glselector.h" #include "imgui_internal.h" +void CreateLanguageCard(void); namespace { const std::map cards = { - {CT_Empty, "CT_Empty"}, - {CT_Disk2, "CT_Disk2"}, - {CT_SSC, "CT_SSC"}, - {CT_MockingboardC, "CT_MockingboardC"}, - {CT_GenericPrinter, "CT_GenericPrinter"}, - {CT_GenericHDD, "CT_GenericHDD"}, - {CT_GenericClock, "CT_GenericClock"}, - {CT_MouseInterface, "CT_MouseInterface"}, - {CT_Z80, "CT_Z80"}, - {CT_Phasor, "CT_Phasor"}, - {CT_Echo, "CT_Echo"}, - {CT_SAM, "CT_SAM"}, - {CT_80Col, "CT_80Col"}, - {CT_Extended80Col, "CT_Extended80Col"}, - {CT_RamWorksIII, "CT_RamWorksIII"}, - {CT_Uthernet, "CT_Uthernet"}, - {CT_LanguageCard, "CT_LanguageCard"}, - {CT_LanguageCardIIe, "CT_LanguageCardIIe"}, - {CT_Saturn128K, "CT_Saturn128K"}, - {CT_FourPlay, "CT_FourPlay"}, - {CT_SNESMAX, "CT_SNESMAX"}, - {CT_Uthernet2, "CT_Uthernet2"}, + {CT_Empty, "Empty"}, + {CT_Disk2, "Disk2"}, + {CT_SSC, "SSC"}, + {CT_MockingboardC, "MockingboardC"}, + {CT_GenericPrinter, "GenericPrinter"}, + {CT_GenericHDD, "GenericHDD"}, + {CT_GenericClock, "GenericClock"}, + {CT_MouseInterface, "MouseInterface"}, + {CT_Z80, "Z80"}, + {CT_Phasor, "Phasor"}, + {CT_Echo, "Echo"}, + {CT_SAM, "SAM"}, + {CT_80Col, "80Col"}, + {CT_Extended80Col, "Extended80Col"}, + {CT_RamWorksIII, "RamWorksIII"}, + {CT_Uthernet, "Uthernet"}, + {CT_LanguageCard, "LanguageCard"}, + {CT_LanguageCardIIe, "LanguageCardIIe"}, + {CT_Saturn128K, "Saturn128K"}, + {CT_FourPlay, "FourPlay"}, + {CT_SNESMAX, "SNESMAX"}, + {CT_VidHD, "VidHD"}, + {CT_Uthernet2, "Uthernet2"}, }; const std::map apple2Types = { - {A2TYPE_APPLE2, "A2TYPE_APPLE2"}, - {A2TYPE_APPLE2PLUS, "A2TYPE_APPLE2PLUS"}, - {A2TYPE_APPLE2JPLUS, "A2TYPE_APPLE2JPLUS"}, - {A2TYPE_APPLE2E, "A2TYPE_APPLE2E"}, - {A2TYPE_APPLE2EENHANCED, "A2TYPE_APPLE2EENHANCED"}, - {A2TYPE_APPLE2C, "A2TYPE_APPLE2C"}, - {A2TYPE_PRAVETS8M, "A2TYPE_PRAVETS8M"}, - {A2TYPE_PRAVETS82, "A2TYPE_PRAVETS82"}, - {A2TYPE_BASE64A, "A2TYPE_BASE64A"}, - {A2TYPE_PRAVETS8A, "A2TYPE_PRAVETS8A"}, - {A2TYPE_TK30002E, "A2TYPE_TK30002E"}, + {A2TYPE_APPLE2, "APPLE2"}, + {A2TYPE_APPLE2PLUS, "APPLE2PLUS"}, + {A2TYPE_APPLE2JPLUS, "APPLE2JPLUS"}, + {A2TYPE_APPLE2E, "APPLE2E"}, + {A2TYPE_APPLE2EENHANCED, "APPLE2EENHANCED"}, + {A2TYPE_APPLE2C, "APPLE2C"}, + {A2TYPE_PRAVETS8M, "PRAVETS8M"}, + {A2TYPE_PRAVETS82, "PRAVETS82"}, + {A2TYPE_BASE64A, "BASE64A"}, + {A2TYPE_PRAVETS8A, "PRAVETS8A"}, + {A2TYPE_TK30002E, "TK30002E"}, }; const std::map cpuTypes = { - {CPU_6502, "CPU_6502"}, - {CPU_65C02, "CPU_65C02"}, - {CPU_Z80, "CPU_Z80"}, + {CPU_6502, "6502"}, + {CPU_65C02, "65C02"}, + {CPU_Z80, "Z80"}, }; const std::map appModes = { - {MODE_LOGO, "MODE_LOGO"}, - {MODE_PAUSED, "MODE_PAUSED"}, - {MODE_RUNNING, "MODE_RUNNING"}, - {MODE_DEBUG, "MODE_DEBUG"}, - {MODE_STEPPING, "MODE_STEPPING"}, - {MODE_BENCHMARK, "MODE_BENCHMARCK"}, + {MODE_LOGO, "LOGO"}, + {MODE_PAUSED, "PAUSED"}, + {MODE_RUNNING, "RUNNING"}, + {MODE_DEBUG, "DEBUG"}, + {MODE_STEPPING, "STEPPING"}, + {MODE_BENCHMARK, "BENCHMARCK"}, }; const std::map statuses = @@ -98,11 +102,11 @@ namespace {0, {CT_Empty, CT_LanguageCard, CT_Saturn128K}}, {1, {CT_Empty, CT_GenericPrinter, CT_Uthernet2}}, {2, {CT_Empty, CT_SSC, CT_Uthernet2}}, - {3, {CT_Empty, CT_Uthernet, CT_Uthernet2}}, + {3, {CT_Empty, CT_Uthernet, CT_Uthernet2, CT_VidHD}}, {4, {CT_Empty, CT_MockingboardC, CT_MouseInterface, CT_Phasor, CT_Uthernet2}}, {5, {CT_Empty, CT_MockingboardC, CT_Z80, CT_SAM, CT_Disk2, CT_FourPlay, CT_SNESMAX, CT_Uthernet2}}, - {6, {CT_Empty, CT_Disk2}}, - {7, {CT_Empty, CT_GenericHDD}}, + {6, {CT_Empty, CT_Disk2, CT_Uthernet2}}, + {7, {CT_Empty, CT_GenericHDD, CT_Uthernet2}}, }; const std::vector expansionCards = @@ -158,11 +162,23 @@ namespace sa2 return statuses.at(status); } - void insertCard(size_t slot, SS_CARDTYPE card) + void insertCard(size_t slot, SS_CARDTYPE card, FrameBase * frame) { CardManager & cardManager = GetCardMgr(); + Video & video = GetVideo(); + const bool oldHasVid = video.HasVidHD(); switch (slot) { + case 3: + { + if (cardManager.QuerySlot(slot) == CT_VidHD) + { + // the old card was a VidHD, which will be removed + // reset it + video.SetVidHD(false); + } + break; + } case 4: case 5: { @@ -172,7 +188,6 @@ namespace sa2 } else { - CardManager & cardManager = GetCardMgr(); if (cardManager.QuerySlot(slot) == CT_MockingboardC) { cardManager.Insert(9 - slot, CT_Empty); // the other @@ -182,19 +197,24 @@ namespace sa2 } }; - if (card == CT_Uthernet2) + cardManager.Insert(slot, card); + + // keep everything consistent + // a bit of a heavy call, but nothing simpler is available now + MemInitializeIO(); + + if (oldHasVid != video.HasVidHD()) { - // only 1 Uthernet2 allowed - for (size_t s = SLOT1; s < NUM_SLOTS; ++s) - { - if (cardManager.QuerySlot(s) == card) - { - cardManager.Insert(s, CT_Empty); - } - } + frame->Destroy(); + frame->Initialize(true); } + } - cardManager.Insert(slot, card); + void setExpansionCard(SS_CARDTYPE card) + { + SetExpansionMemType(card); + CreateLanguageCard(); + MemInitializeIO(); } void setVideoStyle(Video & video, const VideoStyle_e style, const bool enabled) diff --git a/source/frontends/sdl/imgui/settingshelper.h b/source/frontends/sdl/imgui/settingshelper.h index 694d5c393..646d4a4f6 100644 --- a/source/frontends/sdl/imgui/settingshelper.h +++ b/source/frontends/sdl/imgui/settingshelper.h @@ -10,6 +10,8 @@ #include #include +class FrameBase; + namespace sa2 { @@ -24,8 +26,8 @@ namespace sa2 const std::vector & getExpansionCards(); const std::map & getAapple2Types(); - void insertCard(size_t slot, SS_CARDTYPE card); - + void insertCard(size_t slot, SS_CARDTYPE card, FrameBase * frame); + void setExpansionCard(SS_CARDTYPE card); void setVideoStyle(Video & video, const VideoStyle_e style, const bool enabled); void saveTFEEnabled(const int enabled); diff --git a/source/frontends/sdl/main.cpp b/source/frontends/sdl/main.cpp index 516190842..0b2bde3cb 100644 --- a/source/frontends/sdl/main.cpp +++ b/source/frontends/sdl/main.cpp @@ -7,7 +7,6 @@ #include "StdAfx.h" #include "linux/benchmark.h" #include "linux/context.h" -#include "linux/network/uthernet2.h" #include "frontends/common2/fileregistry.h" #include "frontends/common2/utils.h" @@ -94,8 +93,14 @@ void run_sdl(int argc, const char * argv []) std::shared_ptr paddle(new sa2::Gamepad(0)); const Initialisation init(frame, paddle); - applyOptions(options); - frame->Initialize(); + common2::applyOptions(options); + frame->Begin(); + + common2::setSnapshotFilename(options.snapshotFilename); + if (options.loadSnapshot) + { + frame->LoadSnapshot(); + } std::cerr << "Default GL swap interval: " << SDL_GL_GetSwapInterval() << std::endl; @@ -143,7 +148,6 @@ void run_sdl(int argc, const char * argv []) eventTimer.tic(); sa2::writeAudio(); - processEventsUthernet2(5); frame->ProcessEvents(quit); eventTimer.toc(); @@ -151,8 +155,9 @@ void run_sdl(int argc, const char * argv []) frame->ExecuteOneFrame(oneFrame); cpuTimer.toc(); - if (!options.headless) + if (!options.headless && !g_bFullSpeed) { + // in full speed VideoRedrawScreenDuringFullSpeed is called inside SDLFrame::Execute refreshScreenTimer.tic(); frame->VideoPresentScreen(); refreshScreenTimer.toc(); @@ -174,6 +179,7 @@ void run_sdl(int argc, const char * argv []) std::cerr << "Actual clock: " << actualClock << " Hz, " << timeInSeconds << " s" << std::endl; sa2::stopAudio(); } + frame->End(); #endif } diff --git a/source/frontends/sdl/processfile.cpp b/source/frontends/sdl/processfile.cpp index 7fafcd10a..3738e2216 100644 --- a/source/frontends/sdl/processfile.cpp +++ b/source/frontends/sdl/processfile.cpp @@ -89,8 +89,8 @@ namespace sa2 const char * wav = ".wav"; if (strlen(filename) > strlen(yaml) && !strcmp(filename + strlen(filename) - strlen(yaml), yaml)) { - common2::setSnapshotFilename(filename, true); - frame->ResetHardware(); + common2::setSnapshotFilename(filename); + frame->LoadSnapshot(); } else if (strlen(filename) > strlen(wav) && !strcmp(filename + strlen(filename) - strlen(wav), wav)) { diff --git a/source/frontends/sdl/renderer/sdlrendererframe.cpp b/source/frontends/sdl/renderer/sdlrendererframe.cpp index cc87c2d91..be0c8fb49 100644 --- a/source/frontends/sdl/renderer/sdlrendererframe.cpp +++ b/source/frontends/sdl/renderer/sdlrendererframe.cpp @@ -30,9 +30,12 @@ namespace sa2 throw std::runtime_error(SDL_GetError()); } - const Uint32 format = SDL_PIXELFORMAT_ARGB8888; - printRendererInfo(std::cerr, myRenderer, format, options.sdlDriver); + printRendererInfo(std::cerr, myRenderer, ourFormat, options.sdlDriver); + } + void SDLRendererFrame::Initialize(bool resetVideoState) + { + SDLFrame::Initialize(resetVideoState); Video & video = GetVideo(); const int width = video.GetFrameBufferWidth(); @@ -40,7 +43,7 @@ namespace sa2 const int sw = video.GetFrameBufferBorderlessWidth(); const int sh = video.GetFrameBufferBorderlessHeight(); - myTexture.reset(SDL_CreateTexture(myRenderer.get(), format, SDL_TEXTUREACCESS_STATIC, width, height), SDL_DestroyTexture); + myTexture.reset(SDL_CreateTexture(myRenderer.get(), ourFormat, SDL_TEXTUREACCESS_STATIC, width, height), SDL_DestroyTexture); myRect.x = video.GetFrameBufferBorderWidth(); myRect.y = video.GetFrameBufferBorderHeight(); diff --git a/source/frontends/sdl/renderer/sdlrendererframe.h b/source/frontends/sdl/renderer/sdlrendererframe.h index c3a41c8a1..3a5857ad4 100644 --- a/source/frontends/sdl/renderer/sdlrendererframe.h +++ b/source/frontends/sdl/renderer/sdlrendererframe.h @@ -1,6 +1,7 @@ #pragma once #include "frontends/sdl/sdlframe.h" +#include "frontends/common2/gnuframe.h" namespace common2 { @@ -10,17 +11,21 @@ namespace common2 namespace sa2 { - class SDLRendererFrame : public SDLFrame + class SDLRendererFrame : public SDLFrame, public common2::GNUFrame { public: SDLRendererFrame(const common2::EmulatorOptions & options); void VideoPresentScreen() override; + void Initialize(bool resetVideoState) override; protected: void GetRelativeMousePosition(const SDL_MouseMotionEvent & motion, double & x, double & y) const override; private: + + static constexpr Uint32 ourFormat = SDL_PIXELFORMAT_ARGB8888; + SDL_Rect myRect; int myPitch; diff --git a/source/frontends/sdl/sdirectsound.cpp b/source/frontends/sdl/sdirectsound.cpp index 64fcf9ae4..bb4f59011 100644 --- a/source/frontends/sdl/sdirectsound.cpp +++ b/source/frontends/sdl/sdirectsound.cpp @@ -1,6 +1,6 @@ #include "frontends/sdl/sdirectsound.h" -#include "linux/windows/dsound.h" +#include "windows.h" #include "linux/linuxinterface.h" #include diff --git a/source/frontends/sdl/sdlframe.cpp b/source/frontends/sdl/sdlframe.cpp index 376a81aea..0d3f0fc19 100644 --- a/source/frontends/sdl/sdlframe.cpp +++ b/source/frontends/sdl/sdlframe.cpp @@ -136,8 +136,9 @@ namespace sa2 { } - void SDLFrame::Destroy() + void SDLFrame::End() { + CommonFrame::End(); if (!myFullscreen) { common2::Geometry geometry; @@ -145,7 +146,6 @@ namespace sa2 SDL_GetWindowSize(myWindow.get(), &geometry.width, &geometry.height); saveGeometryToRegistry("sa2", geometry); } - common2::CommonFrame::Destroy(); } void SDLFrame::setGLSwapInterval(const int interval) @@ -159,9 +159,9 @@ namespace sa2 } } - void SDLFrame::Initialize() + void SDLFrame::Begin() { - CommonFrame::Initialize(); + CommonFrame::Begin(); mySpeed.reset(); setGLSwapInterval(myTargetGLSwap); ResetHardware(); @@ -178,7 +178,7 @@ namespace sa2 void SDLFrame::SetApplicationIcon() { - const std::string path = myResourcePath + "APPLEWIN.ICO"; + const std::string path = getResourcePath("APPLEWIN.ICO"); std::shared_ptr icon(IMG_Load(path.c_str()), SDL_FreeSurface); if (icon) { @@ -194,7 +194,7 @@ namespace sa2 void SDLFrame::GetBitmap(LPCSTR lpBitmapName, LONG cb, LPVOID lpvBits) { const std::string filename = getBitmapFilename(lpBitmapName); - const std::string path = myResourcePath + filename; + const std::string path = getResourcePath(filename); std::shared_ptr surface(SDL_LoadBMP(path.c_str()), SDL_FreeSurface); if (surface) @@ -399,15 +399,13 @@ namespace sa2 { case SDLK_F12: { - Snapshot_LoadState(); - mySpeed.reset(); - ResetHardware(); + LoadSnapshot(); break; } case SDLK_F11: { const std::string & pathname = Snapshot_GetPathname(); - const std::string message = "Do you want to save the state to " + pathname + "?"; + const std::string message = "Do you want to save the state to: " + pathname + "?"; SoundCore_SetFade(FADE_OUT); if (show_yes_no_dialog(myWindow, "Save state", message)) { @@ -512,6 +510,10 @@ namespace sa2 SDL_SetClipboardText(pText); } } + else if (key.keysym.mod & KMOD_ALT) + { + Video_TakeScreenShot(Video::SCREENSHOT_560x384); + } break; } case SDLK_SCROLLLOCK: @@ -589,8 +591,9 @@ namespace sa2 g_dwCyclesThisFrame -= dwClksPerFrame; if (g_bFullSpeed) { - NTSC_VideoClockResync(g_dwCyclesThisFrame); - GetVideo().VideoRefreshBuffer(GetVideo().GetVideoMode(), true); + // only call VideoPresentScreen every 16ms + // hardcoded in FrameBase::VideoRedrawScreenDuringFullSpeed() + VideoRedrawScreenDuringFullSpeed(g_dwCyclesThisFrame); } } } while (totalCyclesExecuted < cyclesToExecute); @@ -685,6 +688,7 @@ namespace sa2 // entering full speed MB_Mute(); setGLSwapInterval(0); + VideoRedrawScreenDuringFullSpeed(0, true); } else { @@ -722,6 +726,13 @@ namespace sa2 return myHardwareConfig != currentConfig; } + void SDLFrame::LoadSnapshot() + { + common2::CommonFrame::LoadSnapshot(); + mySpeed.reset(); + ResetHardware(); + } + } void SingleStep(bool /* bReinit */) diff --git a/source/frontends/sdl/sdlframe.h b/source/frontends/sdl/sdlframe.h index 7995419c1..340162fb1 100644 --- a/source/frontends/sdl/sdlframe.h +++ b/source/frontends/sdl/sdlframe.h @@ -14,13 +14,13 @@ namespace common2 namespace sa2 { - class SDLFrame : public common2::CommonFrame + class SDLFrame : public virtual common2::CommonFrame { public: SDLFrame(const common2::EmulatorOptions & options); - void Initialize() override; - void Destroy() override; + void Begin() override; + void End() override; void FrameRefreshStatus(int drawflags) override; int FrameMessageBox(LPCSTR lpText, LPCSTR lpCaption, UINT uType) override; @@ -34,6 +34,7 @@ namespace sa2 void ResetHardware(); bool HardwareChanged() const; virtual void ResetSpeed(); + void LoadSnapshot() override; const std::shared_ptr & GetWindow() const; diff --git a/source/linux/build.sh b/source/linux/build.sh index 9b7997463..a0fde73ee 100755 --- a/source/linux/build.sh +++ b/source/linux/build.sh @@ -2,10 +2,8 @@ set -euxo pipefail -wget https://raw.githubusercontent.com/libretro/RetroArch/master/libretro-common/include/libretro.h -P libretro-common/include - mkdir -p build cd build -cmake -DCMAKE_BUILD_TYPE=RELEASE -DLIBRETRO_COMMON_PATH=../libretro-common .. +cmake -DCMAKE_BUILD_TYPE=RELEASE .. make -j 2 diff --git a/source/linux/context.cpp b/source/linux/context.cpp index 24873f593..d0b6c75d8 100644 --- a/source/linux/context.cpp +++ b/source/linux/context.cpp @@ -8,6 +8,18 @@ #include "Interface.h" #include "Log.h" +#include "Utilities.h" +#include "SoundCore.h" +#include "CPU.h" +#include "ParallelPrinter.h" +#include "Riff.h" +#include "SaveState.h" +#include "Memory.h" +#include "Speaker.h" +#include "MouseInterface.h" +#include "Debugger/Debug.h" +#include "Mockingboard.h" + namespace { @@ -80,3 +92,72 @@ RegistryContext::~RegistryContext() { Registry::instance.reset(); } + +void InitialiseEmulator() +{ +#ifdef RIFF_SPKR + RiffInitWriteFile("/tmp/Spkr.wav", SPKR_SAMPLE_RATE, 1); +#endif +#ifdef RIFF_MB + RiffInitWriteFile("/tmp/Mockingboard.wav", 44100, 2); +#endif + + g_nAppMode = MODE_RUNNING; + LogFileOutput("Initialisation\n"); + + g_bFullSpeed = false; + + GetVideo().SetVidHD(false); + LoadConfiguration(true); + SetCurrentCLK6502(); + GetAppleWindowTitle(); + GetFrame().FrameRefreshStatus(DRAW_LEDS | DRAW_BUTTON_DRIVES | DRAW_DISK_STATUS); + + DSInit(); + MB_Initialize(); + SpkrInitialize(); + + MemInitialize(); + + CardManager & cardManager = GetCardMgr(); + cardManager.GetDisk2CardMgr().Reset(); + if (cardManager.QuerySlot(SLOT7) == CT_GenericHDD) + { + dynamic_cast(cardManager.GetRef(SLOT7)).Reset(true); + } + + Snapshot_Startup(); + + DebugInitialize(); +} + +void DestroyEmulator() +{ + CardManager & cardManager = GetCardMgr(); + + Snapshot_Shutdown(); + CMouseInterface* pMouseCard = cardManager.GetMouseCard(); + if (pMouseCard) + { + pMouseCard->Reset(); + } + MemDestroy(); + + SpkrDestroy(); + MB_Destroy(); + DSUninit(); + + tfe_shutdown(); + + if (cardManager.QuerySlot(SLOT7) == CT_GenericHDD) + { + dynamic_cast(cardManager.GetRef(SLOT7)).Destroy(); + } + + PrintDestroy(); + CpuDestroy(); + DebugDestroy(); + + GetCardMgr().GetDisk2CardMgr().Destroy(); + RiffFinishWriteFile(); +} diff --git a/source/linux/context.h b/source/linux/context.h index 6099d4b89..f1c713f19 100644 --- a/source/linux/context.h +++ b/source/linux/context.h @@ -8,6 +8,9 @@ class Registry; void SetFrame(const std::shared_ptr & frame); +void InitialiseEmulator(); +void DestroyEmulator(); + // RAII around Frame Registry and Paddle class Initialisation { diff --git a/source/linux/duplicates/FourPlay.cpp b/source/linux/duplicates/FourPlay.cpp index c4962ab4b..a06778d54 100644 --- a/source/linux/duplicates/FourPlay.cpp +++ b/source/linux/duplicates/FourPlay.cpp @@ -1,5 +1,6 @@ #include "StdAfx.h" #include "FourPlay.h" +#include "YamlHelper.h" void FourPlayCard::InitializeIO(LPBYTE pCxRomPeripheral) { @@ -11,18 +12,22 @@ static const UINT kUNIT_VERSION = 1; std::string FourPlayCard::GetSnapshotCardName(void) { - static const std::string name("4Play"); - return name; + static const std::string name("4Play"); + return name; } void FourPlayCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) { + YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION); + + YamlSaveHelper::Label unit(yamlSaveHelper, "%s: null\n", SS_YAML_KEY_STATE); + // NB. No state for this card } -bool FourPlayCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool FourPlayCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { - if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + if (version < 1 || version > kUNIT_VERSION) + throw std::runtime_error("Card: wrong version"); - return true; + return true; } diff --git a/source/linux/duplicates/Joystick.cpp b/source/linux/duplicates/Joystick.cpp index c18627b92..388ec8018 100644 --- a/source/linux/duplicates/Joystick.cpp +++ b/source/linux/duplicates/Joystick.cpp @@ -36,6 +36,7 @@ void JoySetButtonVirtualKey(UINT button, UINT virtKey) #define SS_YAML_KEY_JOY0TRIMY "Joystick0 TrimY" #define SS_YAML_KEY_JOY1TRIMX "Joystick1 TrimX" #define SS_YAML_KEY_JOY1TRIMY "Joystick1 TrimY" +#define SS_YAML_KEY_PDL_INACTIVE_CYCLE "Paddle%1d Inactive Cycle" static std::string JoyGetSnapshotStructName(void) { @@ -43,16 +44,29 @@ static std::string JoyGetSnapshotStructName(void) return name; } -void JoyLoadSnapshot(YamlLoadHelper& yamlLoadHelper, unsigned int) +void JoyLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { if (!yamlLoadHelper.GetSubMap(JoyGetSnapshotStructName())) return; - yamlLoadHelper.LoadUint64(SS_YAML_KEY_COUNTERRESETCYCLE); yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY0TRIMX); yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY0TRIMY); - yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY1TRIMX); // dump value - yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY1TRIMY); // dump value + yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY1TRIMX); + yamlLoadHelper.LoadInt(SS_YAML_KEY_JOY1TRIMY); + + if (version >= 7) + { + for (UINT n = 0; n < 4; n++) + { + char str[sizeof(SS_YAML_KEY_PDL_INACTIVE_CYCLE) + 1]; + sprintf_s(str, sizeof(str), SS_YAML_KEY_PDL_INACTIVE_CYCLE, n); + yamlLoadHelper.LoadUint64(str); + } + } + else + { + yamlLoadHelper.LoadUint64(SS_YAML_KEY_COUNTERRESETCYCLE); + } yamlLoadHelper.PopMap(); } diff --git a/source/linux/duplicates/SNESMAX.cpp b/source/linux/duplicates/SNESMAX.cpp index 506a872ac..99d9c9f71 100644 --- a/source/linux/duplicates/SNESMAX.cpp +++ b/source/linux/duplicates/SNESMAX.cpp @@ -1,5 +1,6 @@ #include "StdAfx.h" #include "SNESMAX.h" +#include "YamlHelper.h" void SNESMAXCard::InitializeIO(LPBYTE pCxRomPeripheral) { @@ -13,18 +14,24 @@ static const UINT kUNIT_VERSION = 1; std::string SNESMAXCard::GetSnapshotCardName(void) { - static const std::string name("SNES MAX"); - return name; + static const std::string name("SNES MAX"); + return name; } void SNESMAXCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) { + YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION); + + YamlSaveHelper::Label unit(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); + yamlSaveHelper.SaveUint(SS_YAML_KEY_BUTTON_INDEX, m_buttonIndex); } -bool SNESMAXCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool SNESMAXCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { - if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + if (version < 1 || version > kUNIT_VERSION) + throw std::runtime_error("Card: wrong version"); + + yamlLoadHelper.LoadUint(SS_YAML_KEY_BUTTON_INDEX); - return true; + return true; } diff --git a/source/linux/duplicates/SerialComms.cpp b/source/linux/duplicates/SerialComms.cpp index 3f0f770b9..a4681c917 100644 --- a/source/linux/duplicates/SerialComms.cpp +++ b/source/linux/duplicates/SerialComms.cpp @@ -57,7 +57,7 @@ static const UINT kUNIT_VERSION = 2; void CSuperSerialCard::LoadSnapshotDIPSW(YamlLoadHelper& yamlLoadHelper, std::string key, SSC_DIPSW& dipsw) { if (!yamlLoadHelper.GetSubMap(key)) - throw std::string("Card: Expected key: " + key); + throw std::runtime_error("Card: Expected key: " + key); yamlLoadHelper.LoadUint(SS_YAML_KEY_BAUDRATE); yamlLoadHelper.LoadUint(SS_YAML_KEY_FWMODE); @@ -70,13 +70,10 @@ void CSuperSerialCard::LoadSnapshotDIPSW(YamlLoadHelper& yamlLoadHelper, std::st yamlLoadHelper.PopMap(); } -bool CSuperSerialCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) +bool CSuperSerialCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { - if (slot != 2) // fixme - throw std::string("Card: wrong slot"); - if (version < 1 || version > kUNIT_VERSION) - throw std::string("Card: wrong version"); + throw std::runtime_error("Card: wrong version"); SSC_DIPSW dipsw; LoadSnapshotDIPSW(yamlLoadHelper, SS_YAML_KEY_DIPSWDEFAULT, dipsw); diff --git a/source/linux/keyboard.cpp b/source/linux/keyboard.cpp index 7309c7dc4..e7960c8a3 100644 --- a/source/linux/keyboard.cpp +++ b/source/linux/keyboard.cpp @@ -1,6 +1,6 @@ +#include "StdAfx.h" #include "linux/keyboard.h" -#include "StdAfx.h" #include "Core.h" #include "YamlHelper.h" diff --git a/source/linux/keyboard.h b/source/linux/keyboard.h index 72c01bcb7..9d17d9e55 100644 --- a/source/linux/keyboard.h +++ b/source/linux/keyboard.h @@ -1,4 +1,2 @@ -#include "linux/windows/wincompat.h" - void addKeyToBuffer(BYTE key); void addTextToBuffer(const char * text); diff --git a/source/linux/libwindows/CMakeLists.txt b/source/linux/libwindows/CMakeLists.txt new file mode 100644 index 000000000..cdd3c34b7 --- /dev/null +++ b/source/linux/libwindows/CMakeLists.txt @@ -0,0 +1,45 @@ +set(SOURCE_FILES + dmusicc.cpp + dsound.cpp + fileapi.cpp + guiddef.cpp + winhandles.cpp + misc.cpp + strsafe.cpp + winstrings.cpp + timeapi.cpp + winbase.cpp + winnls.cpp + winuser.cpp + ) + +set(HEADER_FILES + dmusicc.h + dsound.h + fileapi.h + gdi.h + guiddef.h + winhandles.h + misc.h + mmreg.h + mmsystem.h + strsafe.h + winstrings.h + timeapi.h + wingdi.h + winbase.h + wincompat.h + windows.h + winerror.h + winnls.h + winuser.h + ) + +add_library(windows STATIC + ${SOURCE_FILES} + ${HEADER_FILES} + ) + +target_include_directories(windows PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ) diff --git a/source/linux/windows/dmusicc.cpp b/source/linux/libwindows/dmusicc.cpp similarity index 81% rename from source/linux/windows/dmusicc.cpp rename to source/linux/libwindows/dmusicc.cpp index f7fe9dc4d..91b8eb5cc 100644 --- a/source/linux/windows/dmusicc.cpp +++ b/source/linux/libwindows/dmusicc.cpp @@ -1,5 +1,5 @@ -#include "linux/windows/dmusicc.h" -#include "linux/windows/winerror.h" +#include "dmusicc.h" +#include "winerror.h" HRESULT IReferenceClock::GetTime(REFERENCE_TIME *pTime) { diff --git a/source/linux/windows/dmusicc.h b/source/linux/libwindows/dmusicc.h similarity index 74% rename from source/linux/windows/dmusicc.h rename to source/linux/libwindows/dmusicc.h index b7f3da0dc..00a46d4ed 100644 --- a/source/linux/windows/dmusicc.h +++ b/source/linux/libwindows/dmusicc.h @@ -1,8 +1,8 @@ #pragma once -#include "linux/windows/winbase.h" -#include "linux/windows/handles.h" -#include "linux/windows/guiddef.h" +#include "winbase.h" +#include "winhandles.h" +#include "guiddef.h" typedef LONGLONG REFERENCE_TIME; struct IReferenceClock : public IUnknown diff --git a/source/linux/windows/dsound.cpp b/source/linux/libwindows/dsound.cpp similarity index 98% rename from source/linux/windows/dsound.cpp rename to source/linux/libwindows/dsound.cpp index 17980a361..44fea2693 100644 --- a/source/linux/windows/dsound.cpp +++ b/source/linux/libwindows/dsound.cpp @@ -1,5 +1,4 @@ -#include "linux/windows/dsound.h" -#include "linux/windows/winerror.h" +#include "windows.h" #include "linux/linuxinterface.h" #include diff --git a/source/linux/windows/dsound.h b/source/linux/libwindows/dsound.h similarity index 96% rename from source/linux/windows/dsound.h rename to source/linux/libwindows/dsound.h index ef3e89747..c5206d3fb 100644 --- a/source/linux/windows/dsound.h +++ b/source/linux/libwindows/dsound.h @@ -1,9 +1,9 @@ #pragma once -#include "linux/windows/winbase.h" -#include "linux/windows/winerror.h" -#include "linux/windows/mmreg.h" -#include "linux/windows/guiddef.h" +#include "winbase.h" +#include "winerror.h" +#include "mmreg.h" +#include "guiddef.h" #include #include diff --git a/source/linux/windows/files.cpp b/source/linux/libwindows/fileapi.cpp similarity index 99% rename from source/linux/windows/files.cpp rename to source/linux/libwindows/fileapi.cpp index 7e9c45326..e775eded5 100644 --- a/source/linux/windows/files.cpp +++ b/source/linux/libwindows/fileapi.cpp @@ -1,4 +1,4 @@ -#include "linux/windows/files.h" +#include "fileapi.h" #include #include diff --git a/source/linux/windows/files.h b/source/linux/libwindows/fileapi.h similarity index 96% rename from source/linux/windows/files.h rename to source/linux/libwindows/fileapi.h index 1725f42a5..e34a37010 100644 --- a/source/linux/windows/files.h +++ b/source/linux/libwindows/fileapi.h @@ -1,7 +1,7 @@ #pragma once -#include "linux/windows/wincompat.h" -#include "linux/windows/handles.h" +#include "wincompat.h" +#include "winhandles.h" #define INVALID_FILE_ATTRIBUTES (~0u) #define INVALID_SET_FILE_POINTER (~0u) diff --git a/source/linux/windows/gdi.h b/source/linux/libwindows/gdi.h similarity index 100% rename from source/linux/windows/gdi.h rename to source/linux/libwindows/gdi.h diff --git a/source/linux/windows/guiddef.cpp b/source/linux/libwindows/guiddef.cpp similarity index 81% rename from source/linux/windows/guiddef.cpp rename to source/linux/libwindows/guiddef.cpp index bdd9e0b7e..4cc051128 100644 --- a/source/linux/windows/guiddef.cpp +++ b/source/linux/libwindows/guiddef.cpp @@ -1,5 +1,5 @@ -#include "linux/windows/guiddef.h" -#include "linux/windows/winerror.h" +#include "guiddef.h" +#include "winerror.h" IUnknown::~IUnknown() { diff --git a/source/linux/windows/guiddef.h b/source/linux/libwindows/guiddef.h similarity index 92% rename from source/linux/windows/guiddef.h rename to source/linux/libwindows/guiddef.h index 7385bccbc..d831eb461 100644 --- a/source/linux/windows/guiddef.h +++ b/source/linux/libwindows/guiddef.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" typedef int GUID; typedef GUID *LPGUID; diff --git a/source/linux/windows/misc.cpp b/source/linux/libwindows/misc.cpp similarity index 74% rename from source/linux/windows/misc.cpp rename to source/linux/libwindows/misc.cpp index ca07a3462..8d9336eda 100644 --- a/source/linux/windows/misc.cpp +++ b/source/linux/libwindows/misc.cpp @@ -1,4 +1,4 @@ -#include "linux/windows/misc.h" +#include "misc.h" BOOL WINAPI PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { diff --git a/source/linux/windows/misc.h b/source/linux/libwindows/misc.h similarity index 93% rename from source/linux/windows/misc.h rename to source/linux/libwindows/misc.h index 39e9e0261..ded5963b5 100644 --- a/source/linux/windows/misc.h +++ b/source/linux/libwindows/misc.h @@ -1,7 +1,7 @@ #pragma once -#include "linux/windows/wincompat.h" -#include "linux/windows/handles.h" +#include "wincompat.h" +#include "winhandles.h" #define CBR_9600 9600 diff --git a/source/linux/windows/mmreg.h b/source/linux/libwindows/mmreg.h similarity index 87% rename from source/linux/windows/mmreg.h rename to source/linux/libwindows/mmreg.h index 987027acd..fa0540d28 100644 --- a/source/linux/windows/mmreg.h +++ b/source/linux/libwindows/mmreg.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" typedef struct _WAVEFORMATEX { WORD wFormatTag; diff --git a/source/linux/windows/mmsystem.h b/source/linux/libwindows/mmsystem.h similarity index 100% rename from source/linux/windows/mmsystem.h rename to source/linux/libwindows/mmsystem.h diff --git a/source/linux/windows/stringcb.cpp b/source/linux/libwindows/strsafe.cpp similarity index 91% rename from source/linux/windows/stringcb.cpp rename to source/linux/libwindows/strsafe.cpp index 201661636..00a0f05ea 100644 --- a/source/linux/windows/stringcb.cpp +++ b/source/linux/libwindows/strsafe.cpp @@ -1,4 +1,4 @@ -#include "linux/windows/stringcb.h" +#include "strsafe.h" #include HRESULT StringCbCopy(char * pszDest, const size_t cbDest, const char * pszSrc) diff --git a/source/linux/windows/stringcb.h b/source/linux/libwindows/strsafe.h similarity index 85% rename from source/linux/windows/stringcb.h rename to source/linux/libwindows/strsafe.h index ac316cfd9..28bfbd172 100644 --- a/source/linux/windows/stringcb.h +++ b/source/linux/libwindows/strsafe.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" #include HRESULT StringCbCopy(char * pszDest, const size_t cbDest, const char * pszSrc); diff --git a/source/linux/windows/time.cpp b/source/linux/libwindows/timeapi.cpp similarity index 98% rename from source/linux/windows/time.cpp rename to source/linux/libwindows/timeapi.cpp index acadbf598..051eb8028 100644 --- a/source/linux/windows/time.cpp +++ b/source/linux/libwindows/timeapi.cpp @@ -1,4 +1,4 @@ -#include "linux/windows/time.h" +#include "timeapi.h" #include #include diff --git a/source/linux/windows/time.h b/source/linux/libwindows/timeapi.h similarity index 94% rename from source/linux/windows/time.h rename to source/linux/libwindows/timeapi.h index a6fe724d2..a2ee00ac1 100644 --- a/source/linux/windows/time.h +++ b/source/linux/libwindows/timeapi.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" #include diff --git a/source/linux/windows/winbase.cpp b/source/linux/libwindows/winbase.cpp similarity index 92% rename from source/linux/windows/winbase.cpp rename to source/linux/libwindows/winbase.cpp index 8ac090c9d..fd1883d4a 100644 --- a/source/linux/windows/winbase.cpp +++ b/source/linux/libwindows/winbase.cpp @@ -1,10 +1,11 @@ -#include "linux/windows/winbase.h" +#include "winbase.h" #include #include #include #include #include +#include DWORD WINAPI GetLastError(void) @@ -42,7 +43,10 @@ void OutputDebugString(const char * str) void ExitProcess(int status) { - throw status; + std::ostringstream buffer("ExitProcess: "); + buffer << status; + + throw std::runtime_error(buffer.str()); } DWORD WINAPI WaitForMultipleObjects(DWORD,const HANDLE*,BOOL,DWORD) diff --git a/source/linux/windows/winbase.h b/source/linux/libwindows/winbase.h similarity index 96% rename from source/linux/windows/winbase.h rename to source/linux/libwindows/winbase.h index d98102efd..5103d9278 100644 --- a/source/linux/windows/winbase.h +++ b/source/linux/libwindows/winbase.h @@ -1,7 +1,7 @@ #pragma once -#include "linux/windows/wincompat.h" -#include "linux/windows/handles.h" +#include "wincompat.h" +#include "winhandles.h" #include diff --git a/source/linux/windows/wincompat.h b/source/linux/libwindows/wincompat.h similarity index 100% rename from source/linux/windows/wincompat.h rename to source/linux/libwindows/wincompat.h diff --git a/source/linux/libwindows/windows.h b/source/linux/libwindows/windows.h new file mode 100644 index 000000000..2f8f24b88 --- /dev/null +++ b/source/linux/libwindows/windows.h @@ -0,0 +1,20 @@ +#pragma once + +#include "wincompat.h" +#include "guiddef.h" +#include "winhandles.h" +#include "wingdi.h" +#include "fileapi.h" +#include "timeapi.h" +#include "misc.h" +#include "strsafe.h" +#include "winstrings.h" +#include "gdi.h" +#include "winbase.h" +#include "winuser.h" +#include "dsound.h" +#include "winerror.h" +#include "mmreg.h" +#include "mmsystem.h" +#include "dmusicc.h" +#include "winnls.h" diff --git a/source/linux/windows/winerror.h b/source/linux/libwindows/winerror.h similarity index 95% rename from source/linux/windows/winerror.h rename to source/linux/libwindows/winerror.h index a4934b08b..c4a73cb50 100644 --- a/source/linux/windows/winerror.h +++ b/source/linux/libwindows/winerror.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" #define FAILED(stat) ((HRESULT)(stat)<0) #define E_NOINTERFACE HRESULT(0x80004002) diff --git a/source/linux/windows/bitmap.h b/source/linux/libwindows/wingdi.h similarity index 86% rename from source/linux/windows/bitmap.h rename to source/linux/libwindows/wingdi.h index b8d53dad2..71fcc2579 100644 --- a/source/linux/windows/bitmap.h +++ b/source/linux/libwindows/wingdi.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" typedef struct tagRGBQUAD { BYTE rgbBlue; diff --git a/source/linux/windows/handles.cpp b/source/linux/libwindows/winhandles.cpp similarity index 82% rename from source/linux/windows/handles.cpp rename to source/linux/libwindows/winhandles.cpp index f17b594b7..2b99f21fe 100644 --- a/source/linux/windows/handles.cpp +++ b/source/linux/libwindows/winhandles.cpp @@ -1,4 +1,4 @@ -#include "linux/windows/handles.h" +#include "winhandles.h" BOOL CloseHandle(HANDLE hObject) { diff --git a/source/linux/windows/handles.h b/source/linux/libwindows/winhandles.h similarity index 93% rename from source/linux/windows/handles.h rename to source/linux/libwindows/winhandles.h index f3faec5d3..0341b77f2 100644 --- a/source/linux/windows/handles.h +++ b/source/linux/libwindows/winhandles.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" struct CHANDLE { diff --git a/source/linux/windows/winnls.cpp b/source/linux/libwindows/winnls.cpp similarity index 96% rename from source/linux/windows/winnls.cpp rename to source/linux/libwindows/winnls.cpp index 266e6cb69..7282a1af2 100644 --- a/source/linux/windows/winnls.cpp +++ b/source/linux/libwindows/winnls.cpp @@ -1,4 +1,4 @@ -#include "linux/windows/winnls.h" +#include "winnls.h" #include #include diff --git a/source/linux/windows/winnls.h b/source/linux/libwindows/winnls.h similarity index 87% rename from source/linux/windows/winnls.h rename to source/linux/libwindows/winnls.h index acc904721..0ead82f1b 100644 --- a/source/linux/windows/winnls.h +++ b/source/linux/libwindows/winnls.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" #define CP_ACP 0 #define CP_UTF8 65001 diff --git a/source/linux/windows/strings.cpp b/source/linux/libwindows/winstrings.cpp similarity index 95% rename from source/linux/windows/strings.cpp rename to source/linux/libwindows/winstrings.cpp index f3fcfc70f..e50dfacb6 100644 --- a/source/linux/windows/strings.cpp +++ b/source/linux/libwindows/winstrings.cpp @@ -1,4 +1,4 @@ -#include "linux/windows/strings.h" +#include "winstrings.h" #include #include diff --git a/source/linux/windows/strings.h b/source/linux/libwindows/winstrings.h similarity index 96% rename from source/linux/windows/strings.h rename to source/linux/libwindows/winstrings.h index 798870264..a38b3a521 100644 --- a/source/linux/windows/strings.h +++ b/source/linux/libwindows/winstrings.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" #include #include diff --git a/source/linux/windows/winuser.cpp b/source/linux/libwindows/winuser.cpp similarity index 72% rename from source/linux/windows/winuser.cpp rename to source/linux/libwindows/winuser.cpp index 07bec439e..ac6dd08cf 100644 --- a/source/linux/windows/winuser.cpp +++ b/source/linux/libwindows/winuser.cpp @@ -1,4 +1,6 @@ -#include "linux/windows/winuser.h" +#include "winuser.h" + +#include HCURSOR LoadCursor(HINSTANCE hInstance, LPCSTR lpCursorName) { @@ -12,7 +14,7 @@ HCURSOR SetCursor(HCURSOR hCursor) UINT_PTR SetTimer(HWND,UINT_PTR,UINT,TIMERPROC) { - return NULL; + return 0; } BOOL KillTimer(HWND hWnd, UINT uIDEvent) @@ -32,5 +34,8 @@ LRESULT WINAPI SendMessage(HWND,UINT,WPARAM,LPARAM) void WINAPI PostQuitMessage(INT status) { - throw status; + std::ostringstream buffer("PostQuitMessage: "); + buffer << status; + + throw std::runtime_error(buffer.str()); } diff --git a/source/linux/windows/winuser.h b/source/linux/libwindows/winuser.h similarity index 97% rename from source/linux/windows/winuser.h rename to source/linux/libwindows/winuser.h index 8f3df0f55..d6fb914f4 100644 --- a/source/linux/windows/winuser.h +++ b/source/linux/libwindows/winuser.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/handles.h" +#include "winhandles.h" typedef void * HCURSOR; diff --git a/source/linux/linuxframe.cpp b/source/linux/linuxframe.cpp index cdbc9cf7b..ad238efbe 100644 --- a/source/linux/linuxframe.cpp +++ b/source/linux/linuxframe.cpp @@ -1,5 +1,6 @@ #include "StdAfx.h" #include "linux/linuxframe.h" +#include "linux/context.h" #include "Interface.h" #include "Log.h" #include "Core.h" @@ -28,7 +29,7 @@ void LinuxFrame::SetFullScreenShowSubunitStatus(bool /* bShow */) { } -bool LinuxFrame::GetBestDisplayResolutionForFullScreen(UINT& /* bestWidth */, UINT& /* bestHeight */ , UINT /* userSpecifiedHeight */) +bool LinuxFrame::GetBestDisplayResolutionForFullScreen(UINT& /* bestWidth */, UINT& /* bestHeight */, UINT /* userSpecifiedWidth */, UINT /* userSpecifiedHeight */) { return false; } @@ -46,15 +47,21 @@ void LinuxFrame::SetLoadedSaveStateFlag(const bool /* bFlag */) { } -void LinuxFrame::Initialize() +void LinuxFrame::ResizeWindow() +{ +} + +void LinuxFrame::Initialize(bool resetVideoState) { - static_assert(sizeof(bgra_t) == 4, "Invalid size of bgra_t"); Video & video = GetVideo(); const size_t numberOfPixels = video.GetFrameBufferWidth() * video.GetFrameBufferHeight(); + + static_assert(sizeof(bgra_t) == 4, "Invalid size of bgra_t"); const size_t numberOfBytes = sizeof(bgra_t) * numberOfPixels; + myFramebuffer.resize(numberOfBytes); - video.Initialize(myFramebuffer.data()); + video.Initialize(myFramebuffer.data(), resetVideoState); LogFileTimeUntilFirstKeyReadReset(); } @@ -97,17 +104,30 @@ void LinuxFrame::Cycle50ScanLines() ApplyVideoModeChange(); } -void LinuxFrame::Restart() -{ - LogFileOutput("Restart: not implemented\n"); -} - void LinuxFrame::GetBitmap(LPCSTR lpBitmapName, LONG cb, LPVOID lpvBits) { LogFileOutput("LoadBitmap: could not load resource %s\n", lpBitmapName); memset(lpvBits, 0, cb); } +void LinuxFrame::Begin() +{ + InitialiseEmulator(); + Initialize(true); +} + +void LinuxFrame::End() +{ + Destroy(); + DestroyEmulator(); +} + +void LinuxFrame::Restart() +{ + End(); + Begin(); +} + int MessageBox(HWND, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { return GetFrame().FrameMessageBox(lpText, lpCaption, uType); diff --git a/source/linux/linuxframe.h b/source/linux/linuxframe.h index eb1650cf4..97cea1e55 100644 --- a/source/linux/linuxframe.h +++ b/source/linux/linuxframe.h @@ -7,7 +7,7 @@ class LinuxFrame : public FrameBase { public: - void Initialize() override; + void Initialize(bool resetVideoState) override; void Destroy() override; void FrameDrawDiskLEDS() override; @@ -15,15 +15,16 @@ class LinuxFrame : public FrameBase void FrameRefreshStatus(int drawflags) override; void FrameUpdateApple2Type() override; void FrameSetCursorPosByMousePos() override; + void ResizeWindow() override; void SetFullScreenShowSubunitStatus(bool bShow) override; - bool GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& bestHeight, UINT userSpecifiedHeight = 0) override; + bool GetBestDisplayResolutionForFullScreen(UINT& bestWidth, UINT& bestHeight, UINT userSpecifiedWidth = 0, UINT userSpecifiedHeight = 0) override; int SetViewportScale(int nNewScale, bool bForce = false) override; void SetAltEnterToggleFullScreen(bool mode) override; void SetLoadedSaveStateFlag(const bool bFlag) override; - void Restart() override; + void Restart() override; // calls End() - Begin() void GetBitmap(LPCSTR lpBitmapName, LONG cb, LPVOID lpvBits) override; void CycleVideoType(); @@ -31,6 +32,12 @@ class LinuxFrame : public FrameBase void ApplyVideoModeChange(); + // these are wrappers around Initialize / Destroy that take care of initialising the emulator components + // FrameBase::Initialize and ::Destroy only deal with the video part of the Frame, not the emulator + // in AppleWin this happens in AppleWin.cpp, but it is useful to share it + virtual void Begin(); + virtual void End(); + protected: std::vector myFramebuffer; }; diff --git a/source/linux/linuxinterface.h b/source/linux/linuxinterface.h index 69714a095..673c7b28a 100644 --- a/source/linux/linuxinterface.h +++ b/source/linux/linuxinterface.h @@ -1,7 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" -#include "linux/windows/dsound.h" +class IDirectSoundBuffer; // Sound void registerSoundBuffer(IDirectSoundBuffer * buffer); diff --git a/source/linux/network/uthernet2.cpp b/source/linux/network/uthernet2.cpp index d768b9b6c..a71273ac7 100644 --- a/source/linux/network/uthernet2.cpp +++ b/source/linux/network/uthernet2.cpp @@ -1,5 +1,7 @@ #include #include "linux/network/uthernet2.h" +#include "linux/network/slirp2.h" +#include "linux/network/registers.h" #include #include @@ -23,11 +25,6 @@ // #define U2_LOG_STATE // #define U2_LOG_UNKNOWN -#include "linux/network/uthernet2.h" -#include "linux/network/registers.h" - -#include "linux/network/slirp2.h" - #ifndef U2_USE_SLIRP #include "linux/network/tfe2.h" #include "Tfe/tfe.h" @@ -39,86 +36,6 @@ namespace { - struct Socket - { - uint16_t transmitBase; - uint16_t transmitSize; - uint16_t receiveBase; - uint16_t receiveSize; - uint16_t registers; - - uint16_t sn_rx_wr; - uint16_t sn_rx_rsr; - - uint8_t sn_sr = SN_SR_CLOSED; - - int myFD = -1; - int myErrno = 0; - - void clearFD(); - void setFD(const int fd, const int status); - void process(); - - ~Socket(); - }; - - void Socket::clearFD() - { - if (myFD != -1) - { - close(myFD); - } - myFD = -1; - sn_sr = SN_SR_CLOSED; - } - - void Socket::setFD(const int fd, const int status) - { - clearFD(); - myFD = fd; - myErrno = 0; - sn_sr = status; - } - - Socket::~Socket() - { - clearFD(); - } - - void Socket::process() - { - if (myFD != -1 && sn_sr == SN_SR_SOCK_INIT && myErrno == EINPROGRESS) - { - pollfd pfd = {.fd = myFD, .events = POLLOUT}; - if (poll(&pfd, 1, 0) > 0) - { - int err = 0; - socklen_t elen = sizeof err; - getsockopt(myFD, SOL_SOCKET, SO_ERROR, &err, &elen); - - if (err == 0) - { - myErrno = 0; - sn_sr = SN_SR_ESTABLISHED; -#ifdef U2_LOG_STATE - LogFileOutput("U2: TCP[]: CONNECTED\n"); -#endif - } - } - } - } - - std::vector memory; - std::vector sockets; - uint8_t modeRegister = 0; - uint16_t dataAddress = 0; - -#ifdef U2_USE_SLIRP - std::shared_ptr slirp; -#endif - - void initialise(); - uint16_t readNetworkWord(const uint8_t * address) { const uint16_t network = *reinterpret_cast(address); @@ -126,32 +43,13 @@ namespace return host; } - bool isThereRoomFor(const size_t i, const size_t len, const size_t header) - { - const Socket & socket = sockets[i]; - const uint16_t rsr = socket.sn_rx_rsr; // already present - const uint16_t size = socket.receiveSize; // total size - - return rsr + len + header < size; // "not =": we do not want to fill the buffer. - } - - uint16_t getFreeRoom(const size_t i) - { - const Socket & socket = sockets[i]; - const uint16_t rsr = socket.sn_rx_rsr; // already present - const uint16_t size = socket.receiveSize; // total size - - return size - rsr; - } - uint8_t getIByte(const uint16_t value, const size_t shift) { return (value >> shift) & 0xFF; } - void write8(const size_t i, const uint8_t value) + void write8(Socket & socket, std::vector & memory, const uint8_t value) { - Socket & socket = sockets[i]; const uint16_t base = socket.receiveBase; const uint16_t address = base + socket.sn_rx_wr; memory[address] = value; @@ -160,903 +58,997 @@ namespace } // reverses the byte order - void write16(const size_t i, const uint16_t value) + void write16(Socket & socket, std::vector & memory, const uint16_t value) { - write8(i, getIByte(value, 8)); // high - write8(i, getIByte(value, 0)); // low + write8(socket, memory, getIByte(value, 8)); // high + write8(socket, memory, getIByte(value, 0)); // low } - void writeData(const size_t i, const uint8_t * data, const size_t len) + void writeData(Socket & socket, std::vector & memory, const uint8_t * data, const size_t len) { for (size_t c = 0; c < len; ++c) { - write8(i, data[c]); + write8(socket, memory, data[c]); } } // no byte reversal template - void writeAny(const size_t i, const T & t) + void writeAny(Socket & socket, std::vector & memory, const T & t) { const uint8_t * data = reinterpret_cast(&t); const uint16_t len = sizeof(T); - writeData(i, data, len); + writeData(socket, memory, data, len); } - void writeDataMacRaw(const size_t i, const uint8_t * data, const size_t len) + void writeDataMacRaw(Socket & socket, std::vector & memory, const uint8_t * data, const size_t len) { // size includes sizeof(size) const uint16_t size = len + sizeof(uint16_t); - write16(i, size); - writeData(i, data, len); + write16(socket, memory, size); + writeData(socket, memory, data, len); } - void writeDataForProtocol(const size_t i, const uint8_t * data, const size_t len, const sockaddr_in & source) + void writeDataForProtocol(Socket & socket, std::vector & memory, const uint8_t * data, const size_t len, const sockaddr_in & source) { - const Socket & socket = sockets[i]; - if (socket.sn_sr == SN_SR_SOCK_UDP) { // these are already in network order - writeAny(i, source.sin_addr); - writeAny(i, source.sin_port); + writeAny(socket, memory, source.sin_addr); + writeAny(socket, memory, source.sin_port); // size does not include sizeof(size) - write16(i, len); + write16(socket, memory, len); } // no header for TCP - writeData(i, data, len); + writeData(socket, memory, data, len); } - void setSocketModeRegister(const size_t i, const uint16_t address, const uint8_t value) +} + +Socket::Socket() + : sn_sr(SN_SR_CLOSED) + , myFD(-1) + , myErrno(0) +{ + +} + +void Socket::clearFD() +{ + if (myFD != -1) { - memory[address] = value; - const uint8_t protocol = value & SN_MR_PROTO_MASK; - switch (protocol) + close(myFD); + } + myFD = -1; + sn_sr = SN_SR_CLOSED; +} + +void Socket::setFD(const int fd, const int status) +{ + clearFD(); + myFD = fd; + myErrno = 0; + sn_sr = status; +} + +Socket::~Socket() +{ + clearFD(); +} + +void Socket::process() +{ + if (myFD != -1 && sn_sr == SN_SR_SOCK_INIT && myErrno == EINPROGRESS) + { + pollfd pfd = {.fd = myFD, .events = POLLOUT}; + if (poll(&pfd, 1, 0) > 0) { - case SN_MR_CLOSED: + int err = 0; + socklen_t elen = sizeof err; + getsockopt(myFD, SOL_SOCKET, SO_ERROR, &err, &elen); + + if (err == 0) + { + myErrno = 0; + sn_sr = SN_SR_ESTABLISHED; #ifdef U2_LOG_STATE - LogFileOutput("U2: Mode[%d]: CLOSED\n", i); + LogFileOutput("U2: TCP[]: CONNECTED\n"); #endif - break; - case SN_MR_TCP: + } + } + } +} + +bool Socket::isThereRoomFor(const size_t len, const size_t header) const +{ + const uint16_t rsr = sn_rx_rsr; // already present + const uint16_t size = receiveSize; // total size + +return rsr + len + header < size; // "not =": we do not want to fill the buffer. +} + +uint16_t Socket::getFreeRoom() const +{ + const uint16_t rsr = sn_rx_rsr; // already present + const uint16_t size = receiveSize; // total size + + return size - rsr; +} + +Uthernet2::Uthernet2(UINT slot) : Card(CT_Uthernet2, slot) +{ + #ifdef U2_USE_SLIRP + mySlirp = std::make_shared(); +#else + const int check = tfe_enabled; + tfe_init(true); + if (tfe_enabled != check) + { + // tfe initialisation failed + return; + } +#endif + Reset(true); +} + +void Uthernet2::setSocketModeRegister(const size_t i, const uint16_t address, const uint8_t value) +{ + myMemory[address] = value; + const uint8_t protocol = value & SN_MR_PROTO_MASK; + switch (protocol) + { + case SN_MR_CLOSED: #ifdef U2_LOG_STATE - LogFileOutput("U2: Mode[%d]: TCP\n", i); + LogFileOutput("U2: Mode[%d]: CLOSED\n", i); #endif - break; - case SN_MR_UDP: + break; + case SN_MR_TCP: #ifdef U2_LOG_STATE - LogFileOutput("U2: Mode[%d]: UDP\n", i); + LogFileOutput("U2: Mode[%d]: TCP\n", i); #endif - break; - case SN_MR_IPRAW: + break; + case SN_MR_UDP: #ifdef U2_LOG_STATE - LogFileOutput("U2: Mode[%d]: IPRAW\n", i); + LogFileOutput("U2: Mode[%d]: UDP\n", i); #endif - break; - case SN_MR_MACRAW: + break; + case SN_MR_IPRAW: #ifdef U2_LOG_STATE - LogFileOutput("U2: Mode[%d]: MACRAW\n", i); + LogFileOutput("U2: Mode[%d]: IPRAW\n", i); #endif - break; + break; + case SN_MR_MACRAW: +#ifdef U2_LOG_STATE + LogFileOutput("U2: Mode[%d]: MACRAW\n", i); +#endif + break; #ifdef U2_LOG_UNKNOWN - default: - LogFileOutput("U2: Unknown protocol: %02x\n", protocol); + default: + LogFileOutput("U2: Unknown protocol: %02x\n", protocol); #endif - } } +} - void setTXSizes(const uint16_t address, uint8_t value) +void Uthernet2::setTXSizes(const uint16_t address, uint8_t value) +{ + myMemory[address] = value; + uint16_t base = TX_BASE; + const uint16_t end = RX_BASE; + for (Socket & socket : mySockets) { - memory[address] = value; - uint16_t base = TX_BASE; - const uint16_t end = RX_BASE; - for (Socket & socket : sockets) - { - socket.transmitBase = base; + socket.transmitBase = base; - const uint8_t bits = value & 0x03; - value >>= 2; + const uint8_t bits = value & 0x03; + value >>= 2; - const uint16_t size = 1 << (10 + bits); - base += size; + const uint16_t size = 1 << (10 + bits); + base += size; - if (base > end) - { - base = end; - } - socket.transmitSize = base - socket.transmitBase; + if (base > end) + { + base = end; } + socket.transmitSize = base - socket.transmitBase; } +} - void setRXSizes(const uint16_t address, uint8_t value) +void Uthernet2::setRXSizes(const uint16_t address, uint8_t value) +{ + myMemory[address] = value; + uint16_t base = RX_BASE; + const uint16_t end = MEM_SIZE; + for (Socket & socket : mySockets) { - memory[address] = value; - uint16_t base = RX_BASE; - const uint16_t end = MEM_SIZE; - for (Socket & socket : sockets) - { - socket.receiveBase = base; + socket.receiveBase = base; - const uint8_t bits = value & 0x03; - value >>= 2; + const uint8_t bits = value & 0x03; + value >>= 2; - const uint16_t size = 1 << (10 + bits); - base += size; + const uint16_t size = 1 << (10 + bits); + base += size; - if (base > end) - { - base = end; - } - socket.receiveSize = base - socket.receiveBase; + if (base > end) + { + base = end; } + socket.receiveSize = base - socket.receiveBase; } +} - uint16_t getTXDataSize(const size_t i) - { - const Socket & socket = sockets[i]; - const uint16_t size = socket.transmitSize; - const uint16_t mask = size - 1; - - const int sn_tx_rd = readNetworkWord(memory.data() + socket.registers + SN_TX_RD0) & mask; - const int sn_tx_wr = readNetworkWord(memory.data() + socket.registers + SN_TX_WR0) & mask; +uint16_t Uthernet2::getTXDataSize(const size_t i) const +{ + const Socket & socket = mySockets[i]; + const uint16_t size = socket.transmitSize; + const uint16_t mask = size - 1; - int dataPresent = sn_tx_wr - sn_tx_rd; - if (dataPresent < 0) - { - dataPresent += size; - } - return dataPresent; - } + const int sn_tx_rd = readNetworkWord(myMemory.data() + socket.registers + SN_TX_RD0) & mask; + const int sn_tx_wr = readNetworkWord(myMemory.data() + socket.registers + SN_TX_WR0) & mask; - uint8_t getTXFreeSizeRegister(const size_t i, const size_t shift) + int dataPresent = sn_tx_wr - sn_tx_rd; + if (dataPresent < 0) { - const int size = sockets[i].transmitSize; - const uint16_t present = getTXDataSize(i); - const uint16_t free = size - present; - const uint8_t reg = getIByte(free, shift); - return reg; + dataPresent += size; } + return dataPresent; +} - uint8_t getRXDataSizeRegister(const size_t i, const size_t shift) - { - const uint16_t rsr = sockets[i].sn_rx_rsr; - const uint8_t reg = getIByte(rsr, shift); - return reg; - } +uint8_t Uthernet2::getTXFreeSizeRegister(const size_t i, const size_t shift) const +{ + const int size = mySockets[i].transmitSize; + const uint16_t present = getTXDataSize(i); + const uint16_t free = size - present; + const uint8_t reg = getIByte(free, shift); + return reg; +} - void updateRSR(const size_t i) - { - Socket & socket = sockets[i]; +uint8_t Uthernet2::getRXDataSizeRegister(const size_t i, const size_t shift) const +{ + const uint16_t rsr = mySockets[i].sn_rx_rsr; + const uint8_t reg = getIByte(rsr, shift); + return reg; +} + +void Uthernet2::updateRSR(const size_t i) +{ + Socket & socket = mySockets[i]; - const int size = socket.receiveSize; - const uint16_t mask = size - 1; + const int size = socket.receiveSize; + const uint16_t mask = size - 1; - const int sn_rx_rd = readNetworkWord(memory.data() + socket.registers + SN_RX_RD0) & mask; - const int sn_rx_wr = socket.sn_rx_wr & mask; - int dataPresent = sn_rx_wr - sn_rx_rd; - if (dataPresent < 0) - { - dataPresent += size; - } - // is this logic correct? - // here we are re-synchronising the size with the pointers - // elsewhere I have seen people updating this value - // by the amount of how much 0x28 has moved forward - // but then we need to keep track of where it was - // the final result should be the same + const int sn_rx_rd = readNetworkWord(myMemory.data() + socket.registers + SN_RX_RD0) & mask; + const int sn_rx_wr = socket.sn_rx_wr & mask; + int dataPresent = sn_rx_wr - sn_rx_rd; + if (dataPresent < 0) + { + dataPresent += size; + } + // is this logic correct? + // here we are re-synchronising the size with the pointers + // elsewhere I have seen people updating this value + // by the amount of how much 0x28 has moved forward + // but then we need to keep track of where it was + // the final result should be the same #ifdef U2_LOG_TRAFFIC - if (socket.sn_rx_rsr != dataPresent) - { - LogFileOutput("U2: RECV[%d]: %d -> %d bytes\n", i, socket.sn_rx_rsr, dataPresent); - } -#endif - socket.sn_rx_rsr = dataPresent; + if (socket.sn_rx_rsr != dataPresent) + { + LogFileOutput("U2: RECV[%d]: %d -> %d bytes\n", i, socket.sn_rx_rsr, dataPresent); } +#endif + socket.sn_rx_rsr = dataPresent; +} - void receiveOnePacketMacRaw(const size_t i) - { - const Socket & socket = sockets[i]; - const uint16_t rsr = socket.sn_rx_rsr; +void Uthernet2::receiveOnePacketMacRaw(const size_t i) +{ + Socket & socket = mySockets[i]; + const uint16_t rsr = socket.sn_rx_rsr; #ifdef U2_USE_SLIRP + { + std::queue> & queue = mySlirp->getQueue(); + while (!queue.empty()) { - std::queue> & queue = slirp->getQueue(); - while (!queue.empty()) + const std::vector & packet = queue.front(); + if (socket.isThereRoomFor(packet.size(), sizeof(uint16_t))) { - const std::vector & packet = queue.front(); - if (isThereRoomFor(i, packet.size(), sizeof(uint16_t))) - { - writeDataMacRaw(i, packet.data(), packet.size()); - queue.pop(); + writeDataMacRaw(socket, myMemory, packet.data(), packet.size()); + queue.pop(); #ifdef U2_LOG_TRAFFIC - LogFileOutput("U2: READ MACRAW[%d]: +%d -> %d bytes (%04x)\n", i, packet.size(), socket.sn_rx_rsr, socket.sn_rx_wr); + LogFileOutput("U2: READ MACRAW[%d]: +%d -> %d bytes (%04x)\n", i, packet.size(), socket.sn_rx_rsr, socket.sn_rx_wr); #endif - } - else - { - break; - } + } + else + { + break; } } + } #else + { + BYTE buffer[MAX_RXLENGTH]; + int len; + if (tfeReceiveOnePacket(myMemory.data() + SHAR0, sizeof(buffer), buffer, len)) { - BYTE buffer[MAX_RXLENGTH]; - int len; - if (tfeReceiveOnePacket(memory.data() + SHAR0, sizeof(buffer), buffer, len)) + if (socket.isThereRoomFor(len, sizeof(uint16_t))) { - if (isThereRoomFor(i, len, sizeof(uint16_t))) - { - writeDataMacRaw(i, buffer, len); + writeDataMacRaw(socket, myMemory, buffer, len); #ifdef U2_LOG_TRAFFIC - LogFileOutput("U2: READ MACRAW[%d]: +%d -> %d bytes\n", i, len, socket.sn_rx_rsr); + LogFileOutput("U2: READ MACRAW[%d]: +%d -> %d bytes\n", i, len, socket.sn_rx_rsr); #endif - } - else - { - // drop it + } + else + { + // drop it #ifdef U2_LOG_TRAFFIC - LogFileOutput("U2: SKIP MACRAW[%d]: %d bytes\n", i, len); + LogFileOutput("U2: SKIP MACRAW[%d]: %d bytes\n", i, len); #endif - } } } -#endif } +#endif +} - // UDP & TCP - void receiveOnePacketFromSocket(const size_t i) +// UDP & TCP +void Uthernet2::receiveOnePacketFromSocket(const size_t i) +{ + Socket & socket = mySockets[i]; + if (socket.myFD != -1) { - Socket & socket = sockets[i]; - if (socket.myFD != -1) + const uint16_t freeRoom = socket.getFreeRoom(); + if (freeRoom > 32) // avoid meaningless reads { - const uint16_t freeRoom = getFreeRoom(i); - if (freeRoom > 32) // avoid meaningless reads - { - std::vector buffer(freeRoom - 1); // do not fill the buffer completely - sockaddr_in source = {0}; - socklen_t len = sizeof(sockaddr_in); - const ssize_t data = recvfrom(socket.myFD, buffer.data(), buffer.size(), 0, (struct sockaddr *) &source, &len); + std::vector buffer(freeRoom - 1); // do not fill the buffer completely + sockaddr_in source = {0}; + socklen_t len = sizeof(sockaddr_in); + const ssize_t data = recvfrom(socket.myFD, buffer.data(), buffer.size(), 0, (struct sockaddr *) &source, &len); #ifdef U2_LOG_TRAFFIC - const char * proto = socket.sn_sr == SN_SR_SOCK_UDP ? "UDP" : "TCP"; + const char * proto = socket.sn_sr == SN_SR_SOCK_UDP ? "UDP" : "TCP"; #endif - if (data > 0) - { - writeDataForProtocol(i, buffer.data(), data, source); + if (data > 0) + { + writeDataForProtocol(socket, myMemory, buffer.data(), data, source); #ifdef U2_LOG_TRAFFIC - LogFileOutput("U2: READ %s[%d]: +%d -> %d bytes\n", proto, i, data, socket.sn_rx_rsr); + LogFileOutput("U2: READ %s[%d]: +%d -> %d bytes\n", proto, i, data, socket.sn_rx_rsr); #endif - } - else if (data == 0) - { - // gracefull termination - socket.clearFD(); - } - else // data < 0; + } + else if (data == 0) + { + // gracefull termination + socket.clearFD(); + } + else // data < 0; + { + const int error = errno; + if (error != EAGAIN && error != EWOULDBLOCK) { - const int error = errno; - if (error != EAGAIN && error != EWOULDBLOCK) - { #ifdef U2_LOG_TRAFFIC - LogFileOutput("U2: ERROR %s[%d]: %d\n", proto, i, error); + LogFileOutput("U2: ERROR %s[%d]: %d\n", proto, i, error); #endif - socket.clearFD(); - } + socket.clearFD(); } } } } +} - void receiveOnePacket(const size_t i) +void Uthernet2::receiveOnePacket(const size_t i) +{ + const Socket & socket = mySockets[i]; + switch (socket.sn_sr) { - const Socket & socket = sockets[i]; - switch (socket.sn_sr) - { - case SN_SR_SOCK_MACRAW: - receiveOnePacketMacRaw(i); - break; - case SN_SR_ESTABLISHED: - case SN_SR_SOCK_UDP: - receiveOnePacketFromSocket(i); - break; - case SN_SR_CLOSED: - break; // nothing to do + case SN_SR_SOCK_MACRAW: + receiveOnePacketMacRaw(i); + break; + case SN_SR_ESTABLISHED: + case SN_SR_SOCK_UDP: + receiveOnePacketFromSocket(i); + break; + case SN_SR_CLOSED: + break; // nothing to do #ifdef U2_LOG_UNKNOWN - default: - LogFileOutput("U2: READ[%d]: unknown mode: %02x\n", i, socket.sn_sr); + default: + LogFileOutput("U2: READ[%d]: unknown mode: %02x\n", i, socket.sn_sr); #endif - }; - } + }; +} - void sendDataMacRaw(const size_t i, const std::vector & data) - { +void Uthernet2::sendDataMacRaw(const size_t i, const std::vector & data) const +{ #ifdef U2_LOG_TRAFFIC - LogFileOutput("U2: SEND MACRAW[%d]: %d bytes\n", i, data.size()); + LogFileOutput("U2: SEND MACRAW[%d]: %d bytes\n", i, data.size()); #endif #ifdef U2_USE_SLIRP - slirp->sendFromGuest(data.data(), data.size()); + mySlirp->sendFromGuest(data.data(), data.size()); #else - tfeTransmitOnePacket(data.data(), data.size()); + tfeTransmitOnePacket(data.data(), data.size()); #endif - } +} - void sendDataToSocket(const size_t i, std::vector & data) +void Uthernet2::sendDataToSocket(const size_t i, std::vector & data) +{ + Socket & socket = mySockets[i]; + if (socket.myFD != -1) { - Socket & socket = sockets[i]; - if (socket.myFD != -1) - { - sockaddr_in destination = {}; - destination.sin_family = AF_INET; + sockaddr_in destination = {}; + destination.sin_family = AF_INET; - // already in network order - // this seems to be ignored for TCP, and so we reuse the same code - const uint8_t * dest = memory.data() + socket.registers + SN_DIPR0; - destination.sin_addr.s_addr = *reinterpret_cast(dest); - destination.sin_port = *reinterpret_cast(memory.data() + socket.registers + SN_DPORT0); + // already in network order + // this seems to be ignored for TCP, and so we reuse the same code + const uint8_t * dest = myMemory.data() + socket.registers + SN_DIPR0; + destination.sin_addr.s_addr = *reinterpret_cast(dest); + destination.sin_port = *reinterpret_cast(myMemory.data() + socket.registers + SN_DPORT0); - const ssize_t res = sendto(socket.myFD, data.data(), data.size(), 0, (const struct sockaddr *) &destination, sizeof(destination)); + const ssize_t res = sendto(socket.myFD, data.data(), data.size(), 0, (const struct sockaddr *) &destination, sizeof(destination)); #ifdef U2_LOG_TRAFFIC - const char * proto = socket.sn_sr == SN_SR_SOCK_UDP ? "UDP" : "TCP"; - LogFileOutput("U2: SEND %s[%d]: %d of %d bytes\n", proto, i, res, data.size()); + const char * proto = socket.sn_sr == SN_SR_SOCK_UDP ? "UDP" : "TCP"; + LogFileOutput("U2: SEND %s[%d]: %d of %d bytes\n", proto, i, res, data.size()); #endif - if (res < 0) + if (res < 0) + { + const int error = errno; + if (error != EAGAIN && error != EWOULDBLOCK) { - const int error = errno; - if (error != EAGAIN && error != EWOULDBLOCK) - { #ifdef U2_LOG_TRAFFIC - LogFileOutput("U2: ERROR %s[%d]: %d\n", proto, i, error); + LogFileOutput("U2: ERROR %s[%d]: %d\n", proto, i, error); #endif - socket.clearFD(); - } + socket.clearFD(); } } } +} - void sendData(const size_t i) - { - const Socket & socket = sockets[i]; - const uint16_t size = socket.transmitSize; - const uint16_t mask = size - 1; +void Uthernet2::sendData(const size_t i) +{ + const Socket & socket = mySockets[i]; + const uint16_t size = socket.transmitSize; + const uint16_t mask = size - 1; - const int sn_tx_rr = readNetworkWord(memory.data() + socket.registers + SN_TX_RD0) & mask; - const int sn_tx_wr = readNetworkWord(memory.data() + socket.registers + SN_TX_WR0) & mask; + const int sn_tx_rr = readNetworkWord(myMemory.data() + socket.registers + SN_TX_RD0) & mask; + const int sn_tx_wr = readNetworkWord(myMemory.data() + socket.registers + SN_TX_WR0) & mask; - const uint16_t base = socket.transmitBase; - const uint16_t rr_address = base + sn_tx_rr; - const uint16_t wr_address = base + sn_tx_wr; + const uint16_t base = socket.transmitBase; + const uint16_t rr_address = base + sn_tx_rr; + const uint16_t wr_address = base + sn_tx_wr; - std::vector data; - if (rr_address < wr_address) - { - data.assign(memory.begin() + rr_address, memory.begin() + wr_address); - } - else - { - const uint16_t end = base + size; - data.assign(memory.begin() + rr_address, memory.begin() + end); - data.insert(data.end(), memory.begin() + base, memory.begin() + wr_address); - } + std::vector data; + if (rr_address < wr_address) + { + data.assign(myMemory.begin() + rr_address, myMemory.begin() + wr_address); + } + else + { + const uint16_t end = base + size; + data.assign(myMemory.begin() + rr_address, myMemory.begin() + end); + data.insert(data.end(), myMemory.begin() + base, myMemory.begin() + wr_address); + } - // move read pointer to writer - memory[socket.registers + SN_TX_RD0] = getIByte(sn_tx_wr, 8); - memory[socket.registers + SN_TX_RD1] = getIByte(sn_tx_wr, 0); + // move read pointer to writer + myMemory[socket.registers + SN_TX_RD0] = getIByte(sn_tx_wr, 8); + myMemory[socket.registers + SN_TX_RD1] = getIByte(sn_tx_wr, 0); - switch (socket.sn_sr) - { - case SN_SR_SOCK_MACRAW: - sendDataMacRaw(i, data); - break; - case SN_SR_ESTABLISHED: - case SN_SR_SOCK_UDP: - sendDataToSocket(i, data); - break; + switch (socket.sn_sr) + { + case SN_SR_SOCK_MACRAW: + sendDataMacRaw(i, data); + break; + case SN_SR_ESTABLISHED: + case SN_SR_SOCK_UDP: + sendDataToSocket(i, data); + break; #ifdef U2_LOG_UNKNOWN - default: - LogFileOutput("U2: SEND[%d]: unknown mode: %02x\n", i, socket.sn_sr); + default: + LogFileOutput("U2: SEND[%d]: unknown mode: %02x\n", i, socket.sn_sr); #endif - } } +} - void resetRXTXBuffers(const size_t i) - { - Socket & socket = sockets[i]; - socket.sn_rx_wr = 0x00; - socket.sn_rx_rsr = 0x00; - memory[socket.registers + SN_TX_RD0] = 0x00; - memory[socket.registers + SN_TX_RD1] = 0x00; - memory[socket.registers + SN_TX_WR0] = 0x00; - memory[socket.registers + SN_TX_WR1] = 0x00; - memory[socket.registers + SN_RX_RD0] = 0x00; - memory[socket.registers + SN_RX_RD1] = 0x00; - } +void Uthernet2::resetRXTXBuffers(const size_t i) +{ + Socket & socket = mySockets[i]; + socket.sn_rx_wr = 0x00; + socket.sn_rx_rsr = 0x00; + myMemory[socket.registers + SN_TX_RD0] = 0x00; + myMemory[socket.registers + SN_TX_RD1] = 0x00; + myMemory[socket.registers + SN_TX_WR0] = 0x00; + myMemory[socket.registers + SN_TX_WR1] = 0x00; + myMemory[socket.registers + SN_RX_RD0] = 0x00; + myMemory[socket.registers + SN_RX_RD1] = 0x00; +} - void openSystemSocket(const size_t i, const int type, const int protocol, const int state) +void Uthernet2::openSystemSocket(const size_t i, const int type, const int protocol, const int state) +{ + Socket & s = mySockets[i]; + const int fd = socket(AF_INET, type | SOCK_NONBLOCK, protocol); + if (fd < -1) { - Socket & s = sockets[i]; - const int fd = socket(AF_INET, type | SOCK_NONBLOCK, protocol); - if (fd < -1) - { #ifdef U2_LOG_STATE - const char * proto = state == SN_SR_SOCK_UDP ? "UDP" : "TCP"; - LogFileOutput("U2: %s[%d]: %s\n", proto, i, strerror(errno)); + const char * proto = state == SN_SR_SOCK_UDP ? "UDP" : "TCP"; + LogFileOutput("U2: %s[%d]: %s\n", proto, i, strerror(errno)); #endif - s.clearFD(); - } - else - { - s.setFD(fd, state); - } + s.clearFD(); + } + else + { + s.setFD(fd, state); } +} - void openSocket(const size_t i) +void Uthernet2::openSocket(const size_t i) +{ + Socket & socket = mySockets[i]; + const uint8_t mr = myMemory[socket.registers + SN_MR]; + const uint8_t protocol = mr & SN_MR_PROTO_MASK; + uint8_t & sr = socket.sn_sr; + switch (protocol) { - Socket & socket = sockets[i]; - const uint8_t mr = memory[socket.registers + SN_MR]; - const uint8_t protocol = mr & SN_MR_PROTO_MASK; - uint8_t & sr = socket.sn_sr; - switch (protocol) - { - case SN_MR_IPRAW: - sr = SN_SR_SOCK_IPRAW; - break; - case SN_MR_MACRAW: - sr = SN_SR_SOCK_MACRAW; - break; - case SN_MR_TCP: - openSystemSocket(i, SOCK_STREAM, IPPROTO_TCP, SN_SR_SOCK_INIT); - break; - case SN_MR_UDP: - openSystemSocket(i, SOCK_DGRAM, IPPROTO_UDP, SN_SR_SOCK_UDP); - break; + case SN_MR_IPRAW: + sr = SN_SR_SOCK_IPRAW; + break; + case SN_MR_MACRAW: + sr = SN_SR_SOCK_MACRAW; + break; + case SN_MR_TCP: + openSystemSocket(i, SOCK_STREAM, IPPROTO_TCP, SN_SR_SOCK_INIT); + break; + case SN_MR_UDP: + openSystemSocket(i, SOCK_DGRAM, IPPROTO_UDP, SN_SR_SOCK_UDP); + break; #ifdef U2_LOG_UNKNOWN - default: - LogFileOutput("U2: OPEN[%d]: unknown mode: %02x\n", i, mr); + default: + LogFileOutput("U2: OPEN[%d]: unknown mode: %02x\n", i, mr); #endif - } - resetRXTXBuffers(i); // needed? + } + resetRXTXBuffers(i); // needed? #ifdef U2_LOG_STATE - LogFileOutput("U2: OPEN[%d]: %02x\n", i, sr); + LogFileOutput("U2: OPEN[%d]: %02x\n", i, sr); #endif - } +} - void closeSocket(const size_t i) - { - Socket & socket = sockets[i]; - socket.clearFD(); +void Uthernet2::closeSocket(const size_t i) +{ + Socket & socket = mySockets[i]; + socket.clearFD(); #ifdef U2_LOG_STATE - LogFileOutput("U2: CLOSE[%d]\n", i); + LogFileOutput("U2: CLOSE[%d]\n", i); #endif - } +} - void connectSocket(const size_t i) - { - Socket & socket = sockets[i]; - const uint8_t * dest = memory.data() + socket.registers + SN_DIPR0; +void Uthernet2::connectSocket(const size_t i) +{ + Socket & socket = mySockets[i]; + const uint8_t * dest = myMemory.data() + socket.registers + SN_DIPR0; - sockaddr_in destination = {}; - destination.sin_family = AF_INET; + sockaddr_in destination = {}; + destination.sin_family = AF_INET; - // already in network order - destination.sin_port = *reinterpret_cast(memory.data() + socket.registers + SN_DPORT0); - destination.sin_addr.s_addr = *reinterpret_cast(dest); + // already in network order + destination.sin_port = *reinterpret_cast(myMemory.data() + socket.registers + SN_DPORT0); + destination.sin_addr.s_addr = *reinterpret_cast(dest); - const int res = connect(socket.myFD, (struct sockaddr *)&destination, sizeof(destination)); + const int res = connect(socket.myFD, (struct sockaddr *)&destination, sizeof(destination)); - if (res == 0) - { - socket.sn_sr = SN_SR_ESTABLISHED; - socket.myErrno = 0; + if (res == 0) + { + socket.sn_sr = SN_SR_ESTABLISHED; + socket.myErrno = 0; #ifdef U2_LOG_STATE - const uint16_t port = readNetworkWord(memory.data() + socket.registers + SN_DPORT0); - LogFileOutput("U2: TCP[%d]: CONNECT to %d.%d.%d.%d:%d\n", i, dest[0], dest[1], dest[2], dest[3], port); + const uint16_t port = readNetworkWord(memory.data() + socket.registers + SN_DPORT0); + LogFileOutput("U2: TCP[%d]: CONNECT to %d.%d.%d.%d:%d\n", i, dest[0], dest[1], dest[2], dest[3], port); #endif - } - else + } + else + { + const int error = errno; + if (error == EINPROGRESS) { - const int error = errno; - if (error == EINPROGRESS) - { - socket.myErrno = error; - } + socket.myErrno = error; + } #ifdef U2_LOG_STATE - LogFileOutput("U2: TCP[%d]: connect: %s\n", i, strerror(error)); + LogFileOutput("U2: TCP[%d]: connect: %s\n", i, strerror(error)); #endif - } } +} - void setCommandRegister(const size_t i, const uint8_t value) +void Uthernet2::setCommandRegister(const size_t i, const uint8_t value) +{ + switch (value) { - switch (value) - { - case SN_CR_OPEN: - openSocket(i); - break; - case SN_CR_CONNECT: - connectSocket(i); - break; - case SN_CR_CLOSE: - case SN_CR_DISCON: - closeSocket(i); - break; - case SN_CR_SEND: - sendData(i); - break; - case SN_CR_RECV: - updateRSR(i); - break; + case SN_CR_OPEN: + openSocket(i); + break; + case SN_CR_CONNECT: + connectSocket(i); + break; + case SN_CR_CLOSE: + case SN_CR_DISCON: + closeSocket(i); + break; + case SN_CR_SEND: + sendData(i); + break; + case SN_CR_RECV: + updateRSR(i); + break; #ifdef U2_LOG_UNKNOWN - default: - LogFileOutput("U2: Unknown command[%d]: %02x\n", i, value); + default: + LogFileOutput("U2: Unknown command[%d]: %02x\n", i, value); #endif - } } +} - uint8_t readSocketRegister(const uint16_t address) +uint8_t Uthernet2::readSocketRegister(const uint16_t address) +{ + const uint16_t i = (address >> 8) - 0x04; + const uint16_t loc = address & 0xFF; + uint8_t value; + switch (loc) { - const uint16_t i = (address >> 8) - 0x04; - const uint16_t loc = address & 0xFF; - uint8_t value; - switch (loc) - { - case SN_MR: - case SN_CR: - value = memory[address]; - break; - case SN_SR: - value = sockets[i].sn_sr; - break; - case SN_TX_FSR0: - value = getTXFreeSizeRegister(i, 8); - break; - case SN_TX_FSR1: - value = getTXFreeSizeRegister(i, 0); - break; - case SN_TX_RD0: - case SN_TX_RD1: - value = memory[address]; - break; - case SN_TX_WR0: - case SN_TX_WR1: - value = memory[address]; - break; - case SN_RX_RSR0: - receiveOnePacket(i); - value = getRXDataSizeRegister(i, 8); - break; - case SN_RX_RSR1: - receiveOnePacket(i); - value = getRXDataSizeRegister(i, 0); - break; - case SN_RX_RD0: - case SN_RX_RD1: - value = memory[address]; - break; - default: + case SN_MR: + case SN_CR: + value = myMemory[address]; + break; + case SN_SR: + value = mySockets[i].sn_sr; + break; + case SN_TX_FSR0: + value = getTXFreeSizeRegister(i, 8); + break; + case SN_TX_FSR1: + value = getTXFreeSizeRegister(i, 0); + break; + case SN_TX_RD0: + case SN_TX_RD1: + value = myMemory[address]; + break; + case SN_TX_WR0: + case SN_TX_WR1: + value = myMemory[address]; + break; + case SN_RX_RSR0: + receiveOnePacket(i); + value = getRXDataSizeRegister(i, 8); + break; + case SN_RX_RSR1: + receiveOnePacket(i); + value = getRXDataSizeRegister(i, 0); + break; + case SN_RX_RD0: + case SN_RX_RD1: + value = myMemory[address]; + break; + default: #ifdef U2_LOG_UNKNOWN - LogFileOutput("U2: Get unknown socket register[%d]: %04x\n", i, address); + LogFileOutput("U2: Get unknown socket register[%d]: %04x\n", i, address); #endif - value = memory[address]; - break; - } - return value; + value = myMemory[address]; + break; } + return value; +} - uint8_t readValueAt(const uint16_t address) +uint8_t Uthernet2::readValueAt(const uint16_t address) +{ + uint8_t value; + switch (address) { - uint8_t value; - switch (address) - { - case MR: - value = modeRegister; - break; - case GAR0 ... UPORT1: - value = memory[address]; - break; - case S0_BASE ... S3_MAX: - value = readSocketRegister(address); - break; - case TX_BASE ... MEM_MAX: - value = memory[address]; - break; - default: + case MR: + value = myModeRegister; + break; + case GAR0 ... UPORT1: + value = myMemory[address]; + break; + case S0_BASE ... S3_MAX: + value = readSocketRegister(address); + break; + case TX_BASE ... MEM_MAX: + value = myMemory[address]; + break; + default: #ifdef U2_LOG_UNKNOWN - LogFileOutput("U2: Read unknown location: %04x\n", address); + LogFileOutput("U2: Read unknown location: %04x\n", address); #endif - // this might not be 100% correct if address >= 0x8000 - // see top of page 13 Uthernet II - value = memory[address & MEM_MAX]; - break; - } - return value; + // this might not be 100% correct if address >= 0x8000 + // see top of page 13 Uthernet II + value = myMemory[address & MEM_MAX]; + break; } + return value; +} - void autoIncrement() +void Uthernet2::autoIncrement() +{ + if (myModeRegister & MR_AI) { - if (modeRegister & MR_AI) + ++myDataAddress; + // Read bottom of Uthernet II page 12 + // Setting the address to values >= 0x8000 is not really supported + switch (myDataAddress) { - ++dataAddress; - // Read bottom of Uthernet II page 12 - // Setting the address to values >= 0x8000 is not really supported - switch (dataAddress) - { - case RX_BASE: - case MEM_SIZE: - dataAddress -= 0x2000; - break; - } + case RX_BASE: + case MEM_SIZE: + myDataAddress -= 0x2000; + break; } } +} - uint8_t readValue() - { - const uint8_t value = readValueAt(dataAddress); - autoIncrement(); - return value; - } +uint8_t Uthernet2::readValue() +{ + const uint8_t value = readValueAt(myDataAddress); + autoIncrement(); + return value; +} - void setIPProtocol(const size_t i, const uint16_t address, const uint8_t value) - { - memory[address] = value; +void Uthernet2::setIPProtocol(const size_t i, const uint16_t address, const uint8_t value) +{ + myMemory[address] = value; #ifdef U2_LOG_STATE - LogFileOutput("U2: IP PROTO[%d] = %d\n", i, value); + LogFileOutput("U2: IP PROTO[%d] = %d\n", i, value); #endif - } +} - void setIPTypeOfService(const size_t i, const uint16_t address, const uint8_t value) - { - memory[address] = value; +void Uthernet2::setIPTypeOfService(const size_t i, const uint16_t address, const uint8_t value) +{ + myMemory[address] = value; #ifdef U2_LOG_STATE - LogFileOutput("U2: IP TOS[%d] = %d\n", i, value); + LogFileOutput("U2: IP TOS[%d] = %d\n", i, value); #endif - } +} - void setIPTTL(const size_t i, const uint16_t address, const uint8_t value) - { - memory[address] = value; +void Uthernet2::setIPTTL(const size_t i, const uint16_t address, const uint8_t value) +{ + myMemory[address] = value; #ifdef U2_LOG_STATE - LogFileOutput("U2: IP TTL[%d] = %d\n", i, value); + LogFileOutput("U2: IP TTL[%d] = %d\n", i, value); #endif - } +} - void writeSocketRegister(const uint16_t address, const uint8_t value) +void Uthernet2::writeSocketRegister(const uint16_t address, const uint8_t value) +{ + const uint16_t i = (address >> 8) - 0x04; + const uint16_t loc = address & 0xFF; + switch (loc) { - const uint16_t i = (address >> 8) - 0x04; - const uint16_t loc = address & 0xFF; - switch (loc) - { - case SN_MR: - setSocketModeRegister(i, address, value); - break; - case SN_CR: - setCommandRegister(i, value); - break; - case SN_PORT0: - case SN_PORT1: - case SN_DPORT0: - case SN_DPORT1: - memory[address] = value; - break; - case SN_DIPR0 ... SN_DIPR3: - memory[address] = value; - break; - case SN_PROTO: - setIPProtocol(i, address, value); - break; - case SN_TOS: - setIPTypeOfService(i, address, value); - break; - case SN_TTL: - setIPTTL(i, address, value); - break; - case SN_TX_WR0: - memory[address] = value; - break; - case SN_TX_WR1: - memory[address] = value; - break; - case SN_RX_RD0: - memory[address] = value; - break; - case SN_RX_RD1: - memory[address] = value; - break; + case SN_MR: + setSocketModeRegister(i, address, value); + break; + case SN_CR: + setCommandRegister(i, value); + break; + case SN_PORT0: + case SN_PORT1: + case SN_DPORT0: + case SN_DPORT1: + myMemory[address] = value; + break; + case SN_DIPR0 ... SN_DIPR3: + myMemory[address] = value; + break; + case SN_PROTO: + setIPProtocol(i, address, value); + break; + case SN_TOS: + setIPTypeOfService(i, address, value); + break; + case SN_TTL: + setIPTTL(i, address, value); + break; + case SN_TX_WR0: + myMemory[address] = value; + break; + case SN_TX_WR1: + myMemory[address] = value; + break; + case SN_RX_RD0: + myMemory[address] = value; + break; + case SN_RX_RD1: + myMemory[address] = value; + break; #ifdef U2_LOG_UNKNOWN - default: - LogFileOutput("U2: Set unknown socket register[%d]: %04x\n", i, address); - break; + default: + LogFileOutput("U2: Set unknown socket register[%d]: %04x\n", i, address); + break; #endif - }; - } + }; +} - void setModeRegister(const uint16_t address, const uint8_t value) +void Uthernet2::setModeRegister(const uint16_t address, const uint8_t value) +{ + if (value & MR_RST) { - if (value & MR_RST) - { - initialise(); - } - else - { - modeRegister = value; - } + Reset(false); + } + else + { + myModeRegister = value; } +} - void writeCommonRegister(const uint16_t address, const uint8_t value) +void Uthernet2::writeCommonRegister(const uint16_t address, const uint8_t value) +{ + switch (address) { - switch (address) - { - case MR: - setModeRegister(address, value); - break; - case GAR0 ... GAR3: - case SUBR0 ... SUBR3: - case SHAR0 ... SHAR5: - case SIPR0 ... SIPR3: - memory[address] = value; - break; - case RMSR: - setRXSizes(address, value); - break; - case TMSR: - setTXSizes(address, value); - break; + case MR: + setModeRegister(address, value); + break; + case GAR0 ... GAR3: + case SUBR0 ... SUBR3: + case SHAR0 ... SHAR5: + case SIPR0 ... SIPR3: + myMemory[address] = value; + break; + case RMSR: + setRXSizes(address, value); + break; + case TMSR: + setTXSizes(address, value); + break; #ifdef U2_LOG_UNKNOWN - default: - LogFileOutput("U2: Set unknown common register: %04x\n", address); - break; + default: + LogFileOutput("U2: Set unknown common register: %04x\n", address); + break; #endif - }; - } + }; +} - void writeValueAt(const uint16_t address, const uint8_t value) +void Uthernet2::writeValueAt(const uint16_t address, const uint8_t value) +{ + switch (address) { - switch (address) - { - case MR ... UPORT1: - writeCommonRegister(address, value); - break; - case S0_BASE ... S3_MAX: - writeSocketRegister(address, value); - break; - case TX_BASE ... MEM_MAX: - memory[address] = value; - break; + case MR ... UPORT1: + writeCommonRegister(address, value); + break; + case S0_BASE ... S3_MAX: + writeSocketRegister(address, value); + break; + case TX_BASE ... MEM_MAX: + myMemory[address] = value; + break; #ifdef U2_LOG_UNKNOWN - default: - LogFileOutput("U2: Write to unknown location: %02x to %04x\n", value, address); - break; + default: + LogFileOutput("U2: Write to unknown location: %02x to %04x\n", value, address); + break; #endif - } } +} - void writeValue(const uint8_t value) - { - writeValueAt(dataAddress, value); - autoIncrement(); - } +void Uthernet2::writeValue(const uint8_t value) +{ + writeValueAt(myDataAddress, value); + autoIncrement(); +} + +void Uthernet2::Reset(const bool powerCycle) +{ + LogFileOutput("U2: Uthernet 2 initialisation\n"); + myModeRegister = 0; - void initialise() + if (powerCycle) { - LogFileOutput("U2: Uthernet 2 initialisation\n"); - modeRegister = 0; // dataAddress is NOT reset, see page 10 of Uthernet II - sockets.resize(4); - memory.clear(); - memory.resize(MEM_SIZE, 0); + myDataAddress = 0; + } - for (size_t i = 0; i < sockets.size(); ++i) - { - sockets[i].clearFD(); - sockets[i].registers = S0_BASE + (i << 8); - } + mySockets.resize(4); + myMemory.clear(); + myMemory.resize(MEM_SIZE, 0); - // initial values - memory[RTR0] = 0x07; - memory[RTR1] = 0xD0; - setRXSizes(RMSR, 0x55); - setTXSizes(TMSR, 0x55); + for (size_t i = 0; i < mySockets.size(); ++i) + { + mySockets[i].clearFD(); + mySockets[i].registers = S0_BASE + (i << 8); + } + + // initial values + myMemory[RTR0] = 0x07; + myMemory[RTR1] = 0xD0; + setRXSizes(RMSR, 0x55); + setTXSizes(TMSR, 0x55); #ifdef U2_USE_SLIRP - slirp->clearQueue(); + mySlirp->clearQueue(); #endif - } +} - BYTE u2_C0(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles) - { - BYTE res = write ? 0 : MemReadFloatingBus(nCycles); +BYTE Uthernet2::IO_C0(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles) +{ + BYTE res = write ? 0 : MemReadFloatingBus(nCycles); #ifdef U2_LOG_VERBOSE - const uint16_t oldAddress = dataAddress; + const uint16_t oldAddress = dataAddress; #endif - const uint8_t loc = address & 0x0F; + const uint8_t loc = address & 0x0F; - if (write) + if (write) + { + switch (loc) { - switch (loc) - { - case C0X_MODE_REGISTER: - setModeRegister(MR, value); - break; - case C0X_ADDRESS_HIGH: - dataAddress = (value << 8) | (dataAddress & 0x00FF); - break; - case C0X_ADDRESS_LOW: - dataAddress = (value << 0) | (dataAddress & 0xFF00); - break; - case C0X_DATA_PORT: - writeValue(value); - break; - } + case C0X_MODE_REGISTER: + setModeRegister(MR, value); + break; + case C0X_ADDRESS_HIGH: + myDataAddress = (value << 8) | (myDataAddress & 0x00FF); + break; + case C0X_ADDRESS_LOW: + myDataAddress = (value << 0) | (myDataAddress & 0xFF00); + break; + case C0X_DATA_PORT: + writeValue(value); + break; } - else + } + else + { + switch (loc) { - switch (loc) - { - case C0X_MODE_REGISTER: - res = modeRegister; - break; - case C0X_ADDRESS_HIGH: - res = getIByte(dataAddress, 8); - break; - case C0X_ADDRESS_LOW: - res = getIByte(dataAddress, 0); - break; - case C0X_DATA_PORT: - res = readValue(); - break; - } + case C0X_MODE_REGISTER: + res = myModeRegister; + break; + case C0X_ADDRESS_HIGH: + res = getIByte(myDataAddress, 8); + break; + case C0X_ADDRESS_LOW: + res = getIByte(myDataAddress, 0); + break; + case C0X_DATA_PORT: + res = readValue(); + break; } + } #ifdef U2_LOG_VERBOSE - const char * mode = write ? "WRITE " : "READ "; - const char c = std::isprint(res) ? res : '.'; - LogFileOutput("U2: %04x: %s %04x[%04x] %02x = %02x, %c [%d = %d]\n", programcounter, mode, address, oldAddress, value, res, c, value, res); + const char * mode = write ? "WRITE " : "READ "; + const char c = std::isprint(res) ? res : '.'; + LogFileOutput("U2: %04x: %s %04x[%04x] %02x = %02x, %c [%d = %d]\n", programcounter, mode, address, oldAddress, value, res, c, value, res); #endif - return res; - } - + return res; } -void registerUthernet2(UINT uSlot) +BYTE u2_C0(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles) { - #ifdef U2_USE_SLIRP - slirp.reset(); - slirp = std::make_shared(); -#else - const int check = tfe_enabled; - tfe_init(true); - if (tfe_enabled != check) - { - // tfe initialisation failed - return; - } -#endif - dataAddress = 0; - initialise(); - RegisterIoHandler(uSlot, u2_C0, u2_C0, nullptr, nullptr, nullptr, nullptr); -} - -void unRegisterUthernet2() -{ - #ifdef U2_USE_SLIRP - slirp.reset(); -#endif - memory.clear(); - sockets.clear(); + UINT uSlot = ((address & 0xff) >> 4) - 8; + Uthernet2* pCard = (Uthernet2*) MemGetSlotParameters(uSlot); + return pCard->IO_C0(programcounter, address, write, value, nCycles); } -void processEventsUthernet2(uint32_t timeout) +void Uthernet2::processEvents(uint32_t timeout) { #ifdef U2_USE_SLIRP - if (slirp) + if (mySlirp) { - slirp->process(timeout); + mySlirp->process(timeout); } #endif - for (Socket & socket : sockets) + for (Socket & socket : mySockets) { socket.process(); } } + +void Uthernet2::InitializeIO(LPBYTE pCxRomPeripheral) +{ + RegisterIoHandler(m_slot, u2_C0, u2_C0, nullptr, nullptr, this, nullptr); +} + +void Uthernet2::Init() +{ + +} + +void Uthernet2::Update(const ULONG nExecutedCycles) +{ + processEvents(0); +} + +void Uthernet2::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) +{ + +} + +bool Uthernet2::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) +{ + return true; +} diff --git a/source/linux/network/uthernet2.h b/source/linux/network/uthernet2.h index 20548774e..e0a952c18 100644 --- a/source/linux/network/uthernet2.h +++ b/source/linux/network/uthernet2.h @@ -1,7 +1,100 @@ #pragma once -// this register the IOCalls for Uthernet 2 on slot 3 -// if TFE is not enabled (or available) all operations will timeout -void registerUthernet2(UINT uSlot); -void unRegisterUthernet2(); -void processEventsUthernet2(uint32_t timeout); +#include "Card.h" + +#include + +class SlirpNet; + +struct Socket +{ + uint16_t transmitBase; + uint16_t transmitSize; + uint16_t receiveBase; + uint16_t receiveSize; + uint16_t registers; + + uint16_t sn_rx_wr; + uint16_t sn_rx_rsr; + + uint8_t sn_sr; + + int myFD; + int myErrno; + + void clearFD(); + void setFD(const int fd, const int status); + void process(); + + bool isThereRoomFor(const size_t len, const size_t header) const; + uint16_t getFreeRoom() const; + + Socket(); + + ~Socket(); +}; + +class Uthernet2 : public Card +{ +public: + Uthernet2(UINT slot); + + virtual void InitializeIO(LPBYTE pCxRomPeripheral); + virtual void Init(); + virtual void Reset(const bool powerCycle); + virtual void Update(const ULONG nExecutedCycles); + virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); + virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); + + BYTE IO_C0(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles); + +private: + std::vector myMemory; + std::vector mySockets; + uint8_t myModeRegister; + uint16_t myDataAddress; + std::shared_ptr mySlirp; + + void setSocketModeRegister(const size_t i, const uint16_t address, const uint8_t value); + void setTXSizes(const uint16_t address, uint8_t value); + void setRXSizes(const uint16_t address, uint8_t value); + uint16_t getTXDataSize(const size_t i) const; + uint8_t getTXFreeSizeRegister(const size_t i, const size_t shift) const; + uint8_t getRXDataSizeRegister(const size_t i, const size_t shift) const; + + void receiveOnePacketMacRaw(const size_t i); + void receiveOnePacketFromSocket(const size_t i); + void receiveOnePacket(const size_t i); + + void sendDataMacRaw(const size_t i, const std::vector & data) const; + void sendDataToSocket(const size_t i, std::vector & data); + void sendData(const size_t i); + + void resetRXTXBuffers(const size_t i); + void updateRSR(const size_t i); + + void openSystemSocket(const size_t i, const int type, const int protocol, const int state); + void openSocket(const size_t i); + void closeSocket(const size_t i); + void connectSocket(const size_t i); + + void setCommandRegister(const size_t i, const uint8_t value); + + uint8_t readSocketRegister(const uint16_t address); + uint8_t readValueAt(const uint16_t address); + + void autoIncrement(); + uint8_t readValue(); + + void setIPProtocol(const size_t i, const uint16_t address, const uint8_t value); + void setIPTypeOfService(const size_t i, const uint16_t address, const uint8_t value); + void setIPTTL(const size_t i, const uint16_t address, const uint8_t value); + void writeSocketRegister(const uint16_t address, const uint8_t value); + + void setModeRegister(const uint16_t address, const uint8_t value); + void writeCommonRegister(const uint16_t address, const uint8_t value); + void writeValueAt(const uint16_t address, const uint8_t value); + void writeValue(const uint8_t value); + + void processEvents(uint32_t timeout); +}; diff --git a/source/linux/paddle.h b/source/linux/paddle.h index 72b2e0a2b..372d42bc4 100644 --- a/source/linux/paddle.h +++ b/source/linux/paddle.h @@ -11,7 +11,7 @@ class Paddle virtual ~Paddle(); virtual bool getButton(int i) const; - virtual double getAxis(int i) const; + virtual double getAxis(int i) const; // -> [-1, 1] int getAxisValue(int i) const; diff --git a/source/linux/registry.cpp b/source/linux/registry.cpp index ddaa71e62..950575fef 100644 --- a/source/linux/registry.cpp +++ b/source/linux/registry.cpp @@ -1,6 +1,5 @@ -#include "linux/windows/stringcb.h" #include "linux/registry.h" - +#include "windows.h" #include "Log.h" Registry::~Registry() diff --git a/source/linux/registry.h b/source/linux/registry.h index de3b808df..c543a4c0a 100644 --- a/source/linux/registry.h +++ b/source/linux/registry.h @@ -1,6 +1,6 @@ #pragma once -#include "linux/windows/wincompat.h" +#include "wincompat.h" #include #include #include diff --git a/source/linux/win.h b/source/linux/win.h deleted file mode 100644 index cf4bbeac1..000000000 --- a/source/linux/win.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "linux/windows/wincompat.h" -#include "linux/windows/guiddef.h" -#include "linux/windows/handles.h" -#include "linux/windows/bitmap.h" -#include "linux/windows/files.h" -#include "linux/windows/time.h" -#include "linux/windows/bitmap.h" -#include "linux/windows/misc.h" -#include "linux/windows/stringcb.h" -#include "linux/windows/strings.h" -#include "linux/windows/gdi.h" -#include "linux/windows/winbase.h" -#include "linux/windows/winuser.h" -#include "linux/windows/dsound.h" -#include "linux/windows/winerror.h" -#include "linux/windows/mmreg.h" -#include "linux/windows/mmsystem.h" -#include "linux/windows/dmusicc.h" -#include "linux/windows/winnls.h" diff --git a/test/TestCPU6502/CMakeLists.txt b/test/TestCPU6502/CMakeLists.txt index 898698e12..b55f05544 100644 --- a/test/TestCPU6502/CMakeLists.txt +++ b/test/TestCPU6502/CMakeLists.txt @@ -2,3 +2,6 @@ add_executable(testcpu6502 stdafx.cpp ../../source/SynchronousEventManager.cpp TestCPU6502.cpp) + + target_link_libraries(testcpu6502 + windows) diff --git a/test/TestCPU6502/TestCPU6502.cpp b/test/TestCPU6502/TestCPU6502.cpp index 2258d80c3..4354d9004 100644 --- a/test/TestCPU6502/TestCPU6502.cpp +++ b/test/TestCPU6502/TestCPU6502.cpp @@ -14,6 +14,7 @@ SynchronousEventManager g_SynchronousEventMgr; LPBYTE memwrite[0x100]; // TODO: Init LPBYTE mem = NULL; // TODO: Init LPBYTE memdirty = NULL; // TODO: Init +LPBYTE memVidHD = NULL; // TODO: Init iofunction IORead[256] = {0}; // TODO: Init iofunction IOWrite[256] = {0}; // TODO: Init diff --git a/test/TestCPU6502/stdafx.h b/test/TestCPU6502/stdafx.h index 2682d701d..d8f8be361 100644 --- a/test/TestCPU6502/stdafx.h +++ b/test/TestCPU6502/stdafx.h @@ -28,7 +28,7 @@ typedef UINT64 uint64_t; #include #include -#include +#include "windows.h" #include #endif