From ac1fe40e0b38e941c2fb6821d6fb9fdf583d2787 Mon Sep 17 00:00:00 2001 From: mithrendal Date: Tue, 10 Mar 2020 22:26:54 +0100 Subject: [PATCH] initial commit of virtualC64 without the GUI v3.4b1 master branch March 3 2020 latest commit point fe1629c --- C64/C64.cpp | 870 +++++ C64/C64.h | 597 +++ C64/C64_types.h | 161 + C64/CIA/CIA.cpp | 1420 ++++++++ C64/CIA/CIA.h | 500 +++ C64/CIA/CIA_types.h | 59 + C64/CIA/TOD.cpp | 145 + C64/CIA/TOD.h | 212 ++ C64/CIA/TOD_types.h | 47 + C64/CPU/CPU.cpp | 481 +++ C64/CPU/CPU.h | 628 ++++ C64/CPU/CPUInstructions.cpp | 3241 +++++++++++++++++ C64/CPU/CPUInstructions.h | 395 ++ C64/CPU/CPU_types.h | 130 + C64/Cartridges/Cartridge.cpp | 507 +++ C64/Cartridges/Cartridge.h | 488 +++ C64/Cartridges/CartridgeRom.cpp | 107 + C64/Cartridges/CartridgeRom.h | 85 + C64/Cartridges/Cartridge_types.h | 94 + .../CustomCartridges/ActionReplay.cpp | 358 ++ .../CustomCartridges/ActionReplay.h | 155 + C64/Cartridges/CustomCartridges/Comal80.cpp | 55 + C64/Cartridges/CustomCartridges/Comal80.h | 48 + .../CustomCartridges/CustomCartridges.h | 50 + C64/Cartridges/CustomCartridges/EasyFlash.cpp | 287 ++ C64/Cartridges/CustomCartridges/EasyFlash.h | 77 + C64/Cartridges/CustomCartridges/Epyx.cpp | 94 + C64/Cartridges/CustomCartridges/Epyx.h | 74 + C64/Cartridges/CustomCartridges/Expert.cpp | 250 ++ C64/Cartridges/CustomCartridges/Expert.h | 80 + C64/Cartridges/CustomCartridges/FinalIII.cpp | 206 ++ C64/Cartridges/CustomCartridges/FinalIII.h | 116 + .../CustomCartridges/FreezeFrame.cpp | 73 + C64/Cartridges/CustomCartridges/FreezeFrame.h | 55 + C64/Cartridges/CustomCartridges/Funplay.cpp | 47 + C64/Cartridges/CustomCartridges/Funplay.h | 36 + C64/Cartridges/CustomCartridges/GeoRam.cpp | 107 + C64/Cartridges/CustomCartridges/GeoRam.h | 53 + C64/Cartridges/CustomCartridges/Isepic.cpp | 197 + C64/Cartridges/CustomCartridges/Isepic.h | 64 + C64/Cartridges/CustomCartridges/Kcs.cpp | 105 + C64/Cartridges/CustomCartridges/Kcs.h | 52 + C64/Cartridges/CustomCartridges/Kingsoft.cpp | 74 + C64/Cartridges/CustomCartridges/Kingsoft.h | 49 + C64/Cartridges/CustomCartridges/Mach5.cpp | 58 + C64/Cartridges/CustomCartridges/Mach5.h | 45 + C64/Cartridges/CustomCartridges/MagicDesk.cpp | 47 + C64/Cartridges/CustomCartridges/MagicDesk.h | 43 + C64/Cartridges/CustomCartridges/MikroAss.cpp | 36 + C64/Cartridges/CustomCartridges/MikroAss.h | 42 + C64/Cartridges/CustomCartridges/Ocean.cpp | 42 + C64/Cartridges/CustomCartridges/Ocean.h | 36 + C64/Cartridges/CustomCartridges/Rex.cpp | 38 + C64/Cartridges/CustomCartridges/Rex.h | 38 + .../CustomCartridges/SimonsBasic.cpp | 52 + C64/Cartridges/CustomCartridges/SimonsBasic.h | 39 + C64/Cartridges/CustomCartridges/StarDos.cpp | 84 + C64/Cartridges/CustomCartridges/StarDos.h | 55 + .../CustomCartridges/SuperGames.cpp | 42 + C64/Cartridges/CustomCartridges/SuperGames.h | 36 + C64/Cartridges/CustomCartridges/WarpSpeed.cpp | 62 + C64/Cartridges/CustomCartridges/WarpSpeed.h | 49 + .../CustomCartridges/Westermann.cpp | 30 + C64/Cartridges/CustomCartridges/Westermann.h | 37 + C64/Cartridges/CustomCartridges/Zaxxon.cpp | 59 + C64/Cartridges/CustomCartridges/Zaxxon.h | 38 + C64/Cartridges/FlashRom.cpp | 327 ++ C64/Cartridges/FlashRom.h | 162 + C64/Computer/ControlPort.cpp | 195 + C64/Computer/ControlPort.h | 126 + C64/Computer/ControlPort_types.h | 52 + C64/Computer/ExpansionPort.cpp | 355 ++ C64/Computer/ExpansionPort.h | 245 ++ C64/Computer/ExpansionPort_types.h | 35 + C64/Computer/IEC.cpp | 235 ++ C64/Computer/IEC.h | 125 + C64/Computer/Keyboard.cpp | 194 + C64/Computer/Keyboard.h | 191 + C64/Computer/ProcessorPort.cpp | 139 + C64/Computer/ProcessorPort.h | 97 + C64/Configure.h | 65 + C64/Datasette/Datasette.cpp | 278 ++ C64/Datasette/Datasette.h | 212 ++ C64/Drive/Disk.cpp | 890 +++++ C64/Drive/Disk.h | 531 +++ C64/Drive/Disk_types.h | 197 + C64/Drive/Drive.cpp | 554 +++ C64/Drive/Drive.h | 478 +++ C64/Drive/DriveMemory.cpp | 137 + C64/Drive/DriveMemory.h | 99 + C64/Drive/Drive_types.h | 35 + C64/Drive/VIA.cpp | 1062 ++++++ C64/Drive/VIA.h | 565 +++ C64/FileFormats/AnyArchive.cpp | 148 + C64/FileFormats/AnyArchive.h | 141 + C64/FileFormats/AnyC64File.cpp | 286 ++ C64/FileFormats/AnyC64File.h | 198 + C64/FileFormats/AnyDisk.cpp | 85 + C64/FileFormats/AnyDisk.h | 103 + C64/FileFormats/CRTFile.cpp | 285 ++ C64/FileFormats/CRTFile.h | 172 + C64/FileFormats/D64File.cpp | 970 +++++ C64/FileFormats/D64File.h | 297 ++ C64/FileFormats/File_types.h | 53 + C64/FileFormats/G64File.cpp | 207 ++ C64/FileFormats/G64File.h | 113 + C64/FileFormats/P00File.cpp | 180 + C64/FileFormats/P00File.h | 91 + C64/FileFormats/PRGFile.cpp | 141 + C64/FileFormats/PRGFile.h | 90 + C64/FileFormats/ROMFile.cpp | 238 ++ C64/FileFormats/ROMFile.h | 109 + C64/FileFormats/Snapshot.cpp | 182 + C64/FileFormats/Snapshot.h | 164 + C64/FileFormats/T64File.cpp | 427 +++ C64/FileFormats/T64File.h | 121 + C64/FileFormats/TAPFile.cpp | 116 + C64/FileFormats/TAPFile.h | 89 + C64/General/MessageQueue.cpp | 111 + C64/General/MessageQueue.h | 85 + C64/General/TimeDelayed.cpp | 157 + C64/General/TimeDelayed.h | 106 + C64/General/VC64Object.cpp | 103 + C64/General/VC64Object.h | 117 + C64/General/VirtualComponent.cpp | 251 ++ C64/General/VirtualComponent.h | 228 ++ C64/General/basic.cpp | 379 ++ C64/General/basic.h | 372 ++ C64/Memory/C64Memory.cpp | 551 +++ C64/Memory/C64Memory.h | 216 ++ C64/Memory/Memory.h | 75 + C64/Memory/Memory_types.h | 51 + C64/Mouse/Mouse.cpp | 180 + C64/Mouse/Mouse.h | 112 + C64/Mouse/Mouse1350.cpp | 100 + C64/Mouse/Mouse1350.h | 79 + C64/Mouse/Mouse1351.cpp | 92 + C64/Mouse/Mouse1351.h | 81 + C64/Mouse/Mouse_types.h | 32 + C64/Mouse/NeosMouse.cpp | 188 + C64/Mouse/NeosMouse.h | 108 + C64/SID/ReSID.cpp | 295 ++ C64/SID/ReSID.h | 143 + C64/SID/SIDBridge.cpp | 485 +++ C64/SID/SIDBridge.h | 316 ++ C64/SID/SID_types.h | 77 + C64/SID/fastsid/FastSID.cpp | 502 +++ C64/SID/fastsid/FastSID.h | 223 ++ C64/SID/fastsid/FastVoice.cpp | 440 +++ C64/SID/fastsid/FastVoice.h | 295 ++ C64/SID/fastsid/waves.h | 1460 ++++++++ C64/SID/resid/dac.cc | 136 + C64/SID/resid/dac.h | 30 + C64/SID/resid/envelope.cc | 278 ++ C64/SID/resid/envelope.h | 425 +++ C64/SID/resid/extfilt.cc | 65 + C64/SID/resid/extfilt.h | 164 + C64/SID/resid/filter.cc | 780 ++++ C64/SID/resid/filter.h | 1792 +++++++++ C64/SID/resid/pot.cc | 31 + C64/SID/resid/pot.h | 36 + C64/SID/resid/resid-config.h | 29 + C64/SID/resid/sid.cc | 1027 ++++++ C64/SID/resid/sid.h | 234 ++ C64/SID/resid/siddefs.h | 87 + C64/SID/resid/spline.h | 276 ++ C64/SID/resid/version.cc | 22 + C64/SID/resid/voice.cc | 127 + C64/SID/resid/voice.h | 109 + C64/SID/resid/wave.cc | 294 ++ C64/SID/resid/wave.h | 587 +++ C64/SID/resid/wave6581_PST.h | 533 +++ C64/SID/resid/wave6581_PS_.h | 533 +++ C64/SID/resid/wave6581_P_T.h | 533 +++ C64/SID/resid/wave6581__ST.h | 533 +++ C64/SID/resid/wave8580_PST.h | 533 +++ C64/SID/resid/wave8580_PS_.h | 533 +++ C64/SID/resid/wave8580_P_T.h | 533 +++ C64/SID/resid/wave8580__ST.h | 533 +++ C64/VICII/VIC.cpp | 886 +++++ C64/VICII/VIC.h | 1619 ++++++++ C64/VICII/VIC_colors.cpp | 464 +++ C64/VICII/VIC_cycles_ntsc.cpp | 518 +++ C64/VICII/VIC_cycles_pal.cpp | 741 ++++ C64/VICII/VIC_debug.cpp | 464 +++ C64/VICII/VIC_draw.cpp | 591 +++ C64/VICII/VIC_memory.cpp | 1003 +++++ C64/VICII/VIC_types.h | 355 ++ README.md | 2 + 189 files changed, 51970 insertions(+) create mode 100755 C64/C64.cpp create mode 100755 C64/C64.h create mode 100755 C64/C64_types.h create mode 100755 C64/CIA/CIA.cpp create mode 100755 C64/CIA/CIA.h create mode 100755 C64/CIA/CIA_types.h create mode 100755 C64/CIA/TOD.cpp create mode 100755 C64/CIA/TOD.h create mode 100755 C64/CIA/TOD_types.h create mode 100755 C64/CPU/CPU.cpp create mode 100755 C64/CPU/CPU.h create mode 100755 C64/CPU/CPUInstructions.cpp create mode 100755 C64/CPU/CPUInstructions.h create mode 100755 C64/CPU/CPU_types.h create mode 100755 C64/Cartridges/Cartridge.cpp create mode 100755 C64/Cartridges/Cartridge.h create mode 100755 C64/Cartridges/CartridgeRom.cpp create mode 100755 C64/Cartridges/CartridgeRom.h create mode 100755 C64/Cartridges/Cartridge_types.h create mode 100755 C64/Cartridges/CustomCartridges/ActionReplay.cpp create mode 100755 C64/Cartridges/CustomCartridges/ActionReplay.h create mode 100755 C64/Cartridges/CustomCartridges/Comal80.cpp create mode 100755 C64/Cartridges/CustomCartridges/Comal80.h create mode 100755 C64/Cartridges/CustomCartridges/CustomCartridges.h create mode 100755 C64/Cartridges/CustomCartridges/EasyFlash.cpp create mode 100755 C64/Cartridges/CustomCartridges/EasyFlash.h create mode 100755 C64/Cartridges/CustomCartridges/Epyx.cpp create mode 100755 C64/Cartridges/CustomCartridges/Epyx.h create mode 100755 C64/Cartridges/CustomCartridges/Expert.cpp create mode 100755 C64/Cartridges/CustomCartridges/Expert.h create mode 100755 C64/Cartridges/CustomCartridges/FinalIII.cpp create mode 100755 C64/Cartridges/CustomCartridges/FinalIII.h create mode 100755 C64/Cartridges/CustomCartridges/FreezeFrame.cpp create mode 100755 C64/Cartridges/CustomCartridges/FreezeFrame.h create mode 100755 C64/Cartridges/CustomCartridges/Funplay.cpp create mode 100755 C64/Cartridges/CustomCartridges/Funplay.h create mode 100755 C64/Cartridges/CustomCartridges/GeoRam.cpp create mode 100755 C64/Cartridges/CustomCartridges/GeoRam.h create mode 100755 C64/Cartridges/CustomCartridges/Isepic.cpp create mode 100755 C64/Cartridges/CustomCartridges/Isepic.h create mode 100755 C64/Cartridges/CustomCartridges/Kcs.cpp create mode 100755 C64/Cartridges/CustomCartridges/Kcs.h create mode 100755 C64/Cartridges/CustomCartridges/Kingsoft.cpp create mode 100755 C64/Cartridges/CustomCartridges/Kingsoft.h create mode 100755 C64/Cartridges/CustomCartridges/Mach5.cpp create mode 100755 C64/Cartridges/CustomCartridges/Mach5.h create mode 100755 C64/Cartridges/CustomCartridges/MagicDesk.cpp create mode 100755 C64/Cartridges/CustomCartridges/MagicDesk.h create mode 100755 C64/Cartridges/CustomCartridges/MikroAss.cpp create mode 100755 C64/Cartridges/CustomCartridges/MikroAss.h create mode 100755 C64/Cartridges/CustomCartridges/Ocean.cpp create mode 100755 C64/Cartridges/CustomCartridges/Ocean.h create mode 100755 C64/Cartridges/CustomCartridges/Rex.cpp create mode 100755 C64/Cartridges/CustomCartridges/Rex.h create mode 100755 C64/Cartridges/CustomCartridges/SimonsBasic.cpp create mode 100755 C64/Cartridges/CustomCartridges/SimonsBasic.h create mode 100755 C64/Cartridges/CustomCartridges/StarDos.cpp create mode 100755 C64/Cartridges/CustomCartridges/StarDos.h create mode 100755 C64/Cartridges/CustomCartridges/SuperGames.cpp create mode 100755 C64/Cartridges/CustomCartridges/SuperGames.h create mode 100755 C64/Cartridges/CustomCartridges/WarpSpeed.cpp create mode 100755 C64/Cartridges/CustomCartridges/WarpSpeed.h create mode 100755 C64/Cartridges/CustomCartridges/Westermann.cpp create mode 100755 C64/Cartridges/CustomCartridges/Westermann.h create mode 100755 C64/Cartridges/CustomCartridges/Zaxxon.cpp create mode 100755 C64/Cartridges/CustomCartridges/Zaxxon.h create mode 100755 C64/Cartridges/FlashRom.cpp create mode 100755 C64/Cartridges/FlashRom.h create mode 100755 C64/Computer/ControlPort.cpp create mode 100755 C64/Computer/ControlPort.h create mode 100755 C64/Computer/ControlPort_types.h create mode 100755 C64/Computer/ExpansionPort.cpp create mode 100755 C64/Computer/ExpansionPort.h create mode 100755 C64/Computer/ExpansionPort_types.h create mode 100755 C64/Computer/IEC.cpp create mode 100755 C64/Computer/IEC.h create mode 100755 C64/Computer/Keyboard.cpp create mode 100755 C64/Computer/Keyboard.h create mode 100755 C64/Computer/ProcessorPort.cpp create mode 100755 C64/Computer/ProcessorPort.h create mode 100755 C64/Configure.h create mode 100755 C64/Datasette/Datasette.cpp create mode 100755 C64/Datasette/Datasette.h create mode 100755 C64/Drive/Disk.cpp create mode 100755 C64/Drive/Disk.h create mode 100755 C64/Drive/Disk_types.h create mode 100755 C64/Drive/Drive.cpp create mode 100755 C64/Drive/Drive.h create mode 100755 C64/Drive/DriveMemory.cpp create mode 100755 C64/Drive/DriveMemory.h create mode 100755 C64/Drive/Drive_types.h create mode 100755 C64/Drive/VIA.cpp create mode 100755 C64/Drive/VIA.h create mode 100755 C64/FileFormats/AnyArchive.cpp create mode 100755 C64/FileFormats/AnyArchive.h create mode 100755 C64/FileFormats/AnyC64File.cpp create mode 100755 C64/FileFormats/AnyC64File.h create mode 100755 C64/FileFormats/AnyDisk.cpp create mode 100755 C64/FileFormats/AnyDisk.h create mode 100755 C64/FileFormats/CRTFile.cpp create mode 100755 C64/FileFormats/CRTFile.h create mode 100755 C64/FileFormats/D64File.cpp create mode 100755 C64/FileFormats/D64File.h create mode 100755 C64/FileFormats/File_types.h create mode 100755 C64/FileFormats/G64File.cpp create mode 100755 C64/FileFormats/G64File.h create mode 100755 C64/FileFormats/P00File.cpp create mode 100755 C64/FileFormats/P00File.h create mode 100755 C64/FileFormats/PRGFile.cpp create mode 100755 C64/FileFormats/PRGFile.h create mode 100755 C64/FileFormats/ROMFile.cpp create mode 100755 C64/FileFormats/ROMFile.h create mode 100755 C64/FileFormats/Snapshot.cpp create mode 100755 C64/FileFormats/Snapshot.h create mode 100755 C64/FileFormats/T64File.cpp create mode 100755 C64/FileFormats/T64File.h create mode 100755 C64/FileFormats/TAPFile.cpp create mode 100755 C64/FileFormats/TAPFile.h create mode 100755 C64/General/MessageQueue.cpp create mode 100755 C64/General/MessageQueue.h create mode 100755 C64/General/TimeDelayed.cpp create mode 100755 C64/General/TimeDelayed.h create mode 100755 C64/General/VC64Object.cpp create mode 100755 C64/General/VC64Object.h create mode 100755 C64/General/VirtualComponent.cpp create mode 100755 C64/General/VirtualComponent.h create mode 100755 C64/General/basic.cpp create mode 100755 C64/General/basic.h create mode 100755 C64/Memory/C64Memory.cpp create mode 100755 C64/Memory/C64Memory.h create mode 100755 C64/Memory/Memory.h create mode 100755 C64/Memory/Memory_types.h create mode 100755 C64/Mouse/Mouse.cpp create mode 100755 C64/Mouse/Mouse.h create mode 100755 C64/Mouse/Mouse1350.cpp create mode 100755 C64/Mouse/Mouse1350.h create mode 100755 C64/Mouse/Mouse1351.cpp create mode 100755 C64/Mouse/Mouse1351.h create mode 100755 C64/Mouse/Mouse_types.h create mode 100755 C64/Mouse/NeosMouse.cpp create mode 100755 C64/Mouse/NeosMouse.h create mode 100755 C64/SID/ReSID.cpp create mode 100755 C64/SID/ReSID.h create mode 100755 C64/SID/SIDBridge.cpp create mode 100755 C64/SID/SIDBridge.h create mode 100755 C64/SID/SID_types.h create mode 100755 C64/SID/fastsid/FastSID.cpp create mode 100755 C64/SID/fastsid/FastSID.h create mode 100755 C64/SID/fastsid/FastVoice.cpp create mode 100755 C64/SID/fastsid/FastVoice.h create mode 100755 C64/SID/fastsid/waves.h create mode 100755 C64/SID/resid/dac.cc create mode 100755 C64/SID/resid/dac.h create mode 100755 C64/SID/resid/envelope.cc create mode 100755 C64/SID/resid/envelope.h create mode 100755 C64/SID/resid/extfilt.cc create mode 100755 C64/SID/resid/extfilt.h create mode 100755 C64/SID/resid/filter.cc create mode 100755 C64/SID/resid/filter.h create mode 100755 C64/SID/resid/pot.cc create mode 100755 C64/SID/resid/pot.h create mode 100755 C64/SID/resid/resid-config.h create mode 100755 C64/SID/resid/sid.cc create mode 100755 C64/SID/resid/sid.h create mode 100755 C64/SID/resid/siddefs.h create mode 100755 C64/SID/resid/spline.h create mode 100755 C64/SID/resid/version.cc create mode 100755 C64/SID/resid/voice.cc create mode 100755 C64/SID/resid/voice.h create mode 100755 C64/SID/resid/wave.cc create mode 100755 C64/SID/resid/wave.h create mode 100755 C64/SID/resid/wave6581_PST.h create mode 100755 C64/SID/resid/wave6581_PS_.h create mode 100755 C64/SID/resid/wave6581_P_T.h create mode 100755 C64/SID/resid/wave6581__ST.h create mode 100755 C64/SID/resid/wave8580_PST.h create mode 100755 C64/SID/resid/wave8580_PS_.h create mode 100755 C64/SID/resid/wave8580_P_T.h create mode 100755 C64/SID/resid/wave8580__ST.h create mode 100755 C64/VICII/VIC.cpp create mode 100755 C64/VICII/VIC.h create mode 100755 C64/VICII/VIC_colors.cpp create mode 100755 C64/VICII/VIC_cycles_ntsc.cpp create mode 100755 C64/VICII/VIC_cycles_pal.cpp create mode 100755 C64/VICII/VIC_debug.cpp create mode 100755 C64/VICII/VIC_draw.cpp create mode 100755 C64/VICII/VIC_memory.cpp create mode 100755 C64/VICII/VIC_types.h diff --git a/C64/C64.cpp b/C64/C64.cpp new file mode 100755 index 00000000..237da6f3 --- /dev/null +++ b/C64/C64.cpp @@ -0,0 +1,870 @@ +/*! + * @file C64.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +// +// Emulator thread +// + +void +threadCleanup(void* thisC64) +{ + assert(thisC64 != NULL); + + C64 *c64 = (C64 *)thisC64; + c64->threadCleanup(); + c64->sid.halt(); + + c64->debug(2, "Execution thread terminated\n"); + c64->putMessage(MSG_HALT); +} + +void +*runThread(void *thisC64) { + + assert(thisC64 != NULL); + + C64 *c64 = (C64 *)thisC64; + bool success = true; + + c64->debug(2, "Execution thread started\n"); + c64->putMessage(MSG_RUN); + + // Configure thread properties... + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + pthread_cleanup_push(threadCleanup, thisC64); + + // Prepare to run... + c64->cpu.clearErrorState(); + c64->drive1.cpu.clearErrorState(); + c64->drive2.cpu.clearErrorState(); + c64->restartTimer(); + + while (likely(success)) { + pthread_testcancel(); + success = c64->executeOneFrame(); + } + + pthread_cleanup_pop(1); + pthread_exit(NULL); +} + + +// +// Class methods +// + +C64::C64() +{ + setDescription("C64"); + debug("Creating virtual C64[%p]\n", this); + + p = NULL; + warp = false; + alwaysWarp = false; + warpLoad = false; + + // Register sub components + VirtualComponent *subcomponents[] = { + + &mem, + &cpu, + &processorPort, + &cia1, &cia2, + &vic, + &sid, + &keyboard, + &port1, + &port2, + &expansionport, + &iec, + &drive1, + &drive2, + &datasette, + &mouse, + NULL }; + + registerSubComponents(subcomponents, sizeof(subcomponents)); + setC64(this); + + // Register snapshot items + SnapshotItem items[] = { + + { &frame, sizeof(frame), CLEAR_ON_RESET }, + { &rasterLine, sizeof(rasterLine), CLEAR_ON_RESET }, + { &rasterCycle, sizeof(rasterCycle), CLEAR_ON_RESET }, + { &frequency, sizeof(frequency), KEEP_ON_RESET }, + { &durationOfOneCycle, sizeof(durationOfOneCycle), KEEP_ON_RESET }, + { &warp, sizeof(warp), CLEAR_ON_RESET }, + { &ultimax, sizeof(ultimax), CLEAR_ON_RESET }, + + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); + + // Set initial hardware configuration + vic.setModel(PAL_8565); + drive1.powerOn(); + drive2.powerOff(); + + // Initialize mach timer info + mach_timebase_info(&timebase); + + reset(); +} + +C64::~C64() +{ + debug(1, "Destroying virtual C64[%p]\n", this); + + halt(); +} + +void +C64::reset() +{ + debug(1, "Resetting virtual C64[%p]\n", this); + + // Reset all sub components + VirtualComponent::reset(); + + // Initialize processor port + mem.poke(0x0000, 0x2F); // Data direction + mem.poke(0x0001, 0x1F); // IO port, set default memory layout + + // Initialize program counter + cpu.regPC = mem.resetVector(); + debug("Setting PC to %04X\n", cpu.regPC); + + rasterCycle = 1; + nanoTargetTime = 0UL; + ping(); +} + +void C64::ping() +{ + debug (2, "Pinging virtual C64[%p]\n", this); + + VirtualComponent::ping(); + putMessage(warp ? MSG_WARP_ON : MSG_WARP_OFF); + putMessage(alwaysWarp ? MSG_ALWAYS_WARP_ON : MSG_ALWAYS_WARP_OFF); +} + +void +C64::setClockFrequency(uint32_t value) +{ + VirtualComponent::setClockFrequency(value); + + frequency = value; + durationOfOneCycle = 10000000000 / value; + debug(2, "Duration of a C64 CPU cycle is %lld 1/10 nsec.\n", durationOfOneCycle); +} + +void +C64::suspend() +{ + debug(2, "Suspending...(%d)\n", suspendCounter); + + if (suspendCounter == 0 && isHalted()) + return; + + halt(); + suspendCounter++; +} + +void +C64::resume() +{ + debug(2, "Resuming (%d)...\n", suspendCounter); + + if (suspendCounter == 0) + return; + + if (--suspendCounter == 0) + run(); +} + +void +C64::dump() { + msg("C64:\n"); + msg("----\n\n"); + msg(" Machine type : %s\n", vic.isPAL() ? "PAL" : "NTSC"); + msg(" Frames per second : %f\n", vic.getFramesPerSecond()); + msg(" Rasterlines per frame : %d\n", vic.getRasterlinesPerFrame()); + msg(" Cycles per rasterline : %d\n", vic.getCyclesPerRasterline()); + msg(" Current cycle : %llu\n", cpu.cycle); + msg(" Current frame : %d\n", frame); + msg(" Current rasterline : %d\n", rasterLine); + msg(" Current rasterline cycle : %d\n", rasterCycle); + msg(" Ultimax mode : %s\n\n", getUltimax() ? "YES" : "NO"); + msg("warp, warpLoad, alwaysWarp : %d %d %d\n", warp, warpLoad, alwaysWarp); + msg("\n"); +} + +C64Model +C64::getModel() +{ + // Look for known configurations + for (unsigned i = 0; i < sizeof(configurations) / sizeof(C64Configuration); i++) { + if (vic.getModel() == configurations[i].vic && + vic.emulateGrayDotBug == configurations[i].grayDotBug && + cia1.getModel() == configurations[i].cia && + cia1.getEmulateTimerBBug() == configurations[i].timerBBug && + sid.getModel() == configurations[i].sid && + // sid.getAudioFilter() == configurations[i].sidFilter && + vic.getGlueLogic() == configurations[i].glue && + mem.getRamInitPattern() == configurations[i].pattern) { + return (C64Model)i; + } + } + + // We've got a non-standard configuration + return C64_CUSTOM; +} + +void +C64::setModel(C64Model m) +{ + if (m != C64_CUSTOM) { + + suspend(); + vic.setModel(configurations[m].vic); + vic.emulateGrayDotBug = configurations[m].grayDotBug; + cia1.setModel(configurations[m].cia); + cia2.setModel(configurations[m].cia); + cia1.setEmulateTimerBBug(configurations[m].timerBBug); + cia2.setEmulateTimerBBug(configurations[m].timerBBug); + sid.setModel(configurations[m].sid); + sid.setAudioFilter(configurations[m].sidFilter); + vic.setGlueLogic(configurations[m].glue); + mem.setRamInitPattern(configurations[m].pattern); + resume(); + } +} + +void +C64::updateVicFunctionTable() +{ + // Assign model independent execution functions + vicfunc[0] = NULL; + vicfunc[12] = &VIC::cycle12; + vicfunc[13] = &VIC::cycle13; + vicfunc[14] = &VIC::cycle14; + vicfunc[15] = &VIC::cycle15; + vicfunc[16] = &VIC::cycle16; + vicfunc[17] = &VIC::cycle17; + vicfunc[18] = &VIC::cycle18; + + for (unsigned cycle = 19; cycle <= 54; cycle++) + vicfunc[cycle] = &VIC::cycle19to54; + + vicfunc[56] = &VIC::cycle56; + + // Assign model specific execution functions + switch (vic.getModel()) { + + case PAL_6569_R1: + case PAL_6569_R3: + case PAL_8565: + + vicfunc[1] = &VIC::cycle1pal; + vicfunc[2] = &VIC::cycle2pal; + vicfunc[3] = &VIC::cycle3pal; + vicfunc[4] = &VIC::cycle4pal; + vicfunc[5] = &VIC::cycle5pal; + vicfunc[6] = &VIC::cycle6pal; + vicfunc[7] = &VIC::cycle7pal; + vicfunc[8] = &VIC::cycle8pal; + vicfunc[9] = &VIC::cycle9pal; + vicfunc[10] = &VIC::cycle10pal; + vicfunc[11] = &VIC::cycle11pal; + vicfunc[55] = &VIC::cycle55pal; + vicfunc[57] = &VIC::cycle57pal; + vicfunc[58] = &VIC::cycle58pal; + vicfunc[59] = &VIC::cycle59pal; + vicfunc[60] = &VIC::cycle60pal; + vicfunc[61] = &VIC::cycle61pal; + vicfunc[62] = &VIC::cycle62pal; + vicfunc[63] = &VIC::cycle63pal; + vicfunc[64] = NULL; + vicfunc[65] = NULL; + break; + + case NTSC_6567_R56A: + + vicfunc[1] = &VIC::cycle1pal; + vicfunc[2] = &VIC::cycle2pal; + vicfunc[3] = &VIC::cycle3pal; + vicfunc[4] = &VIC::cycle4pal; + vicfunc[5] = &VIC::cycle5pal; + vicfunc[6] = &VIC::cycle6pal; + vicfunc[7] = &VIC::cycle7pal; + vicfunc[8] = &VIC::cycle8pal; + vicfunc[9] = &VIC::cycle9pal; + vicfunc[10] = &VIC::cycle10pal; + vicfunc[11] = &VIC::cycle11pal; + vicfunc[55] = &VIC::cycle55ntsc; + vicfunc[57] = &VIC::cycle57ntsc; + vicfunc[58] = &VIC::cycle58ntsc; + vicfunc[59] = &VIC::cycle59ntsc; + vicfunc[60] = &VIC::cycle60ntsc; + vicfunc[61] = &VIC::cycle61ntsc; + vicfunc[62] = &VIC::cycle62ntsc; + vicfunc[63] = &VIC::cycle63ntsc; + vicfunc[64] = &VIC::cycle64ntsc; + vicfunc[65] = NULL; + break; + + case NTSC_6567: + case NTSC_8562: + + vicfunc[1] = &VIC::cycle1ntsc; + vicfunc[2] = &VIC::cycle2ntsc; + vicfunc[3] = &VIC::cycle3ntsc; + vicfunc[4] = &VIC::cycle4ntsc; + vicfunc[5] = &VIC::cycle5ntsc; + vicfunc[6] = &VIC::cycle6ntsc; + vicfunc[7] = &VIC::cycle7ntsc; + vicfunc[8] = &VIC::cycle8ntsc; + vicfunc[9] = &VIC::cycle9ntsc; + vicfunc[10] = &VIC::cycle10ntsc; + vicfunc[11] = &VIC::cycle11ntsc; + vicfunc[55] = &VIC::cycle55ntsc; + vicfunc[57] = &VIC::cycle57ntsc; + vicfunc[58] = &VIC::cycle58ntsc; + vicfunc[59] = &VIC::cycle59ntsc; + vicfunc[60] = &VIC::cycle60ntsc; + vicfunc[61] = &VIC::cycle61ntsc; + vicfunc[62] = &VIC::cycle62ntsc; + vicfunc[63] = &VIC::cycle63ntsc; + vicfunc[64] = &VIC::cycle64ntsc; + vicfunc[65] = &VIC::cycle65ntsc; + break; + + default: + assert(false); + } +} + +void +C64::powerUp() +{ + suspend(); + reset(); + resume(); + run(); +} + +void +C64::run() +{ + if (isHalted()) { + + // Check for ROM images + if (!isRunnable()) { + putMessage(MSG_ROM_MISSING); + return; + } + + // Power up sub components + sid.run(); + + // Start execution thread + pthread_create(&p, NULL, runThread, (void *)this); + } +} + +void +C64::halt() +{ + if (isRunning()) { + + // Cancel execution thread + pthread_cancel(p); + // Wait until thread terminates + pthread_join(p, NULL); + // Finish the current command (to reach a clean state) + step(); + } +} + +void +C64::threadCleanup() +{ + p = NULL; + debug(2, "Execution thread cleanup\n"); +} + +bool +C64::isRunnable() +{ + return + mem.basicRomIsLoaded() && + mem.characterRomIsLoaded() && + mem.kernalRomIsLoaded() && + drive1.mem.romIsLoaded() && + drive2.mem.romIsLoaded(); +} + +bool +C64::isRunning() +{ + return p != NULL; +} + +bool +C64::isHalted() +{ + return p == NULL; +} + +void +C64::step() +{ + cpu.clearErrorState(); + drive1.cpu.clearErrorState(); + drive2.cpu.clearErrorState(); + + // Wait until the execution of the next command has begun + while (cpu.inFetchPhase()) executeOneCycle(); + + // Finish the command + while (!cpu.inFetchPhase()) executeOneCycle(); + + // Execute the first microcycle (fetch phase) and stop there + executeOneCycle(); +} + +void +C64::stepOver() +{ + cpu.clearErrorState(); + drive1.cpu.clearErrorState(); + drive2.cpu.clearErrorState(); + + // If the next instruction is a JSR instruction, ... + if (mem.spypeek(cpu.getPC()) == 0x20) { + // set a soft breakpoint at the next memory location. + cpu.setSoftBreakpoint(cpu.getAddressOfNextInstruction()); + run(); + return; + } + + // Otherwise, stepOver behaves like step + step(); +} + +bool +C64::executeOneLine() +{ + if (rasterCycle == 1) + beginRasterLine(); + + int lastCycle = vic.getCyclesPerRasterline(); + for (unsigned i = rasterCycle; i <= lastCycle; i++) { + if (!_executeOneCycle()) { + if (i == lastCycle) + endRasterLine(); + return false; + } + } + endRasterLine(); + return true; +} + +bool +C64::executeOneFrame() +{ + do { + if (!executeOneLine()) + return false; + } while (rasterLine != 0); + return true; +} + +bool +C64::executeOneCycle() +{ + bool isFirstCycle = rasterCycle == 1; + bool isLastCycle = vic.isLastCycleInRasterline(rasterCycle); + + if (isFirstCycle) beginRasterLine(); + bool result = _executeOneCycle(); + if (isLastCycle) endRasterLine(); + + return result; +} + +bool +C64::_executeOneCycle() +{ + uint8_t result = true; + uint64_t cycle = ++cpu.cycle; + + // <---------- o2 low phase ----------->|<- o2 high phase ->| + // | | + // ,-- C64 ------------------------------|-------------------|--, + // | ,-----, ,-----, ,-----, | ,-----, | | + // | | | | | | | | | | | | + // '-->| VIC | --> | CIA | --> | CIA | --|--> | CPU | -------|--' + // | | | 1 | | 2 | | | | | + // '-----' '-----' '-----' | '-----' | + // v + // IEC bus update IEC bus update + // ^ + // | ,--------, | + // | | | | + // ,-- Drive ----------------------------|--> | VC1541 | ----|--, + // | | | | | | + // | | '--------' | | + // '-------------------------------------|-------------------|--' + + // First clock phase (o2 low) + (vic.*vicfunc[rasterCycle])(); + if (cycle >= cia1.wakeUpCycle) cia1.executeOneCycle(); else cia1.idleCounter++; + if (cycle >= cia2.wakeUpCycle) cia2.executeOneCycle(); else cia2.idleCounter++; + if (iec.isDirtyC64Side) iec.updateIecLinesC64Side(); + + // Second clock phase (o2 high) + result &= cpu.executeOneCycle(); + if (drive1.isPoweredOn()) result &= drive1.execute(durationOfOneCycle); + if (drive2.isPoweredOn()) result &= drive2.execute(durationOfOneCycle); + // if (iec.isDirtyDriveSide) iec.updateIecLinesDriveSide(); + datasette.execute(); + + rasterCycle++; + return result; +} + +void +C64::beginRasterLine() +{ + // First cycle of rasterline + if (rasterLine == 0) { + vic.beginFrame(); + } + vic.beginRasterline(rasterLine); +} + +void +C64::endRasterLine() +{ + vic.endRasterline(); + rasterCycle = 1; + rasterLine++; + + if (rasterLine >= vic.getRasterlinesPerFrame()) { + rasterLine = 0; + endFrame(); + } +} + +void +C64::endFrame() +{ + frame++; + vic.endFrame(); + + // Increment time of day clocks every tenth of a second + cia1.incrementTOD(); + cia2.incrementTOD(); + + // Execute remaining SID cycles + sid.executeUntil(cpu.cycle); + + // Execute other components + iec.execute(); + expansionport.execute(); + port1.execute(); + port2.execute(); + + // Update mouse coordinates + mouse.execute(); + + // Take a snapshot once in a while + if (takeAutoSnapshots && autoSnapshotInterval > 0) { + unsigned fps = (unsigned)vic.getFramesPerSecond(); + if (frame % (fps * autoSnapshotInterval) == 0) { + takeAutoSnapshot(); + } + } + + // Count some sheep (zzzzzz) ... + if (!getWarp()) { + synchronizeTiming(); + } +} + +bool +C64::getWarp() +{ + bool newValue = (warpLoad && iec.isBusy()) || alwaysWarp; + + if (newValue != warp) { + warp = newValue; + + /* Warping has the unavoidable drawback that audio playback gets out of + * sync. To cope with this issue, we silence SID during warp mode and + * fade in smoothly after warping has ended. + */ + + if (warp) { + // Quickly fade out SID + sid.rampDown(); + + } else { + // Smoothly fade in SID + sid.rampUp(); + sid.alignWritePtr(); + restartTimer(); + } + + putMessage(warp ? MSG_WARP_ON : MSG_WARP_OFF); + } + + return warp; +} + +void +C64::setAlwaysWarp(bool b) +{ + if (alwaysWarp != b) { + + alwaysWarp = b; + putMessage(b ? MSG_ALWAYS_WARP_ON : MSG_ALWAYS_WARP_OFF); + } +} + +void +C64::setWarpLoad(bool b) +{ + warpLoad = b; +} + +void +C64::restartTimer() +{ + uint64_t kernelNow = mach_absolute_time(); + uint64_t nanoNow = abs_to_nanos(kernelNow); + + nanoTargetTime = nanoNow + vic.getFrameDelay(); +} + +void +C64::synchronizeTiming() +{ + const uint64_t earlyWakeup = 1500000; /* 1.5 milliseconds */ + + // Get current time in nano seconds + uint64_t nanoAbsTime = abs_to_nanos(mach_absolute_time()); + + // Check how long we're supposed to sleep + int64_t timediff = (int64_t)nanoTargetTime - (int64_t)nanoAbsTime; + if (timediff > 200000000 || timediff < -200000000 /* 0.2 sec */) { + + // The emulator seems to be out of sync, so we better reset the + // synchronization timer + + debug(2, "Emulator lost synchronization (%lld). Restarting timer.\n", timediff); + restartTimer(); + } + + // Convert nanoTargetTime into kernel unit + int64_t kernelTargetTime = nanos_to_abs(nanoTargetTime); + + // Sleep and update target timer + // debug(2, "%p Sleeping for %lld\n", this, kernelTargetTime - mach_absolute_time()); + int64_t jitter = sleepUntil(kernelTargetTime, earlyWakeup); + nanoTargetTime += vic.getFrameDelay(); + + // debug(2, "Jitter = %d", jitter); + if (jitter > 1000000000 /* 1 sec */) { + + // The emulator did not keep up with the real time clock. Instead of + // running behind for a long time, we reset the synchronization timer + + debug(2, "Jitter exceeds limit (%lld). Restarting synchronization timer.\n", jitter); + restartTimer(); + } +} + +void C64::loadFromSnapshotUnsafe(Snapshot *snapshot) +{ + uint8_t *ptr; + + if (snapshot && (ptr = snapshot->getData())) { + loadFromBuffer(&ptr); + keyboard.releaseAll(); // Avoid constantly pressed keys + ping(); + } +} + +void +C64::loadFromSnapshotSafe(Snapshot *snapshot) +{ + debug(2, "C64::loadFromSnapshotSafe\n"); + + suspend(); + loadFromSnapshotUnsafe(snapshot); + resume(); +} + +bool +C64::restoreSnapshot(vector &storage, unsigned nr) +{ + Snapshot *snapshot = getSnapshot(storage, nr); + + if (snapshot) { + loadFromSnapshotSafe(snapshot); + return true; + } + + return false; +} + +size_t +C64::numSnapshots(vector &storage) +{ + return storage.size(); +} + +Snapshot * +C64::getSnapshot(vector &storage, unsigned nr) +{ + return nr < storage.size() ? storage.at(nr) : NULL; + +} + +void +C64::takeSnapshot(vector &storage) +{ + // Delete oldest snapshot if capacity limit has been reached + if (storage.size() >= MAX_SNAPSHOTS) { + deleteSnapshot(storage, MAX_SNAPSHOTS - 1); + } + + Snapshot *snapshot = Snapshot::makeWithC64(this); + storage.insert(storage.begin(), snapshot); + putMessage(MSG_SNAPSHOT_TAKEN); +} + +void +C64::deleteSnapshot(vector &storage, unsigned index) +{ + Snapshot *snapshot = getSnapshot(storage, index); + + if (snapshot) { + delete snapshot; + storage.erase(storage.begin() + index); + } +} + +bool +C64::flash(AnyC64File *file) +{ + bool result = true; + + suspend(); + switch (file->type()) { + + case BASIC_ROM_FILE: + file->flash(mem.rom, 0xA000); + break; + + case CHAR_ROM_FILE: + file->flash(mem.rom, 0xD000); + break; + + case KERNAL_ROM_FILE: + file->flash(mem.rom, 0xE000); + break; + + case VC1541_ROM_FILE: + file->flash(drive1.mem.rom); + file->flash(drive2.mem.rom); + break; + + case V64_FILE: + loadFromSnapshotUnsafe((Snapshot *)file); + break; + + default: + assert(false); + result = false; + } + resume(); + return result; +} + +bool +C64::flash(AnyArchive *file, unsigned item) +{ + bool result = true; + + suspend(); + switch (file->type()) { + + case D64_FILE: + case T64_FILE: + case PRG_FILE: + case P00_FILE: + file->selectItem(item); + file->flashItem(mem.ram); + break; + + default: + assert(false); + result = false; + } + resume(); + return result; +} + +bool +C64::loadRom(const char *filename) +{ + bool result; + bool wasRunnable = isRunnable(); + ROMFile *rom = ROMFile::makeWithFile(filename); + + if (!rom) { + warn("Failed to read ROM image file %s\n", filename); + return false; + } + + suspend(); + result = flash(rom); + resume(); + + if (result) { + debug(2, "Loaded ROM image %s.\n", filename); + } else { + debug(2, "Failed to flash ROM image %s.\n", filename); + } + + if (!wasRunnable && isRunnable()) + putMessage(MSG_READY_TO_RUN); + + delete rom; + return result; +} diff --git a/C64/C64.h b/C64/C64.h new file mode 100755 index 00000000..67304cc6 --- /dev/null +++ b/C64/C64.h @@ -0,0 +1,597 @@ +/*! + * @header C64.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _C64_INC +#define _C64_INC + +// Configuration items +#include "Configure.h" + +// Data types and constants +#include "C64_types.h" + +// General +#include "MessageQueue.h" + +// Loading and saving +#include "Snapshot.h" +#include "T64File.h" +#include "D64File.h" +#include "G64File.h" +#include "PRGFile.h" +#include "P00File.h" +#include "ROMFile.h" +#include "TAPFile.h" +#include "CRTFile.h" + +// Sub components +#include "ProcessorPort.h" +#include "ExpansionPort.h" +#include "IEC.h" +#include "Keyboard.h" +#include "ControlPort.h" +#include "Memory.h" +#include "C64Memory.h" +#include "FlashRom.h" +#include "DriveMemory.h" +#include "VIC.h" +#include "SIDBridge.h" +#include "TOD.h" +#include "CIA.h" +#include "CPU.h" + +// Cartridges +#include "Cartridge.h" +#include "CustomCartridges.h" + +// Peripherals +#include "Drive.h" +#include "Datasette.h" +#include "Mouse.h" + + +/*! @class A complete virtual Commodore 64 + * @brief This class is the most prominent one of all. To run the emulator, + * it is sufficient to create a single object of this type. All + * sub-components are created automatically. The public API gives + * you control over the emulator's behaviour such as running and + * pausing the emulation. Please note that most sub-components + * have their own public API. E.g., to query information from VICII, + * you need to invoke a public method on c64->vic. + */ +class C64 : public VirtualComponent { + + public: + + // + // Hardware components + // + + //! @brief The C64's virtual memory (ROM, RAM, and color RAM) + C64Memory mem; + + //! @brief The C64's virtual CPU + CPU cpu = CPU(MOS_6510, &mem); + + //! @brief The C64's processor port + ProcessorPort processorPort; + + //! @brief The C64's Video Interface Controller + VIC vic; + + //! @brief The C64's first Complex Interface Adapter + CIA1 cia1; + + //! @brief The C64's second Complex Interface Adapter + CIA2 cia2; + + //! @brief The C64's Sound Interface Device + SIDBridge sid; + + //! @brief The C64's virtual keyboard + Keyboard keyboard; + + //! @brief The C64's first control port + ControlPort port1 = ControlPort(1); + + //! @brief The C64's second control port + ControlPort port2 = ControlPort(2); + + //! @brief The C64's expansion port (cartdrige slot) + ExpansionPort expansionport; + + //! @brief The C64's serial bus connecting the VC1541 floppy drives + IEC iec; + + //! @brief A VC1541 floppy drive (with device number 8) + VC1541 drive1 = VC1541(1); + + //! @brief A second VC1541 floppy drive (with device number 9) + VC1541 drive2 = VC1541(2); + + //! @brief A Commodore 1530 (C2N) Datasette + Datasette datasette; + + //! @brief An external mouse + Mouse mouse; + + + // + // Frame, rasterline, and rasterline cycle information + // + + //! @brief The total number of frames drawn since power up + uint64_t frame; + + //! @brief The currently drawn rasterline + /*! @details The first rasterline is numbered 0. The number of rasterlines + * drawn in a single frame depends on the selected VICII model. + * @see VIC::getRasterlinesPerFrame() + */ + uint16_t rasterLine; + + /*! @brief The currently executed rasterline cycle + * @details The first rasterline cycle is numbered 1. The number of cycles + * executed in a single rasterline depends on the selected + * VICII model. + * @see VIC::getCyclesPerRasterline() + */ + uint8_t rasterCycle; + + /*! @brief Current CPU frequency + * @details This value is set in setClockFrequency() + */ + uint32_t frequency; + + /*! @brief Duration of a CPU cycle in 1/10 nano seconds + * @details This value is set in setClockFrequency() + */ + uint64_t durationOfOneCycle; + + //! @brief VICII function table. + /*! @details Stores a pointer to the VICII method that is executed + * in a certain rasterline cycle. + * @note vicfunc[0] is a stub. It is never called, because the first + * rasterline cycle is numbered 1. + */ + void (VIC::*vicfunc[66])(void); + + + // + // Execution thread + // + + //! @brief An invocation counter for implementing suspend() / resume() + unsigned suspendCounter = 0; + + /*! @brief The emulators execution thread + * @details The thread is created when the emulator is started and + * destroyed when the emulator is halted. + */ + pthread_t p; + + private: + + /*! @brief System timer information + * @details Used to put the emulation thread to sleep for the proper + * amount of time. + */ + mach_timebase_info_data_t timebase; + + /*! @brief Wake-up time of the synchronization timer in nanoseconds + * @details This value is recomputed each time the emulator thread is + * put to sleep. + */ + uint64_t nanoTargetTime; + + /*! @brief Indicates if c64 is currently running at maximum speed + * (with timing synchronization disabled) + */ + bool warp; + + //! @brief Indicates that we should always run as possible. + bool alwaysWarp; + + /*! @brief Indicates that we should run as fast as possible at least + * during disk operations. + */ + bool warpLoad; + + + // + // Operation modes + // + + /*! @brief Indicates whether C64 is running in ultimax mode. + * @details Ultimax mode can be enabled by external cartridges by pulling + * game line low and keeping exrom line high. In ultimax mode, + * most of the C64's RAM and ROM is invisible. + */ + bool ultimax; + + + // + // Message queue + // + + /*! @brief Message queue. + * @details Used to communicate with the graphical user interface. The + * GUI registers a listener and a callback function to retrieve + * messages. + */ + MessageQueue queue; + + + // + // Snapshot storage + // + + private: + + //! @brief Indicates if snapshots should be taken automatically. + bool takeAutoSnapshots = true; + + /*! @brief Time in seconds between two auto-saved snapshots + * @note This value only takes effect if takeAutoSnapshots equals true. + */ + long autoSnapshotInterval = 3; + + //! @brief Maximum number of stored snapshots + static const size_t MAX_SNAPSHOTS = 32; + + //! @brief Storage for auto-taken snapshots + vector autoSnapshots; + + //! @brief Storage for user-taken snapshots + vector userSnapshots; + + + // + //! @functiongroup Constructing and destructing + // + + public: + + //! @brief Standard constructor + C64(); + + //! @brief Stabdard destructor + ~C64(); + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void ping(); + void setClockFrequency(uint32_t frequency); + void suspend(); + void resume(); + void dump(); + + + // + //! @functiongroup Configuring the emulator + // + + /*! @brief Returns the emulated C64 model + * @return C64_CUSTOM, if the selected sub-components do not match any + * of the supported C64 models. + */ + C64Model getModel(); + + /*! @brief Sets the currently emulated C64 model + * @param model is any C64Model other than C64_CUSTOM. + * @note It it safe to call this function on a running emulator. + */ + void setModel(C64Model m); + + //! @brief Updates the VIC function table + /*! @details This function is invoked by VIC::setModel(), only. + */ + void updateVicFunctionTable(); + + + // + //! @functiongroup Accessing the message queue + // + + //! @brief Registers a listener callback function + void addListener(const void *sender, void(*func)(const void *, int, long) ) { + queue.addListener(sender, func); + } + + //! @brief Removes a listener callback function + void removeListener(const void *sender) { + queue.removeListener(sender); + } + + //! @brief Gets a notification message from message queue + Message getMessage() { return queue.getMessage(); } + + //! @brief Feeds a notification message into message queue + void putMessage(MessageType msg, uint64_t data = 0) { queue.putMessage(msg, data); } + + + // + //! @functiongroup Running the emulator + // + + /*! @brief Cold starts the virtual C64. + * @details The emulator and all of its sub components are reset and + * the execution thread is started. + * @note It it safe to call this function on a running emulator. + */ + void powerUp(); + + /*! @brief Starts the execution thread. + * @details This method launches the execution thread and is usually + * called after emulation was stopped by a call to halt() or by + * reaching a breakpoint. Calling this functions has no effect, + * if the emulator is currently running. + */ + void run(); + + /*! @brief Stops the emulation execution thread. + * @details The execution thread is canceled, but the internal state + * remains intact. Emulation can be continued by a call to run(). + * Calling this functions has no effect, if the emulator is + * not running. + */ + void halt(); + + /*! @brief The tread exit function. + * @details This method is invoked automatically when the emulator thread + * terminates. + */ + void threadCleanup(); + + //! @brief Returns true iff the virtual C64 is able to run. + /*! @details The emulator needs all four Roms to run. Hence, this method + * returns true if and only if all four Roms are installed. + * @see loadRom() + */ + bool isRunnable(); + + //! @brief Returns true if the emulator is running. + bool isRunning(); + + //! @brief Returns true if the emulator is not running. + bool isHalted(); + + /*! @brief Executes a single instruction. + * @details This method implements the debugger's 'step' action. + */ + void step(); + + /*! @brief Executes until the instruction is reached + * @details This method implements the debugger's 'step over' action. + */ + void stepOver(); + + /*! @brief Executes until the end of the current rasterline is reached. + * @details This method can be called even if a certain portion of the + * current rasterline has already been processed. + */ + bool executeOneLine(); + + /*! @brief Executes until the end of the current frame is reached. + * @details This method can be called even if a certain portion of the + * current frame has already been processed. + */ + bool executeOneFrame(); + + private: + + //! @brief Executes a single CPU cycle + bool executeOneCycle(); + + //! @brief Work horse for executeOneCycle() + bool _executeOneCycle(); + + //! @brief Invoked before executing the first cycle of a rasterline + void beginRasterLine(); + + //! @brief Invoked after executing the last cycle of a rasterline + void endRasterLine(); + + //! @brief Invoked after executing the last rasterline of a frame + void endFrame(); + + + // + //! @functiongroup Managing the execution thread + // + + private: + + //! @brief Converts kernel time to nanoseconds. + uint64_t abs_to_nanos(uint64_t abs) { return abs * timebase.numer / timebase.denom; } + + //! @brief Converts nanoseconds to kernel time. + uint64_t nanos_to_abs(uint64_t nanos) { return nanos * timebase.denom / timebase.numer; } + + public: + + //! @brief Updates variable warp and returns the new value. + /*! @details As a side effect, messages are sent to the GUI if the + * variable has changed its value. + */ + bool getWarp(); + + //! @brief Returns if the emulator should always run full speed. + bool getAlwaysWarp() { return alwaysWarp; } + + //! @brief Setter for alwaysWarp + void setAlwaysWarp(bool b); + + //! @brief Returns if warp mode should be activated during disk access. + bool getWarpLoad() { return warpLoad; } + + //! @brief Setter for warpLoad + void setWarpLoad(bool b); + + /*! @brief Restarts the synchronization timer. + * @details The function is invoked at launch time to initialize the timer + * and reinvoked when the synchronization timer gets out of sync. + */ + void restartTimer(); + + private: + + /*! @brief Puts the emulation the thread to sleep for a while. + * @details This function is called inside endFrame(). It makes the + * emulation thread wait until nanoTargetTime has been reached. + * Before returning, nanoTargetTime is assigned with a new target + * value. + */ + void synchronizeTiming(); + + + // + //! @functiongroup Handling snapshots + // + + public: + + //! @brief Indicates if the auto-snapshot feature is enabled. + bool getTakeAutoSnapshots() { return takeAutoSnapshots; } + + //! @brief Enables or disabled the auto-snapshot feature. + void setTakeAutoSnapshots(bool enable) { takeAutoSnapshots = enable; } + + /*! @brief Disables the auto-snapshot feature temporarily. + * @details This method is called when the snaphshot browser opens. + */ + void suspendAutoSnapshots() { autoSnapshotInterval -= (LONG_MAX / 2); } + + /*! @brief Heal a call to suspendAutoSnapshots() + * @details This method is called when the snaphshot browser closes. + */ + void resumeAutoSnapshots() { autoSnapshotInterval += (LONG_MAX / 2); } + + //! @brief Returns the time between two auto-snapshots in seconds. + long getSnapshotInterval() { return autoSnapshotInterval; } + + //! @brief Sets the time between two auto-snapshots in seconds. + void setSnapshotInterval(long value) { autoSnapshotInterval = value; } + + /*! @brief Loads the current state from a snapshot file + * @note There is an thread-unsafe and thread-safe version of this + * function. The first one can be unsed inside the emulator + * thread or from outside if the emulator is halted. The second + * one can be called any time. + */ + void loadFromSnapshotUnsafe(Snapshot *snapshot); + void loadFromSnapshotSafe(Snapshot *snapshot); + + //! @brief Restores a certain snapshot from the snapshot storage + bool restoreSnapshot(vector &storage, unsigned nr); + bool restoreAutoSnapshot(unsigned nr) { return restoreSnapshot(autoSnapshots, nr); } + bool restoreUserSnapshot(unsigned nr) { return restoreSnapshot(userSnapshots, nr); } + + //! @brief Restores the latest snapshot from the snapshot storage + bool restoreLatestAutoSnapshot() { return restoreAutoSnapshot(0); } + bool restoreLatestUserSnapshot() { return restoreUserSnapshot(0); } + + //! @brief Returns the number of stored snapshots + size_t numSnapshots(vector &storage); + size_t numAutoSnapshots() { return numSnapshots(autoSnapshots); } + size_t numUserSnapshots() { return numSnapshots(userSnapshots); } + + //! @brief Returns an snapshot from the snapshot storage + Snapshot *getSnapshot(vector &storage, unsigned nr); + Snapshot *autoSnapshot(unsigned nr) { return getSnapshot(autoSnapshots, nr); } + Snapshot *userSnapshot(unsigned nr) { return getSnapshot(userSnapshots, nr); } + + /*! @brief Takes a snapshot and inserts it into the snapshot storage + * @details The new snapshot is inserted at position 0 and all others are + * moved one position up. If the buffer is full, the oldest + * snapshot is deleted. + * @note Make sure to call the 'Safe' version outside the emulator + * thread. + */ + void takeSnapshot(vector &storage); + void takeAutoSnapshot() { takeSnapshot(autoSnapshots); } + void takeUserSnapshot() { takeSnapshot(userSnapshots); } + void takeAutoSnapshotSafe() { suspend(); takeSnapshot(autoSnapshots); resume(); } + void takeUserSnapshotSafe() { suspend(); takeSnapshot(userSnapshots); resume(); } + + /*! @brief Deletes a snapshot from the snapshot storage + * @details All remaining snapshots are moved one position down. + */ + void deleteSnapshot(vector &storage, unsigned nr); + void deleteAutoSnapshot(unsigned nr) { deleteSnapshot(autoSnapshots, nr); } + void deleteUserSnapshot(unsigned nr) { deleteSnapshot(userSnapshots, nr); } + + + // + //! @functiongroup Handling Roms + // + + //! @brief Loads a ROM image into memory + bool loadRom(const char *filename); + + + // + //! @functiongroup Flashing files + // + + //! @brief Flashes a single file into memory + bool flash(AnyC64File *file); + + //! @brief Flashes a single item of an archive into memory + bool flash(AnyArchive *file, unsigned item); + + + // + //! @functiongroup Set and query ultimax mode + // + + public: + + //! @brief Returns the ultimax flag + bool getUltimax() { return ultimax; } + + /*! @brief Setter for ultimax + * @details This method is called in function updatePeekPokeLookupTables() + * if a certain game / exrom line combination is provided. + */ + void setUltimax(bool b) { ultimax = b; } + + + // + //! @functiongroup Debugging + // + + /*! @brief Returns true if the executable was compiled for development + * @details In release mode, assertion checking should be switched off + */ + inline bool developmentMode() { +#ifndef NDEBUG + return true; +#endif + return false; + } +}; + +#endif + diff --git a/C64/C64_types.h b/C64/C64_types.h new file mode 100755 index 00000000..f5ed8fed --- /dev/null +++ b/C64/C64_types.h @@ -0,0 +1,161 @@ +// Created by Dirk Hoffmann + +#ifndef C64_TYPES_H +#define C64_TYPES_H + +#include "CPU_types.h" +#include "Memory_types.h" +#include "TOD_types.h" +#include "CIA_types.h" +#include "VIC_types.h" +#include "SID_types.h" +#include "ControlPort_types.h" +#include "ExpansionPort_types.h" +#include "Cartridge_types.h" +#include "Drive_types.h" +#include "Disk_types.h" +#include "Mouse_types.h" +#include "File_types.h" + +//! @brief C64 model +typedef enum { + C64_PAL, + C64_II_PAL, + C64_OLD_PAL, + C64_NTSC, + C64_II_NTSC, + C64_OLD_NTSC, + C64_CUSTOM +} C64Model; + +inline bool isC64Model(C64Model model) { + return model >= C64_PAL && model <= C64_OLD_NTSC; +} + +//! @brief C64 configuration +typedef struct { + VICModel vic; + bool grayDotBug; + CIAModel cia; + bool timerBBug; + SIDModel sid; + bool sidFilter; + GlueLogic glue; + RamInitPattern pattern; +} C64Configuration; + +//! @brief Configurations of standard C64 models +/*! @note sidFilter should be true for all known configurations. We have + * disabled them by default, because the filter emulation is broken + * in the currently used reSID code. Once the reSID bug has been + * fixed, it should be set to true again. + */ +static const C64Configuration configurations[] = { + + // C64 PAL + { PAL_6569_R3, false, MOS_6526, true, MOS_6581, false, GLUE_DISCRETE, INIT_PATTERN_C64 }, + + // C64_II_PAL + { PAL_8565, true, MOS_8521, false, MOS_8580, false, GLUE_CUSTOM_IC, INIT_PATTERN_C64C }, + + // C64_OLD_PAL + { PAL_6569_R1, false, MOS_6526, true, MOS_6581, false, GLUE_DISCRETE, INIT_PATTERN_C64 }, + + // C64_NTSC + { NTSC_6567, false, MOS_6526, false, MOS_6581, false, GLUE_DISCRETE, INIT_PATTERN_C64 }, + + // C64_II_NTSC + { NTSC_8562, true, MOS_8521, true, MOS_8580, false, GLUE_CUSTOM_IC, INIT_PATTERN_C64C }, + + // C64_OLD_NTSC + { NTSC_6567_R56A, false, MOS_6526, false, MOS_6581, false, GLUE_DISCRETE, INIT_PATTERN_C64 } +}; + +/*! @brief Message types + * @details List of all possible message id's + */ +typedef enum { + + MSG_NONE = 0, + + // Running the emulator + MSG_READY_TO_RUN, + MSG_RUN, + MSG_HALT, + + // ROM and snapshot handling + MSG_BASIC_ROM_LOADED, + MSG_CHAR_ROM_LOADED, + MSG_KERNAL_ROM_LOADED, + MSG_VC1541_ROM_LOADED, + MSG_ROM_MISSING, + MSG_SNAPSHOT_TAKEN, + + // CPU related messages + MSG_CPU_OK, + MSG_CPU_SOFT_BREAKPOINT_REACHED, + MSG_CPU_HARD_BREAKPOINT_REACHED, + MSG_CPU_ILLEGAL_INSTRUCTION, + MSG_WARP_ON, + MSG_WARP_OFF, + MSG_ALWAYS_WARP_ON, + MSG_ALWAYS_WARP_OFF, + + // VIC related messages + MSG_PAL, + MSG_NTSC, + + // IEC Bus + MSG_IEC_BUS_BUSY, + MSG_IEC_BUS_IDLE, + + // Keyboard + MSG_KEYMATRIX, + MSG_CHARSET, + + // Peripherals (Disk drive) + MSG_VC1541_ATTACHED, + MSG_VC1541_ATTACHED_SOUND, + MSG_VC1541_DETACHED, + MSG_VC1541_DETACHED_SOUND, + MSG_VC1541_DISK, + MSG_VC1541_DISK_SOUND, + MSG_VC1541_NO_DISK, + MSG_VC1541_NO_DISK_SOUND, + MSG_VC1541_RED_LED_ON, + MSG_VC1541_RED_LED_OFF, + MSG_VC1541_MOTOR_ON, + MSG_VC1541_MOTOR_OFF, + MSG_VC1541_HEAD_UP, + MSG_VC1541_HEAD_UP_SOUND, + MSG_VC1541_HEAD_DOWN, + MSG_VC1541_HEAD_DOWN_SOUND, + + // Peripherals (Disk) + MSG_DISK_SAVED, + MSG_DISK_UNSAVED, + + // Peripherals (Datasette) + MSG_VC1530_TAPE, + MSG_VC1530_NO_TAPE, + MSG_VC1530_PROGRESS, + + // Peripherals (Expansion port) + MSG_CARTRIDGE, + MSG_NO_CARTRIDGE, + MSG_CART_SWITCH + +} MessageType; + +/*! @brief Message + * @details Each message consists of type and a data field. + */ +typedef struct { + MessageType type; + long data; +} Message; + +//! @brief Callback function signature +typedef void Callback(const void *, int, long); + +#endif diff --git a/C64/CIA/CIA.cpp b/C64/CIA/CIA.cpp new file mode 100755 index 00000000..1c322115 --- /dev/null +++ b/C64/CIA/CIA.cpp @@ -0,0 +1,1420 @@ +/* + * (C) 2006 - 2018 Dirk W. Hoffmann. All rights reserved. + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +CIA::CIA() +{ + setDescription("CIA"); + + // Register sub components + VirtualComponent *subcomponents[] = { &tod, NULL }; + registerSubComponents(subcomponents, sizeof(subcomponents)); + + // Register snapshot items + SnapshotItem items[] = { + + { &model, sizeof(model), KEEP_ON_RESET }, + { &emulateTimerBBug, sizeof(emulateTimerBBug), KEEP_ON_RESET }, + + { &counterA, sizeof(counterA), CLEAR_ON_RESET }, + { &latchA, sizeof(latchA), CLEAR_ON_RESET }, + { &counterB, sizeof(counterB), CLEAR_ON_RESET }, + { &latchB, sizeof(latchB), CLEAR_ON_RESET }, + { &delay, sizeof(delay), CLEAR_ON_RESET }, + { &feed, sizeof(feed), CLEAR_ON_RESET }, + { &CRA, sizeof(CRA), CLEAR_ON_RESET }, + { &CRB, sizeof(CRB), CLEAR_ON_RESET }, + { &icr, sizeof(icr), CLEAR_ON_RESET }, + { &icrAck, sizeof(icrAck), CLEAR_ON_RESET }, + { &imr, sizeof(imr), CLEAR_ON_RESET }, + { &PB67TimerMode, sizeof(PB67TimerMode), CLEAR_ON_RESET }, + { &PB67TimerOut, sizeof(PB67TimerOut), CLEAR_ON_RESET }, + { &PB67Toggle, sizeof(PB67Toggle), CLEAR_ON_RESET }, + { &PRA, sizeof(PRA), CLEAR_ON_RESET }, + { &PRB, sizeof(PRB), CLEAR_ON_RESET }, + { &DDRA, sizeof(DDRA), CLEAR_ON_RESET }, + { &DDRB, sizeof(DDRB), CLEAR_ON_RESET }, + { &PA, sizeof(PA), CLEAR_ON_RESET }, + { &PB, sizeof(PB), CLEAR_ON_RESET }, + { &SDR, sizeof(SDR), CLEAR_ON_RESET }, + { &serClk, sizeof(serClk), CLEAR_ON_RESET }, + { &serCounter, sizeof(serCounter), CLEAR_ON_RESET }, + { &CNT, sizeof(CNT), CLEAR_ON_RESET }, + { &INT, sizeof(INT), CLEAR_ON_RESET }, + { &tiredness, sizeof(tiredness), CLEAR_ON_RESET }, + { &wakeUpCycle, sizeof(wakeUpCycle), CLEAR_ON_RESET }, + { &idleCounter, sizeof(idleCounter), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); + + model = MOS_6526; + emulateTimerBBug = true; +} + +CIA::~CIA() +{ +} + +void +CIA::reset() +{ + VirtualComponent::reset(); + + CNT = true; + INT = 1; + + latchA = 0xFFFF; + latchB = 0xFFFF; +} + +void +CIA::setModel(CIAModel m) +{ + debug(2, "setModel(%d)\n", m); + + if (!isCIAModel(m)) { + warn("Unknown CIA model (%d). Assuming first generation.\n", m); + m = MOS_6526; + } + + suspend(); + model = m; + resume(); +} + +void +CIA::triggerRisingEdgeOnFlagPin() +{ + // ICR &= ~0x10; // Note: FLAG pin is inverted +} + +void +CIA::triggerFallingEdgeOnFlagPin() +{ + // TODO: CLEAN THIS UP (USE CORRECT TIMING Interrupt0 etc.) + icr |= 0x10; // Note: FLAG pin is inverted + + // Trigger interrupt, if enabled + if (imr & 0x10) { + INT = 0; + icr |= 0x80; + pullDownInterruptLine(); + } +} + +void +CIA::triggerTimerIrq() +{ + switch (model) { + + case MOS_6526: + delay |= CIASetInt0; + delay |= CIASetIcr0; + return; + + case MOS_8521: + // Test cases: (?) + // testprogs\interrupts\irqnmi\cia-int-irq-new.prg + // testprogs\interrupts\irqnmi\cia-int-nmi-new.prg + delay |= (delay & CIAReadIcr0) ? CIASetInt0 : CIASetInt1; + delay |= (delay & CIAReadIcr0) ? CIASetIcr0 : CIASetIcr1; + return; + + default: + assert(false); + } +} + +void +CIA::triggerTodIrq() +{ + delay |= CIASetInt0; + delay |= CIASetIcr0; +} + +void +CIA::triggerSerialIrq() +{ + delay |= CIASetInt0; + delay |= CIASetIcr0; +} + +uint8_t +CIA::peek(uint16_t addr) +{ + uint8_t result; + + wakeUp(); + + assert(addr <= 0x000F); + switch(addr) { + + case 0x00: // CIA_DATA_PORT_A + { + updatePA(); + return PA; + } + + case 0x01: // CIA_DATA_PORT_B + { + updatePB(); + return PB; + } + case 0x02: // CIA_DATA_DIRECTION_A + + result = DDRA; + break; + + case 0x03: // CIA_DATA_DIRECTION_B + + result = DDRB; + break; + + case 0x04: // CIA_TIMER_A_LOW + + result = LO_BYTE(counterA); + break; + + case 0x05: // CIA_TIMER_A_HIGH + result = HI_BYTE(counterA); + break; + + case 0x06: // CIA_TIMER_B_LOW + + result = LO_BYTE(counterB); + break; + + case 0x07: // CIA_TIMER_B_HIGH + + result = HI_BYTE(counterB); + break; + + case 0x08: // CIA_TIME_OF_DAY_SEC_FRAC + + result = tod.getTodTenth(); + tod.defreeze(); + break; + + case 0x09: // CIA_TIME_OF_DAY_SECONDS + + result = tod.getTodSeconds(); + break; + + case 0x0A: // CIA_TIME_OF_DAY_MINUTES + + result = tod.getTodMinutes(); + break; + + case 0x0B: // CIA_TIME_OF_DAY_HOURS + + tod.freeze(); + result = tod.getTodHours(); + break; + + case 0x0C: // CIA_SERIAL_DATA_REGISTER + + result = SDR; + break; + + case 0x0D: // CIA_INTERRUPT_CONTROL + + // For new CIAs, set upper bit if an IRQ is being triggered + if ((delay & CIASetInt1) && (icr & 0x1F) && model == MOS_8521) { + icr |= 0x80; + } + + // Remember result + result = icr; + + // Release interrupt request + if (INT == 0) { + delay |= CIAClearInt0; + } + + // Discard pending interrupts + delay &= ~(CIASetInt0 | CIASetInt1); + + // Schedule the ICR bits to be cleared + if (model == MOS_8521) { + delay |= CIAClearIcr0; // Uppermost bit + delay |= CIAAckIcr0; // Other bits + icrAck = icr; + } else { + delay |= CIAClearIcr0; // Uppermost bit + icr &= 0x80; // Other bits + } + + // Remember the read access + delay |= CIAReadIcr0; + break; + + case 0x0E: // CIA_CONTROL_REG_A + + result = (uint8_t)(CRA & ~0x10); // Bit 4 is always 0 when read + break; + + case 0x0F: // CIA_CONTROL_REG_B + + result = (uint8_t)(CRB & ~0x10); // Bit 4 is always 0 when read + break; + + default: + result = 0; + panic("Unknown CIA address %04X\n", addr); + break; + } + + return result; +} + +uint8_t +CIA::spypeek(uint16_t addr) +{ + bool running; + + assert(addr <= 0x000F); + switch(addr) { + + case 0x00: // CIA_DATA_PORT_A + return PA; + + case 0x01: // CIA_DATA_PORT_B + return PB; + + case 0x02: // CIA_DATA_DIRECTION_A + return DDRA; + + case 0x03: // CIA_DATA_DIRECTION_B + return DDRB; + + case 0x04: // CIA_TIMER_A_LOW + running = delay & CIACountA3; + return LO_BYTE(counterA - (running ? (uint16_t)idleCounter : 0)); + + case 0x05: // CIA_TIMER_A_HIGH + running = delay & CIACountA3; + return HI_BYTE(counterA - (running ? (uint16_t)idleCounter : 0)); + + case 0x06: // CIA_TIMER_B_LOW + running = delay & CIACountB3; + return LO_BYTE(counterB - (running ? (uint16_t)idleCounter : 0)); + + case 0x07: // CIA_TIMER_B_HIGH + running = delay & CIACountB3; + return HI_BYTE(counterB - (running ? (uint16_t)idleCounter : 0)); + + case 0x08: // CIA_TIME_OF_DAY_SEC_FRAC + return tod.getTodTenth(); + + case 0x09: // CIA_TIME_OF_DAY_SECONDS + return tod.getTodSeconds(); + + case 0x0A: // CIA_TIME_OF_DAY_MINUTES + return tod.getTodMinutes(); + + case 0x0B: // CIA_TIME_OF_DAY_HOURS + return tod.getTodHours(); + + case 0x0C: // CIA_SERIAL_DATA_REGISTER + return SDR; + + case 0x0D: // CIA_INTERRUPT_CONTROL + return icr; + + case 0x0E: // CIA_CONTROL_REG_A + return CRA & ~0x10; + + case 0x0F: // CIA_CONTROL_REG_B + return CRB & ~0x10; + + default: + assert(0); + return 0; + } +} + +void +CIA::poke(uint16_t addr, uint8_t value) +{ + wakeUp(); + + switch(addr) { + + case 0x00: // CIA_DATA_PORT_A + + pokePA(value); + // PRA = value; + // updatePA(); + return; + + case 0x01: // CIA_DATA_PORT_B + + PRB = value; + updatePB(); + return; + + case 0x02: // CIA_DATA_DIRECTION_A + + pokeDDRA(value); + // DDRA = value; + // updatePA(); + return; + + case 0x03: // CIA_DATA_DIRECTION_B + + DDRB = value; + updatePB(); + return; + + case 0x04: // CIA_TIMER_A_LOW + + latchA = (latchA & 0xFF00) | value; + if (delay & CIALoadA2) { + counterA = (counterA & 0xFF00) | value; + } + return; + + case 0x05: // CIA_TIMER_A_HIGH + + latchA = (latchA & 0x00FF) | (value << 8); + if (delay & CIALoadA2) { + counterA = (counterA & 0x00FF) | (value << 8); + } + + // Load counter if timer is stopped + if (!(CRA & 0x01)) { + delay |= CIALoadA0; + } + return; + + case 0x06: // CIA_TIMER_B_LOW + + latchB = (latchB & 0xFF00) | value; + if (delay & CIALoadB2) { + counterB = (counterB & 0xFF00) | value; + } + return; + + case 0x07: // CIA_TIMER_B_HIGH + + latchB = (latchB & 0x00FF) | (value << 8); + if (delay & CIALoadB2) { + counterB = (counterB & 0x00FF) | (value << 8); + } + + // Load counter if timer is stopped + if ((CRB & 0x01) == 0) { + delay |= CIALoadB0; + } + return; + + case 0x08: // CIA_TIME_OF_DAY_SEC_FRAC + + if (CRB & 0x80) { + tod.setAlarmTenth(value); + } else { + tod.setTodTenth(value); + tod.cont(); + } + return; + + case 0x09: // CIA_TIME_OF_DAY_SECONDS + + if (CRB & 0x80) { + tod.setAlarmSeconds(value); + } else { + tod.setTodSeconds(value); + } + return; + + case 0x0A: // CIA_TIME_OF_DAY_MINUTES + + if (CRB & 0x80) { + tod.setAlarmMinutes(value); + } else { + tod.setTodMinutes(value); + } + return; + + case 0x0B: // CIA_TIME_OF_DAY_HOURS + + if (CRB & 0x80) { + tod.setAlarmHours(value); + } else { + // Writing 12 pm into hour register turns to 12 am and vice versa. + if ((value & 0x1F) == 0x12) + value ^= 0x80; + tod.setTodHours(value); + tod.stop(); + } + return; + + case 0x0C: // CIA_DATA_REGISTER + + SDR = value; + delay |= CIASerLoad0; + feed |= CIASerLoad0; + // delay &= ~SerLoad1; + return; + + case 0x0D: // CIA_INTERRUPT_CONTROL + + // Bit 7 means set (1) or clear (0) the other bits + if ((value & 0x80) != 0) { + imr |= (value & 0x1F); + } else { + imr &= ~(value & 0x1F); + } + + // Raise an interrupt in the next cycle if conditions match + if ((imr & icr & 0x1F) && INT) { + if (model == MOS_8521) { + if (!(delay & CIAReadIcr1)) { + delay |= (CIASetInt1 | CIASetIcr1); + } + } else { + delay |= (CIASetInt0 | CIASetIcr0); + } + } + + // Clear pending interrupt if a write has occurred in the previous cycle + // Solution is taken from Hoxs64. It fixes dd0dtest (11) + else if (delay & CIAClearIcr2) { + if (model == MOS_6526) { + delay &= ~(CIASetInt1 | CIASetIcr1); + } + } + + return; + + case 0x0E: // CIA_CONTROL_REG_A + + // -------0 : Stop timer + // -------1 : Start timer + if (value & 0x01) { + delay |= CIACountA1 | CIACountA0; + feed |= CIACountA0; + if (!(CRA & 0x01)) + PB67Toggle |= 0x40; // Toggle is high on start + } else { + delay &= ~(CIACountA1 | CIACountA0); + feed &= ~CIACountA0; + } + + // ------0- : Don't indicate timer underflow on port B + // ------1- : Indicate timer underflow on port B bit 6 + if (value & 0x02) { + PB67TimerMode |= 0x40; + if (!(value & 0x04)) { + if ((delay & CIAPB7Low1) == 0) { + PB67TimerOut &= ~0x40; + } else { + PB67TimerOut |= 0x40; + } + } else { + PB67TimerOut = (PB67TimerOut & ~0x40) | (PB67Toggle & 0x40); + } + } else { + PB67TimerMode &= ~0x40; + } + + // -----0-- : Upon timer underflow, invert port B bit 6 + // -----1-- : Upon timer underflow, generate a positive edge + // on port B bit 6 for one cycle + + // ----0--- : Timer restarts upon underflow + // ----1--- : Timer stops upon underflow (One shot mode) + if (value & 0x08) { + feed |= CIAOneShotA0; + } else { + feed &= ~CIAOneShotA0; + } + + // ---0---- : Nothing to do + // ---1---- : Load start value into timer + if (value & 0x10) { + delay |= CIALoadA0; + } + + // --0----- : Timer counts system cycles + // --1----- : Timer counts positive edges on CNT pin + if (value & 0x20) { + delay &= ~(CIACountA1 | CIACountA0); + feed &= ~CIACountA0; + } + + // -0------ : Serial shift register in input mode (read) + // -1------ : Serial shift register in output mode (write) + if ((value ^ CRA) & 0x40) + { + //serial direction changing + delay &= ~(CIASerLoad0 | CIASerLoad1); + feed &= ~CIASerLoad0; + serCounter = 0; + + delay &= ~(CIASerClk0 | CIASerClk1 | CIASerClk2); + feed &= ~CIASerClk0; + } + + // 0------- : TOD speed = 60 Hz + // 1------- : TOD speed = 50 Hz + // TODO: We need to react on a change of this bit + tod.setHz((value & 0x80) ? 5 /* 50 Hz */ : 6 /* 60 Hz */); + + updatePB(); // Because PB67timerMode and PB6TimerOut may have changed + CRA = value; + + return; + + case 0x0F: // CIA_CONTROL_REG_B + { + // -------0 : Stop timer + // -------1 : Start timer + if (value & 0x01) { + delay |= CIACountB1 | CIACountB0; + feed |= CIACountB0; + if (!(CRB & 0x01)) + PB67Toggle |= 0x80; // Toggle is high on start + } else { + delay &= ~(CIACountB1 | CIACountB0); + feed &= ~CIACountB0; + } + + // ------0- : Don't indicate timer underflow on port B + // ------1- : Indicate timer underflow on port B bit 7 + if (value & 0x02) { + PB67TimerMode |= 0x80; + if ((value & 0x04) == 0) { + if ((delay & CIAPB7Low1) == 0) { + PB67TimerOut &= ~0x80; + } else { + PB67TimerOut |= 0x80; + } + } else { + PB67TimerOut = (PB67TimerOut & ~0x80) | (PB67Toggle & 0x80); + } + } else { + PB67TimerMode &= ~0x80; + } + + // -----0-- : Upon timer underflow, invert port B bit 7 + // -----1-- : Upon timer underflow, generate a positive edge + // on port B bit 7 for one cycle + + // ----0--- : Timer restarts upon underflow + // ----1--- : Timer stops upon underflow (One shot mode) + if (value & 0x08) { + feed |= CIAOneShotB0; + } else { + feed &= ~CIAOneShotB0; + } + + // ---0---- : Nothing to do + // ---1---- : Load start value into timer + if (value & 0x10) { + delay |= CIALoadB0; + } + + // -00----- : Timer counts system cycles + // -01----- : Timer counts positive edges on CNT pin + // -10----- : Timer counts underflows of timer A + // -11----- : Timer counts underflows of timer A occurring along with a + // positive edge on CNT pin + if (value & 0x60) { + delay &= ~(CIACountB1 | CIACountB0); + feed &= ~CIACountB0; + } + + // 0------- : Writing into TOD registers sets TOD + // 1------- : Writing into TOD registers sets alarm time + + updatePB(); // Because PB67timerMode and PB6TimerOut may have changed + CRB = value; + + return; + } + + default: + panic("PANIC: Unknown CIA address (poke) %04X\n", addr); + } +} + +void +CIA::incrementTOD() +{ + wakeUp(); + tod.increment(); +} + +void +CIA::todInterrupt() +{ + delay |= CIATODInt0; +} + +void +CIA::dumpTrace() +{ + const char *indent = " "; // "; + + /* + if (!tracingEnabled()) + return; + */ + + debug(1, "%sICR: %02X IMR: %02X ", indent, icr, imr); + debug(1, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", + delay & CIACountA0 ? "CntA0 " : "", + delay & CIACountA1 ? "CntA1 " : "", + delay & CIACountA2 ? "CntA2 " : "", + delay & CIACountA3 ? "CntA3 " : "", + delay & CIACountB0 ? "CntB0 " : "", + delay & CIACountB1 ? "CntB1 " : "", + delay & CIACountB2 ? "CntB2 " : "", + delay & CIACountB3 ? "CntB3 " : "", + delay & CIALoadA0 ? "LdA0 " : "", + delay & CIALoadA1 ? "LdA1 " : "", + delay & CIALoadA2 ? "LdA2 " : "", + delay & CIALoadB0 ? "LdB0 " : "", + delay & CIALoadB1 ? "LdB1 " : "", + delay & CIALoadB1 ? "LdB2 " : "", + delay & CIAPB6Low0 ? "PB6Lo0 " : "", + delay & CIAPB6Low1 ? "PB6Lo1 " : "", + delay & CIAPB7Low0 ? "PB7Lo0 " : "", + delay & CIAPB7Low1 ? "PB7Lo1 " : "", + delay & CIASetInt0 ? "Int0 " : "", + delay & CIASetInt1 ? "Int1 " : "", + delay & CIAOneShotA0 ? "1ShotA0 " : "", + delay & CIAOneShotB0 ? "1ShotB0 " : ""); + + debug(1, "%sA: %04X (%04X) PA: %02X (%02X) DDRA: %02X CRA: %02X\n", + indent, counterA, latchA, PA, PRA, DDRA, CRA); + debug(1, "%sB: %04X (%04X) PB: %02X (%02X) DDRB: %02X CRB: %02X\n", + indent, counterB, latchB, PB, PRB, DDRB, CRB); +} + +void +CIA::dump() +{ + CIAInfo info = getInfo(); + + msg(" Counter A : %04X\n", info.timerA.count); + msg(" Latch A : %04X\n", info.timerA.latch); + msg(" Data port A : %02X\n", info.portA.reg); + msg(" Data port direction A : %02X\n", info.portA.dir); + msg(" Control register A : %02X\n", CRA); + msg("\n"); + msg(" Counter B : %04X\n", info.timerB.count); + msg(" Latch B : %04X\n", info.timerB.latch); + msg(" Data port B : %02X\n", info.portB.reg); + msg(" Data port direction B : %02X\n", info.portB.dir); + msg(" Control register B : %02X\n", CRB); + msg("\n"); + msg(" Interrupt control reg : %02X\n", info.icr); + msg(" Interrupt mask reg : %02X\n", info.imr); + msg("\n"); + tod.dump(); +} + +CIAInfo +CIA::getInfo() +{ + CIAInfo info; + + info.portA.port = PA; + info.portA.reg = PRA; + info.portA.dir = DDRA; + + info.portB.port = PB; + info.portB.reg = PRB; + info.portB.dir = DDRB; + + info.timerA.count = LO_HI(spypeek(0x04), spypeek(0x05)); + info.timerA.latch = latchA; + info.timerA.running = (delay & CIACountA3); + info.timerA.toggle = CRA & 0x04; + info.timerA.pbout = CRA & 0x02; + info.timerA.oneShot = CRA & 0x08; + + info.timerB.count = LO_HI(spypeek(0x06), spypeek(0x07)); + info.timerB.latch = latchB; + info.timerB.running = (delay & CIACountB3); + info.timerB.toggle = CRB & 0x04; + info.timerB.pbout = CRB & 0x02; + info.timerB.oneShot = CRB & 0x08; + + info.icr = icr; + info.imr = imr; + info.intLine = INT; + + info.tod = tod.getInfo(); + info.todIntEnable = icr & 0x04; + + return info; +} + +void +CIA::executeOneCycle() +{ + wakeUp(); + + uint64_t oldDelay = delay; + uint64_t oldFeed = feed; + + // + // Layout of timer (A and B) + // + + // Source: "A Software Model of the CIA6526" by Wolfgang Lorenz + // + // Phi2 Phi2 Phi2 + // | | | + // timerA ----- ------v------ ------v------ ----------v--------- + // input ---->| & |--->| dwDelay & |-X-| dwDelay & |---->| decrement counter| + // --->| | | CountA2 | | | CountA3 | | (1) | + // | ----- ------------- | ------------- | | + // ----------------- ^ Clr | | | + // | bCRA & 0x01 | Clr (3) | | ------------------| new counter = 0? | + // | timer A start |<---- | | | | | + // ----------------- | | v v | | + // ----- | ----- | timer A | + // | & | | | & | | 16 bit counter | + // | | | | | | and latch | + // ----- | ----- | | + // ^ ^ | |(2) | | + // | | ---------|------------- | | + // | | | | | | + // timer A | | | ----- | | | + // output <-----------|-X-------------X--->|>=1|---X---->| load from latch | + // | --->| | | (4) | + // ----- | ----- -------------------- + // |>=1| | + // | | | Phi2 + // ----- | | + // ^ ^ | ------v------ ---------------- + // | | ---| dwDelay & |<---| bcRA & 0x10 | + // | ---------------- | LoadA1 | | force load | + // | Phi2 | ------------- ---------------- + // | | | ^ Clr + // ----------------- | ------v------ | | + // | bCRA & 0x08 | | | dwDelay & | | Phi2 + // | one shot |---X->| oneShotA0 |-- + // ----------------- ------------- + + + // Timer A + + // Decrement counter + + if (delay & CIACountA3) + counterA--; // (1) + + // Check underflow condition + bool timerAOutput = (counterA == 0 && (delay & CIACountA2)); // (2) + + if (timerAOutput) { + + icrAck &= ~0x01; + + // Stop timer in one shot mode + if ((delay | feed) & CIAOneShotA0) { // (3) + CRA &= ~0x01; + delay &= ~(CIACountA2 | CIACountA1 | CIACountA0); + feed &= ~CIACountA0; + } + + // Timer A output to timer B in cascade mode + if ((CRB & 0x61) == 0x41 || ((CRB & 0x61) == 0x61 && CNT)) { + delay |= CIACountB1; + } + + // Reload counter immediately + delay |= CIALoadA1; + } + + // Load counter + if (delay & CIALoadA1) // (4) + reloadTimerA(); + + // Timer B + + // Decrement counter + if (delay & CIACountB3) { + counterB--; // (1) + // debug("Counter B down to %04X \n", counterB); + } + + // Check underflow condition + bool timerBOutput = (counterB == 0 && (delay & CIACountB2)); // (2) + + if (timerBOutput) { + + icrAck &= ~0x02; + + // Stop timer in one shot mode + if ((delay | feed) & CIAOneShotB0) { // (3) + CRB &= ~0x01; + delay &= ~(CIACountB2 | CIACountB1 | CIACountB0); + feed &= ~CIACountB0; + } + delay |= CIALoadB1; + } + + // Load counter + if (delay & CIALoadB1) // (4) + reloadTimerB(); + + // + // Serial register + // + + // Generate clock signal + if (timerAOutput && (CRA & 0x40) /* output mode */ ) { + + if (serCounter) { + + // Toggle serial clock signal + feed ^= CIASerClk0; + + } else if (delay & CIASerLoad1) { + + // Load shift register + delay &= ~(CIASerLoad1 | CIASerLoad0); + feed &= ~CIASerLoad0; + serCounter = 8; + feed ^= CIASerClk0; + } + } + + // Run shift register with generated clock signal + if (serCounter) { + if ((delay & (CIASerClk2 | CIASerClk1)) == CIASerClk1) { // Positive edge + if (serCounter == 1) { + delay |= CIASerInt0; // Trigger interrupt + } + } + else if ((delay & (CIASerClk2 | CIASerClk1)) == CIASerClk2) { // Negative edge + serCounter--; + } + } + + // + // Timer output to PB6 (timer A) and PB7 (timer B) + // + + // Source: "A Software Model of the CIA6526" by Wolfgang Lorenz + // + // (7) ----------------- + // -------------------------->| bCRA & 0x04 | + // | | timer mode | ---------------- + // | | 0x00: pulse |->| 0x02 (timer) | + // timerA | Flip --------------- | (7) | | | + // output -X----->| bPB67Toggle |---->| 0x04: toggle | | bCRA & 0x02 | + // (5) | ^ 0x40 | | (8) | | output mode |-> PB6 out + // --------------- ----------------- | | + // ^ Set ----------------- | 0x00 (port) | + // | | port B bit 6 |->| | + // ----------------- 0->1| | output | ---------------- + // | bCRA & 0x01 |------ ----------------- + // | timer A start | + // ----------------- + + // Timer A output to PB6 + + if (timerAOutput) { + + PB67Toggle ^= 0x40; // (5) toggle underflow counter bit + + if (CRA & 0x02) { // (6) + + if ((CRA & 0x04) == 0) { + // (7) set PB6 high for one clock cycle + PB67TimerOut |= 0x40; + delay |= CIAPB6Low0; + delay &= ~CIAPB6Low1; + } else { + // (8) toggle PB6 (copy bit 6 from PB67Toggle) + // PB67TimerOut = (PB67TimerOut & 0xBF) | (PB67Toggle & 0x40); + PB67TimerOut ^= 0x40; + } + } + } + + // Timer B output to PB7 + + if (timerBOutput) { + + PB67Toggle ^= 0x80; // (5) toggle underflow counter bit + + if (CRB & 0x02) { // (6) + + if ((CRB & 0x04) == 0) { + // (7) set PB7 high for one clock cycle + PB67TimerOut |= 0x80; + delay |= CIAPB7Low0; + delay &= ~CIAPB7Low1; + } else { + // (8) toggle PB7 (copy bit 7 from PB67Toggle) + // PB67TimerOut = (PB67TimerOut & 0x7F) | (PB67Toggle & 0x80); + PB67TimerOut ^= 0x80; + } + } + } + + // Set PB67 back to low + if (delay & CIAPB6Low1) + PB67TimerOut &= ~0x40; + + if (delay & CIAPB7Low1) + PB67TimerOut &= ~0x80; + + + // + // Interrupt logic + // + + // Source: "A Software Model of the CIA6526" by Wolfgang Lorenz + // + // ---------- + // | bIMR & |---- + // | 0x01 | | ----- + // ---------- ---->| & |---- + // timerA (9) Set ---------- ---->| | | + // output ------------>| bICR & | | ----- | + // ---------->| 0x01 |---- | ----- + // | Clr ---------- -->|>=1|--- + // | ---------- -->| | | + // | | bIMR & |---- | ----- | + // | | 0x02 | | ----- | | + // | ---------- ---->| & |---- | + // timerB | (10) Set ---------- ---->| | | + // output --|--------->| bICR & | | ----- | + // X--------->| 0x01 |---- | + // | Clr ---------- | + // read | | + // ICR ------X---------------X------------------- | + // | (12) | | + // v Clr v Clr | + // ------ ---------- ---------------- | (11) + // Int <--| -1 |<-----| bICR & |<-----| dwDelay & |<--- + // ouptput | | | 0x80 | Set | Interrupt1 | + // (14) ------ ---------- (13) -------^-------- + // | + // Phi2 + + if (timerAOutput) { // (9) + icr |= 0x01; + } + + // if (timerBOutput && !(delay & CIAReadIcr0)) { // (10) + if (timerBOutput) { // (10) + + if ((delay & CIAReadIcr0) && emulateTimerBBug) { + + // The old CIA chips (NMOS technology) exhibit a race condition here + // which is known as the "timer B bug". If ICR is currently read, + // the read access occurs *after* timer B sets bit 2. Hence, bit 2 + // won't show up. + + } else { + icr |= 0x02; + } + } + + // Check for timer interrupt + if ((timerAOutput && (imr & 0x01)) || (timerBOutput && (imr & 0x02))) { // (11) + triggerTimerIrq(); + } + + // Check for TOD interrupt + if (delay & CIATODInt0) { + icr |= 0x04; + if (imr & 0x04) { + triggerTodIrq(); + } + } + + // Check for Serial interrupt + if (delay & CIASerInt2) { + icr |= 0x08; + if (imr & 0x08) { + triggerSerialIrq(); + } + } + + if (delay & (CIAClearIcr1 | CIAAckIcr1 | CIASetIcr1 | CIASetInt1 | CIAClearInt0)) { + + if (delay & CIAClearIcr1) { // (12) + icr &= 0x7F; + } + if (delay & CIAAckIcr1) { + icr &= ~icrAck; + } + if (delay & CIASetIcr1) { // (13) + icr |= 0x80; + } + if (delay & CIASetInt1) { // (14) + INT = 0; + pullDownInterruptLine(); + } + if (delay & CIAClearInt0) { // (14) + INT = 1; + releaseInterruptLine(); + } + } + + // Move delay flags left and feed in new bits + delay = ((delay << 1) & DelayMask) | feed; + + // Go into idle state if possible + if (oldDelay == delay && oldFeed == feed) { + if (++tiredness > 8) { + sleep(); + tiredness = 0; + } + } else { + tiredness = 0; + } +} + +void +CIA::sleep() +{ + assert(idleCounter == 0); + + // Determine maximum possible sleep cycles based on timer counts + uint64_t cycle = c64->cpu.cycle; + uint64_t sleepA = (counterA > 2) ? (cycle + counterA - 1) : 0; + uint64_t sleepB = (counterB > 2) ? (cycle + counterB - 1) : 0; + + // CIAs with stopped timers can sleep forever + if (!(feed & CIACountA0)) sleepA = UINT64_MAX; + if (!(feed & CIACountB0)) sleepB = UINT64_MAX; + + wakeUpCycle = MIN(sleepA, sleepB); + // setWakeUpCycle(MIN(sleepA, sleepB)); +} + +void +CIA::wakeUp() +{ + // uint64_t idleCycles = idleCounter; + + // Make up for missed cycles + if (idleCounter) { + if (feed & CIACountA0) { + assert(counterA >= idleCounter); + counterA -= idleCounter; + } + if (feed & CIACountB0) { + assert(counterB >= idleCounter); + counterB -= idleCounter; + } + idleCounter = 0; + // resetIdleCounter(); + } + wakeUpCycle = 0; + // setWakeUpCycle(0); +} + + +// ----------------------------------------------------------------------------------------- +// Complex Interface Adapter 1 +// ----------------------------------------------------------------------------------------- + +CIA1::CIA1() +{ + setDescription("CIA1"); + debug(3, " Creating CIA1 at address %p...\n", this); +} + +CIA1::~CIA1() +{ + debug(3, " Releasing CIA1\n"); +} + +void +CIA1::dump() +{ + msg("CIA 1:\n"); + msg("------\n\n"); + CIA::dump(); +} + +void +CIA1::pullDownInterruptLine() +{ + c64->cpu.pullDownIrqLine(CPU::INTSRC_CIA); +} + +void +CIA1::releaseInterruptLine() +{ + c64->cpu.releaseIrqLine(CPU::INTSRC_CIA); +} + +// ------- +// JOYB0, COL0 <--> | PA0 | +// JOYB1, COL1 <--> | PA1 | +// JOYB2, COL2 <--> | PA2 | +// JOYB3, COL3 <--> | PA3 | +// BTNB, COL4 <--> | PA4 | +// COL5 <--> | PA5 | +// COL6 <--> | PA6 | +// COL <--> | PA7 | +// ------- + +uint8_t +CIA1::portAinternal() +{ + return PRA; +} + +uint8_t +CIA1::portAexternal() +{ + return 0xFF; + // return c64->keyboard.getColumnValues(PB); +} + +void +CIA1::updatePA() +{ + uint8_t oldPA = PA; + + PA = (portAinternal() & DDRA) | (portAexternal() & ~DDRA); + + // Get lines which are driven actively low by port 2 + uint8_t rowMask = ~PRB & DDRB & c64->port1.bitmask(); + + // Pull lines low that are connected by a pressed key + PA &= c64->keyboard.getColumnValues(rowMask); + + // The control port can always bring the port lines low + PA &= c64->port2.bitmask(); + + // An edge on PA4 triggers the NeosMouse on port 2 + if (FALLING_EDGE_BIT(oldPA, PA, 4)) + c64->mouse.fallingStrobe(2 /* Port */); + if (RISING_EDGE_BIT(oldPA, PA, 4)) + c64->mouse.risingStrobe(2 /* Port */); +} + +// ------- +// JOYA0, ROW0 <--> | PB0 | +// JOYA1, ROW1 <--> | PB1 | +// JOYA2, ROW2 <--> | PB2 | +// JOYA3, ROW3 <--> | PB3 | +// BTNA/LP, ROW4 <--> | PB4 | --> LP (VIC) +// ROW5 <--> | PB5 | +// ROW6 <--> | PB6 | +// ROW <--> | PB7 | +// ------- + +uint8_t +CIA1::portBinternal() +{ + return PRB; +} + +uint8_t +CIA1::portBexternal() +{ + return 0xFF; + // return c64->keyboard.getRowValues(PA); +} + +void +CIA1::updatePB() +{ + uint8_t oldPB = PB; + + PB = (portBinternal() & DDRB) | (portBexternal() & ~DDRB); + + // Get lines which are driven actively low by port 1 + uint8_t columnMask = ~PRA & DDRA & c64->port2.bitmask(); + + // Pull lines low that are connected by a pressed key + PB &= c64->keyboard.getRowValues(columnMask); + + // Check if timer A underflow shows up on PB6 + if (GET_BIT(PB67TimerMode, 6)) + COPY_BIT(PB67TimerOut, PB, 6); + + // Check if timer B underflow shows up on PB7 + if (GET_BIT(PB67TimerMode, 7)) + COPY_BIT(PB67TimerOut, PB, 7); + + // The control port can always bring the port lines low + PB &= c64->port1.bitmask(); + + // PB4 is connected to the VIC (LP pin). + c64->vic.setLP(GET_BIT(PB, 4) != 0); + + // An edge on PB4 triggers the NeosMouse on port 1 + if (FALLING_EDGE_BIT(oldPB, PB, 4)) + c64->mouse.fallingStrobe(1 /* Port */); + if (RISING_EDGE_BIT(oldPB, PB, 4)) + c64->mouse.risingStrobe(1 /* Port */); +} + + +// ----------------------------------------------------------------------------------------- +// Complex Interface Adapter 2 +// ----------------------------------------------------------------------------------------- + +CIA2::CIA2() +{ + setDescription("CIA2"); + debug(3, " Creating CIA2 at address %p...\n", this); +} + +CIA2::~CIA2() +{ + debug(3, " Releasing CIA2...\n"); +} + +void +CIA2::reset() +{ + CIA::reset(); + + counterA = 0xFFFF; + counterB = 0xFFFF; +} + +void +CIA2::dump() +{ + msg("CIA 2:\n"); + msg("------\n\n"); + CIA::dump(); +} + +void +CIA2::pullDownInterruptLine() +{ + c64->cpu.pullDownNmiLine(CPU::INTSRC_CIA); +} + +void +CIA2::releaseInterruptLine() +{ + c64->cpu.releaseNmiLine(CPU::INTSRC_CIA); +} + +// ------- +// VA14 <--- | PA0 | +// VA15 <--- | PA1 | +// User port (pin M) <--> | PA2 | +// ATN <--- | PA3 | +// CLK <--- | PA4 | +// DATA <--- | PA5 | +// CLK ---> | PA6 | +// DATA ---> | PA7 | +// ------- + +uint8_t +CIA2::portAinternal() +{ + return PRA; +} + +uint8_t +CIA2::portAexternal() +{ + uint8_t result = 0x3F; + result |= (c64->iec.clockLine ? 0x40 : 0x00); + result |= (c64->iec.dataLine ? 0x80 : 0x00); + + return result; +} + +void +CIA2::updatePA() +{ + PA = (portAinternal() & DDRA) | (portAexternal() & ~DDRA); + + // PA0 (VA14) and PA1 (VA15) determine the memory bank seen by the VIC + // c64->vic.updateBankAddr(); + + // Mark IEC bus as dirty + c64->iec.setNeedsUpdateC64Side(); +} + +// ------- +// User port (pin C) <--> | PB0 | +// User port (pin D) <--> | PB1 | +// User port (pin E) <--> | PB2 | +// User port (pin F) <--> | PB3 | +// User port (pin H) <--> | PB4 | +// User port (pin J) <--> | PB5 | +// User port (pin K) <--> | PB6 | +// User port (pin L) <--> | PB7 | +// ------- + +uint8_t +CIA2::portBinternal() +{ + uint8_t result = PRB; + + // Check if timer A underflow shows up on PB6 + if (GET_BIT(PB67TimerMode, 6)) + COPY_BIT(PB67TimerOut, result, 6); + + // Check if timer B underflow shows up on PB7 + if (GET_BIT(PB67TimerMode, 7)) + COPY_BIT(PB67TimerOut, result, 7); + + return result; +} + +uint8_t +CIA2::portBexternal() +{ + // User port is not implemented. All pins are high if nothing is connected. + return 0xFF; +} + +void +CIA2::updatePB() +{ + PB = (portBinternal() & DDRB) | (portBexternal() & ~DDRB); +} + +void +CIA2::pokePA(uint8_t value) +{ + CIA::pokePA(value); + + // PA0 (VA14) and PA1 (VA15) determine the memory bank seen by VICII + c64->vic.switchBank(0xDD00); +} + +void +CIA2::pokeDDRA(uint8_t value) +{ + CIA::pokeDDRA(value); + + // PA0 (VA14) and PA1 (VA15) determine the memory bank seen by VICII + c64->vic.switchBank(0xDD02); + +} + diff --git a/C64/CIA/CIA.h b/C64/CIA/CIA.h new file mode 100755 index 00000000..7426a14f --- /dev/null +++ b/C64/CIA/CIA.h @@ -0,0 +1,500 @@ +/*! + * @header CIA.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright 2006 - 2018 Dirk W. Hoffmann + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CIA_H +#define _CIA_H + +#include "TOD.h" +#include "CIA_types.h" + +// Forward declarations +class VIC; +class IEC; +class Keyboard; +class Joystick; + +// Adapted from PC64WIN +#define CIACountA0 (1ULL << 0) // Decrements timer A +#define CIACountA1 (1ULL << 1) +#define CIACountA2 (1ULL << 2) +#define CIACountA3 (1ULL << 3) +#define CIACountB0 (1ULL << 4) // Decrements timer B +#define CIACountB1 (1ULL << 5) +#define CIACountB2 (1ULL << 6) +#define CIACountB3 (1ULL << 7) +#define CIALoadA0 (1ULL << 8) // Loads timer A +#define CIALoadA1 (1ULL << 9) +#define CIALoadA2 (1ULL << 10) +#define CIALoadB0 (1ULL << 11) // Loads timer B +#define CIALoadB1 (1ULL << 12) +#define CIALoadB2 (1ULL << 13) +#define CIAPB6Low0 (1ULL << 14) // Sets pin PB6 low +#define CIAPB6Low1 (1ULL << 15) +#define CIAPB7Low0 (1ULL << 16) // Sets pin PB7 low +#define CIAPB7Low1 (1ULL << 17) +#define CIASetInt0 (1ULL << 18) // Triggers an interrupt +#define CIASetInt1 (1ULL << 19) +#define CIAClearInt0 (1ULL << 20) // Releases the interrupt line +#define CIAOneShotA0 (1ULL << 21) +#define CIAOneShotB0 (1ULL << 22) +#define CIAReadIcr0 (1ULL << 23) // Indicates that ICR was read recently +#define CIAReadIcr1 (1ULL << 24) +#define CIAClearIcr0 (1ULL << 25) // Clears bit 8 in ICR register +#define CIAClearIcr1 (1ULL << 26) +#define CIAClearIcr2 (1ULL << 27) +#define CIAAckIcr0 (1ULL << 28) // Clears bit 0 - 7 in ICR register +#define CIAAckIcr1 (1ULL << 29) +#define CIASetIcr0 (1ULL << 30) // Sets bit 8 in ICR register +#define CIASetIcr1 (1ULL << 31) +#define CIATODInt0 (1ULL << 32) // Triggers an interrupt with TOD as source +#define CIASerInt0 (1ULL << 33) // Triggers an interrupt with serial register as source +#define CIASerInt1 (1ULL << 34) +#define CIASerInt2 (1ULL << 35) +#define CIASerLoad0 (1ULL << 36) // Loads the serial shift register +#define CIASerLoad1 (1ULL << 37) +#define CIASerClk0 (1ULL << 38) // Clock signal driving the serial register +#define CIASerClk1 (1ULL << 39) +#define CIASerClk2 (1ULL << 40) +#define CIASerClk3 (1ULL << 41) + +#define DelayMask ~((1ULL << 42) | CIACountA0 | CIACountB0 | CIALoadA0 | CIALoadB0 | CIAPB6Low0 | CIAPB7Low0 | CIASetInt0 | CIAClearInt0 | CIAOneShotA0 | CIAOneShotB0 | CIAReadIcr0 | CIAClearIcr0 | CIAAckIcr0 | CIASetIcr0 | CIATODInt0 | CIASerInt0 | CIASerLoad0 | CIASerClk0) + + +/*! @brief Virtual complex interface adapter (CIA) + * @details The original C64 contains two CIA chips (CIA 1 and CIA 2). Each + * chip features two programmable timers and a real-time clock. + * Furthermore, the CIA chips manage the communication with connected + * peripheral devices such as joysticks, printers or the keyboard. + * The CIA class implements the common functionality of both CIAs. + */ +class CIA : public VirtualComponent { + + //! @brief Selected chip model + CIAModel model; + + //! @brief Indicates if timer B bug should be emulated + bool emulateTimerBBug; + +protected: + + //! @brief Timer A counter + uint16_t counterA; + + //! @brief Timer B counter + uint16_t counterB; + +private: + + //! @brief Timer A latch + uint16_t latchA; + + //! @brief Timer B latch + uint16_t latchB; + + //! @brief Time of day clock + TOD tod = TOD(this); + + + // + // Adapted from PC64Win by Wolfgang Lorenz + // + + // + // Control + // + + //! @brief Performs delay by shifting left at each clock + uint64_t delay; + + //! @brief New bits to feed into dwDelay + uint64_t feed; + + //! @brief Control register A + uint8_t CRA; + + //! @brief Control register B + uint8_t CRB; + + //! @brief Interrupt control register + uint8_t icr; + + //! @brief ICR bits that need to deleted when CIAAckIcr1 hits + uint8_t icrAck; + + //! @brief Interrupt mask register + uint8_t imr; + +protected: + + //! @brief Bit mask for PB outputs: 0 = port register, 1 = timer + uint8_t PB67TimerMode; + + //! @brief PB outputs bits 6 and 7 in timer mode + uint8_t PB67TimerOut; + + //! @brief PB outputs bits 6 and 7 in toggle mode + uint8_t PB67Toggle; + + + // + // Port registers + // + +protected: + + //! @brief Peripheral data register A + uint8_t PRA; + + //! @brief Peripheral data register B + uint8_t PRB; + + //! @brief Data directon register A (0 = input, 1 = output) + uint8_t DDRA; + + //! @brief Data directon register B (0 = input, 1 = output) + uint8_t DDRB; + + //! @brief Peripheral port A (pins PA0 to PA7) + uint8_t PA; + + //! @brief Peripheral port A (pins PB0 to PB7) + uint8_t PB; + + + // + // Shift register logic + // + +private: + + //! @brief Serial data register + /*! @details http://unusedino.de/ec64/technical/misc/cia6526/serial.html + * "The serial port is a buffered, 8-bit synchronous shift register system. + * A control bit selects input or output mode. In input mode, data on the SP pin + * is shifted into the shift register on the rising edge of the signal applied + * to the CNT pin. After 8 CNT pulses, the data in the shift register is dumped + * into the Serial Data Register and an interrupt is generated. In the output + * mode, TIMER A is used for the baud rate generator. Data is shifted out on the + * SP pin at 1/2 the underflow rate of TIMER A. [...] Transmission will start + * following a write to the Serial Data Register (provided TIMER A is running + * and in continuous mode). The clock signal derived from TIMER A appears as an + * output on the CNT pin. The data in the Serial Data Register will be loaded + * into the shift register then shift out to the SP pin when a CNT pulse occurs. + * Data shifted out becomes valid on the falling edge of CNT and remains valid + * until the next falling edge. After 8 CNT pulses, an interrupt is generated to + * indicate more data can be sent. If the Serial Data Register was loaded with + * new information prior to this interrupt, the new data will automatically be + * loaded into the shift register and transmission will continue. If the + * microprocessor stays one byte ahead of the shift register, transmission will + * be continuous. If no further data is to be transmitted, after the 8th CNT + * pulse, CNT will return high and SP will remain at the level of the last data + * bit transmitted. SDR data is shifted out MSB first and serial input data + * should also appear in this format. + */ + uint8_t SDR; + + //! @brief Clock signal for driving the serial register + bool serClk; + + //! @brief Shift register counter + /*! @details The counter is set to 8 when the shift register is loaded and decremented + * when a bit is shifted out. + */ + uint8_t serCounter; + + // + // Chip interface (port pins) + // + + //! @brief Serial clock or input timer clock or timer gate + bool CNT; + bool INT; + + + // + // Speeding up emulation (CIA sleep logic) + // + + //! @brief Idle counter + /*! @details When the VIA state does not change during execution, this + * variable is increased by one. If it exceeds a certain + * threshhold, the chip is put into idle state via sleep() + */ + uint8_t tiredness; + +public: + + //! @brief Wakeup cycle + uint64_t wakeUpCycle; + + //! @brief Number of skipped executions + uint64_t idleCounter; + +public: + + //! @brief Constructor + CIA(); + + //! @brief Destructor + ~CIA(); + + //! @brief Bring the CIA back to its initial state + void reset(); + + //! @brief Dump internal state + void dump(); + + //! @brief Dump trace line + void dumpTrace(); + + + // + //! @functiongroup Accessing device properties + // + + //! @brief Returns the currently plugged in chip model. + CIAModel getModel() { return model; } + + //! @brief Sets the chip model. + void setModel(CIAModel m); + + //! @brief Determines if the emulated model is affected by the timer B bug. + bool hasTimerBBug() { return model == MOS_6526; } + + //! @brief Returns true if the timer B bug should be emulated. + bool getEmulateTimerBBug() { return emulateTimerBBug; } + + //! @brief Enables or disables emulation of the timer B bug. + void setEmulateTimerBBug(bool value) { emulateTimerBBug = value; } + + //! @brief Getter for peripheral port A + uint8_t getPA() { return PA; } + uint8_t getDDRA() { return DDRA; } + + //! @brief Getter for peripheral port B + uint8_t getPB() { return PB; } + uint8_t getDDRB() { return DDRB; } + + //! @brief Collects all data to be shown in the GUI's debug panel + CIAInfo getInfo(); + + //! @brief Simulates a rising edge on the flag pin + void triggerRisingEdgeOnFlagPin(); + + //! @brief Simulates a falling edge on the flag pin + void triggerFallingEdgeOnFlagPin(); + +private: + + // + // Interrupt control + // + + /*! @brief Requests the CPU to interrupt + * @details This function is abstract and implemented differently by CIA1 and CIA2. + * CIA 1 activates the IRQ line and CIA 2 the NMI line. + */ + virtual void pullDownInterruptLine() = 0; + + /*! @brief Removes the interrupt requests + * @details This function is abstract and implemented differently by CIA1 and CIA2. + * CIA 1 clears the IRQ line and CIA 2 the NMI line. + */ + virtual void releaseInterruptLine() = 0; + + /*! @brief Load latched value into timer. + * @details As a side effect, CountA2 is cleared. This causes the timer to wait + * for one cycle before it continues to count. + */ + void reloadTimerA() { counterA = latchA; delay &= ~CIACountA2; } + + /*! @brief Loads latched value into timer. + * @details As a side effect, CountB2 is cleared. This causes the timer to wait for + * one cycle before it continues to count. + */ + void reloadTimerB() { counterB = latchB; delay &= ~CIACountB2; } + + /*! @brief Triggers a timer interrupt + * @details Invoked inside executeOneCycle() if IRQ conditions are met. + */ + void triggerTimerIrq(); + + /*! @brief Triggers a TOD interrupt + * @details Invoked inside executeOneCycle() if IRQ conditions are met. + */ + void triggerTodIrq(); + + /*! @brief Triggers a serial interrupt + * @details Invoked inside executeOneCycle() if IRQ conditions are met. + */ + void triggerSerialIrq(); + +private: + + // + // Port registers + // + + //! @brief Values driving port A from inside the chip + virtual uint8_t portAinternal() = 0; + + //! @brief Values driving port A from outside the chip + virtual uint8_t portAexternal() = 0; + +public: + + //! @brief Computes the values which we currently see at port A + virtual void updatePA() = 0; + +private: + + //! @brief Values driving port B from inside the chip + virtual uint8_t portBinternal() = 0; + + //! @brief Values driving port B from outside the chip + virtual uint8_t portBexternal() = 0; + + //! @brief Computes the values which we currently see at port B + virtual void updatePB() = 0; + +protected: + + //! @brief Action method for poking the PA register + virtual void pokePA(uint8_t value) { PRA = value; updatePA(); } + + //! @brief Action method for poking the DDRA register + virtual void pokeDDRA(uint8_t value) { DDRA = value; updatePA(); } + + + // + //! @functiongroup Accessing the I/O address space + // + +public: + + //! @brief Peeks a value from a CIA register. + uint8_t peek(uint16_t addr); + + //! @brief Peeks a value from a CIA register without causing side effects. + uint8_t spypeek(uint16_t addr); + + //! @brief Pokes a value into a CIA register. + void poke(uint16_t addr, uint8_t value); + + + // + //! @functiongroup Running the device + // + +public: + + //! @brief Executes the CIA for one cycle + void executeOneCycle(); + + //! @brief Increments the TOD clock by one tenth of a second + void incrementTOD(); + + + // + //! @functiongroup Handling interrupt requests + // + + //! @brief Handles an interrupt request from TOD + void todInterrupt(); + + + // + //! @functiongroup Speeding up emulation + // + +private: + + //! @brief Puts the CIA into idle state. + void sleep(); + + //! @brief Emulates all previously skipped cycles. + void wakeUp(); +}; + + +/*! @class The first virtual complex interface adapter (CIA 1) + * @details The CIA 1 chips differs from the CIA 2 chip in several smaller + * aspects. For example, the CIA 1 interrupts the CPU via the + * IRQ line (maskable interrupts). Furthermore, the keyboard is + * connected to the the C64 via the CIA 1 chip. + */ +class CIA1 : public CIA { + +public: + + CIA1(); + ~CIA1(); + void dump(); + +private: + + void pullDownInterruptLine(); + void releaseInterruptLine(); + + uint8_t portAinternal(); + uint8_t portAexternal(); + void updatePA(); + uint8_t portBinternal(); + uint8_t portBexternal(); + void updatePB(); +}; + +/*! @brief The second virtual complex interface adapter (CIA 2) + * @details The CIA 2 chips differs from the CIA 1 chip in several smaller + * aspects. For example, the CIA 2 interrupts the CPU via the + * NMI line (non maskable interrupts). Furthermore, the CIA 2 + * controlls the memory bank seen by the video controller. + */ +class CIA2 : public CIA { + +public: + + CIA2(); + ~CIA2(); + void reset(); + void dump(); + +private: + + void pullDownInterruptLine(); + void releaseInterruptLine(); + + uint8_t portAinternal(); + uint8_t portAexternal(); + +public: + + void updatePA(); + +private: + + uint8_t portBinternal(); + uint8_t portBexternal(); + void updatePB(); + void pokePA(uint8_t value); + void pokeDDRA(uint8_t value); +}; + +#endif diff --git a/C64/CIA/CIA_types.h b/C64/CIA/CIA_types.h new file mode 100755 index 00000000..4e719f31 --- /dev/null +++ b/C64/CIA/CIA_types.h @@ -0,0 +1,59 @@ +// +// CIA_types.h +// V64 +// +// Created by Dirk Hoffmann on 15.04.18. +// + +#ifndef CIA_TYPES_H +#define CIA_TYPES_H + +//! @brief CIA model +typedef enum { + MOS_6526, + MOS_8521 +} CIAModel; + +inline bool isCIAModel(CIAModel model) { + return (model == MOS_6526) || (model == MOS_8521); +} + +/*! @brief TOD info + * @details Used by CIA::getInfo() to collect debug information + */ +typedef struct { + struct { + uint8_t port; + uint8_t reg; + uint8_t dir; + } portA; + struct { + uint8_t port; + uint8_t reg; + uint8_t dir; + } portB; + struct { + uint16_t count; + uint16_t latch; + bool running; + bool toggle; + bool pbout; + bool oneShot; + } timerA; + struct { + uint16_t count; + uint16_t latch; + bool running; + bool toggle; + bool pbout; + bool oneShot; + } timerB; + uint8_t icr; + uint8_t imr; + bool intLine; + TODInfo tod; + bool todIntEnable; +} CIAInfo; + + +#endif diff --git a/C64/CIA/TOD.cpp b/C64/CIA/TOD.cpp new file mode 100755 index 00000000..a043d987 --- /dev/null +++ b/C64/CIA/TOD.cpp @@ -0,0 +1,145 @@ +/*! + * @file TOD.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "C64.h" + +TOD::TOD(CIA *cia) +{ + setDescription("TOD"); + debug(3, " Creating TOD at address %p...\n", this); + + this->cia = cia; + + // Register snapshot items + SnapshotItem items[] = { + + { &tod.value, sizeof(tod.value), CLEAR_ON_RESET }, + { &latch.value, sizeof(latch.value), CLEAR_ON_RESET }, + { &alarm.value, sizeof(alarm.value), CLEAR_ON_RESET }, + { &frozen, sizeof(frozen), CLEAR_ON_RESET }, + { &stopped, sizeof(stopped), CLEAR_ON_RESET }, + { &matching, sizeof(matching), CLEAR_ON_RESET }, + { &hz, sizeof(hz), CLEAR_ON_RESET }, + { &frequencyCounter, sizeof(frequencyCounter), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +void +TOD::reset() +{ + VirtualComponent::reset(); + tod.hours = 1; + stopped = true; + hz = 60; +} + +void +TOD::dump() +{ + msg(" Time of day : %02X:%02X:%02X:%02X\n", + tod.hours, tod.minutes, tod.seconds, tod.tenth); + msg(" Alarm : %02X:%02X:%02X:%02X\n", + alarm.hours, alarm.minutes, alarm.seconds, alarm.tenth); + msg(" Latch : %02X:%02X:%02X:%02X\n", + latch.hours, latch.minutes, latch.seconds, latch.tenth); + msg(" Frozen : %s\n", frozen ? "yes" : "no"); + msg(" Stopped : %s\n", stopped ? "yes" : "no"); + msg("\n"); +} + +TODInfo +TOD::getInfo() +{ + TODInfo info; + + info.time = tod; + info.latch = latch; + info.alarm = alarm; + + return info; +} + + +void +TOD::increment() +{ + if (stopped) + return; + + if (++frequencyCounter % hz != 0) + return; + + // 1/10 seconds + if (tod.tenth != 0x09) { + tod.tenth = incBCD(tod.tenth); + } else { + tod.tenth = 0; + + // Seconds + if (tod.seconds != 0x59) { + tod.seconds = incBCD(tod.seconds) & 0x7F; + } else { + tod.seconds = 0; + + // Minutes + if (tod.minutes != 0x59) { + tod.minutes = incBCD(tod.minutes) & 0x7F; + } else { + tod.minutes = 0; + + // Hours + uint8_t pm = tod.hours & 0x80; + uint8_t hr = tod.hours & 0x1F; + + if (hr == 0x11) { + pm ^= 0x80; + } + if (hr == 0x12) { + hr = 0x01; + } else if (hr == 0x09) { + hr = 0x10; + } else { + uint8_t hr_lo = hr & 0x0F; + uint8_t hr_hi = hr & 0x10; + hr = hr_hi | ((hr_lo + 1) & 0x0F); + } + + tod.hours = pm | hr; + } + } + } + + checkForInterrupt(); + return; +} + +void +TOD::checkForInterrupt() +{ + if (!matching && tod.value == alarm.value) { + cia->todInterrupt(); + } + + matching = (tod.value == alarm.value); +} diff --git a/C64/CIA/TOD.h b/C64/CIA/TOD.h new file mode 100755 index 00000000..cb490957 --- /dev/null +++ b/C64/CIA/TOD.h @@ -0,0 +1,212 @@ +/*! + * @header TOD.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _TOD_INC +#define _TOD_INC + +#include "VirtualComponent.h" +#include "TOD_types.h" + +class CIA; + +//! @brief Increments a BCD number by one. +inline uint8_t incBCD(uint8_t bcd) { + return ((bcd & 0x0F) == 0x09) ? (bcd & 0xF0) + 0x10 : (bcd & 0xF0) + ((bcd + 0x01) & 0x0F); +} + +/*! @brief Time of day clock (TOD) + * @details Each CIA chip contains a time of day clock, counting hours, + * minutes, seconds and tenth of a second. Every TOD clock features + * an alarm mechanism. When the alarm time is reached, an interrupt + * is initiated. + */ +class TOD : public VirtualComponent { + + friend CIA; + +private: + + //! @brief Reference to the connected CIA + CIA *cia; + + //! @brief Time of day clock + TimeOfDay tod; + + //! @brief Time of day clock latch + TimeOfDay latch; + + //! @brief Alarm time + TimeOfDay alarm; + + /*! @brief Indicates if the TOD registers are frozen + * @details The CIA chip freezes the registers when the hours-part is read + * and reactivates them, when the 1/10th part is read. Although + * the values stay constant, the internal clock continues to run. + * Purpose: If you start reading with the hours-part, the clock + * won't change until you have read the whole time. + */ + bool frozen; + + /*! @brief Indicates if the TOD clock is halted. + * @details The CIA chip stops the TOD clock when the hours-part is + * written and restarts it, when the 1/10th part is written. + * Purpose: The clock will only start running when the time is + * completely set. + */ + bool stopped; + + /*! @brief Indicates if tod time matches the alarm time + * @details This value is read in checkForInterrupt() for edge detection. + */ + bool matching; + + /*! @brief Indicates if TOD is driven by a 50 Hz or 60 Hz signal + * @details Valid values are 5 (50 Hz mode) and 6 (60 Hz mode) + */ + uint8_t hz; + + /*! @brief Frequency counter + * @details This counter is driven by the A/C power frequency and + * determines when TOD should increment. This variable is + * incremented in function increment() which is called in + * endFrame(). Hence, frequencyCounter is a 50 Hz signal in PAL + * mode and a 60 Hz signal in NTSC mode. + */ + uint64_t frequencyCounter; + +public: + + // + //! @functiongroup Creating and destructing + // + + //! @brief Constructor + TOD(CIA *cia); + + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void dump(); + + + // + //! @functiongroup Configuring the component + // + + //! @brief Sets the frequency of the driving clock. + void setHz(uint8_t value) { assert(value == 5 || value == 6); hz = value; } + + //! @brief Returns the current configuration. + TODInfo getInfo(); + + + // + //! @functiongroup Running the component + // + +private: + + //! @brief Freezes the time of day clock. + void freeze() { if (!frozen) { latch.value = tod.value; frozen = true; } } + + //! @brief Unfreezes the time of day clock. + void defreeze() { frozen = false; } + + //! @brief Stops the time of day clock. + void stop() { frequencyCounter = 0; stopped = true; } + + //! @brief Starts the time of day clock. + void cont() { stopped = false; } + + //! @brief Returns the hours digits of the time of day clock. + uint8_t getTodHours() { return (frozen ? latch.hours : tod.hours) & 0x9F; } + + //! @brief Returns the minutes digits of the time of day clock. + uint8_t getTodMinutes() { return (frozen ? latch.minutes : tod.minutes) & 0x7F; } + + //! @brief Returns the seconds digits of the time of day clock. + uint8_t getTodSeconds() { return (frozen ? latch.seconds : tod.seconds) & 0x7F; } + + //! @brief Returns the tenth-of-a-second digits of the time of day clock. + uint8_t getTodTenth() { return (frozen ? latch.tenth : tod.tenth) & 0x0F; } + + //! @brief Returns the hours digits of the alarm time. + uint8_t getAlarmHours() { return alarm.hours & 0x9F; } + + //! @brief Returns the minutes digits of the alarm time. + uint8_t getAlarmMinutes() { return alarm.minutes & 0x7F; } + + //! @brief Returns the seconds digits of the alarm time. + uint8_t getAlarmSeconds() { return alarm.seconds & 0x7F; } + + //! @brief Returns the tenth-of-a-second digits of the alarm time. + uint8_t getAlarmTenth() { return alarm.tenth & 0x0F; } + + + //! @brief Sets the hours digits of the time of day clock. + void setTodHours(uint8_t value) { tod.hours = value & 0x9F; checkForInterrupt(); } + + //! @brief Sets the minutes digits of the time of day clock. + void setTodMinutes(uint8_t value) { + tod.minutes = value & 0x7F; checkForInterrupt(); } + + //! @brief Sets the seconds digits of the time of day clock. + void setTodSeconds(uint8_t value) { + tod.seconds = value & 0x7F; checkForInterrupt(); } + + //! @brief Sets the tenth-of-a-second digits of the time of day clock. + void setTodTenth(uint8_t value) { + tod.tenth = value & 0x0F; checkForInterrupt(); } + + //! @brief Sets the hours digits of the alarm time. + void setAlarmHours(uint8_t value) { + alarm.hours = value & 0x9F; checkForInterrupt(); } + + //! @brief Sets the minutes digits of the alarm time. + void setAlarmMinutes(uint8_t value) { + alarm.minutes = value & 0x7F; checkForInterrupt(); } + + //! @brief Sets the seconds digits of the alarm time. + void setAlarmSeconds(uint8_t value) { + alarm.seconds = value & 0x7F; checkForInterrupt(); } + + //! @brief Sets the tenth-of-a-second digits of the time of day clock. + void setAlarmTenth(uint8_t value) { + alarm.tenth = value & 0x0F; checkForInterrupt(); } + + /*! @brief Increments the TOD clock by one tenth of a second. + * @see C64::endFrame() + */ + void increment(); + + /*! @brief Updates variable 'matching' + * @details If a positive edge occurs, the connected CIA will be requested + * to trigger an interrupt. + */ + void checkForInterrupt(); +}; + +#endif + + diff --git a/C64/CIA/TOD_types.h b/C64/CIA/TOD_types.h new file mode 100755 index 00000000..3a93e080 --- /dev/null +++ b/C64/CIA/TOD_types.h @@ -0,0 +1,47 @@ +/*! + * @header TOD_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TOD_TYPES_H +#define TOD_TYPES_H + +#include + +//! @brief Time of day information (TOD) +typedef union { + struct { + uint8_t tenth; + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + }; + uint32_t value; +} TimeOfDay; + +/*! @brief TOD info + * @details Used by TOD::getInfo() to collect debug information + */ +typedef struct { + TimeOfDay time; + TimeOfDay latch; + TimeOfDay alarm; +} TODInfo; + +#endif diff --git a/C64/CPU/CPU.cpp b/C64/CPU/CPU.cpp new file mode 100755 index 00000000..a5ab797b --- /dev/null +++ b/C64/CPU/CPU.cpp @@ -0,0 +1,481 @@ +/*! + * @file CPU.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +CPU::CPU(CPUModel model, Memory *mem) +{ + this->model = model; + this->mem = mem; + + setDescription(model == MOS_6502 ? "CPU(6502)" : "CPU"); + debug(3, " Creating %s at address %p...\n", getDescription(), this); + + // Chip model + model = MOS_6510; + + // Establish callback for each instruction + registerInstructions(); + + // Clear all breakpoint tags + for (int i = 0; i < 65536; i++) { + breakpoint[i] = NO_BREAKPOINT; + } + + // Register snapshot items + SnapshotItem items[] = { + + // Lifetime items + { &model, sizeof(model), KEEP_ON_RESET }, + + // Internal state + { &cycle, sizeof(cycle), CLEAR_ON_RESET }, + { &errorState, sizeof(errorState), CLEAR_ON_RESET }, + { &next, sizeof(next), CLEAR_ON_RESET }, + + { ®A, sizeof(regA), CLEAR_ON_RESET }, + { ®X, sizeof(regX), CLEAR_ON_RESET }, + { ®Y, sizeof(regY), CLEAR_ON_RESET }, + { ®PC, sizeof(regPC), CLEAR_ON_RESET }, + { ®SP, sizeof(regSP), CLEAR_ON_RESET }, + { ®P, sizeof(regP), CLEAR_ON_RESET }, + { ®ADL, sizeof(regADL), CLEAR_ON_RESET }, + { ®ADH, sizeof(regADH), CLEAR_ON_RESET }, + { ®IDL, sizeof(regIDL), CLEAR_ON_RESET }, + { ®D, sizeof(regD), CLEAR_ON_RESET }, + { &overflow, sizeof(overflow), CLEAR_ON_RESET }, + { &pc, sizeof(pc), CLEAR_ON_RESET }, + { &rdyLine, sizeof(rdyLine), CLEAR_ON_RESET }, + { &rdyLineUp, sizeof(rdyLineUp), CLEAR_ON_RESET }, + { &rdyLineDown, sizeof(rdyLineDown), CLEAR_ON_RESET }, + { &nmiLine, sizeof(nmiLine), CLEAR_ON_RESET }, + { &irqLine, sizeof(irqLine), CLEAR_ON_RESET }, + { &doNmi, sizeof(doNmi), CLEAR_ON_RESET }, + { &doIrq, sizeof(doIrq), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +CPU::~CPU() +{ + debug(3, " Releasing CPU...\n"); +} + +void +CPU::reset() +{ + VirtualComponent::reset(); + + setB(1); + rdyLine = true; + next = fetch; + levelDetector.clear(); + edgeDetector.clear(); + + clearTraceBuffer(); +} + +void +CPU::dump() +{ + DisassembledInstruction instr = disassemble(true /* hex output */); + + msg("CPU:\n"); + msg("----\n\n"); + msg("%s: %s %s %s %s %s %s %s %s %s\n", + instr.pc, + instr.byte1, instr.byte2, instr.byte3, + instr.a, instr.x, instr.y, instr.sp, + instr.flags, + instr.command); + msg(" Rdy line : %s\n", rdyLine ? "high" : "low"); + msg(" Nmi line : %02X\n", nmiLine); + msg(" Edge detector : %02X\n", edgeDetector.current()); + msg(" doNmi : %s\n", doNmi ? "yes" : "no"); + msg(" Irq line : %02X\n", irqLine); + msg("Level detector : %02X\n", levelDetector.current()); + msg(" doIrq : %s\n", doIrq ? "yes" : "no"); + msg(" IRQ routine : %02X%02X\n", mem->spypeek(0xFFFF), mem->spypeek(0xFFFE)); + msg(" NMI routine : %02X%02X\n", mem->spypeek(0xFFFB), mem->spypeek(0xFFFA)); + msg("\n"); + + c64->processorPort.dump(); +} + +size_t +CPU::stateSize() +{ + return VirtualComponent::stateSize() + + levelDetector.stateSize() + + edgeDetector.stateSize(); +} + +void +CPU::didLoadFromBuffer(uint8_t **buffer) +{ + levelDetector.loadFromBuffer(buffer); + edgeDetector.loadFromBuffer(buffer); +} + +void +CPU::didSaveToBuffer(uint8_t **buffer) +{ + levelDetector.saveToBuffer(buffer); + edgeDetector.saveToBuffer(buffer); +} + +CPUInfo +CPU::getInfo() +{ + CPUInfo info; + + info.cycle = cycle; + info.pc = pc; + info.a = regA; + info.x = regX; + info.y = regY; + info.sp = regSP; + info.nFlag = getN(); + info.vFlag = getV(); + info.bFlag = getB(); + info.dFlag = getD(); + info.iFlag = getI(); + info.zFlag = getZ(); + info.cFlag = getC(); + + return info; +} + +void +CPU::pullDownNmiLine(IntSource bit) +{ + assert(bit != 0); + + // Check for falling edge on physical line + if (!nmiLine) { + edgeDetector.write(1); + } + + nmiLine |= bit; +} + +void +CPU::releaseNmiLine(IntSource source) +{ + nmiLine &= ~source; +} + +void +CPU::pullDownIrqLine(IntSource source) +{ + assert(source != 0); + + irqLine |= source; + levelDetector.write(irqLine); +} + +void +CPU::releaseIrqLine(IntSource source) +{ + irqLine &= ~source; + levelDetector.write(irqLine); +} + +void +CPU::setRDY(bool value) +{ + if (rdyLine) + { + rdyLine = value; + if (!rdyLine) rdyLineDown = cycle; + } + else + { + rdyLine = value; + if (rdyLine) rdyLineUp = cycle; + } +} + +unsigned +CPU::getLengthOfInstruction(uint8_t opcode) +{ + switch(addressingMode[opcode]) { + case ADDR_IMPLIED: + case ADDR_ACCUMULATOR: + return 1; + case ADDR_IMMEDIATE: + case ADDR_ZERO_PAGE: + case ADDR_ZERO_PAGE_X: + case ADDR_ZERO_PAGE_Y: + case ADDR_INDIRECT_X: + case ADDR_INDIRECT_Y: + case ADDR_RELATIVE: + return 2; + case ADDR_ABSOLUTE: + case ADDR_ABSOLUTE_X: + case ADDR_ABSOLUTE_Y: + case ADDR_DIRECT: + case ADDR_INDIRECT: + return 3; + } + return 1; +} + + +void +CPU::setErrorState(ErrorState state) +{ + if (errorState == state) + return; + + errorState = state; + + switch (errorState) { + case CPU_OK: + c64->putMessage(MSG_CPU_OK); + return; + case CPU_SOFT_BREAKPOINT_REACHED: + c64->putMessage(MSG_CPU_SOFT_BREAKPOINT_REACHED); + return; + case CPU_HARD_BREAKPOINT_REACHED: + c64->putMessage(MSG_CPU_HARD_BREAKPOINT_REACHED); + return; + case CPU_ILLEGAL_INSTRUCTION: + c64->putMessage(MSG_CPU_ILLEGAL_INSTRUCTION); + return; + default: + assert(false); + } +} + +unsigned +CPU::recordedInstructions() +{ + return writePtr >= readPtr ? writePtr - readPtr : traceBufferSize + writePtr - readPtr; +} + +void +CPU::recordInstruction() +{ + RecordedInstruction i; + uint8_t opcode = mem->spypeek(pc); + unsigned length = getLengthOfInstruction(opcode); + + i.cycle = cycle; + i.pc = pc; + i.byte1 = opcode; + i.byte2 = length > 1 ? mem->spypeek(i.pc + 1) : 0; + i.byte3 = length > 2 ? mem->spypeek(i.pc + 2) : 0; + i.a = regA; + i.x = regX; + i.y = regY; + i.sp = regSP; + i.flags = getP(); + + assert(writePtr < traceBufferSize); + + traceBuffer[writePtr] = i; + writePtr = (writePtr + 1) % traceBufferSize; + if (writePtr == readPtr) { + readPtr = (readPtr + 1) % traceBufferSize; + } + // debug("readPtr = %d writePtr = %d size = %d\n", readPtr, writePtr, recordedInstructions()); +} + +RecordedInstruction +CPU::readRecordedInstruction() +{ + assert(recordedInstructions() != 0); + assert(readPtr < traceBufferSize); + + RecordedInstruction result = traceBuffer[readPtr]; + readPtr = (readPtr + 1) % traceBufferSize; + + return result; +} + +RecordedInstruction +CPU::readRecordedInstruction(unsigned previous) +{ + // debug("previous = %d recInstr = %d\n",previous, recordedInstructions()); + // assert(previous < recordedInstructions()); + return traceBuffer[(writePtr + traceBufferSize - previous - 1) % traceBufferSize]; +} + + +DisassembledInstruction +CPU::disassemble(RecordedInstruction instr, bool hex) +{ + DisassembledInstruction result; + + uint8_t opcode = instr.byte1; + uint8_t length = getLengthOfInstruction(opcode); + + result.addr = instr.pc; + result.size = length; + + // Convert command + char operand[6]; + switch (addressingMode[opcode]) { + + case ADDR_IMMEDIATE: + case ADDR_ZERO_PAGE: + case ADDR_ZERO_PAGE_X: + case ADDR_ZERO_PAGE_Y: + case ADDR_INDIRECT_X: + case ADDR_INDIRECT_Y: { + uint8_t value = mem->spypeek(instr.pc + 1); + hex ? sprint8x(operand, value) : sprint8d(operand, value); + break; + } + case ADDR_DIRECT: + case ADDR_INDIRECT: + case ADDR_ABSOLUTE: + case ADDR_ABSOLUTE_X: + case ADDR_ABSOLUTE_Y: { + uint16_t value = LO_HI(mem->spypeek(instr.pc + 1),mem->spypeek(instr.pc + 2)); + hex ? sprint16x(operand, value) : sprint16d(operand, value); + break; + } + case ADDR_RELATIVE: { + uint16_t value = instr.pc + 2 + (int8_t)mem->spypeek(instr.pc + 1); + hex ? sprint16x(operand, value) : sprint16d(operand, value); + break; + } + default: + break; + } + + switch (addressingMode[opcode]) { + case ADDR_IMPLIED: + case ADDR_ACCUMULATOR: + strcpy(result.command, "xxx"); + break; + case ADDR_IMMEDIATE: + strcpy(result.command, hex ? "xxx #hh" : "xxx #ddd"); + memcpy(&result.command[5], operand, hex ? 2 : 3); + break; + case ADDR_ZERO_PAGE: + strcpy(result.command, hex ? "xxx hh" : "xxx ddd"); + memcpy(&result.command[4], operand, hex ? 2 : 3); + break; + case ADDR_ZERO_PAGE_X: + strcpy(result.command, hex ? "xxx hh,X" : "xxx ddd,X"); + memcpy(&result.command[4], operand, hex ? 2 : 3); + break; + case ADDR_ZERO_PAGE_Y: + strcpy(result.command, hex ? "xxx hh,Y" : "xxx ddd,Y"); + memcpy(&result.command[4], operand, hex ? 2 : 3); + break; + case ADDR_ABSOLUTE: + case ADDR_DIRECT: + strcpy(result.command, hex ? "xxx hhhh" : "xxx ddddd"); + memcpy(&result.command[4], operand, hex ? 4 : 5); + break; + case ADDR_ABSOLUTE_X: + strcpy(result.command, hex ? "xxx hhhh,X" : "xxx ddddd,X"); + memcpy(&result.command[4], operand, hex ? 4 : 5); + break; + case ADDR_ABSOLUTE_Y: + strcpy(result.command, hex ? "xxx hhhh,Y" : "xxx ddddd,Y"); + memcpy(&result.command[4], operand, hex ? 4 : 5); + break; + case ADDR_INDIRECT: + strcpy(result.command, hex ? "xxx (hhhh)" : "xxx (ddddd)"); + memcpy(&result.command[5], operand, hex ? 4 : 5); + break; + case ADDR_INDIRECT_X: + strcpy(result.command, hex ? "xxx (hh,X)" : "xxx (ddd,X)"); + memcpy(&result.command[5], operand, hex ? 2 : 3); + break; + case ADDR_INDIRECT_Y: + strcpy(result.command, hex ? "xxx (hh),Y" : "xxx (ddd),Y"); + memcpy(&result.command[5], operand, hex ? 2 : 3); + break; + case ADDR_RELATIVE: + strcpy(result.command, hex ? "xxx hhhh" : "xxx ddddd"); + memcpy(&result.command[4], operand, hex ? 4 : 5); + break; + default: + strcpy(result.command, "???"); + } + + // Copy mnemonic + strncpy(result.command, mnemonic[opcode], 3); + + // Convert register contents to strings + hex ? sprint16x(result.pc, instr.pc) : sprint16d(result.pc, instr.pc); + hex ? sprint8x(result.a, instr.a) : sprint8d(result.a, instr.a); + hex ? sprint8x(result.x, instr.x) : sprint8d(result.x, instr.x); + hex ? sprint8x(result.y, instr.y) : sprint8d(result.y, instr.y); + hex ? sprint8x(result.sp, instr.sp) : sprint8d(result.sp, instr.sp); + + // Convert memory contents to strings + if (length >= 1) { + hex ? sprint8x(result.byte1, instr.byte1) : sprint8d(result.byte1, instr.byte1); + } else { + hex ? strcpy(result.byte1, " ") : strcpy(result.byte1, " "); + } + if (length >= 2) { + hex ? sprint8x(result.byte2, instr.byte2) : sprint8d(result.byte2, instr.byte2); + } else { + hex ? strcpy(result.byte2, " ") : strcpy(result.byte2, " "); + } + if (length >= 3) { + hex ? sprint8x(result.byte3, instr.byte3) : sprint8d(result.byte3, instr.byte3); + } else { + hex ? strcpy(result.byte3, " ") : strcpy(result.byte3, " "); + } + + // Convert flags to a string + result.flags[0] = (instr.flags & N_FLAG) ? 'N' : 'n'; + result.flags[1] = (instr.flags & V_FLAG) ? 'V' : 'v'; + result.flags[2] = '-'; + result.flags[3] = (instr.flags & B_FLAG) ? 'B' : 'b'; + result.flags[4] = (instr.flags & D_FLAG) ? 'D' : 'd'; + result.flags[5] = (instr.flags & I_FLAG) ? 'I' : 'i'; + result.flags[6] = (instr.flags & Z_FLAG) ? 'Z' : 'z'; + result.flags[7] = (instr.flags & C_FLAG) ? 'C' : 'c'; + result.flags[8] = 0; + + return result; +} + +DisassembledInstruction +CPU::disassemble(uint16_t addr, bool hex) +{ + RecordedInstruction instr; + + instr.pc = addr; + instr.byte1 = mem->spypeek(addr); + instr.byte2 = mem->spypeek(addr + 1); + instr.byte3 = mem->spypeek(addr + 2); + instr.a = regA; + instr.x = regX; + instr.y = regY; + instr.sp = regSP; + instr.flags = getP(); + + return disassemble(instr, hex); +} + + diff --git a/C64/CPU/CPU.h b/C64/CPU/CPU.h new file mode 100755 index 00000000..b24b5a4d --- /dev/null +++ b/C64/CPU/CPU.h @@ -0,0 +1,628 @@ +/*! + * @header CPU.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CPU_INC +#define _CPU_INC + +#include "CPU_types.h" +#include "CPUInstructions.h" +#include "TimeDelayed.h" + +class Memory; + +/*! @class The virtual 6502 / 6510 processor + */ +class CPU : public VirtualComponent { + + // + // Types + // + + public: + + //! @brief Bit positions of all 7 CPU flags + typedef enum : uint8_t { + C_FLAG = 0x01, + Z_FLAG = 0x02, + I_FLAG = 0x04, + D_FLAG = 0x08, + B_FLAG = 0x10, + V_FLAG = 0x40, + N_FLAG = 0x80 + } Flag; + + //! @brief Possible interrupt sources + typedef enum : uint8_t { + INTSRC_CIA = 0x01, + INTSRC_VIC = 0x02, + INTSRC_VIA1 = 0x04, + INTSRC_VIA2 = 0x08, + INTSRC_EXPANSION = 0x10, + INTSRC_KEYBOARD = 0x20 + } IntSource; + + + // + // References to other components + // + + private: + + //! @brief Reference to the connected virtual memory + Memory *mem; + + + // + // Chip properties + // + + /*! @brief Selected model + * @details Right now, this atrribute is only used to distinguish the + * C64 CPU (MOS6510) from the VC1541 CPU (MOS6502). Hardware + * differences between both models are not emulated. + */ + CPUModel model; + + + // + // Lookup tables + // + + //! @brief Mapping from opcodes to microinstructions + /*! @details The mapped microinstruction is the first microinstruction to + * be executed after the fetch phase (second microcycle). + */ + MicroInstruction actionFunc[256]; + + /*! @brief Textual representation for each opcode + * @note Used by the disassembler, only. + */ + const char *mnemonic[256]; + + /*! @brief Adressing mode of each opcode + * @note Used by the disassembler, only. + */ + AddressingMode addressingMode[256]; + + //! @brief Breakpoint tag for each memory cell + uint8_t breakpoint[65536]; + + + // + // Internal state + // + + public: + + //! @brief Elapsed C64 clock cycles since power up + uint64_t cycle; + + //! @brief Current error state + ErrorState errorState; + + private: + + //! @brief Next microinstruction to be executed + /*! @see executeOneCycle() + */ + MicroInstruction next; + + + // + // Registers + // + + public: + + //! @brief Accumulator + uint8_t regA; + + //! @brief X register + uint8_t regX; + + //! @brief Y register + uint8_t regY; + + //! @brief Program counter + uint16_t regPC; + + //! @brief Stack pointer + uint8_t regSP; + + private: + + /*! @brief Processor status register (flags) + * @details 7 6 5 4 3 2 1 0 + * N O - B D I Z C + */ + uint8_t regP; + + //! @brief Address data (low byte) + uint8_t regADL; + + //! @brief Address data (high byte) + uint8_t regADH; + + //! @brief Input data latch (indirect addressing modes) + uint8_t regIDL; + + //! @brief Data buffer + uint8_t regD; + + /*! @brief Address overflow indicater + * @details Indicates when the page boundary has been crossed. + */ + bool overflow; + + /*! @brief Memory location of the currently executed command. + * @details This variable is overwritten with the current value of the + * program counter (regPC) when the CPU executes the fetch phase + * of an instruction. Hence, this value always contains the start + * address of the currently executed command, even if some + * microcycles of the command have already been computed. + */ + uint16_t pc; + + + // + // Port lines + // + + public: + + /*! @brief Ready line (RDY) + * @details If this line is low, the CPU freezes on the next read access. + * RDY is pulled down by VICII to perform longer lasting read + * operations. + */ + bool rdyLine; + + private: + + //! @brief Cycle of the most recent rising edge of the rdyLine + uint64_t rdyLineUp; + + //! @brief Cycle of the most recent falling edge of the rdyLine + uint64_t rdyLineDown; + + public: + + /*! @brief NMI line (non maskable interrupts) + * @details This variable is usually set to 0 which means that the NMI + * line is in high state. When an external component requests an + * NMI nterrupt, this line is pulled low. In that case, this + * variable has a positive value and the set bits indicate the + * interrupt source. + */ + uint8_t nmiLine; + + + /*! @brief IRQ line (maskable interrupts) + * @details This variable is usually set to 0 which means that the IRQ + * line is in high state. When an external component requests an + * IRQ nterrupt, this line is pulled low. In that case, this + * variable has a positive value and the set bits indicate the + * interrupt source. + */ + uint8_t irqLine; + + private: + + /*! @brief Edge detector of NMI line + * @details https://wiki.nesdev.com/w/index.php/CPU_interrupts + * "The NMI input is connected to an edge detector. This edge + * detector polls the status of the NMI line during φ2 of each + * CPU cycle (i.e., during the second half of each cycle) and + * raises an internal signal if the input goes from being high + * during one cycle to being low during the next. The internal + * signal goes high during φ1 of the cycle that follows the one + * where the edge is detected, and stays high until the NMI has + * been handled." + */ + TimeDelayed edgeDetector = TimeDelayed(1, &cycle); + + /*! @brief Level detector of IRQ line + * @details https://wiki.nesdev.com/w/index.php/CPU_interrupts + * "The IRQ input is connected to a level detector. If a low + * level is detected on the IRQ input during φ2 of a cycle, an + * internal signal is raised during φ1 the following cycle, + * remaining high for that cycle only (or put another way, + * remaining high as long as the IRQ input is low during the + * preceding cycle's φ2). + */ + TimeDelayed levelDetector = TimeDelayed(1, &cycle); + + //! @brief Result of the edge detector polling operation + /*! @details https://wiki.nesdev.com/w/index.php/CPU_interrupts + * "The output from the edge detector and level detector are + * polled at certain points to detect pending interrupts. For + * most instructions, this polling happens during the final + * cycle of the instruction, before the opcode fetch for the + * next instruction. If the polling operation detects that an + * interrupt has been asserted, the next "instruction" executed + * is the interrupt sequence. Many references will claim that + * interrupts are polled during the last cycle of an + * instruction, but this is true only when talking about the + * output from the edge and level detectors." + * Variable is set in macro POLL_INTS (CPUInstructions.h) + */ + bool doNmi; + + //! @brief Result of the level detector polling operation + /*! details https://wiki.nesdev.com/w/index.php/CPU_interrupts + * @note "If both an NMI and an IRQ are pending at the end of an + * instruction, the NMI will be handled and the pending status + * of the IRQ forgotten (though it's likely to be detected again + * during later polling)." + * Variable is set in macro POLL_INTS (CPUInstructions.h) + */ + bool doIrq; + + + // + // Trace buffer + // + + //! @brief Trace buffer size + static const unsigned traceBufferSize = 1024; + + //! @brief Ring buffer for storing the CPU state + RecordedInstruction traceBuffer[traceBufferSize]; + + //! @brief Trace buffer read pointer + unsigned readPtr; + + //! @brief Trace buffer write pointer + unsigned writePtr; + + + // + //! @functiongroup Constructing and destructing + // + + public: + + //! @brief Constructor + CPU(CPUModel model, Memory *mem); + + //! @brief Destructor + ~CPU(); + + //! @brief Returns true if this is the C64's CPU + bool isC64CPU() { return model == MOS_6510; } + + private: + + //! @brief Registers a single opcode + /*! @details Initializes all lookup table entries for this opcode. + */ + void registerCallback(uint8_t opcode, const char *mnemonic, + AddressingMode mode, MicroInstruction mInstr); + + //! @brief Registers the complete instruction set. + void registerInstructions(); + + //! @brief Registers the legal instruction set, only. + void registerLegalInstructions(); + + //! @brief Registers the illegal instructions set, only. + void registerIllegalInstructions(); + + + // + //! @functiongroup Methods from VirtualComponent + // + + public: + + void reset(); + void dump(); + size_t stateSize(); + void didLoadFromBuffer(uint8_t **buffer); + void didSaveToBuffer(uint8_t **buffer); + + + // + //! @functiongroup Gathering debug information + // + + //! @brief Returns the internal state. + CPUInfo getInfo(); + + + // + //! @functiongroup Handling registers and flags + // + + /*! @brief Returns the frozen program counter. + * @note This function returns variable pc that always points to the + * beginning of the currently executed command. If execution is + * not in microcycle 0 (fetch phase) and the currently executed + * command spans over multiple bytes in memory, the program + * counter (regPC) may already have been incremented. + */ + uint16_t getPC() { return pc; } + + //! @brief Redirects the CPU to a new instruction in memory. + void jumpToAddress(uint16_t addr) { pc = regPC = addr; next = fetch; } + + //! @brief Returns N_FLAG, if Negative flag is set, 0 otherwise. + uint8_t getN() { return regP & N_FLAG; } + + //! @brief 0: Negative-flag is cleared, any other value: flag is set. + void setN(uint8_t bit) { bit ? regP |= N_FLAG : regP &= ~N_FLAG; } + + //! @brief Returns V_FLAG, if Overflow flag is set, 0 otherwise. + uint8_t getV() { return regP & V_FLAG; } + + //! @brief 0: Overflow-flag is cleared, any other value: flag is set. + void setV(uint8_t bit) { bit ? regP |= V_FLAG : regP &= ~V_FLAG; } + + //! @brief Returns B_FLAG, if Break flag is set, 0 otherwise. + uint8_t getB() { return regP & B_FLAG; } + + //! @brief 0: Break-flag is cleared, any other value: flag is set. + void setB(uint8_t bit) { bit ? regP |= B_FLAG : regP &= ~B_FLAG; } + + //! @brief Returns D_FLAG, if Decimal flag is set, 0 otherwise. + uint8_t getD() { return regP & D_FLAG; } + + //! @brief 0: Decimal-flag is cleared, any other value: flag is set. + void setD(uint8_t bit) { bit ? regP |= D_FLAG : regP &= ~D_FLAG; } + + //! @brief Returns I_FLAG, if Interrupt flag is set, 0 otherwise. + uint8_t getI() { return regP & I_FLAG; } + + //! @brief 0: Interrupt-flag is cleared, any other value: flag is set. + void setI(uint8_t bit) { bit ? regP |= I_FLAG : regP &= ~I_FLAG; } + + //! @brief Returns Z_FLAG, if Zero flag is set, 0 otherwise. + uint8_t getZ() { return regP & Z_FLAG; } + + //! @brief 0: Zero-flag is cleared, any other value: flag is set. + void setZ(uint8_t bit) { bit ? regP |= Z_FLAG : regP &= ~Z_FLAG; } + + //! @brief Returns C_FLAG, if Carry flag is set, 0 otherwise. + uint8_t getC() { return regP & C_FLAG; } + + //! @brief 0: Carry-flag is cleared, any other value: flag is set. + void setC(uint8_t bit) { bit ? regP |= C_FLAG : regP &= ~C_FLAG; } + + private: + + /*! @brief Returns the contents of the status register + * @details Each bit in the status register corresponds to the value of + * a single flag, except bit 5 which is always set. + */ + uint8_t getP() { return regP | 0b00100000; } + + /*! @brief Returns the status register without the B flag + * @details The bit position of the B flag is always 0. This function is + * needed for proper interrupt handling. When an IRQ or NMI is + * triggered internally, the status register is pushed on the + * stack with the B-flag cleared. + */ + uint8_t getPWithClearedB() { return getP() & 0b11101111; } + + //! @brief Writes a value to the status register. + void setP(uint8_t p) { regP = p; } + + //! @brief Writes a value to the status register without overwriting B. + void setPWithoutB(uint8_t p) { regP = (p & 0b11101111) | (regP & 0b00010000); } + + //! @brief Changes low byte of the program counter only. + void setPCL(uint8_t lo) { regPC = (regPC & 0xff00) | lo; } + + //! @brief Changes high byte of the program counter only. + void setPCH(uint8_t hi) { regPC = (regPC & 0x00ff) | ((uint16_t)hi << 8); } + + //! @brief Increments the program counter by the specified amount. + void incPC(uint8_t offset = 1) { regPC += offset; } + + /*! @brief Increments the program counter's low byte. + * @note The high byte does not change. + */ + void incPCL(uint8_t offset = 1) { setPCL(LO_BYTE(regPC) + offset); } + + /*! @brief Increments the program counter's high byte. + * @note The low byte does not change. + */ + void incPCH(uint8_t offset = 1) { setPCH(HI_BYTE(regPC) + offset); } + + //! @brief Loads the accumulator. The Z- and N-flag may change. + void loadA(uint8_t a) { regA = a; setN(a & 0x80); setZ(a == 0); } + + //! @brief Loads the X register. The Z- and N-flag may change. + void loadX(uint8_t x) { regX = x; setN(x & 0x80); setZ(x == 0); } + + //! @brief Loads the Y register. The Z- and N-flag may change. + void loadY(uint8_t y) { regY = y; setN(y & 0x80); setZ(y == 0); } + + + // + //! @functiongroup Performing ALU operations (CPUInstructions.cpp) + // + + void adc(uint8_t op); + void adc_binary(uint8_t op); + void adc_bcd(uint8_t op); + void sbc(uint8_t op); + void sbc_binary(uint8_t op); + void sbc_bcd(uint8_t op); + void branch(int8_t offset); + void cmp(uint8_t op1, uint8_t op2); + uint8_t ror(uint8_t op); + uint8_t rol(uint8_t op); + + + // + //! @functiongroup Handling interrupts + // + + public: + + /*! @brief Pulls down the NMI line. + * @details Pulling down the NMI line requests the CPU to interrupt. + */ + void pullDownNmiLine(IntSource source); + + /*! @brief Releases the NMI line. + * @note Other sources might still hold the line down. + */ + void releaseNmiLine(IntSource source); + + /*! @brief Pulls down the IRQ line. + * @details Pulling down the IRQ line requests the CPU to interrupt. + */ + void pullDownIrqLine(IntSource source); + + /*! @brief Releases the IRQ line. + * @note Other sources might still hold the line down. + */ + void releaseIrqLine(IntSource source); + + //! @brief Sets the RDY line. + void setRDY(bool value); + + + // + //! @functiongroup Examining the currently executed instruction + // + + /*! @brief Returns the length of an instruction in bytes. + * @result Integer value between 1 and 3. + */ + unsigned getLengthOfInstruction(uint8_t opcode); + + /*! @brief Returns the length of instruction in bytes. + * @result Integer value between 1 and 3. + */ + unsigned getLengthOfInstructionAtAddress(uint16_t addr) { + return getLengthOfInstruction(mem->spypeek(addr)); } + + /*! @brief Returns the length of the currently executed instruction. + * @result Integer value between 1 and 3. + */ + unsigned getLengthOfCurrentInstruction() { + return getLengthOfInstructionAtAddress(pc); } + + /*! @brief Returns the address of the next instruction to execute. + * @result Integer value between 1 and 3. + */ + uint16_t getAddressOfNextInstruction() { + return pc + getLengthOfCurrentInstruction(); } + + /*! @brief Returns true if the next microcycle is the fetch cycle. + * @details The fetch cycle is the first microinstruction of each command. + */ + bool inFetchPhase() { return next == fetch; } + + + // + //! @functiongroup Executing the device + // + + /*! @brief Executes the next micro instruction. + * @return true, if the micro instruction was processed successfully. + * false, if the CPU was halted, e.g., by reaching a breakpoint. + */ + bool executeOneCycle(); + + //! @brief Returns the current error state. + ErrorState getErrorState() { return errorState; } + + //! @brief Sets the error state. + void setErrorState(ErrorState state); + + //! @brief Sets the error state back to normal. + void clearErrorState() { setErrorState(CPU_OK); } + + + // + //! @functiongroup Handling breakpoints + // + + //! @brief Checks if a hard breakpoint is set at the provided address. + bool hardBreakpoint(uint16_t addr) { return (breakpoint[addr] & HARD_BREAKPOINT) != 0; } + + //! @brief Sets a hard breakpoint at the provided address. + void setHardBreakpoint(uint16_t addr) { breakpoint[addr] |= HARD_BREAKPOINT; } + + //! @brief Deletes a hard breakpoint at the provided address. + void deleteHardBreakpoint(uint16_t addr) { breakpoint[addr] &= ~HARD_BREAKPOINT; } + + //! @brief Sets or deletes a hard breakpoint at the provided address. + void toggleHardBreakpoint(uint16_t addr) { breakpoint[addr] ^= HARD_BREAKPOINT; } + + //! @brief Checks if a soft breakpoint is set at the provided address. + bool softBreakpoint(uint16_t addr) { return (breakpoint[addr] & SOFT_BREAKPOINT) != 0; } + + //! @brief Sets a soft breakpoint at the provided address. + void setSoftBreakpoint(uint16_t addr) { breakpoint[addr] |= SOFT_BREAKPOINT; } + + //! @brief Deletes a soft breakpoint at the specified address. + void deleteSoftBreakpoint(uint16_t addr) { breakpoint[addr] &= ~SOFT_BREAKPOINT; } + + //! @brief Sets or deletes a hard breakpoint at the specified address. + void toggleSoftBreakpoint(uint16_t addr) { breakpoint[addr] ^= SOFT_BREAKPOINT; } + + + // + //! @functiongroup Tracing the program execution + // + + //! @brief Clears the trace buffer. + void clearTraceBuffer() { readPtr = writePtr = 0; } + + //! @brief Returns the number of recorded instructions. + unsigned recordedInstructions(); + + //! @brief Records an instruction. + void recordInstruction(); + + /*! @brief Reads and removes a recorded instruction from the trace buffer. + * @note The trace buffer must not be empty. + */ + RecordedInstruction readRecordedInstruction(); + + /*! @brief Reads a recorded instruction from the trace buffer. + * @note 'previous' must be smaller than the number of recorded + * instructions. + */ + RecordedInstruction readRecordedInstruction(unsigned previous); + + + // + //! @functiongroup Disassembling instructions + // + + //! @brief Disassembles a previously recorded instruction + DisassembledInstruction disassemble(RecordedInstruction instr, bool hex); + + //! @brief Disassembles an instruction at the specified memory location + DisassembledInstruction disassemble(uint16_t addr, bool hex); + + //! @brief Disassembles the current instruction. + DisassembledInstruction disassemble(bool hex) { return disassemble(pc, hex); } + +}; + +#endif diff --git a/C64/CPU/CPUInstructions.cpp b/C64/CPU/CPUInstructions.cpp new file mode 100755 index 00000000..b0561330 --- /dev/null +++ b/C64/CPU/CPUInstructions.cpp @@ -0,0 +1,3241 @@ +/*! + * @file CPUInstructions.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +CPU::adc(uint8_t op) +{ + if (getD()) + adc_bcd(op); + else + adc_binary(op); +} + +void +CPU::adc_binary(uint8_t op) +{ + uint16_t sum = regA + op + (getC() ? 1 : 0); + + setC(sum > 255); + setV(!((regA ^ op) & 0x80) && ((regA ^ sum) & 0x80)); + loadA((uint8_t)sum); +} + +void +CPU::adc_bcd(uint8_t op) +{ + uint16_t sum = regA + op + (getC() ? 1 : 0); + uint8_t highDigit = (regA >> 4) + (op >> 4); + uint8_t lowDigit = (regA & 0x0F) + (op & 0x0F) + (getC() ? 1 : 0); + + // Check for overflow conditions + // If an overflow occurs on a BCD digit, it needs to be fixed by adding the pseudo-tetrade 0110 (=6) + if (lowDigit > 9) { + lowDigit = lowDigit + 6; + } + if (lowDigit > 0x0F) { + highDigit++; + } + + setZ((sum & 0xFF) == 0); + setN(highDigit & 0x08); + setV((((highDigit << 4) ^ regA) & 0x80) && !((regA ^ op) & 0x80)); + + if (highDigit > 9) { + highDigit = (highDigit + 6); + } + if (highDigit > 0x0F) { + setC(1); + } else { + setC(0); + } + + lowDigit &= 0x0F; + regA = (uint8_t)((highDigit << 4) | lowDigit); +} + +void +CPU::cmp(uint8_t op1, uint8_t op2) +{ + uint8_t tmp = op1 - op2; + + setC(op1 >= op2); + setN(tmp & 128); + setZ(tmp == 0); +} + +void +CPU::sbc(uint8_t op) +{ + if (getD()) + sbc_bcd(op); + else + sbc_binary(op); +} + +void +CPU::sbc_binary(uint8_t op) +{ + uint16_t sum = regA - op - (getC() ? 0 : 1); + + setC(sum <= 255); + setV(((regA ^ sum) & 0x80) && ((regA ^ op) & 0x80)); + loadA((uint8_t)sum); +} + +void +CPU::sbc_bcd(uint8_t op) +{ + uint16_t sum = regA - op - (getC() ? 0 : 1); + uint8_t highDigit = (regA >> 4) - (op >> 4); + uint8_t lowDigit = (regA & 0x0F) - (op & 0x0F) - (getC() ? 0 : 1); + + // Check for underflow conditions + // If an overflow occurs on a BCD digit, it needs to be fixed by subtracting the pseudo-tetrade 0110 (=6) + if (lowDigit & 0x10) { + lowDigit = lowDigit - 6; + highDigit--; + } + if (highDigit & 0x10) { + highDigit = highDigit - 6; + } + + setC(sum < 0x100); + setV(((regA ^ sum) & 0x80) && ((regA ^ op) & 0x80)); + setZ((sum & 0xFF) == 0); + setN(sum & 0x80); + + regA = (uint8_t)((highDigit << 4) | (lowDigit & 0x0f)); +} + +void +CPU::registerCallback(uint8_t opcode, const char *mnc, + AddressingMode mode, MicroInstruction mInstr) +{ + // Table is write once! + assert(mInstr == JAM || actionFunc[opcode] == JAM); + + mnemonic[opcode] = mnc; + addressingMode[opcode] = mode; + actionFunc[opcode] = mInstr; +} + +void CPU::registerInstructions() +{ + for (int i=0; i<256; i++) { + registerCallback(i, "???", ADDR_IMPLIED, JAM); + } + registerLegalInstructions(); + registerIllegalInstructions(); +} + +void CPU::registerLegalInstructions() +{ + registerCallback(0x69, "ADC", ADDR_IMMEDIATE, ADC_imm); + registerCallback(0x65, "ADC", ADDR_ZERO_PAGE, ADC_zpg); + registerCallback(0x75, "ADC", ADDR_ZERO_PAGE_X, ADC_zpg_x); + registerCallback(0x6D, "ADC", ADDR_ABSOLUTE, ADC_abs); + registerCallback(0x7D, "ADC", ADDR_ABSOLUTE_X, ADC_abs_x); + registerCallback(0x79, "ADC", ADDR_ABSOLUTE_Y, ADC_abs_y); + registerCallback(0x61, "ADC", ADDR_INDIRECT_X, ADC_ind_x); + registerCallback(0x71, "ADC", ADDR_INDIRECT_Y, ADC_ind_y); + + registerCallback(0x29, "AND", ADDR_IMMEDIATE, AND_imm); + registerCallback(0x25, "AND", ADDR_ZERO_PAGE, AND_zpg); + registerCallback(0x35, "AND", ADDR_ZERO_PAGE_X, AND_zpg_x); + registerCallback(0x2D, "AND", ADDR_ABSOLUTE, AND_abs); + registerCallback(0x3D, "AND", ADDR_ABSOLUTE_X, AND_abs_x); + registerCallback(0x39, "AND", ADDR_ABSOLUTE_Y, AND_abs_y); + registerCallback(0x21, "AND", ADDR_INDIRECT_X, AND_ind_x); + registerCallback(0x31, "AND", ADDR_INDIRECT_Y, AND_ind_y); + + registerCallback(0x0A, "ASL", ADDR_ACCUMULATOR, ASL_acc); + registerCallback(0x06, "ASL", ADDR_ZERO_PAGE, ASL_zpg); + registerCallback(0x16, "ASL", ADDR_ZERO_PAGE_X, ASL_zpg_x); + registerCallback(0x0E, "ASL", ADDR_ABSOLUTE, ASL_abs); + registerCallback(0x1E, "ASL", ADDR_ABSOLUTE_X, ASL_abs_x); + + registerCallback(0x90, "BCC", ADDR_RELATIVE, BCC_rel); + registerCallback(0xB0, "BCS", ADDR_RELATIVE, BCS_rel); + registerCallback(0xF0, "BEQ", ADDR_RELATIVE, BEQ_rel); + + registerCallback(0x24, "BIT", ADDR_ZERO_PAGE, BIT_zpg); + registerCallback(0x2C, "BIT", ADDR_ABSOLUTE, BIT_abs); + + registerCallback(0x30, "BMI", ADDR_RELATIVE, BMI_rel); + registerCallback(0xD0, "BNE", ADDR_RELATIVE, BNE_rel); + registerCallback(0x10, "BPL", ADDR_RELATIVE, BPL_rel); + registerCallback(0x00, "BRK", ADDR_IMPLIED, BRK); + registerCallback(0x50, "BVC", ADDR_RELATIVE, BVC_rel); + registerCallback(0x70, "BVS", ADDR_RELATIVE, BVS_rel); + + registerCallback(0x18, "CLC", ADDR_IMPLIED, CLC); + registerCallback(0xD8, "CLD", ADDR_IMPLIED, CLD); + registerCallback(0x58, "CLI", ADDR_IMPLIED, CLI); + registerCallback(0xB8, "CLV", ADDR_IMPLIED, CLV); + + registerCallback(0xC9, "CMP", ADDR_IMMEDIATE, CMP_imm); + registerCallback(0xC5, "CMP", ADDR_ZERO_PAGE, CMP_zpg); + registerCallback(0xD5, "CMP", ADDR_ZERO_PAGE_X, CMP_zpg_x); + registerCallback(0xCD, "CMP", ADDR_ABSOLUTE, CMP_abs); + registerCallback(0xDD, "CMP", ADDR_ABSOLUTE_X, CMP_abs_x); + registerCallback(0xD9, "CMP", ADDR_ABSOLUTE_Y, CMP_abs_y); + registerCallback(0xC1, "CMP", ADDR_INDIRECT_X, CMP_ind_x); + registerCallback(0xD1, "CMP", ADDR_INDIRECT_Y, CMP_ind_y); + + registerCallback(0xE0, "CPX", ADDR_IMMEDIATE, CPX_imm); + registerCallback(0xE4, "CPX", ADDR_ZERO_PAGE, CPX_zpg); + registerCallback(0xEC, "CPX", ADDR_ABSOLUTE, CPX_abs); + + registerCallback(0xC0, "CPY", ADDR_IMMEDIATE, CPY_imm); + registerCallback(0xC4, "CPY", ADDR_ZERO_PAGE, CPY_zpg); + registerCallback(0xCC, "CPY", ADDR_ABSOLUTE, CPY_abs); + + registerCallback(0xC6, "DEC", ADDR_ZERO_PAGE, DEC_zpg); + registerCallback(0xD6, "DEC", ADDR_ZERO_PAGE_X, DEC_zpg_x); + registerCallback(0xCE, "DEC", ADDR_ABSOLUTE, DEC_abs); + registerCallback(0xDE, "DEC", ADDR_ABSOLUTE_X, DEC_abs_x); + + registerCallback(0xCA, "DEX", ADDR_IMPLIED, DEX); + registerCallback(0x88, "DEY", ADDR_IMPLIED, DEY); + + registerCallback(0x49, "EOR", ADDR_IMMEDIATE, EOR_imm); + registerCallback(0x45, "EOR", ADDR_ZERO_PAGE, EOR_zpg); + registerCallback(0x55, "EOR", ADDR_ZERO_PAGE_X, EOR_zpg_x); + registerCallback(0x4D, "EOR", ADDR_ABSOLUTE, EOR_abs); + registerCallback(0x5D, "EOR", ADDR_ABSOLUTE_X, EOR_abs_x); + registerCallback(0x59, "EOR", ADDR_ABSOLUTE_Y, EOR_abs_y); + registerCallback(0x41, "EOR", ADDR_INDIRECT_X, EOR_ind_x); + registerCallback(0x51, "EOR", ADDR_INDIRECT_Y, EOR_ind_y); + + registerCallback(0xE6, "INC", ADDR_ZERO_PAGE, INC_zpg); + registerCallback(0xF6, "INC", ADDR_ZERO_PAGE_X, INC_zpg_x); + registerCallback(0xEE, "INC", ADDR_ABSOLUTE, INC_abs); + registerCallback(0xFE, "INC", ADDR_ABSOLUTE_X, INC_abs_x); + + registerCallback(0xE8, "INX", ADDR_IMPLIED, INX); + registerCallback(0xC8, "INY", ADDR_IMPLIED, INY); + + registerCallback(0x4C, "JMP", ADDR_DIRECT, JMP_abs); + registerCallback(0x6C, "JMP", ADDR_INDIRECT, JMP_abs_ind); + + registerCallback(0x20, "JSR", ADDR_DIRECT, JSR); + + registerCallback(0xA9, "LDA", ADDR_IMMEDIATE, LDA_imm); + registerCallback(0xA5, "LDA", ADDR_ZERO_PAGE, LDA_zpg); + registerCallback(0xB5, "LDA", ADDR_ZERO_PAGE_X, LDA_zpg_x); + registerCallback(0xAD, "LDA", ADDR_ABSOLUTE, LDA_abs); + registerCallback(0xBD, "LDA", ADDR_ABSOLUTE_X, LDA_abs_x); + registerCallback(0xB9, "LDA", ADDR_ABSOLUTE_Y, LDA_abs_y); + registerCallback(0xA1, "LDA", ADDR_INDIRECT_X, LDA_ind_x); + registerCallback(0xB1, "LDA", ADDR_INDIRECT_Y, LDA_ind_y); + + registerCallback(0xA2, "LDX", ADDR_IMMEDIATE, LDX_imm); + registerCallback(0xA6, "LDX", ADDR_ZERO_PAGE, LDX_zpg); + registerCallback(0xB6, "LDX", ADDR_ZERO_PAGE_Y,LDX_zpg_y); + registerCallback(0xAE, "LDX", ADDR_ABSOLUTE, LDX_abs); + registerCallback(0xBE, "LDX", ADDR_ABSOLUTE_Y, LDX_abs_y); + + registerCallback(0xA0, "LDY", ADDR_IMMEDIATE, LDY_imm); + registerCallback(0xA4, "LDY", ADDR_ZERO_PAGE, LDY_zpg); + registerCallback(0xB4, "LDY", ADDR_ZERO_PAGE_X, LDY_zpg_x); + registerCallback(0xAC, "LDY", ADDR_ABSOLUTE, LDY_abs); + registerCallback(0xBC, "LDY", ADDR_ABSOLUTE_X, LDY_abs_x); + + registerCallback(0x4A, "LSR", ADDR_ACCUMULATOR, LSR_acc); + registerCallback(0x46, "LSR", ADDR_ZERO_PAGE, LSR_zpg); + registerCallback(0x56, "LSR", ADDR_ZERO_PAGE_X, LSR_zpg_x); + registerCallback(0x4E, "LSR", ADDR_ABSOLUTE, LSR_abs); + registerCallback(0x5E, "LSR", ADDR_ABSOLUTE_X, LSR_abs_x); + + registerCallback(0xEA, "NOP", ADDR_IMPLIED, NOP); + + registerCallback(0x09, "ORA", ADDR_IMMEDIATE, ORA_imm); + registerCallback(0x05, "ORA", ADDR_ZERO_PAGE, ORA_zpg); + registerCallback(0x15, "ORA", ADDR_ZERO_PAGE_X, ORA_zpg_x); + registerCallback(0x0D, "ORA", ADDR_ABSOLUTE, ORA_abs); + registerCallback(0x1D, "ORA", ADDR_ABSOLUTE_X, ORA_abs_x); + registerCallback(0x19, "ORA", ADDR_ABSOLUTE_Y, ORA_abs_y); + registerCallback(0x01, "ORA", ADDR_INDIRECT_X, ORA_ind_x); + registerCallback(0x11, "ORA", ADDR_INDIRECT_Y, ORA_ind_y); + + registerCallback(0x48, "PHA", ADDR_IMPLIED, PHA); + registerCallback(0x08, "PHP", ADDR_IMPLIED, PHP); + registerCallback(0x68, "PLA", ADDR_IMPLIED, PLA); + registerCallback(0x28, "PLP", ADDR_IMPLIED, PLP); + + registerCallback(0x2A, "ROL", ADDR_ACCUMULATOR, ROL_acc); + registerCallback(0x26, "ROL", ADDR_ZERO_PAGE, ROL_zpg); + registerCallback(0x36, "ROL", ADDR_ZERO_PAGE_X, ROL_zpg_x); + registerCallback(0x2E, "ROL", ADDR_ABSOLUTE, ROL_abs); + registerCallback(0x3E, "ROL", ADDR_ABSOLUTE_X, ROL_abs_x); + + registerCallback(0x6A, "ROR", ADDR_ACCUMULATOR, ROR_acc); + registerCallback(0x66, "ROR", ADDR_ZERO_PAGE, ROR_zpg); + registerCallback(0x76, "ROR", ADDR_ZERO_PAGE_X, ROR_zpg_x); + registerCallback(0x6E, "ROR", ADDR_ABSOLUTE, ROR_abs); + registerCallback(0x7E, "ROR", ADDR_ABSOLUTE_X, ROR_abs_x); + + registerCallback(0x40, "RTI", ADDR_IMPLIED, RTI); + registerCallback(0x60, "RTS", ADDR_IMPLIED, RTS); + + registerCallback(0xE9, "SBC", ADDR_IMMEDIATE, SBC_imm); + registerCallback(0xE5, "SBC", ADDR_ZERO_PAGE, SBC_zpg); + registerCallback(0xF5, "SBC", ADDR_ZERO_PAGE_X, SBC_zpg_x); + registerCallback(0xED, "SBC", ADDR_ABSOLUTE, SBC_abs); + registerCallback(0xFD, "SBC", ADDR_ABSOLUTE_X, SBC_abs_x); + registerCallback(0xF9, "SBC", ADDR_ABSOLUTE_Y, SBC_abs_y); + registerCallback(0xE1, "SBC", ADDR_INDIRECT_X, SBC_ind_x); + registerCallback(0xF1, "SBC", ADDR_INDIRECT_Y, SBC_ind_y); + + registerCallback(0x38, "SEC", ADDR_IMPLIED, SEC); + registerCallback(0xF8, "SED", ADDR_IMPLIED, SED); + registerCallback(0x78, "SEI", ADDR_IMPLIED, SEI); + + registerCallback(0x85, "STA", ADDR_ZERO_PAGE, STA_zpg); + registerCallback(0x95, "STA", ADDR_ZERO_PAGE_X, STA_zpg_x); + registerCallback(0x8D, "STA", ADDR_ABSOLUTE, STA_abs); + registerCallback(0x9D, "STA", ADDR_ABSOLUTE_X, STA_abs_x); + registerCallback(0x99, "STA", ADDR_ABSOLUTE_Y, STA_abs_y); + registerCallback(0x81, "STA", ADDR_INDIRECT_X, STA_ind_x); + registerCallback(0x91, "STA", ADDR_INDIRECT_Y, STA_ind_y); + + registerCallback(0x86, "STX", ADDR_ZERO_PAGE, STX_zpg); + registerCallback(0x96, "STX", ADDR_ZERO_PAGE_Y, STX_zpg_y); + registerCallback(0x8E, "STX", ADDR_ABSOLUTE, STX_abs); + + registerCallback(0x84, "STY", ADDR_ZERO_PAGE, STY_zpg); + registerCallback(0x94, "STY", ADDR_ZERO_PAGE_X, STY_zpg_x); + registerCallback(0x8C, "STY", ADDR_ABSOLUTE, STY_abs); + + registerCallback(0xAA, "TAX", ADDR_IMPLIED, TAX); + registerCallback(0xA8, "TAY", ADDR_IMPLIED, TAY); + registerCallback(0xBA, "TSX", ADDR_IMPLIED, TSX); + registerCallback(0x8A, "TXA", ADDR_IMPLIED, TXA); + registerCallback(0x9A, "TXS", ADDR_IMPLIED, TXS); + registerCallback(0x98, "TYA", ADDR_IMPLIED, TYA); +} + +void +CPU::registerIllegalInstructions() +{ + registerCallback(0x93, "SHA*", ADDR_INDIRECT_Y, SHA_ind_y); + registerCallback(0x9F, "SHA*", ADDR_ABSOLUTE_Y, SHA_abs_y); + + registerCallback(0x4B, "ALR*", ADDR_IMMEDIATE, ALR_imm); + + registerCallback(0x0B, "ANC*", ADDR_IMMEDIATE, ANC_imm); + registerCallback(0x2B, "ANC*", ADDR_IMMEDIATE, ANC_imm); + + registerCallback(0x8B, "ANE*", ADDR_IMMEDIATE, ANE_imm); + + registerCallback(0x6B, "ARR*", ADDR_IMMEDIATE, ARR_imm); + registerCallback(0xCB, "AXS*", ADDR_IMMEDIATE, AXS_imm); + + registerCallback(0xC7, "DCP*", ADDR_ZERO_PAGE, DCP_zpg); + registerCallback(0xD7, "DCP*", ADDR_ZERO_PAGE_X, DCP_zpg_x); + registerCallback(0xC3, "DCP*", ADDR_INDIRECT_X, DCP_ind_x); + registerCallback(0xD3, "DCP*", ADDR_INDIRECT_Y, DCP_ind_y); + registerCallback(0xCF, "DCP*", ADDR_ABSOLUTE, DCP_abs); + registerCallback(0xDF, "DCP*", ADDR_ABSOLUTE_X, DCP_abs_x); + registerCallback(0xDB, "DCP*", ADDR_ABSOLUTE_Y, DCP_abs_y); + + registerCallback(0xE7, "ISC*", ADDR_ZERO_PAGE, ISC_zpg); + registerCallback(0xF7, "ISC*", ADDR_ZERO_PAGE_X, ISC_zpg_x); + registerCallback(0xE3, "ISC*", ADDR_INDIRECT_X, ISC_ind_x); + registerCallback(0xF3, "ISC*", ADDR_INDIRECT_Y, ISC_ind_y); + registerCallback(0xEF, "ISC*", ADDR_ABSOLUTE, ISC_abs); + registerCallback(0xFF, "ISC*", ADDR_ABSOLUTE_X, ISC_abs_x); + registerCallback(0xFB, "ISC*", ADDR_ABSOLUTE_Y, ISC_abs_y); + + registerCallback(0xBB, "LAS*", ADDR_ABSOLUTE_Y, LAS_abs_y); + + registerCallback(0xA7, "LAX*", ADDR_ZERO_PAGE, LAX_zpg); + registerCallback(0xB7, "LAX*", ADDR_ZERO_PAGE_Y, LAX_zpg_y); + registerCallback(0xA3, "LAX*", ADDR_INDIRECT_X, LAX_ind_x); + registerCallback(0xB3, "LAX*", ADDR_INDIRECT_Y, LAX_ind_y); + registerCallback(0xAF, "LAX*", ADDR_ABSOLUTE, LAX_abs); + registerCallback(0xBF, "LAX*", ADDR_ABSOLUTE_Y, LAX_abs_y); + + registerCallback(0xAB, "LXA*", ADDR_IMMEDIATE, LXA_imm); + + registerCallback(0x80, "NOP*", ADDR_IMMEDIATE, NOP_imm); + registerCallback(0x82, "NOP*", ADDR_IMMEDIATE, NOP_imm); + registerCallback(0x89, "NOP*", ADDR_IMMEDIATE, NOP_imm); + registerCallback(0xC2, "NOP*", ADDR_IMMEDIATE, NOP_imm); + registerCallback(0xE2, "NOP*", ADDR_IMMEDIATE, NOP_imm); + registerCallback(0x1A, "NOP*", ADDR_IMPLIED, NOP); + registerCallback(0x3A, "NOP*", ADDR_IMPLIED, NOP); + registerCallback(0x5A, "NOP*", ADDR_IMPLIED, NOP); + registerCallback(0x7A, "NOP*", ADDR_IMPLIED, NOP); + registerCallback(0xDA, "NOP*", ADDR_IMPLIED, NOP); + registerCallback(0xFA, "NOP*", ADDR_IMPLIED, NOP); + registerCallback(0x04, "NOP*", ADDR_ZERO_PAGE, NOP_zpg); + registerCallback(0x44, "NOP*", ADDR_ZERO_PAGE, NOP_zpg); + registerCallback(0x64, "NOP*", ADDR_ZERO_PAGE, NOP_zpg); + registerCallback(0x0C, "NOP*", ADDR_ABSOLUTE, NOP_abs); + registerCallback(0x14, "NOP*", ADDR_ZERO_PAGE_X, NOP_zpg_x); + registerCallback(0x34, "NOP*", ADDR_ZERO_PAGE_X, NOP_zpg_x); + registerCallback(0x54, "NOP*", ADDR_ZERO_PAGE_X, NOP_zpg_x); + registerCallback(0x74, "NOP*", ADDR_ZERO_PAGE_X, NOP_zpg_x); + registerCallback(0xD4, "NOP*", ADDR_ZERO_PAGE_X, NOP_zpg_x); + registerCallback(0xF4, "NOP*", ADDR_ZERO_PAGE_X, NOP_zpg_x); + registerCallback(0x1C, "NOP*", ADDR_ABSOLUTE_X, NOP_abs_x); + registerCallback(0x3C, "NOP*", ADDR_ABSOLUTE_X, NOP_abs_x); + registerCallback(0x5C, "NOP*", ADDR_ABSOLUTE_X, NOP_abs_x); + registerCallback(0x7C, "NOP*", ADDR_ABSOLUTE_X, NOP_abs_x); + registerCallback(0xDC, "NOP*", ADDR_ABSOLUTE_X, NOP_abs_x); + registerCallback(0xFC, "NOP*", ADDR_ABSOLUTE_X, NOP_abs_x); + + registerCallback(0x27, "RLA*", ADDR_ZERO_PAGE, RLA_zpg); + registerCallback(0x37, "RLA*", ADDR_ZERO_PAGE_X, RLA_zpg_x); + registerCallback(0x23, "RLA*", ADDR_INDIRECT_X, RLA_ind_x); + registerCallback(0x33, "RLA*", ADDR_INDIRECT_Y, RLA_ind_y); + registerCallback(0x2F, "RLA*", ADDR_ABSOLUTE, RLA_abs); + registerCallback(0x3F, "RLA*", ADDR_ABSOLUTE_X, RLA_abs_x); + registerCallback(0x3B, "RLA*", ADDR_ABSOLUTE_Y, RLA_abs_y); + + registerCallback(0x67, "RRA*", ADDR_ZERO_PAGE, RRA_zpg); + registerCallback(0x77, "RRA*", ADDR_ZERO_PAGE_X, RRA_zpg_x); + registerCallback(0x63, "RRA*", ADDR_INDIRECT_X, RRA_ind_x); + registerCallback(0x73, "RRA*", ADDR_INDIRECT_Y, RRA_ind_y); + registerCallback(0x6F, "RRA*", ADDR_ABSOLUTE, RRA_abs); + registerCallback(0x7F, "RRA*", ADDR_ABSOLUTE_X, RRA_abs_x); + registerCallback(0x7B, "RRA*", ADDR_ABSOLUTE_Y, RRA_abs_y); + + registerCallback(0x87, "SAX*", ADDR_ZERO_PAGE, SAX_zpg); + registerCallback(0x97, "SAX*", ADDR_ZERO_PAGE_Y, SAX_zpg_y); + registerCallback(0x83, "SAX*", ADDR_INDIRECT_X, SAX_ind_x); + registerCallback(0x8F, "SAX*", ADDR_ABSOLUTE, SAX_abs); + + registerCallback(0xEB, "SBC*", ADDR_IMMEDIATE, SBC_imm); + + registerCallback(0x9E, "SHX*", ADDR_ABSOLUTE_Y, SHX_abs_y); + registerCallback(0x9C, "SHY*", ADDR_ABSOLUTE_X, SHY_abs_x); + + registerCallback(0x07, "SLO*", ADDR_ZERO_PAGE, SLO_zpg); + registerCallback(0x17, "SLO*", ADDR_ZERO_PAGE_X, SLO_zpg_x); + registerCallback(0x03, "SLO*", ADDR_INDIRECT_X, SLO_ind_x); + registerCallback(0x13, "SLO*", ADDR_INDIRECT_Y, SLO_ind_y); + registerCallback(0x0F, "SLO*", ADDR_ABSOLUTE, SLO_abs); + registerCallback(0x1F, "SLO*", ADDR_ABSOLUTE_X, SLO_abs_x); + registerCallback(0x1B, "SLO*", ADDR_ABSOLUTE_Y, SLO_abs_y); + + registerCallback(0x47, "SRE*", ADDR_ZERO_PAGE, SRE_zpg); + registerCallback(0x57, "SRE*", ADDR_ZERO_PAGE_X, SRE_zpg_x); + registerCallback(0x43, "SRE*", ADDR_INDIRECT_X, SRE_ind_x); + registerCallback(0x53, "SRE*", ADDR_INDIRECT_Y, SRE_ind_y); + registerCallback(0x4F, "SRE*", ADDR_ABSOLUTE, SRE_abs); + registerCallback(0x5F, "SRE*", ADDR_ABSOLUTE_X, SRE_abs_x); + registerCallback(0x5B, "SRE*", ADDR_ABSOLUTE_Y, SRE_abs_y); + + registerCallback(0x9B, "TAS*", ADDR_ABSOLUTE_Y, TAS_abs_y); +} + +bool +CPU::executeOneCycle() +{ + uint8_t instr; + + switch (next) { + + case fetch: + + /* DEBUG */ + /* + if (PC == 0x08EB) { + uint8_t reg = c64->vic.spypeek(0x1E); + debug("Writing result: %02X (%02X), rasterline: %d sprite0.y = %02X\n", c64->cpu.A, reg, c64->rasterline, c64->vic.spypeek(0x01)); + // startTracing(); + } + */ + + pc = regPC; + + // Check interrupt lines + if (unlikely(doNmi)) { + + if (isC64CPU()) { + c64->expansionport.nmiWillTrigger(); + } + + // debug("NMI (source = %02X)\n", nmiLine); + // if (tracingEnabled()) debug("NMI (source = %02X)\n", nmiLine); + IDLE_FETCH + edgeDetector.clear(); + next = nmi_2; + doNmi = false; + doIrq = false; // NMI wins + return true; + + } else if (unlikely(doIrq)) { + + // if (tracingEnabled()) debug("IRQ (source = %02X)\n", irqLine); + IDLE_FETCH + next = irq_2; + doIrq = false; + return true; + } + + // Execute fetch phase + FETCH_OPCODE + next = actionFunc[instr]; + + // Disassemble command if requested + if (unlikely(tracingEnabled())) { + + recordInstruction(); + + /* + RecordedInstruction recorded = readRecordedInstruction(0); + DisassembledInstruction instr = disassemble(recorded, true); + + { + // c64->cia1.dumpTrace(); + // c64->cia2.dumpTrace(); + } + msg("%s %s: %d %d %s %s %s %s %s %s %s %s %s\n", + (this == &c64->drive1.cpu) ? " " : "", + instr.pc, + c64->rasterLine, + c64->rasterCycle, + instr.byte1, instr.byte2, instr.byte3, + instr.a, instr.x, instr.y, instr.sp, + instr.flags, + instr.command); + */ + } + + + // Check breakpoint tag + if (unlikely(breakpoint[pc] != NO_BREAKPOINT)) { + if (breakpoint[pc] & SOFT_BREAKPOINT) { + // Soft breakpoints get deleted when reached + breakpoint[pc] &= ~SOFT_BREAKPOINT; + setErrorState(CPU_SOFT_BREAKPOINT_REACHED); + } else { + setErrorState(CPU_HARD_BREAKPOINT_REACHED); + } + debug(1, "Breakpoint reached\n"); + } + + return errorState == CPU_OK; + + // + // Illegal instructions + // + + case JAM: + + setErrorState(CPU_ILLEGAL_INSTRUCTION); + CONTINUE + + case JAM_2: + POLL_INT + DONE + + // + // IRQ handling + // + + case irq_2: + + IDLE_READ_IMPLIED + CONTINUE + + case irq_3: + + PUSH_PCH + CONTINUE + + case irq_4: + + PUSH_PCL + // Check for interrupt hijacking + // If there is a positive edge on the NMI line ... + if (edgeDetector.current()) { + + // ... jump to the NMI vector instead of the IRQ vector. + edgeDetector.clear(); + next = nmi_5; + return true; + } + CONTINUE + + case irq_5: + + mem->poke(0x100+(regSP--), getPWithClearedB()); + CONTINUE + + case irq_6: + + READ_FROM(0xFFFE) + setPCL(regD); + setI(1); + CONTINUE + + case irq_7: + + READ_FROM(0xFFFF) + setPCH(regD); + DONE + + // + // NMI handling + // + + case nmi_2: + + IDLE_READ_IMPLIED + CONTINUE + + case nmi_3: + + PUSH_PCH + CONTINUE + + case nmi_4: + + PUSH_PCL + CONTINUE + + case nmi_5: + + mem->poke(0x100+(regSP--), getPWithClearedB()); + CONTINUE + + case nmi_6: + + READ_FROM(0xFFFA) + setPCL(regD); + setI(1); + CONTINUE + + case nmi_7: + + READ_FROM(0xFFFB) + setPCH(regD); + + if (isC64CPU()) { + c64->expansionport.nmiDidTrigger(); + } + DONE + + // + // Adressing mode: Immediate (shared behavior) + // + + case BRK: case RTI: case RTS: + + IDLE_READ_IMMEDIATE + CONTINUE + + // + // Adressing mode: Implied (shared behavior) + // + + case PHA: case PHP: case PLA: case PLP: + + IDLE_READ_IMPLIED + CONTINUE + + // + // Adressing mode: Zero-Page (shared behavior) + // + + case ADC_zpg: case AND_zpg: case ASL_zpg: case BIT_zpg: + case CMP_zpg: case CPX_zpg: case CPY_zpg: case DEC_zpg: + case EOR_zpg: case INC_zpg: case LDA_zpg: case LDX_zpg: + case LDY_zpg: case LSR_zpg: case NOP_zpg: case ORA_zpg: + case ROL_zpg: case ROR_zpg: case SBC_zpg: case STA_zpg: + case STX_zpg: case STY_zpg: case DCP_zpg: case ISC_zpg: + case LAX_zpg: case RLA_zpg: case RRA_zpg: case SAX_zpg: + case SLO_zpg: case SRE_zpg: + + FETCH_ADDR_LO + CONTINUE + + case ASL_zpg_2: case DEC_zpg_2: case INC_zpg_2: case LSR_zpg_2: + case ROL_zpg_2: case ROR_zpg_2: case DCP_zpg_2: case ISC_zpg_2: + case RLA_zpg_2: case RRA_zpg_2: case SLO_zpg_2: case SRE_zpg_2: + + READ_FROM_ZERO_PAGE + CONTINUE + + // + // Adressing mode: Zero-Page Indexed (shared behavior) + // + + case ADC_zpg_x: case AND_zpg_x: case ASL_zpg_x: case CMP_zpg_x: + case DEC_zpg_x: case EOR_zpg_x: case INC_zpg_x: case LDA_zpg_x: + case LDY_zpg_x: case LSR_zpg_x: case NOP_zpg_x: case ORA_zpg_x: + case ROL_zpg_x: case ROR_zpg_x: case SBC_zpg_x: case STA_zpg_x: + case STY_zpg_x: case DCP_zpg_x: case ISC_zpg_x: case RLA_zpg_x: + case RRA_zpg_x: case SLO_zpg_x: case SRE_zpg_x: + + case LDX_zpg_y: case STX_zpg_y: case LAX_zpg_y: case SAX_zpg_y: + + FETCH_ADDR_LO + CONTINUE + + case ADC_zpg_x_2: case AND_zpg_x_2: case ASL_zpg_x_2: case CMP_zpg_x_2: + case DEC_zpg_x_2: case EOR_zpg_x_2: case INC_zpg_x_2: case LDA_zpg_x_2: + case LDY_zpg_x_2: case LSR_zpg_x_2: case NOP_zpg_x_2: case ORA_zpg_x_2: + case ROL_zpg_x_2: case ROR_zpg_x_2: case SBC_zpg_x_2: case DCP_zpg_x_2: + case ISC_zpg_x_2: case RLA_zpg_x_2: case RRA_zpg_x_2: case SLO_zpg_x_2: + case SRE_zpg_x_2: case STA_zpg_x_2: case STY_zpg_x_2: + + READ_FROM_ZERO_PAGE + ADD_INDEX_X + CONTINUE + + case LDX_zpg_y_2: case LAX_zpg_y_2: case STX_zpg_y_2: case SAX_zpg_y_2: + + READ_FROM_ZERO_PAGE + ADD_INDEX_Y + CONTINUE + + case ASL_zpg_x_3: case DEC_zpg_x_3: case INC_zpg_x_3: case LSR_zpg_x_3: + case ROL_zpg_x_3: case ROR_zpg_x_3: case DCP_zpg_x_3: case ISC_zpg_x_3: + case RLA_zpg_x_3: case RRA_zpg_x_3: case SLO_zpg_x_3: case SRE_zpg_x_3: + + READ_FROM_ZERO_PAGE + CONTINUE + + + // + // Adressing mode: Absolute (shared behavior) + // + + case ADC_abs: case AND_abs: case ASL_abs: case BIT_abs: + case CMP_abs: case CPX_abs: case CPY_abs: case DEC_abs: + case EOR_abs: case INC_abs: case LDA_abs: case LDX_abs: + case LDY_abs: case LSR_abs: case NOP_abs: case ORA_abs: + case ROL_abs: case ROR_abs: case SBC_abs: case STA_abs: + case STX_abs: case STY_abs: case DCP_abs: case ISC_abs: + case LAX_abs: case RLA_abs: case RRA_abs: case SAX_abs: + case SLO_abs: case SRE_abs: + + FETCH_ADDR_LO + CONTINUE + + case ADC_abs_2: case AND_abs_2: case ASL_abs_2: case BIT_abs_2: + case CMP_abs_2: case CPX_abs_2: case CPY_abs_2: case DEC_abs_2: + case EOR_abs_2: case INC_abs_2: case LDA_abs_2: case LDX_abs_2: + case LDY_abs_2: case LSR_abs_2: case NOP_abs_2: case ORA_abs_2: + case ROL_abs_2: case ROR_abs_2: case SBC_abs_2: case STA_abs_2: + case STX_abs_2: case STY_abs_2: case DCP_abs_2: case ISC_abs_2: + case LAX_abs_2: case RLA_abs_2: case RRA_abs_2: case SAX_abs_2: + case SLO_abs_2: case SRE_abs_2: + + FETCH_ADDR_HI + CONTINUE + + case ASL_abs_3: case DEC_abs_3: case INC_abs_3: case LSR_abs_3: + case ROL_abs_3: case ROR_abs_3: case DCP_abs_3: case ISC_abs_3: + case RLA_abs_3: case RRA_abs_3: case SLO_abs_3: case SRE_abs_3: + + READ_FROM_ADDRESS + CONTINUE + + // + // Adressing mode: Absolute Indexed (shared behavior) + // + + case ADC_abs_x: case AND_abs_x: case ASL_abs_x: case CMP_abs_x: + case DEC_abs_x: case EOR_abs_x: case INC_abs_x: case LDA_abs_x: + case LDY_abs_x: case LSR_abs_x: case NOP_abs_x: case ORA_abs_x: + case ROL_abs_x: case ROR_abs_x: case SBC_abs_x: case STA_abs_x: + case DCP_abs_x: case ISC_abs_x: case RLA_abs_x: case RRA_abs_x: + case SHY_abs_x: case SLO_abs_x: case SRE_abs_x: + + case ADC_abs_y: case AND_abs_y: case CMP_abs_y: case EOR_abs_y: + case LDA_abs_y: case LDX_abs_y: case LSR_abs_y: case ORA_abs_y: + case SBC_abs_y: case STA_abs_y: case DCP_abs_y: case ISC_abs_y: + case LAS_abs_y: case LAX_abs_y: case RLA_abs_y: case RRA_abs_y: + case SHA_abs_y: case SHX_abs_y: case SLO_abs_y: case SRE_abs_y: + case TAS_abs_y: + + FETCH_ADDR_LO + CONTINUE + + case ADC_abs_x_2: case AND_abs_x_2: case ASL_abs_x_2: case CMP_abs_x_2: + case DEC_abs_x_2: case EOR_abs_x_2: case INC_abs_x_2: case LDA_abs_x_2: + case LDY_abs_x_2: case LSR_abs_x_2: case NOP_abs_x_2: case ORA_abs_x_2: + case ROL_abs_x_2: case ROR_abs_x_2: case SBC_abs_x_2: case STA_abs_x_2: + case DCP_abs_x_2: case ISC_abs_x_2: case RLA_abs_x_2: case RRA_abs_x_2: + case SHY_abs_x_2: case SLO_abs_x_2: case SRE_abs_x_2: + + FETCH_ADDR_HI + ADD_INDEX_X + CONTINUE + + case ADC_abs_y_2: case AND_abs_y_2: case CMP_abs_y_2: case EOR_abs_y_2: + case LDA_abs_y_2: case LDX_abs_y_2: case LSR_abs_y_2: case ORA_abs_y_2: + case SBC_abs_y_2: case STA_abs_y_2: case DCP_abs_y_2: case ISC_abs_y_2: + case LAS_abs_y_2: case LAX_abs_y_2: case RLA_abs_y_2: case RRA_abs_y_2: + case SHA_abs_y_2: case SHX_abs_y_2: case SLO_abs_y_2: case SRE_abs_y_2: + case TAS_abs_y_2: + + FETCH_ADDR_HI + ADD_INDEX_Y + CONTINUE + + case ASL_abs_x_3: case DEC_abs_x_3: case INC_abs_x_3: case LSR_abs_x_3: + case ROL_abs_x_3: case ROR_abs_x_3: case DCP_abs_x_3: case ISC_abs_x_3: + case RLA_abs_x_3: case RRA_abs_x_3: case STA_abs_x_3: case SLO_abs_x_3: + case SRE_abs_x_3: + + case LSR_abs_y_3: case STA_abs_y_3: case DCP_abs_y_3: case ISC_abs_y_3: + case RLA_abs_y_3: case RRA_abs_y_3: case SLO_abs_y_3: case SRE_abs_y_3: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { FIX_ADDR_HI } + CONTINUE + + case ASL_abs_x_4: case DEC_abs_x_4: case INC_abs_x_4: case LSR_abs_x_4: + case ROL_abs_x_4: case ROR_abs_x_4: case DCP_abs_x_4: case ISC_abs_x_4: + case RLA_abs_x_4: case RRA_abs_x_4: case SLO_abs_x_4: case SRE_abs_x_4: + + case DCP_abs_y_4: case LSR_abs_y_4: case ISC_abs_y_4: case RLA_abs_y_4: + case RRA_abs_y_4: case SLO_abs_y_4: case SRE_abs_y_4: + + READ_FROM_ADDRESS + CONTINUE + + // + // Adressing mode: Indexed Indirect (shared behavior) + // + + case ADC_ind_x: case AND_ind_x: case ASL_ind_x: case CMP_ind_x: + case DEC_ind_x: case EOR_ind_x: case INC_ind_x: case LDA_ind_x: + case LDX_ind_x: case LDY_ind_x: case LSR_ind_x: case ORA_ind_x: + case ROL_ind_x: case ROR_ind_x: case SBC_ind_x: case STA_ind_x: + case DCP_ind_x: case ISC_ind_x: case LAX_ind_x: case RLA_ind_x: + case RRA_ind_x: case SAX_ind_x: case SLO_ind_x: case SRE_ind_x: + + FETCH_POINTER_ADDR + CONTINUE + + case ADC_ind_x_2: case AND_ind_x_2: case ASL_ind_x_2: case CMP_ind_x_2: + case DEC_ind_x_2: case EOR_ind_x_2: case INC_ind_x_2: case LDA_ind_x_2: + case LDX_ind_x_2: case LDY_ind_x_2: case LSR_ind_x_2: case ORA_ind_x_2: + case ROL_ind_x_2: case ROR_ind_x_2: case SBC_ind_x_2: case STA_ind_x_2: + case DCP_ind_x_2: case ISC_ind_x_2: case LAX_ind_x_2: case RLA_ind_x_2: + case RRA_ind_x_2: case SAX_ind_x_2: case SLO_ind_x_2: case SRE_ind_x_2: + + IDLE_READ_FROM_ADDRESS_INDIRECT + ADD_INDEX_X_INDIRECT + CONTINUE + + case ADC_ind_x_3: case AND_ind_x_3: case ASL_ind_x_3: case CMP_ind_x_3: + case DEC_ind_x_3: case EOR_ind_x_3: case INC_ind_x_3: case LDA_ind_x_3: + case LDX_ind_x_3: case LDY_ind_x_3: case LSR_ind_x_3: case ORA_ind_x_3: + case ROL_ind_x_3: case ROR_ind_x_3: case SBC_ind_x_3: case STA_ind_x_3: + case DCP_ind_x_3: case ISC_ind_x_3: case LAX_ind_x_3: case RLA_ind_x_3: + case RRA_ind_x_3: case SAX_ind_x_3: case SLO_ind_x_3: case SRE_ind_x_3: + + FETCH_ADDR_LO_INDIRECT + CONTINUE + + case ADC_ind_x_4: case AND_ind_x_4: case ASL_ind_x_4: case CMP_ind_x_4: + case DEC_ind_x_4: case EOR_ind_x_4: case INC_ind_x_4: case LDA_ind_x_4: + case LDX_ind_x_4: case LDY_ind_x_4: case LSR_ind_x_4: case ORA_ind_x_4: + case ROL_ind_x_4: case ROR_ind_x_4: case SBC_ind_x_4: case STA_ind_x_4: + case DCP_ind_x_4: case ISC_ind_x_4: case LAX_ind_x_4: case RLA_ind_x_4: + case RRA_ind_x_4: case SAX_ind_x_4: case SLO_ind_x_4: case SRE_ind_x_4: + + FETCH_ADDR_HI_INDIRECT + CONTINUE + + case ASL_ind_x_5: case DEC_ind_x_5: case INC_ind_x_5: case LSR_ind_x_5: + case ROL_ind_x_5: case ROR_ind_x_5: case DCP_ind_x_5: case ISC_ind_x_5: + case RLA_ind_x_5: case RRA_ind_x_5: case SLO_ind_x_5: case SRE_ind_x_5: + + READ_FROM_ADDRESS + CONTINUE + + // + // Adressing mode: Indirect Indexed (shared behavior) + // + + case ADC_ind_y: case AND_ind_y: case CMP_ind_y: case EOR_ind_y: + case LDA_ind_y: case LDX_ind_y: case LDY_ind_y: case LSR_ind_y: + case ORA_ind_y: case SBC_ind_y: case STA_ind_y: case DCP_ind_y: + case ISC_ind_y: case LAX_ind_y: case RLA_ind_y: case RRA_ind_y: + case SHA_ind_y: case SLO_ind_y: case SRE_ind_y: + + FETCH_POINTER_ADDR + CONTINUE + + case ADC_ind_y_2: case AND_ind_y_2: case CMP_ind_y_2: case EOR_ind_y_2: + case LDA_ind_y_2: case LDX_ind_y_2: case LDY_ind_y_2: case LSR_ind_y_2: + case ORA_ind_y_2: case SBC_ind_y_2: case STA_ind_y_2: case DCP_ind_y_2: + case ISC_ind_y_2: case LAX_ind_y_2: case RLA_ind_y_2: case RRA_ind_y_2: + case SHA_ind_y_2: case SLO_ind_y_2: case SRE_ind_y_2: + + FETCH_ADDR_LO_INDIRECT + CONTINUE + + case ADC_ind_y_3: case AND_ind_y_3: case CMP_ind_y_3: case EOR_ind_y_3: + case LDA_ind_y_3: case LDX_ind_y_3: case LDY_ind_y_3: case LSR_ind_y_3: + case ORA_ind_y_3: case SBC_ind_y_3: case STA_ind_y_3: case DCP_ind_y_3: + case ISC_ind_y_3: case LAX_ind_y_3: case RLA_ind_y_3: case RRA_ind_y_3: + case SHA_ind_y_3: case SLO_ind_y_3: case SRE_ind_y_3: + + FETCH_ADDR_HI_INDIRECT + ADD_INDEX_Y + CONTINUE + + case LSR_ind_y_4: case STA_ind_y_4: case DCP_ind_y_4: case ISC_ind_y_4: + case RLA_ind_y_4: case RRA_ind_y_4: case SLO_ind_y_4: case SRE_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { FIX_ADDR_HI } + CONTINUE + + case LSR_ind_y_5: case DCP_ind_y_5: case ISC_ind_y_5: case RLA_ind_y_5: + case RRA_ind_y_5: case SLO_ind_y_5: case SRE_ind_y_5: + + READ_FROM_ADDRESS + CONTINUE + + // + // Adressing mode: Relative (shared behavior) + // + + case BCC_rel_2: case BCS_rel_2: case BEQ_rel_2: case BMI_rel_2: + case BNE_rel_2: case BPL_rel_2: case BVC_rel_2: case BVS_rel_2: + { + IDLE_READ_IMPLIED + uint8_t pc_hi = HI_BYTE(regPC); + regPC += (int8_t)regD; + + if (unlikely(pc_hi != HI_BYTE(regPC))) { + next = (regD & 0x80) ? branch_3_underflow : branch_3_overflow; + return true; + } + DONE + } + + case branch_3_underflow: + + IDLE_READ_FROM(regPC + 0x100) + POLL_INT_AGAIN + DONE + + case branch_3_overflow: + + IDLE_READ_FROM(regPC - 0x100) + POLL_INT_AGAIN + DONE + + + // Instruction: ADC + // + // Operation: A,C := A+M+C + // + // Flags: N Z C I D V + // / / / - - / + + case ADC_imm: + + READ_IMMEDIATE + adc(regD); + POLL_INT + DONE + + case ADC_zpg_2: + case ADC_zpg_x_3: + + READ_FROM_ZERO_PAGE + adc(regD); + POLL_INT + DONE + + case ADC_abs_x_3: + case ADC_abs_y_3: + case ADC_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + adc(regD); + POLL_INT + DONE + } + + case ADC_abs_3: + case ADC_abs_x_4: + case ADC_abs_y_4: + case ADC_ind_x_5: + case ADC_ind_y_5: + + READ_FROM_ADDRESS + adc(regD); + POLL_INT + DONE + + + // Instruction: AND + // + // Operation: A := A AND M + // + // Flags: N Z C I D V + // / / - - - - + + case AND_imm: + + READ_IMMEDIATE + loadA(regA & regD); + POLL_INT + DONE + + case AND_zpg_2: + case AND_zpg_x_3: + + READ_FROM_ZERO_PAGE + loadA(regA & regD); + POLL_INT + DONE + + case AND_abs_x_3: + case AND_abs_y_3: + case AND_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + loadA(regA & regD); + POLL_INT + DONE + } + + case AND_abs_3: + case AND_abs_x_4: + case AND_abs_y_4: + case AND_ind_x_5: + case AND_ind_y_5: + + READ_FROM_ADDRESS + loadA(regA & regD); + POLL_INT + DONE + + + // Instruction: ASL + // + // Operation: C <- (A|M << 1) <- 0 + // + // Flags: N Z C I D V + // / / / - - - + + #define DO_ASL_ACC setC(regA & 0x80); loadA(regA << 1); + #define DO_ASL setC(regD & 0x80); regD = regD << 1; + + case ASL_acc: + + IDLE_READ_IMPLIED + DO_ASL_ACC + POLL_INT + DONE + + case ASL_zpg_3: + case ASL_zpg_x_4: + + WRITE_TO_ZERO_PAGE + DO_ASL + CONTINUE + + case ASL_abs_4: + case ASL_abs_x_5: + case ASL_ind_x_6: + + WRITE_TO_ADDRESS + DO_ASL + CONTINUE + + case ASL_zpg_4: + case ASL_zpg_x_5: + + WRITE_TO_ZERO_PAGE_AND_SET_FLAGS + POLL_INT + DONE + + case ASL_abs_5: + case ASL_abs_x_6: + case ASL_ind_x_7: + + WRITE_TO_ADDRESS_AND_SET_FLAGS + POLL_INT + DONE + + + // Instruction: BCC + // + // Operation: Branch on C = 0 + // + // Flags: N Z C I D V + // - - - - - - + + case BCC_rel: + + READ_IMMEDIATE + POLL_INT + + if (!getC()) { + CONTINUE + } else { + DONE + } + + + // Instruction: BCS + // + // Operation: Branch on C = 1 + // + // Flags: N Z C I D V + // - - - - - - + + case BCS_rel: + + READ_IMMEDIATE + POLL_INT + + if (getC()) { + CONTINUE + } else { + DONE + } + + + // Instruction: BEQ + // + // Operation: Branch on Z = 1 + // + // Flags: N Z C I D V + // - - - - - - + + case BEQ_rel: + + READ_IMMEDIATE + POLL_INT + + if (getZ()) { + CONTINUE + } else { + DONE + } + + + // Instruction: BIT + // + // Operation: A AND M, N := M7, V := M6 + // + // Flags: N Z C I D V + // / / - - - / + + case BIT_zpg_2: + + READ_FROM_ZERO_PAGE + setN(regD & 128); + setV(regD & 64); + setZ((regD & regA) == 0); + POLL_INT + DONE + + case BIT_abs_3: + + READ_FROM_ADDRESS + setN(regD & 128); + setV(regD & 64); + setZ((regD & regA) == 0); + POLL_INT + DONE + + + // Instruction: BMI + // + // Operation: Branch on N = 1 + // + // Flags: N Z C I D V + // - - - - - - + + case BMI_rel: + + READ_IMMEDIATE + POLL_INT + + if (getN()) { + CONTINUE + } else { + DONE + } + + + // Instruction: BNE + // + // Operation: Branch on Z = 0 + // + // Flags: N Z C I D V + // - - - - - - + + case BNE_rel: + + READ_IMMEDIATE + POLL_INT + + if (!getZ()) { + CONTINUE + } else { + DONE + } + + + // Instruction: BPL + // + // Operation: Branch on N = 0 + // + // Flags: N Z C I D V + // - - - - - - + + case BPL_rel: + + READ_IMMEDIATE + POLL_INT + + if (!getN()) { + CONTINUE + } else { + DONE + } + + + // Instruction: BRK + // + // Operation: Forced Interrupt (Break) + // + // Flags: N Z C I D V B + // - - - 1 - - 1 + + case BRK_2: + + setB(1); + PUSH_PCH + CONTINUE + + case BRK_3: + + PUSH_PCL + + // Check for interrupt hijacking + // If there is a positive edge on the NMI line, ... + if (edgeDetector.current()) { + + // ... jump to the NMI vector instead of the IRQ vector. + edgeDetector.clear(); + next = BRK_nmi_4; + return true; + + } else { + CONTINUE + } + + case BRK_4: + + PUSH_P + CONTINUE + + case BRK_5: + + READ_FROM(0xFFFE); + setPCL(regD); + setI(1); + CONTINUE + + case BRK_6: + + READ_FROM(0xFFFF); + setPCH(regD); + POLL_INT + doNmi = false; // Only the level detector is polled here. This is + // the reason why only IRQs can be triggered right + // after a BRK command, but not NMIs. + DONE + + case BRK_nmi_4: + + PUSH_P + CONTINUE + + case BRK_nmi_5: + + READ_FROM(0xFFFA); + setPCL(regD); + setI(1); + CONTINUE + + case BRK_nmi_6: + + READ_FROM(0xFFFB); + setPCH(regD); + POLL_INT + DONE + + + // Instruction: BVC + // + // Operation: Branch on V = 0 + // + // Flags: N Z C I D V + // - - - - - - + + case BVC_rel: + + READ_IMMEDIATE + POLL_INT + + if (!getV()) { + CONTINUE + } else { + DONE + } + + + // Instruction: BVS + // + // Operation: Branch on V = 1 + // + // Flags: N Z C I D V + // - - - - - - + + case BVS_rel: + + READ_IMMEDIATE + POLL_INT + + if (getV()) { + CONTINUE + } else { + DONE + } + + + // Instruction: CLC + // + // Operation: C := 0 + // + // Flags: N Z C I D V + // - - 0 - - - + + case CLC: + + IDLE_READ_IMPLIED + setC(0); + POLL_INT + DONE + + + // Instruction: CLD + // + // Operation: D := 0 + // + // Flags: N Z C I D V + // - - - - 0 - + + case CLD: + + IDLE_READ_IMPLIED + setD(0); + POLL_INT + DONE + + + // Instruction: CLI + // + // Operation: I := 0 + // + // Flags: N Z C I D V + // - - - 0 - - + + case CLI: + + POLL_INT + setI(0); + IDLE_READ_IMPLIED + DONE + + + // Instruction: CLV + // + // Operation: V := 0 + // + // Flags: N Z C I D V + // - - - - - 0 + + case CLV: + + IDLE_READ_IMPLIED + setV(0); + POLL_INT + DONE + + + // Instruction: CMP + // + // Operation: A-M + // + // Flags: N Z C I D V + // / / / - - - + + case CMP_imm: + + READ_IMMEDIATE + cmp(regA, regD); + POLL_INT + DONE + + case CMP_zpg_2: + case CMP_zpg_x_3: + + READ_FROM_ZERO_PAGE + cmp(regA, regD); + POLL_INT + DONE + + case CMP_abs_x_3: + case CMP_abs_y_3: + case CMP_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + cmp(regA, regD); + POLL_INT + DONE + } + + case CMP_abs_3: + case CMP_abs_x_4: + case CMP_abs_y_4: + case CMP_ind_x_5: + case CMP_ind_y_5: + + READ_FROM_ADDRESS + cmp(regA, regD); + POLL_INT + DONE + + + // Instruction: CPX + // + // Operation: X-M + // + // Flags: N Z C I D V + // / / / - - - + + case CPX_imm: + + READ_IMMEDIATE + cmp(regX, regD); + POLL_INT + DONE + + case CPX_zpg_2: + + READ_FROM_ZERO_PAGE + cmp(regX, regD); + POLL_INT + DONE + + case CPX_abs_3: + + READ_FROM_ADDRESS + cmp(regX, regD); + POLL_INT + DONE + + + // Instruction: CPY + // + // Operation: Y-M + // + // Flags: N Z C I D V + // / / / - - - + + case CPY_imm: + + READ_IMMEDIATE + cmp(regY, regD); + POLL_INT + DONE + + case CPY_zpg_2: + + READ_FROM_ZERO_PAGE + cmp(regY, regD); + POLL_INT + DONE + + case CPY_abs_3: + + READ_FROM_ADDRESS + cmp(regY, regD); + POLL_INT + DONE + + + // Instruction: DEC + // + // Operation: M := : M - 1 + // + // Flags: N Z C I D V + // / / - - - - + + #define DO_DEC regD--; + + case DEC_zpg_3: + case DEC_zpg_x_4: + + WRITE_TO_ZERO_PAGE + DO_DEC + CONTINUE + + case DEC_zpg_4: + case DEC_zpg_x_5: + + WRITE_TO_ZERO_PAGE_AND_SET_FLAGS + POLL_INT + DONE + + case DEC_abs_4: + case DEC_abs_x_5: + case DEC_ind_x_6: + + WRITE_TO_ADDRESS + DO_DEC + CONTINUE + + case DEC_abs_5: + case DEC_abs_x_6: + case DEC_ind_x_7: + + WRITE_TO_ADDRESS_AND_SET_FLAGS + POLL_INT + DONE + + + // Instruction: DEX + // + // Operation: X := X - 1 + // + // Flags: N Z C I D V + // / / - - - - + + case DEX: + + IDLE_READ_IMPLIED + loadX(regX - 1); + POLL_INT + DONE + + + // Instruction: DEY + // + // Operation: Y := Y - 1 + // + // Flags: N Z C I D V + // / / - - - - + + case DEY: + + IDLE_READ_IMPLIED + loadY(regY - 1); + POLL_INT + DONE + + + // Instruction: EOR + // + // Operation: A := A XOR M + // + // Flags: N Z C I D V + // / / - - - - + + #define DO_EOR loadA(regA ^ regD); + + case EOR_imm: + + READ_IMMEDIATE + DO_EOR + POLL_INT + DONE + + case EOR_zpg_2: + case EOR_zpg_x_3: + + READ_FROM_ZERO_PAGE + DO_EOR + POLL_INT + DONE + + case EOR_abs_x_3: + case EOR_abs_y_3: + case EOR_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + DO_EOR + POLL_INT + DONE + } + + case EOR_abs_3: + case EOR_abs_x_4: + case EOR_abs_y_4: + case EOR_ind_x_5: + case EOR_ind_y_5: + + READ_FROM_ADDRESS + DO_EOR + POLL_INT + DONE + + + // Instruction: INC + // + // Operation: M := M + 1 + // + // Flags: N Z C I D V + // / / - - - - + + #define DO_INC regD++; + + case INC_zpg_3: + case INC_zpg_x_4: + + WRITE_TO_ZERO_PAGE + DO_INC + CONTINUE + + case INC_zpg_4: + case INC_zpg_x_5: + + WRITE_TO_ZERO_PAGE_AND_SET_FLAGS + POLL_INT + DONE + + case INC_abs_4: + case INC_abs_x_5: + case INC_ind_x_6: + + WRITE_TO_ADDRESS + DO_INC + CONTINUE + + case INC_abs_5: + case INC_abs_x_6: + case INC_ind_x_7: + + WRITE_TO_ADDRESS_AND_SET_FLAGS + POLL_INT + DONE + + + // Instruction: INX + // + // Operation: X := X + 1 + // + // Flags: N Z C I D V + // / / - - - - + + case INX: + + IDLE_READ_IMPLIED + loadX(regX + 1); + POLL_INT + DONE + + + // Instruction: INY + // + // Operation: Y := Y + 1 + // + // Flags: N Z C I D V + // / / - - - - + + case INY: + + IDLE_READ_IMPLIED + loadY(regY + 1); + POLL_INT + DONE + + + // Instruction: JMP + // + // Operation: PC := Operand + // + // Flags: N Z C I D V + // - - - - - - + + case JMP_abs: + + FETCH_ADDR_LO + CONTINUE + + case JMP_abs_2: + + FETCH_ADDR_HI + regPC = LO_HI(regADL, regADH); + POLL_INT + DONE + + case JMP_abs_ind: + + FETCH_ADDR_LO + CONTINUE + + case JMP_abs_ind_2: + + FETCH_ADDR_HI + CONTINUE + + case JMP_abs_ind_3: + + READ_FROM_ADDRESS + setPCL(regD); + regADL++; + CONTINUE + + case JMP_abs_ind_4: + + READ_FROM_ADDRESS + setPCH(regD); + POLL_INT + DONE + + + // Instruction: JSR + // + // Operation: PC to stack, PC := Operand + // + // Flags: N Z C I D V + // - - - - - - + + case JSR: + + FETCH_ADDR_LO + CONTINUE + + case JSR_2: + + IDLE_PULL + CONTINUE + + case JSR_3: + + PUSH_PCH + CONTINUE + + case JSR_4: + + PUSH_PCL + CONTINUE + + case JSR_5: + + FETCH_ADDR_HI + regPC = LO_HI(regADL, regADH); + POLL_INT + DONE + + + // Instruction: LDA + // + // Operation: A := M + // + // Flags: N Z C I D V + // / / - - - - + + case LDA_imm: + + READ_IMMEDIATE + loadA(regD); + POLL_INT + DONE + + case LDA_zpg_2: + case LDA_zpg_x_3: + + READ_FROM_ZERO_PAGE + loadA(regD); + POLL_INT + DONE + + case LDA_abs_x_3: + case LDA_abs_y_3: + case LDA_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + loadA(regD); + POLL_INT + DONE + } + + case LDA_abs_3: + case LDA_abs_x_4: + case LDA_abs_y_4: + case LDA_ind_x_5: + case LDA_ind_y_5: + + READ_FROM_ADDRESS + loadA(regD); + POLL_INT + DONE + + + // Instruction: LDX + // + // Operation: X := M + // + // Flags: N Z C I D V + // / / - - - - + + case LDX_imm: + + READ_IMMEDIATE + loadX(regD); + POLL_INT + DONE + + case LDX_zpg_2: + case LDX_zpg_y_3: + + READ_FROM_ZERO_PAGE + loadX(regD); + POLL_INT + DONE + + case LDX_abs_y_3: + case LDX_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + loadX(regD); + POLL_INT + DONE + } + + case LDX_abs_3: + case LDX_abs_y_4: + case LDX_ind_x_5: + case LDX_ind_y_5: + + READ_FROM_ADDRESS + loadX(regD); + POLL_INT + DONE + + + // Instruction: LDY + // + // Operation: Y := M + // + // Flags: N Z C I D V + // / / - - - - + + case LDY_imm: + + READ_IMMEDIATE + loadY(regD); + POLL_INT + DONE + + case LDY_zpg_2: + case LDY_zpg_x_3: + + READ_FROM_ZERO_PAGE + loadY(regD); + POLL_INT + DONE + + case LDY_abs_x_3: + case LDY_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + loadY(regD); + POLL_INT + DONE + } + + case LDY_abs_3: + case LDY_abs_x_4: + case LDY_ind_x_5: + case LDY_ind_y_5: + + READ_FROM_ADDRESS + loadY(regD); + POLL_INT + DONE + + + // Instruction: LSR + // + // Operation: 0 -> (A|M >> 1) -> C + // + // Flags: N Z C I D V + // 0 / / - - - + + case LSR_acc: + + IDLE_READ_IMPLIED + setC(regA & 1); loadA(regA >> 1); + POLL_INT + DONE + + case LSR_zpg_3: + case LSR_zpg_x_4: + + WRITE_TO_ZERO_PAGE + setC(regD & 1); regD = regD >> 1; + CONTINUE + + case LSR_zpg_4: + case LSR_zpg_x_5: + + WRITE_TO_ZERO_PAGE_AND_SET_FLAGS + POLL_INT + DONE + + case LSR_abs_4: + case LSR_abs_x_5: + case LSR_abs_y_5: + case LSR_ind_x_6: + case LSR_ind_y_6: + + WRITE_TO_ADDRESS + setC(regD & 1); regD = regD >> 1; + CONTINUE + + case LSR_abs_5: + case LSR_abs_x_6: + case LSR_abs_y_6: + case LSR_ind_x_7: + case LSR_ind_y_7: + + WRITE_TO_ADDRESS_AND_SET_FLAGS + POLL_INT + DONE + + + // Instruction: NOP + // + // Operation: No operation + // + // Flags: N Z C I D V + // - - - - - - + + case NOP: + + IDLE_READ_IMPLIED + POLL_INT + DONE + + case NOP_imm: + + IDLE_READ_IMMEDIATE + POLL_INT + DONE + + case NOP_zpg_2: + case NOP_zpg_x_3: + + IDLE_READ_FROM_ZERO_PAGE + POLL_INT + DONE + + case NOP_abs_x_3: + + IDLE_READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + POLL_INT + DONE + } + + case NOP_abs_3: + case NOP_abs_x_4: + + IDLE_READ_FROM_ADDRESS + POLL_INT + DONE + + + // Instruction: ORA + // + // Operation: A := A v M + // + // Flags: N Z C I D V + // / / - - - - + + case ORA_imm: + + READ_IMMEDIATE + loadA(regA | regD); + POLL_INT + DONE + + case ORA_zpg_2: + case ORA_zpg_x_3: + + READ_FROM_ZERO_PAGE + loadA(regA | regD); + POLL_INT + DONE + + case ORA_abs_x_3: + case ORA_abs_y_3: + case ORA_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + loadA(regA | regD); + POLL_INT + DONE + } + + case ORA_abs_3: + case ORA_abs_x_4: + case ORA_abs_y_4: + case ORA_ind_x_5: + case ORA_ind_y_5: + + READ_FROM_ADDRESS + loadA(regA | regD); + POLL_INT + DONE + + + // Instruction: PHA + // + // Operation: A to stack + // + // Flags: N Z C I D V + // - - - - - - + + case PHA_2: + + PUSH_A + POLL_INT + DONE + + + // Instruction: PHA + // + // Operation: P to stack + // + // Flags: N Z C I D V + // - - - - - - + + case PHP_2: + + PUSH_P + POLL_INT + DONE + + + // Instruction: PLA + // + // Operation: Stack to A + // + // Flags: N Z C I D V + // - - - - - - + + case PLA_2: + + regSP++; + CONTINUE + + case PLA_3: + + PULL_A + POLL_INT + DONE + + + // Instruction: PLP + // + // Operation: Stack to p + // + // Flags: N Z C I D V + // / / / / / / + + case PLP_2: + + IDLE_PULL + regSP++; + CONTINUE + + case PLP_3: + + POLL_INT // Interrupts are polled before P is pulled + PULL_P + DONE + + + // Instruction: ROL + // + // ----------------------- + // | | + // Operation: ---(A|M << 1) <- C <--- + // + // Flags: N Z C I D V + // / / / - - - + + #define DO_ROL_ACC { int c = !!getC(); setC(regA & 0x80); loadA((regA << 1) | c); } + #define DO_ROL { int c = !!getC(); setC(regD & 0x80); regD = (regD << 1) | c; } + + case ROL_acc: + + IDLE_READ_IMPLIED + DO_ROL_ACC + POLL_INT + DONE + + case ROL_zpg_3: + case ROL_zpg_x_4: + + WRITE_TO_ZERO_PAGE + DO_ROL + CONTINUE + + case ROL_zpg_4: + case ROL_zpg_x_5: + + WRITE_TO_ZERO_PAGE_AND_SET_FLAGS + POLL_INT + DONE + + case ROL_abs_4: + case ROL_abs_x_5: + case ROL_ind_x_6: + + WRITE_TO_ADDRESS + DO_ROL + CONTINUE + + case ROL_abs_5: + case ROL_abs_x_6: + case ROL_ind_x_7: + + WRITE_TO_ADDRESS_AND_SET_FLAGS + POLL_INT + DONE + + + // Instruction: ROR + // + // ----------------------- + // | | + // Operation: --->(A|M >> 1) -> C --- + // + // Flags: N Z C I D V + // / / / - - - + + #define DO_ROR_ACC { int c = !!getC(); setC(regA & 0x1); loadA((regA >> 1) | (c << 7)); } + #define DO_ROR { int c = !!getC(); setC(regD & 0x1); regD = (regD >> 1) | (c << 7); } + + case ROR_acc: + + IDLE_READ_IMPLIED + DO_ROR_ACC + POLL_INT + DONE + + case ROR_zpg_3: + case ROR_zpg_x_4: + + WRITE_TO_ZERO_PAGE + DO_ROR + CONTINUE + + case ROR_zpg_4: + case ROR_zpg_x_5: + + WRITE_TO_ZERO_PAGE_AND_SET_FLAGS + POLL_INT + DONE + + case ROR_abs_4: + case ROR_abs_x_5: + case ROR_ind_x_6: + + WRITE_TO_ADDRESS + DO_ROR + CONTINUE + + case ROR_abs_5: + case ROR_abs_x_6: + case ROR_ind_x_7: + + WRITE_TO_ADDRESS_AND_SET_FLAGS + POLL_INT + DONE + + + // Instruction: RTI + // + // Operation: P from Stack, PC from Stack + // + // Flags: N Z C I D V + // / / / / / / + + case RTI_2: + + IDLE_PULL + regSP++; + CONTINUE + + case RTI_3: + + PULL_P + regSP++; + CONTINUE + + case RTI_4: + + PULL_PCL + regSP++; + CONTINUE + + case RTI_5: + + PULL_PCH + POLL_INT + DONE + + + // Instruction: RTS + // + // Operation: PC from Stack + // + // Flags: N Z C I D V + // - - - - - - + + case RTS_2: + + IDLE_PULL + regSP++; + CONTINUE + + case RTS_3: + + PULL_PCL + regSP++; + CONTINUE + + case RTS_4: + + PULL_PCH + CONTINUE + + case RTS_5: + + IDLE_READ_IMMEDIATE + POLL_INT + DONE + + + // Instruction: SBC + // + // Operation: A := A - M - (~C) + // + // Flags: N Z C I D V + // / / / - - / + + case SBC_imm: + + READ_IMMEDIATE + sbc(regD); + POLL_INT + DONE + + case SBC_zpg_2: + case SBC_zpg_x_3: + + READ_FROM_ZERO_PAGE + sbc(regD); + POLL_INT + DONE + + case SBC_abs_x_3: + case SBC_abs_y_3: + case SBC_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + sbc(regD); + POLL_INT + DONE + } + + case SBC_abs_3: + case SBC_abs_x_4: + case SBC_abs_y_4: + case SBC_ind_x_5: + case SBC_ind_y_5: + + READ_FROM_ADDRESS + sbc(regD); + POLL_INT + DONE + + + // Instruction: SEC + // + // Operation: C := 1 + // + // Flags: N Z C I D V + // - - 1 - - - + + case SEC: + + IDLE_READ_IMPLIED + setC(1); + POLL_INT + DONE + + + // Instruction: SED + // + // Operation: D := 1 + // + // Flags: N Z C I D V + // - - - - 1 - + + case SED: + + IDLE_READ_IMPLIED + setD(1); + POLL_INT + DONE + + + // Instruction: SEI + // + // Operation: I := 1 + // + // Flags: N Z C I D V + // - - - 1 - - + + case SEI: + + POLL_IRQ + setI(1); + + case SEI_cont: // fallthrough + + next = SEI_cont; + IDLE_READ_IMPLIED + POLL_NMI + DONE + + + // Instruction: STA + // + // Operation: M := A + // + // Flags: N Z C I D V + // - - - - - - + + case STA_zpg_2: + case STA_zpg_x_3: + + regD = regA; + WRITE_TO_ZERO_PAGE + POLL_INT + DONE + + case STA_abs_3: + case STA_abs_x_4: + + regD = regA; + WRITE_TO_ADDRESS + POLL_INT + DONE + + case STA_abs_y_4: + case STA_ind_x_5: + case STA_ind_y_5: + + regD = regA; + WRITE_TO_ADDRESS + POLL_INT + DONE + + + // Instruction: STX + // + // Operation: M := X + // + // Flags: N Z C I D V + // - - - - - - + + case STX_zpg_2: + case STX_zpg_y_3: + + regD = regX; + WRITE_TO_ZERO_PAGE + POLL_INT + DONE + + case STX_abs_3: + + regD = regX; + WRITE_TO_ADDRESS + POLL_INT + DONE + + + // Instruction: STY + // + // Operation: M := Y + // + // Flags: N Z C I D V + // - - - - - - + + case STY_zpg_2: + case STY_zpg_x_3: + + regD = regY; + WRITE_TO_ZERO_PAGE + POLL_INT + DONE + + case STY_abs_3: + + regD = regY; + WRITE_TO_ADDRESS + POLL_INT + DONE + + + // Instruction: TAX + // + // Operation: X := A + // + // Flags: N Z C I D V + // / / - - - - + + case TAX: + + IDLE_READ_IMPLIED + loadX(regA); + POLL_INT + DONE + + + // Instruction: TAY + // + // Operation: Y := A + // + // Flags: N Z C I D V + // / / - - - - + + case TAY: + + IDLE_READ_IMPLIED + loadY(regA); + POLL_INT + DONE + + + // Instruction: TSX + // + // Operation: X := Stack pointer + // + // Flags: N Z C I D V + // / / - - - - + + case TSX: + + IDLE_READ_IMPLIED + loadX(regSP); + POLL_INT + DONE + + + // Instruction: TXA + // + // Operation: A := X + // + // Flags: N Z C I D V + // / / - - - - + + case TXA: + + IDLE_READ_IMPLIED + loadA(regX); + POLL_INT + DONE + + + // Instruction: TXS + // + // Operation: Stack pointer := X + // + // Flags: N Z C I D V + // - - - - - - + + case TXS: + + IDLE_READ_IMPLIED + regSP = regX; + POLL_INT + DONE + + + // Instruction: TYA + // + // Operation: A := Y + // + // Flags: N Z C I D V + // / / - - - - + + case TYA: + + IDLE_READ_IMPLIED + loadA(regY); + POLL_INT + DONE + + + // + // Illegal instructions + // + + + // Instruction: ALR + // + // Operation: AND, followed by LSR + // + // Flags: N Z C I D V + // / / / - - - + + case ALR_imm: + + READ_IMMEDIATE + regA = regA & regD; + setC(regA & 1); + loadA(regA >> 1); + POLL_INT + DONE + + + // Instruction: ANC + // + // Operation: A := A & op, N flag is copied to C + // + // Flags: N Z C I D V + // / / / - - - + + case ANC_imm: + + READ_IMMEDIATE + loadA(regA & regD); + setC(getN()); + POLL_INT + DONE + + + // Instruction: ARR + // + // Operation: AND, followed by ROR + // + // Flags: N Z C I D V + // / / / - - / + + case ARR_imm: + { + READ_IMMEDIATE + + uint8_t tmp2 = regA & regD; + + // Taken from Frodo... + regA = (getC() ? (tmp2 >> 1) | 0x80 : tmp2 >> 1); + if (!getD()) { + setN(regA & 0x80); + setZ(regA == 0); + setC(regA & 0x40); + setV((regA & 0x40) ^ ((regA & 0x20) << 1)); + } else { + int c_flag; + + setN(getC()); + setZ(regA == 0); + setV((tmp2 ^ regA) & 0x40); + if ((tmp2 & 0x0f) + (tmp2 & 0x01) > 5) + regA = (regA & 0xf0) | ((regA + 6) & 0x0f); + c_flag = (tmp2 + (tmp2 & 0x10)) & 0x1f0; + if (c_flag > 0x50) { + setC(1); + regA += 0x60; + } else { + setC(0); + } + } + POLL_INT + DONE + } + + + // Instruction: AXS + // + // Operation: X = (A & X) - op + // + // Flags: N Z C I D V + // / / / - - - + + case AXS_imm: + { + READ_IMMEDIATE + + uint8_t op2 = regA & regX; + uint8_t tmp = op2 - regD; + + setC(op2 >= regD); + loadX(tmp); + POLL_INT + DONE + } + + + // Instruction: DCP + // + // Operation: DEC followed by CMP + // + // Flags: N Z C I D V + // / / / - - - + + case DCP_zpg_3: + case DCP_zpg_x_4: + + WRITE_TO_ZERO_PAGE + regD--; + CONTINUE + + case DCP_zpg_4: + case DCP_zpg_x_5: + + WRITE_TO_ZERO_PAGE_AND_SET_FLAGS + cmp(regA, regD); + POLL_INT + DONE + + case DCP_abs_4: + case DCP_abs_x_5: + case DCP_abs_y_5: + case DCP_ind_x_6: + case DCP_ind_y_6: + + WRITE_TO_ADDRESS + regD--; + CONTINUE + + case DCP_abs_5: + case DCP_abs_x_6: + case DCP_abs_y_6: + case DCP_ind_x_7: + case DCP_ind_y_7: + + WRITE_TO_ADDRESS_AND_SET_FLAGS + cmp(regA, regD); + POLL_INT + DONE + + + // Instruction: ISC + // + // Operation: INC followed by SBC + // + // Flags: N Z C I D V + // / / / - - / + + case ISC_zpg_3: + case ISC_zpg_x_4: + + WRITE_TO_ZERO_PAGE + regD++; + CONTINUE + + case ISC_zpg_4: + case ISC_zpg_x_5: + + WRITE_TO_ZERO_PAGE_AND_SET_FLAGS + sbc(regD); + POLL_INT + DONE + + case ISC_abs_4: + case ISC_abs_x_5: + case ISC_abs_y_5: + case ISC_ind_x_6: + case ISC_ind_y_6: + + WRITE_TO_ADDRESS + regD++; + CONTINUE + + case ISC_abs_5: + case ISC_abs_x_6: + case ISC_abs_y_6: + case ISC_ind_x_7: + case ISC_ind_y_7: + + WRITE_TO_ADDRESS_AND_SET_FLAGS + sbc(regD); + POLL_INT + DONE + + + // Instruction: LAS + // + // Operation: SP,X,A = op & SP + // + // Flags: N Z C I D V + // / / - - - - + + case LAS_abs_y_3: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + regD &= regSP; + regSP = regD; + regX = regD; + loadA(regD); + POLL_INT + DONE + } + + case LAS_abs_y_4: + + READ_FROM_ADDRESS + regD &= regSP; + regSP = regD; + regX = regD; + loadA(regD); + POLL_INT + DONE + + + // Instruction: LAX + // + // Operation: LDA, followed by LDX + // + // Flags: N Z C I D V + // / / - - - - + + case LAX_zpg_2: + case LAX_zpg_y_3: + + READ_FROM_ZERO_PAGE + loadA(regD); + loadX(regD); + POLL_INT + DONE + + case LAX_abs_y_3: + case LAX_ind_y_4: + + READ_FROM_ADDRESS + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI + CONTINUE + } else { + loadA(regD); + loadX(regD); + POLL_INT + DONE + } + + case LAX_abs_3: + case LAX_abs_y_4: + case LAX_ind_x_5: + case LAX_ind_y_5: + + READ_FROM_ADDRESS; + loadA(regD); + loadX(regD); + POLL_INT + DONE + + + // Instruction: RLA + // + // Operation: ROL, followed by AND + // + // Flags: N Z C I D V + // / / / - - - + + case RLA_zpg_3: + case RLA_zpg_x_4: + + WRITE_TO_ZERO_PAGE + DO_ROL + CONTINUE + + case RLA_zpg_4: + case RLA_zpg_x_5: + + WRITE_TO_ZERO_PAGE + loadA(regA & regD); + POLL_INT + DONE + + case RLA_abs_4: + case RLA_abs_x_5: + case RLA_abs_y_5: + case RLA_ind_x_6: + case RLA_ind_y_6: + + WRITE_TO_ADDRESS + DO_ROL + CONTINUE + + case RLA_abs_5: + case RLA_abs_x_6: + case RLA_abs_y_6: + case RLA_ind_x_7: + case RLA_ind_y_7: + + WRITE_TO_ADDRESS + loadA(regA & regD); + POLL_INT + DONE + + // Instruction: RRA + // + // Operation: ROR, followed by ADC + // + // Flags: N Z C I D V + // / / / - - / + + case RRA_zpg_3: + case RRA_zpg_x_4: + + WRITE_TO_ZERO_PAGE + DO_ROR + CONTINUE + + case RRA_zpg_4: + case RRA_zpg_x_5: + + WRITE_TO_ZERO_PAGE + adc(regD); + POLL_INT + DONE + + case RRA_abs_4: + case RRA_abs_x_5: + case RRA_abs_y_5: + case RRA_ind_x_6: + case RRA_ind_y_6: + + WRITE_TO_ADDRESS + DO_ROR + CONTINUE + + case RRA_abs_5: + case RRA_abs_x_6: + case RRA_abs_y_6: + case RRA_ind_x_7: + case RRA_ind_y_7: + + WRITE_TO_ADDRESS + adc(regD); + POLL_INT + DONE + + + // Instruction: SAX + // + // Operation: Mem := A & X + // + // Flags: N Z C I D V + // - - - - - - + + case SAX_zpg_2: + case SAX_zpg_y_3: + + regD = regA & regX; + WRITE_TO_ZERO_PAGE + POLL_INT + DONE + + case SAX_abs_3: + case SAX_ind_x_5: + + regD = regA & regX; + WRITE_TO_ADDRESS + POLL_INT + DONE + + + // Instruction: SHA + // + // Operation: Mem := A & X & (M + 1) + // + // Flags: N Z C I D V + // - - - - - - + + case SHA_abs_y_3: + + IDLE_READ_FROM_ADDRESS + + /* "There are two unstable conditions, the first is when a DMA is + * going on while the instruction executes (the CPU is halted by + * the VIC-II) then the & M+1 part drops off." + */ + + regD = regA & regX & (rdyLineUp == cycle ? 0xFF : regADH + 1); + + /* "The other unstable condition is when the addressing/indexing + * causes a page boundary crossing, in that case the highbyte of + * the target address may become equal to the value stored." + */ + + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI; + regADH = regA & regX & regADH; + } + + CONTINUE + + case SHA_abs_y_4: + + WRITE_TO_ADDRESS + POLL_INT + DONE + + case SHA_ind_y_4: + + IDLE_READ_FROM_ADDRESS + + /* "There are two unstable conditions, the first is when a DMA is + * going on while the instruction executes (the CPU is halted by + * the VIC-II) then the & M+1 part drops off." + */ + + regD = regA & regX & (rdyLineUp == cycle ? 0xFF : regADH + 1); + + /* "The other unstable condition is when the addressing/indexing + * causes a page boundary crossing, in that case the highbyte of + * the target address may become equal to the value stored." + */ + + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI; + regADH = regA & regX & regADH; + } + + CONTINUE + + case SHA_ind_y_5: + + WRITE_TO_ADDRESS + POLL_INT + DONE + + + // Instruction: SHX + // + // Operation: Mem := X & (HI_BYTE(op) + 1) + // + // Flags: N Z C I D V + // - - - - - - + + case SHX_abs_y_3: + + IDLE_READ_FROM_ADDRESS + + /* "There are two unstable conditions, the first is when a DMA is + * going on while the instruction executes (the CPU is halted by + * the VIC-II) then the & M+1 part drops off." + */ + + regD = regX & (rdyLineUp == cycle ? 0xFF : regADH + 1); + + /* "The other unstable condition is when the addressing/indexing + * causes a page boundary crossing, in that case the highbyte of + * the target address may become equal to the value stored." + */ + + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI; + regADH = regX & regADH; + } + + CONTINUE + + case SHX_abs_y_4: + + WRITE_TO_ADDRESS + POLL_INT + DONE + + + // Instruction: SHY + // + // Operation: Mem := Y & (HI_BYTE(op) + 1) + // + // Flags: N Z C I D V + // - - - - - - + + case SHY_abs_x_3: + + IDLE_READ_FROM_ADDRESS + + /* "There are two unstable conditions, the first is when a DMA is + * going on while the instruction executes (the CPU is halted by + * the VIC-II) then the & M+1 part drops off." + */ + + regD = regY & (rdyLineUp == cycle ? 0xFF : regADH + 1); + + /* "The other unstable condition is when the addressing/indexing + * causes a page boundary crossing, in that case the highbyte of + * the target address may become equal to the value stored." + */ + + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI; + regADH = regY & regADH; + } + + CONTINUE + + case SHY_abs_x_4: + + WRITE_TO_ADDRESS + POLL_INT + DONE + + + // Instruction: SLO (ASO) + // + // Operation: ASL memory location, followed by OR on accumulator + // + // Flags: N Z C I D V + // / / / - - - + + #define DO_SLO setC(regD & 128); regD <<= 1; + + case SLO_zpg_3: + case SLO_zpg_x_4: + + WRITE_TO_ZERO_PAGE + DO_SLO + CONTINUE + + case SLO_zpg_4: + case SLO_zpg_x_5: + + WRITE_TO_ZERO_PAGE + loadA(regA | regD); + POLL_INT + DONE + + case SLO_abs_4: + case SLO_abs_x_5: + case SLO_abs_y_5: + case SLO_ind_x_6: + case SLO_ind_y_6: + + WRITE_TO_ADDRESS + DO_SLO + CONTINUE + + case SLO_abs_5: + case SLO_abs_x_6: + case SLO_abs_y_6: + case SLO_ind_x_7: + case SLO_ind_y_7: + + WRITE_TO_ADDRESS + loadA(regA | regD); + POLL_INT + DONE + + + // Instruction: SRE (LSE) + // + // Operation: LSR, followed by EOR + // + // Flags: N Z C I D V + // / / / - - - + + #define DO_SRE setC(regD & 1); regD >>= 1; + + case SRE_zpg_3: + case SRE_zpg_x_4: + + WRITE_TO_ZERO_PAGE + DO_SRE + CONTINUE + + case SRE_zpg_4: + case SRE_zpg_x_5: + + WRITE_TO_ZERO_PAGE + loadA(regA ^ regD); + POLL_INT + DONE + + case SRE_abs_4: + case SRE_abs_x_5: + case SRE_abs_y_5: + case SRE_ind_x_6: + case SRE_ind_y_6: + + WRITE_TO_ADDRESS + DO_SRE + CONTINUE + + case SRE_abs_5: + case SRE_abs_x_6: + case SRE_abs_y_6: + case SRE_ind_x_7: + case SRE_ind_y_7: + + WRITE_TO_ADDRESS + loadA(regA ^ regD); + POLL_INT + DONE + + + // Instruction: TAS (SHS) + // + // Operation: SP := A & X, Mem := SP & (HI_BYTE(op) + 1) + // + // Flags: N Z C I D V + // - - - - - - + + case TAS_abs_y_3: + + IDLE_READ_FROM_ADDRESS + + regSP = regA & regX; + + /* "There are two unstable conditions, the first is when a DMA is + * going on while the instruction executes (the CPU is halted by + * the VIC-II) then the & M+1 part drops off." + */ + + regD = regA & regX & (rdyLineUp == cycle ? 0xFF : regADH + 1); + + /* "The other unstable condition is when the addressing/indexing + * causes a page boundary crossing, in that case the highbyte of + * the target address may become equal to the value stored." + */ + + if (PAGE_BOUNDARY_CROSSED) { + FIX_ADDR_HI; + regADH = regA & regX & regADH; + } + + CONTINUE + + case TAS_abs_y_4: + + WRITE_TO_ADDRESS + POLL_INT + DONE + + // Instruction: ANE + // + // Operation: A = X & op & (A | 0xEE) (taken from Frodo) + // + // Flags: N Z C I D V + // / / - - - - + + case ANE_imm: + + READ_IMMEDIATE + loadA(regX & regD & (regA | 0xEE)); + POLL_INT + DONE + + + // Instruction: LXA + // + // Operation: A = X = op & (A | 0xEE) (taken from Frodo) + // + // Flags: N Z C I D V + // / / - - - - + + case LXA_imm: + + READ_IMMEDIATE + regX = regD & (regA | 0xEE); + loadA(regX); + POLL_INT + DONE + + default: + + panic("UNIMPLEMENTED OPCODE: %d (%02X)\n", next, next); + assert(false); + } +} + diff --git a/C64/CPU/CPUInstructions.h b/C64/CPU/CPUInstructions.h new file mode 100755 index 00000000..8a05e5d2 --- /dev/null +++ b/C64/CPU/CPUInstructions.h @@ -0,0 +1,395 @@ +/*! + * @header CPUInstructions.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Microinstructions +typedef enum { + + fetch, + + JAM, JAM_2, + + irq_2, irq_3, irq_4, irq_5, irq_6, irq_7, + nmi_2, nmi_3, nmi_4, nmi_5, nmi_6, nmi_7, + + ADC_imm, + ADC_zpg, ADC_zpg_2, + ADC_zpg_x, ADC_zpg_x_2, ADC_zpg_x_3, + ADC_abs, ADC_abs_2, ADC_abs_3, + ADC_abs_x, ADC_abs_x_2, ADC_abs_x_3, ADC_abs_x_4, + ADC_abs_y, ADC_abs_y_2, ADC_abs_y_3, ADC_abs_y_4, + ADC_ind_x, ADC_ind_x_2, ADC_ind_x_3, ADC_ind_x_4, ADC_ind_x_5, + ADC_ind_y, ADC_ind_y_2, ADC_ind_y_3, ADC_ind_y_4, ADC_ind_y_5, + + AND_imm, + AND_zpg, AND_zpg_2, + AND_zpg_x, AND_zpg_x_2, AND_zpg_x_3, + AND_abs, AND_abs_2, AND_abs_3, + AND_abs_x, AND_abs_x_2, AND_abs_x_3, AND_abs_x_4, + AND_abs_y, AND_abs_y_2, AND_abs_y_3, AND_abs_y_4, + AND_ind_x, AND_ind_x_2, AND_ind_x_3, AND_ind_x_4, AND_ind_x_5, + AND_ind_y, AND_ind_y_2, AND_ind_y_3, AND_ind_y_4, AND_ind_y_5, + + ASL_acc, + ASL_zpg, ASL_zpg_2, ASL_zpg_3, ASL_zpg_4, + ASL_zpg_x, ASL_zpg_x_2, ASL_zpg_x_3, ASL_zpg_x_4, ASL_zpg_x_5, + ASL_abs, ASL_abs_2, ASL_abs_3, ASL_abs_4, ASL_abs_5, + ASL_abs_x, ASL_abs_x_2, ASL_abs_x_3, ASL_abs_x_4, ASL_abs_x_5, ASL_abs_x_6, + ASL_ind_x, ASL_ind_x_2, ASL_ind_x_3, ASL_ind_x_4, ASL_ind_x_5, ASL_ind_x_6, ASL_ind_x_7, + + branch_3_underflow, branch_3_overflow, + BCC_rel, BCC_rel_2, + BCS_rel, BCS_rel_2, + BEQ_rel, BEQ_rel_2, + + BIT_zpg, BIT_zpg_2, + BIT_abs, BIT_abs_2, BIT_abs_3, + + BMI_rel, BMI_rel_2, + BNE_rel, BNE_rel_2, + BPL_rel, BPL_rel_2, + + BRK, BRK_2, BRK_3, BRK_4, BRK_5, BRK_6, + BRK_nmi_4, BRK_nmi_5, BRK_nmi_6, + + BVC_rel, BVC_rel_2, + BVS_rel, BVS_rel_2, + CLC, + CLD, + CLI, + CLV, + + CMP_imm, + CMP_zpg, CMP_zpg_2, + CMP_zpg_x, CMP_zpg_x_2, CMP_zpg_x_3, + CMP_abs, CMP_abs_2, CMP_abs_3, + CMP_abs_x, CMP_abs_x_2, CMP_abs_x_3, CMP_abs_x_4, + CMP_abs_y, CMP_abs_y_2, CMP_abs_y_3, CMP_abs_y_4, + CMP_ind_x, CMP_ind_x_2, CMP_ind_x_3, CMP_ind_x_4, CMP_ind_x_5, + CMP_ind_y, CMP_ind_y_2, CMP_ind_y_3, CMP_ind_y_4, CMP_ind_y_5, + + CPX_imm, + CPX_zpg, CPX_zpg_2, + CPX_abs, CPX_abs_2, CPX_abs_3, + + CPY_imm, + CPY_zpg, CPY_zpg_2, + CPY_abs, CPY_abs_2, CPY_abs_3, + + DEC_zpg, DEC_zpg_2, DEC_zpg_3, DEC_zpg_4, + DEC_zpg_x, DEC_zpg_x_2, DEC_zpg_x_3, DEC_zpg_x_4, DEC_zpg_x_5, + DEC_abs, DEC_abs_2, DEC_abs_3, DEC_abs_4, DEC_abs_5, + DEC_abs_x, DEC_abs_x_2, DEC_abs_x_3, DEC_abs_x_4, DEC_abs_x_5, DEC_abs_x_6, + DEC_ind_x, DEC_ind_x_2, DEC_ind_x_3, DEC_ind_x_4, DEC_ind_x_5, DEC_ind_x_6, DEC_ind_x_7, + + DEX, + DEY, + + EOR_imm, + EOR_zpg, EOR_zpg_2, + EOR_zpg_x, EOR_zpg_x_2, EOR_zpg_x_3, + EOR_abs, EOR_abs_2, EOR_abs_3, + EOR_abs_x, EOR_abs_x_2, EOR_abs_x_3, EOR_abs_x_4, + EOR_abs_y, EOR_abs_y_2, EOR_abs_y_3, EOR_abs_y_4, + EOR_ind_x, EOR_ind_x_2, EOR_ind_x_3, EOR_ind_x_4, EOR_ind_x_5, + EOR_ind_y, EOR_ind_y_2, EOR_ind_y_3, EOR_ind_y_4, EOR_ind_y_5, + + INC_zpg, INC_zpg_2, INC_zpg_3, INC_zpg_4, + INC_zpg_x, INC_zpg_x_2, INC_zpg_x_3, INC_zpg_x_4, INC_zpg_x_5, + INC_abs, INC_abs_2, INC_abs_3, INC_abs_4, INC_abs_5, + INC_abs_x, INC_abs_x_2, INC_abs_x_3, INC_abs_x_4, INC_abs_x_5, INC_abs_x_6, + INC_ind_x, INC_ind_x_2, INC_ind_x_3, INC_ind_x_4, INC_ind_x_5, INC_ind_x_6, INC_ind_x_7, + + INX, + INY, + + JMP_abs, JMP_abs_2, + JMP_abs_ind, JMP_abs_ind_2, JMP_abs_ind_3, JMP_abs_ind_4, + + JSR, JSR_2, JSR_3, JSR_4, JSR_5, + + LDA_imm, + LDA_zpg, LDA_zpg_2, + LDA_zpg_x, LDA_zpg_x_2, LDA_zpg_x_3, + LDA_abs, LDA_abs_2, LDA_abs_3, + LDA_abs_x, LDA_abs_x_2, LDA_abs_x_3, LDA_abs_x_4, + LDA_abs_y, LDA_abs_y_2, LDA_abs_y_3, LDA_abs_y_4, + LDA_ind_x, LDA_ind_x_2, LDA_ind_x_3, LDA_ind_x_4, LDA_ind_x_5, + LDA_ind_y, LDA_ind_y_2, LDA_ind_y_3, LDA_ind_y_4, LDA_ind_y_5, + + LDX_imm, + LDX_zpg, LDX_zpg_2, + LDX_zpg_y, LDX_zpg_y_2, LDX_zpg_y_3, + LDX_abs, LDX_abs_2, LDX_abs_3, + LDX_abs_y, LDX_abs_y_2, LDX_abs_y_3, LDX_abs_y_4, + LDX_ind_x, LDX_ind_x_2, LDX_ind_x_3, LDX_ind_x_4, LDX_ind_x_5, + LDX_ind_y, LDX_ind_y_2, LDX_ind_y_3, LDX_ind_y_4, LDX_ind_y_5, + + LDY_imm, + LDY_zpg, LDY_zpg_2, + LDY_zpg_x, LDY_zpg_x_2, LDY_zpg_x_3, + LDY_abs, LDY_abs_2, LDY_abs_3, + LDY_abs_x, LDY_abs_x_2, LDY_abs_x_3, LDY_abs_x_4, + LDY_ind_x, LDY_ind_x_2, LDY_ind_x_3, LDY_ind_x_4, LDY_ind_x_5, + LDY_ind_y, LDY_ind_y_2, LDY_ind_y_3, LDY_ind_y_4, LDY_ind_y_5, + + LSR_acc, + LSR_zpg, LSR_zpg_2, LSR_zpg_3, LSR_zpg_4, + LSR_zpg_x, LSR_zpg_x_2, LSR_zpg_x_3, LSR_zpg_x_4, LSR_zpg_x_5, + LSR_abs, LSR_abs_2, LSR_abs_3, LSR_abs_4, LSR_abs_5, + LSR_abs_x, LSR_abs_x_2, LSR_abs_x_3, LSR_abs_x_4, LSR_abs_x_5, LSR_abs_x_6, + LSR_abs_y, LSR_abs_y_2, LSR_abs_y_3, LSR_abs_y_4, LSR_abs_y_5, LSR_abs_y_6, + LSR_ind_x, LSR_ind_x_2, LSR_ind_x_3, LSR_ind_x_4, LSR_ind_x_5, LSR_ind_x_6, LSR_ind_x_7, + LSR_ind_y, LSR_ind_y_2, LSR_ind_y_3, LSR_ind_y_4, LSR_ind_y_5, LSR_ind_y_6, LSR_ind_y_7, + + NOP, + NOP_imm, + NOP_zpg, NOP_zpg_2, + NOP_zpg_x, NOP_zpg_x_2, NOP_zpg_x_3, + NOP_abs, NOP_abs_2, NOP_abs_3, + NOP_abs_x, NOP_abs_x_2, NOP_abs_x_3, NOP_abs_x_4, + + ORA_imm, + ORA_zpg, ORA_zpg_2, + ORA_zpg_x, ORA_zpg_x_2, ORA_zpg_x_3, + ORA_abs, ORA_abs_2, ORA_abs_3, + ORA_abs_x, ORA_abs_x_2, ORA_abs_x_3, ORA_abs_x_4, + ORA_abs_y, ORA_abs_y_2, ORA_abs_y_3, ORA_abs_y_4, + ORA_ind_x, ORA_ind_x_2, ORA_ind_x_3, ORA_ind_x_4, ORA_ind_x_5, + ORA_ind_y, ORA_ind_y_2, ORA_ind_y_3, ORA_ind_y_4, ORA_ind_y_5, + + PHA, PHA_2, + PHP, PHP_2, + PLA, PLA_2, PLA_3, + PLP, PLP_2, PLP_3, + + ROL_acc, + ROL_zpg, ROL_zpg_2, ROL_zpg_3, ROL_zpg_4, + ROL_zpg_x, ROL_zpg_x_2, ROL_zpg_x_3, ROL_zpg_x_4, ROL_zpg_x_5, + ROL_abs, ROL_abs_2, ROL_abs_3, ROL_abs_4, ROL_abs_5, + ROL_abs_x, ROL_abs_x_2, ROL_abs_x_3, ROL_abs_x_4, ROL_abs_x_5, ROL_abs_x_6, + ROL_ind_x, ROL_ind_x_2, ROL_ind_x_3, ROL_ind_x_4, ROL_ind_x_5, ROL_ind_x_6, ROL_ind_x_7, + + ROR_acc, + ROR_zpg, ROR_zpg_2, ROR_zpg_3, ROR_zpg_4, + ROR_zpg_x, ROR_zpg_x_2, ROR_zpg_x_3, ROR_zpg_x_4, ROR_zpg_x_5, + ROR_abs, ROR_abs_2, ROR_abs_3, ROR_abs_4, ROR_abs_5, + ROR_abs_x, ROR_abs_x_2, ROR_abs_x_3, ROR_abs_x_4, ROR_abs_x_5, ROR_abs_x_6, + ROR_ind_x, ROR_ind_x_2, ROR_ind_x_3, ROR_ind_x_4, ROR_ind_x_5, ROR_ind_x_6, ROR_ind_x_7, + + RTI, RTI_2, RTI_3, RTI_4, RTI_5, + RTS, RTS_2, RTS_3, RTS_4, RTS_5, + + SBC_imm, + SBC_zpg, SBC_zpg_2, + SBC_zpg_x, SBC_zpg_x_2, SBC_zpg_x_3, + SBC_abs, SBC_abs_2, SBC_abs_3, + SBC_abs_x, SBC_abs_x_2, SBC_abs_x_3, SBC_abs_x_4, + SBC_abs_y, SBC_abs_y_2, SBC_abs_y_3, SBC_abs_y_4, + SBC_ind_x, SBC_ind_x_2, SBC_ind_x_3, SBC_ind_x_4, SBC_ind_x_5, + SBC_ind_y, SBC_ind_y_2, SBC_ind_y_3, SBC_ind_y_4, SBC_ind_y_5, + + SEC, + SED, + SEI, SEI_cont, + + STA_zpg, STA_zpg_2, + STA_zpg_x, STA_zpg_x_2, STA_zpg_x_3, + STA_abs, STA_abs_2, STA_abs_3, + STA_abs_x, STA_abs_x_2, STA_abs_x_3, STA_abs_x_4, + STA_abs_y, STA_abs_y_2, STA_abs_y_3, STA_abs_y_4, + STA_ind_x, STA_ind_x_2, STA_ind_x_3, STA_ind_x_4, STA_ind_x_5, + STA_ind_y, STA_ind_y_2, STA_ind_y_3, STA_ind_y_4, STA_ind_y_5, + + STX_zpg, STX_zpg_2, + STX_zpg_y, STX_zpg_y_2, STX_zpg_y_3, + STX_abs, STX_abs_2, STX_abs_3, + + STY_zpg, STY_zpg_2, + STY_zpg_x, STY_zpg_x_2, STY_zpg_x_3, + STY_abs, STY_abs_2, STY_abs_3, + + TAX, + TAY, + TSX, + TXA, + TXS, + TYA, + + // Illegal instructions + + ALR_imm, + ANC_imm, + ANE_imm, + ARR_imm, + AXS_imm, + + DCP_zpg, DCP_zpg_2, DCP_zpg_3, DCP_zpg_4, + DCP_zpg_x, DCP_zpg_x_2, DCP_zpg_x_3, DCP_zpg_x_4, DCP_zpg_x_5, + DCP_abs, DCP_abs_2, DCP_abs_3, DCP_abs_4, DCP_abs_5, + DCP_abs_x, DCP_abs_x_2, DCP_abs_x_3, DCP_abs_x_4, DCP_abs_x_5, DCP_abs_x_6, + DCP_abs_y, DCP_abs_y_2, DCP_abs_y_3, DCP_abs_y_4, DCP_abs_y_5, DCP_abs_y_6, + DCP_ind_x, DCP_ind_x_2, DCP_ind_x_3, DCP_ind_x_4, DCP_ind_x_5, DCP_ind_x_6, DCP_ind_x_7, + DCP_ind_y, DCP_ind_y_2, DCP_ind_y_3, DCP_ind_y_4, DCP_ind_y_5, DCP_ind_y_6, DCP_ind_y_7, + + ISC_zpg, ISC_zpg_2, ISC_zpg_3, ISC_zpg_4, + ISC_zpg_x, ISC_zpg_x_2, ISC_zpg_x_3, ISC_zpg_x_4, ISC_zpg_x_5, + ISC_abs, ISC_abs_2, ISC_abs_3, ISC_abs_4, ISC_abs_5, + ISC_abs_x, ISC_abs_x_2, ISC_abs_x_3, ISC_abs_x_4, ISC_abs_x_5, ISC_abs_x_6, + ISC_abs_y, ISC_abs_y_2, ISC_abs_y_3, ISC_abs_y_4, ISC_abs_y_5, ISC_abs_y_6, + ISC_ind_x, ISC_ind_x_2, ISC_ind_x_3, ISC_ind_x_4, ISC_ind_x_5, ISC_ind_x_6, ISC_ind_x_7, + ISC_ind_y, ISC_ind_y_2, ISC_ind_y_3, ISC_ind_y_4, ISC_ind_y_5, ISC_ind_y_6, ISC_ind_y_7, + + LAS_abs_y, LAS_abs_y_2, LAS_abs_y_3, LAS_abs_y_4, + + LAX_zpg, LAX_zpg_2, + LAX_zpg_y, LAX_zpg_y_2, LAX_zpg_y_3, + LAX_abs, LAX_abs_2, LAX_abs_3, + LAX_abs_y, LAX_abs_y_2, LAX_abs_y_3, LAX_abs_y_4, + LAX_ind_x, LAX_ind_x_2, LAX_ind_x_3, LAX_ind_x_4, LAX_ind_x_5, + LAX_ind_y, LAX_ind_y_2, LAX_ind_y_3, LAX_ind_y_4, LAX_ind_y_5, + + LXA_imm, + + RLA_zpg, RLA_zpg_2, RLA_zpg_3, RLA_zpg_4, + RLA_zpg_x, RLA_zpg_x_2, RLA_zpg_x_3, RLA_zpg_x_4, RLA_zpg_x_5, + RLA_abs, RLA_abs_2, RLA_abs_3, RLA_abs_4, RLA_abs_5, + RLA_abs_x, RLA_abs_x_2, RLA_abs_x_3, RLA_abs_x_4, RLA_abs_x_5, RLA_abs_x_6, + RLA_abs_y, RLA_abs_y_2, RLA_abs_y_3, RLA_abs_y_4, RLA_abs_y_5, RLA_abs_y_6, + RLA_ind_x, RLA_ind_x_2, RLA_ind_x_3, RLA_ind_x_4, RLA_ind_x_5, RLA_ind_x_6, RLA_ind_x_7, + RLA_ind_y, RLA_ind_y_2, RLA_ind_y_3, RLA_ind_y_4, RLA_ind_y_5, RLA_ind_y_6, RLA_ind_y_7, + + RRA_zpg, RRA_zpg_2, RRA_zpg_3, RRA_zpg_4, + RRA_zpg_x, RRA_zpg_x_2, RRA_zpg_x_3, RRA_zpg_x_4, RRA_zpg_x_5, + RRA_abs, RRA_abs_2, RRA_abs_3, RRA_abs_4, RRA_abs_5, + RRA_abs_x, RRA_abs_x_2, RRA_abs_x_3, RRA_abs_x_4, RRA_abs_x_5, RRA_abs_x_6, + RRA_abs_y, RRA_abs_y_2, RRA_abs_y_3, RRA_abs_y_4, RRA_abs_y_5, RRA_abs_y_6, + RRA_ind_x, RRA_ind_x_2, RRA_ind_x_3, RRA_ind_x_4, RRA_ind_x_5, RRA_ind_x_6, RRA_ind_x_7, + RRA_ind_y, RRA_ind_y_2, RRA_ind_y_3, RRA_ind_y_4, RRA_ind_y_5, RRA_ind_y_6, RRA_ind_y_7, + + SAX_zpg, SAX_zpg_2, + SAX_zpg_y, SAX_zpg_y_2, SAX_zpg_y_3, + SAX_abs, SAX_abs_2, SAX_abs_3, + SAX_ind_x, SAX_ind_x_2, SAX_ind_x_3, SAX_ind_x_4, SAX_ind_x_5, + + SHA_ind_y, SHA_ind_y_2, SHA_ind_y_3, SHA_ind_y_4, SHA_ind_y_5, + SHA_abs_y, SHA_abs_y_2, SHA_abs_y_3, SHA_abs_y_4, + + SHX_abs_y, SHX_abs_y_2, SHX_abs_y_3, SHX_abs_y_4, + SHY_abs_x, SHY_abs_x_2, SHY_abs_x_3, SHY_abs_x_4, + + SLO_zpg, SLO_zpg_2, SLO_zpg_3, SLO_zpg_4, + SLO_zpg_x, SLO_zpg_x_2, SLO_zpg_x_3, SLO_zpg_x_4, SLO_zpg_x_5, + SLO_abs, SLO_abs_2, SLO_abs_3, SLO_abs_4, SLO_abs_5, + SLO_abs_x, SLO_abs_x_2, SLO_abs_x_3, SLO_abs_x_4, SLO_abs_x_5, SLO_abs_x_6, + SLO_abs_y, SLO_abs_y_2, SLO_abs_y_3, SLO_abs_y_4, SLO_abs_y_5, SLO_abs_y_6, + SLO_ind_x, SLO_ind_x_2, SLO_ind_x_3, SLO_ind_x_4, SLO_ind_x_5, SLO_ind_x_6, SLO_ind_x_7, + SLO_ind_y, SLO_ind_y_2, SLO_ind_y_3, SLO_ind_y_4, SLO_ind_y_5, SLO_ind_y_6, SLO_ind_y_7, + + SRE_zpg, SRE_zpg_2, SRE_zpg_3, SRE_zpg_4, + SRE_zpg_x, SRE_zpg_x_2, SRE_zpg_x_3, SRE_zpg_x_4, SRE_zpg_x_5, + SRE_abs, SRE_abs_2, SRE_abs_3, SRE_abs_4, SRE_abs_5, + SRE_abs_x, SRE_abs_x_2, SRE_abs_x_3, SRE_abs_x_4, SRE_abs_x_5, SRE_abs_x_6, + SRE_abs_y, SRE_abs_y_2, SRE_abs_y_3, SRE_abs_y_4, SRE_abs_y_5, SRE_abs_y_6, + SRE_ind_x, SRE_ind_x_2, SRE_ind_x_3, SRE_ind_x_4, SRE_ind_x_5, SRE_ind_x_6, SRE_ind_x_7, + SRE_ind_y, SRE_ind_y_2, SRE_ind_y_3, SRE_ind_y_4, SRE_ind_y_5, SRE_ind_y_6, SRE_ind_y_7, + + TAS_abs_y, TAS_abs_y_2, TAS_abs_y_3, TAS_abs_y_4 + +} MicroInstruction; + +// Atomic CPU tasks +#define FETCH_OPCODE \ + if (likely(rdyLine)) instr = mem->peek(regPC++); else return true; +#define FETCH_ADDR_LO \ + if (likely(rdyLine)) regADL = mem->peek(regPC++); else return true; +#define FETCH_ADDR_HI \ + if (likely(rdyLine)) regADH = mem->peek(regPC++); else return true; +#define FETCH_POINTER_ADDR \ + if (likely(rdyLine)) regIDL = mem->peek(regPC++); else return true; +#define FETCH_ADDR_LO_INDIRECT \ + if (likely(rdyLine)) regADL = mem->peek((uint16_t)regIDL++); else return true; +#define FETCH_ADDR_HI_INDIRECT \ + if (likely(rdyLine)) regADH = mem->peek((uint16_t)regIDL++); else return true; +#define IDLE_FETCH \ + if (likely(rdyLine)) (void)mem->peek(regPC); else return true; + + +#define READ_RELATIVE \ + if (likely(rdyLine)) regD = mem->peek(regPC); else return true; +#define READ_IMMEDIATE \ + if (likely(rdyLine)) regD = mem->peek(regPC++); else return true; +#define READ_FROM(x) \ + if (likely(rdyLine)) regD = mem->peek(x); else return true; +#define READ_FROM_ADDRESS \ + if (likely(rdyLine)) regD = mem->peek(HI_LO(regADH, regADL)); else return true; +#define READ_FROM_ZERO_PAGE \ + if (likely(rdyLine)) regD = mem->peekZP(regADL); else return true; +#define READ_FROM_ADDRESS_INDIRECT \ + if (likely(rdyLine)) regD = mem->peekZP(regDL); else return true; + +#define IDLE_READ_IMPLIED \ + if (likely(rdyLine)) (void)mem->peek(regPC); else return true; +#define IDLE_READ_IMMEDIATE \ + if (likely(rdyLine)) (void)mem->peek(regPC++); else return true; +#define IDLE_READ_FROM(x) \ + if (likely(rdyLine)) (void)mem->peek(x); else return true; +#define IDLE_READ_FROM_ADDRESS \ + if (likely(rdyLine)) (void)(mem->peek(HI_LO(regADH, regADL))); else return true; +#define IDLE_READ_FROM_ZERO_PAGE \ + if (likely(rdyLine)) (void)mem->peekZP(regADL); else return true; +#define IDLE_READ_FROM_ADDRESS_INDIRECT \ + if (likely(rdyLine)) (void)mem->peekZP(regIDL); else return true; + +#define WRITE_TO_ADDRESS \ +mem->poke(HI_LO(regADH, regADL), regD); +#define WRITE_TO_ADDRESS_AND_SET_FLAGS \ + mem->poke(HI_LO(regADH, regADL), regD); setN(regD & 0x80); setZ(regD == 0); +#define WRITE_TO_ZERO_PAGE \ + mem->pokeZP(regADL, regD); +#define WRITE_TO_ZERO_PAGE_AND_SET_FLAGS \ + mem->pokeZP(regADL, regD); setN(regD & 0x80); setZ(regD == 0); + +#define ADD_INDEX_X overflow = ((int)regADL + (int)regX > 0xFF); regADL += regX; +#define ADD_INDEX_Y overflow = ((int)regADL + (int)regY > 0xFF); regADL += regY; +#define ADD_INDEX_X_INDIRECT regIDL += regX; +#define ADD_INDEX_Y_INDIRECT regIDL += regY; + +#define PUSH_PCL mem->pokeStack(regSP--, LO_BYTE(regPC)); +#define PUSH_PCH mem->pokeStack(regSP--, HI_BYTE(regPC)); +#define PUSH_P mem->pokeStack(regSP--, getP()); +#define PUSH_P_WITH_B_SET mem->pokeStack(regSP--, getP() | B_FLAG); +#define PUSH_A mem->pokeStack(regSP--, regA); +#define PULL_PCL if (likely(rdyLine)) setPCL(mem->peekStack(regSP)); else return true; +#define PULL_PCH if (likely(rdyLine)) setPCH(mem->peekStack(regSP)); else return true; +#define PULL_P if (likely(rdyLine)) setPWithoutB(mem->peekStack(regSP)); else return true; +#define PULL_A if (likely(rdyLine)) loadA(mem->peekStack(regSP)); else return true; +#define IDLE_PULL if (likely(rdyLine)) (void)mem->peekStack(regSP); else return true; + +#define PAGE_BOUNDARY_CROSSED overflow +#define FIX_ADDR_HI regADH++; + +#define POLL_IRQ doIrq = (levelDetector.delayed() && !getI()); +#define POLL_NMI doNmi = edgeDetector.delayed(); +#define POLL_INT POLL_IRQ POLL_NMI +#define POLL_INT_AGAIN doIrq |= (levelDetector.delayed() && !getI()); \ + doNmi |= edgeDetector.delayed(); +#define CONTINUE next = (MicroInstruction)((int)next+1); return true; +#define DONE next = fetch; return true; diff --git a/C64/CPU/CPU_types.h b/C64/CPU/CPU_types.h new file mode 100755 index 00000000..a9b75d88 --- /dev/null +++ b/C64/CPU/CPU_types.h @@ -0,0 +1,130 @@ +/*! + * @header CPU_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CPU_TYPES_H +#define CPU_TYPES_H + +#include + +//! @brief Processor model +typedef enum { + MOS_6510 = 0, + MOS_6502 = 1 +} CPUModel; + +//! @brief Addressing mode +typedef enum { + ADDR_IMPLIED, + ADDR_ACCUMULATOR, + ADDR_IMMEDIATE, + ADDR_ZERO_PAGE, + ADDR_ZERO_PAGE_X, + ADDR_ZERO_PAGE_Y, + ADDR_ABSOLUTE, + ADDR_ABSOLUTE_X, + ADDR_ABSOLUTE_Y, + ADDR_INDIRECT_X, + ADDR_INDIRECT_Y, + ADDR_RELATIVE, + ADDR_DIRECT, + ADDR_INDIRECT +} AddressingMode; + +/*! @brief Breakpoint type + * @details Each memory call is marked with a breakpoint tag. Originally, + * each cell is tagged with NO_BREAKPOINT which has no effect. CPU + * execution will stop if the memory cell is tagged with one of the + * following breakpoint types: + * HARD_BREAKPOINT : Execution is halted. + * SOFT_BREAKPOINT : Execution is halted and the tag is deleted. + */ +typedef enum { + NO_BREAKPOINT = 0x00, + HARD_BREAKPOINT = 0x01, + SOFT_BREAKPOINT = 0x02 +} Breakpoint; + + +/*! @brief Error state of the virtual CPU + * @details CPU_OK indicates normal operation. When a (soft or hard) + * breakpoint is reached, state CPU_BREAKPOINT_REACHED is entered. + * CPU_ILLEGAL_INSTRUCTION is set when an opcode is not understood + * by the CPU. Once the CPU enters a different state than CPU_OK, + * the execution thread is terminated. + */ +typedef enum { + CPU_OK = 0, + CPU_SOFT_BREAKPOINT_REACHED, + CPU_HARD_BREAKPOINT_REACHED, + CPU_ILLEGAL_INSTRUCTION +} ErrorState; + +/*! @brief CPU info + * @details Used by getInfo() to collect debug information + */ +typedef struct { + uint64_t cycle; + uint16_t pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t sp; + bool nFlag; + bool vFlag; + bool bFlag; + bool dFlag; + bool iFlag; + bool zFlag; + bool cFlag; +} CPUInfo; + +//! @brief Recorded instruction +typedef struct { + uint64_t cycle; + uint16_t pc; + uint8_t byte1; + uint8_t byte2; + uint8_t byte3; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t sp; + uint8_t flags; +} RecordedInstruction; + +//! @brief Disassembled instruction +typedef struct { + uint64_t cycle; + uint16_t addr; + uint8_t size; + char byte1[4]; + char byte2[4]; + char byte3[4]; + char pc[6]; + char a[4]; + char x[4]; + char y[4]; + char sp[4]; + char flags[9]; + char command[16]; +} DisassembledInstruction; + +#endif diff --git a/C64/Cartridges/Cartridge.cpp b/C64/Cartridges/Cartridge.cpp new file mode 100755 index 00000000..8c2397f8 --- /dev/null +++ b/C64/Cartridges/Cartridge.cpp @@ -0,0 +1,507 @@ +/*! + * @file Cartridge.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "C64.h" + +Cartridge::Cartridge(C64 *c64, const char *description) +{ + setDescription(description); + debug("Creating cartridge at address %p...\n", this); + + this->c64 = c64; + + memset(packet, 0, sizeof(packet)); + + SnapshotItem items[] = { + + // Configuration items + { &gameLineInCrtFile, sizeof(gameLineInCrtFile), KEEP_ON_RESET }, + { &exromLineInCrtFile, sizeof(exromLineInCrtFile), KEEP_ON_RESET }, + { &numPackets, sizeof(numPackets), KEEP_ON_RESET }, + + { &chipL, sizeof(chipL), CLEAR_ON_RESET }, + { &chipH, sizeof(chipH), CLEAR_ON_RESET }, + { &mappedBytesL, sizeof(mappedBytesL), CLEAR_ON_RESET }, + { &mappedBytesH, sizeof(mappedBytesH), CLEAR_ON_RESET }, + { &offsetL, sizeof(offsetL), CLEAR_ON_RESET }, + { &offsetH, sizeof(offsetH), CLEAR_ON_RESET }, + + { &ramCapacity, sizeof(ramCapacity), KEEP_ON_RESET }, + { &persistentRam, sizeof(persistentRam), KEEP_ON_RESET }, + + { &switchPos, sizeof(switchPos), KEEP_ON_RESET }, + { &led, sizeof(led), CLEAR_ON_RESET }, + + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +Cartridge::~Cartridge() +{ + debug("Releasing cartridge...\n"); + + // Deallocate RAM (if any) + if (externalRam) { + assert(ramCapacity > 0); + free(externalRam); + } +} + +void +Cartridge::dealloc() +{ + for (unsigned i = 0; i < numPackets; i++) { + assert(packet[i] != NULL); + delete packet[i]; + packet[i] = NULL; + } + + numPackets = 0; +} + +void +Cartridge::reset() +{ + // Reset external RAM + if (externalRam && !persistentRam) { + memset(externalRam, 0xFF, ramCapacity); + } + + // Reset all chip packets + for (unsigned i = 0; i < numPackets; i++) { + packet[i]->reset(); + } + + // Bank in visibile chips (chips with low numbers show up first) + for (int i = MAX_PACKETS - 1; i >= 0; i--) { + bankIn(i); + } +} + +void +Cartridge::resetCartConfig() { + + c64->expansionport.setGameAndExrom(gameLineInCrtFile, exromLineInCrtFile); +} + +bool +Cartridge::isSupportedType(CartridgeType type) +{ + switch (type) { + + case CRT_NORMAL: + case CRT_ACTION_REPLAY: + case CRT_KCS_POWER: + case CRT_FINAL_III: + case CRT_SIMONS_BASIC: + case CRT_OCEAN: + case CRT_EXPERT: + case CRT_FUNPLAY: + case CRT_SUPER_GAMES: + case CRT_ATOMIC_POWER: + case CRT_EPYX_FASTLOAD: + case CRT_WESTERMANN: + case CRT_REX: + + case CRT_WARPSPEED: + + case CRT_ZAXXON: + case CRT_MAGIC_DESK: + + case CRT_COMAL80: + + case CRT_MIKRO_ASS: + + case CRT_STARDOS: + case CRT_EASYFLASH: + + case CRT_ACTION_REPLAY3: + + case CRT_FREEZE_FRAME: + + case CRT_MACH5: + + case CRT_KINGSOFT: + + case CRT_ISEPIC: + case CRT_GEO_RAM: + return true; + + default: + return false; + } +} + +Cartridge * +Cartridge::makeWithType(C64 *c64, CartridgeType type) +{ + assert(isSupportedType(type)); + + switch (type) { + + case CRT_NORMAL: return new Cartridge(c64); + case CRT_ACTION_REPLAY: return new ActionReplay(c64); + case CRT_KCS_POWER: return new KcsPower(c64); + case CRT_FINAL_III: return new FinalIII(c64); + case CRT_SIMONS_BASIC: return new SimonsBasic(c64); + case CRT_OCEAN: return new Ocean(c64); + case CRT_EXPERT: return new Expert(c64); + case CRT_FUNPLAY: return new Funplay(c64); + case CRT_SUPER_GAMES: return new Supergames(c64); + case CRT_ATOMIC_POWER: return new AtomicPower(c64); + case CRT_EPYX_FASTLOAD: return new EpyxFastLoad(c64); + case CRT_WESTERMANN: return new Westermann(c64); + case CRT_REX: return new Rex(c64); + case CRT_WARPSPEED: return new WarpSpeed(c64); + case CRT_ZAXXON: return new Zaxxon(c64); + case CRT_MAGIC_DESK: return new MagicDesk(c64); + case CRT_COMAL80: return new Comal80(c64); + case CRT_MIKRO_ASS: return new MikroAss(c64); + case CRT_STARDOS: return new StarDos(c64); + case CRT_EASYFLASH: return new EasyFlash(c64); + case CRT_ACTION_REPLAY3: return new ActionReplay3(c64); + case CRT_FREEZE_FRAME: return new FreezeFrame(c64); + case CRT_MACH5: return new Mach5(c64); + case CRT_KINGSOFT: return new Kingsoft(c64); + case CRT_ISEPIC: return new Isepic(c64); + case CRT_GEO_RAM: return new GeoRAM(c64); + + default: + assert(false); // Should not reach + return NULL; + } +} + +Cartridge * +Cartridge::makeWithCRTFile(C64 *c64, CRTFile *file) +{ + Cartridge *cart; + + cart = makeWithType(c64, file->cartridgeType()); + assert(cart != NULL); + + // Remember powerup values for game line and exrom line + cart->gameLineInCrtFile = file->initialGameLine(); + cart->exromLineInCrtFile = file->initialExromLine(); + + // Load chip packets + cart->numPackets = 0; + for (unsigned i = 0; i < file->chipCount(); i++) { + cart->loadChip(i, file); + } + + return cart; +} + +size_t +Cartridge::packetStateSize() +{ + size_t result = 0; + + for (unsigned i = 0; i < numPackets; i++) { + assert(packet[i] != NULL); + result += packet[i]->stateSize(); + } + + return result; +} + +void +Cartridge::loadPacketsFromBuffer(uint8_t **buffer) +{ + for (unsigned i = 0; i < numPackets; i++) { + assert(packet[i] == NULL); + packet[i] = new CartridgeRom(); + packet[i]->loadFromBuffer(buffer); + } +} + +void +Cartridge::savePacketsToBuffer(uint8_t **buffer) +{ + for (unsigned i = 0; i < numPackets; i++) { + assert(packet[i] != NULL); + packet[i]->saveToBuffer(buffer); + } +} + +size_t +Cartridge::stateSize() +{ + return VirtualComponent::stateSize() + + packetStateSize() + + ramCapacity; +} + +void +Cartridge::didLoadFromBuffer(uint8_t **buffer) +{ + setRamCapacity(ramCapacity); + + loadPacketsFromBuffer(buffer); + readBlock(buffer, externalRam, ramCapacity); +} + +void +Cartridge::didSaveToBuffer(uint8_t **buffer) +{ + savePacketsToBuffer(buffer); + writeBlock(buffer, externalRam, ramCapacity); +} + +void +Cartridge::dump() +{ + msg("\n"); + msg("Cartridge\n"); + msg("---------\n"); + + msg(" Cartridge type: %d\n", getCartridgeType()); + msg(" Game line in CRT file: %d\n", gameLineInCrtFile); + msg("Exrom line in CRT file: %d\n", exromLineInCrtFile); + msg(" Number of Rom packets: %d\n", numPackets); + + for (unsigned i = 0; i < numPackets; i++) { + msg(" Chip %3d: %d KB starting at $%04X\n", + i, packet[i]->size / 1024, packet[i]->loadAddress); + } + msg("\n"); +} + +uint8_t +Cartridge::peek(uint16_t addr) +{ + assert(isROMLaddr(addr) || isROMHaddr(addr)); + + uint16_t relAddr = addr & 0x1FFF; + + // Question: Is it correct to return a value from RAM if no ROM is mapped? + if (isROMLaddr(addr)) { + return (relAddr < mappedBytesL) ? peekRomL(relAddr) : c64->mem.ram[addr]; + } else { + return (relAddr < mappedBytesH) ? peekRomH(relAddr) : c64->mem.ram[addr]; + } +} + +uint8_t +Cartridge::peekRomL(uint16_t addr) +{ + assert(addr <= 0x1FFF); + assert(chipL >= 0 && chipL < numPackets); + + return packet[chipL]->peek(addr + offsetL); +} + +uint8_t +Cartridge::peekRomH(uint16_t addr) +{ + assert(addr <= 0x1FFF); + assert(chipH >= 0 && chipH < numPackets); + + return packet[chipH]->peek(addr + offsetH); +} + +void +Cartridge::poke(uint16_t addr, uint8_t value) +{ + assert(isROMLaddr(addr) || isROMHaddr(addr)); + + uint16_t relAddr = addr & 0x1FFF; + + if (isROMLaddr(addr) && relAddr < mappedBytesL) { + pokeRomL(relAddr, value); + } + if (isROMHaddr(addr) && relAddr < mappedBytesH) { + pokeRomH(relAddr, value); + } + + // Write to RAM if we don't run in Ultimax mode + if (!c64->getUltimax()) { + c64->mem.ram[addr] = value; + } +} + +uint32_t +Cartridge::getRamCapacity() +{ + if (ramCapacity == 0) { + assert(externalRam == NULL); + } else { + assert(externalRam != NULL); + } + return ramCapacity; +} + +void +Cartridge::setRamCapacity(uint32_t size) +{ + // Free + if (ramCapacity != 0 || externalRam != NULL) { + assert(ramCapacity > 0 && externalRam != NULL); + free(externalRam); + externalRam = NULL; + ramCapacity = 0; + } + + // Allocate + if (size > 0) { + externalRam = (uint8_t *)malloc((size_t)size); + ramCapacity = size; + memset(externalRam, 0xFF, size); + } +} + +void +Cartridge::loadChip(unsigned nr, CRTFile *c) +{ + assert(nr < MAX_PACKETS); + assert(c != NULL); + + uint16_t size = c->chipSize(nr); + uint16_t start = c->chipAddr(nr); + uint16_t type = c->chipType(nr); + + // Perform some consistency checks + if (start < 0x8000) { + warn("Ignoring chip %d: Start address too low (%04X)\n", nr, start); + return; + } + if (0x10000 - start < size) { + warn("Ignoring chip %d: Invalid size (start: %04X size: %04X)/n", nr, start, size); + return; + } + + // Delete old chip packet if present + if (packet[nr]) { + delete packet[nr]; + } + + // Create new chip packet + switch (type) { + + case 0: // ROM + packet[nr] = new CartridgeRom(size, start, c->chipData(nr)); + break; + + case 1: // RAM + warn("Ignoring chip %d, because it has type RAM.\n", nr); + return; + + case 2: // Flash ROM + warn("Chip %d is a Flash Rom. Creating a Rom instead.\n", nr); + packet[nr] = new CartridgeRom(size, start, c->chipData(nr)); + break; + + default: + warn("Ignoring chip %d, because it has unknown type %d.\n", nr, type); + return; + } + + numPackets++; +} + +void +Cartridge::bankInROML(unsigned nr, uint16_t size, uint16_t offset) +{ + chipL = nr; + mappedBytesL = size; + offsetL = offset; +} + +void +Cartridge::bankInROMH(unsigned nr, uint16_t size, uint16_t offset) +{ + chipH = nr; + mappedBytesH = size; + offsetH = offset; +} + +void +Cartridge::bankIn(unsigned nr) +{ + assert(nr < MAX_PACKETS); + + if (packet[nr] == NULL) + return; + + assert(packet[nr]->size <= 0x4000); + + if (packet[nr]->mapsToLH()) { + + bankInROML(nr, 0x2000, 0); // chip covers ROML and (part of) ROMH + bankInROMH(nr, packet[nr]->size - 0x2000, 0x2000); + debug(2, "Banked in chip %d in ROML and ROMH\n", nr); + + } else if (packet[nr]->mapsToL()) { + + bankInROML(nr, packet[nr]->size, 0); // chip covers (part of) ROML + debug(2, "Banked in chip %d in ROML\n", nr); + + } else if (packet[nr]->mapsToH()) { + + bankInROMH(nr, packet[nr]->size, 0); // chip covers (part of) ROMH + debug(2, "Banked in chip %d to ROMH\n", nr); + + } else { + + warn("Cannot map chip %d. Invalid start address.\n", nr); + } +} + +void +Cartridge::bankOut(unsigned nr) +{ + assert(nr < MAX_PACKETS); + + if (packet[nr]->mapsToL()) { + + chipL = -1; + mappedBytesL = 0; + offsetL = 0; + + } else if (packet[nr]->mapsToH()) { + + chipH = -1; + mappedBytesH = 0; + offsetH = 0; + } +} + +void +Cartridge::setSwitch(int8_t pos) +{ + switchPos = pos; + c64->putMessage(MSG_CART_SWITCH); +} + +void +Cartridge::resetWithoutDeletingRam() +{ + uint8_t ram[0x10000]; + + debug(1, "Resetting virtual C64 (preserving RAM)\n"); + + memcpy(ram, c64->mem.ram, 0x10000); + c64->reset(); + memcpy(c64->mem.ram, ram, 0x10000); +} diff --git a/C64/Cartridges/Cartridge.h b/C64/Cartridges/Cartridge.h new file mode 100755 index 00000000..db8546d5 --- /dev/null +++ b/C64/Cartridges/Cartridge.h @@ -0,0 +1,488 @@ +/*! + * @header Cartridge.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CARTRIDGE_INC +#define _CARTRIDGE_INC + +#include "VirtualComponent.h" +#include "Cartridge_types.h" +#include "CRTFile.h" +#include "CartridgeRom.h" + +class ExpansionPort; + +/*! + * @brief Cartridge that can be plugged into the C64's expansion port + */ +class Cartridge : public VirtualComponent { + +public: + + //! @brief Maximum number of chip packets on a single cartridge. + static const unsigned MAX_PACKETS = 128; + +private: + + // + // Cartridge configuration + // + + /*! @brief Initial value of the game line + * @details The value is read from the CRT filt and the game line is set + * to it when the cartridge is plugged into the expansion port. + */ + bool gameLineInCrtFile = 1; + + /*! @brief Initial value of the exrom line + * @details The value is read from the CRT filt and the exrom line is set + * to it when the cartridge is plugged into the expansion port. + */ + bool exromLineInCrtFile = 1; + +protected: + + // + // ROM packets + // + + //! @brief Number of ROM packets + uint8_t numPackets = 0; + + //! @brief ROM chips contained in this cartridge + CartridgeRom *packet[MAX_PACKETS]; + + //! @brief The ROM packet that is currently mapped to ROMx + uint8_t chipL = 0; + uint8_t chipH = 0; + + //! @brief Number of bytes that are mapped to ROMx + /*! @details For most cartridges, this value is equals packet[romX]->size + * which means that the ROM is completely mapped. + * A value of 0 indicates that no ROM is currently mapped. + */ + uint16_t mappedBytesL = 0; + uint16_t mappedBytesH = 0; + + //! @brief Offset into the ROM chip's data array + /*! @details The first ROMx byte has index offsetx + * The last ROMx byte has index offsetx + mappedBytesx - 1 + */ + uint16_t offsetL = 0; + uint16_t offsetH = 0; + +private: + + // + // On-board RAM + // + + /*! @brief Additional RAM + * @details Some cartridges such as ActionReplay contain additional RAM. + * By default, this variable is NULL. + */ + uint8_t *externalRam = NULL; + + /*! @brief Capacity of the additional RAM in bytes + * @note This value is 0 if and only if externaRam is NULL. + */ + uint32_t ramCapacity = 0; + + //! @brief Indicates if the RAM is kept alive during a reset. + bool persistentRam = false; + + + // + // Hardware switches + // + + /*! @brief Current position of the cartridge switch (if any) + * @details Only a cery few cartridges such as ISEPIC and EXPERT have + * a switch. + */ + int8_t switchPos = 0; + + //! @brief Status of the cartridge LED (true = on) + bool led = false; + + +protected: + + // + // Temporary storage (TODO: Move to custom cartridge classes) + // + + /*! @brief Temporary storage for cycle information + * @details Some custom cartridges need to remember when certain event + * took place. When such an event happens, they preserve the + * cycle in this variable. Only a few cartridges make use of this + * variable. + */ + // uint64_t cycle = 0; + + /*! @brief Temporary storage + * @details Some custom cartridges contain additonal registers or jumpers. + * They preserve these values in these general-purpose variables. + * Only a few cartridges make use of this variable. + */ + // uint8_t val[16]; + +public: + + // + //! @functiongroup Class methods + // + + /*! @brief Checks the cartridge type. + * @details Returns true iff the cartridge type is supported. + */ + static bool isSupportedType(CartridgeType type); + + /*! @brief Returns true if addr is located in the ROML address space. + * @details If visible, ROML is always mapped to 0x8000 - 0x9FFF. + */ + static bool isROMLaddr (uint16_t addr) { return addr >= 0x8000 && addr <= 0x9FFF; } + + /*! @brief Returns true if addr is located in the ROMH address space. + * @details ROMH can appear in 0xA000 - 0xBFFF or 0xE000 - 0xFFFF. + */ + static bool isROMHaddr (uint16_t addr) { + return (addr >= 0xA000 && addr <= 0xBFFF) || (addr >= 0xE000 && addr <= 0xFFFF); } + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Convenience constructor + Cartridge(C64 *c64, const char *description = "Cartridge"); + + //! @brief Destructor + ~Cartridge(); + + //! @brief Deletes all chip packages + void dealloc(); + + //! @brief Returns the cartridge type + virtual CartridgeType getCartridgeType() { return CRT_NORMAL; } + + //! @brief Factory method + /*! @details Creates a cartridge with the specified type. Make sure to pass + * containers of the supported cartridge type, only. + * @seealso isSupportedType + */ + static Cartridge *makeWithType(C64 *c64, CartridgeType type); + + //! @brief Factory method + /*! @details Creates a cartridge from a CRT file. Make sure to pass + * containers of the supported cartridge type, only. + * @seealso isSupportedType + */ + static Cartridge *makeWithCRTFile(C64 *c64, CRTFile *file); + + //! @brief State size function for chip packet data + virtual size_t packetStateSize(); + + //! @brief Loads all chip packets from a buffer + virtual void loadPacketsFromBuffer(uint8_t **buffer); + + //! @brief Saves all chip packets to a buffer + virtual void savePacketsToBuffer(uint8_t **buffer); + + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void ping() { }; + size_t stateSize(); + void willLoadFromBuffer(uint8_t **buffer) { dealloc(); } + void didLoadFromBuffer(uint8_t **buffer); + void didSaveToBuffer(uint8_t **buffer); + void dump(); + + + // + //! @functiongroup Managing the cartridge configuration + // + + //! @brief Returns the initial state of the game line. + bool getGameLineInCrtFile() { return gameLineInCrtFile; } + + //! @brief Returns the initial state of the exrom line. + bool getExromLineInCrtFile() { return exromLineInCrtFile; } + + /*! @brief Resets the Game and Exrom line. + * @details The default implementation resets the values to those found + * in the CRT file. Some custom cartridges need other start + * configurations and overwrite this function. + */ + virtual void resetCartConfig(); + + + // + //! @functiongroup Handling ROM packets + // + + //! @brief Reads in a chip packet from a CRT file + virtual void loadChip(unsigned nr, CRTFile *c); + + //! @brief Banks in a rom chip into the ROML space + void bankInROML(unsigned nr, uint16_t size, uint16_t offset); + + //! @brief Banks in a rom chip into the ROMH space + void bankInROMH(unsigned nr, uint16_t size, uint16_t offset); + + //! @brief Banks in a rom chip + /*! @details This function calls bankInROML or bankInROMH with the default + * parameters for this chip as provided in the CRT file. + */ + void bankIn(unsigned nr); + + //! @brief Banks out a chip + /*! @details RAM contents will show in memory + */ + void bankOut(unsigned nr); + + + // + //! @functiongroup Peeking and poking + // + + /*! @brief Peek fallthrough + * @param addr must be a value in + * ROML range (0x8000 - 0x9FFF) or + * ROMH range (0xA000 - 0xBFFF, 0xE000 - 0xFFFF). + */ + virtual uint8_t peek(uint16_t addr); + + /*! @brief Peek fallthrough for the ROML space + * @param addr must be a value between 0x0000 - 0x1FFF. + */ + virtual uint8_t peekRomL(uint16_t addr); + + /*! @brief Peek fallthrough for the ROMH space + * @details addr must be a value between 0x0000 - 0x1FFF. + */ + virtual uint8_t peekRomH(uint16_t addr); + + /*! @brief Poke fallthrough + * @param addr must be a value in + * ROML range (0x8000 - 0x9FFF) or + * ROMH range (0xA000 - 0xBFFF, 0xE000 - 0xFFFF). + */ + virtual void poke(uint16_t addr, uint8_t value); + + /*! @brief Poke fallthrough for the ROML space + * @param addr must be a value between 0x0000 - 0x1FFF. + */ + virtual void pokeRomL(uint16_t addr, uint8_t value) { return; } + + /*! @brief Poke fallthrough for the ROMH space + * @details addr must be a value between 0x0000 - 0x1FFF. + */ + virtual void pokeRomH(uint16_t addr, uint8_t value) { return; } + + //! @brief Same as peek, but without side effects. + virtual uint8_t spypeek(uint16_t addr) { return peek(addr); } + + //! @brief Same as peekRomL, but without side effects + uint8_t spypeekRomL(uint16_t addr) { return peekRomL(addr); } + + //! @brief Same as peekRomH, but without side effects + uint8_t spypeekRomH(uint16_t addr) { return peekRomH(addr); } + + //! @brief Peek fallthrough for I/O space 1 + virtual uint8_t peekIO1(uint16_t addr) { return 0; } + + //! @brief Same as peekIO1, but without side effects. + virtual uint8_t spypeekIO1(uint16_t addr) { return peekIO1(addr); } + + //! @brief Peek fallthrough for I/O space 2 + virtual uint8_t peekIO2(uint16_t addr) { return 0; } + + //! @brief Same as peekIO2, but without side effects. + virtual uint8_t spypeekIO2(uint16_t addr) { return peekIO2(addr); } + + //! @brief Poke fallthrough for I/O space 1 + virtual void pokeIO1(uint16_t addr, uint8_t value) { } + + //! @brief Poke fallthrough for I/O space 2 + virtual void pokeIO2(uint16_t addr, uint8_t value) { } + + + // + //! @functiongroup Managing on-board RAM + // + + //! @brief Returns the RAM size in bytes. + uint32_t getRamCapacity(); + + //! @brief Assigns external RAM to this cartridge. + /*! @details This functions frees any previously assigned RAM and allocates + * memory of the specified size. The size is stored in variable + * ramCapacity. + */ + void setRamCapacity(uint32_t size); + + //! @brief Returns true if RAM data is preserved during a reset. + bool getPersistentRam() { return persistentRam; } + + //! @brief Enables or disables persistent RAM. + void setPersistentRam(bool value) { persistentRam = value; } + + //! @brief Reads a byte from the on-board RAM. + uint8_t peekRAM(uint16_t addr) { + assert(addr < ramCapacity); return externalRam[addr]; } + + //! @brief Writes a byte into the on-board RAM. + void pokeRAM(uint16_t addr, uint8_t value) { + assert(addr < ramCapacity); externalRam[addr] = value; } + + //! @brief Erase the on-board RAM. + void eraseRAM(uint8_t value) { + assert(externalRam != NULL); memset(externalRam, value, ramCapacity); } + + + // + // Operating buttons + // + + //! @brief Returns the number of available cartridge buttons + virtual unsigned numButtons() { return 0; } + + /*! @brief Returns a textual description for a button. + * @return NULL, if there is no such button. + */ + virtual const char *getButtonTitle(unsigned nr) { return NULL; } + + /*! @brief Presses a button + * @note Make sure to call releaseButton() afterwards. + */ + virtual void pressButton(unsigned nr) { } + + /*! @brief Releases a button + * @note Make sure to call pressButton() before. + */ + virtual void releaseButton(unsigned nr) { } + + + // + // Operating switches + // + + //! @brief Returns true if the cartridge has a switch + virtual bool hasSwitch() { return false; } + + //! @brief Returns the current position of the switch + virtual int8_t getSwitch() { return switchPos; } + + //! @brief Convenience wrappers around getSwitch() + bool switchIsNeutral() { return getSwitch() == 0; } + bool switchIsLeft() { return getSwitch() < 0; } + bool switchIsRight() { return getSwitch() > 0; } + + /*! @brief Returns a textual description for a switch position. + * @return NULL, if the switch cannot be positioned this way. + */ + virtual const char *getSwitchDescription(int8_t pos) { return NULL; } + + //! @brief Convenience wrappers around getSwitchDescription() + bool validSwitchPosition(int8_t pos) { return getSwitchDescription(pos) != NULL; } + + //! @brief Puts the switch in the provided position + virtual void setSwitch(int8_t pos); + + + // + // Operating LEDs + // + + //! @brief Returns true if the cartridge has a LED. + virtual bool hasLED() { return false; } + + //! @brief Returns true if the LED is switched on. + virtual bool getLED() { return led; } + + //! @brief Switches the LED on or off. + virtual void setLED(bool value) { led = value; } + + + // + // Delegation methods + // + + //! @brief Execution thread callback + /*! @details This function is invoked by the expansion port. Only a few + * cartridges such as EpyxFastLoader will do some action here. + */ + virtual void execute() { }; + + //! @brief Modifies the memory source lookup tables if required + virtual void updatePeekPokeLookupTables() { }; + + //! @brief Called when the C64 CPU is about to trigger an NMI + virtual void nmiWillTrigger() { } + + //! @brief Called after the C64 CPU has processed the NMI instruction + virtual void nmiDidTrigger() { } + + + // + // Helpers + // + + void resetWithoutDeletingRam(); +}; + + +/*! + * @brief Cartridge with an auxililary control register + * @details Many non-standard cartridges carry an addition register on board. + */ +class CartridgeWithRegister : public Cartridge { + +protected: + uint8_t control; + +public: + using Cartridge::Cartridge; + + void reset() { + Cartridge::reset(); + control = 0; + } + size_t stateSize() { + return Cartridge::stateSize() + 1; + } + void didLoadFromBuffer(uint8_t **buffer) + { + Cartridge::didLoadFromBuffer(buffer); + control = read8(buffer); + } + void didSaveToBuffer(uint8_t **buffer) + { + Cartridge::didSaveToBuffer(buffer); + write8(buffer, control); + } +}; + +#endif diff --git a/C64/Cartridges/CartridgeRom.cpp b/C64/Cartridges/CartridgeRom.cpp new file mode 100755 index 00000000..547ae383 --- /dev/null +++ b/C64/Cartridges/CartridgeRom.cpp @@ -0,0 +1,107 @@ +/*! + * @file CartridgeRom.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "CartridgeRom.h" + +CartridgeRom::CartridgeRom() +{ + setDescription("CartridgeRom"); + debug(3, " Creating cartridge Rom at address %p...\n", this); + + // Register snapshot items + SnapshotItem items[] = { + + // Internal state + { &size, sizeof(size), KEEP_ON_RESET }, + { &loadAddress, sizeof(loadAddress), KEEP_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +/* +CartridgeRom::CartridgeRom(uint8_t **buffer) : CartridgeRom() +{ + assert(buffer != NULL); + loadFromBuffer(buffer); +} +*/ + +CartridgeRom::CartridgeRom(uint16_t size, uint16_t loadAddress, const uint8_t *buffer) : CartridgeRom() +{ + this->size = size; + this->loadAddress = loadAddress; + rom = new uint8_t[size]; + if (buffer) { + memcpy(rom, buffer, size); + } +} + +CartridgeRom::~CartridgeRom() +{ + assert(rom != NULL); + delete[] rom; +} + +size_t +CartridgeRom::stateSize() +{ + return VirtualComponent::stateSize() + size; +} + +void +CartridgeRom::didLoadFromBuffer(uint8_t **buffer) +{ + if (rom) delete[] rom; + rom = new uint8_t[size]; + + readBlock(buffer, rom, size); +} + +void +CartridgeRom::didSaveToBuffer(uint8_t **buffer) +{ + writeBlock(buffer, rom, size); +} + +bool +CartridgeRom::mapsToL() { + assert(rom != NULL); + return loadAddress == 0x8000 && size <= 0x2000; +} + +bool +CartridgeRom::mapsToLH() { + assert(rom != NULL); + return loadAddress == 0x8000 && size > 0x2000; +} + +bool +CartridgeRom::mapsToH() { + assert(rom != NULL); + return loadAddress == 0xA000 || loadAddress == 0xE000; +} + +uint8_t +CartridgeRom::peek(uint16_t addr) +{ + assert(addr < size); + return rom[addr]; +} diff --git a/C64/Cartridges/CartridgeRom.h b/C64/Cartridges/CartridgeRom.h new file mode 100755 index 00000000..3fbb2815 --- /dev/null +++ b/C64/Cartridges/CartridgeRom.h @@ -0,0 +1,85 @@ +/*! + * @header CartridgeRom.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CARTRIDGEROM_INC +#define _CARTRIDGEROM_INC + +#include "VirtualComponent.h" + +/*! @brief This class implements a cartridge Rom chip + */ +class CartridgeRom : public VirtualComponent { + + protected: + + //! @brief Rom data + uint8_t *rom = NULL; + + public: + + //! @brief Size in bytes + uint16_t size = 0; + + /*! @brief Load address + * @details This value is taken from the .CRT file. Possible values are + * $8000 for chips mapping into the ROML area, $A000 for chips + * mapping into the ROMH area in 16KB game mode, and $E000 for + * chips mapping into the ROMH area in ultimax mode. + */ + uint16_t loadAddress = 0; + + public: + + //! @brief Constructor + CartridgeRom(); + // CartridgeRom(uint8_t **buffer); + CartridgeRom(uint16_t _size, uint16_t _loadAddress, const uint8_t *buffer = NULL); + + //! @brief Destructor + ~CartridgeRom(); + + //! @brief Methods from VirtualComponent + size_t stateSize(); + void didLoadFromBuffer(uint8_t **buffer); + void didSaveToBuffer(uint8_t **buffer); + + //! @brief Returns true if this Rom chip maps to ROML, only. + bool mapsToL(); + + //! @brief Returns true if this Rom chip maps to ROML and ROMH. + bool mapsToLH(); + + //! @brief Returns true if this Rom chip maps to ROMH, only. + bool mapsToH(); + + //! @brief Reads a ROM cell + uint8_t peek(uint16_t addr); + + //! @brief Reads a ROM cell without side effects + uint8_t spypeek(uint16_t addr) { return peek(addr); } + + //! @brief Writes a ROM cell + void poke(uint16_t addr, uint8_t value) { } + +}; + +#endif + + diff --git a/C64/Cartridges/Cartridge_types.h b/C64/Cartridges/Cartridge_types.h new file mode 100755 index 00000000..b5b706be --- /dev/null +++ b/C64/Cartridges/Cartridge_types.h @@ -0,0 +1,94 @@ +/*! + * @header Cartridge_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CARTRIDGE_TYPES_H +#define CARTRIDGE_TYPES_H + +typedef enum { + CRT_NORMAL = 0, + CRT_ACTION_REPLAY = 1, + CRT_KCS_POWER = 2, + CRT_FINAL_III = 3, + CRT_SIMONS_BASIC = 4, + CRT_OCEAN = 5, + CRT_EXPERT = 6, + CRT_FUNPLAY = 7, + CRT_SUPER_GAMES = 8, + CRT_ATOMIC_POWER = 9, + CRT_EPYX_FASTLOAD = 10, + CRT_WESTERMANN = 11, + CRT_REX = 12, + CRT_FINAL_I = 13, + CRT_MAGIC_FORMEL = 14, + CRT_GAME_SYSTEM_SYSTEM_3 = 15, + CRT_WARPSPEED = 16, + CRT_DINAMIC = 17, + CRT_ZAXXON = 18, + CRT_MAGIC_DESK = 19, + CRT_SUPER_SNAPSHOT_V5 = 20, + CRT_COMAL80 = 21, + CRT_STRUCTURE_BASIC = 22, + CRT_ROSS = 23, + CRT_DELA_EP64 = 24, + CRT_DELA_EP7x8 = 25, + CRT_DELA_EP256 = 26, + CRT_REX_EP256 = 27, + CRT_MIKRO_ASS = 28, + CRT_FINAL_PLUS = 29, + CRT_ACTION_REPLAY4 = 30, + CRT_STARDOS = 31, + CRT_EASYFLASH = 32, + CRT_EASYFLASH_XBANK = 33, + CRT_CAPTURE = 34, + CRT_ACTION_REPLAY3 = 35, + CRT_RETRO_REPLAY = 36, + CRT_MMC64 = 37, + CRT_MMC_REPLAY = 38, + CRT_IDE64 = 39, + CRT_SUPER_SNAPSHOT = 40, + CRT_IEEE488 = 41, + CRT_GAME_KILLER = 42, + CRT_P64 = 43, + CRT_EXOS = 44, + CRT_FREEZE_FRAME = 45, + CRT_FREEZE_MACHINE = 46, + CRT_SNAPSHOT64 = 47, + CRT_SUPER_EXPLODE_V5 = 48, + CRT_MAGIC_VOICE = 49, + CRT_ACTION_REPLAY2 = 50, + CRT_MACH5 = 51, + CRT_DIASHOW_MAKER = 52, + CRT_PAGEFOX = 53, + CRT_KINGSOFT = 54, + CRT_SILVERROCK_128 = 55, + CRT_FORMEL64 = 56, + CRT_RGCD = 57, + CRT_RRNETMK3 = 58, + CRT_EASYCALC = 59, + CRT_GMOD2 = 60, + + CRT_ISEPIC = 253, + CRT_GEO_RAM = 254, + CRT_NONE = 255 + +} CartridgeType; + +#endif diff --git a/C64/Cartridges/CustomCartridges/ActionReplay.cpp b/C64/Cartridges/CustomCartridges/ActionReplay.cpp new file mode 100755 index 00000000..7f45fcb7 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/ActionReplay.cpp @@ -0,0 +1,358 @@ +/*! + * @file ActionReplay.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +// +// Action Replay (hardware revision 3) +// + +//! @brief An older generation Action Replay cartridge +uint8_t +ActionReplay3::peek(uint16_t addr) +{ + if (addr >= 0x8000 && addr <= 0x9FFF) { + return packet[bank()]->peek(addr - 0x8000); + } + + if (addr >= 0xE000 && addr <= 0xFFFF) { + return packet[bank()]->peek(addr - 0xE000); + } + + if (addr >= 0xA000 && addr <= 0xBFFF) { + return packet[bank()]->peek(addr - 0xA000); + } + + assert(false); + return 0; +} + +uint8_t +ActionReplay3::peekIO1(uint16_t addr) +{ + return 0; +} + +uint8_t +ActionReplay3::peekIO2(uint16_t addr) +{ + uint16_t offset = addr - 0xDF00; + return disabled() ? 0 : packet[bank()]->peek(0x1F00 + offset); +} + +void +ActionReplay3::pokeIO1(uint16_t addr, uint8_t value) +{ + if (!disabled()) + setControlReg(value); +} + +const char * +ActionReplay3::getButtonTitle(unsigned nr) +{ + return (nr == 1) ? "Freeze" : (nr == 2) ? "Reset" : NULL; +} + +void +ActionReplay3::pressButton(unsigned nr) +{ + assert(nr <= numButtons()); + debug("Pressing %s button.\n", getButtonTitle(nr)); + + c64->suspend(); + + switch (nr) { + + case 1: // Freeze + + c64->cpu.pullDownNmiLine(CPU::INTSRC_EXPANSION); + c64->cpu.pullDownIrqLine(CPU::INTSRC_EXPANSION); + + // By setting the control register to 0, exrom/game is set to 1/0 + // which activates ultimax mode. This mode is reset later, in the + // ActionReplay's interrupt handler. + setControlReg(0); + break; + + case 2: // Reset + + resetWithoutDeletingRam(); + break; + } + + c64->resume(); +} + +void +ActionReplay3::releaseButton(unsigned nr) +{ + assert(nr <= numButtons()); + debug("Releasing %s button.\n", getButtonTitle(nr)); + + c64->suspend(); + + switch (nr) { + + case 1: // Freeze + + c64->cpu.releaseNmiLine(CPU::INTSRC_EXPANSION); + c64->cpu.releaseIrqLine(CPU::INTSRC_EXPANSION); + break; + } + + c64->resume(); +} + +void +ActionReplay3::setControlReg(uint8_t value) +{ + control = value; + c64->expansionport.setGameAndExrom(game(), exrom()); +} + + +// +// Action Replay (hardware revision 4 and above) +// + +//! @brief A newer generation Action Replay cartridge +ActionReplay::ActionReplay(C64 *c64) : CartridgeWithRegister(c64, "AR") +{ + debug("ActionReplay constructor\n"); + + // Allocate 8KB on-board memory + setRamCapacity(0x2000); +} + +void +ActionReplay::reset() +{ + Cartridge::reset(); + setControlReg(0); +} + +void +ActionReplay::resetCartConfig() +{ + debug("Starting ActionReplay cartridge in 8K game mode.\n"); + c64->expansionport.setCartridgeMode(CRT_8K); +} + +uint8_t +ActionReplay::peek(uint16_t addr) +{ + if (ramIsEnabled(addr)) { + return peekRAM(addr & 0x1FFF); + } + return Cartridge::peek(addr); +} + +void +ActionReplay::poke(uint16_t addr, uint8_t value) +{ + if (ramIsEnabled(addr)) { + pokeRAM(addr & 0x1FFF, value); + } +} + +uint8_t +ActionReplay::peekIO1(uint16_t addr) +{ + return control; +} + +uint8_t +ActionReplay::peekIO2(uint16_t addr) +{ + assert(addr >= 0xDF00 && addr <= 0xDFFF); + uint16_t offset = addr & 0xFF; + + // I/O space 2 mirrors $1F00 to $1FFF from the selected ROM bank or RAM. + if (ramIsEnabled(addr)) { + return peekRAM(0x1F00 + offset); + } else { + return packet[chipL]->peek(0x1F00 + offset); + } +} + +void +ActionReplay::pokeIO1(uint16_t addr, uint8_t value) +{ + if (!disabled()) + setControlReg(value); +} + +void +ActionReplay::pokeIO2(uint16_t addr, uint8_t value) +{ + assert(addr >= 0xDF00 && addr <= 0xDFFF); + uint16_t offset = addr & 0xFF; + + if (ramIsEnabled(addr)) { + pokeRAM(0x1F00 + offset, value); + } +} + +const char * +ActionReplay::getButtonTitle(unsigned nr) +{ + return (nr == 1) ? "Freeze" : (nr == 2) ? "Reset" : NULL; +} + +void +ActionReplay::pressButton(unsigned nr) +{ + assert(nr <= numButtons()); + debug("Pressing %s button.\n", getButtonTitle(nr)); + + c64->suspend(); + + switch (nr) { + + case 1: // Freeze + + // Turn Ultimax mode on + setControlReg(0x23); + + // Pressing the freeze bottom pulls down both the NMI and the IRQ line + c64->cpu.pullDownNmiLine(CPU::INTSRC_EXPANSION); + c64->cpu.pullDownIrqLine(CPU::INTSRC_EXPANSION); + break; + + case 2: // Reset + + resetWithoutDeletingRam(); + break; + } + + c64->resume(); +} + +void +ActionReplay::releaseButton(unsigned nr) +{ + assert(nr <= numButtons()); + debug("Releasing %s button.\n", getButtonTitle(nr)); + + c64->suspend(); + + switch (nr) { + + case 1: // Freeze + + c64->cpu.releaseNmiLine(CPU::INTSRC_EXPANSION); + c64->cpu.releaseIrqLine(CPU::INTSRC_EXPANSION); + break; + } + + c64->resume(); +} + +void +ActionReplay::setControlReg(uint8_t value) +{ + control = value; + + debug(2, "PC: %04X setControlReg(%02X)\n", c64->cpu.getPC(), value); + + assert((value & 0x80) == 0); + /* "7 extra ROM bank selector (A15) (unused) + * 6 1 = resets FREEZE-mode (turns back to normal mode) + * 5 1 = enable RAM at ROML ($8000-$9FFF) & + * I/O2 ($DF00-$DFFF = $9F00-$9FFF) + * 4 ROM bank selector high (A14) + * 3 ROM bank selector low (A13) + * 2 1 = disable cartridge (turn off $DE00) + * 1 1 = /EXROM high + * 0 1 = /GAME low" [VICE] + */ + + c64->expansionport.setGameAndExrom(game(), exrom()); + + bankInROML(bank(), 0x2000, 0); + bankInROMH(bank(), 0x2000, 0); + + if (disabled()) { + debug(2, "Action Replay cartridge disabled.\n"); + } + + if (resetFreezeMode() || disabled()) { + c64->cpu.releaseNmiLine(CPU::INTSRC_EXPANSION); + c64->cpu.releaseIrqLine(CPU::INTSRC_EXPANSION); + } +} + +bool +ActionReplay::ramIsEnabled(uint16_t addr) +{ + if (control & 0x20) { + + if (addr >= 0xDF00 && addr <= 0xDFFF) { // RAM mirrored in IO2 + return true; + } + + return addr >= 0x8000 && addr <= 0x9FFF; // RAM mapped to ROML + } + + return false; +} + + +// +// Atomic Power +// + +AtomicPower::AtomicPower(C64 *c64) : ActionReplay(c64) { + + debug("AtomicPower constructor\n"); + setDescription("AtomicPower"); +}; + +bool +AtomicPower::game() +{ + return specialMapping() ? 0 : ActionReplay::game(); +} + +bool +AtomicPower::exrom() +{ + return specialMapping() ? 0 : ActionReplay::exrom(); +} + +bool +AtomicPower::ramIsEnabled(uint16_t addr) +{ + if (control & 0x20) { + + if (addr >= 0xDF00 && addr <= 0xDFFF) { // RAM mirrored in IO2 + return true; + } + if (specialMapping()) { + return addr >= 0xA000 && addr <= 0xBFFF; // RAM mapped to ROMH + } else { + return addr >= 0x8000 && addr <= 0x9FFF; // RAM mapped to ROML + } + } + + return false; +} + diff --git a/C64/Cartridges/CustomCartridges/ActionReplay.h b/C64/Cartridges/CustomCartridges/ActionReplay.h new file mode 100755 index 00000000..70cf33ba --- /dev/null +++ b/C64/Cartridges/CustomCartridges/ActionReplay.h @@ -0,0 +1,155 @@ +/*! + * @header ActionReplay.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ACTIONREPLAY_INC +#define _ACTIONREPLAY_INC + +#include "Cartridge.h" + +// +// Action Replay (hardware version 3) +// + +class ActionReplay3 : public CartridgeWithRegister { + +public: + + ActionReplay3(C64 *c64) : CartridgeWithRegister(c64, "AR3") { }; + CartridgeType getCartridgeType() { return CRT_ACTION_REPLAY3; } + + // + //! @functiongroup Methods from Cartridge + // + + uint8_t peek(uint16_t addr); + uint8_t peekIO1(uint16_t addr); + uint8_t peekIO2(uint16_t addr); + + void pokeIO1(uint16_t addr, uint8_t value); + + unsigned numButtons() { return 2; } + const char *getButtonTitle(unsigned nr); + void pressButton(unsigned nr); + void releaseButton(unsigned nr); + + //! @brief Sets the cartridge's control register + /*! @details This function triggers all side effects that take place when + * the control register value changes. + */ + void setControlReg(uint8_t value); + + unsigned bank() { return control & 0x01; } + bool game() { return !!(control & 0x02); } + bool exrom() { return !(control & 0x08); } + bool disabled() { return !!(control & 0x04); } +}; + + +// +// Action Replay (hardware version 4 and above) +// + +class ActionReplay : public CartridgeWithRegister { + +public: + + ActionReplay(C64 *c64); + CartridgeType getCartridgeType() { return CRT_ACTION_REPLAY; } + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + size_t stateSize() { return Cartridge::stateSize() + 1; } + void didLoadFromBuffer(uint8_t **buffer) { + Cartridge::didLoadFromBuffer(buffer); control = read8(buffer); } + void didSaveToBuffer(uint8_t **buffer) { + Cartridge::didSaveToBuffer(buffer); write8(buffer, control); } + + // + //! @functiongroup Methods from Cartridge + // + + void resetCartConfig(); + + uint8_t peek(uint16_t addr); + uint8_t peekIO1(uint16_t addr); + uint8_t peekIO2(uint16_t addr); + + void poke(uint16_t addr, uint8_t value); + void pokeIO1(uint16_t addr, uint8_t value); + void pokeIO2(uint16_t addr, uint8_t value); + + unsigned numButtons() { return 2; } + const char *getButtonTitle(unsigned nr); + void pressButton(unsigned nr); + void releaseButton(unsigned nr); + + + //! @brief Sets the cartridge's control register + /*! @details This function triggers all side effects that take place when + * the control register value changes. + */ + void setControlReg(uint8_t value); + + virtual unsigned bank() { return (control >> 3) & 0x03; } + virtual bool game() { return (control & 0x01) == 0; } + virtual bool exrom() { return (control & 0x02) != 0; } + virtual bool disabled() { return (control & 0x04) != 0; } + virtual bool resetFreezeMode() { return (control & 0x40) != 0; } + + //! @brief Returns true if the cartridge RAM shows up at addr + virtual bool ramIsEnabled(uint16_t addr); +}; + + +// +// Atomic Power (a derivation of the Action Replay cartridge) +// + +class AtomicPower : public ActionReplay { + +public: + + AtomicPower(C64 *c64); + CartridgeType getCartridgeType() { return CRT_ATOMIC_POWER; } + + /*! @brief Indicates if special ROM / RAM config has to be used. + * @details In contrast to the Action Replay cartridge, Atomic Power + * has the ability to map the on-board RAM to the ROMH area + * at $A000 - $BFFF. To enable this special configuration, the + * control register has to be configured as follows: + * Bit 0b10000000 (Extra ROM) is 0. + * Bit 0b01000000 (Freeze clear) is 0. + * Bit 0b00100000 (RAM enable) is 1. + * Bit 0b00000100 (Disable) is 0. + * Bit 0b00000010 (Exrom) is 1. + * Bit 0b00000001 (Game) is 0. + */ + bool specialMapping() { return (control & 0b11100111) == 0b00100010; } + + bool game(); + bool exrom(); + bool ramIsEnabled(uint16_t addr); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/Comal80.cpp b/C64/Cartridges/CustomCartridges/Comal80.cpp new file mode 100755 index 00000000..b3bed3e3 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Comal80.cpp @@ -0,0 +1,55 @@ +/*! + * @file Comal80.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +Comal80::reset() +{ + CartridgeWithRegister::reset(); + c64->expansionport.setCartridgeMode(CRT_16K); + bankIn(0); +} + +void +Comal80::pokeIO1(uint16_t addr, uint8_t value) +{ + if (addr >= 0xDE00 && addr <= 0xDEFF) { + + control = value & 0xC7; + bankIn(value & 0x03); + + switch (value & 0xE0) { + + case 0xe0: + c64->expansionport.setCartridgeMode(CRT_OFF); + break; + + case 0x40: + c64->expansionport.setCartridgeMode(CRT_8K); + break; + + default: + c64->expansionport.setCartridgeMode(CRT_16K); + break; + } + } +} diff --git a/C64/Cartridges/CustomCartridges/Comal80.h b/C64/Cartridges/CustomCartridges/Comal80.h new file mode 100755 index 00000000..d7e8d51c --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Comal80.h @@ -0,0 +1,48 @@ +/*! + * @header Comal80.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _COMAL80_INC +#define _COMAL80_INC + +#include "Cartridge.h" + +class Comal80 : public CartridgeWithRegister { + + //! Control register + uint8_t control; + +public: + + Comal80(C64 *c64) : CartridgeWithRegister(c64, "Comal80") { }; + CartridgeType getCartridgeType() { return CRT_COMAL80; } + + // + //! @functiongroup Methods from Cartridge + // + + + void reset(); + uint8_t peekIO1(uint16_t addr) { return control; } + uint8_t peekIO2(uint16_t addr) { return 0; } + void pokeIO1(uint16_t addr, uint8_t value); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/CustomCartridges.h b/C64/Cartridges/CustomCartridges/CustomCartridges.h new file mode 100755 index 00000000..e160041b --- /dev/null +++ b/C64/Cartridges/CustomCartridges/CustomCartridges.h @@ -0,0 +1,50 @@ +/*! + * @header CustomCartridges.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CUSTOM_CARTRIDGES_INC +#define _CUSTOM_CARTRIDGES_INC + +#include "Cartridge.h" +#include "ActionReplay.h" +#include "Comal80.h" +#include "EasyFlash.h" +#include "Epyx.h" +#include "Expert.h" +#include "FinalIII.h" +#include "FreezeFrame.h" +#include "Funplay.h" +#include "GeoRam.h" +#include "Isepic.h" +#include "Kcs.h" +#include "Kingsoft.h" +#include "Mach5.h" +#include "MagicDesk.h" +#include "MikroAss.h" +#include "Ocean.h" +#include "Rex.h" +#include "SimonsBasic.h" +#include "StarDos.h" +#include "SuperGames.h" +#include "WarpSpeed.h" +#include "Westermann.h" +#include "Zaxxon.h" + +#endif diff --git a/C64/Cartridges/CustomCartridges/EasyFlash.cpp b/C64/Cartridges/CustomCartridges/EasyFlash.cpp new file mode 100755 index 00000000..aa36bb1e --- /dev/null +++ b/C64/Cartridges/CustomCartridges/EasyFlash.cpp @@ -0,0 +1,287 @@ +/*! + * @file EasyFlash.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +EasyFlash::EasyFlash(C64 *c64) : Cartridge(c64, "EasyFlash") +{ + flashRomL.setDescription("FlashRom_L"); + flashRomH.setDescription("FlashRom_H"); + + bank = 0; + + // Allocate 256 bytes on-board RAM + setRamCapacity(256); + + // Start in Ultimax mode + jumper = false; +} + +void +EasyFlash::reset() +{ + Cartridge::reset(); + + bank = 0; + eraseRAM(0xFF); + jumper = false; +} + +void +EasyFlash::dump() +{ + Cartridge::dump(); + + msg("EasyFlash\n"); + msg("---------\n\n"); + + msg("bank = %d\n", bank); + for (unsigned i = 0; i < 256; i++) { + msg("%02X ", peekRAM(i)); + if (i % 16 == 15) msg("\n"); + } + msg("\n"); + + flashRomL.dump(); + flashRomH.dump(); +} + +size_t +EasyFlash::stateSize() +{ + return Cartridge::stateSize() + + 1 + + 1 + + flashRomL.stateSize() + + flashRomH.stateSize(); +} + +void +EasyFlash::didLoadFromBuffer(uint8_t **buffer) +{ + Cartridge::didLoadFromBuffer(buffer); + bank = read8(buffer); + jumper = (bool)read8(buffer); + flashRomL.loadFromBuffer(buffer); + flashRomH.loadFromBuffer(buffer); +} + +void +EasyFlash::didSaveToBuffer(uint8_t **buffer) +{ + Cartridge::didSaveToBuffer(buffer); + write8(buffer, bank); + write8(buffer, (uint8_t)jumper); + flashRomL.saveToBuffer(buffer); + flashRomH.saveToBuffer(buffer); +} + +void +EasyFlash::resetCartConfig() +{ + c64->expansionport.setCartridgeMode(CRT_ULTIMAX); +} + +void +EasyFlash::loadChip(unsigned nr, CRTFile *c) +{ + static int bank; + + uint16_t chipSize = c->chipSize(nr); + uint16_t chipAddr = c->chipAddr(nr); + uint8_t *chipData = c->chipData(nr); + + if (nr == 0) { + bank = 0; + } + + if(chipSize != 0x2000) { + warn("Package %d has chip size %04X. Expected 0x2000.\n", nr, chipSize); + return; + } + + // Check for missing banks + if (bank % 2 == 0 && isROMHaddr(chipAddr)) { + debug(1, "Skipping Rom bank %dL ...\n", bank / 2); + bank++; + } + if (bank % 2 == 1 && isROMLaddr(chipAddr)) { + debug(1, "Skipping Rom bank %dH ...\n", bank / 2); + bank++; + } + + if (isROMLaddr(chipAddr)) { + + debug(1, "Loading Rom bank %dL ...\n", bank / 2); + flashRomL.loadBank(bank / 2, chipData); + bank++; + + } else if (isROMHaddr(chipAddr)) { + + debug(1, "Loading Rom bank %dH ...\n", bank / 2); + flashRomH.loadBank(bank / 2, chipData); + bank++; + + } else { + + warn("Package %d has an invalid load address (%04X).", nr, chipAddr); + return; + } +} + +uint8_t +EasyFlash::peek(uint16_t addr) +{ + if (isROMLaddr(addr)) { + return flashRomL.peek(bank, addr & 0x1FFF); + + } else if (isROMHaddr(addr)) { + return flashRomH.peek(bank, addr & 0x1FFF); + + } else { + assert(false); + return 0; + } +} + +/* +uint8_t +EasyFlash::spypeek(uint16_t addr) +{ + if (isROMLaddr(addr)) { + return flashRomL.spypeek(bank, addr & 0x1FFF); + + } else if (isROMHaddr(addr)) { + return flashRomH.spypeek(bank, addr & 0x1FFF); + + } else { + assert(false); + return 0; + } +} +*/ + +void +EasyFlash::poke(uint16_t addr, uint8_t value) +{ + if (isROMLaddr(addr)) { + flashRomL.poke(bank, addr & 0x1FFF, value); + + } else if (isROMHaddr(addr)) { + flashRomH.poke(bank, addr & 0x1FFF, value); + + } else { + assert(false); + } +} + +uint8_t +EasyFlash::peekIO1(uint16_t addr) +{ + // debug("WARNING: peekIO1\n"); + return 0; +} + +uint8_t +EasyFlash::peekIO2(uint16_t addr) +{ + return peekRAM(addr & 0xFF); +} + +void +EasyFlash::pokeIO1(uint16_t addr, uint8_t value) +{ + if (addr == 0xDE00) { // Bank register + + bank = value & 0x3F; + return; + } + + if (addr == 0xDE02) { // Mode register + + setLED((value & 0x80) != 0); + + uint8_t MXG = value & 0x07; + /* MXG + * 000 : GAME from jumper, EXROM high (i.e. Ultimax or Off) + * 001 : Reserved, don’t use this + * 010 : GAME from jumper, EXROM low (i.e. 16K or 8K) + * 011 : Reserved, don’t use this + * 100 : Cartridge ROM off (RAM at $DF00 still available) + * 101 : Ultimax (Low bank at $8000, high bank at $e000) + * 110 : 8k Cartridge (Low bank at $8000) + * 111 : 16k cartridge (Low bank at $8000, high bank at $a000) + */ + + bool exrom; + bool game; + + switch (MXG) { + + case 0b000: + case 0b001: + game = jumper; + exrom = 1; + break; + + case 0b010: + case 0b011: + game = jumper; + exrom = 0; + break; + + case 0b100: + game = 1; + exrom = 1; + break; + + case 0b101: + game = 0; + exrom = 1; + break; + + case 0b110: + game = 1; + exrom = 0; + break; + + case 0b111: + game = 0; + exrom = 0; + break; + + default: + assert(false); + return; + } + + c64->expansionport.setGameAndExrom(game, exrom); + } +} + +void +EasyFlash::pokeIO2(uint16_t addr, uint8_t value) +{ + pokeRAM(addr & 0xFF, value); +} + + + diff --git a/C64/Cartridges/CustomCartridges/EasyFlash.h b/C64/Cartridges/CustomCartridges/EasyFlash.h new file mode 100755 index 00000000..9efd3791 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/EasyFlash.h @@ -0,0 +1,77 @@ +/*! + * @header EasyFlash.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _EASYFLASH_INC +#define _EASYFLASH_INC + +#include "Cartridge.h" + +class EasyFlash : public Cartridge { + + //!@brief Flash Rom mapping to ROML ($8000 - $9FFF) + FlashRom flashRomL; + + //!@brief Flash Rom mapping to ROMH ($A000 - $B000 or $E000 - $FFFF) + FlashRom flashRomH; + + //!@brief Selected memory bank + uint8_t bank; + + //!@brief The jumper + bool jumper; + +public: + + // + //! @functiongroup Creating and destructing + // + + EasyFlash(C64 *c64); + CartridgeType getCartridgeType() { return CRT_EASYFLASH; } + + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void dump(); + size_t stateSize(); + void didLoadFromBuffer(uint8_t **buffer); + void didSaveToBuffer(uint8_t **buffer); + + + // + //! @functiongroup Methods from Cartridge + // + + void resetCartConfig(); + void loadChip(unsigned nr, CRTFile *c); + uint8_t peek(uint16_t addr); + void poke(uint16_t addr, uint8_t value); + uint8_t peekIO1(uint16_t addr); + uint8_t peekIO2(uint16_t addr); + void pokeIO1(uint16_t addr, uint8_t value); + void pokeIO2(uint16_t addr, uint8_t value); + bool hasLED() { return true; } +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/Epyx.cpp b/C64/Cartridges/CustomCartridges/Epyx.cpp new file mode 100755 index 00000000..f02487f6 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Epyx.cpp @@ -0,0 +1,94 @@ +/*! + * @file Epyx.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +EpyxFastLoad::reset() +{ + Cartridge::reset(); + dischargeCapacitor(); +} + +size_t +EpyxFastLoad::stateSize() +{ + return Cartridge::stateSize() + 8; +} +void +EpyxFastLoad::didLoadFromBuffer(uint8_t **buffer) +{ + Cartridge::didLoadFromBuffer(buffer); + cycle = read64(buffer); +} + +void +EpyxFastLoad::didSaveToBuffer(uint8_t **buffer) +{ + Cartridge::didSaveToBuffer(buffer); + write64(buffer, cycle); +} + +void +EpyxFastLoad::resetCartConfig() +{ + c64->expansionport.setCartridgeMode(CRT_8K); +} + +uint8_t +EpyxFastLoad::peekRomL(uint16_t addr) +{ + dischargeCapacitor(); + return Cartridge::peekRomL(addr); +} + +uint8_t +EpyxFastLoad::peekIO1(uint16_t addr) +{ + dischargeCapacitor(); + return 0; +} + +uint8_t +EpyxFastLoad::peekIO2(uint16_t addr) +{ + // I/O 2 mirrors the last 256 ROM bytes + return packet[0]->peek(addr & 0x1FFF); +} + +void +EpyxFastLoad::execute() +{ + // Switch cartridge off when the capacitor is fully charged + if (c64->cpu.cycle > cycle) { + c64->expansionport.setCartridgeMode(CRT_OFF); + } +} + +void +EpyxFastLoad::dischargeCapacitor() +{ + // Switch on cartridge + c64->expansionport.setCartridgeMode(CRT_8K); + + // Schedule cartridge to be switched off in about 512 CPU cycles + cycle = c64->cpu.cycle + 512; +} diff --git a/C64/Cartridges/CustomCartridges/Epyx.h b/C64/Cartridges/CustomCartridges/Epyx.h new file mode 100755 index 00000000..f33c8f58 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Epyx.h @@ -0,0 +1,74 @@ +/*! + * @header Epyx.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _EPYX_INC +#define _EPYX_INC + +#include "Cartridge.h" + +class EpyxFastLoad : public Cartridge { + +private: + + //! @brief Indicates when the capacitor discharges. + /*! @details The Epyx cartridge utilizes a capacitor to switch the ROM on + * and off. During normal operation, the capacitor charges + * slowly. When it is completely charged, the ROM gets disabled. + * When the cartridge is attached, the capacitor is discharged + * and the ROM visible. To avoid the ROM to be disabled, the + * cartridge can either read from ROML or I/O space 1. Both + * operations discharge the capacitor and keep the ROM alive. + */ + uint64_t cycle = 0; + +public: + + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_EPYX_FASTLOAD; } + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + size_t stateSize(); + void didLoadFromBuffer(uint8_t **buffer); + void didSaveToBuffer(uint8_t **buffer); + + // + //! @functiongroup Methods from Cartridge + // + + void resetCartConfig(); + uint8_t peekRomL(uint16_t addr); + uint8_t spypeekRomL(uint16_t addr) { return Cartridge::peekRomL(addr); } + uint8_t peekIO1(uint16_t addr); + uint8_t spypeekIO1(uint16_t addr) { return 0; } + uint8_t peekIO2(uint16_t addr); + void execute(); + +private: + + //! @brief Discharges the cartridge's capacitor + void dischargeCapacitor(); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/Expert.cpp b/C64/Cartridges/CustomCartridges/Expert.cpp new file mode 100755 index 00000000..682f9a93 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Expert.cpp @@ -0,0 +1,250 @@ +/*! + * @file Expert.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This implementation is based mainly by the following documents: +// Schematics and explanation by Martin Sikström: +// https://people.kth.se/~e93_msi/c64/expert.html + +#include "C64.h" + +Expert::Expert(C64 *c64) : Cartridge(c64) +{ + setDescription("Expert"); + + active = false; + setSwitch(0); + + // Allocate 8KB bytes persistant RAM + setRamCapacity(0x2000); + setPersistentRam(true); + + debug("Expert cartridge created\n"); +} + +void +Expert::reset() +{ + Cartridge::reset(); +} + +void +Expert::dump() +{ + Cartridge::dump(); + + msg(" active: %d\n", active); + msg(" switch: %d ", getSwitch()); + if (switchInPrgPosition()) msg("(PRG)\n"); + if (switchInOffPosition()) msg("(OFF)\n"); + if (switchInOnPosition()) msg("(ON)\n"); + msg(" NMI vector: %04X\n", LO_HI(peekRAM(0x1FFA), peekRAM(0x1FFB))); + msg(" IRQ vector: %04X\n", LO_HI(peekRAM(0x1FFE), peekRAM(0x1FFF))); + msg(" Reset vector: %04X\n", LO_HI(peekRAM(0x1FFC), peekRAM(0x1FFD))); +} + +size_t +Expert::stateSize() +{ + return Cartridge::stateSize() + 1; +} + +void +Expert::didLoadFromBuffer(uint8_t **buffer) +{ + Cartridge::didLoadFromBuffer(buffer); + active = read8(buffer); +} + +void +Expert::didSaveToBuffer(uint8_t **buffer) +{ + Cartridge::didSaveToBuffer(buffer); + write8(buffer, (uint8_t)active); +} + +void +Expert::loadChip(unsigned nr, CRTFile *c) +{ + uint16_t chipSize = c->chipSize(nr); + uint16_t chipAddr = c->chipAddr(nr); + uint8_t *chipData = c->chipData(nr); + + if (nr != 0 || chipSize != 0x2000 || chipAddr != 0x8000) { + warn("Corrupted CRT file. Aborting."); + return; + } + + assert(getRamCapacity() == chipSize); + + // Initialize RAM with data from CRT file + debug("Copying file contents into Expert RAM\n"); + for (unsigned i = 0; i < chipSize; i++) { + pokeRAM(i, chipData[i]); + } +} + +uint8_t +Expert::peek(uint16_t addr) +{ + if (cartridgeRamIsVisible(addr)) { + + // Get value from cartridge RAM + return peekRAM(addr & 0x1FFF); + + } else { + + // Get value as if no cartridge was attached + return c64->mem.peek(addr, 1, 1); + } +} + +uint8_t +Expert::peekIO1(uint16_t addr) +{ + // Any IO1 access disables the cartridge + active = false; + + return 0; +} + +void +Expert::poke(uint16_t addr, uint8_t value) +{ + if (cartridgeRamIsVisible(addr)) { + + // Write value into cartridge RAM if it is write enabled + if (cartridgeRamIsWritable(addr)) { + pokeRAM(addr & 0x1FFF, value); + } + + } else { + + // Write value as if no cartridge was attached + c64->mem.poke(addr, value, 1, 1); + } +} + +void +Expert::pokeIO1(uint16_t addr, uint8_t value) +{ + assert(addr >= 0xDE00 && addr <= 0xDEFF); + + debug("Expert::pokeIO1\n"); + + // Any IO1 access disabled the cartridge + active = false; +} + +const char * +Expert::getButtonTitle(unsigned nr) +{ + return (nr == 1) ? "Reset" : (nr == 2) ? "ESM" : NULL; +} + +void +Expert::pressButton(unsigned nr) +{ + assert(nr <= numButtons()); + debug("Pressing %s button.\n", getButtonTitle(nr)); + + c64->suspend(); + + switch (nr) { + + case 1: // Reset + + if (switchInOnPosition()) { active = true; } + resetWithoutDeletingRam(); + break; + + case 2: // ESM (Freeze) + + if (switchInOnPosition()) { active = true; } + + // The Expert cartridge uses two three-state buffers in parallel to + // force the NMI line high, even if a program leaves it low to + // protect itself against freezers. The following code is surely + // not accurate, but it forces an NMI a trigger, regardless of the + // current value of the NMI line. + + uint8_t oldLine = c64->cpu.nmiLine; + uint8_t newLine = oldLine | CPU::INTSRC_EXPANSION; + + c64->cpu.releaseNmiLine((CPU::IntSource)0xFF); + c64->cpu.pullDownNmiLine((CPU::IntSource)newLine); + c64->cpu.releaseNmiLine(CPU::INTSRC_EXPANSION); + break; + } + + c64->resume(); +} + + + +const char * +Expert::getSwitchDescription(int8_t pos) +{ + return (pos == -1) ? "Prg" : (pos == 0) ? "Off" : (pos == 1) ? "On" : NULL; +} + +bool +Expert::cartridgeRamIsVisible(uint16_t addr) +{ + if (addr < 0x8000) { + assert(false); // Should never be called for this address space + return false; + } + if (addr < 0xA000) { + return switchInPrgPosition() || (switchInOnPosition() && active); + } + if (addr < 0xE000) { + return false; + } + return switchInOnPosition() && active; +} + +bool +Expert::cartridgeRamIsWritable(uint16_t addr) +{ + return isROMLaddr(addr); +} + +void +Expert::updatePeekPokeLookupTables() +{ + // Setting up faked Ultimax mode. We let the Game and Exrom line as they + // are, but reroute all access to ROML and ROMH into the cartridge. + + // Reroute ROML + c64->mem.peekSrc[0x8] = c64->mem.pokeTarget[0x8] = M_CRTLO; + c64->mem.peekSrc[0x9] = c64->mem.pokeTarget[0x9] = M_CRTLO; + + // Reroute ROMH + c64->mem.peekSrc[0xE] = c64->mem.pokeTarget[0xE] = M_CRTLO; + c64->mem.peekSrc[0xF] = c64->mem.pokeTarget[0xF] = M_CRTLO; +} + +void +Expert::nmiWillTrigger() +{ + // Activate cartridge if switch is in 'ON' position + if (switchInOnPosition()) { active = 1; } +} diff --git a/C64/Cartridges/CustomCartridges/Expert.h b/C64/Cartridges/CustomCartridges/Expert.h new file mode 100755 index 00000000..f5a7c058 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Expert.h @@ -0,0 +1,80 @@ +/*! + * @header Expert.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _EXPERT_INC +#define _EXPERT_INC + +#include "Cartridge.h" + +class Expert : public Cartridge { + + // On-board flipflop + bool active; + +public: + + Expert(C64 *c64); + CartridgeType getCartridgeType() { return CRT_EXPERT; } + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void dump(); + size_t stateSize(); + void didLoadFromBuffer(uint8_t **buffer); + void didSaveToBuffer(uint8_t **buffer); + + // + //! @functiongroup Methods from Cartridge + // + + void loadChip(unsigned nr, CRTFile *c); + + unsigned numButtons() { return 2; } + const char *getButtonTitle(unsigned nr); + void pressButton(unsigned nr); + + bool hasSwitch() { return true; } + const char *getSwitchDescription(int8_t pos); + bool switchInPrgPosition() { return switchIsLeft(); } + bool switchInOffPosition() { return switchIsNeutral(); } + bool switchInOnPosition() { return switchIsRight(); } + + void updatePeekPokeLookupTables(); + uint8_t peek(uint16_t addr); + uint8_t peekIO1(uint16_t addr); + uint8_t spypeekIO1(uint16_t addr) { return 0; } + void poke(uint16_t addr, uint8_t value); + void pokeIO1(uint16_t addr, uint8_t value); + + void nmiWillTrigger(); + + //! @brief Returns true if cartridge RAM is visible + bool cartridgeRamIsVisible(uint16_t addr); + + //! @brief Returns true if cartridge RAM is write enabled + bool cartridgeRamIsWritable(uint16_t addr); +}; + + +#endif diff --git a/C64/Cartridges/CustomCartridges/FinalIII.cpp b/C64/Cartridges/CustomCartridges/FinalIII.cpp new file mode 100755 index 00000000..58eacc4c --- /dev/null +++ b/C64/Cartridges/CustomCartridges/FinalIII.cpp @@ -0,0 +1,206 @@ +/*! + * @file FinalIII.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +FinalIII::reset() +{ + CartridgeWithRegister::reset(); + freeezeButtonIsPressed = false; + qD = true; + bankIn(0); +} + +void +FinalIII::resetCartConfig() +{ + c64->expansionport.setCartridgeMode(CRT_16K); +} + +uint8_t +FinalIII::peekIO1(uint16_t addr) +{ + // I/O space 1 mirrors $1E00 to $1EFF from ROML + return peekRomL(addr & 0x1FFF); +} + +uint8_t +FinalIII::peekIO2(uint16_t addr) +{ + // I/O space 2 space mirrors $1F00 to $1FFF from ROML + return peekRomL(addr & 0x1FFF); +} + +void +FinalIII::pokeIO2(uint16_t addr, uint8_t value) { + + // The control register is mapped to address 0xFF in I/O space 2. + if (addr == 0xDFFF && writeEnabled()) { + setControlReg(value); + } + + // debug("hidden = %d nmi = %d game = %d exrom = %d qD = %d bank = %d\n", hidden(), nmi(), game(), exrom(), qD, bank()); + +#if 0 + /* "7 Hide this register (1 = hidden) + * 6 NMI line (0 = low = active) *1) + * 5 GAME line (0 = low = active) *2) + * 4 EXROM line (0 = low = active) + * 2-3 unassigned (usually set to 0) + * 0-1 number of bank to show at $8000 + * + * 1) if either the freezer button is pressed, + * or bit 6 is 0, then an NMI is generated + * + * 2) if the freezer button is pressed, GAME + * is also forced low" [VICE] + */ + + uint8_t hide = value & 0x80; + uint8_t nmi = value & 0x40; + uint8_t game = value & 0x20; + uint8_t exrom = value & 0x10; + uint8_t bank = value & 0x03; + + // Bit 7 + if (hide) { + c64->expansionport.setCartridgeMode(CRT_OFF); + } + + // Bit 6 + nmi ? c64->cpu.releaseNmiLine(CPU::INTSRC_EXPANSION) : + c64->cpu.pullDownNmiLine(CPU::INTSRC_EXPANSION); + + // Bit 5 and 4 + c64->expansionport.setGameAndExrom(game, exrom); + + // Bit 1 and 0 + bankIn(bank); +#endif +} + +void +FinalIII::nmiDidTrigger() +{ + if (freeezeButtonIsPressed) { + debug("NMI WHILE FREEZE BUTTON IS PRESSED\n"); + + // After the NMI has been processed by the CPU, the cartridge's counter + // has reached a value that overflows qD to 0. This has two side + // effects. First, the Game line switches to 0. Second, because qD is + // also connectec to the counter's enable pin, the counter freezes. This + // keeps qD low until the freeze button is released by the user. + + qD = false; + updateGame(); + } +} + +const char * +FinalIII::getButtonTitle(unsigned nr) +{ + return (nr == 1) ? "Freeze" : (nr == 2) ? "Reset" : NULL; +} + +void +FinalIII::pressButton(unsigned nr) +{ + assert(nr <= numButtons()); + debug("Pressing %s button.\n", getButtonTitle(nr)); + + c64->suspend(); + + switch (nr) { + + case 1: // Freeze + + freeezeButtonIsPressed = true; + updateNMI(); + break; + + case 2: // Reset + + resetWithoutDeletingRam(); + break; + } + + c64->resume(); +} + +void +FinalIII::releaseButton(unsigned nr) +{ + assert(nr <= numButtons()); + debug("Releasing %s button.\n", getButtonTitle(nr)); + + c64->suspend(); + + switch (nr) { + + case 1: // Freeze + + freeezeButtonIsPressed = false; + qD = true; + updateNMI(); + updateGame(); + break; + } + + c64->resume(); +} + +void +FinalIII::setControlReg(uint8_t value) +{ + control = value; + + // Update external lines + updateNMI(); + updateGame(); + c64->expansionport.setExromLine(exrom()); + + // Switch memory bank + bankIn(control & 0x03); + +} + +bool +FinalIII::writeEnabled() +{ + return !hidden() || freeezeButtonIsPressed; +} + +void +FinalIII::updateNMI() +{ + if (nmi() && !freeezeButtonIsPressed) { + c64->cpu.releaseNmiLine(CPU::INTSRC_EXPANSION); + } else { + c64->cpu.pullDownNmiLine(CPU::INTSRC_EXPANSION); + } +} + +void +FinalIII::updateGame() +{ + c64->expansionport.setGameLine(game() && qD); +} diff --git a/C64/Cartridges/CustomCartridges/FinalIII.h b/C64/Cartridges/CustomCartridges/FinalIII.h new file mode 100755 index 00000000..3afb4d45 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/FinalIII.h @@ -0,0 +1,116 @@ +/*! + * @header FinalIII.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _FINALIII_INC +#define _FINALIII_INC + +#include "Cartridge.h" + +class FinalIII : public CartridgeWithRegister { + + //! @brief Indicates if the freeze button is currenty pressed. + bool freeezeButtonIsPressed; + + /*! @brief The QD pin of the Final Cartridge III's 4-bit counter. + * @details The counter's purpose is to delay grounding the Game line + * when the freeze button is pressed. Doing so lets the + * CPU read the NMI vector with the old Game/Exrom combination. + */ + bool qD; + +public: + + FinalIII(C64 *c64) : CartridgeWithRegister(c64, "FinalIII") { }; + CartridgeType getCartridgeType() { return CRT_FINAL_III; } + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + + size_t stateSize() { + return CartridgeWithRegister::stateSize() + 2; + } + void didLoadFromBuffer(uint8_t **buffer) + { + CartridgeWithRegister::didLoadFromBuffer(buffer); + freeezeButtonIsPressed = (bool)read8(buffer); + qD = (bool)read8(buffer); + } + void didSaveToBuffer(uint8_t **buffer) + { + CartridgeWithRegister::didSaveToBuffer(buffer); + write8(buffer, (uint8_t)freeezeButtonIsPressed); + write8(buffer, (uint8_t)qD); + } + + // + //! @functiongroup Methods from Cartridge + // + + void resetCartConfig(); + + uint8_t peekIO1(uint16_t addr); + uint8_t peekIO2(uint16_t addr); + void pokeIO2(uint16_t addr, uint8_t value); + void nmiDidTrigger(); + + + // + //! @functiongroup Methods from Cartridge + // + + unsigned numButtons() { return 2; } + const char *getButtonTitle(unsigned nr); + void pressButton(unsigned nr); + void releaseButton(unsigned nr); + + //! @brief Writes a new value into the control register. + void setControlReg(uint8_t value); + + bool hidden() { return (control & 0x80) != 0; } + bool nmi() { return (control & 0x40) != 0; } + bool game() { return (control & 0x20) != 0; } + bool exrom() { return (control & 0x10) != 0; } + uint8_t bank() { return (control & 0x03); } + + /*! @brief Indicates if the control register is write enabled. + * @note Final Cartridge III enables and disables the control register + * by masking the clock signal. + */ + bool writeEnabled(); + + /*! @brief Updates the NMI line + * @note The NMI line is driven by the control register and the + * current position of the freeze button. + */ + void updateNMI(); + + /*! @brief Updates the Game line + * @note The game line is driven by the control register and counter + * output qD. + */ + void updateGame(); +}; + +#endif + diff --git a/C64/Cartridges/CustomCartridges/FreezeFrame.cpp b/C64/Cartridges/CustomCartridges/FreezeFrame.cpp new file mode 100755 index 00000000..7c21cd3e --- /dev/null +++ b/C64/Cartridges/CustomCartridges/FreezeFrame.cpp @@ -0,0 +1,73 @@ +/*! + * @file FreezeFrame.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +FreezeFrame::reset() +{ + Cartridge::reset(); + + // In Ultimax mode, the same ROM chip that appears in ROML also appears + // in ROMH. By default, it it appears in ROML only, so let's bank it in + // ROMH manually. + bankInROMH(0, 0x2000, 0); +} + +uint8_t +FreezeFrame::peekIO1(uint16_t addr) +{ + // Reading from IO1 switched to 8K game mode + c64->expansionport.setCartridgeMode(CRT_8K); + return 0; +} + +uint8_t +FreezeFrame::peekIO2(uint16_t addr) +{ + // Reading from IO2 disables the cartridge + c64->expansionport.setCartridgeMode(CRT_OFF); + return 0; +} + +void +FreezeFrame::pressButton(unsigned nr) +{ + if (nr == 1) { + + // Pressing the freeze button triggers an NMI in Ultimax mode + suspend(); + c64->expansionport.setCartridgeMode(CRT_ULTIMAX); + c64->cpu.pullDownNmiLine(CPU::INTSRC_EXPANSION); + resume(); + } +} + +void +FreezeFrame::releaseButton(unsigned nr) +{ + if (nr == 1) { + + suspend(); + c64->cpu.releaseNmiLine(CPU::INTSRC_EXPANSION); + resume(); + } +} diff --git a/C64/Cartridges/CustomCartridges/FreezeFrame.h b/C64/Cartridges/CustomCartridges/FreezeFrame.h new file mode 100755 index 00000000..0d77db2e --- /dev/null +++ b/C64/Cartridges/CustomCartridges/FreezeFrame.h @@ -0,0 +1,55 @@ +/*! + * @header FreezeFrame.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _FREEZEFRAME_INC +#define _FREEZEFRAME_INC + +#include "Cartridge.h" + +class FreezeFrame : public Cartridge { + +public: + + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_FREEZE_FRAME; } + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + + // + //! @functiongroup Methods from Cartridge + // + + uint8_t peekIO1(uint16_t addr); + uint8_t spypeekIO1(uint16_t addr) { return 0; } + uint8_t peekIO2(uint16_t addr); + uint8_t spypeekIO2(uint16_t addr) { return 0; } + + unsigned numButtons() { return 1; } + const char *getButtonTitle(unsigned nr) { return (nr == 1) ? "Freeze" : NULL; } + void pressButton(unsigned nr); + void releaseButton(unsigned nr); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/Funplay.cpp b/C64/Cartridges/CustomCartridges/Funplay.cpp new file mode 100755 index 00000000..fc6ff6be --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Funplay.cpp @@ -0,0 +1,47 @@ +/*! + * @file Funplay.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +Funplay::pokeIO1(uint16_t addr, uint8_t value) +{ + /* + * Bank switching is done by writing to $DE00. + * + * Bit in DE00 -> 76543210 + * xx210xx3 <- Bit in selected bank number + * + * A value of $86 is written to disable the cartridge. + */ + + if (addr == 0xDE00) { + + if (value == 0x86) { + c64->expansionport.setCartridgeMode(CRT_OFF); + return; + } + + uint8_t bank = ((value >> 3) & 0x07) | ((value << 3) & 0x08); + assert(bank < 16); + bankIn(bank); + } +} diff --git a/C64/Cartridges/CustomCartridges/Funplay.h b/C64/Cartridges/CustomCartridges/Funplay.h new file mode 100755 index 00000000..20b4d075 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Funplay.h @@ -0,0 +1,36 @@ +/*! + * @header Funplay.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _FUNPLAY_INC +#define _FUNPLAY_INC + +#include "Cartridge.h" + +class Funplay : public Cartridge { + +public: + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_FUNPLAY; } + + void pokeIO1(uint16_t addr, uint8_t value); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/GeoRam.cpp b/C64/Cartridges/CustomCartridges/GeoRam.cpp new file mode 100755 index 00000000..07e262ea --- /dev/null +++ b/C64/Cartridges/CustomCartridges/GeoRam.cpp @@ -0,0 +1,107 @@ +/*! + * @file GeoRam.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +GeoRAM::GeoRAM(C64 *c64) : Cartridge(c64) +{ + setDescription("GeoRAM"); +} + +void +GeoRAM::reset() +{ + if (!getPersistentRam()) { + debug("Erasing GeoRAM\n"); + eraseRAM(0); + } else { + debug("Preserving GeoRAM\n"); + } +} + +size_t +GeoRAM::stateSize() +{ + return Cartridge::stateSize() + 2; +} + +void +GeoRAM::didLoadFromBuffer(uint8_t **buffer) +{ + Cartridge::didLoadFromBuffer(buffer); + bank = read8(buffer); + page = read8(buffer); +} + +void +GeoRAM::didSaveToBuffer(uint8_t **buffer) +{ + Cartridge::didSaveToBuffer(buffer); + write8(buffer, bank); + write8(buffer, page); +} + +unsigned +GeoRAM::offset(uint8_t addr) +{ + /* From VICE: + * "The GeoRAM is a banked memory system. It uses the registers at + * $dffe and $dfff to determine what part of the GeoRAM memory should + * be mapped to $de00-$deff. + * The register at $dfff selects which 16k block to map, and $dffe + * selects a 256-byte page in that block. Since there are only 64 + * 256-byte pages inside of 16k, the value in $dffe ranges from 0 to 63." + */ + + unsigned bankOffset = (bank * 16384) % getRamCapacity(); + unsigned pageOffset = (page & 0x3F) * 256; + return bankOffset + pageOffset + addr; +} + +uint8_t +GeoRAM::peekIO1(uint16_t addr) +{ + assert(addr >= 0xDE00 && addr <= 0xDEFF); + return peekRAM(offset(addr - 0xDE00)); +} + +uint8_t +GeoRAM::peekIO2(uint16_t addr) +{ + return 0; +} + +void +GeoRAM::pokeIO1(uint16_t addr, uint8_t value) +{ + assert(addr >= 0xDE00 && addr <= 0xDEFF); + pokeRAM(offset(addr - 0xDE00), value); +} + +void +GeoRAM::pokeIO2(uint16_t addr, uint8_t value) +{ + if (addr & 1) { + bank = value; // Bank select + } else { + page = value; // Page select + } +} diff --git a/C64/Cartridges/CustomCartridges/GeoRam.h b/C64/Cartridges/CustomCartridges/GeoRam.h new file mode 100755 index 00000000..c0101408 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/GeoRam.h @@ -0,0 +1,53 @@ +/*! + * @header GeoRam.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _GEORAM_INC +#define _GEORAM_INC + +#include "Cartridge.h" + +class GeoRAM : public Cartridge { + +private: + + //! @brief Selected RAM bank + uint8_t bank; + + //! @brief Selected page inside the selected RAM bank. + uint8_t page; + + //! @brief Computes the offset for accessing the cartridge RAM + unsigned offset(uint8_t addr); + +public: + GeoRAM(C64 *c64); + CartridgeType getCartridgeType() { return CRT_GEO_RAM; } + void reset(); + size_t stateSize(); + void didLoadFromBuffer(uint8_t **buffer); + void didSaveToBuffer(uint8_t **buffer); + uint8_t peekIO1(uint16_t addr); + uint8_t peekIO2(uint16_t addr); + void pokeIO1(uint16_t addr, uint8_t value); + void pokeIO2(uint16_t addr, uint8_t value); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/Isepic.cpp b/C64/Cartridges/CustomCartridges/Isepic.cpp new file mode 100755 index 00000000..f664ce74 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Isepic.cpp @@ -0,0 +1,197 @@ +/*! + * @file Isepic.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +Isepic::Isepic(C64 *c64) : Cartridge(c64) +{ + setDescription("Isepic"); + + // Allocate 2KB bytes on-board RAM + setRamCapacity(2048); + + // Reset the page selector flipflops + page = 0; + + // We start with an enabled cartridge (without triggering an NMI) + Cartridge::setSwitch(1); + + debug("Isepic cartridge created\n"); +} + +void +Isepic::reset() +{ + Cartridge::reset(); + eraseRAM(0); + page = 0; +} + +size_t +Isepic::stateSize() +{ + return Cartridge::stateSize() + 9; +} + +void +Isepic::didLoadFromBuffer(uint8_t **buffer) +{ + Cartridge::didLoadFromBuffer(buffer); + page = read8(buffer); + oldPeekSource = (MemoryType)read32(buffer); + oldPokeTarget = (MemoryType)read32(buffer); +} + +void +Isepic::didSaveToBuffer(uint8_t **buffer) +{ + Cartridge::didSaveToBuffer(buffer); + write8(buffer, page); + write32(buffer, (MemoryType)oldPeekSource); + write32(buffer, (MemoryType)oldPokeTarget); +} + +uint8_t +Isepic::peek(uint16_t addr) +{ + assert((addr & 0xF000) == 0xF000); + + // Intercept if the NMI vector is accessed + if (cartIsVisible() && (addr == 0xFFFA || addr == 0xFFFB)) { + return peekRAM((page * 256) + (addr & 0xFF)); + } else { + return c64->mem.peek(addr, oldPeekSource); + } +} + +uint8_t +Isepic::peekIO1(uint16_t addr) +{ + assert(addr >= 0xDE00 && addr <= 0xDEFF); + + if (cartIsVisible()) { + page = ((addr & 0b001) << 2) | (addr & 0b010) | ((addr & 0b100) >> 2); + } + + return 0; +} + +uint8_t +Isepic::peekIO2(uint16_t addr) +{ + assert(addr >= 0xDF00 && addr <= 0xDFFF); + + if (cartIsVisible()) { + return peekRAM((page * 256) + (addr & 0xFF)); + } else { + return Cartridge::peekIO2(addr); + } +} + +void +Isepic::poke(uint16_t addr, uint8_t value) +{ + assert((addr & 0xF000) == 0xF000); + + // Intercept if the NMI vector is accessed + if (cartIsVisible() && (addr == 0xFFFA || addr == 0xFFFB)) { + pokeRAM((page * 256) + (addr & 0xFF), value); + } else { + c64->mem.poke(addr, value, oldPokeTarget); + } +} + +void +Isepic::pokeIO1(uint16_t addr, uint8_t value) +{ + assert(addr >= 0xDE00 && addr <= 0xDEFF); + + (void)peekIO1(addr); +} + +void +Isepic::pokeIO2(uint16_t addr, uint8_t value) +{ + assert(addr >= 0xDF00 && addr <= 0xDFFF); + + if (cartIsVisible()) { + pokeRAM((page * 256) + (addr & 0xFF), value); + } else { + Cartridge::pokeIO2(addr, value); + } +} + +const char * +Isepic::getSwitchDescription(int8_t pos) +{ + return (pos == -1) ? "Off" : (pos == 1) ? "On" : NULL; +} + +void +Isepic::setSwitch(int8_t pos) +{ + c64->suspend(); + + bool oldVisible = cartIsVisible(); + Cartridge::setSwitch(pos); + bool newVisible = cartIsVisible(); + + if (oldVisible != newVisible) { + + // Enforce a call to updatePeekPokeLookupTables() + c64->expansionport.setCartridgeMode(CRT_OFF); + + if (newVisible) { + + debug("Activating Ipsec cartridge\n"); + + // Trigger NMI + c64->cpu.pullDownNmiLine(CPU::INTSRC_EXPANSION); + c64->cpu.releaseNmiLine(CPU::INTSRC_EXPANSION); + + } else { + + debug("Hiding Ipsec cartridge\n"); + } + } + + c64->resume(); +} + +void +Isepic::updatePeekPokeLookupTables() +{ + /* If the ISEPIC cartridge is active, it intercepts memory accesses to the + * NMI vector at $FFFA / $FFFB. This is done by an inverter and two 8-bit + * NANDs of type SN5430 that compare the address lines with the bit pattern + * 1111.1111.1111.101x. If the pattern matches, it enables Ultimax mode by + * pulling down the GAME line. + * + * To emulate this custom behaviour, we redirect the peekSource and + * pokeTarget for the uppermost memory page to the cartridge. + */ + + oldPeekSource = c64->mem.peekSrc[0xF]; + oldPokeTarget = c64->mem.pokeTarget[0xF]; + + c64->mem.peekSrc[0xF] = M_CRTHI; + c64->mem.pokeTarget[0xF] = M_CRTHI; +} diff --git a/C64/Cartridges/CustomCartridges/Isepic.h b/C64/Cartridges/CustomCartridges/Isepic.h new file mode 100755 index 00000000..3a6f903a --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Isepic.h @@ -0,0 +1,64 @@ +/*! + * @header Isepic.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ISEPIC_INC +#define _ISEPIC_INC + +#include "Cartridge.h" + +class Isepic : public Cartridge { + + //! @brief Selected page inside the selected RAM bank. + uint8_t page; + + //! @brief Original mapping of the uppermost memory page + MemoryType oldPeekSource; + MemoryType oldPokeTarget; + +public: + Isepic(C64 *c64); + CartridgeType getCartridgeType() override { return CRT_ISEPIC; } + + void reset() override; + size_t stateSize() override; + void didLoadFromBuffer(uint8_t **buffer) override; + void didSaveToBuffer(uint8_t **buffer) override; + + bool hasSwitch() override { return true; } + const char *getSwitchDescription(int8_t pos) override; + void setSwitch(int8_t pos) override; + bool switchInOffPosition() { return switchIsLeft(); } + bool switchInOnPosition() { return switchIsRight(); } + + bool cartIsVisible() { return switchInOnPosition(); } + bool cartIsHidden() { return !cartIsVisible(); } + + void updatePeekPokeLookupTables() override; + uint8_t peek(uint16_t addr) override; + uint8_t peekIO1(uint16_t addr) override; + uint8_t peekIO2(uint16_t addr) override; + void poke(uint16_t addr, uint8_t value) override; + void pokeIO1(uint16_t addr, uint8_t value) override; + void pokeIO2(uint16_t addr, uint8_t value) override; +}; + + +#endif diff --git a/C64/Cartridges/CustomCartridges/Kcs.cpp b/C64/Cartridges/CustomCartridges/Kcs.cpp new file mode 100755 index 00000000..b9d7d415 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Kcs.cpp @@ -0,0 +1,105 @@ +/*! + * @file Kcs.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +KcsPower::KcsPower(C64 *c64) : Cartridge(c64) +{ + // Allocate 128 bytes on-board RAM + setRamCapacity(0x80); +} + +void +KcsPower::reset() +{ + Cartridge::reset(); + eraseRAM(0xFF); +} + +uint8_t +KcsPower::peekIO1(uint16_t addr) +{ + c64->expansionport.setGameAndExrom(1, addr & 0x02 ? 1 : 0); + return peekRomL(0x1E00 | (addr & 0xFF)); +} + +uint8_t +KcsPower::spypeekIO1(uint16_t addr) +{ + return peekRomL(0x1E00 | (addr & 0xFF)); +} + +uint8_t +KcsPower::peekIO2(uint16_t addr) +{ + if (addr & 0x80) { + + /* + uint8_t exrom = c64->expansionport.getExromLine() ? 0x80 : 0x00; + uint8_t game = c64->expansionport.getGameLine() ? 0x40 : 0x00; + return exrom | game | (c64->vic.getDataBusPhi1() & 0x3F); + */ + return peekRAM(addr & 0x7F); + + } else { + + // Return value from onboard RAM + return peekRAM(addr & 0x7F); + } +} + +void +KcsPower::pokeIO1(uint16_t addr, uint8_t value) +{ + c64->expansionport.setGameAndExrom(0, (addr & 0b10) ? 1 : 0); +} + +void +KcsPower::pokeIO2(uint16_t addr, uint8_t value) +{ + if (!(addr & 0x80)) { + pokeRAM(addr & 0x7F, value); + } +} + +void +KcsPower::pressButton(unsigned nr) +{ + if (nr == 1) { + + // Pressing the button triggers an NMI in Ultimax mode + suspend(); + c64->expansionport.setCartridgeMode(CRT_ULTIMAX); + c64->cpu.pullDownNmiLine(CPU::INTSRC_EXPANSION); + resume(); + } +}; + +void +KcsPower::releaseButton(unsigned nr) +{ + if (nr == 1) { + + suspend(); + c64->cpu.releaseNmiLine(CPU::INTSRC_EXPANSION); + resume(); + } +}; diff --git a/C64/Cartridges/CustomCartridges/Kcs.h b/C64/Cartridges/CustomCartridges/Kcs.h new file mode 100755 index 00000000..360711c5 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Kcs.h @@ -0,0 +1,52 @@ +/*! + * @header Kcs.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _KCS_INC +#define _KCS_INC + +#include "Cartridge.h" + +class KcsPower : public Cartridge { + +public: + KcsPower(C64 *c64); + CartridgeType getCartridgeType() { return CRT_KCS_POWER; } + + void reset(); + + // + //! @functiongroup Methods from Cartridge + // + + uint8_t peekIO1(uint16_t addr); + uint8_t spypeekIO1(uint16_t addr); + uint8_t peekIO2(uint16_t addr); + void pokeIO1(uint16_t addr, uint8_t value); + void pokeIO2(uint16_t addr, uint8_t value); + + unsigned numButtons() { return 1; } + const char *getButtonTitle(unsigned nr) { return (nr == 1) ? "Freeze" : NULL; } + void pressButton(unsigned nr); + void releaseButton(unsigned nr); +}; + +#endif + diff --git a/C64/Cartridges/CustomCartridges/Kingsoft.cpp b/C64/Cartridges/CustomCartridges/Kingsoft.cpp new file mode 100755 index 00000000..35f618d1 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Kingsoft.cpp @@ -0,0 +1,74 @@ +/*! + * @file Kingsoft.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +Kingsoft::resetCartConfig() +{ + // Start in 16KB game mode by reading from I/O space 1 + (void)peekIO1(0); +} + +void +Kingsoft::updatePeekPokeLookupTables() +{ + // Tweak lookup tables if we run in Ultimax mode + if (c64->getUltimax()) { + + // $0000 - $7FFF and $C000 - $DFFF are usable the normal way + uint8_t exrom = 0x10; + uint8_t game = 0x08; + uint8_t index = (c64->processorPort.read() & 0x07) | exrom | game; + + for (unsigned bank = 0x1; bank <= 0x7; bank++) { + MemoryType type = c64->mem.bankMap[index][bank]; + c64->mem.peekSrc[bank] = c64->mem.pokeTarget[bank] = type; + } + for (unsigned bank = 0xC; bank <= 0xD; bank++) { + MemoryType type = c64->mem.bankMap[index][bank]; + c64->mem.peekSrc[bank] = c64->mem.pokeTarget[bank] = type; + } + } +} + +uint8_t +Kingsoft::peekIO1(uint16_t addr) +{ + // Switch to 16KB game mode + c64->expansionport.setCartridgeMode(CRT_16K); + + // Bank in second packet to ROMH + bankInROMH(1, 0x2000, 0); + + return 0; +} + +void +Kingsoft::pokeIO1(uint16_t addr, uint8_t value) +{ + // Switch to (faked) Ultimax mode + c64->expansionport.setCartridgeMode(CRT_ULTIMAX); + + // Bank in third packet to ROMH + bankInROMH(2, 0x2000, 0); +} + diff --git a/C64/Cartridges/CustomCartridges/Kingsoft.h b/C64/Cartridges/CustomCartridges/Kingsoft.h new file mode 100755 index 00000000..7f9ba988 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Kingsoft.h @@ -0,0 +1,49 @@ +/*! + * @header Kingsoft.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _KINGSOFT_INC +#define _KINGSOFT_INC + +#include "Cartridge.h" + +class Kingsoft : public Cartridge { + +public: + + Kingsoft(C64 *c64) : Cartridge(c64, "Kingsoft") { }; + CartridgeType getCartridgeType() { return CRT_KINGSOFT; } + + // + //! @functiongroup Methods from Cartridge + // + + void resetCartConfig(); + + uint8_t peekIO1(uint16_t addr); + uint8_t spyPeek(uint16_t addr) { return 0; } + void pokeIO1(uint16_t addr, uint8_t value); + + void updatePeekPokeLookupTables(); +}; + + + +#endif diff --git a/C64/Cartridges/CustomCartridges/Mach5.cpp b/C64/Cartridges/CustomCartridges/Mach5.cpp new file mode 100755 index 00000000..56cc41af --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Mach5.cpp @@ -0,0 +1,58 @@ +/*! + * @file Mach5.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +Mach5::reset() +{ + Cartridge::reset(); + // c64->expansionport.setCartridgeMode(CRT_8K); + // bankIn(0); +} + +uint8_t +Mach5::peekIO1(uint16_t addr) +{ + // debug("Mach5::peekIO1(%x)", addr); + return peekRomL(0x1E00 | LO_BYTE(addr)); +} + +uint8_t +Mach5::peekIO2(uint16_t addr) +{ + // debug("Mach5::peekIO2(%x)", addr); + return peekRomL(0x1F00 | LO_BYTE(addr)); +} + +void +Mach5::pokeIO1(uint16_t addr, uint8_t value) +{ + debug("Enabling Mach5 in 8K game mode\n"); + c64->expansionport.setCartridgeMode(CRT_8K); +} + +void +Mach5::pokeIO2(uint16_t addr, uint8_t value) +{ + debug("Switching Mach5 off\n"); + c64->expansionport.setCartridgeMode(CRT_OFF); +} diff --git a/C64/Cartridges/CustomCartridges/Mach5.h b/C64/Cartridges/CustomCartridges/Mach5.h new file mode 100755 index 00000000..49f84c20 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Mach5.h @@ -0,0 +1,45 @@ +/*! + * @header Mach5.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MACH5_INC +#define _MACH5_INC + +#include "Cartridge.h" + +class Mach5 : public Cartridge { + +public: + + Mach5(C64 *c64) : Cartridge(c64, "Mach5") { }; + CartridgeType getCartridgeType() { return CRT_MACH5; } + + // + //! @functiongroup Methods from Cartridge + // + + void reset(); + uint8_t peekIO1(uint16_t addr); + uint8_t peekIO2(uint16_t addr); + void pokeIO1(uint16_t addr, uint8_t value); + void pokeIO2(uint16_t addr, uint8_t value); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/MagicDesk.cpp b/C64/Cartridges/CustomCartridges/MagicDesk.cpp new file mode 100755 index 00000000..d584d03d --- /dev/null +++ b/C64/Cartridges/CustomCartridges/MagicDesk.cpp @@ -0,0 +1,47 @@ +/*! + * @file MagicDesk.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +uint8_t +MagicDesk::peekIO1(uint16_t addr) +{ + return control; +} + +void +MagicDesk::pokeIO1(uint16_t addr, uint8_t value) +{ + control = value & 0x8F; + + /* This cartridge type is very similar to the OCEAN cart type: ROM memory + * is organized in 8Kb ($2000) banks located at $8000-$9FFF. Bank + * switching is done by writing the bank number to $DE00. Deviant from the + * Ocean type, bit 8 is cleared for selecting one of the ROM banks. If bit + * 8 is set ($DE00 = $80), the GAME/EXROM lines are disabled, turning on + * RAM at $8000-$9FFF instead of ROM. + */ + + if (addr == 0xDE00) { + c64->expansionport.setExromLine(value & 0x80); + bankIn(value & 0x0F); + } +} diff --git a/C64/Cartridges/CustomCartridges/MagicDesk.h b/C64/Cartridges/CustomCartridges/MagicDesk.h new file mode 100755 index 00000000..967b6142 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/MagicDesk.h @@ -0,0 +1,43 @@ +/*! + * @header MagicDesk.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MAGICDESK_INC +#define _MAGICDESK_INC + +#include "Cartridge.h" + +class MagicDesk : public CartridgeWithRegister { + +public: + + MagicDesk(C64 *c64) : CartridgeWithRegister(c64, "MagicDesk") { }; + CartridgeType getCartridgeType() { return CRT_MAGIC_DESK; } + + // + //! @functiongroup Methods from Cartridge + // + + uint8_t peekIO1(uint16_t addr); + void pokeIO1(uint16_t addr, uint8_t value); +}; + + +#endif diff --git a/C64/Cartridges/CustomCartridges/MikroAss.cpp b/C64/Cartridges/CustomCartridges/MikroAss.cpp new file mode 100755 index 00000000..22f04bd0 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/MikroAss.cpp @@ -0,0 +1,36 @@ +/*! + * @file MikroAss.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +uint8_t +MikroAss::peekIO1(uint16_t addr) +{ + // debug("MikroAss::peekIO1(%x)", addr); + return peekRomL(0x1E00 | LO_BYTE(addr)); +} + +uint8_t +MikroAss::peekIO2(uint16_t addr) +{ + // debug("MikroAss::peekIO2(%x)", addr); + return peekRomL(0x1F00 | LO_BYTE(addr)); +} diff --git a/C64/Cartridges/CustomCartridges/MikroAss.h b/C64/Cartridges/CustomCartridges/MikroAss.h new file mode 100755 index 00000000..f3eb950a --- /dev/null +++ b/C64/Cartridges/CustomCartridges/MikroAss.h @@ -0,0 +1,42 @@ +/*! + * @header MikroAss.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MIKROASS_INC +#define _MIKROASS_INC + +#include "Cartridge.h" + +class MikroAss : public Cartridge { + +public: + + MikroAss(C64 *c64) : Cartridge(c64, "Mikro Assembler") { }; + CartridgeType getCartridgeType() { return CRT_MIKRO_ASS; } + + // + //! @functiongroup Methods from Cartridge + // + + uint8_t peekIO1(uint16_t addr); + uint8_t peekIO2(uint16_t addr); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/Ocean.cpp b/C64/Cartridges/CustomCartridges/Ocean.cpp new file mode 100755 index 00000000..405485a1 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Ocean.cpp @@ -0,0 +1,42 @@ +/*! + * @file Ocean.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +/* + * For more information: http://codebase64.org/doku.php?id=base:crt_file_format + * + * "Memory is divided into 8Kb ($2000) banks. For the lower 128Kb, memory is + * banked into $8000-$9FFF and for the upper 128Kb, memory is banked into + * $A000-$BFFF. Note that the Terminator 2 cartridge loads all 64 banks at + * $8000-$9FFF. + * + * Bank switching is done by writing to $DE00. The lower six bits give the bank + * number (ranging from 0-63). Bit 8 in this selection word is always set." + */ + +void +Ocean::pokeIO1(uint16_t addr, uint8_t value) +{ + if (addr == 0xDE00) { + bankIn(value & 0x3F); + } +} diff --git a/C64/Cartridges/CustomCartridges/Ocean.h b/C64/Cartridges/CustomCartridges/Ocean.h new file mode 100755 index 00000000..babc41b2 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Ocean.h @@ -0,0 +1,36 @@ +/*! + * @header Ocean.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _OCEAN_INC +#define _OCEAN_INC + +#include "Cartridge.h" + +class Ocean : public Cartridge { + +public: + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_OCEAN; } + void pokeIO1(uint16_t addr, uint8_t value); +}; + +#endif + diff --git a/C64/Cartridges/CustomCartridges/Rex.cpp b/C64/Cartridges/CustomCartridges/Rex.cpp new file mode 100755 index 00000000..5b9752c5 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Rex.cpp @@ -0,0 +1,38 @@ +/*! + * @file Rex.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +uint8_t +Rex::peekIO2(uint16_t addr) +{ + // Any read access to $DF00 - $DFBF disables the ROM + if (addr >= 0xDF00 && addr <= 0xDFBF) { + c64->expansionport.setCartridgeMode(CRT_OFF); + } + + // Any read access to $DFC0 - $DFFF switches to 8KB configuration + if (addr >= 0xDFC0 && addr <= 0xDFFF) { + c64->expansionport.setCartridgeMode(CRT_8K); + } + + return 0; +} diff --git a/C64/Cartridges/CustomCartridges/Rex.h b/C64/Cartridges/CustomCartridges/Rex.h new file mode 100755 index 00000000..6d8ddae7 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Rex.h @@ -0,0 +1,38 @@ +/*! + * @header Rex.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _REX_INC +#define _REX_INC + +#include "Cartridge.h" + +class Rex : public Cartridge { + +public: + + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_REX; } + + uint8_t peekIO2(uint16_t addr); + uint8_t spypeekIO2(uint16_t addr) { return 0; } +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/SimonsBasic.cpp b/C64/Cartridges/CustomCartridges/SimonsBasic.cpp new file mode 100755 index 00000000..f6a7d89b --- /dev/null +++ b/C64/Cartridges/CustomCartridges/SimonsBasic.cpp @@ -0,0 +1,52 @@ +/*! + * @file SimonsBasic.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +SimonsBasic::reset() +{ + bankIn(0); + bankIn(1); +} + +uint8_t +SimonsBasic::peekIO1(uint16_t addr) +{ + if (addr == 0xDE00) { + c64->expansionport.setCartridgeMode(CRT_8K); + } + return Cartridge::peekIO1(addr); +} + +uint8_t +SimonsBasic::readIO1(uint16_t addr) +{ + return Cartridge::peekIO1(addr); +} + +void +SimonsBasic::pokeIO1(uint16_t addr, uint8_t value) +{ + if (addr == 0xDE00) { + c64->expansionport.setCartridgeMode(CRT_16K); + } +} diff --git a/C64/Cartridges/CustomCartridges/SimonsBasic.h b/C64/Cartridges/CustomCartridges/SimonsBasic.h new file mode 100755 index 00000000..0ce934e4 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/SimonsBasic.h @@ -0,0 +1,39 @@ +/*! + * @header SimonsBasic.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _SIMONS_BASIC_INC +#define _SIMONS_BASIC_INC + +#include "Cartridge.h" + +class SimonsBasic : public Cartridge { + +public: + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_SIMONS_BASIC; } + void reset(); + uint8_t peekIO1(uint16_t addr); + uint8_t readIO1(uint16_t addr); + void pokeIO1(uint16_t addr, uint8_t value); +}; + +#endif + diff --git a/C64/Cartridges/CustomCartridges/StarDos.cpp b/C64/Cartridges/CustomCartridges/StarDos.cpp new file mode 100755 index 00000000..3fd30fe3 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/StarDos.cpp @@ -0,0 +1,84 @@ +/*! + * @file StarDos.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +StarDos::reset() +{ + Cartridge::reset(); + voltage = 0; + latestVoltageUpdate = 0; +} + +void +StarDos::updateVoltage() +{ + // If the capacitor is untouched, it slowly raises to 2.0V + + if (voltage < 2000000 /* 2.0V */) { + uint64_t elapsedCycles = c64->cpu.cycle - latestVoltageUpdate; + voltage += MIN(2000000 - voltage, elapsedCycles * 2); + } + latestVoltageUpdate = c64->cpu.cycle; +} + +void +StarDos::charge() +{ + updateVoltage(); + voltage += MIN(5000000 /* 5.0V */ - voltage, 78125); + if (voltage > 2700000 /* 2.7V */) { + enableROML(); + } +} + +void +StarDos::discharge() +{ + updateVoltage(); + voltage -= MIN(voltage, 78125); + if (voltage < 1400000 /* 1.4V */) { + disableROML(); + } +} + +void +StarDos::enableROML() +{ + c64->expansionport.setExromLine(0); +} + +void +StarDos::disableROML() +{ + c64->expansionport.setExromLine(1); +} + +void +StarDos::updatePeekPokeLookupTables() +{ + // Replace Kernel by the StarDos kernel + if (c64->mem.peekSrc[0xE] == M_KERNAL) { + c64->mem.peekSrc[0xE] = M_CRTHI; + c64->mem.peekSrc[0xF] = M_CRTHI; + } +} diff --git a/C64/Cartridges/CustomCartridges/StarDos.h b/C64/Cartridges/CustomCartridges/StarDos.h new file mode 100755 index 00000000..550e8537 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/StarDos.h @@ -0,0 +1,55 @@ +/*! + * @header StarDos.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _STARDOS_INC +#define _STARDOS_INC + +#include "Cartridge.h" + +class StarDos : public Cartridge { + + private: + + uint64_t voltage = 5000000; + uint64_t latestVoltageUpdate = 0; + + public: + + StarDos(C64 *c64) : Cartridge(c64, "StarDos") { }; + CartridgeType getCartridgeType() { return CRT_STARDOS; } + void reset(); + + void updateVoltage(); + void charge(); + void discharge(); + void enableROML(); + void disableROML(); + + void updatePeekPokeLookupTables(); + uint8_t peekIO1(uint16_t addr) { charge(); return 0; } + uint8_t peekIO2(uint16_t addr) { discharge(); return 0; } + void pokeIO1(uint16_t addr, uint8_t value) { charge(); } + void pokeIO2(uint16_t addr, uint8_t value) { discharge(); } + + bool hasResetButton() { return true; } +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/SuperGames.cpp b/C64/Cartridges/CustomCartridges/SuperGames.cpp new file mode 100755 index 00000000..fe3b975d --- /dev/null +++ b/C64/Cartridges/CustomCartridges/SuperGames.cpp @@ -0,0 +1,42 @@ +/*! + * @file SuperGames.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +Supergames::pokeIO2(uint16_t addr, uint8_t value) +{ + /* Bits 0, 1: Bank bits 0 and 1 + * Bits 2: Exrom / Game control + * Bits 3: if 1, further writes to DE00 have no effect (not implemented) + */ + + if (addr == 0xDF00) { + + if (value & 0x04) { + c64->expansionport.setCartridgeMode(CRT_8K); + } else { + c64->expansionport.setCartridgeMode(CRT_16K); + } + + bankIn(value & 0x03); + } +} diff --git a/C64/Cartridges/CustomCartridges/SuperGames.h b/C64/Cartridges/CustomCartridges/SuperGames.h new file mode 100755 index 00000000..124f4c07 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/SuperGames.h @@ -0,0 +1,36 @@ +/*! + * @header SuperGames.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _SUPERGAMES_INC +#define _SUPERGAMES_INC + +#include "Cartridge.h" + +class Supergames : public Cartridge { + +public: + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_SUPER_GAMES; } + + void pokeIO2(uint16_t addr, uint8_t value); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/WarpSpeed.cpp b/C64/Cartridges/CustomCartridges/WarpSpeed.cpp new file mode 100755 index 00000000..23d2475c --- /dev/null +++ b/C64/Cartridges/CustomCartridges/WarpSpeed.cpp @@ -0,0 +1,62 @@ +/*! + * @file WarpSpeed.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +WarpSpeed::resetCartConfig() +{ + c64->expansionport.setCartridgeMode(CRT_16K); +} + +uint8_t +WarpSpeed::peekIO1(uint16_t addr) +{ + return Cartridge::peekRomL(0x1E00 | (addr & 0xFF)); +} + +uint8_t +WarpSpeed::peekIO2(uint16_t addr) +{ + return Cartridge::peekRomL(0x1F00 | (addr & 0xFF)); +} + +void +WarpSpeed::pokeIO1(uint16_t addr, uint8_t value) +{ + c64->expansionport.setCartridgeMode(CRT_16K); +} + +void +WarpSpeed::pokeIO2(uint16_t addr, uint8_t value) +{ + c64->expansionport.setCartridgeMode(CRT_OFF); +} + +void +WarpSpeed::pressButton(unsigned nr) +{ + assert(nr <= numButtons()); + + c64->suspend(); + resetWithoutDeletingRam(); + c64->resume(); +} diff --git a/C64/Cartridges/CustomCartridges/WarpSpeed.h b/C64/Cartridges/CustomCartridges/WarpSpeed.h new file mode 100755 index 00000000..2cae446e --- /dev/null +++ b/C64/Cartridges/CustomCartridges/WarpSpeed.h @@ -0,0 +1,49 @@ +/*! + * @header WarpSpeed.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _WARPSPEED_INC +#define _WARPSPEED_INC + +#include "Cartridge.h" + +class WarpSpeed : public Cartridge { + +public: + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_WARPSPEED; } + + // + //! @functiongroup Methods from Cartridge + // + + void resetCartConfig(); + bool hasResetButton() { return true; } + uint8_t peekIO1(uint16_t addr); + uint8_t peekIO2(uint16_t addr); + void pokeIO1(uint16_t addr, uint8_t value); + void pokeIO2(uint16_t addr, uint8_t value); + + unsigned numButtons() { return 1; } + const char *getButtonTitle(unsigned nr) { return (nr == 1) ? "Reset" : NULL; } + void pressButton(unsigned nr); +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/Westermann.cpp b/C64/Cartridges/CustomCartridges/Westermann.cpp new file mode 100755 index 00000000..3f69eba3 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Westermann.cpp @@ -0,0 +1,30 @@ +/*! + * @file Westermann.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +uint8_t +Westermann::peekIO2(uint16_t addr) +{ + // Reading from I/O 2 switched the cartridge on + c64->expansionport.setCartridgeMode(CRT_8K); + return 0; +} diff --git a/C64/Cartridges/CustomCartridges/Westermann.h b/C64/Cartridges/CustomCartridges/Westermann.h new file mode 100755 index 00000000..daf88ec2 --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Westermann.h @@ -0,0 +1,37 @@ +/*! + * @header Westermann.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _WESTERMANN_INC +#define _WESTERMANN_INC + +#include "Cartridge.h" + +class Westermann : public Cartridge { + +public: + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_WESTERMANN; } + + uint8_t peekIO2(uint16_t addr); + uint8_t spypeekIO2(uint16_t addr) { return 0; } +}; + +#endif diff --git a/C64/Cartridges/CustomCartridges/Zaxxon.cpp b/C64/Cartridges/CustomCartridges/Zaxxon.cpp new file mode 100755 index 00000000..f1d712ed --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Zaxxon.cpp @@ -0,0 +1,59 @@ +/*! + * @file Zaxxon.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +Zaxxon::reset() +{ + Cartridge::reset(); + + // Make sure peekRomL() is called for the whole 8KB ROML range. + mappedBytesL = 0x2000; +} + +uint8_t +Zaxxon::peekRomL(uint16_t addr) +{ + /* "The (Super) Zaxxon carts use a 4Kb ($1000) ROM at $8000-$8FFF (mirrored + * in $9000-$9FFF) along with two 8Kb ($2000) cartridge banks located at + * $A000-$BFFF. One of the two banks is selected by doing a read access to + * either the $8000-$8FFF area (bank 0 is selected) or to $9000-$9FFF area + * (bank 1 is selected)." + */ + if (addr < 0x1000) { + bankIn(1); + return Cartridge::peekRomL(addr); + } else { + bankIn(2); + return Cartridge::peekRomL(addr - 0x1000); + } +} + +uint8_t +Zaxxon::spypeekRomL(uint16_t addr) +{ + if (addr < 0x1000) { + return Cartridge::peekRomL(addr); + } else { + return Cartridge::peekRomL(addr - 0x1000); + } +} diff --git a/C64/Cartridges/CustomCartridges/Zaxxon.h b/C64/Cartridges/CustomCartridges/Zaxxon.h new file mode 100755 index 00000000..d5db78cf --- /dev/null +++ b/C64/Cartridges/CustomCartridges/Zaxxon.h @@ -0,0 +1,38 @@ +/*! + * @header Zaxxon.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ZAXXON_INC +#define _ZAXXON_INC + +#include "Cartridge.h" + +class Zaxxon : public Cartridge { + +public: + using Cartridge::Cartridge; + CartridgeType getCartridgeType() { return CRT_ZAXXON; } + + void reset(); + uint8_t peekRomL(uint16_t addr); + uint8_t spypeekRomL(uint16_t addr); +}; + +#endif diff --git a/C64/Cartridges/FlashRom.cpp b/C64/Cartridges/FlashRom.cpp new file mode 100755 index 00000000..3e3ad09f --- /dev/null +++ b/C64/Cartridges/FlashRom.cpp @@ -0,0 +1,327 @@ +/*! + * @file FlashRom.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "FlashRom.h" + +const char * +FlashRom::getStateAsString(FlashRomState state) +{ + switch(state) { + case FLASH_READ:return "FLASH_READ"; + case FLASH_MAGIC_1: return "FLASH_MAGIC_1"; + case FLASH_MAGIC_2: return "FLASH_MAGIC_2"; + case FLASH_AUTOSELECT: return "FLASH_AUTOSELECT"; + case FLASH_BYTE_PROGRAM: return "FLASH_BYTE_PROGRAM"; + case FLASH_BYTE_PROGRAM_ERROR: return "FLASH_BYTE_PROGRAM_ERROR"; + case FLASH_ERASE_MAGIC_1: return "FLASH_ERASE_MAGIC_1"; + case FLASH_ERASE_MAGIC_2: return "FLASH_ERASE_MAGIC_2"; + case FLASH_ERASE_SELECT: return "FLASH_ERASE_SELECT"; + case FLASH_CHIP_ERASE: return "FLASH_CHIP_ERASE"; + case FLASH_SECTOR_ERASE: return "FLASH_SECTOR_ERASE"; + case FLASH_SECTOR_ERASE_TIMEOUT: return "FLASH_SECTOR_ERASE_TIMEOUT"; + case FLASH_SECTOR_ERASE_SUSPEND: return "FLASH_SECTOR_ERASE_SUSPEND"; + default: + assert(false); + } +} + +FlashRom::FlashRom() +{ + setDescription("FlashRom"); + debug(3, " Creating FlashRom at address %p...\n", this); + + state = FLASH_READ; + baseState = FLASH_READ; + + numSectors = 8; + sectorSize = 0x10000; // 64 KB + size = 0x80000; // 512 KB + + rom = new uint8_t[size]; + memset(rom, 0xFF, size); + + // Register snapshot items + SnapshotItem items[] = { + { &state, sizeof(state), KEEP_ON_RESET }, + { &baseState, sizeof(baseState), KEEP_ON_RESET }, + { rom, size, KEEP_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +FlashRom::~FlashRom() +{ + debug(3, " Releasing FlashRom ...\n"); + delete[] rom; +} + +void +FlashRom::loadBank(unsigned bank, uint8_t *data) +{ + assert(data != NULL); + memcpy(rom + bank * 0x2000, data, 0x2000); +} + +void +FlashRom::reset() +{ + debug("Resetting\n"); + state = FLASH_READ; + baseState = FLASH_READ; +} + +void +FlashRom::dump() +{ + msg("FlashRom\n"); + msg("--------\n\n"); + + msg(" state: %d\n", state); + msg(" baseState: %d\n", baseState); + msg("numSectors: %d\n", numSectors); + msg("sectorSize: %d\n", sectorSize); + msg(" rom: %p\n\n", rom); +} + +uint8_t +FlashRom::peek(uint32_t addr) +{ + assert(addr < size); + + uint8_t result; + + switch (state) { + + case FLASH_AUTOSELECT: + + switch(addr & 0xFF) { + + case 0: + return 0x01; // Manufacturer ID + + case 1: + return 0xA4; // Device ID + + case 2: + return 0; + } + return rom[addr]; + + case FLASH_BYTE_PROGRAM_ERROR: + + // TODO + result = rom[addr]; + break; + + case FLASH_SECTOR_ERASE_SUSPEND: + + // TODO + result = rom[addr]; + break; + + case FLASH_CHIP_ERASE: + + // TODO + result = rom[addr]; + break; + + case FLASH_SECTOR_ERASE: + + // TODO + result = rom[addr]; + break; + + case FLASH_SECTOR_ERASE_TIMEOUT: + + // TODO + result = rom[addr]; + break; + + default: + + // TODO + result = rom[addr]; + break; + } + + return result; +} + +void +FlashRom::poke(uint32_t addr, uint8_t value) +{ + assert(addr < size); + + switch (state) { + + case FLASH_READ: + + if (firstCommandAddr(addr) && value == 0xAA) { + + state = FLASH_MAGIC_1; + debug("%s\n", getStateAsString(state)); + return; + } + + return; + + case FLASH_MAGIC_1: + + if (secondCommandAddr(addr) && value == 0x55) { + + state = FLASH_MAGIC_2; + debug("%s\n", getStateAsString(state)); + return; + } + + state = baseState; + debug("Back to %s\n", getStateAsString(state)); + return; + + case FLASH_MAGIC_2: + + if (firstCommandAddr(addr)) { + + switch(value) { + + case 0xF0: + + state = FLASH_READ; + baseState = FLASH_READ; + debug("%s\n", getStateAsString(state)); + return; + + case 0x90: + + state = FLASH_AUTOSELECT; + baseState = FLASH_AUTOSELECT; + debug("%s\n", getStateAsString(state)); + return; + + case 0xA0: + state = FLASH_BYTE_PROGRAM; + debug("%s\n", getStateAsString(state)); + return; + + case 0x80: + state = FLASH_ERASE_MAGIC_1; + debug("%s\n", getStateAsString(state)); + return; + } + } + + state = baseState; + debug("Back to %s\n", getStateAsString(state)); + break; + + case FLASH_BYTE_PROGRAM: + + if (!doByteProgram(addr, value)) { + + state = FLASH_BYTE_PROGRAM_ERROR; + debug("%s\n", getStateAsString(state)); + return; + } + + state = baseState; + debug("Back to %s\n", getStateAsString(state)); + return; + + case FLASH_ERASE_MAGIC_1: + + // TODO + break; + + case FLASH_ERASE_MAGIC_2: + + // TODO + break; + + case FLASH_ERASE_SELECT: + + // TODO + break; + + case FLASH_SECTOR_ERASE_TIMEOUT: + + // TODO + break; + + case FLASH_SECTOR_ERASE: + + // TODO + break; + + case FLASH_SECTOR_ERASE_SUSPEND: + + // TODO + break; + + case FLASH_BYTE_PROGRAM_ERROR: + case FLASH_AUTOSELECT: + + if (addr == 0x5555 && value == 0xAA) { + + state = FLASH_MAGIC_1; + debug("%s\n", getStateAsString(state)); + return; + } + if (value == 0xF0) { + + state = FLASH_READ; + baseState = FLASH_READ; + debug("%s\n", getStateAsString(state)); + return; + } + return; + + case FLASH_CHIP_ERASE: + default: + + // TODO + break; + } +} + +bool +FlashRom::doByteProgram(uint32_t addr, uint8_t value) +{ + assert(addr < size); + + rom[addr] &= value; + return rom[addr] == value; +} + +void +FlashRom::doChipErase() { + + debug("Erasing chip ...\n"); + memset(rom, 0xFF, size); +} + +void +FlashRom::doSectorErase(uint32_t addr) +{ + assert(addr < size); + + debug("Erasing sector %d ... %d\n", addr >> 4); + memset(rom + (addr & 0x0000), 0xFF, sectorSize); +} diff --git a/C64/Cartridges/FlashRom.h b/C64/Cartridges/FlashRom.h new file mode 100755 index 00000000..9f5faafe --- /dev/null +++ b/C64/Cartridges/FlashRom.h @@ -0,0 +1,162 @@ +/*! + * @header FlashRom.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _FLASHROM_INC +#define _FLASHROM_INC + +#include "VirtualComponent.h" + +/*! @brief This class implements a Flash Rom module of type Am29F040B + * @details Flash Rom modules of this type are used, e.g., by the EasyFlash + * cartridge. + * The implementation is based on the following ressources: + * 29F040.pdf: Data sheet published by AMD + * flash040core.c: Part of the VICE emulator + */ +class FlashRom : public VirtualComponent { + + private: + + //! @brief Flash Rom states (taken from VICE) + typedef enum { + FLASH_READ = 0, + FLASH_MAGIC_1, + FLASH_MAGIC_2, + FLASH_AUTOSELECT, + FLASH_BYTE_PROGRAM, + FLASH_BYTE_PROGRAM_ERROR, + FLASH_ERASE_MAGIC_1, + FLASH_ERASE_MAGIC_2, + FLASH_ERASE_SELECT, + FLASH_CHIP_ERASE, + FLASH_SECTOR_ERASE, + FLASH_SECTOR_ERASE_TIMEOUT, + FLASH_SECTOR_ERASE_SUSPEND + } FlashRomState; + + //! @brief Current Flash Rom state + FlashRomState state; + + //! @brief State taken after an operations has been completed. + FlashRomState baseState; + + //! @brief Number of sectors in this Flash Rom + size_t numSectors; + + //! @brief Size of a single sector in bytes + size_t sectorSize; // 64 KB + + //! @brief Total size of the Flash Rom in bytes + size_t size; // 512 KB + + //! @brief Flash Rom data + uint8_t *rom; + + public: + + // + //! @brief Class methods + // + + //! @brief Validity check for bank numbers + static bool isBankNumber(unsigned bank) { return bank < 64; } + + //! @brief Converts a Flash Rom state to a string + static const char *getStateAsString(FlashRomState state); + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Constructor + FlashRom(); + + //! @brief Destructor + ~FlashRom(); + + /*! @brief Loads an 8 KB chunk of Rom data from a buffer. + * @details This method is used when loading the contents from a CRT file. + */ + void loadBank(unsigned bank, uint8_t *data); + + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void dump(); + + // + //! @functiongroup Accessing Rom cells + // + + //! @brief Reads a Rom cell + uint8_t peek(uint32_t addr); + + //! @brief Convenience wrapper with bank,offset addressing + uint8_t peek(unsigned bank, uint16_t addr) { + assert(isBankNumber(bank)); return peek(bank * 0x2000 + addr); } + + //! @brief Reads a Rom cell without side effects + uint8_t spypeek(uint32_t addr) { return peek(addr); } + + //! @brief Convenience wrapper with bank,offset addressing + uint8_t spypeek(unsigned bank, uint16_t addr) { + assert(isBankNumber(bank)); return peek(bank * 0x2000 + addr); } + + //! @brief Writes a Rom cell + void poke(uint32_t addr, uint8_t value); + + //! @brief Convenience wrapper with bank,offset addressing + void poke(unsigned bank, uint16_t addr, uint8_t value) { + assert(isBankNumber(bank)); poke(bank * 0x2000 + addr, value); } + + + // + //! @functiongroup Performing flash operations + // + + //! @brief Checks if addr serves as the first command address. + bool firstCommandAddr(uint32_t addr) { return (addr & 0x7FF) == 0x555; } + + //! @brief Checks if addr serves as the second command address. + bool secondCommandAddr(uint32_t addr) { return (addr & 0x7FF) == 0x2AA; } + + //! @brief Performs a "Byte Program" operation + bool doByteProgram(uint32_t addr, uint8_t value); + + //! @brief Convenience wrapper with bank,offset addressing + bool doByteProgram(unsigned bank, uint16_t addr, uint8_t value) { + assert(isBankNumber(bank)); return doByteProgram(bank * 0x2000 + addr, value); } + + //! @brief Performs a "Sector Erase" operation + void doSectorErase(uint32_t addr); + + //! @brief Convenience wrapper with bank,offset addressing + void doSectorErase(unsigned bank, uint16_t addr) { + assert(isBankNumber(bank)); doSectorErase(bank * 0x2000 + addr); } + + //! @brief Performs a "Chip Erase" operation + void doChipErase(); +}; + +#endif diff --git a/C64/Computer/ControlPort.cpp b/C64/Computer/ControlPort.cpp new file mode 100755 index 00000000..ceadcace --- /dev/null +++ b/C64/Computer/ControlPort.cpp @@ -0,0 +1,195 @@ +/*! + * @header ControlPort.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright 2018 Dirk W. Hoffmann + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +ControlPort::ControlPort(int portNr) +{ + assert(portNr == 1 || portNr == 2); + + nr = portNr; + autofire = false; + autofireBullets = -3; + autofireFrequency = 2.5; + bulletCounter = 0; + nextAutofireFrame = 0; + + setDescription("ControlPort"); + debug(3, " Creating ControlPort %d at address %p...\n", nr, this); + + // Register snapshot items + /* + SnapshotItem items[] = { + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); + */ +} + +ControlPort::~ControlPort() +{ +} + +void +ControlPort::reset() +{ + VirtualComponent::reset(); + + button = false; + axisX = 0; + axisY = 0; +} + +void +ControlPort::didLoadFromBuffer(uint8_t **buffer) +{ + // Discard any active joystick movements + button = false; + axisX = 0; + axisY = 0; +} + +void +ControlPort::dump() +{ + msg("ControlPort port %d\n", nr); + msg("------------------\n"); + msg("Button: %s AxisX: %d AxisY: %d\n", button ? "YES" : "NO", axisX, axisY); + msg("Bitmask: %02X\n", bitmask()); +} + +void +ControlPort::scheduleNextShot() +{ + nextAutofireFrame = c64->frame + + (int)(c64->vic.getFramesPerSecond() / (2 * autofireFrequency)); +} + +void +ControlPort::execute() +{ + if (!autofire || autofireFrequency <= 0.0) + return; + + // Wait until it's time to push or release fire + if (c64->frame != nextAutofireFrame) + return; + + // Are there any bullets left? + if (bulletCounter) { + if (button) { + button = false; + bulletCounter--; + } else { + button = true; + } + scheduleNextShot(); + } +} + +void +ControlPort::trigger(JoystickEvent event) +{ + switch (event) { + + case PULL_UP: + axisY = -1; + break; + case PULL_DOWN: + axisY = 1; + break; + case PULL_LEFT: + axisX = -1; + break; + case PULL_RIGHT: + axisX = 1; + break; + case PRESS_FIRE: + + if (autofire) { + if (bulletCounter) { + // Cease fire + bulletCounter = 0; + button = false; + } else { + // Load magazine + bulletCounter = (autofireBullets < 0) ? UINT64_MAX : autofireBullets; + button = true; + scheduleNextShot(); + } + } else { + button = true; + } + break; + case RELEASE_X: + axisX = 0; + break; + case RELEASE_Y: + axisY = 0; + break; + case RELEASE_XY: + axisX = 0; + axisY = 0; + break; + case RELEASE_FIRE: + if (!autofire) + button = false; + break; + + default: + assert(0); + } +} + +void +ControlPort::setAutofire(bool value) +{ + if (!(autofire = value)) { + button = false; + } +} + +void +ControlPort::setAutofireBullets(int value) +{ + autofireBullets = value; + if (bulletCounter > 0) { + bulletCounter = (autofireBullets < 0) ? UINT64_MAX : autofireBullets; + } +} + +uint8_t +ControlPort::bitmask() { + + uint8_t result = 0xFF; + + if (axisY == -1) CLR_BIT(result, 0); + if (axisY == 1) CLR_BIT(result, 1); + if (axisX == -1) CLR_BIT(result, 2); + if (axisX == 1) CLR_BIT(result, 3); + if (button) CLR_BIT(result, 4); + + uint8_t mouseBits = c64->mouse.readControlPort(nr); + result &= mouseBits; + + return result; +} + + diff --git a/C64/Computer/ControlPort.h b/C64/Computer/ControlPort.h new file mode 100755 index 00000000..6d882ba7 --- /dev/null +++ b/C64/Computer/ControlPort.h @@ -0,0 +1,126 @@ +/*! + * @header ControlPort.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CONTROLPORT_H +#define CONTROLPORT_H + +#include "VirtualComponent.h" +#include "ControlPort_types.h" + +class ControlPort : public VirtualComponent { + +private: + + //! @brief Represented control port (1 or 2) + int nr; + + //! @brief True, if button is pressed. + bool button; + + /*! @brief Horizontal joystick position + * @details Valid valued are -1 (LEFT), 1 (RIGHT), or 0 (RELEASED) + */ + int axisX; + + /*! @brief Vertical joystick position + * @details Valid valued are -1 (UP), 1 (DOWN), or 0 (RELEASED) + */ + int axisY; + + //! @brief True if multi-shot mode in enabled + bool autofire; + + //! @brief Number of bullets per gun volley + int autofireBullets; + + //! @brief Autofire frequency in Hz + float autofireFrequency; + + //! @brief Bullet counter used in multi-fire mode + uint64_t bulletCounter; + + //! @brief Next frame to auto-press or auto-release the fire button + uint64_t nextAutofireFrame; + +public: + + //! @brief Constructor + ControlPort(int p); + + //! @brief Destructor + ~ControlPort(); + + //! @brief Method from VirtualComponent + void reset(); + + //! @brief Method from VirtualComponent + void didLoadFromBuffer(uint8_t **buffer); + + //! @brief Method from VirtualComponent + void dump(); + + //! @brief Returns true if auto-fire mode is enabled. + bool getAutofire() { return autofire; } + + //! @brief Enables or disables autofire. + void setAutofire(bool value); + + /*! @brief Returns the number of bullets per gun volley. + * @details A negative value represents infinity. + */ + int getAutofireBullets() { return autofireBullets; } + + /*! @brief Sets the number of bullets per gun volley. + * @details A negative value represents infinity. + */ + void setAutofireBullets(int value); + + //! @brief Returns the autofire frequency. + float getAutofireFrequency() { return autofireFrequency; } + + //! @brief Sets the autofire frequency. + void setAutofireFrequency(float value) { autofireFrequency = value; } + + //! @brief Updates variable nextAutofireFrame + void scheduleNextShot(); + + //! @brief Execution function for this control port + /*! @details This method is invoked at the end of each frame. It is needed + * needed to implement the autofire functionality, only. + */ + void execute(); + + //! @brief Triggers a joystick event + void trigger(JoystickEvent event); + + /*! @brief Returns the current joystick movement in form a bit mask + * @details The bits are in the same order as they show up in the + * CIA's data port registers + */ + uint8_t bitmask(); + + //! @brief Returns the potentiometer X value (analog mouse) + uint8_t potX(); + + //! @brief Returns the potentiometer Y value (analog mouse) + uint8_t potY(); +}; + +#endif diff --git a/C64/Computer/ControlPort_types.h b/C64/Computer/ControlPort_types.h new file mode 100755 index 00000000..ce088688 --- /dev/null +++ b/C64/Computer/ControlPort_types.h @@ -0,0 +1,52 @@ +/*! + * @header ControlPort_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CONTROLPORT_TYPES_H +#define CONTROLPORT_TYPES_H + +/*! @brief Joystick directions + */ +typedef enum { + + JOYSTICK_UP, + JOYSTICK_DOWN, + JOYSTICK_LEFT, + JOYSTICK_RIGHT, + JOYSTICK_FIRE + +} JoystickDirection; + +/*! @brief Joystick events + */ +typedef enum { + + PULL_UP, + PULL_DOWN, + PULL_LEFT, + PULL_RIGHT, + PRESS_FIRE, + RELEASE_X, + RELEASE_Y, + RELEASE_XY, + RELEASE_FIRE + +} JoystickEvent; + +#endif diff --git a/C64/Computer/ExpansionPort.cpp b/C64/Computer/ExpansionPort.cpp new file mode 100755 index 00000000..3471d4a7 --- /dev/null +++ b/C64/Computer/ExpansionPort.cpp @@ -0,0 +1,355 @@ +/* + * Written by Dirk Hoffmann based on the original code by A. Carl Douglas. + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +ExpansionPort::ExpansionPort() +{ + setDescription("Expansion port"); + debug(3, " Creating expansion port at address %p...\n", this); + + // Register snapshot items + SnapshotItem items[] = { + + // Internal state + { &gameLine, sizeof(gameLine), KEEP_ON_RESET }, + { &exromLine, sizeof(exromLine), KEEP_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +ExpansionPort::~ExpansionPort() +{ + debug(3, " Releasing expansion port...\n"); + detachCartridge(); +} + +void +ExpansionPort::reset() +{ + VirtualComponent::reset(); + + if (cartridge) { + cartridge->reset(); + cartridge->resetCartConfig(); + } else { + setCartridgeMode(CRT_OFF); + } +} + +void +ExpansionPort::ping() +{ + VirtualComponent::ping(); + c64->putMessage(cartridge ? MSG_CARTRIDGE : MSG_NO_CARTRIDGE); + c64->putMessage(MSG_CART_SWITCH); +} + +size_t +ExpansionPort::stateSize() +{ + return VirtualComponent::stateSize() + + 2 + + (cartridge ? cartridge->stateSize() : 0); +} + +void +ExpansionPort::didLoadFromBuffer(uint8_t **buffer) +{ + // Delete old cartridge (if any) + if (cartridge != NULL) { + delete cartridge; + cartridge = NULL; + } + + // Read cartridge type and cartridge (if any) + CartridgeType cartridgeType = (CartridgeType)read16(buffer); + if (cartridgeType != CRT_NONE) { + cartridge = Cartridge::makeWithType(c64, cartridgeType); + cartridge->loadFromBuffer(buffer); + } +} + +void +ExpansionPort::didSaveToBuffer(uint8_t **buffer) +{ + // Write cartridge type and data (if any) + write16(buffer, cartridge ? cartridge->getCartridgeType() : CRT_NONE); + if (cartridge != NULL) + cartridge->saveToBuffer(buffer); +} + +void +ExpansionPort::dump() +{ + msg("Expansion port\n"); + msg("--------------\n"); + + msg(" Game line: %d\n", gameLine); + msg("Exrom line: %d\n", exromLine); + + if (cartridge == NULL) { + msg("No cartridge attached\n"); + } else { + cartridge->dump(); + } +} + +CartridgeType +ExpansionPort::getCartridgeType() +{ + return cartridge ? cartridge->getCartridgeType() : CRT_NONE; +} + +uint8_t +ExpansionPort::peek(uint16_t addr) +{ + return cartridge ? cartridge->peek(addr) : 0; +} + +uint8_t +ExpansionPort::spypeek(uint16_t addr) +{ + return cartridge ? cartridge->spypeek(addr) : 0; +} + +/* +uint8_t +ExpansionPort::peek(uint16_t addr) +{ + assert((addr >= 0x8000 && addr <= 0x9FFF) || + (addr >= 0xA000 && addr <= 0xBFFF) || + (addr >= 0xE000 && addr <= 0xFFFF)); + + if (cartridge) { + if (addr <= 0x9FFF) { + return cartridge->peekRomLabs(addr); + } else { + return cartridge->peekRomHabs(addr); + } + } + return 0; +} +*/ + +uint8_t +ExpansionPort::peekIO1(uint16_t addr) +{ + /* "Die beiden mit "I/O 1" und "I/O 2" bezeichneten Bereiche + * sind für Erweiterungskarten reserviert und normalerweise ebenfalls offen, + * ein Lesezugriff liefert auch hier "zufällige" Daten (dass diese Daten gar + * nicht so zufällig sind, wird in Kapitel 4 noch ausführlich erklärt. Ein + * Lesen von offenen Adressen liefert nämlich auf vielen C64 das zuletzt vom + * VIC gelesene Byte zurück!)" [C.B.] + */ + return cartridge ? cartridge->peekIO1(addr) : c64->vic.getDataBusPhi1(); +} + +uint8_t +ExpansionPort::spypeekIO1(uint16_t addr) +{ + return cartridge ? cartridge->spypeekIO1(addr) : c64->vic.getDataBusPhi1(); +} + +uint8_t +ExpansionPort::peekIO2(uint16_t addr) +{ + return cartridge ? cartridge->peekIO2(addr) : c64->vic.getDataBusPhi1(); +} + +uint8_t +ExpansionPort::spypeekIO2(uint16_t addr) +{ + return cartridge ? cartridge->spypeekIO2(addr) : c64->vic.getDataBusPhi1(); +} + +void +ExpansionPort::poke(uint16_t addr, uint8_t value) +{ + if (cartridge) { + cartridge->poke(addr, value); + } else if (!c64->getUltimax()) { + c64->mem.ram[addr] = value; + } +} + +void +ExpansionPort::pokeIO1(uint16_t addr, uint8_t value) +{ + assert(addr >= 0xDE00 && addr <= 0xDEFF); + + if (cartridge) cartridge->pokeIO1(addr, value); +} + +void +ExpansionPort::pokeIO2(uint16_t addr, uint8_t value) +{ + assert(addr >= 0xDF00 && addr <= 0xDFFF); + + if (cartridge) cartridge->pokeIO2(addr, value); +} + +void +ExpansionPort::setGameLine(bool value) +{ + gameLine = value; + c64->vic.setUltimax(!gameLine && exromLine); + c64->mem.updatePeekPokeLookupTables(); +} + +void +ExpansionPort::setExromLine(bool value) +{ + exromLine = value; + c64->vic.setUltimax(!gameLine && exromLine); + c64->mem.updatePeekPokeLookupTables(); +} + +void +ExpansionPort::setGameAndExrom(bool game, bool exrom) +{ + gameLine = game; + exromLine = exrom; + c64->vic.setUltimax(!gameLine && exromLine); + c64->mem.updatePeekPokeLookupTables(); +} + +CartridgeMode +ExpansionPort::getCartridgeMode() +{ + switch ((exromLine ? 0b10 : 0) | (gameLine ? 0b01 : 0)) { + + case 0b00: return CRT_16K; + case 0b01: return CRT_8K; + case 0b10: return CRT_ULTIMAX; + default: return CRT_OFF; + } +} + +void +ExpansionPort::setCartridgeMode(CartridgeMode mode) +{ + switch (mode) { + case CRT_16K: setGameAndExrom(0,0); return; + case CRT_8K: setGameAndExrom(1,0); return; + case CRT_ULTIMAX: setGameAndExrom(0,1); return; + default: setGameAndExrom(1,1); + } +} + +void +ExpansionPort::updatePeekPokeLookupTables() +{ + if (cartridge) cartridge->updatePeekPokeLookupTables(); +} + +void +ExpansionPort::attachCartridge(Cartridge *c) +{ + assert(c != NULL); + + // Remove old cartridge (if any) and assign new one + detachCartridge(); + cartridge = c; + + // Reset cartridge to update exrom and game line on the expansion port + cartridge->reset(); + + c64->putMessage(MSG_CARTRIDGE); + if (cartridge->hasSwitch()) c64->putMessage(MSG_CART_SWITCH); + + debug(1, "Cartridge attached to expansion port"); + cartridge->dump(); +} + +bool +ExpansionPort::attachCartridgeAndReset(CRTFile *file) +{ + assert(file != NULL); + + Cartridge *cartridge = Cartridge::makeWithCRTFile(c64, file); + + if (cartridge) { + + suspend(); + attachCartridge(cartridge); + c64->reset(); + resume(); + return true; + } + + return false; +} + +bool +ExpansionPort::attachGeoRamCartridge(uint32_t capacity) +{ + switch (capacity) { + case 64: case 128: case 256: case 512: case 1024: case 2048: case 4096: + break; + default: + warn("Cannot create GeoRAM cartridge of size %d\n", capacity); + return false; + } + + Cartridge *geoRAM = Cartridge::makeWithType(c64, CRT_GEO_RAM); + uint32_t capacityInBytes = capacity * 1024; + geoRAM->setRamCapacity(capacityInBytes); + debug("Created GeoRAM cartridge (%d KB)\n", capacity); + + attachCartridge(geoRAM); + return true; +} + +void +ExpansionPort::attachIsepicCartridge() +{ + debug("Creating Isepic cartridge\n"); + + Cartridge *isepic = Cartridge::makeWithType(c64, CRT_ISEPIC); + return attachCartridge(isepic); +} + +void +ExpansionPort::detachCartridge() +{ + if (cartridge) { + + suspend(); + + delete cartridge; + cartridge = NULL; + + setCartridgeMode(CRT_OFF); + + debug(1, "Cartridge detached from expansion port"); + c64->putMessage(MSG_NO_CARTRIDGE); + + resume(); + } +} + +void +ExpansionPort::detachCartridgeAndReset() +{ + suspend(); + detachCartridge(); + c64->reset(); + resume(); +} diff --git a/C64/Computer/ExpansionPort.h b/C64/Computer/ExpansionPort.h new file mode 100755 index 00000000..b242a312 --- /dev/null +++ b/C64/Computer/ExpansionPort.h @@ -0,0 +1,245 @@ +/*! + * @header ExpansionPort.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * For more information: http://www.c64-wiki.com/index.php/Cartridge + * + * "The cartridge system implemented in the C64 provides an easy way to + * hook 8 or 16 kilobytes of ROM into the computer's address space: + * This allows for applications and games up to 16 K, or BASIC expansions + * up to 8 K in size and appearing to the CPU along with the built-in + * BASIC ROM. In theory, such a cartridge need only contain the + * ROM circuit without any extra support electronics." + * + * Bank switching info: http://www.c64-wiki.com/index.php/Bankswitching + * http://www.harries.dk/files/C64MemoryMaps.pdf + * + * As well read the Commodore 64 Programmers Reference Guide pages 260-267. + */ + +#ifndef _EXPANSIONPORT_H +#define _EXPANSIONPORT_H + +#include "Cartridge.h" +#include "ExpansionPort_types.h" + +class ExpansionPort : public VirtualComponent { + +private: + + /*! @brief Attached cartridge + * @details NULL, if no cartridge is plugged in. + */ + Cartridge *cartridge = NULL; + + /*! @brief Current value of the Game line. + * @details Equals 1, if no cartridge if attached. + */ + bool gameLine = 1; + + /*! @brief Current value of the Exrom line. + * @details Equals 1, if no cartridge if attached. + */ + bool exromLine = 1; + +public: + + //! @brief Constructor + ExpansionPort(); + + //! @brief Destructor + ~ExpansionPort(); + + //! @brief Method from VirtualComponent + void reset(); + + //! @brief Method from VirtualComponent + void ping(); + + //! @brief Method from VirtualComponent + size_t stateSize(); + + //! @brief Method from VirtualComponent + void didLoadFromBuffer(uint8_t **buffer); + + //! @brief Method from VirtualComponent + void didSaveToBuffer(uint8_t **buffer); + + //! @brief Method from VirtualComponent + void dump(); + + //! @brief Execution thread callback + /*! @details This method is invoked after each rasterline. + */ + void execute() { if (cartridge) cartridge->execute(); } + + //! @brief Peek fallthrough + uint8_t peek(uint16_t addr); + + //! @brief Same as peek, but without side effects + uint8_t spypeek(uint16_t addr); + + //! @brief Peek fallthrough for I/O space 1 + uint8_t peekIO1(uint16_t addr); + + //! @brief Same as peekIO1, but without side effects + uint8_t spypeekIO1(uint16_t addr); + + //! @brief Peek fallthrough for I/O space 2 + uint8_t peekIO2(uint16_t addr); + + //! @brief Same as peekIO2, but without side effects + uint8_t spypeekIO2(uint16_t addr); + + //! @brief Poke fallthrough + void poke(uint16_t addr, uint8_t value); + + //! @brief Poke fallthrough for I/O space 1 + void pokeIO1(uint16_t addr, uint8_t value); + + //! @brief Poke fallthrough for I/O space 2 + void pokeIO2(uint16_t addr, uint8_t value); + + //! @brief Returns the cartridge type + CartridgeType getCartridgeType(); + + //! @brief Returns the state of the Game line + bool getGameLine() { return gameLine; } + + /*! @brief Sets the state of the Game line + * @note This value affects the C64's and VICII's mem source table. + */ + void setGameLine(bool value); + + //! @brief Returns the state of the Exrom line + bool getExromLine() { return exromLine; } + + /*! @brief Sets the state of the Exrom line + * @note This value affects the C64's and VICII's mem source table. + */ + void setExromLine(bool value); + + /*! @brief Returns the current cartridge mode. + * @details The cartridge mode is determined by the current values of the + * Game and Exrom line. + */ + CartridgeMode getCartridgeMode(); + + //! @brief Sets the state of the Game and Exrom line + void setGameAndExrom(bool game, bool exrom); + + //! @brief Convenience wrapper for setGame(), setExrom() + void setCartridgeMode(CartridgeMode mode); + + + /*! @brief Modifies the memory source lookup tables if required + * @details This function is called in C64::updatePeekPokeLookupTables() + * to allow cartridges to manipulate the lookup tables after the + * default values have been set. + * Background: Some cartridges such as StarDos change the game + * and exrom line on-the-fly to achieve very special memory + * mappings. + * For most cartridges, this function does nothing. + */ + void updatePeekPokeLookupTables(); + + //! @brief Returns true if a cartridge is attached to the expansion port + bool getCartridgeAttached() { return cartridge != NULL; } + + //! @brief Attaches a cartridge to the expansion port. + void attachCartridge(Cartridge *c); + + //! @brief Attaches a cartridge from a file and resets. + bool attachCartridgeAndReset(CRTFile *c); + + //! @brief Creates and attaches a GeoRAM cartridge + bool attachGeoRamCartridge(uint32_t capacity); + + //! @brief Creates and attaches an Isepic cartridge + void attachIsepicCartridge(); + + //! @brief Removes a cartridge from the expansion port (if any) + void detachCartridge(); + + //! @brief Removes a cartridge from the expansion port and resets + void detachCartridgeAndReset(); + + // + //! @functiongroup Operating cartridge buttons + // + + //! @brief Returns the number of available cartridge buttons + virtual unsigned numButtons() { return cartridge ? cartridge->numButtons() : 0; } + + //! @brief Returns a textual description for a button. + virtual const char *getButtonTitle(unsigned nr) { + return cartridge ? cartridge->getButtonTitle(nr) : NULL; } + + //! @brief Presses a button + virtual void pressButton(unsigned nr) { debug("Pressing %d\n", nr); if (cartridge) cartridge->pressButton(nr); } + + //! @brief Releases a button + virtual void releaseButton(unsigned nr) { debug("Releasing %d\n", nr); if (cartridge) cartridge->releaseButton(nr); } + + //! @brief Returns true if a cartridge with a switch is attached + bool hasSwitch() { return cartridge ? cartridge->hasSwitch() : false; } + + //! @brief Returns the current position of the switch + int8_t getSwitch() { return cartridge ? cartridge->getSwitch() : 0; } + bool switchIsNeutral() { return cartridge ? cartridge->switchIsNeutral() : false; } + bool switchIsLeft() { return cartridge ? cartridge->switchIsLeft() : false; } + bool switchIsRight() { return cartridge ? cartridge->switchIsRight() : false; } + const char *getSwitchDescription(int8_t pos) { + return cartridge ? cartridge->getSwitchDescription(pos) : NULL; } + bool validSwitchPosition(int8_t pos) { + return cartridge ? cartridge->validSwitchPosition(pos) : false; } + + //! @brief Puts the switch in the provided position + void setSwitch(uint8_t pos) { if (cartridge) cartridge->setSwitch(pos); } + + //! @brief Returns true if the cartridge has a LED. + bool hasLED() { return cartridge ? cartridge->hasLED() : false; } + + //! @brief Returns true if the LED is switched on. + bool getLED() { return cartridge ? cartridge->getLED() : false; } + + //! @brief Switches the LED on or off. + void setLED(bool value) { if (cartridge) cartridge->setLED(value); } + + //! @brief Returns true if the attached cartridge has a RAM backing battery. + bool hasBattery() { return cartridge ? cartridge->getPersistentRam() : false; } + + //! @brief Enables or disables RAM backing during a reset. + void setBattery(bool value) { if (cartridge) cartridge->setPersistentRam(value); } + + + // + // Notifications + // + + //! @brief Called when the C64 CPU is about to trigger an NMI + void nmiWillTrigger() { if (cartridge) cartridge->nmiWillTrigger(); } + + //! @brief Called after the C64 CPU has processed the NMI instruction + void nmiDidTrigger() { if (cartridge) cartridge->nmiDidTrigger(); } + +}; + +#endif diff --git a/C64/Computer/ExpansionPort_types.h b/C64/Computer/ExpansionPort_types.h new file mode 100755 index 00000000..ce29f44d --- /dev/null +++ b/C64/Computer/ExpansionPort_types.h @@ -0,0 +1,35 @@ +/*! + * @header ExpansionPort_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef EXPANSIONPORT_TYPES_H +#define EXPANSIONPORT_TYPES_H + +/*! @brief Cartridge mode + */ +typedef enum { + + CRT_16K, + CRT_8K, + CRT_ULTIMAX, + CRT_OFF + +} CartridgeMode; + +#endif diff --git a/C64/Computer/IEC.cpp b/C64/Computer/IEC.cpp new file mode 100755 index 00000000..3eccb0c3 --- /dev/null +++ b/C64/Computer/IEC.cpp @@ -0,0 +1,235 @@ +/* + * (C) 2006 - 2018 Dirk W. Hoffmann. All rights reserved. + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +IEC::IEC() +{ + setDescription("IEC"); + debug(3, " Creating IEC bus at address %p...\n", this); + + // Register snapshot items + SnapshotItem items[] = { + + // { &driveIsConnected, sizeof(driveIsConnected), CLEAR_ON_RESET }, + { &atnLine, sizeof(atnLine), CLEAR_ON_RESET }, + { &clockLine, sizeof(clockLine), CLEAR_ON_RESET }, + { &dataLine, sizeof(dataLine), CLEAR_ON_RESET }, + { &isDirtyC64Side, sizeof(isDirtyC64Side), CLEAR_ON_RESET }, + { &isDirtyDriveSide, sizeof(isDirtyDriveSide), CLEAR_ON_RESET }, + + { &device1Atn, sizeof(device1Atn), CLEAR_ON_RESET }, + { &device1Clock, sizeof(device1Clock), CLEAR_ON_RESET }, + { &device1Data, sizeof(device1Data), CLEAR_ON_RESET }, + + { &device2Atn, sizeof(device2Atn), CLEAR_ON_RESET }, + { &device2Clock, sizeof(device2Clock), CLEAR_ON_RESET }, + { &device2Data, sizeof(device2Data), CLEAR_ON_RESET }, + + { &ciaAtn, sizeof(ciaAtn), CLEAR_ON_RESET }, + { &ciaClock, sizeof(ciaClock), CLEAR_ON_RESET }, + { &ciaData, sizeof(ciaData), CLEAR_ON_RESET }, + + { &busActivity, sizeof(busActivity), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +IEC::~IEC() +{ + debug(3, " Releasing IEC bus...\n"); +} + +void +IEC::reset() +{ + VirtualComponent::reset(); + + atnLine = 1; + clockLine = 1; + dataLine = 1; + + device1Atn = 1; + device1Clock = 1; + device1Data = 1; + + device2Atn = 1; + device2Clock = 1; + device2Data = 1; + + ciaAtn = 1; + ciaClock = 1; + ciaData = 1; +} + +void +IEC::ping() +{ + VirtualComponent::ping(); + + c64->putMessage(busActivity > 0 ? MSG_IEC_BUS_BUSY : MSG_IEC_BUS_IDLE); +} + +void +IEC::dump() +{ + msg("IEC bus\n"); + msg("-------\n"); + msg("\n"); + dumpTrace(); + msg("\n"); + msg(" DDRB (VIA1) : %02X (Drive 1)\n", c64->drive1.via1.getDDRB()); + msg(" DDRB (VIA1) : %02X (Drive 2)\n", c64->drive2.via1.getDDRB()); + msg(" DDRA (CIA2) : %02X\n\n", c64->cia2.getDDRA()); + msg(" Bus activity : %d\n", busActivity); + + msg("\n"); +} + +void +IEC::dumpTrace() +{ + debug(1, "ATN: %d CLK: %d DATA: %d\n", atnLine, clockLine, dataLine); +} + +bool IEC::_updateIecLines() +{ + // Save current values + bool oldAtnLine = atnLine; + bool oldClockLine = clockLine; + bool oldDataLine = dataLine; + + // Compute bus signals (inverted and "wired AND") + atnLine = !ciaAtn; + clockLine = !device1Clock && !device2Clock && !ciaClock; + dataLine = !device1Data && !device2Data && !ciaData; + + // Auto-acknowdlege logic + + /* From the SERVICE MANUAL MODEL 1540/1541 DISK DRIVE (PN-314002-01) + * + * "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed + * at PB7 and CA1 of UC3 after being inverted by UA1. ATNA (Attention + * Acknowledge) is an output from PB4 of UC3 which is sensed on the data + * line pin 5 of P2 and P3 after being exclusively "ored" by UD3 and + * inverted by UB1." + * + * ---- + * ATNA (VIA) -----------| | --- + * --- | =1 |---| 1 |o---> & DATA (IEC) + * ATN (IEC) --| 1 |o---| | --- + * --- ---- UB1 + * UA1 UD3 + * + * if (driveIsConnected()) { + * bool ua1 = !atnLine; + * bool ud3 = ua1 ^ deviceAtn; + * bool ub1 = !ud3; + * dataLine &= ub1; + * } + */ + dataLine &= c64->drive1.isPoweredOff() || (atnLine ^ device1Atn); + dataLine &= c64->drive2.isPoweredOff() || (atnLine ^ device2Atn); + + return (oldAtnLine != atnLine || + oldClockLine != clockLine || + oldDataLine != dataLine); +} + +void +IEC::updateIecLines() +{ + bool signals_changed; + + // Update bus lines + signals_changed = _updateIecLines(); + + if (signals_changed) { + + c64->cia2.updatePA(); + + // ATN signal is connected to CA1 pin of VIA 1 + c64->drive1.via1.CA1action(!atnLine); + c64->drive2.via1.CA1action(!atnLine); + + if (tracingEnabled()) { + dumpTrace(); + } + + if (busActivity == 0) { + + // Reset watchdog counter + busActivity = 30; + + // Bus has just been activated + c64->putMessage(MSG_IEC_BUS_BUSY); + + } else { + + // Reset watchdog counter + busActivity = 30; + } + } +} + +void +IEC::updateIecLinesC64Side() +{ + // Get bus signals from C64 side + uint8_t ciaBits = c64->cia2.getPA(); + ciaAtn = !!(ciaBits & 0x08); + ciaClock = !!(ciaBits & 0x10); + ciaData = !!(ciaBits & 0x20); + + updateIecLines(); + isDirtyC64Side = false; +} + +void +IEC::updateIecLinesDriveSide() +{ + // Get bus signals from drive 1 + uint8_t device1Bits = c64->drive1.via1.getPB(); + device1Atn = !!(device1Bits & 0x10); + device1Clock = !!(device1Bits & 0x08); + device1Data = !!(device1Bits & 0x02); + + // Get bus signals from drive 2 + uint8_t device2Bits = c64->drive2.via1.getPB(); + device2Atn = !!(device2Bits & 0x10); + device2Clock = !!(device2Bits & 0x08); + device2Data = !!(device2Bits & 0x02); + + updateIecLines(); + isDirtyDriveSide = false; +} + +void +IEC::execute() +{ + if (busActivity > 0) { + + if (--busActivity == 0) { + + // Bus goes idle + c64->putMessage(MSG_IEC_BUS_IDLE); + } + } +} + diff --git a/C64/Computer/IEC.h b/C64/Computer/IEC.h new file mode 100755 index 00000000..704e5a36 --- /dev/null +++ b/C64/Computer/IEC.h @@ -0,0 +1,125 @@ +/*! + * @header IEC.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright 2006 - 2018 Dirk W. Hoffmann + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _IEC_INC +#define _IEC_INC + +#include "VirtualComponent.h" + +class IEC : public VirtualComponent { + +public: + + //! @brief Current value of the IEC bus atn line + bool atnLine; + + //! @brief Current value of the IEC bus clock line + bool clockLine; + + //! @brief Current value of the IEC bus data line + bool dataLine; + + /*! @brief Indicates if the bus lines variables need an undate, + * because the values coming from the C64 side have changed. + * @deprecated + */ + bool isDirtyC64Side; + + /*! @brief Indicates if the bus lines variables need an undate, + * because the values coming from the drive side have changed. + * @deprecated + */ + bool isDirtyDriveSide; + + //! @brief Bus driving values from drive 1 side + bool device1Atn; + bool device1Clock; + bool device1Data; + + //! @brief Bus driving values from drive 2 side + bool device2Atn; + bool device2Clock; + bool device2Data; + + //! @brief Bus driving values from CIA side + bool ciaAtn; + bool ciaClock; + bool ciaData; + +private: + + //! @brief Used to determine if the bus is idle or if data is transferred + uint32_t busActivity; + +public: + + //! @brief Constructor + IEC(); + + //! @brief Destructor + ~IEC(); + + //! @brief Method from VirtualComponent + void reset(); + + //! @brief Method from VirtualComponent + void ping(); + + //! @brief Method from VirtualComponent + void dump(); + + //! @brief Writes trace output to console. + void dumpTrace(); + + //! @brief Returns true if the IEC currently transfers data. + bool isBusy() { return busActivity > 0; } + + //! @brief Requensts an update of the bus lines from the C64 side. + //! @deprecated + void setNeedsUpdateC64Side() { isDirtyC64Side = true; } + + //! @brief Requensts an update of the bus lines from the drive side. + //! @deprecated + void setNeedsUpdateDriveSide() { isDirtyDriveSide = true; } + + //! @brief Updates all three bus lines. + /*! @details The new values are determined by VIA1 (drive side) and + * CIA2 (C64 side). + */ + void updateIecLinesC64Side(); + void updateIecLinesDriveSide(); + + //! @brief Execution function for observing the bus activity. + /*! @details This method is invoked periodically. It's only purpose is to + * determines if data is transmitted on the bus. + */ + void execute(); + +private: + + void updateIecLines(); + + //! @brief Work horse for method updateIecLines + /*! @details Returns true if at least one line changed it's value. + */ + bool _updateIecLines(); +}; + +#endif diff --git a/C64/Computer/Keyboard.cpp b/C64/Computer/Keyboard.cpp new file mode 100755 index 00000000..5a5f7b07 --- /dev/null +++ b/C64/Computer/Keyboard.cpp @@ -0,0 +1,194 @@ +/* + * (C) 2006 Dirk W. Hoffmann. All rights reserved. + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +Keyboard::Keyboard() +{ + setDescription("Keyboard"); + debug(3, "Creating keyboard at address %p...\n", this); + + // Register snapshot items + SnapshotItem items[] = { + + { &kbMatrixRow, sizeof(kbMatrixRow), CLEAR_ON_RESET | BYTE_ARRAY }, + { &kbMatrixCol, sizeof(kbMatrixCol), CLEAR_ON_RESET | BYTE_ARRAY }, + { &shiftLock, sizeof(shiftLock), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +Keyboard::~Keyboard() +{ +} + +void +Keyboard::reset() +{ + VirtualComponent::reset(); + + // Release all keys (resets the keyboard matrix) + releaseAll(); +} + +void +Keyboard::dump() +{ + msg("Keyboard:\n"); + msg("---------\n\n"); + msg("Keyboard matrix: "); + for (int i = 0; i < 8; i++) { + msg("%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n ", + (kbMatrixRow[i] & 0x01) != 0, + (kbMatrixRow[i] & 0x02) != 0, + (kbMatrixRow[i] & 0x04) != 0, + (kbMatrixRow[i] & 0x08) != 0, + (kbMatrixRow[i] & 0x10) != 0, + (kbMatrixRow[i] & 0x20) != 0, + (kbMatrixRow[i] & 0x40) != 0, + (kbMatrixRow[i] & 0x80) != 0, + + (kbMatrixCol[i] & 0x01) != 0, + (kbMatrixCol[i] & 0x02) != 0, + (kbMatrixCol[i] & 0x04) != 0, + (kbMatrixCol[i] & 0x08) != 0, + (kbMatrixCol[i] & 0x10) != 0, + (kbMatrixCol[i] & 0x20) != 0, + (kbMatrixCol[i] & 0x40) != 0, + (kbMatrixCol[i] & 0x80) != 0); + } + msg("\n"); + msg("Shift lock: %s pressed\n", shiftLock ? "" : "not"); +} + +void +Keyboard::setShiftLock(bool value) +{ + if (value != shiftLock) { + shiftLock = value; + c64->putMessage(MSG_KEYMATRIX); + } +} + +uint8_t +Keyboard::getRowValues(uint8_t columnMask) +{ + uint8_t result = 0xff; + + for (unsigned i = 0; i < 8; i++) { + if (GET_BIT(columnMask, i)) { + result &= kbMatrixRow[i]; + } + } + + // Check for shift lock + if (shiftLock && GET_BIT(columnMask, 6)) + CLR_BIT(result, 4); + + return result; +} + +uint8_t +Keyboard::getColumnValues(uint8_t rowMask) +{ + uint8_t result = 0xff; + + for (unsigned i = 0; i < 8; i++) { + if (GET_BIT(rowMask, i)) { + result &= kbMatrixCol[i]; + } + } + + // Check for shift lock + if (shiftLock && GET_BIT(rowMask, 4)) + CLR_BIT(result, 6); + + return result; +} + + +void +Keyboard::pressKey(uint8_t row, uint8_t col) +{ + assert(row < 8); + assert(col < 8); + + // debug("pressKey(%d,%d)\n", row, col); + + kbMatrixRow[row] &= ~(1 << col); + kbMatrixCol[col] &= ~(1 << row); + + c64->putMessage(MSG_KEYMATRIX); +} + +void +Keyboard::pressRestoreKey() +{ + c64->cpu.pullDownNmiLine(CPU::INTSRC_KEYBOARD); +} + +void +Keyboard::releaseKey(uint8_t row, uint8_t col) +{ + assert(row < 8); + assert(col < 8); + + // Only release right shift key if shift lock is not pressed + if (row == 6 && col == 4 && shiftLock) + return; + + kbMatrixRow[row] |= (1 << col); + kbMatrixCol[col] |= (1 << row); + + c64->putMessage(MSG_KEYMATRIX); +} + +void +Keyboard::releaseRestoreKey() +{ + c64->cpu.releaseNmiLine(CPU::INTSRC_KEYBOARD); +} + +bool +Keyboard::keyIsPressed(uint8_t row, uint8_t col) +{ + // We can either check the row or column matrix + bool result1 = (kbMatrixRow[row] & (1 << col)) == 0; + // bool result2 = (kbMatrixCol[col] & (1 << row)) == 0; + // assert(result1 == result2); + + return result1; +} + +void +Keyboard::toggleKey(uint8_t row, uint8_t col) +{ + if (keyIsPressed(row, col)) { + releaseKey(row, col); + } else { + pressKey(row,col); + } +} + +bool +Keyboard::inUpperCaseMode() +{ + return (c64->vic.spypeek(0x18) & 0x02) == 0; +} + diff --git a/C64/Computer/Keyboard.h b/C64/Computer/Keyboard.h new file mode 100755 index 00000000..5ef4cf56 --- /dev/null +++ b/C64/Computer/Keyboard.h @@ -0,0 +1,191 @@ +/*! + * @header Keyboard.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright 2006 - 2018 Dirk W. Hoffmann + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _KEYBOARD_INC +#define _KEYBOARD_INC + +#include "VirtualComponent.h" + +/*! @class The virtual keyboard of a C64 + * @details This class manages the keyboard matrix of the virtual C64. + * Keyboard management works as follows: When the GUI recognizes a + * key press or a key release, it calls one of the functions in this + * class to tell the virtual keyboard about the event. The called + * functions do nothing more than clearing or setting a bit in the + * 8x8 keyboard matrix.Each key corresponds to a specific bit in the + * matrix and is uniquely determined by a row and a column value. + * + * Communication with the virtual computer is managed solely by the + * CIA chip. When a special CIA register is peeked, method + * getRowValues is called which finally gets the contents of the + * keyboard matrix into the virtual C64. + */ +class Keyboard : public VirtualComponent { + +private: + + //! @brief The C64 keyboard matrix indexed by row + uint8_t kbMatrixRow[8]; + + //! @brief The C64 keyboard matrix indexed by column + uint8_t kbMatrixCol[8]; + + //! @brief True iff shift lock is pressed + bool shiftLock; + +public: + + //! @brief Constructor + Keyboard(); + + //! @brief Destructor + ~Keyboard(); + + //! @brief Methods from VirtualComponent + void reset(); + void dump(); + + /*! @brief Checks if a certain key is currently pressed. + * @details The key is identified by its native row and column index. + */ + bool keyIsPressed(uint8_t row, uint8_t col); + + //! @brief Checks if the shift lock key is held down. + bool shiftLockIsHoldDown() { return shiftLock; } + + //! @brief Checks if the left shift key is currently pressed. + bool leftShiftIsPressed() { return keyIsPressed(1,7); } + + //! @brief Checks if the right shift key is currently pressed. + bool rightShiftIsPressed() { return keyIsPressed(6,4); } + + //! @brief Checks if the commodore key is currently pressed. + bool commodoreIsPressed() { return keyIsPressed(7,5); } + + //! @brief Checks if the CTRL key is currently pressed. + bool ctrlIsPressed() { return keyIsPressed(7,2); } + + //! @brief Checks if the runstop key is currently pressed. + bool runstopIsPressed() { return keyIsPressed(7,7); } + + + /*! @brief Presses a key. + * @details The key is identified by its native row and column index. + */ + void pressKey(uint8_t row, uint8_t col); + + //! @brief Presses the left shift hey. + // void pressLeftShiftKey() { pressKey(1,7); } + + //! @brief Presses the commodore key. + void pressCommodoreKey() { pressKey(7,5); } + + //! @brief Presses the CTRL key. + void pressCtrlKey() { pressKey(7,2); } + + //! @brief Presses the runstop key. + void pressRunstopKey() { pressKey(7,7); } + + //! @brief Presses the restore key. + void pressRestoreKey(); + + + /*! @brief Releases a pressed key. + * @details The key is identified by its native row and column index. + */ + void releaseKey(uint8_t row, uint8_t col); + + //! @brief Releases the left shift key. + // void releaseShiftKey() { releaseKey(1,7); } + + //! @brief Releases the commodore key. + void releaseCommodoreKey() { releaseKey(7,5); } + + //! @brief Releases the CTRL key. + void releaseCtrlKey() { releaseKey(7,2); } + + //! @brief Releases the runstop key. + void releaseRunstopKey() { releaseKey(7,7); } + + //! @brief Releases the restore key. + void releaseRestoreKey(); + + //! @brief Clears the keyboard matrix. + void releaseAll() { for (unsigned i = 0; i < 8; i++) kbMatrixRow[i] = kbMatrixCol[i] = 0xFF; } + + /*! @brief Toggles a certain key. + * @details The key is identified by its native row and column index. + */ + void toggleKey(uint8_t row, uint8_t col); + + //! @brief Toggles the shift key. + void toggleShiftKey() { toggleKey(1,7); } + + //! @brief Toggles the commodore key. + void toggleCommodoreKey() { toggleKey(7,5); } + + //! @brief Toggles the control key. + void toggleCtrlKey() { toggleKey(7,2); } + + //! @brief Toggles the runstop key. + void toggleRunstopKey() { toggleKey(7,7); } + + + // + //! @functiongroup Handling the shift lock key + // + + //! @brief Setter for shiftLock + /*! @details Sends a KEYMATRIX message if the variable changes + */ + void setShiftLock(bool value); + + /*! @brief Presses the shift lock key + * @details Pressing shift lock has the same effect as holding the + * right shift key permanently. + */ + void pressShiftLockKey() { setShiftLock(true); } + + //! @brief Releases the shift lock key + void releaseShiftLockKey() { setShiftLock(false); } + + + // + //! @functiongroup Accessing the keyboard matrix + // + + /*! @brief Reads a row from keyboard matrix + * @param columnMask Indicates the rows to read + */ + uint8_t getRowValues(uint8_t columnMask); + + /*! @brief Reads a column from keyboard matrix. + * @param rowMask Indicates the rows to read + */ + uint8_t getColumnValues(uint8_t rowMask); + + + //! @brief Returns true if the C64 is currently in upper case mode. + /*! @details To shift between two modes, press SHIFT + COMMODORE. + */ + bool inUpperCaseMode(); +}; + +#endif diff --git a/C64/Computer/ProcessorPort.cpp b/C64/Computer/ProcessorPort.cpp new file mode 100755 index 00000000..80f213ad --- /dev/null +++ b/C64/Computer/ProcessorPort.cpp @@ -0,0 +1,139 @@ +/*! + * @file ProcessorPort.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +ProcessorPort::ProcessorPort() +{ + setDescription("ProcessorPort"); + debug(3, "Creating processor port at address %p...\n", this); + + // Register snapshot items + SnapshotItem items[] = { + + { &port, sizeof(port), CLEAR_ON_RESET }, + { &direction, sizeof(direction), CLEAR_ON_RESET }, + { &dischargeCycleBit3, sizeof(dischargeCycleBit3), CLEAR_ON_RESET }, + { &dischargeCycleBit6, sizeof(dischargeCycleBit6), CLEAR_ON_RESET }, + { &dischargeCycleBit7, sizeof(dischargeCycleBit7), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +ProcessorPort::~ProcessorPort() +{ +} + +void +ProcessorPort::dump() +{ + msg("Processor port:\n"); + msg("---------------\n\n"); + msg("port: %02X\n", port); + msg("direction: %02X\n", direction); + msg("Bit 3 discharge cycle: %ld\n", dischargeCycleBit3); + msg("Bit 6 discharge cycle: %ld\n", dischargeCycleBit6); + msg("Bit 7 discharge cycle: %ld\n", dischargeCycleBit7); +} + +uint8_t +ProcessorPort::read() +{ + // If the port bits are configured as inputs and no datasette is attached, + // the following values are returned: + // + // Bit 0: 1 (bit is driven by a pull-up resistor) + // Bit 1: 1 (bit is driven by a pull-up resistor) + // Bit 2: 1 (bit is driven by a pull-up resistor) + // ??? Bit 3: Eventually 0 (acts a a capacitor) + // Bit 3: 0 (bit is driven by a pull-down resistor) + // Bit 4: 1 (bit is driven by a pull-up resistor) + // Bit 5: 0 (bit is driven by a pull-down resistor) + // Bit 6: Eventually 0 (acts a a capacitor) + // Bit 7: Eventually 0 (acts a a capacitor) + // + // In reality, discharging times for bits 3, 6, and 7 depend on both + // CPU temperature and how long the output was 1 befor the bit became + // an input. + + uint8_t bit3 = (dischargeCycleBit3 > c64->cpu.cycle) ? 0x08 : 0x00; + uint8_t bit6 = (dischargeCycleBit6 > c64->cpu.cycle) ? 0x40 : 0x00; + uint8_t bit7 = (dischargeCycleBit7 > c64->cpu.cycle) ? 0x80 : 0x00; + uint8_t bit4 = c64->datasette.getPlayKey() ? 0x00 : 0x10; + uint8_t bits = bit7 | bit6 | bit4 | bit3 | 0x07; + + return (port & direction) | (bits & ~direction); +} + +uint8_t +ProcessorPort::readDirection() +{ + return direction; +} + +void +ProcessorPort::write(uint8_t value) +{ + port = value; + + // Check for datasette motor bit + if (direction & 0x20) { + c64->datasette.setMotor((value & 0x20) == 0); + } + + // When writing to the port register, the last VIC byte appears in 0x0001 + c64->mem.ram[0x0001] = c64->vic.getDataBusPhi1(); + + // Switch memory banks + c64->mem.updatePeekPokeLookupTables(); +} + +void +ProcessorPort::writeDirection(uint8_t value) +{ + uint64_t dischargeCycles = 350000; // VICE value + // uint64_t dischargeCycles = 246312; // Hoxs64 value + + // Check floating status of bits 3, 6, and 7. + + // 1) If bits 3, 6, and 7 are configured as outputs, they are not floating + if (GET_BIT(value, 3)) dischargeCycleBit3 = 0; + if (GET_BIT(value, 6)) dischargeCycleBit6 = 0; + if (GET_BIT(value, 7)) dischargeCycleBit7 = 0; + + // 2) If bits 3, 6, and 7 change from output to input, they become floating + if (FALLING_EDGE_BIT(direction, value, 3) && GET_BIT(port, 3) != 0) + dischargeCycleBit3 = UINT64_MAX; + if (FALLING_EDGE_BIT(direction, value, 6) && GET_BIT(port, 6) != 0) + dischargeCycleBit6 = c64->cpu.cycle + dischargeCycles; + if (FALLING_EDGE_BIT(direction, value, 7) && GET_BIT(port, 7) != 0) + dischargeCycleBit7 = c64->cpu.cycle + dischargeCycles; + + direction = value; + + // When writing to the direction register, the last VIC byte appears + c64->mem.ram[0x0000] = c64->vic.getDataBusPhi1(); + + // Switch memory banks + c64->mem.updatePeekPokeLookupTables(); +} + + diff --git a/C64/Computer/ProcessorPort.h b/C64/Computer/ProcessorPort.h new file mode 100755 index 00000000..4bc0ea54 --- /dev/null +++ b/C64/Computer/ProcessorPort.h @@ -0,0 +1,97 @@ +/*! + * @header ProcessorPort.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PROCESSORPORT_H +#define _PROCESSORPORT_H + +#include "VirtualComponent.h" + +/*! @brief Processor port + * @details The C64 CPU contains a processor port register and a data + * direction register that indicates if a processor bit is configured + * as input or output. The register serves multiple pursposes. + * Firstly, it is used for bank switching, i.e. it decided for + * certain memory regions if ROM or RAM is avaible. Secondly, it is + * used to communicate with the datasette. + */ +class ProcessorPort : public VirtualComponent { + + //! @brief Processor port bits + uint8_t port; + + //! @brief Processor port direction bits + uint8_t direction; + + //! @brief Clock cycle when floating bit values reach zero + /*! @details Bit 3, 6, and 7 of the processor need our special attention. + * When the direction of these bits is changed from output to + * input, there will be no external signal driving them. As a + * result, the bits will be in a floating state and act as an + * capacitor. They will discharge slowly and eventually reach + * zero. These variables are used to indicate when the zero level + * is reached. All three variables are queried in readPort() and + * have the following semantics: + * dischargeCycleBit > current cycle => bit reads as 1 + * (if configured as input) + * otherwise => bit reads as 0 + * (if configured as input) + */ + uint64_t dischargeCycleBit3; + uint64_t dischargeCycleBit6; + uint64_t dischargeCycleBit7; + +public: + + // + //! @functiongroup Creating and destructing + // + + //! @brief Constructor + ProcessorPort(); + + //! @brief Destructor + ~ProcessorPort(); + + + // + //! @functiongroup Methods from VirtualComponent + // + + void dump(); + + + // + //! @functiongroup Accessing the port registers + // + + //! @brief Reads from the processor port register. + uint8_t read(); + + //! @brief Reads from the processor port direction register. + uint8_t readDirection(); + + //! @brief Writes to the processor port register. + void write(uint8_t value); + + //! @brief Writes to the processor port direction register. + void writeDirection(uint8_t value); +}; + +#endif diff --git a/C64/Configure.h b/C64/Configure.h new file mode 100755 index 00000000..a3c4a3d6 --- /dev/null +++ b/C64/Configure.h @@ -0,0 +1,65 @@ +/*! + * @header Configure.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CONFIGURE_H +#define CONFIGURE_H + +// Snapshot version number of this release +#define V_MAJOR 3 +#define V_MINOR 3 +#define V_SUBMINOR 0 + +// Disable assertion checking (Uncomment in release build) +// #define NDEBUG + +// Default debug level for all components (Set to 1 in release build) +#define DEBUG_LEVEL 1 + +#endif + +// RELEASE NOTES (Version 3.3.1) +// +// New code base is compatible with XCode 10.2. +// Fixed several bugs that caused random crashes when eclosing an emulator window. +// Gamepad events prevent the Mac from going into sleep mode now. +// The lists of recently inserted URLs are shared across multiple documents. +// +// +// TODOs for the next release: +// +// +// +// +// CLEANUP: +// +// OPTIMIZATION: +// +// Check, how -Ofast compares to -O3 +// Check how USE_OPTIMIZATION_PROFILE = true influences runtime +// +// +// Update IEC bus inside CIA and VIA. Use delay flags if neccessary +// Use a simpler implementation for the raster irq trigger. Edge sensitive matching value +// Call CA1 action in VIA class only if the pin value really has changed. +// +// Add setter API for SID stuff +// + diff --git a/C64/Datasette/Datasette.cpp b/C64/Datasette/Datasette.cpp new file mode 100755 index 00000000..d75b7df2 --- /dev/null +++ b/C64/Datasette/Datasette.cpp @@ -0,0 +1,278 @@ +/*! + * @header Datasette.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright 2015 - 2016 Dirk W. Hoffmann + * @brief Declares Datasette class + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +Datasette::Datasette() +{ + setDescription("Datasette"); + debug(3, "Creating virtual datasette at address %p\n", this); + + // Register snapshot items + SnapshotItem items[] = { + + // Tape properties (will survive reset) + { &size, sizeof(size), KEEP_ON_RESET }, + { &type, sizeof(type), KEEP_ON_RESET }, + { &durationInCycles, sizeof(durationInCycles), KEEP_ON_RESET }, + + // Internal state (will be cleared on reset) + { &head, sizeof(head), CLEAR_ON_RESET }, + { &headInCycles, sizeof(headInCycles), CLEAR_ON_RESET }, + { &headInSeconds, sizeof(headInSeconds), CLEAR_ON_RESET }, + { &nextRisingEdge, sizeof(nextRisingEdge), CLEAR_ON_RESET }, + { &nextFallingEdge, sizeof(nextFallingEdge), CLEAR_ON_RESET }, + { &playKey, sizeof(playKey), CLEAR_ON_RESET }, + { &motor, sizeof(motor), CLEAR_ON_RESET }, + + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +Datasette::~Datasette() +{ + debug(3, "Releasing Datasette...\n"); + + if (data) + delete[] data; +} + +void +Datasette::reset() +{ + VirtualComponent::reset(); + rewind(); +} + +void +Datasette::ping() +{ + VirtualComponent::ping(); + c64->putMessage(hasTape() ? MSG_VC1530_TAPE : MSG_VC1530_NO_TAPE); + c64->putMessage(MSG_VC1530_PROGRESS); +} + +size_t +Datasette::stateSize() +{ + return VirtualComponent::stateSize() + size; +} + +void +Datasette::didLoadFromBuffer(uint8_t **buffer) +{ + if (data) delete[] data; + + if (size) { + data = new uint8_t[size]; + readBlock(buffer, data, size); + } +} + +void +Datasette::didSaveToBuffer(uint8_t **buffer) +{ + if (size) { + assert(data != NULL); + writeBlock(buffer, data, size); + } +} + +void +Datasette::setHeadInCycles(uint64_t value) +{ + printf("Fast forwarding to cycle %lld (duration %lld)\n", value, durationInCycles); + rewind(); + while (headInCycles <= value && head < size) + advanceHead(true); + printf("Head is %llu (max %llu)\n", head, size); +} + +bool +Datasette::insertTape(TAPFile *a) +{ + suspend(); + + size = a->getSize(); + type = a->TAPversion(); + + debug(2, "Inserting tape (size = %d, type = %d)...\n", size, type); + + // Copy data + data = (uint8_t *)malloc(size); + memcpy(data, a->getData(), size); + + // Determine tape length (by fast forwarding) + rewind(); + while (head < size) + advanceHead(true /* Don't send tape progress messages */); + + durationInCycles = headInCycles; + rewind(); + + c64->putMessage(MSG_VC1530_TAPE); + resume(); + + return true; +} + +void +Datasette::ejectTape() +{ + suspend(); + + debug(2, "Ejecting tape\n"); + + if (!hasTape()) + return; + + pressStop(); + + assert(data != NULL); + free(data); + data = NULL; + size = 0; + type = 0; + durationInCycles = 0; + head = -1; + + c64->putMessage(MSG_VC1530_NO_TAPE); + resume(); +} + +void +Datasette::advanceHead(bool silent) +{ + // Return if end of tape has been reached already + if (head == size) + return; + + // Update head and headInCycles + int length, skip; + length = pulseLength(&skip); + head += skip; + headInCycles += length; + + // Send message if the tapeCounter (in seconds) changes + uint32_t newHeadInSeconds = (uint32_t)(headInCycles / c64->frequency); + if (newHeadInSeconds != headInSeconds && !silent) + c64->putMessage(MSG_VC1530_PROGRESS); + + // Update headInSeconds + headInSeconds = newHeadInSeconds; +} + +int +Datasette::pulseLength(int *skip) +{ + assert(head < size); + + if (data[head] != 0) { + // Pulse lengths between 1 * 8 and 255 * 8 + if (skip) *skip = 1; + return 8 * data[head]; + } + + if (type == 0) { + // Pulse lengths greater than 8 * 255 (TAP V0 files) + if (skip) *skip = 1; + return 8 * 256; + } else { + // Pulse lengths greater than 8 * 255 (TAP V1 files) + if (skip) *skip = 4; + return LO_LO_HI_HI(data[head+1], data[head+2], data[head+3], 0); + } +} + +void +Datasette::pressPlay() +{ + if (!hasTape()) + return; + + debug("Datasette::pressPlay\n"); + playKey = true; + + // Schedule first pulse + uint64_t length = pulseLength(); + nextRisingEdge = length / 2; + nextFallingEdge = length; +} + +void +Datasette::pressStop() +{ + debug("Datasette::pressStop\n"); + setMotor(false); + playKey = false; +} + +void +Datasette::setMotor(bool value) +{ + if (motor == value) + return; + + motor = value; +} + +void +Datasette::_execute() +{ + if (!hasTape() || !playKey || !motor) + return; + + nextRisingEdge--; + nextFallingEdge--; + + if (nextRisingEdge == 0) { + _executeRising(); + return; + } + + if (nextFallingEdge == 0 && head < size) { + _executeFalling(); + return; + } + + if (head >= size) { + pressStop(); + } +} + +void +Datasette::_executeRising() +{ + c64->cia1.triggerRisingEdgeOnFlagPin(); +} + +void +Datasette::_executeFalling() +{ + c64->cia1.triggerFallingEdgeOnFlagPin(); + + // Schedule next pulse + advanceHead(); + uint64_t length = pulseLength(); + nextRisingEdge = length / 2; + nextFallingEdge = length; +} diff --git a/C64/Datasette/Datasette.h b/C64/Datasette/Datasette.h new file mode 100755 index 00000000..2d09d261 --- /dev/null +++ b/C64/Datasette/Datasette.h @@ -0,0 +1,212 @@ +/*! + * @header Datasette.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _DATASETTE_INC +#define _DATASETTE_INC + +class TAPFile; + +//! @brief A Commodore 1530 (C2N) tape recorder (Datasette) +class Datasette : public VirtualComponent { + + // + // Tape + // + + //! @brief Data buffer (contains the raw data of the TAP archive) + uint8_t *data = NULL; + + //! @brief Size of the attached data buffer + uint64_t size = 0; + + /*! @brief Data format (TAP type) + * @details In TAP format 0, data byte 0 signals a long pulse without + * stating its length precisely. + * In TAP format 1, each 0 is followed by three bytes stating + * the precise length in LO_LO_HI_00 format. + */ + uint8_t type = 0; + + /*! @brief Tape length in cycles + * @details The value is set when insertTape() is called. It is computed + * by iterating over all pulses in the data buffer. + */ + uint64_t durationInCycles = 0; + + + // + // Datasette + // + + /*! @brief Read/Write head + * @details Value must be between 0 and size. + * @note head == size indicates EOT (End Of Tape) + */ + uint64_t head = 0; + + /*! @brief Read/Write head + * @details Head position, measured in cycles + */ + uint64_t headInCycles = 0; + + /*! @brief Read/Write head + * @details Head position, measured in seconds + */ + uint32_t headInSeconds = 0; + + /*! @brief Next scheduled rising edge on data line + */ + int64_t nextRisingEdge = 0; + + /*! @brief Next scheduled falling edge on data line + */ + int64_t nextFallingEdge = 0; + + /*! @brief Indicates whether the play key is pressed + */ + bool playKey = false; + + /*! @brief Indicates whether the motor is on + */ + bool motor = false; + + +public: + + // + //! @functiongroup Creating and destructing + // + + //! @brief Constructor + Datasette(); + + //! @brief Destructor + ~Datasette(); + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void ping(); + size_t stateSize(); + void didLoadFromBuffer(uint8_t **buffer); + void didSaveToBuffer(uint8_t **buffer); + + + // + //! @functiongroup Handling virtual tapes + // + + //! @brief Returns true if a tape is inserted. + bool hasTape() { return size != 0; } + + //! @brief Inserts a TAP archive as a virtual tape. + bool insertTape(TAPFile *a); + + /*! @brief Ejects the virtual tape. + * @details Does nothing, if no tape is present. + */ + void ejectTape(); + + //! @brief Returns the tape type (TAP format, 0 or 1). + uint8_t getType() { return type; } + + //! @brief Returns the tape length in cycles. + uint64_t getDurationInCycles() { return durationInCycles; } + + //! @brief Returns the tape length in seconds. + uint32_t getDurationInSeconds() { return (uint32_t)(durationInCycles / (uint64_t)PAL_CLOCK_FREQUENCY); } + + + // + //! @functiongroup Operating the read/write head + // + + //! @brief Puts the read/write head at the beginning of the tape. + void rewind() { head = headInSeconds = headInCycles = 0; } + + /*! @brief Advances the read/write head one pulse. + * @details This methods updates head, headInCycles, and headInSeconds. + * @param silent indicates if a MSG_VC1530_PROGRESS should be sent. + */ + void advanceHead(bool silent = false); + + //! @brief Returns the head position + uint64_t getHead() { return head; } + + //! @brief Returns the head position in CPU cycles + uint64_t getHeadInCycles() { return headInCycles; } + + //! @brief Returns the head position in seconds + uint32_t getHeadInSeconds() { return headInSeconds; } + + //! @brief Sets the current head position in cycles. + void setHeadInCycles(uint64_t value); + + //! @brief Returns the pulse length at the current head position + int pulseLength(int *skip); + int pulseLength() { return pulseLength(NULL); } + + + // + //! @functiongroup Running the device + // + + /*! @brief Returns true if the play key is pressed + */ + bool getPlayKey() { return playKey; } + + /*! @brief Press play on tape + */ + void pressPlay(); + + /*! @brief Press stop key + */ + void pressStop(); + + /*! @brief Returns true if the datasette motor is switched on + */ + bool getMotor() { return motor; } + + /*! @brief Switches motor on or off + */ + void setMotor(bool value); + + /*! @brief Executes the virtual datasette + */ + void execute() { if (playKey && motor) _execute(); } + +private: + + //! @brief Internal execution function + void _execute(); + + //! @brief Simulates the falling edge of a pulse + void _executeFalling(); + + //! @brief Simulates the rising edge of a pulse + void _executeRising(); + +}; + +#endif + + diff --git a/C64/Drive/Disk.cpp b/C64/Drive/Disk.cpp new file mode 100755 index 00000000..9b17f5ef --- /dev/null +++ b/C64/Drive/Disk.cpp @@ -0,0 +1,890 @@ +/*! + * @file Disk.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright 2015 - 2018 Dirk W. Hoffmann + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +const Disk::TrackDefaults Disk::trackDefaults[43] = { + + { 0, 0, 0, 0, 0, 0 }, // Padding + + // Speedzone 3 (outer tracks) + { 21, 3, 7693, 7693 * 8, 0, 0.268956 }, // Track 1 + { 21, 3, 7693, 7693 * 8, 21, 0.724382 }, // Track 2 + { 21, 3, 7693, 7693 * 8, 42, 0.177191 }, // Track 3 + { 21, 3, 7693, 7693 * 8, 63, 0.632698 }, // Track 4 + { 21, 3, 7693, 7693 * 8, 84, 0.088173 }, // Track 5 + { 21, 3, 7693, 7693 * 8, 105, 0.543583 }, // Track 6 + { 21, 3, 7693, 7693 * 8, 126, 0.996409 }, // Track 7 + { 21, 3, 7693, 7693 * 8, 147, 0.451883 }, // Track 8 + { 21, 3, 7693, 7693 * 8, 168, 0.907342 }, // Track 9 + { 21, 3, 7693, 7693 * 8, 289, 0.362768 }, // Track 10 + { 21, 3, 7693, 7693 * 8, 210, 0.815512 }, // Track 11 + { 21, 3, 7693, 7693 * 8, 231, 0.268338 }, // Track 12 + { 21, 3, 7693, 7693 * 8, 252, 0.723813 }, // Track 13 + { 21, 3, 7693, 7693 * 8, 273, 0.179288 }, // Track 14 + { 21, 3, 7693, 7693 * 8, 294, 0.634779 }, // Track 15 + { 21, 3, 7693, 7693 * 8, 315, 0.090253 }, // Track 16 + { 21, 3, 7693, 7693 * 8, 336, 0.545712 }, // Track 17 + + // Speedzone 2 + { 19, 2, 7143, 7143 * 8, 357, 0.945418 }, // Track 18 + { 19, 2, 7143, 7143 * 8, 376, 0.506081 }, // Track 19 + { 19, 2, 7143, 7143 * 8, 395, 0.066622 }, // Track 20 + { 19, 2, 7143, 7143 * 8, 414, 0.627303 }, // Track 21 + { 19, 2, 7143, 7143 * 8, 433, 0.187862 }, // Track 22 + { 19, 2, 7143, 7143 * 8, 452, 0.748403 }, // Track 23 + { 19, 2, 7143, 7143 * 8, 471, 0.308962 }, // Track 24 + + // Speedzone 1 + { 18, 1, 6667, 6667 * 8, 490, 0.116926 }, // Track 25 + { 18, 1, 6667, 6667 * 8, 508, 0.788086 }, // Track 26 + { 18, 1, 6667, 6667 * 8, 526, 0.459190 }, // Track 27 + { 18, 1, 6667, 6667 * 8, 544, 0.130238 }, // Track 28 + { 18, 1, 6667, 6667 * 8, 562, 0.801286 }, // Track 29 + { 18, 1, 6667, 6667 * 8, 580, 0.472353 }, // Track 30 + + // Speedzone 0 (inner tracks) + { 17, 0, 6250, 6250 * 8, 598, 0.834120 }, // Track 31 + { 17, 0, 6250, 6250 * 8, 615, 0.614880 }, // Track 32 + { 17, 0, 6250, 6250 * 8, 632, 0.395480 }, // Track 33 + { 17, 0, 6250, 6250 * 8, 649, 0.176140 }, // Track 34 + { 17, 0, 6250, 6250 * 8, 666, 0.956800 }, // Track 35 + + // Speedzone 0 (usually unused tracks) + { 17, 0, 6250, 6250 * 8, 683, 0.300 }, // Track 36 + { 17, 0, 6250, 6250 * 8, 700, 0.820 }, // Track 37 + { 17, 0, 6250, 6250 * 8, 717, 0.420 }, // Track 38 + { 17, 0, 6250, 6250 * 8, 734, 0.940 }, // Track 39 + { 17, 0, 6250, 6250 * 8, 751, 0.540 }, // Track 40 + { 17, 0, 6250, 6250 * 8, 768, 0.130 }, // Track 41 + { 17, 0, 6250, 6250 * 8, 785, 0.830 } // Track 42 +}; + + +Disk::Disk() +{ + setDescription("Disk"); + + // Register snapshot items + SnapshotItem items[] = { + { &writeProtected, sizeof(writeProtected), KEEP_ON_RESET }, + { &modified, sizeof(modified), KEEP_ON_RESET }, + { &data, sizeof(data), KEEP_ON_RESET }, + { &length, sizeof(length), KEEP_ON_RESET | WORD_ARRAY }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); + + // Create bit expansion table + // Note that this table expects a LITTLE ENDIAN architecture to work. If you compile + // the emulator on a BIG ENDIAN architecture, the byte order needs to be reversed. + for (unsigned i = 0; i < 256; i++) { + bitExpansion[i] = 0; + if (i & 0x80) bitExpansion[i] |= 0x0000000000000001; + if (i & 0x40) bitExpansion[i] |= 0x0000000000000100; + if (i & 0x20) bitExpansion[i] |= 0x0000000000010000; + if (i & 0x10) bitExpansion[i] |= 0x0000000001000000; + if (i & 0x08) bitExpansion[i] |= 0x0000000100000000; + if (i & 0x04) bitExpansion[i] |= 0x0000010000000000; + if (i & 0x02) bitExpansion[i] |= 0x0001000000000000; + if (i & 0x01) bitExpansion[i] |= 0x0100000000000000; + } + + clearDisk(); +} + +Disk::~Disk() +{ +} + +void +Disk::dump() +{ + msg("Floppy disk\n"); + msg("-----------\n\n"); + + for (Halftrack ht = 1; ht <= maxNumberOfHalftracks; ht++) { + uint16_t length = lengthOfHalftrack(ht); + msg("Halftrack %2d: %d Bits (%d Bytes)\n", ht, length, length / 8); + } + msg("\n"); +} + +void +Disk::ping() +{ + VirtualComponent::ping(); +} + +void +Disk::setModified(bool b) +{ + if (b != modified) { + modified = b; + c64->drive1.ping(); + c64->drive2.ping(); + } +} + + +void +Disk::encodeGcr(uint8_t value, Track t, HeadPosition offset) +{ + assert(isTrackNumber(t)); + + uint8_t nibble1 = bin2gcr(value >> 4); + uint8_t nibble2 = bin2gcr(value & 0xF); + + writeBitToTrack(t, offset++, nibble1 & 0x10); + writeBitToTrack(t, offset++, nibble1 & 0x08); + writeBitToTrack(t, offset++, nibble1 & 0x04); + writeBitToTrack(t, offset++, nibble1 & 0x02); + writeBitToTrack(t, offset++, nibble1 & 0x01); + + writeBitToTrack(t, offset++, nibble2 & 0x10); + writeBitToTrack(t, offset++, nibble2 & 0x08); + writeBitToTrack(t, offset++, nibble2 & 0x04); + writeBitToTrack(t, offset++, nibble2 & 0x02); + writeBitToTrack(t, offset++, nibble2 & 0x01); +} + +void +Disk::encodeGcr(uint8_t *values, size_t length, Track t, HeadPosition offset) +{ + for (size_t i = 0; i < length; i++, values++, offset += 10) { + encodeGcr(*values, t, offset); + } +} + +void +Disk::encodeGcr(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4, Track t, unsigned offset) +{ + assert(isTrackNumber(t)); + + uint8_t buffer[] = { b1, b2, b3, b4 }; + + encodeGcr(buffer, 4, t, offset); + /* + uint64_t shift_reg = 0; + + // Shift in + shift_reg = gcr[b1 >> 4]; + shift_reg = (shift_reg << 5) | gcr[b1 & 0x0F]; + shift_reg = (shift_reg << 5) | gcr[b2 >> 4]; + shift_reg = (shift_reg << 5) | gcr[b2 & 0x0F]; + shift_reg = (shift_reg << 5) | gcr[b3 >> 4]; + shift_reg = (shift_reg << 5) | gcr[b3 & 0x0F]; + shift_reg = (shift_reg << 5) | gcr[b4 >> 4]; + shift_reg = (shift_reg << 5) | gcr[b4 & 0x0F]; + + // Shift out + writeByteToTrack(t, offset + 4 * 8, shift_reg & 0xFF); shift_reg >>= 8; + writeByteToTrack(t, offset + 3 * 8, shift_reg & 0xFF); shift_reg >>= 8; + writeByteToTrack(t, offset + 2 * 8, shift_reg & 0xFF); shift_reg >>= 8; + writeByteToTrack(t, offset + 1 * 8, shift_reg & 0xFF); shift_reg >>= 8; + writeByteToTrack(t, offset, shift_reg & 0xFF); + */ +} + +uint8_t +Disk::decodeGcrNibble(uint8_t *gcr) +{ + assert(gcr != NULL); + + uint5_t codeword = (gcr[0] << 4) | (gcr[1] << 3) | (gcr[2] << 2) | (gcr[3] << 1) | gcr[4]; + assert(is_uint5_t(codeword)); + + return invgcr[codeword]; +} + +uint8_t +Disk::decodeGcr(uint8_t *gcr) +{ + assert(gcr != NULL); + + uint4_t nibble1 = decodeGcrNibble(gcr); + uint4_t nibble2 = decodeGcrNibble(gcr + 5); + + // assert(is_uint4_t(nibble1)); + // assert(is_uint4_t(nibble2)); + + return (nibble1 << 4) | nibble2; +} + +uint64_t +Disk::_bitDelay(Halftrack ht, HeadPosition pos) { + + assert(isValidHeadPositon(ht, pos)); + + // In the current implementation, we assume that the density bits were + // set to their correct values when a bit was written to disk. According + // to this assumption, the returned value is determined solely by the + // track position of the drive head. + + if (ht <= 33) + return 4 * 10000; // Density bits = 00: 4 * 16/16 * 10^4 1/10 nsec + if (ht <= 47) + return 4 * 9375; // Density bits = 01: 4 * 15/16 * 10^4 1/10 nsec + if (ht <= 59) + return 4 * 8750; // Density bits = 10: 4 * 14/16 * 10^4 1/10 nsec + + return 4 * 8125; // Density bits = 11: 4 * 13/16 * 10^4 1/10 nsec +} + +void +Disk::clearHalftrack(Halftrack ht) +{ + memset(&data.halftrack[ht], 0x55, sizeof(data.halftrack[ht])); + length.halftrack[ht] = sizeof(data.halftrack[ht]) * 8; +} + +void +Disk::clearDisk() +{ + // memset(&data, 0x55, sizeof(data)); + for (Halftrack ht = 1; ht <= maxNumberOfHalftracks; ht++) { + // length.halftrack[ht] = sizeof(data.halftrack[ht]) * 8; + clearHalftrack(ht); + } + writeProtected = false; + modified = false; +} + +bool +Disk::halftrackIsEmpty(Halftrack ht) +{ + assert(isHalftrackNumber(ht)); + for (unsigned i = 0; i < sizeof(data.halftrack[ht]); i++) + if (data.halftrack[ht][i] != 0x55) return false; + return true; +} + +bool +Disk::trackIsEmpty(Track t) +{ + assert(isTrackNumber(t)); + return halftrackIsEmpty(2 * t - 1); +} + +unsigned +Disk::nonemptyHalftracks() +{ + unsigned result = 0; + + for (unsigned ht = 1; ht < 85; ht++) { + if (!halftrackIsEmpty(ht)) + result++; + } + + return result; +} + + +// +// Analyzing the disk +// + +void +Disk::analyzeHalftrack(Halftrack ht) +{ + assert(isHalftrackNumber(ht)); + + uint16_t len = length.halftrack[ht]; + + errorLog.clear(); + errorStartIndex.clear(); + errorEndIndex.clear(); + + // The result of the analysis is stored in variable trackInfo. + memset(&trackInfo, 0, sizeof(trackInfo)); + trackInfo.length = len; + + // Setup working buffer (two copies of the track, each bit represented by one byte). + for (unsigned i = 0; i < maxBytesOnTrack; i++) + trackInfo.byte[i] = bitExpansion[data.halftrack[ht][i]]; + memcpy(trackInfo.bit + len, trackInfo.bit, len); + + // Indicates where the sector headers blocks and the sectors data blocks start. + uint8_t sync[sizeof(trackInfo.bit)]; + memset(sync, 0, sizeof(sync)); + + // Scan for SYNC sequences and decode the byte that follows. + unsigned noOfOnes = 0; + for (unsigned i = 0; i < 2 * len - 10; i++) { + + assert(trackInfo.bit[i] <= 1); + if (trackInfo.bit[i] == 0 && noOfOnes >= 10) { + + // <--- SYNC ---><-- sync[i] --> + // 11111 .... 1110 + // ^ <- We are at offset i which is here + sync[i] = decodeGcr(trackInfo.bit + i); + + if (sync[i] == 0x08) { + debug(2, "Sector header block found at offset %d\n", i); + } else if (sync[i] == 0x07) { + debug(2, "Sector data block found at offset %d\n", i); + } else { + log(i, 10, "Invalid sector ID %02X at index %d. Should be 0x07 or 0x08.", sync[i], i); + } + } + noOfOnes = trackInfo.bit[i] ? (noOfOnes + 1) : 0; + } + + // Lookup first sector header block + unsigned startOffset; + for (startOffset = 0; startOffset < len; startOffset++) { + if (sync[startOffset] == 0x08) { + break; + } + } + if (startOffset == len) { + log(0, len, "Track contains no sector header block."); + return; + } + + // Compute offsets for all sectors + uint8_t sector = UINT8_MAX; + for (unsigned i = startOffset; i < startOffset + len; i++) { + + if (sync[i] == 0x08) { + + sector = decodeGcr(trackInfo.bit + i + 20); + + if (isSectorNumber(sector)) { + if (trackInfo.sectorInfo[sector].headerEnd != 0) + break; // We've seen this sector already, so we are done. + trackInfo.sectorInfo[sector].headerBegin = i; + trackInfo.sectorInfo[sector].headerEnd = i + headerBlockSize; + } else { + log(i + 20, 10, "Header block at index %d contains an invalid sector number (%d).", i, sector); + } + + } else if (sync[i] == 0x07) { + + if (isSectorNumber(sector)) { + trackInfo.sectorInfo[sector].dataBegin = i; + trackInfo.sectorInfo[sector].dataEnd = i + dataBlockSize; + } else { + log(i + 20, 10, "Data block at index %d contains an invalid sector number (%d).", i, sector); + } + } + } + + // Check integrity of all sector blocks + // For each sector ... + Track t = (ht + 1) / 2; + for (Sector s = 0; s < trackDefaults[t].sectors; s++) { + + SectorInfo *info = &trackInfo.sectorInfo[s]; + bool hasHeader = info->headerBegin != info->headerEnd; + bool hasData = info->dataBegin != info->dataEnd; + + if (!hasHeader && !hasData) { + log(0, 0, "Sector %d not found.\n", s); + continue; + } + + if (hasHeader) { + analyzeSectorHeaderBlock(info->headerBegin); + } else { + log(0, 0, "Sector %d has no header block.\n", s); + } + + if (hasData) { + analyzeSectorDataBlock(info->dataBegin); + } else { + log(0, 0, "Sector %d has no data block.\n", s); + } + } +} + +void +Disk::analyzeSectorHeaderBlock(size_t offset) +{ + // The first byte must be 0x08 (indicating a header block) + assert(decodeGcr(trackInfo.bit + offset) == 0x08); + offset += 10; + + uint8_t s = decodeGcr(trackInfo.bit + offset + 10); + uint8_t t = decodeGcr(trackInfo.bit + offset + 20); + uint8_t id2 = decodeGcr(trackInfo.bit + offset + 30); + uint8_t id1 = decodeGcr(trackInfo.bit + offset + 40); + uint8_t checksum = id1 ^ id2 ^ t ^ s; + + if (checksum != decodeGcr(trackInfo.bit + offset)) { + log(offset, 10, "Header block at index %d contains an invalid checksum.\n", offset); + } +} + +void +Disk::analyzeSectorDataBlock(size_t offset) +{ + // The first byte must be 0x07 (indicating a header block) + assert(decodeGcr(trackInfo.bit + offset) == 0x07); + offset += 10; + + uint8_t checksum = 0; + for (unsigned i = 0; i < 256; i++, offset += 10) { + checksum ^= decodeGcr(trackInfo.bit + offset); + } + + if (checksum != decodeGcr(trackInfo.bit + offset)) { + log(offset, 10, "Data block at index %d contains an invalid checksum.\n", offset); + } +} + +void +Disk::log(size_t begin, size_t length, const char *fmt, ...) +{ + char buf[256]; + + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + errorLog.push_back(std::string(buf)); + errorStartIndex.push_back(begin); + errorEndIndex.push_back(begin + length); +} + +const char * +Disk::diskNameAsString() +{ + analyzeTrack(18); + + unsigned i; + size_t offset = trackInfo.sectorInfo[0].dataBegin + (0x90 * 10); + + for (i = 0; i < 255; i++, offset += 10) { + uint8_t value = decodeGcr(trackInfo.bit + offset); + if (value == 0xA0) + break; + text[i] = value; + } + text[i] = 0; + return text; +} + + +const char * +Disk::trackDataAsString() +{ + size_t i; + for (i = 0; i < trackInfo.length; i++) { + if (trackInfo.bit[i]) { + text[i] = '1'; + } else { + text[i] = '0'; + } + } + text[i] = 0; + return text; +} + +const char * +Disk::sectorHeaderAsString(Sector nr) +{ + assert(isSectorNumber(nr)); + size_t begin = trackInfo.sectorInfo[nr].headerBegin; + size_t end = trackInfo.sectorInfo[nr].headerEnd; + return (begin == end) ? "" : sectorBytesAsString(trackInfo.bit + begin, 10); +} + +const char * +Disk::sectorDataAsString(Sector nr) +{ + assert(isSectorNumber(nr)); + size_t begin = trackInfo.sectorInfo[nr].dataBegin; + size_t end = trackInfo.sectorInfo[nr].dataEnd; + return (begin == end) ? "" : sectorBytesAsString(trackInfo.bit + begin, 256); +} + +const char * +Disk::sectorBytesAsString(uint8_t *buffer, size_t length) +{ + size_t gcr_offset = 0; + size_t str_offset = 0; + + for (size_t i = 0; i < length; i++, gcr_offset += 10, str_offset += 3) { + uint8_t value = decodeGcr(buffer + gcr_offset); + sprint8x(text + str_offset, value); + text[str_offset + 2] = ' '; + } + text[str_offset] = 0; + return text; +} + +// +// Decoding disk data +// + +size_t +Disk::decodeDisk(uint8_t *dest) +{ + // Determine highest non-empty track + Track t = 42; + while (t > 0 && trackIsEmpty(t)) t--; + + // Decode disk + if (t <= 35) + return decodeDisk(dest, 35); + + if (t <= 40) + return decodeDisk(dest, 40); + + return decodeDisk(dest, 42); +} + +size_t +Disk::decodeDisk(uint8_t *dest, unsigned numTracks) +{ + unsigned numBytes = 0; + + assert(numTracks == 35 || numTracks == 40 || numTracks == 42); + + // For each full track ... + for (Track t = 1; t <= numTracks; t++) { + + if (trackIsEmpty(t)) + break; + + debug(2, "Decoding track %d %s\n", t, dest ? "" : "(test run)"); + numBytes += decodeTrack(t, dest + (dest ? numBytes : 0)); + } + + return numBytes; +} + +size_t +Disk::decodeTrack(Track t, uint8_t *dest) +{ + unsigned numBytes = 0; + unsigned numSectors = numberOfSectorsInTrack(t); + + // Gather sector information + analyzeTrack(t); + + // For each sector ... + for (unsigned s = 0; s < numSectors; s++) { + + debug(3, " Decoding sector %d\n", s); + SectorInfo info = sectorLayout(s); + if (info.dataBegin != info.dataEnd) { + numBytes += decodeSector(info.dataBegin, dest + (dest ? numBytes : 0)); + } else { + + // The decoder failed to decode this sector. + break; + + // numBytes += decodeBrokenSector(dest + (dest ? numBytes : 0)); + } + } + + return numBytes; +} + +size_t +Disk::decodeSector(size_t offset, uint8_t *dest) +{ + // The first byte must be 0x07 (indicating a data block) + assert(decodeGcr(trackInfo.bit + offset) == 0x07); + offset += 10; + + if (dest) { + for (unsigned i = 0; i < 256; i++) { + dest[i] = decodeGcr(trackInfo.bit + offset); + offset += 10; + } + } + + return 256; +} + +/* +size_t +Disk::decodeBrokenSector(uint8_t *dest) +{ + if (dest) { + for (unsigned i = 0; i < 256; i++) { + dest[i] = 0; + } + } + + return 256; +} +*/ + + +// +// Encoding disk data +// + +void +Disk::encodeArchive(G64File *a) +{ + debug(2, "Encoding G64 archive\n"); + + assert(a != NULL); + + clearDisk(); + for (Halftrack ht = 1; ht <= 84; ht++) { + + a->selectHalftrack(ht); + uint16_t size = a->getSizeOfHalftrack(); + + if (size == 0) { + if (ht > 1) { + // Make this halftrack as long as the previous halftrack + length.halftrack[ht] = length.halftrack[ht - 1]; + } + continue; + } + + if (size > 7928) { + warn("Halftrack %d has %d bytes. Must be less than 7928\n", ht, size); + continue; + } + debug(2, " Encoding halftrack %d (%d bytes)\n", ht, size); + length.halftrack[ht] = 8 * size; + + for (unsigned i = 0; i < size; i++) { + int b = a->readHalftrack(); + assert(b != -1); + data.halftrack[ht][i] = (uint8_t)b; + } + assert(a->readHalftrack() == -1 /* EOF */); + } +} + +void +Disk::encodeArchive(D64File *a, bool alignTracks) +{ + assert(a != NULL); + + // 64COPY (fails on VICE test drive/skew) + /* + int tailGap[4] = { 9, 9, 9, 9 }; + uint16_t trackLength[4] = + { + 6250 * 8, // Tracks 31 - 35..42 (inner tracks) + 6666 * 8, // Tracks 25 - 30 + 7142 * 8, // Tracks 18 - 24 + 7692 * 8 // Tracks 1 - 17 (outer tracks) + }; + */ + + // Hoxs64 (passes VICE test drive/skew) + int tailGap[4] = { 9, 12, 17, 8 }; + uint16_t trackLength[4] = + { + 6250 * 8, // Tracks 31 - 35..42 (inner tracks) + 6667 * 8, // Tracks 25 - 30 + 7143 * 8, // Tracks 18 - 24 + 7693 * 8 // Tracks 1 - 17 (outer tracks) + }; + + // VirtualC64 2.4 + /* + const int tailGap[4] = { 13, 16, 21, 12 }; + const uint16_t trackLength[4] = + { + (uint16_t)(8 * 17 * (354 + tailGap[0])), // Tracks 31 - 35..42 (inner tracks) + (uint16_t)(8 * 18 * (354 + tailGap[1])), // Tracks 25 - 30 + (uint16_t)(8 * 19 * (354 + tailGap[2])), // Tracks 18 - 24 + (uint16_t)(8 * 21 * (354 + tailGap[3])) // Tracks 1 - 17 (outer tracks) + }; + */ + + size_t encodedBits; + unsigned numTracks = a->numberOfTracks(); + + debug(2, "Encoding D64 archive with %d tracks\n", numTracks); + + // Wipe out track data + clearDisk(); + + // Assign track length + for (Halftrack ht = 1; ht <= maxNumberOfHalftracks; ht++) + length.halftrack[ht] = trackLength[speedZoneOfHalftrack(ht)]; + + // Encode tracks + HeadPosition start; + for (Track t = 1; t <= numTracks; t++) { + + unsigned zone = speedZoneOfTrack(t); + if (alignTracks) { + start = (HeadPosition)(length.track[t][0] * trackDefaults[t].stagger); + } else { + start = 0; + } + encodedBits = encodeTrack(a, t, tailGap[zone], start); + debug(2, "Encoded %d bits (%d bytes) for track %d.\n", + encodedBits, encodedBits / 8, t); + } + + // Do some consistency checking + for (Halftrack ht = 1; ht <= maxNumberOfHalftracks; ht++) { + assert(length.halftrack[ht] <= sizeof(data.halftrack[ht]) * 8); + } +} + +size_t +Disk::encodeTrack(D64File *a, Track t, uint8_t tailGap, HeadPosition start) +{ + assert(isTrackNumber(t)); + debug(3, "Encoding track %d\n", t); + + size_t totalEncodedBits = 0; + + // For each sector in this track ... + for (Sector s = 0; s < trackDefaults[t].sectors; s++) { + + size_t encodedBits = encodeSector(a, t, s, start, tailGap); + start += (HeadPosition)encodedBits; + totalEncodedBits += encodedBits; + } + + return totalEncodedBits; +} + +size_t +Disk::encodeSector(D64File *a, Track t, Sector s, HeadPosition start, int tailGap) +{ + assert(a != NULL); + assert(isValidTrackSectorPair(t, s)); + + HeadPosition offset = start; + uint8_t errorCode = a->errorCode(t, s); + + a->selectTrackAndSector(t, s); + + debug(4, " Encoding track/sector %d/%d\n", t, s); + + // Get disk id and compute checksum + uint8_t id1 = a->diskId1(); + uint8_t id2 = a->diskId2(); + uint8_t checksum = id1 ^ id2 ^ t ^ s; // Header checksum byte + + // SYNC (0xFF 0xFF 0xFF 0xFF 0xFF) + if (errorCode == 0x3) { + writeBitToTrack(t, offset, 0, 40); // NO_SYNC SEQUENCE_ERROR + } else { + writeBitToTrack(t, offset, 1, 40); + } + offset += 40; + + // Header ID + if (errorCode == 0x2) { + encodeGcr(0x00, t, offset); // HEADER_BLOCK_NOT_FOUND_ERROR + } else { + encodeGcr(0x08, t, offset); + } + offset += 10; + + // Checksum + if (errorCode == 0x9) { + encodeGcr(checksum ^ 0xFF, t, offset); // HEADER_BLOCK_CHECKSUM_ERROR + } else { + encodeGcr(checksum, t, offset); + } + offset += 10; + + // Sector and track number + encodeGcr(s, t, offset); + offset += 10; + encodeGcr(t, t, offset); + offset += 10; + + // Disk ID (two bytes) + if (errorCode == 0xB) { + encodeGcr(id2 ^ 0xFF, t, offset); // DISK_ID_MISMATCH_ERROR + offset += 10; + encodeGcr(id1 ^ 0xFF, t, offset); // DISK_ID_MISMATCH_ERROR + } else { + encodeGcr(id2, t, offset); + offset += 10; + encodeGcr(id1, t, offset); + } + offset += 10; + + // 0x0F, 0x0F + encodeGcr(0x0F, t, offset); + offset += 10; + encodeGcr(0x0F, t, offset); + offset += 10; + + + // 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 + writeGapToTrack(t, offset, 9); + offset += 9 * 8; + + // SYNC (0xFF 0xFF 0xFF 0xFF 0xFF) + if (errorCode == 3) { + writeBitToTrack(t, offset, 0, 40); // NO_SYNC_SEQUENCE_ERROR + } else { + writeBitToTrack(t, offset, 1, 40); + } + offset += 40; + + // Data ID + if (errorCode == 0x4) { + // The error value is important here: + // (1) If the first GCR bit equals 0, the sector can still be read. + // (2) If the first GCR bit equals 1, the SYNC sequence continues. + // In this case, the bit sequence gets out of sync and the data + // can't be read. + // Hoxs64 and VICE 3.2 write 0x00 which results in option (1) + encodeGcr(0x00, t, offset); // DATA_BLOCK_NOT_FOUND_ERROR + } else { + encodeGcr(0x07, t, offset); + } + offset += 10; + + // Data bytes + checksum = 0; + for (unsigned i = 0; i < 256; i++, offset += 10) { + uint8_t byte = (uint8_t)a->readTrack(); + checksum ^= byte; + encodeGcr(byte, t, offset); + } + + // Checksum + if (errorCode == 0x5) { + encodeGcr(checksum ^ 0xFF, t, offset); // DATA_BLOCK_CHECKSUM_ERROR + } else { + encodeGcr(checksum, t, offset); + } + offset += 10; + + // 0x00, 0x00 + encodeGcr(0x00, t, offset); + offset += 10; + encodeGcr(0x00, t, offset); + offset += 10; + + // Tail gap (0x55 0x55 ... 0x55) + writeGapToTrack(t, offset, tailGap); + offset += tailGap * 8; + + // Return the number of encoded bits + return offset - start; +} diff --git a/C64/Drive/Disk.h b/C64/Drive/Disk.h new file mode 100755 index 00000000..179f58c5 --- /dev/null +++ b/C64/Drive/Disk.h @@ -0,0 +1,531 @@ +/*! + * @header Disk.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright 2015 - 2018 Dirk W. Hoffmann + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _DISK_INC +#define _DISK_INC + +#include +#include +#include + +#include "VirtualComponent.h" +#include "Disk_types.h" + +class C64; +class VC1541; +class D64File; +class G64File; + + +//! @brief A virtual floppy disk +class Disk : public VirtualComponent { + +public: + + // + // Constants and lookup tables + // + + //! @brief Disk parameters of a standard floppy disk + typedef struct { + + uint8_t sectors; // Typical number of sectors in this track + uint8_t speedZone; // Default speed zone for this track + uint16_t lengthInBytes; // Typical track size in bits + uint16_t lengthInBits; // Typical track size in bits + Sector firstSectorNr; // Logical number of first sector in track + double stagger; // Relative position of the first bit (taken from Hoxs64) + + } TrackDefaults; + + static const TrackDefaults trackDefaults[43]; + + + //! @brief Disk error codes + /*! @details Some D64 files contain an error code for each sector. + * If possible, these errors are reproduced during disk encoding. + */ + typedef enum { + DISK_OK = 0x1, + HEADER_BLOCK_NOT_FOUND_ERROR = 0x2, + NO_SYNC_SEQUENCE_ERROR = 0x3, + DATA_BLOCK_NOT_FOUND_ERROR = 0x4, + DATA_BLOCK_CHECKSUM_ERROR = 0x5, + WRITE_VERIFY_ERROR_ON_FORMAT_ERROR = 0x6, + WRITE_VERIFY_ERROR = 0x7, + WRITE_PROTECT_ON_ERROR = 0x8, + HEADER_BLOCK_CHECKSUM_ERROR = 0x9, + WRITE_ERROR = 0xA, + DISK_ID_MISMATCH_ERROR = 0xB, + DRIVE_NOT_READY_ERRROR = 0xF + } DiskErrorCode; + +private: + + /*! @brief GCR encoding table + * @details Maps 4 data bits to 5 GCR bits. + */ + const uint5_t gcr[16] = { + + 0x0a, 0x0b, 0x12, 0x13, /* 0 - 3 */ + 0x0e, 0x0f, 0x16, 0x17, /* 4 - 7 */ + 0x09, 0x19, 0x1a, 0x1b, /* 8 - 11 */ + 0x0d, 0x1d, 0x1e, 0x15 /* 12 - 15 */ + }; + + /*! @brief Inverse GCR encoding table + * @detaiels Maps 5 GCR bits to 4 data bits. Invalid patterns are marked with 255. + */ + const uint4_t invgcr[32] = { + + 255, 255, 255, 255, /* 0x00 - 0x03 */ + 255, 255, 255, 255, /* 0x04 - 0x07 */ + 255, 8, 0, 1, /* 0x08 - 0x0B */ + 255, 12, 4, 5, /* 0x0C - 0x0F */ + 255, 255, 2, 3, /* 0x10 - 0x13 */ + 255, 15, 6, 7, /* 0x14 - 0x17 */ + 255, 9, 10, 11, /* 0x18 - 0x1B */ + 255, 13, 14, 255 /* 0x1C - 0x1F */ + }; + + /*! @brief Maps a byte to an expanded 64 bit representation + * @details Example: 0110 ... -> 00000000 00000001 0000001 00000000 ... + * This method is used to quickly inflate a bit stream into a + * byte stream. + */ + uint64_t bitExpansion[256]; + + + // + // Disk properties + // + + /*! @brief Write protection mark + */ + bool writeProtected; + + /*! @brief Indicates whether data has been written + * @details Depending on this flag, the GUI shows a warning dialog + * before a disk gets ejected. + */ + bool modified; + + + // + // Disk data + // + +public: + + /*! @brief Disk data + * @details The first valid track and halftrack number is 1. + * data.halftack[i] points to the first byte of halftrack i, + * data.track[i] points to the first byte of track i + */ + union { + struct { + uint8_t _pad[maxBytesOnTrack]; + uint8_t halftrack[85][maxBytesOnTrack]; + }; + uint8_t track[43][2 * maxBytesOnTrack]; + } data; + + /*! @brief Length of each halftrack in bits + * @details length.halftack[i] is the length of halftrack i, + * length.track[i][0] is the length of track i, + * length.track[i][1] is the length of halftrack above track i + */ + union { + struct { + uint16_t _pad; + uint16_t halftrack[85]; + }; + uint16_t track[43][2]; + } length; + + + // + // Debug information + // + +private: + + //! @brief Track layout as determined by analyzeTrack + TrackInfo trackInfo; + + //! @brief Error log created by analyzeTrack + std::vector errorLog; + + //! @brief Stores the start offset of the erroneous bit sequence + std::vector errorStartIndex; + + //! @brief Stores the end offset of the erroneous bit sequence + std::vector errorEndIndex; + + //! @brief Textual representation of track data + char text[maxBitsOnTrack + 1]; + + +public: + + // + //! @functiongroup Constructing and destructing + // + + //! @brief Constructor + Disk(); + + //! @brief Destructor + ~Disk(); + + + // + //! @functiongroup Methods from VirtualComponent + // + + void dump(); + void ping(); + + + + //! @brief Returns write protection flag + bool isWriteProtected() { return writeProtected; } + + //! @brief Sets write protection flag + void setWriteProtection(bool b) { writeProtected = b; } + + //! @brief Toggles the write protection flag + void toggleWriteProtection() { writeProtected = !writeProtected; } + + //! @brief Returns modified flag + bool isModified() { return modified; } + + //! @brief Sets modified flag + void setModified(bool b); + + + // + //! @functiongroup Handling Gcr encoded data + // + +public: + + //! @brief Converts a 4 bit binary value to a 5 bit GCR codeword + uint5_t bin2gcr(uint4_t value) { assert(is_uint4_t(value)); return gcr[value]; } + + //! @brief Converts a 5 bit GCR codeword to a 4 bit binary value + uint4_t gcr2bin(uint5_t value) { assert(is_uint5_t(value)); return invgcr[value]; } + + //! @brief Returns true if the provided 5 bit codeword is a valid GCR codeword + bool isGcr(uint5_t value) { assert(is_uint5_t(value)); return invgcr[value] != 0xFF; } + + //! @brief Encodes a single byte as a GCR bitstream. + /*! @details Writes 10 bits to the specified position on disk. + */ + void encodeGcr(uint8_t value, Track t, HeadPosition offset); + + //! @brief Encodes multiple bytes as a GCR bitstream. + /*! @details Writes length * 10 bits to the specified position on disk. + */ + void encodeGcr(uint8_t *values, size_t length, Track t, HeadPosition offset); + + /*! @brief Translates four data bytes into five GCR encodes bytes + *! @deprecated + */ + void encodeGcr(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4, Track t, unsigned offset); + + //! @brief Decodes a nibble (4 bit) from a previously encoded GCR bitstream. + /*! @return 0xFF, if no valid GCR sequence is found. + */ + uint8_t decodeGcrNibble(uint8_t *gcrBits); + + //! @brief Decodes a byte (8 bit) form a previously encoded GCR bitstream. + /*! @note Returns an unpredictable result if invalid GCR sequences are found. + */ + uint8_t decodeGcr(uint8_t *gcrBits); + + + // + //! @functiongroup Accessing disk data + // + + //! @brief Returns true if the provided drive head position is valid. + bool isValidHeadPositon(Halftrack ht, HeadPosition pos) { + return isHalftrackNumber(ht) && pos < length.halftrack[ht]; } + + //! @brief Fixes a wrapped over head position. + HeadPosition fitToBounds(Halftrack ht, HeadPosition pos) { + uint16_t len = length.halftrack[ht]; + return pos < 0 ? pos + len : pos >= len ? pos - len : pos; } + + /*! @brief Returns the duration of a single bit in 1/10 nano seconds. + * @details The returned value is the time span the drive head resists + * over the specified bit. The value is determined by the + * the density bits at the time the bit was written to disk. + * @note The head position is expected to be inside the halftrack bounds. + */ + uint64_t _bitDelay(Halftrack ht, HeadPosition pos); + + /*! @brief Returns the duration of a single bit in 1/10 nano seconds. + */ + uint64_t bitDelay(Halftrack ht, HeadPosition pos) { + return _bitDelay(ht, fitToBounds(ht, pos)); + } + + /*! @brief Reads a single bit from disk. + * @note The head position is expected to be inside the halftrack bounds. + * @result 0x00 or 0x01 + */ + uint8_t _readBitFromHalftrack(Halftrack ht, HeadPosition pos) { + assert(isValidHeadPositon(ht, pos)); + return (data.halftrack[ht][pos / 8] & (0x80 >> (pos % 8))) != 0; + } + + /*! @brief Reads a single bit from disk. + * @result 0x00 or 0x01 + */ + uint8_t readBitFromHalftrack(Halftrack ht, HeadPosition pos) { + return _readBitFromHalftrack(ht, fitToBounds(ht, pos)); + } + + /*! @brief Writes a single bit to disk. + * @note The head position is expected to be inside the halftrack bounds. + */ + void _writeBitToHalftrack(Halftrack ht, HeadPosition pos, bool bit) { + assert(isValidHeadPositon(ht, pos)); + if (bit) { + data.halftrack[ht][pos / 8] |= (0x0080 >> (pos % 8)); + } else { + data.halftrack[ht][pos / 8] &= (0xFF7F >> (pos % 8)); + } + } + + void _writeBitToTrack(Track t, HeadPosition pos, bool bit) { + _writeBitToHalftrack(2 * t - 1, pos, bit); + } + + //! @brief Writes a single bit to disk. + void writeBitToHalftrack(Halftrack ht, HeadPosition pos, bool bit) { + _writeBitToHalftrack(ht, fitToBounds(ht, pos), bit); + } + + void writeBitToTrack(Track t, HeadPosition pos, bool bit) { + writeBitToHalftrack(2 * t - 1, pos, bit); + } + + //! @brief Writes a single bit to disk multiple times. + void writeBitToHalftrack(Halftrack ht, HeadPosition pos, bool bit, size_t count) { + for (size_t i = 0; i < count; i++) + writeBitToHalftrack(ht, pos++, bit); + } + + void writeBitToTrack(Track t, HeadPosition pos, bool bit, size_t count) { + writeBitToHalftrack(2 * t - 1, pos, bit, count); + } + + //! @brief Writes a single byte to disk. + void writeByteToHalftrack(Halftrack ht, HeadPosition pos, uint8_t byte) { + for (uint8_t mask = 0x80; mask != 0; mask >>= 1) + writeBitToHalftrack(ht, pos++, byte & mask); + } + + void writeByteToTrack(Track t, HeadPosition pos, uint8_t byte) { + writeByteToHalftrack(2 * t - 1, pos, byte); + } + + //! @brief Writes a certain number of interblock bytes to disk. + void writeGapToHalftrack(Halftrack ht, HeadPosition pos, size_t length) { + for (size_t i = 0; i < length; i++, pos += 8) + writeByteToHalftrack(ht, pos, 0x55); + } + + void writeGapToTrack(Track t, HeadPosition pos, size_t length) { + writeGapToHalftrack(2 * t - 1, pos, length); + } + + //! @brief Clears a single half-track. + void clearHalftrack(Halftrack ht); + + /*! @brief Reverts to a factory-new disk. + * @details All disk data gets erased and the copy protection mark removed. + */ + void clearDisk(); + + /*! @brief Returns true if a if a track is cleared out. + * @warning Don't call this method frequently, because it scans the whole track. + */ + bool trackIsEmpty(Track t); + + /*! @brief Checks if a halftrack is cleared out. + * @warning Don't call this method frequently, because it scans the whole track. + */ + bool halftrackIsEmpty(Halftrack ht); + + /*! @brief Returns the number of halftracks containing data + * @warning Don't call this method frequently, because it scans the whole disk. + */ + unsigned nonemptyHalftracks(); + + + // + //! @functiongroup Analyzing the disk + // + +public: + + //! @brief Returns the length of a halftrack in bits + uint16_t lengthOfHalftrack(Halftrack ht) { + assert(isHalftrackNumber(ht)); return length.halftrack[ht]; } + + //! @brief Returns the length of a track in bits + uint16_t lengthOfTrack(Track t) { + assert(isTrackNumber(t)); return length.track[t][0]; } + + //! @brief Analyzes the sector layout + /*! @details The start and end offsets of all sectors are determined and writes + * into variable trackLayout. + */ + void analyzeHalftrack(Halftrack ht); + + void analyzeTrack(Track t) { assert(isTrackNumber(t)); analyzeHalftrack(2 * t - 1); } + +private: + + //! @brief Checks the integrity of a sector header block + void analyzeSectorHeaderBlock(size_t offset); + + //! @brief Checks the integrity of a sector data block + void analyzeSectorDataBlock(size_t offset); + + //! @brief Writes an error message into the error log + void log(size_t begin, size_t length, const char *fmt, ...); + +public: + + //! @brief Returns a sector layout from variable trackInfo + SectorInfo sectorLayout(Sector nr) { + assert(isSectorNumber(nr)); return trackInfo.sectorInfo[nr]; } + + //! @brief Returns the number of entries in the error log + unsigned numErrors() { return (unsigned)errorLog.size(); } + + //! @brief Reads an error message from the error log + std::string errorMessage(unsigned nr) { return errorLog.at(nr); } + + //! @brief Reads the error begin index from the error log + size_t firstErroneousBit(unsigned nr) { return errorStartIndex.at(nr); } + + //! @brief Reads the error end index from the error log + size_t lastErroneousBit(unsigned nr) { return errorEndIndex.at(nr); } + + //! @brief Returns a textual representation of the disk name + const char *diskNameAsString(); + + //! @brief Returns a textual representation of the data stored in trackInfo + const char *trackDataAsString(); + + //! @brief Returns a textual representation of the data stored in trackInfo + const char *sectorHeaderAsString(Sector nr); + + //! @brief Returns a textual representation of the data stored in trackInfo + const char *sectorDataAsString(Sector nr); + +private: + + //! @brief Returns a textual representation + const char *sectorBytesAsString(uint8_t *buffer, size_t length); + + + // + //! @functiongroup Decoding disk data + // + +public: + + /*! @brief Converts the disk into a byte stream. + * @details The byte stream is compatible with the D64 file format. + * @param dest Target buffer. If parameter is NULL, a test run is + * performed. Test runs are used to determine upfront how many + * bytes will be written. + * @return Number of bytes written. + */ + size_t decodeDisk(uint8_t *dest); + +private: + + /*! @brief Work horse for decodeDisk(uint8_t *) + * @param numTracks must be either 35, 40, or 42. + */ + size_t decodeDisk(uint8_t *dest, unsigned numTracks); + + //! @brief Decodes all sectors of a track + size_t decodeTrack(Track t, uint8_t *dest); + + //! @brief Decodes a single sector + size_t decodeSector(size_t offset, uint8_t *dest); + + //! @brief Decodes a single broken sector (results in all zeroes) + // size_t decodeBrokenSector(uint8_t *dest); + + + // + //! @functiongroup Encoding disk data + // + +public: + + /*! @brief Converts a G64 archive into a virtual floppy disk. */ + void encodeArchive(G64File *a); + + /*! @brief Converts a D64 archive into a floppy disk. + * @details The method creates sync marks, GRC encoded header and data + * blocks, checksums and gaps. + * @param alignTracks If true, the first sector always starts at the + * beginning of a track. + */ + void encodeArchive(D64File *a, bool alignTracks); + + //! @brief Converts a D64 archive into a floppy disk. + void encodeArchive(D64File *a) { encodeArchive(a, false); } + +private: + + /*! @brief Encode a single track + * @details This function translates the logical byte sequence of a single track into + * the native VC1541 byte representation. The native representation includes + * sync marks, GCR data etc. + * @param tailGapEven + * Number of tail bytes follwowing sectors with even sector numbers. + * @param tailGapOdd + * Number of tail bytes follwowing sectors with odd sector numbers. + * @return Number of written bits. + */ + size_t encodeTrack(D64File *a, Track t, uint8_t tailGap, HeadPosition start); + + /*! @brief Encode a single sector + * @details This function translates the logical byte sequence of a single sector + * into the native VC1541 byte representation. The sector is closed by + * 'gap' tail gap bytes. + * @return Number of written bits. + */ + size_t encodeSector(D64File *a, Track t, Sector sector, HeadPosition start, int gap); +}; + +#endif diff --git a/C64/Drive/Disk_types.h b/C64/Drive/Disk_types.h new file mode 100755 index 00000000..257e6259 --- /dev/null +++ b/C64/Drive/Disk_types.h @@ -0,0 +1,197 @@ +// +// Disk_types.h +// V64 +// +// Created by Dirk Hoffmann on 04.06.18. +// + +#ifndef DISK_TYPES_H +#define DISK_TYPES_H + +#include + +/* Overview: + * + * ----------------------------------------------------------------- + * Track layout: | 1 | 1.5 | 2 | 2.5 | ... | 35 | 35.5 | ... | 42 | 42.5 | + * ----------------------------------------------------------------- + * Halftrack addressing: | 1 | 2 | 3 | 4 | | 69 | 70 | | 83 | 84 | + * Track addressing: | 1 | | 2 | | | 35 | | | 42 | | + * ----------------------------------------------------------------- + */ + + +// +// Constants +// + +/*! @brief Maximum number of tracks on a single disk. + * @note Tracks are indexed from 1 to 42. There is no track 0! + */ +static const unsigned maxNumberOfTracks = 42; + +/*! @brief Maximum number of halftracks on a single disk. + * @note Tracks are indexed from 1 to 84. There is no halftrack 0! + */ +static const unsigned maxNumberOfHalftracks = 84; + +/*! @brief Maximum number of sectors in a single track + * @details Sectors are numbered from 0 to 20. + */ +static const unsigned maxNumberOfSectors = 21; + +/*! @brief Maximum number of bits stored on a single track. + * @details Each track can store a maximum of 7928 bytes. The exact number depends on + * the track number (inner tracks contain fewer bytes) and the actual write + * speed of a drive. + */ +static const unsigned maxBytesOnTrack = 7928; + +/*! @brief Maximum number of bits stored on a single track. + */ +static const unsigned maxBitsOnTrack = maxBytesOnTrack * 8; + +/*! @brief Returns the average duration of a single bit in 1/10 nano seconds. + * @details The returned value is the time span the drive head resists + * over a single bit. + * @note The exact value depends on the speed zone and the drive's + * rotation speed. We assume a rotation speed of 300 rpm. + */ + static const uint64_t averageBitTimeSpan[] = { + 4 * 10000, // 4 * 16/16 * 10^4 1/10 nsec + 4 * 9375, // 4 * 15/16 * 10^4 1/10 nsec + 4 * 8750, // 4 * 14/16 * 10^4 1/10 nsec + 4 * 8125 // 4 * 13/16 * 10^4 1/10 nsec + }; + +/*! @brief Average number of bits stored on a single track. + * @note The values are based on a drive with 300 rotations per minute + * which means that a full rotation lasts 2.000.000.000 1/10 nsec. + */ +static const unsigned averageBitsOnTrack[4] = { + 50000, // 200.000.000.000 / averageBitTimeSpan[0] + 53333, // 200.000.000.000 / averageBitTimeSpan[1] + 57142, // 200.000.000.000 / averageBitTimeSpan[2] + 61528 // 200.000.000.000 / averageBitTimeSpan[3] +}; + +/*! @brief Average number of bytes stored on a single track. + * @note The values are based on a drive with 300 rotations per minute. + */ +static const unsigned averageBytesOnTrack[4] = { + 6250, // averageBitsOnTrack[0] / 8 + 6666, // averageBitsOnTrack[1] / 8 + 7142, // averageBitsOnTrack[2] / 8 + 7692 // averageBitsOnTrack[3] / 8 +}; + +/*! @brief Size of a sector header block in bits. + */ +static const unsigned headerBlockSize = 10 * 8; + +/*! @brief Size of a sector data block in bits. + * @details Each data block consists of 325 GCR bytes (coding 260 real bytes) + */ +static const unsigned dataBlockSize = 325 * 8; + +/*! @brief Maximum number of files that can be stored on a single disk + * @details VC1541 DOS stores the directors on track 18 which contains 19 sectors. + * Sector 0 is reserved for the BAM. Each of the remaining sectors can + * hold up to 8 directory entries, summing um to a total of 144 items. + */ +// static const unsigned maxNumberOfFiles = 144; + + +// +// Types +// + +/*! @brief Data type for addressing full tracks on disk + * @see Halftrack + */ +typedef unsigned Track; + +/*! @brief Checks if a given number is a valid track number + */ +static inline bool isTrackNumber(unsigned nr) { return 1 <= nr && nr <= maxNumberOfTracks; } + +/*! @brief Data type for addressing half and full tracks on disk + * @details The VC1541 drive head can move between position 1 and 85. + * The odd numbers between 1 and 70 mark the 35 tracks that + * are used by VC1541 DOS. This means that DOS moves the + * drive head always two positions up or down. If programmed + * manually, the head can also be position on half tracks + * and on tracks beyond 35. + * @see Track + */ +typedef unsigned Halftrack; + +/*! @brief Checks if a given number is a valid halftrack number + */ +static inline bool isHalftrackNumber(unsigned nr) { return 1 <= nr && nr <= maxNumberOfHalftracks; } + +//! @brief Data type for addressing sectors inside a track +typedef unsigned Sector; + +//! @brief Checks if a given number is a valid sector number +static inline bool isSectorNumber(unsigned nr) { return nr < maxNumberOfSectors; } + +//! @brief Returns the number of sectors stored in a track +static inline unsigned numberOfSectorsInTrack(Track t) { + return (t < 1) ? 0 : (t < 18) ? 21 : (t < 25) ? 19 : (t < 31) ? 18 : (t < 43) ? 17 : 0; } + +//! @brief Returns the number of sectors stored in a halftrack +static inline unsigned numberOfSectorsInHalftrack(Halftrack ht) { + return numberOfSectorsInTrack((ht + 1) / 2); } + +//! @brief Returns the default speed zone of a track +static inline unsigned speedZoneOfTrack(Track t) { + return (t < 18) ? 3 : (t < 25) ? 2 : (t < 31) ? 1 : 0; } + +//! @brief Returns the default speed zone of a halftrack +static inline unsigned speedZoneOfHalftrack(Halftrack ht) { + return (ht < 35) ? 3 : (ht < 49) ? 2 : (ht < 61) ? 1 : 0; } + +//! @brief Checks if the given pair is a valid track / sector combination +static inline bool isValidTrackSectorPair(Track t, Sector s) { + return s < numberOfSectorsInTrack(t); +} + +//! @brief Checks if the given pair is a valid halftrack / sector combination +static inline bool isValidHalftrackSectorPair(Halftrack ht, Sector s) { + return s < numberOfSectorsInHalftrack(ht); +} + +//! @brief Data type for specifying the head position inside a track +typedef int32_t HeadPosition; + +//! @brief Layout information of a single sector +typedef struct { + size_t headerBegin; + size_t headerEnd; + size_t dataBegin; + size_t dataEnd; +} SectorInfo; + +//! @brief Information about a single track as gathered by analyzeTrack() +/*! @note To provide a fast access, the the track data is stored as a byte stream. + * Each byte represents a single bit and is either 0 or 1. The stored sequence + * is repeated twice to ease the handling of wrap arounds. + */ +typedef struct { + + // Length of the track in bits + size_t length; + + // Track data + union { + uint8_t bit[2 * maxBitsOnTrack]; + uint64_t byte[2 * maxBytesOnTrack]; + }; + + // Sector layout data + SectorInfo sectorInfo[22]; + +} TrackInfo; + +#endif diff --git a/C64/Drive/Drive.cpp b/C64/Drive/Drive.cpp new file mode 100755 index 00000000..884da566 --- /dev/null +++ b/C64/Drive/Drive.cpp @@ -0,0 +1,554 @@ +/*! + * @file Drive.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +VC1541::VC1541(unsigned nr) +{ + assert(nr == 1 || nr == 2); + + deviceNr = nr; + setDescription(deviceNr == 1 ? "Drive1" : "Drive2"); + cpu.setDescription(deviceNr == 1 ? "Drive1CPU" : "Drive2CPU"); + debug(3, "Creating %s at address %p\n", getDescription()); + + // Register sub components + VirtualComponent *subcomponents[] = { &mem, &cpu, &via1, &via2, &disk, NULL }; + registerSubComponents(subcomponents, sizeof(subcomponents)); + + // Register snapshot items + SnapshotItem items[] = { + + // Life-time items + { &sendSoundMessages, sizeof(sendSoundMessages), KEEP_ON_RESET }, + { &durationOfOneCpuCycle, sizeof(durationOfOneCpuCycle), KEEP_ON_RESET }, + { &poweredOn, sizeof(poweredOn), KEEP_ON_RESET }, + + // Internal state + { &spinning, sizeof(spinning), CLEAR_ON_RESET }, + { &redLED, sizeof(redLED), CLEAR_ON_RESET }, + { &elapsedTime, sizeof(elapsedTime), CLEAR_ON_RESET }, + { &nextClock, sizeof(nextClock), CLEAR_ON_RESET }, + { &nextCarry, sizeof(nextCarry), CLEAR_ON_RESET }, + { &carryCounter, sizeof(carryCounter), CLEAR_ON_RESET }, + { &counterUF4, sizeof(counterUF4), CLEAR_ON_RESET }, + { &bitReadyTimer, sizeof(bitReadyTimer), CLEAR_ON_RESET }, + { &byteReadyCounter, sizeof(byteReadyCounter), CLEAR_ON_RESET }, + { &halftrack, sizeof(halftrack), CLEAR_ON_RESET }, + { &offset, sizeof(offset), CLEAR_ON_RESET }, + { &zone, sizeof(zone), CLEAR_ON_RESET }, + { &readShiftreg, sizeof(readShiftreg), CLEAR_ON_RESET }, + { &writeShiftreg, sizeof(writeShiftreg), CLEAR_ON_RESET }, + { &sync, sizeof(sync), CLEAR_ON_RESET }, + { &byteReady, sizeof(byteReady), CLEAR_ON_RESET }, + + // Disk properties (will survive reset) + { &insertionStatus, sizeof(insertionStatus), KEEP_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); + + insertionStatus = NOT_INSERTED; + sendSoundMessages = true; + resetDisk(); +} + +VC1541::~VC1541() +{ + debug(3, "Releasing VC1541...\n"); +} + +void +VC1541::reset() +{ + VirtualComponent::reset(); + + cpu.regPC = 0xEAA0; + halftrack = 41; +} + +void +VC1541::resetDisk() +{ + debug (3, "Resetting disk in VC1541...\n"); + + disk.clearDisk(); +} + +void +VC1541::ping() +{ + VirtualComponent::ping(); + + c64->putMessage(poweredOn ? MSG_VC1541_ATTACHED : MSG_VC1541_DETACHED, deviceNr); + c64->putMessage(redLED ? MSG_VC1541_RED_LED_ON : MSG_VC1541_RED_LED_OFF, deviceNr); + c64->putMessage(spinning ? MSG_VC1541_MOTOR_ON : MSG_VC1541_MOTOR_OFF, deviceNr); + c64->putMessage(hasDisk() ? MSG_VC1541_DISK : MSG_VC1541_NO_DISK, deviceNr); + c64->putMessage(hasModifiedDisk() ? MSG_DISK_UNSAVED : MSG_DISK_SAVED, deviceNr); + +} + +void +VC1541::setClockFrequency(uint32_t frequency) +{ + durationOfOneCpuCycle = 10000000000 / frequency; + debug("Duration a CPU cycle is %lld 1/10 nsec.\n", durationOfOneCpuCycle); +} + +void +VC1541::dump() +{ + msg("VC1541\n"); + msg("------\n\n"); + msg(" Bit ready timer : %d\n", bitReadyTimer); + msg(" Head position : Track %d, Bit offset %d\n", halftrack, offset); + msg(" SYNC : %d\n", sync); + msg(" Read mode : %s\n", readMode() ? "YES" : "NO"); + msg("\n"); + mem.dump(); + startTracing(); +} + +void +VC1541::powerUp() +{ + suspend(); + reset(); + resume(); +} + +bool +VC1541::execute(uint64_t duration) +{ + uint8_t result = true; + + elapsedTime += duration; + while (nextClock < elapsedTime || nextCarry < elapsedTime) { + + if (nextClock <= nextCarry) { + + // Execute CPU and VIAs + uint64_t cycle = ++cpu.cycle; + result = cpu.executeOneCycle(); + if (cycle >= via1.wakeUpCycle) via1.execute(); else via1.idleCounter++; + if (cycle >= via2.wakeUpCycle) via2.execute(); else via2.idleCounter++; + updateByteReady(); + if (c64->iec.isDirtyDriveSide) c64->iec.updateIecLinesDriveSide(); + + nextClock += 10000; + + } else { + + // Execute read/write logic + if (spinning) executeUF4(); + nextCarry += delayBetweenTwoCarryPulses[zone]; + } + } + assert(nextClock >= elapsedTime && nextCarry >= elapsedTime); + + return result; +} + +/* +bool +VC1541::execute(uint64_t duration) +{ + uint8_t result = true; + + elapsedTime += duration; + + if (nextCarry < elapsedTime && nextCarry < nextClock) { + // Execute read/write logic + if (spinning) executeUF4(); + nextCarry += delayBetweenTwoCarryPulses[zone]; + } + + if (nextClock < elapsedTime) { + // Execute CPU and VIAs + uint64_t cycle = ++cpu.cycle; + if (cycle >= via1.wakeUpCycle) via1.execute(); else via1.idleCounter++; + if (cycle >= via2.wakeUpCycle) via2.execute(); else via2.idleCounter++; + result = cpu.executeOneCycle(); + nextClock += 10000; + } + + if (nextCarry < elapsedTime) { + // Execute read/write logic + if (spinning) executeUF4(); + nextCarry += delayBetweenTwoCarryPulses[zone]; + } + + assert(nextClock >= elapsedTime && nextCarry >= elapsedTime); + return result; +} +*/ + +void +VC1541::executeUF4() +{ + // Increase counter + counterUF4++; + carryCounter++; + + // We assume that a new bit comes in every fourth cycle. + // Later, we can decouple timing here to emulate asynchronicity. + if (carryCounter % 4 == 0) { + + // When a bit comes in and ... + // ... it's value equals 0, nothing happens. + // ... it's value equals 1, counter UF4 is reset. + if (readMode() && readBitFromHead()) { + counterUF4 = 0; + } + rotateDisk(); + } + + // Update SYNC signal + sync = (readShiftreg & 0x3FF) != 0x3FF || writeMode(); + if (!sync) byteReadyCounter = 0; + + // The lower two bits of counter UF4 are used to clock the logic board: + // + // (6) Load the write shift register + // | if the byte ready counter equals 7. + // v + // ---- ---- ---- ---- + // QBQA: | 00 01 | 10 11 | 00 01 | 10 11 | + // ---- ---- ---- ---- + // ^ ^ ^ ^ ^ + // | | | | | + // | | | (2) Byte ready is always 1 here. + // | (1) (1) Byte ready may be 0 here. + // | + // (3) Execute UE3 (the byte ready counter) + // (4) Execute write shift register + // (5) Execute read shift register + // + + switch (counterUF4 & 0x03) { + + case 0x00: + case 0x01: + + // Computation of the Byte Ready and the Load signal + // + // 74LS191 --- + // ------- VIA2::CA2 ---| | + // SYNC --o| Load | UF4::QB --o| & |o-- Byte Ready + // QB ---| Clk | ---| | + // | QD | --- | --- + // | QC |--| | --- | --- + // | QB |--| & |o--| 1 |o-----------| | + // | QA |--| | --- UF4::QB --| & |o-- load UD3 + // ------- --- UF4::QA --| | + // UE3 --- + + // (1) Update value on Byte Ready line + updateByteReady(); + break; + + case 0x02: + + // (2) + raiseByteReady(); + + // (3) Execute byte ready counter + byteReadyCounter = sync ? (byteReadyCounter + 1) % 8 : 0; + + // (4) Execute the write shift register + if (writeMode() && !getLightBarrier()) { + writeBitToHead(writeShiftreg & 0x80); + disk.setModified(true); + } + writeShiftreg <<= 1; + + // (5) Execute read shift register + readShiftreg <<= 1; + readShiftreg |= ((counterUF4 & 0x0C) == 0); + break; + + case 0x03: + + // (6) + if (byteReadyCounter == 7) { + writeShiftreg = via2.getPA(); + } + break; + } +} + +void +VC1541::updateByteReady() +{ + // + // 74LS191 --- + // ------- VIA2::CA2 ---| | + // SYNC --o| Load | UF4::QB --o| & |o-- Byte Ready + // QB ---| Clk | ---| | + // | QD | --- | --- + // | QC |--| | --- | + // | QB |--| & |o--| 1 |o--------- + // | QA |--| | --- + // ------- --- + // UE3 + + bool ca2 = via2.getCA2(); + bool qb = counterUF4 & 0x02; + bool ue3 = (byteReadyCounter == 7); + bool newByteReady = !(ca2 && !qb && ue3); + + if (byteReady != newByteReady) { + byteReady = newByteReady; + via2.CA1action(byteReady); + } +} + +void +VC1541::raiseByteReady() +{ + if (!byteReady) { + byteReady = true; + via2.CA1action(true); + } +} + +/* +void +VC1541::setByteReadyLine(bool value) +{ + if (byteReady != value) { + byteReady = value; + via2.CA1action(value); + } +} +*/ + +void +VC1541::setZone(uint2_t value) +{ + assert(is_uint2_t(value)); + + if (value != zone) { + debug(2, "Switching from disk zone %d to disk zone %d\n", zone, value); + zone = value; + } +} + +void +VC1541::powerOn() +{ + if (poweredOn) return; + + suspend(); + + poweredOn = true; + if (soundMessagesEnabled()) + c64->putMessage(MSG_VC1541_ATTACHED_SOUND, deviceNr); + ping(); + + resume(); +} + +void +VC1541::powerOff() +{ + if (!poweredOn) return; + + suspend(); + + reset(); + + poweredOn = false; + if (soundMessagesEnabled()) + c64->putMessage(MSG_VC1541_DETACHED_SOUND, deviceNr); + ping(); + + resume(); +} + +void +VC1541::setRedLED(bool b) +{ + if (!redLED && b) { + redLED = true; + c64->putMessage(MSG_VC1541_RED_LED_ON, deviceNr); + } else if (redLED && !b) { + redLED = false; + c64->putMessage(MSG_VC1541_RED_LED_OFF, deviceNr); + } +} + +void +VC1541::setRotating(bool b) +{ + if (!spinning && b) { + spinning = true; + c64->putMessage(MSG_VC1541_MOTOR_ON, deviceNr); + } else if (spinning && !b) { + spinning = false; + c64->putMessage(MSG_VC1541_MOTOR_OFF, deviceNr); + } +} + +void +VC1541::moveHeadUp() +{ + if (halftrack < 84) { + + float position = (float)offset / (float)disk.lengthOfHalftrack(halftrack); + halftrack++; + offset = (HeadPosition)(position * disk.lengthOfHalftrack(halftrack)); + + debug(2, "Moving head up to halftrack %d (track %2.1f) (offset %d)\n", + halftrack, (halftrack + 1) / 2.0, offset); + debug(2, "Halftrack %d has %d bits.\n", halftrack, disk.lengthOfHalftrack(halftrack)); + } + + assert(disk.isValidHeadPositon(halftrack, offset)); + + c64->putMessage(MSG_VC1541_HEAD_UP, deviceNr); + if (halftrack % 2 && sendSoundMessages) { + // Play sound for full tracks, only + c64->putMessage(MSG_VC1541_HEAD_UP_SOUND, deviceNr); + } +} + +void +VC1541::moveHeadDown() +{ + if (halftrack > 1) { + float position = (float)offset / (float)disk.lengthOfHalftrack(halftrack); + halftrack--; + offset = (HeadPosition)(position * disk.lengthOfHalftrack(halftrack)); + + debug(2, "Moving head down to halftrack %d (track %2.1f)\n", + halftrack, (halftrack + 1) / 2.0); + debug(2, "Halftrack %d has %d bits.\n", halftrack, disk.lengthOfHalftrack(halftrack)); + } + + assert(disk.isValidHeadPositon(halftrack, offset)); + + c64->putMessage(MSG_VC1541_HEAD_DOWN, deviceNr); + if (halftrack % 2 && sendSoundMessages) + // Play sound for full tracks, only + c64->putMessage(MSG_VC1541_HEAD_DOWN_SOUND, deviceNr); +} + +void +VC1541::setModifiedDisk(bool value) +{ + disk.setModified(value); + c64->putMessage(value ? MSG_DISK_UNSAVED : MSG_DISK_SAVED, deviceNr); +} + +void +VC1541::prepareToInsert() +{ + c64->resume(); + + debug("prepareToInsert\n"); + assert(insertionStatus == NOT_INSERTED); + + // Block the light barrier by taking the disk half out + insertionStatus = PARTIALLY_INSERTED; + + c64->resume(); +} + +void +VC1541::insertDisk(AnyArchive *a) +{ + suspend(); + + debug("insertDisk\n"); + assert(a != NULL); + assert(insertionStatus == PARTIALLY_INSERTED); + + switch (a->type()) { + + case D64_FILE: + disk.clearDisk(); + disk.encodeArchive((D64File *)a); + break; + + case G64_FILE: + disk.clearDisk(); + disk.encodeArchive((G64File *)a); + break; + + default: { + + // All other archives cannot be encoded directly. + // We convert them to a D64 archive first. + + D64File *converted = D64File::makeWithAnyArchive(a); + disk.clearDisk(); + disk.encodeArchive(converted); + break; + } + } + + insertionStatus = FULLY_INSERTED; + + c64->putMessage(MSG_VC1541_DISK, deviceNr); + c64->putMessage(MSG_DISK_SAVED, deviceNr); + if (sendSoundMessages) + c64->putMessage(MSG_VC1541_DISK_SOUND, deviceNr); + + resume(); +} + +void +VC1541::prepareToEject() +{ + suspend(); + + debug("prepareToEject\n"); + assert(insertionStatus == FULLY_INSERTED); + + // Block the light barrier by taking the disk half out + insertionStatus = PARTIALLY_INSERTED; + + // Make sure the drive can no longer read from this disk + disk.clearDisk(); + + resume(); +} + +void +VC1541::ejectDisk() +{ + suspend(); + + debug("ejectDisk\n"); + assert(insertionStatus == PARTIALLY_INSERTED); + + // Unblock the light barrier by taking the disk out + insertionStatus = NOT_INSERTED; + + // Notify listener + c64->putMessage(MSG_VC1541_NO_DISK, deviceNr); + if (sendSoundMessages) + c64->putMessage(MSG_VC1541_NO_DISK_SOUND, deviceNr); + + resume(); +} + diff --git a/C64/Drive/Drive.h b/C64/Drive/Drive.h new file mode 100755 index 00000000..a135ba34 --- /dev/null +++ b/C64/Drive/Drive.h @@ -0,0 +1,478 @@ +/*! + * @header Drive.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This implementation is based on the following two documents written + * by Ruud Baltissen. Ruud, thank you for this excellent work! + * + * Description: http://www.baltissen.org/newhtm/1541a.htm + * Schematics: http://www.baltissen.org/images/1540.gif + */ + +#ifndef _VC1541_INC +#define _VC1541_INC + +#include "VIA.h" +#include "Disk.h" + +/*! + * @brief A Commodore VC 1541 disk drive + */ +class VC1541 : public VirtualComponent { + + // + // Constants + // + + /*! @brief Time between two carry pulses of UE7 in 1/10 nano seconds + * @details The VC1541 drive is clocked by 16 Mhz. The base frequency is + * divided by N where N ranges from 13 (density bits = 11) to 16 + * (density bits = 00). On the logic board, this is done with + * a 4-bit counter of type 74SL193 whose reset value bits are + * connected to the two density bits (PB5 and PB6 of VIA2). It + * follows that a single bit is ready after approx. 3.25 CPU + * cycles in the fastest zone and approx. 4 CPU cycles in the + * slowest zone. + */ + const uint64_t delayBetweenTwoCarryPulses[4] = { + 10000, // Density bits = 00: Carry pulse every 16/16 * 10^4 1/10 nsec + 9375, // Density bits = 01: Carry pulse every 15/16 * 10^4 1/10 nsec + 8750, // Density bits = 10: Carry pulse every 14/16 * 10^4 1/10 nsec + 8125 // Density bits = 11: Carry pulse every 13/16 * 10^4 1/10 nsec + }; + + + // + // Sub components + // + +public: + + //! @brief Memory of the virtual drive + VC1541Memory mem = VC1541Memory(this); + + //! @brief The drive's CPU + CPU cpu = CPU(MOS_6502, &mem); + + //! @brief VIA6522 connecting the drive CPU with the IEC bus + VIA1 via1 = VIA1(this); + + //! @brief VIA6522 connecting the drive CPU with the read/write head + VIA2 via2 = VIA2(this); + + //! @brief A single sided 5,25" floppy disk + Disk disk; + + + // + // Drive status + // + +private: + + //! @brief Internal number of this drive + /*! @details The first drive has number 1, the second drive number 2, etc. + * The number is set once and never changed afterwards. + * When the drive writes a message into the message queue, the + * drive number is provided in the data field to let the GUI + * know about the sender. + */ + unsigned deviceNr; + + //! @brief Indicates whether the disk drive is powered on. + bool poweredOn; + + //! @brief Indicates whether the disk is rotating. + bool spinning; + + //! @brief Indicates whether the red LED is on. + bool redLED; + + //! @brief Indicates if or how a disk is inserted. + DiskInsertionStatus insertionStatus; + + //! @brief Indicates whether the drive shall send sound notifications. + bool sendSoundMessages; + + + // + // Clocking logic + // + + //! @brief Elapsed time since power up in 1/10 nano seconds + uint64_t elapsedTime; + + //! @brief Duration of a single CPU clock cycle in 1/10 nano seconds + uint64_t durationOfOneCpuCycle; + + /*! @brief Indicates when the next drive clock cycle occurs. + * @details The VC1541 drive is clocked by 16 MHz. The clock signal is + * fed into a counter which serves as a frequency divider. It's + * output is used to clock the drive's CPU and the two VIA chips. + */ + int64_t nextClock; + + /*! @brief Indicates when the next carry output pulse occurs on UE7. + * @details The 16 MHz signal is also fed into UE7, a 74SL193 4-bit + * couter, which generates a carry output signal on overflow. + * The pre-load inputs of this counter are connected to PB5 and + * PB6 of VIA2 (the 'density bits'). This means that a carry + * signal is generated every 13th cycle (from the 16 Mhz clock) + * when both density bits are 0 and every 16th cycle when both + * density bits are 1. The carry signal drives uf4, a counter of + * the same type. + */ + int64_t nextCarry; + +public: + + /*! @brief Counts the number of carry pulses from UE7. + * @details In a perfect setting, a new bit is read from or written to the + * drive after four carry pulses. + */ + int64_t carryCounter; + + /*! @brief The second 74SL193 4-bit counter on the logic board. + * @details This counter is driven by the carry output of UE7. It has + * four outputs QA, QB, QC, and QD. QA and QB are used to clock + * most of the other components. QC and QD are fed into a + * NOR gate whose output is connected to the serial input pin of + * the input shift register. + */ + uint4_t counterUF4; + + + // + // Read/Write logic + // + + //! @brief The next bit will be ready after this number of cycles. + int16_t bitReadyTimer; + + /*! @brief Byte ready counter (UE3) + * @details The VC1540 logic board contains a 4-bit-counter of type + * 72LS191 which is advanced whenever a bit is ready. By reaching + * 7, the counter signals that a byte is ready. In that case, + * the write shift register is loaded with new data and pin CA1 + * of VIA2 changes state. This state change causes the current + * contents of the read shift register to be latched into the + * input register of VIA2. + */ + uint8_t byteReadyCounter; + + //! @brief Halftrack position of the read/write head + Halftrack halftrack; + + //! @brief Position of the drive head inside the current track + HeadPosition offset; + + /*! @brief Current disk zone + * @details Each track belongs to one of four zones. Whenever the drive + * moves the r/w head, it computes the new number and writes into + * PB5 and PB6 of via2. These bits are hard-wired to a 74LS193 + * counter on the logic board that breaks down the 16 Mhz base + * frequency. This mechanism is used to slow down the read/write + * process on inner tracks. + */ + uint8_t zone; + + /*! @brief The 74LS164 serial to parallel shift register + * @details In read mode, this register is fed by the drive head with data. + */ + uint16_t readShiftreg; + + /*! @brief The 74LS165 parallel to serial shift register + * @details In write mode, this register feeds the drive head with data. + */ + uint8_t writeShiftreg; + + /*! @brief Current value of the SYNC line + * @details The SYNC signal plays an important role for timing + * synchronization. It becomes true when the beginning of a SYNC + * is detected. On the logic board, the SYNC signal is computed + * by a NAND gate that combines the 10 previously read bits from + * the input shift register and VIA2::CB2 (the r/w mode pin). + * Connecting CB2 to the NAND gates ensures that SYNC can only be + * true in read mode. When SYNC becomes false (meaning that a 0 + * was pushed into the shift register), the byteReadyCounter is + * reset. + */ + bool sync; + + /*! @brief Current value of the ByteReady line + * @details This signal goes low when a byte has been processed. + */ + bool byteReady; + + public: + + // + //! @functiongroup Creating and destructing + // + + //! @brief Custom Constructor + /*! @param deviceNr must be 1 (first drive) or 2 (second drive). + */ + VC1541(unsigned deviceNr); + + //! @brief Standard destructor + ~VC1541(); + + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void ping(); + void dump(); + void setClockFrequency(uint32_t frequency); + + /*! @brief Resets all disk related properties + * @note This method is needed, because reset() keeps the disk alive. + */ + void resetDisk(); + + + // + //! @functiongroup Configuring the device + // + + //! @brief Returns true if sound messages are sent to the GUI. + bool soundMessagesEnabled() { return sendSoundMessages; } + + //! @brief Enables or disables sending of sound messages. + void setSendSoundMessages(bool b) { sendSoundMessages = b; } + + + // + //! @functiongroup Working with the drive + // + + //! @brief Returns the device number + /*! @return 0 for the first drive, 1 for the second. + */ + unsigned getDeviceNr() { return deviceNr; } + + //! @brief Returns true iff the drive is powered on. + bool isPoweredOn() { return poweredOn; } + + //! @brief Returns true iff the drive is powered off. + bool isPoweredOff() { return !poweredOn; } + + //! @brief Powers the drive on + void powerOn(); + + //! @brief Powers the drive off + void powerOff(); + + //! @brief Toggle between power on and power off + void togglePowerSwitch() { isPoweredOn() ? powerOff() : powerOn(); } + + //! @brief Returns true iff the red drive LED is on. + bool getRedLED() { return redLED; }; + + //! @brief Turns red drive LED on or off. + void setRedLED(bool b); + + //! @brief Returns true iff the drive engine is on. + bool isRotating() { return spinning; }; + + //! @brief Turns the drive engine on or off. + void setRotating(bool b); + + + // + //! @functiongroup Handling virtual disks + // + + //! @brief Returns true if a disk is partially inserted. + bool hasPartiallyInsertedDisk() { return insertionStatus == PARTIALLY_INSERTED; } + + //! @brief Returns true if a disk is fully inserted. + bool hasDisk() { return insertionStatus == FULLY_INSERTED; } + + //! @brief Returns true if a modified disk is fully inserted. + bool hasModifiedDisk() { return hasDisk() && disk.isModified(); } + + //! @brief Sets or clears the modification flags of a disk + void setModifiedDisk(bool value); + + //! @brief Returns true if a write protected disk is fully inserted. + bool hasWriteProtectedDisk() { return hasDisk() && disk.isWriteProtected(); } + + /*! @brief Prepares to insert a disk + * @details This functions puts a disk partially into the drive. + * disk. As a result, the light barrier is blocked. + * @warning Only call this functions if no disk is inserted. + */ + void prepareToInsert(); + + /*! @brief Inserts an archive as a virtual disk. + * @warning Make sure to eject a previously inserted disk before calling + * this function. + * @note Inserting an archive as a disk is a time consuming task + * because variouls conversion have to take place. E.g., if you + * provide a T64 archive, it is first converted to a D64 archive. + * After that, all tracks will be GCR-encoded and written to a + * new disk. + */ + void insertDisk(AnyArchive *a); + + /*! @brief Returns the current state of the write protection barrier + * @details If the light barrier is blocked, the drive head is unable to + * modify bits on disk. + * @note We block the write barrier on power up for about 1.5 sec, + * because the drive enters write mode during the power up phase. + * I'm unsure if this is normal drive behavior or an emulator + * bug. Any hint on this is very welcome! + */ + bool getLightBarrier() { + return + (cpu.cycle < 1500000) + || hasPartiallyInsertedDisk() + || disk.isWriteProtected(); + } + + /*! @brief Prepares to eject a disk + * @details This functions opens the drive lid and partially removes the + * disk. As a result, no data can be read any more and the light + * barrier is blocked. + * @warning Only call this functions if a disk is inserted. + */ + void prepareToEject(); + + /*! @brief Finishes the ejection of a disk + * @details This function assumes that the drive lid is already open. + * It fully removes the disk and frees the light barrier. + * @note To eject a disk in the right way, make sure that some time + * elapsed between the two calls to prepareToEject() and + * ejectDisk(). Otherwise, the VC1541 DOS does not recognize + * the ejection. + */ + void ejectDisk(); + + + // + //! @functiongroup Running the device + // + + //! @brief Cold starts the floppy drive + /*! @details Mimics the effect of switching the drive off and on again. + */ + void powerUp(); + + //! @brief Executes all pending cycles of the virtual drive + /*! @details The number of cycles is determined by the target time + * which is elapsedTime + duration. + */ + bool execute(uint64_t duration); + +private: + + //! @brief Emulates a trigger event on the carry output pin of UE7. + void executeUF4(); + +public: + + /*! @brief Returns true iff drive is in read mode + * @details The drive is in read mode iff port pin VIA2::CB2 equals 1. + */ + bool readMode() { return via2.getCB2(); } + + //! @brief Returns true iff drive is in write mode + bool writeMode() { return !readMode(); } + + //! @brief Returns the halftrack under the drive head + Halftrack getHalftrack() { return halftrack; } + + //! @brief Moves the drive head to the specified track + void setTrack(Track t) { assert(isTrackNumber(t)); halftrack = 2 * t - 1; } + + //! @brief Moves the drive head to the specified halftrack + void setHalftrack(Halftrack ht) { assert(isHalftrackNumber(ht)); halftrack = ht; } + + //! @brief Returns the number of bits in the current halftrack + uint16_t sizeOfCurrentHalftrack() { + return hasDisk() ? disk.lengthOfHalftrack(halftrack) : 0; } + + //! @brief Returns the position of the drive head inside the current track + HeadPosition getOffset() { return offset; } + + //! @brief Sets the position of the drive head inside the current track + void setOffset(HeadPosition pos) { + if (hasDisk() && disk.isValidHeadPositon(halftrack, pos)) offset = pos; + } + + //! @brief Moves head one halftrack up + void moveHeadUp(); + + //! @brief Moves head one halftrack down + void moveHeadDown(); + + //! @brief Returns the current value of the sync signal + bool getSync() { return sync; } + + //! @brief Updates the value on the byte ready line + /*! @note The byte ready line is connected to the CA1 pin of VIA2. + * Pulling this signal low causes important side effects. + * Firstly, the contents of the read shift register is latched + * into the VIA chip. Secondly, the V flag is set inside the CPU. + * @seealso CA1action() + */ + void updateByteReady(); + + //! @brief Raises the byte ready line + void raiseByteReady(); + + //! @brief Returns the current track zone (0 to 3) + bool getZone() { return zone; } + + /*! @brief Sets the current track zone + * @param z drive zone (0 to 3) + */ + void setZone(uint2_t value); + + /*! @brief Reads a single bit from the disk head + * @result 0 or 1 + */ + uint8_t readBitFromHead() { return disk.readBitFromHalftrack(halftrack, offset); } + + //! @brief Writes a single bit to the disk head + void writeBitToHead(uint8_t bit) { + disk.writeBitToHalftrack(halftrack, offset, bit); } + + //! @brief Advances drive head position by one bit + void rotateDisk() { if (++offset >= disk.lengthOfHalftrack(halftrack)) offset = 0; } + + //! @brief Moves drive head position back by one bit + void rotateBack() { if (--offset < 0) offset = disk.lengthOfHalftrack(halftrack) - 1; } + +private: + + //! @brief Advances drive head position by eight bits + void rotateDiskByOneByte() { for (unsigned i = 0; i < 8; i++) rotateDisk(); } + + //! @brief Moves drive head position back by eight bits + void rotateBackByOneByte() { for (unsigned i = 0; i < 8; i++) rotateBack(); } +}; + +#endif diff --git a/C64/Drive/DriveMemory.cpp b/C64/Drive/DriveMemory.cpp new file mode 100755 index 00000000..aeda26c6 --- /dev/null +++ b/C64/Drive/DriveMemory.cpp @@ -0,0 +1,137 @@ +/*! + * @file DriveMemory.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "C64.h" + +VC1541Memory::VC1541Memory(VC1541 *drive) +{ + setDescription("1541MEM"); + debug(3, " Creating VC1541 memory at %p...\n", this); + + this->drive = drive; + + memset(rom, 0, sizeof(rom)); + stack = &ram[0x0100]; + + // Register snapshot items + SnapshotItem items[] = { + + { ram, sizeof(ram), KEEP_ON_RESET }, + { rom, sizeof(rom), KEEP_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +VC1541Memory::~VC1541Memory() +{ + debug(3, " Releasing VC1541 memory at %p...\n", this); +} + +void +VC1541Memory::reset() +{ + VirtualComponent::reset(); + + // Initialize RAM with powerup pattern (pattern from Hoxs64) + for (unsigned i = 0; i < sizeof(ram); i++) { + ram[i] = (i & 64) ? 0xFF : 0x00; + } +} + +void +VC1541Memory::dump() +{ + msg("VC1541 Memory:\n"); + msg("--------------\n\n"); + msg("VC1541 ROM :%s loaded\n", romIsLoaded() ? "" : " not"); + msg("\n"); +} + +uint8_t +VC1541Memory::peek(uint16_t addr) +{ + if (addr >= 0x8000) { + + // 0xC000 - 0xFFFF : ROM + // 0x8000 - 0xBFFF : ROM (repeated) + //return mem[addr | 0xC000]; + return rom[addr & 0x3FFF]; + + } else { + + // Map to range 0x0000 - 0x1FFF + addr &= 0x1FFF; + + // 0x0000 - 0x07FF : RAM + // 0x0800 - 0x17FF : unmapped + // 0x1800 - 0x1BFF : VIA 1 (repeats every 16 bytes) + // 0x1C00 - 0x1FFF : VIA 2 (repeats every 16 bytes) + return + (addr < 0x0800) ? ram[addr] : + (addr < 0x1800) ? addr >> 8 : + (addr < 0x1C00) ? drive->via1.peek(addr & 0xF) : + drive->via2.peek(addr & 0xF); + } +} + +uint8_t +VC1541Memory::spypeek(uint16_t addr) +{ + if (addr >= 0x8000) { + return rom[addr & 0x3FFF]; + } else { + addr &= 0x1FFF; + return + (addr < 0x0800) ? ram[addr] : + (addr < 0x1800) ? addr >> 8 : + (addr < 0x1C00) ? drive->via1.spypeek(addr & 0xF) : + drive->via2.spypeek(addr & 0xF); + } +} + +void +VC1541Memory::poke(uint16_t addr, uint8_t value) +{ + if (addr >= 0x8000) { // ROM + return; + } + + // Map to range 0x0000 - 0x1FFF + addr &= 0x1FFF; + + if (addr < 0x0800) { // RAM + ram[addr] = value; + return; + } + + if (addr >= 0x1C00) { // VIA 2 + drive->via2.poke(addr & 0xF, value); + return; + } + + if (addr >= 0x1800) { // VIA 1 + drive->via1.poke(addr & 0xF, value); + return; + } +} + diff --git a/C64/Drive/DriveMemory.h b/C64/Drive/DriveMemory.h new file mode 100755 index 00000000..0324da64 --- /dev/null +++ b/C64/Drive/DriveMemory.h @@ -0,0 +1,99 @@ +/*! + * @header DriveMemory.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _VC1541MEMORY_INC +#define _VC1541MEMORY_INC + +#include "Memory.h" + +class VC1541; + +/*! @brief Represents RAM and ROM of a virtual VC1541 floopy disk drive. + */ +class VC1541Memory : public Memory { + + private: + + //! @brief Reference to the connected disk drive + VC1541 *drive; + + public: + + //! @brief Random Access Memory + uint8_t ram[0x0800]; + + //! @brief Read Only Memory + uint8_t rom[0x4000]; + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Constructor + VC1541Memory(VC1541 *drive); + + //! @brief Destructor + ~VC1541Memory(); + + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void dump(); + + + // + //! @functiongroup Accessing ROM + // + + //! @brief Returns true iff the ROM image has been loaded. + bool romIsLoaded() { return (rom[0] | rom[1]) != 0x00; } + + //! @brief Removes the ROM image from memory + void deleteRom() { memset(rom, 0, sizeof(rom)); } + + /*! @brief Computes a 64-bit fingerprint for the VC1541 ROM. + * @return fingerprint or 0, if no Basic ROM is installed. + */ + uint64_t romFingerprint() { + return romIsLoaded() ? fnv_1a_64(rom, sizeof(rom)) : 0; } + + + // + //! @functiongroup Accessing RAM + // + + // Reading from memory + uint8_t peek(uint16_t addr); + uint8_t peekZP(uint8_t addr) { return ram[addr]; } + + // Reading from memory without side effects + uint8_t spypeek(uint16_t addr); + + // Writing into memory + void poke(uint16_t addr, uint8_t value); + void pokeZP(uint8_t addr, uint8_t value) { ram[addr] = value; } +}; + +#endif diff --git a/C64/Drive/Drive_types.h b/C64/Drive/Drive_types.h new file mode 100755 index 00000000..f7e7fe6f --- /dev/null +++ b/C64/Drive/Drive_types.h @@ -0,0 +1,35 @@ +/*! + * @header DriveTypes.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef VC1541_TYPES_H +#define VC1541_TYPES_H + +inline bool isValidDriveNr(unsigned nr) { return nr == 1 || nr == 2; } + +/*! @enum Disk insertion status + */ +typedef enum { + NOT_INSERTED = 0, + PARTIALLY_INSERTED, + FULLY_INSERTED +} DiskInsertionStatus; + +#endif diff --git a/C64/Drive/VIA.cpp b/C64/Drive/VIA.cpp new file mode 100755 index 00000000..981d12db --- /dev/null +++ b/C64/Drive/VIA.cpp @@ -0,0 +1,1062 @@ +/*! + * @file VIA.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +// +// VIA 6522 (Commons) +// + +VIA6522::VIA6522(VC1541 *drive) +{ + setDescription("VIA"); + + this->drive = drive; + + // Register snapshot items + SnapshotItem items[] = { + { &pa, sizeof(pa), CLEAR_ON_RESET }, + { &ca1, sizeof(ca1), CLEAR_ON_RESET }, + { &ca2, sizeof(ca2), CLEAR_ON_RESET }, + { &pb, sizeof(pb), CLEAR_ON_RESET }, + { &cb1, sizeof(cb1), CLEAR_ON_RESET }, + { &cb2, sizeof(cb2), CLEAR_ON_RESET }, + { &ddra, sizeof(ddra), CLEAR_ON_RESET }, + { &ddrb, sizeof(ddrb), CLEAR_ON_RESET }, + { &ora, sizeof(ora), CLEAR_ON_RESET }, + { &orb, sizeof(orb), CLEAR_ON_RESET }, + { &ira, sizeof(ira), CLEAR_ON_RESET }, + { &irb, sizeof(irb), CLEAR_ON_RESET }, + { &t1, sizeof(t1), CLEAR_ON_RESET }, + { &t1_latch_lo, sizeof(t1_latch_lo), CLEAR_ON_RESET }, + { &t1_latch_hi, sizeof(t1_latch_hi), CLEAR_ON_RESET }, + { &t2, sizeof(t2), CLEAR_ON_RESET }, + { &t2_latch_lo, sizeof(t2_latch_lo), CLEAR_ON_RESET }, + { &pcr, sizeof(pcr), CLEAR_ON_RESET }, + { &acr, sizeof(acr), CLEAR_ON_RESET }, + { &ier, sizeof(ier), CLEAR_ON_RESET }, + { &ifr, sizeof(ifr), CLEAR_ON_RESET }, + { &sr, sizeof(sr), CLEAR_ON_RESET }, + { &delay, sizeof(delay), CLEAR_ON_RESET }, + { &feed, sizeof(feed), CLEAR_ON_RESET }, + { &tiredness, sizeof(tiredness), CLEAR_ON_RESET }, + { &wakeUpCycle, sizeof(wakeUpCycle), CLEAR_ON_RESET }, + { &idleCounter, sizeof(idleCounter), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +VIA6522::~VIA6522() +{ +} + +void VIA6522::reset() +{ + VirtualComponent::reset(); + + t1 = 0x01AA; + t2 = 0x01AA; + t1_latch_hi = 0x01; + t1_latch_lo = 0x05; // Makes "drive/defaults.prg" happy + t2_latch_lo = 0xAA; + feed = (VIACountA0 | VIACountB0); +} + +void +VIA6522::dump() +{ + const char *latchingA = inputLatchingEnabledA() ? "enabled" : "disabled"; + const char *latchingB = inputLatchingEnabledB() ? "enabled" : "disabled"; + uint16_t t1Latch = LO_HI(t1_latch_lo, t1_latch_hi); + uint16_t t2Latch = LO_HI(t2_latch_lo, 0); + + msg("VIA:\n"); + msg("----\n\n"); + msg(" Input register (IRA) : %02X\n", ira); + msg(" Input register (IRB) : %02X\n", irb); + msg(" Output register (ORA) : %02X\n", ora); + msg(" Output register (ORB) : %02X\n", orb); + msg(" Data direction register (DDRA) : %02X\n", ddra); + msg(" Data direction register (DDRB) : %02X\n", ddrb); + msg("Peripheral control register (PCR) : %02X\n", pcr); + msg(" Auxiliary register (ACR) : %02X\n", acr); + msg(" Interrupt enable register (IER) : %02X\n", ier); + msg(" Interrupt flag register (IFR) : %02X\n", ifr); + msg(" Shift register (SR) : %02X\n", sr); + msg(" Input latching A : %s\n", latchingA); + msg(" Input latching B : %s\n", latchingB); + msg(" Timer 1 : %d (latched: %d)\n", t1, t1Latch); + msg(" Timer 2 : %d (latched: %d)\n", t2, t2Latch); + msg(" IO memory : "); + msg("\n"); +} + +bool +VIA6522::isVia2() +{ + return this == &drive->via2; +} + + +// +// Execution functions +// + +void +VIA6522::execute() +{ + wakeUp(); + + uint64_t oldDelay = delay; + uint64_t oldFeed = feed; + + // Execute timers + executeTimer1(); + executeTimer2(); + + // Trigger interrupt if requested + if (unlikely(delay & VIAClrInterrupt1)) { + releaseIrqLine(); + } + if (unlikely(delay & VIAInterrupt1)) { + pullDownIrqLine(); + } + + // Set or clear CA2 or CB2 if requested + if (unlikely(delay & (VIASetCA1out1 | VIAClearCA1out1 | VIASetCA2out1 | VIAClearCA2out1 | VIASetCB2out1 | VIAClearCB2out1))) { + if (delay & VIASetCA1out1) { setCA1(true); } + if (delay & VIAClearCA1out1) { setCA1(false); } + if (delay & VIASetCA2out1) { ca2 = true; } + if (delay & VIAClearCA2out1) { ca2 = false; } + if (delay & VIASetCB2out1) { cb2 = true; } + if (delay & VIAClearCB2out1) { cb2 = false; } + } + + // Move trigger event flags left and feed in new bits + delay = ((delay << 1) & VIAClearBits) | feed; + + // Go into idle state if possible + if (oldDelay == delay && oldFeed == feed) { + if (++tiredness > 4) { + sleep(); + tiredness = 0; + } + } else { + tiredness = 0; + } +} + +void +VIA6522::executeTimer1() +{ + // Reload counter + if (delay & VIAReloadA2) { + t1 = HI_LO(t1_latch_hi, t1_latch_lo); + } + + // Decrement counter + else if (delay & VIACountA1) { + t1--; + } + + // Check for timer underflow + if (t1 == 0) { + + // Reload counter in 2 cycles + delay |= VIAReloadA0; + + // Check if an interrupt should be triggered + if (!(feed & VIAPostOneShotA0)) { + + // Set interrupt flag + setInterruptFlag_T1(); + + // Toggle PB7 output bit + feed ^= VIAPB7out0; + } + + // Prevent further interrupts in one-shot mode + if (!freeRun()) { + feed |= VIAPostOneShotA0; + } + } +} + +void +VIA6522::executeTimer2() +{ + // Decrement counter + if (delay & VIACountB1) { + t2--; + } + + // Check for timer underflow + if (t2 == 0 && (delay & VIACountB0)) { + + // Check if an interrupt should be triggered + if (!(delay & VIAPostOneShotB0)) { + + // Set interrupt flag + setInterruptFlag_T2(); + + // Prevent further interrupts + feed |= VIAPostOneShotB0; + } + } +} + + +// +// Peeking and poking +// + +uint8_t +VIA6522::peek(uint16_t addr) +{ + assert (addr <= 0xF); + + wakeUp(); + + switch(addr) { + + case 0x0: // ORB - Output register B + return peekORB(); + + case 0x1: // ORA - Output register A + return peekORA(true); + + case 0x2: // DDRB - Data direction register B + return ddrb; + + case 0x3: // DDRA - Data direction register A + return ddra; + + case 0x4: // T1 low-order counter + + // "8 BITS FROM T1 LOW-ORDER COUNTER TRANSFERRED TO MPU. IN ADDITION T1 INTERRUPT FLAG + // IS RESET (BIT 6 IN INTERRUPT FLAG REGISTER)" [F. K.] + + clearInterruptFlag_T1(); + return LO_BYTE(t1); + + case 0x5: // T1 high-order counter + + // "8 BITS FROM T1 HIGH-ORDER COUNTER TRANSFERRED TO MPU2" [F. K.] + + return HI_BYTE(t1); + + case 0x6: // T1 low-order latch + + // "8 BITS FROM T1 LOW ORDER-LATCHES TRANSFERRED TO MPU. UNLIKE REG 4 OPERATION, + // THIS DOES NOT CAUSE RESET OF T1 INTERRUPT FLAG" [F. K.] + + return t1_latch_lo; + + case 0x7: // T1 high-order latch + + // "8 BITS FROM T1 HIGH-ORDER LATCHES TRANSFERRED TO MPU + return t1_latch_hi; + + case 0x8: // T2 low-order latch/counter + + // "8 BITS FROM T2 LOW-ORDER COUNTER TRANSFERRED TO MPU. T2 INTERRUPT FLAG IS RESET" [F. K.] + + clearInterruptFlag_T2(); + return LO_BYTE(t2); + + case 0x9: // T2 high-order counter COUNTER TRANSFERRED TO MPU" [F. K.] + + // "8 BITS FROM T2 HIGH-ORDER + return HI_BYTE(t2); + + case 0xA: // Shift register + + clearInterruptFlag_SR(); + return sr; + + case 0xB: // Auxiliary control register + + return acr; + + case 0xC: // Peripheral control register + + return pcr; + + case 0xD: // IFR - Interrupt Flag Register + + assert((ifr & 0x80) == 0); + assert((ier & 0x80) == 0); + return ifr | ((ifr & ier) ? 0x80 : 0x00); + + case 0xE: // Interrupt enable register + + return ier | 0x80; // Bit 7 (set/clear bit) always shows up as 1 + + case 0xF: // ORA - Output register A (no handshake) + return peekORA(false); + } + + assert(0); + return 0; +} + +uint8_t +VIA6522::peekORA(bool handshake) +{ + clearInterruptFlag_CA1(); + + uint8_t CA2control = (pcr >> 1) & 0x07; // ----xxx- + + switch (CA2control) { + case 0: // Input mode: Interrupt on negative edge + clearInterruptFlag_CA2(); + break; + case 1: // Input mode: Interrupt on negative edge, no register clearance + break; + case 2: // Input mode: Interrupt on positive edge + clearInterruptFlag_CA2(); + break; + case 3: // Input mode: Interrupt on positive edge, no register clearance + break; + case 4: // Handshake output mode + // Set CA2 output low on a read or write of the Peripheral A Output + // Register. Reset CA2 high with an active transition on CAl. + clearInterruptFlag_CA2(); + if (handshake) delay |= VIAClearCA2out1; + break; + case 5: // Pulse output mode + // CA2 goes low for one cycle following a read or write of the + // Peripheral A Output Register. + clearInterruptFlag_CA2(); + if (handshake) delay |= VIAClearCA2out1 | VIASetCA2out0; + break; + case 6: // Manual output mode (keep line low) + break; + case 7: // Manual output mode (keep line low) + break; + } + + // Update processor port + updatePA(); + + // Update input register + if (!inputLatchingEnabledA()) { + ira = pa; + } + return ira; +} + +uint8_t +VIA6522::peekORB() +{ + clearInterruptFlag_CB1(); + + uint8_t CB2control = (pcr >> 5) & 0x07; // xxx----- + + switch (CB2control) { + case 0: // Input mode: Interrupt on negative edge + clearInterruptFlag_CB2(); + break; + case 1: // Input mode: Interrupt on negative edge, no register clearance + break; + case 2: // Input mode: Interrupt on positive edge + clearInterruptFlag_CB2(); + break; + case 3: // Input mode: Interrupt on positive edge, no register clearance + break; + case 4: // Handshake output mode + // In contrast to CA2, CB2 is only affected on write accesses. + break; + case 5: // Pulse output mode + // In contrast to CA2, CB2 is only affected on write accesses. + break; + case 6: // Manual output mode (keep line low) + break; + case 7: // Manual output mode (keep line low) + break; + } + + // Update processor port + updatePB(); + + // Update input register + if (!inputLatchingEnabledB()) { + irb = pb; + } + return irb; +} + +uint8_t +VIA6522::spypeek(uint16_t addr) +{ + assert (addr <= 0xF); + + // spypeek is not functional yet, because the VIA counter values are + // wrong if the chip is in idle state. Fix this before using this function. + // Look at CIA::spypeek to see how this can be done. + assert(false); + + switch(addr) { + + case 0x4: // T1 low-order counter + + return LO_BYTE(t1); + + case 0x8: // T2 low-order latch/counter + + return LO_BYTE(t2); + + case 0xA: // Shift register + case 0xB: // Auxiliary control register + case 0xC: // Peripheral control register + + break; // TODO + + case 0xD: { // IFR - Interrupt Flag Register + + uint8_t ioD = ifr & 0x7F; + uint8_t irq = (ifr & ier) ? 0x80 : 0x00; + return ioD | irq; + } + + default: + return peek(addr); + } + + return 0; +} + +void VIA6522::poke(uint16_t addr, uint8_t value) +{ + assert (addr <= 0x0F); + + wakeUp(); + + switch(addr) { + + case 0x0: // ORB - Output register B + + pokeORB(value); + return; + + case 0x1: // ORA - Output register A + + pokeORA(value, true); + return; + + case 0x2: // DDRB - Data direction register B + + // "0" ASSOCIATED PB PIN IS AN INPUT (HIGH IMPEDANCE) + // "1" ASSOCIATED PB PIN IS AN OUTPUT WHOSE LEVEL IS DETERMINED BY ORB REGISTER BIT" [F. K.] + ddrb = value; + updatePB(); + return; + + case 0x3: // DDRA - Data direction register A + + // "0" ASSOCIATED PB PIN IS AN INPUT (HIGH IMPEDANCE) + // "1" ASSOCIATED PB PIN IS AN OUTPUT WHOSE LEVEL IS DETERMINED BY ORA REGISTER BIT" [F. K.] + + ddra = value; + updatePA(); + return; + + case 0x4: // T1L-L (write) / T1C-L (read) + + // (1) Write low order latch. + + t1_latch_lo = value; // (1) + return; + + case 0x5: // T1C-H (read and write) + + // Write into high order latch. + t1_latch_hi = value; + + // Write into high order counter + // and transfer low order latch into low order counter. + t1 = HI_LO(value, t1_latch_lo); + + // Reset T1 interrupt flag. + clearInterruptFlag_T1(); + + // If ACR7 = 1, a "write T1C-H" operation will cause PB7 to go low. + if (PB7OutputEnabled()) { + feed ^= VIAPB7out0; + } + + delay |= VIAReloadA2; + feed &= ~(VIAPostOneShotA0); + delay &= ~(VIAPostOneShotA0); + delay &= ~(VIACountA1 | VIAReloadA1); + updatePB(); + return; + + case 0x6: // T1L-L (read and write) + + // Write low order latch. + t1_latch_lo = value; + + return; + + case 0x7: // T1L-H (read and write) + + // Write high order latch. + t1_latch_hi = value; + + // (2) Reset Tl interrupt flag. + clearInterruptFlag_T1(); + return; + + case 0x8: // T2L-L (write) / T2C-L (read) + + t2_latch_lo = value; + return; + + case 0x9: // T2C-H + + t2 = HI_LO(value, t2_latch_lo); + clearInterruptFlag_T2(); + feed &= ~(VIAPostOneShotB0); + delay &= ~(VIAPostOneShotB0); + delay &= ~(VIACountB1 | VIAReloadB1); + return; + + case 0xA: // Shift register + + clearInterruptFlag_SR(); + sr = value; + return; + + case 0xB: // Auxiliary control register + + acr = value; + + if (acr & 0x20) { + + // In pulse counting mode, T2 counts negative pulses on PB6, + // so we disable automatic counting. + delay &= ~(VIACountB0); + feed &= ~VIACountB0; + + } else { + + // In timed interrupt mode, T2 counts down every cycle. + delay |= VIACountB0; + feed |= VIACountB0; + } + + if (acr & 0x80) { + // Output shows up at port pin PB7, starting with '1'. + feed |= VIAPB7out0; + } + updatePB(); + return; + + case 0xC: // Peripheral control register + + pokePCR(value); + return; + + case 0xD: // IFR - Interrupt Flag Register + + // Writing 1 will clear the corresponding bit + ifr &= ~value; + // IRQ(); + if (ifr & ier) { + pullDownIrqLine(); + } else { + releaseIrqLine(); + } + return; + + case 0xE: // IER - Interrupt Enable Register + + // Bit 7 distinguishes between set and clear + // If bit 7 is 1, writing 1 will set the corresponding bit + // If bit 7 is 0, writing 1 will clear the corresponding bit + if (value & 0x80) { + ier |= value; + } else { + ier &= ~value; + } + ier &= 0x7F; + // IRQ(); + if (ifr & ier) { + pullDownIrqLine(); + } else { + releaseIrqLine(); + } + return; + + case 0xF: // ORA - Output register A (no handshake) + + pokeORA(value, false); + return; + } +} + +void +VIA6522::pokeORA(uint8_t value, bool handshake) +{ + clearInterruptFlag_CA1(); + + // Take care of side effects + switch ((pcr >> 1) & 0x07) { + case 0: // Input mode: Interrupt on negative edge + clearInterruptFlag_CA2(); + break; + case 1: // Input mode: Interrupt on negative edge, no register clearance + break; + case 2: // Input mode: Interrupt on positive edge + clearInterruptFlag_CA2(); + break; + case 3: // Input mode: Interrupt on positive edge, no register clearance + break; + case 4: // Handshake output mode + clearInterruptFlag_CA2(); + if (handshake) delay |= VIAClearCA2out1; + break; + case 5: // Pulse output mode + clearInterruptFlag_CA2(); + if (handshake) delay |= VIAClearCA2out1 | VIASetCA2out0; + break; + case 6: // Manual output mode (keep line low) + break; + case 7: // Manual output mode (keep line low) + break; + } + + ora = value; + updatePA(); +} + +void +VIA6522::pokeORB(uint8_t value) +{ + clearInterruptFlag_CB1(); + + // Take care of side effects + switch ((pcr >> 5) & 0x07) { + case 0: // Input mode: Interrupt on negative edge + clearInterruptFlag_CB2(); + break; + case 1: // Input mode: Interrupt on negative edge, no register clearance + break; + case 2: // Input mode: Interrupt on positive edge + clearInterruptFlag_CB2(); + break; + case 3: // Input mode: Interrupt on positive edge, no register clearance + break; + case 4: // Handshake output mode + clearInterruptFlag_CB2(); + delay |= VIAClearCB2out1; + break; + case 5: // Pulse output mode + clearInterruptFlag_CB2(); + delay |= VIAClearCB2out1 | VIAClearCB2out0; + break; + case 6: // Manual output mode (keep line low) + break; + case 7: // Manual output mode (keep line low) + break; + } + + orb = value; + updatePB(); +} + +void +VIA6522::pokePCR(uint8_t value) +{ + pcr = value; + + // Check CA2 control bits + switch(ca2Control()) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + ca2 = true; + break; + case 6: // Hold CA2 low + ca2 = false; + break; + case 7: // Hold CA2 high + ca2 = true; + break; + } + + // Check CB2 control bits + switch(cb2Control()) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + cb2 = true; + break; + case 6: // Hold CB2 low + cb2 = false; + break; + case 7: // Hold CB2 high + cb2 = true; + break; + } +} + +uint8_t +VIA6522::portAinternal() +{ + return ora; +} + +void +VIA6522::updatePA() +{ + pa = (portAinternal() & ddra) | (portAexternal() & ~ddra); +} + +uint8_t +VIA6522::portBinternal() +{ + return orb; +} + +void +VIA6522::updatePB() +{ + pb = (portBinternal() & ddrb) | (portBexternal() & ~ddrb); + + if (PB7OutputEnabled()) { + if (feed & VIAPB7out0) { + SET_BIT(pb, 7); + } else { + CLR_BIT(pb, 7); + } + } +} + +/* +void +VIA6522::toggleCA1() +{ + // Check for active transition (positive or negative edge) + uint8_t ctrl = ca1Control(); + bool active = (ca1 && ctrl == 0) || (!ca1 && ctrl == 1); + ca1 = !ca1; + + if (!ca1) + CA1LowAction(); + + if (!active) + return; + + // Set interrupt flag + setInterruptFlag_CA1(); + if (GET_BIT(ier, 1)) { + delay |= VIAInterrupt1; + } + + // Latch peripheral port into input register if latching is enabled + if (inputLatchingEnabledA()) { + updatePA(); + ira = pa; + } + + // Check for handshake mode with CA2 + if (ca2Control() == 4) { + ca2 = true; + } +} +*/ + +void +VIA6522::setCA1(bool value) +{ + if (ca1 == value) + return; + + ca1 = value; + + // VIA2 sets the V flag on a negative transition + if (!value && isVia2()) drive->cpu.setV(1); + + // Check for active transition (can be positive or negative) + uint8_t ctrl = ca1Control(); + bool active = (!ca1 && ctrl == 0) || (ca1 && ctrl == 1); + if (!active) return; + + // Set interrupt flag + SET_BIT(ifr, 1); + if (GET_BIT(ier, 1)) { + delay |= VIAInterrupt0; + } + + // Latch peripheral port into input register if latching is enabled + if (inputLatchingEnabledA()) { + updatePA(); + ira = pa; + } + + // Check for handshake mode with CA2 + if (ca2Control() == 4) { + ca2 = true; + } +} + +void +VIA6522::CA1action(bool value) +{ + wakeUp(); + + if (value) { + // delay |= VIASetCA1out0; + delay |= VIASetCA1out1; + } else { + // delay |= VIAClearCA1out0; + delay |= VIAClearCA1out1; + } +} + +void +VIA6522::sleep() +{ + assert(idleCounter == 0); + + // Determine maximum possible sleep cycles based on timer counts + uint64_t sleepA = (t1 > 2) ? (drive->cpu.cycle + t1 - 1) : 0; + uint64_t sleepB = (t2 > 2) ? (drive->cpu.cycle + t2 - 1) : 0; + + // VIAs with stopped timers can sleep forever + if (!(delay & VIACountA1)) sleepA = UINT64_MAX; + if (!(delay & VIACountB1)) sleepB = UINT64_MAX; + + wakeUpCycle = MIN(sleepA, sleepB); +} + +void +VIA6522::wakeUp() +{ + uint64_t idleCycles = idleCounter; + + // Make up for missed cycles + if (idleCycles) { + if (delay & VIACountA1) { + assert((delay & (VIACountA0)) != 0); + assert((feed & (VIACountA0)) != 0); + assert(t1 > idleCycles); + t1 -= idleCycles; + } else { + assert((delay & (VIACountA0)) == 0); + assert((feed & (VIACountA0)) == 0); + } + if (delay & VIACountB1) { + assert((delay & (VIACountB0)) != 0); + assert((feed & (VIACountB0)) != 0); + assert(t2 > idleCycles); + t2 -= idleCycles; + } else { + assert((delay & (VIACountB0)) == 0); + assert((feed & (VIACountB0)) == 0); + } + idleCounter = 0; + } + wakeUpCycle = 0; +} + + +// +// VIA 1 +// + +VIA1::VIA1(VC1541 *drive) : VIA6522(drive) +{ + setDescription("VIA1"); + debug(3, " Creating VIA1 at address %p...\n", this); +} + +VIA1::~VIA1() +{ + debug(3, " Releasing VIA1...\n"); +} + +void +VIA1::pullDownIrqLine() { + drive->cpu.pullDownIrqLine(CPU::INTSRC_VIA1); +} + +void +VIA1::releaseIrqLine() { + drive->cpu.releaseIrqLine(CPU::INTSRC_VIA1); +} + +uint8_t +VIA1::portAexternal() +{ + return 0xFF; +} + +uint8_t +VIA1::portBexternal() +{ + // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + // ----------------------------------------------------------------- + // | ATN | Device addr. | ATN | Clock | Clock | Data | Data | + // | in | | ack | out | in | out | in | + + uint8_t external = + (c64->iec.atnLine ? 0x00 : 0x80) | + (c64->iec.clockLine ? 0x00 : 0x04) | + (c64->iec.dataLine ? 0x00 : 0x01); + + external |= 0x1A; // All "out" pins are read as 1 + + if (drive->getDeviceNr() == 2) { + external |= 0x20; /* device number 9 */ + } + + return external; +} + +void +VIA1::updatePB() +{ + VIA6522::updatePB(); + c64->iec.setNeedsUpdateDriveSide(); + // c64->iec.updateIecLinesDriveSide(); +} + +// +// VIA 2 +// + +VIA2::VIA2(VC1541 *drive) : VIA6522(drive) +{ + setDescription("VIA2"); + debug(3, " Creating VIA2 at address %p...\n", this); +} + +VIA2::~VIA2() +{ + debug(3, " Releasing VIA2...\n"); +} + +uint8_t +VIA2::portAexternal() +{ + // TODO: Which value is returned in write mode? + return drive->readShiftreg & 0xFF; +} + +uint8_t +VIA2::portBexternal() +{ + bool sync = drive->getSync(); + bool barrier = drive->getLightBarrier(); + + return (sync ? 0x80 : 0x00) | (barrier ? 0x00 : 0x10) | 0x6F; +} + +void +VIA2::updatePB() +{ + uint8_t oldPb = pb; + VIA6522::updatePB(); + uint8_t newPb = pb; + + // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + // ----------------------------------------------------------------- + // | SYNC | Timer control | Write | LED | Rot. | Stepper motor | + // | | (4 disk zones)|protect| | motor | (head move) | + + // Bits 6 and 5 + if ((newPb & 0x60) != (oldPb & 0x60)) + drive->setZone((newPb >> 5) & 0x03); + + // Bit 3 + if (GET_BIT(newPb, 3) != GET_BIT(oldPb, 3)) + drive->setRedLED(GET_BIT(newPb, 3)); + + // Bit 2 + if (GET_BIT(newPb, 2) != GET_BIT(oldPb, 2)) + drive->setRotating(GET_BIT(newPb, 2)); + + // Head stepper motor + + // Bits 1 and 0 + /* + if ((pb & 0x03) != (oldPb & 0x03)) { + + // A decrease (00-11-10-01-00...) moves the head down + // An increase (00-01-10-11-00...) moves the head up + + if ((pb & 0x03) == ((oldPb + 1) & 0x03)) { + drive->moveHeadUp(); + } else if ((pb & 0x03) == ((oldPb - 1) & 0x03)) { + drive->moveHeadDown(); + } else { + warn("Unexpected stepper motor control sequence\n"); + } + } + */ + + if (newPb & 0x04) { // We only move the head if the motor is on + + // Relationship between halftracks and stepper positions: + // + // Halftrack number: 01 02 03 04 05 06 07 08 ... + // Stepper position: 0 1 2 3 0 1 2 3 ... + + int oldPos = (int)((drive->getHalftrack() - 1) & 0x03); + int newPos = (int)(newPb & 0x03); + + if (newPos != oldPos) { + if (newPos == ((oldPos + 1) & 0x03)) { + drive->moveHeadUp(); + // assert(newPos == ((drive->getHalftrack() - 1) & 0x03)); + } else if (newPos == ((oldPos - 1) & 0x03)) { + drive->moveHeadDown(); + // assert(newPos == ((drive->getHalftrack() - 1) & 0x03)); + } else { + debug(2, "Unexpected stepper motor control sequence\n"); + } + } + } +} + +void +VIA2::pullDownIrqLine() +{ + drive->cpu.pullDownIrqLine(CPU::INTSRC_VIA2); +} + +void +VIA2::releaseIrqLine() +{ + drive->cpu.releaseIrqLine(CPU::INTSRC_VIA2); +} + + diff --git a/C64/Drive/VIA.h b/C64/Drive/VIA.h new file mode 100755 index 00000000..c9bb3012 --- /dev/null +++ b/C64/Drive/VIA.h @@ -0,0 +1,565 @@ +/*! + * @header VIA.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This implementation is mainly based on the document + * + * "R6522 VERSATILE INTERFACE ADAPTER" by Frank Kontros [F. K.] + * + * and the Hoxs64 implementation by David Horrocks. + */ + +#ifndef _VIA6522_INC +#define _VIA6522_INC + +#include "VirtualComponent.h" + +class VC1541; + +#define VIACountA0 (1ULL << 0) // Forces timer 1 to decrement every cycle +#define VIACountA1 (1ULL << 1) +#define VIACountB0 (1ULL << 2) // Forces timer 2 to decrement every cycle +#define VIACountB1 (1ULL << 3) +#define VIAReloadA0 (1ULL << 4) // Forces timer 1 to reload +#define VIAReloadA1 (1ULL << 5) +#define VIAReloadA2 (1ULL << 6) +#define VIAReloadB0 (1ULL << 7) // Forces timer 2 to reload +#define VIAReloadB1 (1ULL << 8) +#define VIAReloadB2 (1ULL << 9) +#define VIAPostOneShotA0 (1ULL << 10) // Indicates that timer 1 has fired in one shot mode +#define VIAPostOneShotB0 (1ULL << 11) // Indicates that timer 2 has fired in one shot mode +#define VIAInterrupt0 (1ULL << 12) // Holds down the interrupt line +#define VIAInterrupt1 (1ULL << 13) +#define VIASetCA1out0 (1ULL << 14) // Sets CA2 pin high +#define VIASetCA1out1 (1ULL << 15) +#define VIAClearCA1out0 (1ULL << 16) // Sets CA2 pin low +#define VIAClearCA1out1 (1ULL << 17) +#define VIASetCA2out0 (1ULL << 18) // Sets CA2 pin high +#define VIASetCA2out1 (1ULL << 19) +#define VIAClearCA2out0 (1ULL << 20) // Sets CA2 pin low +#define VIAClearCA2out1 (1ULL << 21) +#define VIASetCB2out0 (1ULL << 22) // Sets CB2 pin high +#define VIASetCB2out1 (1ULL << 23) +#define VIAClearCB2out0 (1ULL << 24) // Sets CB2 pin low +#define VIAClearCB2out1 (1ULL << 25) +#define VIAPB7out0 (1ULL << 26) // Current value of PB7 pin (if output is enabled) +#define VIAClrInterrupt0 (1ULL << 27) // Releases the interrupt line +#define VIAClrInterrupt1 (1ULL << 28) + +#define VIAClearBits ~((1ULL << 29) | VIACountA0 | VIACountB0 | VIAReloadA0 | VIAReloadB0 | VIAPostOneShotA0 | VIAPostOneShotB0 | VIAInterrupt0 | VIASetCA1out0 | VIAClearCA1out0 | VIASetCA2out0 | VIAClearCA2out0 | VIASetCB2out0 | VIAClearCB2out0 | VIAPB7out0 | VIAClrInterrupt0) + +/*! @brief Virtual VIA6522 controller + @details The VC1541 drive contains two VIAs on its logic board. + */ +class VIA6522 : public VirtualComponent { + + friend class VC1541; + + protected: + + //! @brief Owner of this VIA + /*! @details Either a reference to the first or the second drive. + */ + VC1541 *drive; + + + // + // Peripheral interface + // + + //! @brief Peripheral port A + /*! @details "The Peripheral A port consists of 8 lines which can be + * individually programmed to act as an input or an output + * under control of a Data Direction Register. The polarity + * of output pins is controlled by an Output Register and + * input data can be latched into an internal register under + * control of the CA1 line." + */ + uint8_t pa; + + //! @brief Peripheral A control lines + /*! @details "The two peripheral A control lines act as interrupt inputs + * or as handshake outputs. Each line controls an internal + * interrupt flag with a corresponding interrupt enable bit. + * In addition, CA1controls the latching of data on + * Peripheral A Port input lines. The various modes of + * operation are controlled by the system processor through + * the internal control registers." + */ + bool ca1; + bool ca2; + + //! @brief Peripheral port B + /*! @details "The Peripheral B port consists of 8 lines which can be + * individually programmed to act as an input or an output + * under control of a Data Direction Register. The polarity + * of output pins is controlled by an Output Register and + * input data can be latched into an internal register under + * control of the CA1 line." + */ + uint8_t pb; + + //! @brief + /*! @details "The Peripheral B control lines act as interrupt inputs or + * as handshake outputs. As with CA1 and CA2, each line + * controls an interrupt flag with a corresponding interrupt + * enable bit. In addition, these lines act as a serial port + * under control of the Shift Register." + */ + bool cb1; + bool cb2; + + + // + // Port registers + // + + //! @brief Data direction registers + /*! @details "Each port has a Data Direction Register (DDRA, DDRB) for + * specifying whether the peripheral pins are to act as + * inputs or outputs. A 0 in a bit of the Data Direction + * Register causes the corresponding peripheral pin to act + * as an input. A 1 causes the pin to act as an output." + */ + uint8_t ddra; + uint8_t ddrb; + + //! @brief Output registers + /*! @details "Each peripheral pin is also controlled by a bit in the + * Output Register (ORA, ORB) and an Input Register (IRA, IRB). + * When the pin is programmed to act as an output, the voltage + * on the pin is controlled by the corre­sponding bit of the + * Output Register. A 1 in the Output Register causes the pin + * to go high, and a 0 causes the pin to go low. Data can be + * written into Output Register bits corresponding to pins + * which are programmed to act as inputs; however, the pin will + * be unaffected. + */ + uint8_t ora; + uint8_t orb; + + //! @brief Input registers + /*! @details "Reading a peripheral port causes the contents of the Input + * Register (IRA, IRB) to be transferred onto the Data Bus. + * With input latching disabled, IRA will always reflect the + * data on the PA pins. With input latching enabled, IRA will + * reflect the contents of the Port A prior to setting the CA1 + * Interrupt Flag (IFRl) by an active transition on CA1. + */ + uint8_t ira; + uint8_t irb; + + + // + // Timers + // + + /*! @brief VIA timer 1 + * @details "Interval Timer T1 consists of two 8-bit latches and a + * 16-bit counter. The latches store data which is to be + * loaded into the counter. After loading, the counter + * decrements at 02 clock rate. Upon reaching zero, an + * interrupt flag is set, and IRQ goes low if the T1 + * interrupt is enabled. Timer 1 then disables any further + * interrupts or automatically transfers the contents of + * the latches into the counter and continues to decrement. + * In addition, the timer may be programmed to invert the + * output signal on a peripheral pin (PB7) each time it + * "times-out." + */ + uint16_t t1; // T1C + uint8_t t1_latch_lo; // T1L_L + uint8_t t1_latch_hi; // T1L_H + + /*! @brief VIA timer 2 + * @details "Timer 2 operates as an interval timer (in the "one-shot" + * mode only), or as a counter for counting negative pulses + * on the PB6 peripheral pin. A single control bit in the + * Auxiliary Control Register selects between these two + * modes. This timer is comprised of a "write-only" low-order + * latch (T2L-L), a "read-only" low-order counter (T2C-L) and + * a read/write high order counter (T2C-H). The counter + * registers act as a 16-bit counter which decrements at + * 02 rate." + */ + uint16_t t2; // T1C + uint8_t t2_latch_lo; // T2L_L + + //! @brief Peripheral control register + uint8_t pcr; + + //! @brief Auxiliary register + uint8_t acr; + + //! @brief Interrupt enable register + uint8_t ier; + + //! @brief Interrupt flag register + uint8_t ifr; + + //! @brief Shift register + uint8_t sr; + + //! @brief Event triggering queue + uint64_t delay; + + //! @brief New bits to feed in + //! @details Bits set in this variable makes a trigger event persistent. + uint64_t feed; + + + // + // Speeding up emulation (sleep logic) + // + + //! @brief Idle counter + /*! @details When the VIA state does not change during execution, this + * variable is increased by one. If it exceeds a certain + * threshhold, the chip is put into idle state via sleep() + */ + uint8_t tiredness; + + //! @brief Wakeup cycle + uint64_t wakeUpCycle; + + //! @brief Number of skipped executions + uint64_t idleCounter; + +public: + + // + //! @functiongroup Constructing and destructing + // + + //! @brief Constructor + VIA6522(VC1541 *drive); + + //! @brief Destructor + ~VIA6522(); + + // + //! @functiongroup Methods from VirtualComponent + // + + void reset(); + void dump(); + + //! @brief Returns true if this object emulates is VIA2 + bool isVia2(); + + //! @brief Getter for data directon register A + uint8_t getDDRA() { return ddra; } + + //! @brief Getter for data directon register B + uint8_t getDDRB() { return ddrb; } + + //! @brief Getter for peripheral A port + uint8_t getPA() { return pa; } + + //! @brief Getter for peripheral B port + uint8_t getPB() { return pb; } + + //! @brief Getter for peripheral A control pin 2 + bool getCA2() { return ca2; } + + //! @brief Getter for peripheral B control pin 2 + bool getCB2() { return cb2; } + + //! @brief Executes the virtual VIA for one cycle. + void execute(); + +private: + + //! @brief Executes timer 1 for one cycle. + void executeTimer1(); + + //! @brief Executes timer 2 for one cycle. + void executeTimer2(); + +public: + + /*! @brief Special peek function for the I/O memory range + * @details The peek function only handles those registers that are + * treated similarly by both VIA chips + */ + virtual uint8_t peek(uint16_t addr); + +private: + + //! @brief Special peek function for output register A + /*! @details Variable handshake is needed to distiguish if ORA is read + * via address 0x1 (handshake enabled) or address 0xF (no handshake). + */ + uint8_t peekORA(bool handshake); + + //! @brief Special peek function for output register B + uint8_t peekORB(); + +public: + + //! @brief Same as peek, but without side effects + uint8_t spypeek(uint16_t addr); + + /*! @brief Special poke function for the I/O memory range + * @details The poke function only handles those registers that are treated + * similarly by both VIA chips + */ + void poke(uint16_t addr, uint8_t value); + +private: + + //! @brief Special poke function for output register A + /*! @details Variable handshake is needed to distiguish if ORA is written + * via address 0x1 (handshake enabled) or address 0xF (no handshake). + */ + void pokeORA(uint8_t value, bool handshake); + + //! @brief Special poke function for output register B + void pokeORB(uint8_t value); + + //! @brief Special poke function for the PCR register + void pokePCR(uint8_t value); + + + // + // Internal Configuration + // + + //! @brief Returns true iff timer 1 is in free-run mode (continous interrupts) + bool freeRun() { return (acr & 0x40) != 0; } + + //! @brief Returns true iff timer 2 counts pulses on pin PB6 + bool countPulses() { return (acr & 0x20) != 0; } + + //! @brief Returns true iff an output pulse is generated on each T1 load operation + bool PB7OutputEnabled() { return (acr & 0x80) != 0; } + + //! @brief Checks if input latching is enabled + bool inputLatchingEnabledA() { return (GET_BIT(acr,0)); } + + //! @brief Checks if input latching is enabled + bool inputLatchingEnabledB() { return (GET_BIT(acr,1)); } + + + // + // Peripheral Control Register (PCR) + // + + //! @brief Returns the CA1 control bit of the peripheral control register + uint8_t ca1Control() { return pcr & 0x01; } + + //! @brief Returns the three CA2 control bits of the peripheral control register + uint8_t ca2Control() { return (pcr >> 1) & 0x07; } + + //! @brief Returns the CB1 control bit of the peripheral control register + uint8_t cb1Control() { return (pcr >> 4) & 0x01; } + + //! @brief Returns the three CB2 control bits of the peripheral control register + uint8_t cb2Control() { return (pcr >> 5) & 0x07; } + + + // + // Ports + // + +protected: + + //! @brief Bit values driving port A from inside the chip + uint8_t portAinternal(); + + //! @brief Bit values driving port A from outside the chip + virtual uint8_t portAexternal() = 0; + + /*! @brief Computes the current bit values visible at port A + * @details Value is stored in variable pa + */ + virtual void updatePA(); + + //! @brief Bit values driving port B from inside the chip + uint8_t portBinternal(); + + //! @brief Bit values driving port B from outside the chip + virtual uint8_t portBexternal() = 0; + + /*! @brief Computes the current bit values visible at port B + * @details Value is stored in variable pb + */ + virtual void updatePB(); + + + // + // Peripheral control lines + // + +public: + + //! @brief Schedules a transition on the CA1 pin for the next cycle + void CA1action(bool value); + +private: + + //! @brief Performs a transition on the CA1 pin + void setCA1(bool value); + + + // + // Interrupt handling + // + +public: + + //! @brief Pulls down the IRQ line + virtual void pullDownIrqLine() = 0; + + //! @brief Releases the IRQ line + virtual void releaseIrqLine() = 0; + + /*! @brief Releases the IRQ line if IFR and IER have no matching bits. + * @details This method is invoked whenever a bit in the IFR or IER is + * is cleared. + */ + void releaseIrqLineIfNeeded() { if ((ifr & ier) == 0) delay |= VIAClrInterrupt0; } + + /* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * --------------------------------------------------------------------------------- + * | IRQ | Timer 1 | Timer 2 | CB1 | CB2 |Shift Reg| CA1 | CA2 | + * + * Timer 1 - Set by: Time-out of T1 + * Cleared by: Read t1 low or write t1 high + * Timer 2 - Set by: Time-out of T2 + * Cleared by: Read t2 low or write t2 high + * CB1 - Set by: Active edge on CB1 + * Cleared by: Read or write to register 0 (ORB) + * CB2 - Set by: Active edge on CB2 + * Cleared by: Read or write to register 0 (ORB), + * if CB2 is not selected as "INDEPENDENT". + * Shift Reg - Set by: 8 shifts completed + * Cleared by: Read or write to register 10 (0xA) + * CA1 - Set by: Active edge on CA1 + * Cleared by: Read or write to register 1 (ORA) + * CA2 - Set by: Active edge on CA2 + * Cleared by: Read or write to register 1 (ORA), + * if CA2 is not selected as "INDEPENDENT". + */ + + /*! @brief Sets the Timer 1 interrupt flag + * @details If the bit was 0, an interrupt is triggered if enabled. + */ + void setInterruptFlag_T1() { + if (!GET_BIT(ifr, 6) && GET_BIT(ier, 6)) delay |= VIAInterrupt0; + SET_BIT(ifr, 6); + } + /*! @brief Clears the Timer 1 interrupt flag + * @details The interrupt line may be cleared as a side effect. + */ + void clearInterruptFlag_T1() { CLR_BIT(ifr, 6); releaseIrqLineIfNeeded(); } + + /*! @brief Sets the Timer 2 interrupt flag + * @details If the bit was 0, an interrupt is triggered if enabled. + */ + void setInterruptFlag_T2() { + if (!GET_BIT(ifr, 5) && GET_BIT(ier, 5)) delay |= VIAInterrupt0; + SET_BIT(ifr, 5); + } + + /*! @brief Clears the Timer 2 interrupt flag + * @details The interrupt line may be cleared as a side effect. + */ + void clearInterruptFlag_T2() { CLR_BIT(ifr, 5); releaseIrqLineIfNeeded(); } + + /*! @brief Clears the CB1 interrupt flag + * @details The interrupt line may be cleared as a side effect. + */ + void clearInterruptFlag_CB1() { CLR_BIT(ifr, 4); releaseIrqLineIfNeeded(); } + + /*! @brief Clears the CB2 interrupt flag + * @details The interrupt line may be cleared as a side effect. + */ + void clearInterruptFlag_CB2() { CLR_BIT(ifr, 3); releaseIrqLineIfNeeded(); } + + /*! @brief Clears the Shift Register interrupt flag + * @details The interrupt line may be cleared as a side effect. + */ + void clearInterruptFlag_SR() { CLR_BIT(ifr, 2); releaseIrqLineIfNeeded(); } + + //! @brief Sets the CB1 interrupt flag + // void setInterruptFlag_CA1() { SET_BIT(ifr, 1); } + + /*! @brief Clears the CB1 interrupt flag + * @details The interrupt line may be cleared as a side effect. + */ + void clearInterruptFlag_CA1() { CLR_BIT(ifr, 1); releaseIrqLineIfNeeded(); } + + /*! @brief Clears the CA2 interrupt flag + * @details The interrupt line may be cleared as a side effect. + */ + void clearInterruptFlag_CA2() { CLR_BIT(ifr, 0); releaseIrqLineIfNeeded(); } + + + // + //! @functiongroup Speeding up emulation + // + + //! @brief Puts the VIA into idle state. + void sleep(); + + //! @brief Emulates all previously skipped cycles. + void wakeUp(); +}; + + +/*! @brief First virtual VIA6522 controller + * @details VIA1 serves as hardware interface between the VC1541 CPU + * and the IEC bus. + */ +class VIA1 : public VIA6522 { + +public: + + VIA1(VC1541 *drive); + ~VIA1(); + + uint8_t portAexternal(); + uint8_t portBexternal(); + void updatePB(); + void pullDownIrqLine(); + void releaseIrqLine(); +}; + +/*! @brief Second virtual VIA6522 controller + * @details VIA2 serves as hardware interface between the VC1541 CPU + * and the drive logic. + */ +class VIA2 : public VIA6522 { + +public: + + VIA2(VC1541 *drive); + ~VIA2(); + + uint8_t portAexternal(); + uint8_t portBexternal(); + void updatePB(); + void pullDownIrqLine(); + void releaseIrqLine(); +}; + +#endif diff --git a/C64/FileFormats/AnyArchive.cpp b/C64/FileFormats/AnyArchive.cpp new file mode 100755 index 00000000..78c805dd --- /dev/null +++ b/C64/FileFormats/AnyArchive.cpp @@ -0,0 +1,148 @@ +/*! + * @file AnyArchive.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "T64File.h" +#include "D64File.h" +#include "PRGFile.h" +#include "P00File.h" +#include "G64File.h" + +AnyArchive * +AnyArchive::makeWithFile(const char *path) +{ + assert(path != NULL); + + if (T64File::isT64File(path)) { + return T64File::makeWithFile(path); + } + if (D64File::isD64File(path)) { + return D64File::makeWithFile(path); + } + if (PRGFile::isPRGFile(path)) { + return PRGFile::makeWithFile(path); + } + if (P00File::isP00File(path)) { + return P00File::makeWithFile(path); + } + if (G64File::isG64File(path)) { + return G64File::makeWithFile(path); + } + return NULL; +} + +const unsigned short * +AnyArchive::getUnicodeNameOfItem() +{ + translateToUnicode(getNameOfItem(), unicode, 0xE000, sizeof(unicode) / 2); + return unicode; +} + +size_t +AnyArchive::getSizeOfItem() +{ + int size = 0; + + seekItem(0); + while (readItem() != EOF) + size++; + + seekItem(0); + return size; +} + +int +AnyArchive::readItem() +{ + int result; + + assert(iEof <= size); + + if (iFp < 0) + return -1; + + // Get byte + result = data[iFp++]; + + // Check for end of file + if (iFp == iEof) + iFp = -1; + + return result; +} + +const char * +AnyArchive::readItemHex(size_t num) +{ + assert(sizeof(name) > 3 * num); + + for (unsigned i = 0; i < num; i++) { + + int byte = readItem(); + if (byte == EOF) break; + sprintf(name + (3 * i), "%02X ", byte); + } + + return name; +} + +void +AnyArchive::flashItem(uint8_t *buffer) +{ + int byte; + assert(buffer != NULL); + + size_t offset = getDestinationAddrOfItem(); + + seekItem(0); + + while ((byte = readItem()) != EOF) { + if (offset <= 0xFFFF) { + buffer[offset++] = (uint8_t)byte; + } else { + break; + } + } +} + +void +AnyArchive::dumpDirectory() +{ + int numItems = numberOfItems(); + + msg("Archive: %s\n", getName()); + msg("-------\n"); + msg(" Path: %s\n", getPath()); + msg(" Items: %d\n", numItems); + + for (unsigned i = 0; i < numItems; i++) { + + selectItem(i); + msg(" Item %2d: %s (%d bytes, load address: %d)\n", + i, getNameOfItem(), getSizeOfItem(), getDestinationAddrOfItem()); + msg(" "); + selectItem(i); + for (unsigned j = 0; j < 8; j++) { + int byte = readItem(); + if (byte != -1) + msg("%02X ", byte); + } + msg("\n"); + } +} diff --git a/C64/FileFormats/AnyArchive.h b/C64/FileFormats/AnyArchive.h new file mode 100755 index 00000000..3c201a64 --- /dev/null +++ b/C64/FileFormats/AnyArchive.h @@ -0,0 +1,141 @@ +/*! + * @header AnyArchive.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * For a detailed description of the various file formats, see + * http://www.infinite-loop.at/Power20/Documentation/Power20-LiesMich/AE-Dateiformate.html + */ + +#ifndef _ANYARCHIVE_INC +#define _ANYARCHIVE_INC + +#include "AnyC64File.h" + +/*! @class AnyArchive + * @brief This class adds an API to AnyC64File for handling file formats + * that store a collection of multiple C64 files (archives). + */ +class AnyArchive : public AnyC64File { + +protected: + + /*! @brief File pointer + * @details An offset into the data range of the selected item. + */ + long iFp = -1; + + /*! @brief End of file position + * @details This value equals the last valid offset plus 1 + */ + long iEof = -1; + +public: + + // + //! @functiongroup Creating and destructing objects + // + + /*! @brief Factory method + * @return A T64File, D64File, PRGFile, P00File, or G64File object, + * depending on the type of the specified file. + */ + static AnyArchive *makeWithFile(const char *filename); + + + // + //! @functiongroup Selecting an item + // + + //! @brief Returns the number of items in this archive. + virtual int numberOfItems() { return 0; } + + /*! @brief Selects the active item + * @details All item related methods work on the active item. + */ + virtual void selectItem(unsigned item) { }; + + + // + //! @functiongroup Accessing item attributes + // + + /*! @brief Returns a string representation of the item type. + * @details E.g., "PRG" for a program file. + */ + virtual const char *getTypeOfItemAsString() { return ""; } + + //! @brief Returns the name of the selected item. + virtual const char *getNameOfItem() { return ""; } + + /*! @brief Returns the name of the selected item in unichar format. + * @details The returned unichars are compatible with font C64ProMono + * which is used, e.g., in the mount dialogs preview panel. + * @note Never call this function for nonexisting items. + */ + const unsigned short *getUnicodeNameOfItem(); + + + // + //! @functiongroup Reading data from the file + // + + //! @brief Returns the size of an item in bytes + virtual size_t getSizeOfItem(); + + //! @brief Returns the size of an item in blocks + virtual size_t getSizeOfItemInBlocks() { return (getSizeOfItem() + 253) / 254; } + + //! @brief Moves the file pointer to the specified offset. + /*! @details Use seek(0) to return to the beginning of the selected item. + */ + virtual void seekItem(long offset) { }; + + /*! @brief Reads a byte from the selected item. + * @return EOF (-1) if all bytes have been read in. + */ + virtual int readItem(); + + /*! @brief Reads multiple bytes in form of a hex dump string. + * @param Number of bytes ranging from 1 to 85. + */ + virtual const char *readItemHex(size_t num); + + /*! @brief Returns the proposed memory location of the selected item. + * @details When a file is flashed into memory, the item data is copied + * to this location. + * @seealso flashItem() + */ + virtual uint16_t getDestinationAddrOfItem() { return 0; } + + /*! @brief Flashes the selected item into memory. + * @param buffer must be a pointer to the C64 RAM + */ + void flashItem(uint8_t *buffer); + + + // + //! @functiongroup Debugging + // + + virtual void dumpDirectory(); + +}; + +#endif + diff --git a/C64/FileFormats/AnyC64File.cpp b/C64/FileFormats/AnyC64File.cpp new file mode 100755 index 00000000..95bba68f --- /dev/null +++ b/C64/FileFormats/AnyC64File.cpp @@ -0,0 +1,286 @@ +/*! + * @file AnyC64File.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "AnyC64File.h" + +AnyC64File::AnyC64File() +{ + const char *defaultName = "HELLO VIRTUALC64"; + memcpy(name, defaultName, strlen(defaultName) + 1); + + memset(name, 0, sizeof(name)); + memset(unicode, 0, sizeof(unicode)); +} + +AnyC64File::~AnyC64File() +{ + dealloc(); + + if (path) + free(path); +} + +void +AnyC64File::dealloc() +{ + if (data == NULL) { + assert(size == 0); + return; + } + + delete[] data; + data = NULL; + size = 0; + fp = -1; + eof = -1; +} + +/* +bool +AnyC64File::checkBufferHeader(const uint8_t *buffer, size_t length, const uint8_t *header) +{ + assert(buffer != NULL); + assert(header != NULL); + + unsigned i; + + for (i = 0; i < length && header[i] != 0; i++) { + if (header[i] != buffer[i]) + return false; + } + + return header[i] == 0; +} +*/ + +void +AnyC64File::setPath(const char *str) +{ + assert(str != NULL); + + // Set path + if (path) free(path); + path = strdup(str); + + // Set default name (path without suffix) + memset(name, 0, sizeof(name)); + char *filename = extractFilenameWithoutSuffix(path); + strncpy(name, filename, sizeof(name) - 1); + free(filename); + ascii2petStr(name); +} + +const unsigned short * +AnyC64File::getUnicodeName() +{ + translateToUnicode(getName(), unicode, 0xE000, sizeof(unicode) / 2); + return unicode; +} + +void +AnyC64File::seek(long offset) +{ + fp = (offset < size) ? offset : -1; +} + +int +AnyC64File::read() +{ + int result; + + assert(eof <= size); + + if (fp < 0) + return -1; + + // Get byte + result = data[fp++]; + + // Check for end of file + if (fp == eof) + fp = -1; + + return result; +} + +const char * +AnyC64File::readHex(size_t num) +{ + assert(sizeof(name) > 3 * num); + + for (unsigned i = 0; i < num; i++) { + + int byte = read(); + if (byte == EOF) break; + sprintf(name + (3 * i), "%02X ", byte); + } + + return name; +} + +void +AnyC64File::flash(uint8_t *buffer, size_t offset) +{ + int byte; + assert(buffer != NULL); + + seek(0); + + while ((byte = read()) != EOF) { + if (offset <= 0xFFFF) { + buffer[offset++] = (uint8_t)byte; + } else { + break; + } + } +} + +bool +AnyC64File::readFromBuffer(const uint8_t *buffer, size_t length) +{ + assert (buffer != NULL); + + dealloc(); + if ((data = new uint8_t[length]) == NULL) + return false; + + memcpy(data, buffer, length); + size = length; + eof = length; + fp = 0; + return true; +} + +bool +AnyC64File::readFromFile(const char *filename) +{ + assert (filename != NULL); + + bool success = false; + uint8_t *buffer = NULL; + FILE *file = NULL; + struct stat fileProperties; + + // Check file type + if (!hasSameType(filename)) { + goto exit; + } + + // Get file properties + if (stat(filename, &fileProperties) != 0) { + goto exit; + } + + // Open file + if (!(file = fopen(filename, "r"))) { + goto exit; + } + + // Allocate memory + if (!(buffer = new uint8_t[fileProperties.st_size])) { + goto exit; + } + + // Read from file + int c; + for (unsigned i = 0; i < fileProperties.st_size; i++) { + c = fgetc(file); + if (c == EOF) + break; + buffer[i] = (uint8_t)c; + } + + // Read from buffer (subclass specific behaviour) + dealloc(); + if (!readFromBuffer(buffer, (unsigned)fileProperties.st_size)) { + goto exit; + } + + setPath(filename); + success = true; + + debug(1, "File %s read successfully\n", path); + +exit: + + if (file) + fclose(file); + if (buffer) + delete[] buffer; + + return success; +} + +size_t +AnyC64File::writeToBuffer(uint8_t *buffer) +{ + assert(data != NULL); + + if (buffer) { + memcpy(buffer, data, size); + } + return size; +} + +bool +AnyC64File::writeToFile(const char *filename) +{ + bool success = false; + uint8_t *data = NULL; + FILE *file; + size_t filesize; + + // Determine file size + filesize = writeToBuffer(NULL); + if (filesize == 0) + return false; + + // Open file + assert (filename != NULL); + if (!(file = fopen(filename, "w"))) { + goto exit; + } + + // Allocate memory + if (!(data = new uint8_t[filesize])) { + goto exit; + } + + // Write to buffer + if (!writeToBuffer(data)) { + goto exit; + } + + // Write to file + for (unsigned i = 0; i < filesize; i++) { + fputc(data[i], file); + } + + success = true; + +exit: + + if (file) + fclose(file); + if (data) + delete[] data; + + return success; +} diff --git a/C64/FileFormats/AnyC64File.h b/C64/FileFormats/AnyC64File.h new file mode 100755 index 00000000..2b493100 --- /dev/null +++ b/C64/FileFormats/AnyC64File.h @@ -0,0 +1,198 @@ +/*! + * @header AnyC64File.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ANYC64FILE_INC +#define _ANYC64FILE_INC + +#include "VC64Object.h" + +/*! @class AnyC64File + * @brief Base class for all supported file types. + * @details Provides the basic functionality for reading and writing files. + */ +class AnyC64File : public VC64Object { + +protected: + + //! @brief The physical name (full path) of this file. + char *path = NULL; + + /*! @brief The logical name of this file. + * @details Some archives store a logical name in their header section. + * If they don't store a name, the raw filename is used (path + * and extension stripped off). + */ + char name[256]; + + /*! @brief Unicode representation of the logical name. + * @seealso getUnicodeName + */ + unsigned short unicode[256]; + + //! @brief The raw data of this file. + uint8_t *data = NULL; + + //! @brief The size of this file in bytes. + size_t size = 0; + + /*! @brief File pointer + * @details An offset into the data array. + */ + long fp = -1; + + /*! @brief End of file position + * @details This value equals the last valid offset plus 1 + */ + long eof = -1; + + +protected: + + /*! @brief Checks the header signature of a buffer. + * @details This functions is used to determine if the buffer contains the + * binary representation of a special file type, e.g., the + * representation of a T64 file. + * @param buffer Pointer to buffer, must not be NULL + * @param length Length of the buffer + * @param header Expected byte sequence, terminated by EOF + * @return Returns true iff magic bytes match. + */ + /* + static bool checkBufferHeader(const uint8_t *buffer, size_t length, + const uint8_t *header); + */ + + // + //! @functiongroup Creating and destructing objects + // + +public: + + //! @brief Constructor + AnyC64File(); + + //! @brief Destructor + virtual ~AnyC64File(); + + //! @brief Frees the memory allocated by this object. + virtual void dealloc(); + + + // + //! @functiongroup Accessing file attributes + // + + //! @brief Returns the type of this file. + virtual C64FileType type() { return UNKNOWN_FILE_FORMAT; } + + /*! @brief Returns a string representation of the file type. + * @details E.g., a T64 file returns "T64". + */ + virtual const char *typeAsString() { return ""; } + + //! @brief Returns the physical name of this file. + const char *getPath() { return path ? path : ""; } + + //! @brief Sets the physical name of this file. + void setPath(const char *path); + + //! @brief Returns the logical name of this file. + virtual const char *getName() { return name; } + + /*! @brief Returns the logical name as unicode character array. + * @details The provides unicode format is compatible with font C64ProMono + * which is used, e.g., in the mount dialogs preview panel. + */ + const unsigned short *getUnicodeName(); + + + // + //! @functiongroup Reading data from the file + // + + /*! @brief Returns the number of bytes in this file. + * @details After getSize() calls to read(), EOF is returned. + */ + virtual size_t getSize() { return size; } + + //! @brief Moves the file pointer to the specified offset. + /*! @details Use seek(0) to return to the beginning of the file. + */ + virtual void seek(long offset); + + /*! @brief Reads a byte. + * @return EOF (-1) if the end of file has been reached. + */ + virtual int read(); + + /*! @brief Reads multiple bytes in form of a hex dump string. + * @param Number of bytes ranging from 1 to 85. + */ + const char *readHex(size_t num = 1); + + /*! @brief Uses getByte() to copy the file into the C64 memory. + * @param buffer must be a pointer to RAM or ROM + */ + virtual void flash(uint8_t *buffer, size_t offset = 0); + + + // + //! @functiongroup Serializing + // + + //! @brief Required buffer size for this file + size_t sizeOnDisk() { return writeToBuffer(NULL); } + + /*! @brief Returns true iff this file has the same type as the + * file stored in the specified file. + */ + virtual bool hasSameType(const char *filename) { return false; } + + /*! @brief Reads the file contents from a memory buffer. + * @param buffer The address of a binary representation in memory. + * @param length The size of the binary representation. + */ + virtual bool readFromBuffer(const uint8_t *buffer, size_t length); + + /*! @brief Reads the file contents from a file. + * @details This function requires no custom implementation. It first + * reads in the file contents in memory and invokes + * readFromBuffer afterwards. + * @param filename The name of a file on disk. + */ + bool readFromFile(const char *filename); + + /*! @brief Writes the file contents into a memory buffer. + * @details If a NULL pointer is passed in, a test run is performed. Test + * runs are performed to determine the size of the file on disk. + * @param buffer The address of the buffer in memory. + */ + virtual size_t writeToBuffer(uint8_t *buffer); + + /*! @brief Writes the file contents to a file. + * @details This function requires no custom implementation. It invokes + * writeToBuffer first and writes the data to disk afterwards. + * @param filename The name of a file to be written. + */ + bool writeToFile(const char *filename); +}; + +#endif diff --git a/C64/FileFormats/AnyDisk.cpp b/C64/FileFormats/AnyDisk.cpp new file mode 100755 index 00000000..a03df272 --- /dev/null +++ b/C64/FileFormats/AnyDisk.cpp @@ -0,0 +1,85 @@ +/*! + * @file AnyDisk.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "D64File.h" +#include "G64File.h" + +AnyDisk * +AnyDisk::makeWithFile(const char *path) +{ + assert(path != NULL); + + if (D64File::isD64File(path)) { + return D64File::makeWithFile(path); + } + if (G64File::isG64File(path)) { + return G64File::makeWithFile(path); + } + return NULL; +} + +int +AnyDisk::readHalftrack() +{ + int result; + + assert(tEof <= size); + + if (tFp < 0) + return -1; + + // Get byte + result = data[tFp++]; + + // Check for end of file + if (tFp == tEof) + tFp = -1; + + return result; +} + +const char * +AnyDisk::readHalftrackHex(size_t num) +{ + assert(sizeof(name) > 3 * num); + + for (unsigned i = 0; i < num; i++) { + + int byte = readHalftrack(); + if (byte == EOF) break; + sprintf(name + (3 * i), "%02X ", byte); + } + + return name; +} + +void +AnyDisk::copyHalftrack(uint8_t *buffer, size_t offset) +{ + int byte; + + assert(buffer != NULL); + + seekHalftrack(0); + + while ((byte = readHalftrack()) != EOF) { + buffer[offset++] = (uint8_t)byte; + } +} diff --git a/C64/FileFormats/AnyDisk.h b/C64/FileFormats/AnyDisk.h new file mode 100755 index 00000000..9c4b66b0 --- /dev/null +++ b/C64/FileFormats/AnyDisk.h @@ -0,0 +1,103 @@ +/*! + * @header AnyDisk.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ANYDISK_INC +#define _ANYDISK_INC + +#include "AnyArchive.h" + +/*! @class AnyDisk + * @brief This class adds an API to AnyArchive for handling file formats + * that represent a physical floppy disk. + */ +class AnyDisk : public AnyArchive { + +protected: + + /*! @brief File pointer + * @details An offset into the data range of the selected track. + */ + long tFp = -1; + + /*! @brief End of file position + * @details This value equals the last valid offset plus 1 + */ + long tEof = -1; + +public: + + // + //! @functiongroup Creating and destructing objects + // + + /*! @brief Factory method + * @return A D64File or G64File, + * depending on the type of the specified file. + */ + static AnyDisk *makeWithFile(const char *filename); + + + // + //! @functiongroup Selecting a track or halftrack + // + + //! @brief Returns the number of halftracks of the represented disk. + virtual int numberOfHalftracks() { return 0; } + virtual int numberOfTracks() { return (numberOfHalftracks() + 1) / 2; } + + /*! @brief Selects the active track + * @details All track related methods work on the active item. + */ + virtual void selectHalftrack(Halftrack ht) { }; + virtual void selectTrack(Track t) { selectHalftrack(2 * t - 1); } + + + // + //! @functiongroup Reading data from a track + // + + //! @brief Returns the size of the selected haltrack in bytes + virtual size_t getSizeOfHalftrack() { return 0; } + virtual size_t getSizeOfTrack() { return getSizeOfHalftrack(); } + + //! @brief Moves the file pointer to the specified offset. + /*! @details Use seek(0) to return to the beginning of the selected track. + */ + virtual void seekHalftrack(long offset) { } + virtual void seekTrack(long offset) { seekHalftrack(offset); } + + /*! @brief Reads a byte from the selected track. + * @return EOF (-1) if all bytes have been read in. + */ + virtual int readHalftrack(); + virtual int readTrack() { return readHalftrack(); } + + /*! @brief Reads multiple bytes in form of a hex dump string. + * @param Number of bytes ranging from 1 to 85. + */ + virtual const char *readHalftrackHex(size_t num); + virtual const char *readTrackHex(size_t num) { return readHalftrackHex(num); } + + //! @brief Copies the selected track into the specified buffer. + virtual void copyHalftrack(uint8_t *buffer, size_t offset = 0); + virtual void copyTrack(uint8_t *buffer, size_t offset = 0) { copyHalftrack(buffer, offset); } +}; + +#endif diff --git a/C64/FileFormats/CRTFile.cpp b/C64/FileFormats/CRTFile.cpp new file mode 100755 index 00000000..437ca535 --- /dev/null +++ b/C64/FileFormats/CRTFile.cpp @@ -0,0 +1,285 @@ +/*! + * @file CRTFile.cpp + * Original implementation by A. Carl Douglas, 2009 + * Modified and maintained by Dirk W. Hoffmann + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "CRTFile.h" +#include "Cartridge.h" + +const uint8_t CRTFile::magicBytes[] = { + 'C','6','4',' ','C','A','R','T','R','I','D','G','E',' ',' ',' ' }; + +bool +CRTFile::isCRTBuffer(const uint8_t *buffer, size_t length) +{ + if (length < 0x40) return false; + return matchingBufferHeader(buffer, magicBytes, sizeof(magicBytes)); +} + +CartridgeType +CRTFile::typeOfCRTBuffer(const uint8_t *buffer, size_t length) +{ + assert(isCRTBuffer(buffer, length)); + return (CartridgeType)LO_HI(buffer[0x17], buffer[0x16]); +} + +const char * +CRTFile::typeNameOfCRTBuffer(const uint8_t *buffer, size_t length) +{ + CartridgeType type = typeOfCRTBuffer(buffer, length); + return CRTFile::cartridgeTypeName(type); +} + +bool +CRTFile::isSupportedCRTBuffer(const uint8_t *buffer, size_t length) +{ + if (!isCRTBuffer(buffer, length)) + return false; + return Cartridge::isSupportedType(typeOfCRTBuffer(buffer, length)); +} + +bool +CRTFile::isUnsupportedCRTBuffer(const uint8_t *buffer, size_t length) +{ + return isCRTBuffer(buffer, length) && !isSupportedCRTBuffer(buffer, length); +} + +bool +CRTFile::isCRTFile(const char *path) +{ + assert(path != NULL); + + if (!checkFileSuffix(path, ".CRT") && !checkFileSuffix(path, ".crt")) + return false; + + if (!checkFileSize(path, 0x40, -1)) + return false; + + if (!matchingFileHeader(path, magicBytes, sizeof(magicBytes))) + return false; + + return true; +} + +const char * +CRTFile::cartridgeTypeName(CartridgeType type) +{ + switch (type) { + + case CRT_NORMAL: return "Normal cartridge"; + case CRT_ACTION_REPLAY: return "Action Replay"; + case CRT_KCS_POWER: return "KCS Power"; + case CRT_FINAL_III: return "Final Cartridge III"; + case CRT_SIMONS_BASIC: return "Simons Basic"; + case CRT_OCEAN: return "Ocean"; + case CRT_EXPERT: return "Expert"; + case CRT_FUNPLAY: return "Fun Play"; + case CRT_SUPER_GAMES: return "Super Games"; + case CRT_ATOMIC_POWER: return "Atomic Power"; + case CRT_EPYX_FASTLOAD: return "Epyx Fastload"; + case CRT_WESTERMANN: return "Westermann"; + case CRT_REX: return "REX"; + case CRT_FINAL_I: return "Final Cartridge I"; + case CRT_MAGIC_FORMEL: return "Magic Formel"; + case CRT_GAME_SYSTEM_SYSTEM_3: return "Game System 3"; + case CRT_WARPSPEED: return "WarpSpeed"; + case CRT_DINAMIC: return "Dinamic"; + case CRT_ZAXXON: return "Zaxxon (SEGA)"; + case CRT_MAGIC_DESK: return "Magic Desk"; + case CRT_SUPER_SNAPSHOT_V5: return "Super Snapshot"; + case CRT_COMAL80: return "Comal 80"; + case CRT_STRUCTURE_BASIC: return "Structured Basic"; + case CRT_ROSS: return "Ross"; + case CRT_DELA_EP64: return "Dela EP64"; + case CRT_DELA_EP7x8: return "Dela EP7x8"; + case CRT_DELA_EP256: return "Dela EP256"; + case CRT_REX_EP256: return "Rex EP256"; + case CRT_MIKRO_ASS: return "Mikro Assembler"; + case CRT_FINAL_PLUS: return "Final Plus"; + case CRT_ACTION_REPLAY4: return "Action replay 4"; + case CRT_STARDOS: return "Stardos"; + case CRT_EASYFLASH: return "Easyflash"; + case CRT_EASYFLASH_XBANK: return "Easyflash (XBank)"; + case CRT_CAPTURE: return "Capture"; + case CRT_ACTION_REPLAY3: return "Action replay 3"; + case CRT_RETRO_REPLAY: return "Metro replay"; + case CRT_MMC64: return "MMC 64"; + case CRT_MMC_REPLAY: return "MMC replay"; + case CRT_IDE64: return "IDE 64"; + case CRT_SUPER_SNAPSHOT: return "Super snapshot"; + case CRT_IEEE488: return "IEEE 488"; + case CRT_GAME_KILLER: return "Game killer"; + case CRT_P64: return "P64"; + case CRT_EXOS: return "Exos"; + case CRT_FREEZE_FRAME: return "Freeze frame"; + case CRT_FREEZE_MACHINE: return "Freeze machine"; + case CRT_SNAPSHOT64: return "Snapshot 64"; + case CRT_SUPER_EXPLODE_V5: return "Super explode V5"; + case CRT_MAGIC_VOICE: return "Magic voice"; + case CRT_ACTION_REPLAY2: return "Action replay 2"; + case CRT_MACH5: return "Mach 5"; + case CRT_DIASHOW_MAKER: return "Diashow Maker"; + case CRT_PAGEFOX: return "Pagefox"; + case CRT_KINGSOFT: return "Kingsoft"; + case CRT_SILVERROCK_128: return "Silverrock 128"; + case CRT_FORMEL64: return "Formel 64"; + case CRT_RGCD: return "RGCD"; + case CRT_RRNETMK3: return "RRNETMK3"; + case CRT_EASYCALC: return "Easy calc"; + case CRT_GMOD2: return "GMOD 2"; + + default: return ""; + } +} + +CRTFile::CRTFile() +{ + setDescription("CRTFile"); + memset(chips, 0, sizeof(chips)); +} + +CRTFile * +CRTFile::makeWithBuffer(const uint8_t *buffer, size_t length) +{ + CRTFile *cartridge = new CRTFile(); + + if (!cartridge->readFromBuffer(buffer, length)) { + delete cartridge; + return NULL; + } + + return cartridge; +} + +CRTFile * +CRTFile::makeWithFile(const char *filename) +{ + CRTFile *cartridge = new CRTFile(); + + if (!cartridge->readFromFile(filename)) { + delete cartridge; + return NULL; + } + + return cartridge; +} + +void +CRTFile::dealloc() +{ + AnyC64File::dealloc(); + memset(chips, 0, sizeof(chips)); + numberOfChips = 0; +} + +bool +CRTFile::readFromBuffer(const uint8_t *buffer, size_t length) +{ + if (!AnyC64File::readFromBuffer(buffer, length)) + return false; + + // Only proceed if the cartridge header matches + if (memcmp("C64 CARTRIDGE ", data, 16) != 0) { + warn("Bad cartridge signature. Expected 'C64 CARTRIDGE '\n"); + return false; + } + + // Some CRT files contain incosistencies. We try to fix them here. + if (!repair()) { + debug("Failed to repair broken CRT file\n"); + return false; + } + + // Cartridge header size + uint32_t headerSize = HI_HI_LO_LO(data[0x10],data[0x11],data[0x12],data[0x13]); + + // Minimum header size is 0x40. Some cartridges show a value of 0x20 which is wrong. + if (headerSize < 0x40) headerSize = 0x40; + + msg("Cartridge: %s\n", getName()); + msg(" Header: %08X bytes long (normally 0x40)\n", headerSize); + msg(" Type: %d\n", cartridgeType()); + msg(" Game: %d\n", initialGameLine()); + msg(" Exrom: %d\n", initialExromLine()); + + // Load chip packets + uint8_t *ptr = &data[headerSize]; + for (numberOfChips = 0; ptr < data + length; numberOfChips++) { + + if (numberOfChips == MAX_PACKETS) { + warn("CRT file contains too many chip packets. Aborting!\n"); + break; + } + + if (memcmp("CHIP", ptr, 4) != 0) { + warn("Unexpected data in cartridge, expected 'CHIP'\n"); + return false; + } + + // Remember start address of each chip section + chips[numberOfChips] = ptr; + + ptr += 0x10; + ptr += chipSize(numberOfChips); + } + + debug("CRT file imported successfully (%d chips)\n", numberOfChips); + return true; +} + +CartridgeType +CRTFile::cartridgeType() { + + uint16_t type = LO_HI(data[0x17], data[0x16]); + return CartridgeType(type); +} + +const char * +CRTFile::cartridgeTypeName() +{ + return cartridgeTypeName(cartridgeType()); +} + +bool +CRTFile::repair() +{ + if ((data == NULL) != (size == 0)) { + debug("CRT file inconsistency: data = %p size = %d\n", data, size); + return false; + } + + // Compute a fingerprint for the CRT file + uint64_t fingerprint = fnv_1a_64(data, size); + debug("CRT fingerprint: %llx\n", fingerprint); + + // Check for known inconsistencies + switch (fingerprint) { + + case 0xb2a479a5a2ee6cd5: // Mikro Assembler (invalid CRT type) + + // Replace invalid CRT type $00 by $1C + debug("Repairing broken Mikro Assembler cartridge\n"); + data[0x17] = 0x1C; + break; + } + + return true; +} + diff --git a/C64/FileFormats/CRTFile.h b/C64/FileFormats/CRTFile.h new file mode 100755 index 00000000..de25c836 --- /dev/null +++ b/C64/FileFormats/CRTFile.h @@ -0,0 +1,172 @@ +/*! + * @header CRTFile.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* For details about the .CRT format, + * see: http://vice-emu.sourceforge.net/vice_16.html + * http://ist.uwaterloo.ca/~schepers/formats/CRT.TXT + * + * + * As well read the Commodore 64 Programmers Reference Guide pages 260-267. + */ + +#ifndef _CRTFILE_INC +#define _CRTFILE_INC + +#include "AnyC64File.h" + +/*! @class CRTFile + * @brief Represents a file of the CRT format type (cartridges). + */ +class CRTFile : public AnyC64File { + +private: + + //! @brief Maximum number of chip packets in a CRT file. + static const unsigned MAX_PACKETS = 128; + + //! @brief Header signature + static const uint8_t magicBytes[]; + + //! @brief Number of chips contained in cartridge file + unsigned int numberOfChips = 0; + + //! @brief Indicates where each chip section starts + uint8_t *chips[MAX_PACKETS]; + +public: + + // + //! @functiongroup Class methods + // + + //! @brief Returns true if buffer contains a CRT file. + static bool isCRTBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns the cartridge type number stored in the CRT buffer. + static CartridgeType typeOfCRTBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns the cartridge type name stored in the CRT buffer. + static const char *typeNameOfCRTBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true if buffer contains a supported CRT file. + static bool isSupportedCRTBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true if buffer contains a CRT file of unsupported type. + static bool isUnsupportedCRTBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true if path points to a CRT file. + static bool isCRTFile(const char *path); + + //! @brief Returns the cartridge type in plain text + static const char *cartridgeTypeName(CartridgeType type); + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Constructor + CRTFile(); + + //! @brief Factory method + static CRTFile *makeWithBuffer(const uint8_t *buffer, size_t length); + + //! @brief Factory method + static CRTFile *makeWithFile(const char *filename); + + + // + //! @functiongroup Methods from AnyC64File + // + + void dealloc(); + C64FileType type() { return CRT_FILE; } + const char *typeAsString() { return "CRT"; } + const char *getName() { return (char *)&data[0x20]; } + bool hasSameType(const char *filename) { return CRTFile::isCRTFile(filename); } + bool readFromBuffer(const uint8_t *buffer, size_t length); + + + // + //! @functiongroup Retrieving cartridge information + // + + //! @brief Returns the version number of the cartridge. + uint16_t cartridgeVersion() { return LO_HI(data[0x15], data[0x14]); } + + /*! @brief Returns the cartridge type (e.g., SimonsBasic, FinalIII) + * @details Don't confuse with ContainerType + */ + CartridgeType cartridgeType(); + + //! @brief Returns the cartridge type in plain text + const char *cartridgeTypeName(); + + //! @brief Returns the initial value of the Exrom line + bool initialExromLine() { return data[0x18] != 0; } + + //! @brief Returns the initial value of the Game line + bool initialGameLine() { return data[0x19] != 0; } + + + // + //! @functiongroup Retrieving chip information + // + + //! @brief Returns how many chips are contained in this cartridge + uint8_t chipCount() { return numberOfChips; } + + //! @brief Returns where the data of a certain chip can be found + uint8_t *chipData(unsigned nr) { return chips[nr]+0x10; } + + //! @brief Returns the size of chip (8 KB or 16 KB) + uint16_t chipSize(unsigned nr) { return LO_HI(chips[nr][0xF], chips[nr][0xE]); } + + //! @brief Returns the type of chip + /*! @details 0 = ROM, 1 = RAM, 2 = Flash ROM + */ + uint16_t chipType(unsigned nr) { return LO_HI(chips[nr][0x9], chips[nr][0x8]); } + + //! @brief Return bank information (what is this exactly?) + uint16_t chipBank(unsigned nr) { return LO_HI(chips[nr][0xB], chips[nr][0xA]); } + + //! Returns start of chip rom in address space + uint16_t chipAddr(unsigned nr) { return LO_HI(chips[nr][0xD], chips[nr][0xC]); } + + + // + // @functiongroup Scanning and repairing a CRT file + // + + /*! @brief Checks the file for inconsistencies and tries to repair it + * @details This method can eliminate the following inconsistencies: + * invalid cartridge IDs: + * some non-standard cartridges (cartridges with custom + * hardware on board) are marked as standard. If such a + * cartridge is recognised, the ID is corrected. + * @result true, if archive was consistent or could be repaired. + * false, if an inconsistency has been detected that could not + * be repaired. + */ + bool repair(); +}; + +#endif diff --git a/C64/FileFormats/D64File.cpp b/C64/FileFormats/D64File.cpp new file mode 100755 index 00000000..2c16f593 --- /dev/null +++ b/C64/FileFormats/D64File.cpp @@ -0,0 +1,970 @@ +/*! + * @file D64File.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "D64File.h" +#include "T64File.h" +#include "PRGFile.h" +#include "P00File.h" + +bool +D64File::isD64Buffer(const uint8_t *buffer, size_t length) +{ + // Unfortunaltely, D64 containers do not contain magic bytes. + // We can only check the buffer size + + return + length == D64_683_SECTORS || + length == D64_683_SECTORS_ECC || + length == D64_768_SECTORS || + length == D64_768_SECTORS_ECC || + length == D64_802_SECTORS || + length == D64_802_SECTORS_ECC; +} + +bool +D64File::isD64File(const char *filename) +{ + bool fileOK = false; + + assert (filename != NULL); + + if (!checkFileSuffix(filename, ".D64") && !checkFileSuffix(filename, ".d64")) + return false; + + fileOK = checkFileSize(filename, D64_683_SECTORS, D64_683_SECTORS) + || checkFileSize(filename, D64_683_SECTORS_ECC, D64_683_SECTORS_ECC) + || checkFileSize(filename, D64_768_SECTORS, D64_768_SECTORS) + || checkFileSize(filename, D64_768_SECTORS_ECC, D64_768_SECTORS_ECC) + || checkFileSize(filename, D64_802_SECTORS, D64_802_SECTORS) + || checkFileSize(filename, D64_802_SECTORS_ECC, D64_802_SECTORS_ECC); + + // Unfortunaltely, D64 containers do not contain magic bytes, + // so we can't check anything further here + + return fileOK; +} + + +D64File::D64File() +{ + debug("D64File::D64File()\n"); + + setDescription("D64Archive"); + memset(errors, 0x01, sizeof(errors)); +} + +D64File::D64File(unsigned tracks, bool ecc) : D64File() +{ + debug("D64File::D64File(%d)\n", tracks); + + switch(tracks) { + case 35: + size = ecc ? D64_683_SECTORS_ECC : D64_683_SECTORS; + break; + + case 40: + size = ecc ? D64_768_SECTORS_ECC : D64_768_SECTORS; + break; + + case 42: + size = ecc ? D64_802_SECTORS_ECC : D64_802_SECTORS; + break; + + default: + assert(false); + } + + data = new uint8_t[size]; + memset(data, 0, size); + // numTracks = tracks; +} + +D64File * +D64File::makeWithBuffer(const uint8_t *buffer, size_t length) +{ + D64File *archive = new D64File(); + + if (!archive->readFromBuffer(buffer, length)) { + delete archive; + return NULL; + } + + return archive; +} + +D64File * +D64File::makeWithFile(const char *path) +{ + D64File *archive = new D64File(); + + if (!archive->readFromFile(path)) { + delete archive; + return NULL; + } + + return archive; +} + +D64File * +D64File::makeWithAnyArchive(AnyArchive *otherArchive) +{ + assert(otherArchive != NULL); + + // Create a standard 35 track disk with no error checking codes + D64File *archive = new D64File(35, false); + archive->debug(1, "Creating D64 archive from a %s archive...\n", + otherArchive->typeAsString()); + + // Copy file path + archive->setPath(otherArchive->getPath()); + + // Current position of data write ptr + Track track = 1; + Sector sector = 0; + + // Write BAM + archive->writeBAM(otherArchive->getName()); + + // Loop over all entries in archive + int numberOfItems = otherArchive->numberOfItems(); + for (unsigned i = 0; i < numberOfItems; i++) { + + otherArchive->selectItem(i); + + archive->writeDirectoryEntry(i, + otherArchive->getNameOfItem(), + track, sector, + otherArchive->getSizeOfItem()); + + // Every file is preceded with two bytes containing its load address + uint16_t loadAddr = otherArchive->getDestinationAddrOfItem(); + archive->writeByteToSector(LO_BYTE(loadAddr), &track, §or); + archive->writeByteToSector(HI_BYTE(loadAddr), &track, §or); + + // Write raw data to disk + int byte; + unsigned num = 0; + + archive->debug(2, "Will write %d bytes\n", otherArchive->getSizeOfItem()); + + otherArchive->selectItem(i); + while ((byte = otherArchive->readItem()) != EOF) { + archive->writeByteToSector(byte, &track, §or); + num++; + } + + archive->debug(2, "D64 item %d: %d bytes written\n", i, num); + // Item i has been written. Goto next free sector and proceed with the next item + (void)archive->nextTrackAndSector(track, sector, &track, §or); + } + + archive->debug(2, "%s archive created.\n", archive->typeAsString()); + + return archive; +} + +D64File * +D64File::makeWithDisk(Disk *disk) +{ + uint8_t buffer[D64_802_SECTORS]; + + assert(disk != NULL); + + // Translate disk contents into a byte stream + size_t len = disk->decodeDisk(buffer); + + // Check if the disk has been fully decoded + if (len != D64_683_SECTORS && len != D64_768_SECTORS && len != D64_802_SECTORS) { + return NULL; + } + + // Create object from byte stream + return makeWithBuffer(buffer, len); +} + +const char * +D64File::getName() +{ + int i, pos = offset(18, 0) + 0x90; + + for (i = 0; i < 255; i++) { + if (data[pos+i] == 0xA0) + break; + name[i] = data[pos+i]; + } + name[i] = 0x00; + return name; +} + +bool +D64File::readFromBuffer(const uint8_t *buffer, size_t length) +{ + unsigned numSectors; + bool errorCodes; + + switch (length) + { + case D64_683_SECTORS: // 35 tracks, no errors + + debug(2, "D64 file contains 35 tracks, no EC bytes\n"); + numSectors = 683; + errorCodes = false; + break; + + case D64_683_SECTORS_ECC: // 35 tracks, 683 error bytes + + debug(2, "D64 file contains 35 tracks, 683 EC bytes\n"); + numSectors = 683; + errorCodes = true; + break; + + case D64_768_SECTORS: // 40 tracks, no errors + + debug(2, "D64 file contains 40 tracks, no EC bytes\n"); + numSectors = 768; + errorCodes = false; + break; + + case D64_768_SECTORS_ECC: // 40 tracks, 768 error bytes + + debug(2, "D64 file contains 40 tracks, 768 EC bytes\n"); + numSectors = 768; + errorCodes = true; + break; + + case D64_802_SECTORS: // 42 tracks, no error bytes + + debug(2, "D64 file contains 42 tracks, no EC bytes\n"); + numSectors = 802; + errorCodes = false; + break; + + case D64_802_SECTORS_ECC: // 42 tracks, 802 error bytes + + debug(2, "D64 file contains 42 tracks, 802 EC bytes\n"); + numSectors = 802; + errorCodes = true; + break; + + default: + warn("D64 has an unknown format\n"); + return false; + } + + AnyC64File::readFromBuffer(buffer, length); + + // Copy error codes into seperate array + if (errorCodes) { + memcpy(errors, data + (numSectors * 256), numSectors); + } + + return true; +} + +int +D64File::numberOfItems() +{ + long offsets[144]; // A C64 disk contains at most 144 files + unsigned noOfFiles; + + scanDirectory(offsets, &noOfFiles); + + return noOfFiles; +} + +void +D64File::selectItem(unsigned item) +{ + // Only proceed of item exists + if (item >= numberOfItems()) + return; + + // Remember the selection + selectedItem = item; + + // Move file pointer to the first data byte + iFp = findItem(item); +} + +const char * +D64File::getTypeOfItemAsString() +{ + assert(selectedItem != -1); + + const char *extension = ""; + long pos = findDirectoryEntry(selectedItem); + + if (pos > 0) + (void)itemIsVisible(data[pos] /* file type byte */, &extension); + + return extension; +} + +const char * +D64File::getNameOfItem() +{ + assert(selectedItem != -1); + + long pos; + int i; + + // Search the selected item in directory + if ((pos = findDirectoryEntry(selectedItem)) <= 0) + return NULL; + + pos += 0x03; // The filename begins here + for (i = 0; i < 16; i++) { + if (data[pos+i] == 0xA0) + break; + name[i] = data[pos+i]; + } + name[i] = 0x00; + return name; +} + +size_t +D64File::getSizeOfItem() +{ + if (selectedItem < 0) + return 0; + + // In a D64 archive, the bytes of a single file item are not ordered + // consecutively. Hence, we have to step through the data byte by byte. + long oldFp = iFp; + size_t result = 0; + + while (readItem() != EOF) + result++; + + iFp = oldFp; + return result; +} + +size_t +D64File::getSizeOfItemInBlocks() +{ + assert(selectedItem != -1); + + long pos = findDirectoryEntry(selectedItem); + return (pos > 0) ? LO_HI(data[pos+0x1C],data[pos+0x1D]) : 0; +} + +void +D64File::seekItem(long offset) +{ + // Reset file pointer to the beginning of the selected item + iFp = findItem(selectedItem); + + // Advance file pointer to the requested position + for (unsigned i = 0; i < offset; i++) + (void)readItem(); +} + +int +D64File::readItem() +{ + int result; + + if (iFp < 0) + return -1; + + // Get byte + result = data[iFp]; + + // Check for end of file + if (isEndOfFile(iFp)) { + iFp = -1; + return result; + } + + if (isLastByteOfSector(iFp)) { + + // Continue reading in new sector + if (!jumpToNextSector(&iFp)) { + // The current sector points to an invalid next track/sector + // We won't jump off the cliff and terminate reading here. + iFp = -1; + return result; + } else { + // Skip the first two data bytes of the new sector as they encode the + // next track/sector + iFp += 2; + return result; + } + } + + // Continue reading in current sector + iFp++; + return result; +} + +uint16_t +D64File::getDestinationAddrOfItem() +{ + int track; + int sector; + uint16_t result; + + assert(selectedItem != -1); + + // Search for beginning of file data + long pos = findDirectoryEntry(selectedItem); + if (pos <= 0) + return 0; + + track = data[pos + 0x01]; + sector = data[pos + 0x02]; + if ((pos = offset(track, sector)) < 0) + return 0; + + result = LO_HI(data[pos+2],data[pos+3]); + return result; +} + +long +D64File::findItem(long item) +{ + long p; + + if (item < 0) + return -1; + + // Find directory entry + if ((p = findDirectoryEntry(item)) <= 0) + return -1; + + // Find first data sector + if ((p = offset(data[p+0x01], data[p+0x02])) < 0) + return -1; + + // Skip t/s sequence + p += 2; + + // Skip destination address + p += 2; + + return p; +} + + +bool +D64File::itemIsVisible(uint8_t typeChar, const char **extension) +{ + const char *result = NULL; + + switch (typeChar) { + case 0x80: result = "DEL"; break; + case 0x81: result = "SEQ"; break; + case 0x82: result = "PRG"; break; + case 0x83: result = "USR"; break; + case 0x84: result = "REL"; break; + + case 0x01: result = "*SEQ"; break; + case 0x02: result = "*PRG"; break; + case 0x03: result = "*USR"; break; + + case 0xA0: result = "DEL"; break; + case 0xA1: result = "SEQ"; break; + case 0xA2: result = "PRG"; break; + case 0xA3: result = "USR"; break; + + case 0xC0: result = "DEL <"; break; + case 0xC1: result = "SEQ <"; break; + case 0xC2: result = "PRG <"; break; + case 0xC3: result = "USR <"; break; + case 0xC4: result = "REL <"; break; + } + + if (extension) + *extension = result ? result : ""; + + // printf("itemIsVisible as %s\n", result == NULL ? "" : result); + return result != NULL; +} + + +// +// Accessing archive attributes +// + +int +D64File::numberOfHalftracks() +{ + switch (size) { + + case D64_683_SECTORS: + case D64_683_SECTORS_ECC: + return 2 * 35; + + case D64_768_SECTORS: + case D64_768_SECTORS_ECC: + return 2 * 40; + + case D64_802_SECTORS: + case D64_802_SECTORS_ECC: + return 2 * 42; + + default: + assert(false); + return 0; + } +} + +void +D64File::selectHalftrack(Halftrack ht) +{ + assert(isHalftrackNumber(ht)); + + selectedHalftrack = ht; + Track t = (ht + 1) / 2; + + // Check if a real track is requested (D64 files do not store halftracks) + if ((selectedHalftrack % 2) == 0) { + tFp = tEof = -1; + return; + } + + // Check if the requested track is stored inside the D64 file + if (t > numberOfTracks()) { + tFp = tEof = -1; + return; + } + + tFp = offset(t, 0); + tEof = tFp + getSizeOfHalftrack(); +} + +size_t +D64File::getSizeOfHalftrack() +{ + if (selectedHalftrack % 2) { + return numberOfSectorsInHalftrack(selectedHalftrack) * 256; + } else { + return 0; // Real halftrack (not stored inside D64 files) + } +} + +void +D64File::seekHalftrack(long offset) +{ + // Reset file pointer to the first data byte. + selectHalftrack(selectedHalftrack); + + // Advance file pointer to the requested position. + if (tFp != -1) + tFp += offset; + + // Invalidate fp if it is out of range. + if (tFp >= size) + tFp = -1; +} + +void +D64File::selectTrackAndSector(Track t, Sector s) +{ + assert(isValidTrackSectorPair(t, s)); + + selectHalftrack(2 * t - 1); + assert(tFp != -1); + + tFp += 256 * s; + tEof = tFp + 256; +} + + +// +//! @functiongroup Accessing tracks and sectors +// + +uint8_t +D64File::errorCode(Track t, Sector s) +{ + assert(isValidTrackSectorPair(t, s)); + + Sector index = Disk::trackDefaults[t].firstSectorNr + s; + assert(index < 802); + + return errors[index]; + +} + +int +D64File::offset(Track track, Sector sector) +{ + // secCnt[track] is the number of the first sector on track 'track' + const unsigned secCnt[43] = { 0 /* pad */, + 0, 21, 42, 63, 84, 105, 126, 147, + 168, 189, 210, 231, 252, 273, 294, 315, + 336, 357, 376, 395, 414, 433, 452, 471, + 490, 508, 526, 544, 562, 580, 598, 615, + 632, 649, 666, 683, 700, 717, 734, 751, + 768, 785 }; + + if (isValidTrackSectorPair(track, sector)) { + return (secCnt[track] + sector) * 256; + } else { + return -1; + } +} + +bool +D64File::nextTrackAndSector(Track track, Sector sector, + Track *nextTrack, Sector *nextSector, + bool skipDirectoryTrack) +{ + assert(nextTrack != NULL); + assert(nextSector != NULL); + assert(isValidTrackSectorPair(track, sector)); + + // Interleave pattern for all four speed zones and the directory track + Sector zone3[] = { 10,11,12,13,14,15,16,17,18,19,20,0,1,2,3,4,5,6,7,8,9 }; + Sector zone2[] = { 10,11,12,13,14,15,16,17,18,0,1,2,3,4,5,6,7,8,9 }; + Sector trk18[] = { 3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,1,2 }; + Sector zone1[] = { 10,11,12,13,14,15,16,17,1,0,2,3,4,5,6,7,8,9 }; + Sector zone0[] = { 10,11,12,13,14,15,16,0,1,2,3,4,5,6,7,8,9 }; + + // Determine interleave pattern for this track + Sector *next = + (track < 18) ? zone3 : + (track == 18) ? trk18 : + (track < 25) ? zone2 : + (track < 31) ? zone1 : zone0; + + // Move to next sector + sector = next[sector]; + + // Move to next track if we wrapped over + if (sector == 0) { + if (track < numberOfTracks()) { + track = (track == 17 && skipDirectoryTrack) ? 19 : track + 1; + sector = 0; + } else { + return false; // there is no next track + } + } + + assert(isValidTrackSectorPair(track, sector)); + *nextTrack = track; + *nextSector = sector; + return true; +} + +bool +D64File::jumpToNextSector(long *pos) +{ + int nTrack, nSector; + long newPos; + + nTrack = nextTrack(*pos); + nSector = nextSector(*pos); + + if (nTrack > numberOfTracks()) + return false; + + if ((newPos = offset(nTrack, nSector)) < 0) + return false; + + *pos = newPos; + return true; +} + +bool +D64File::writeByteToSector(uint8_t byte, Track *t, Sector *s) +{ + Track track = *t; + Sector sector = *s; + + assert(isValidTrackSectorPair(track, sector)); + + int pos = offset(track, sector); + uint8_t positionOfLastDataByte = data[pos + 1]; + + if (positionOfLastDataByte == 0xFF) { + + // No free slots in this sector, proceed to next one + if (!nextTrackAndSector(track, sector, &track, §or)) { + return false; // Sorry, disk is full + } + + // link previous sector with the new one + data[pos++] = (uint8_t)track; + data[pos] = (uint8_t)sector; + pos = offset(track, sector); + positionOfLastDataByte = 0; + } + + // Write byte + if (positionOfLastDataByte == 0) { + markSectorAsUsed(track, sector); + data[pos + 2] = byte; + data[pos + 1] = 0x02; + } else { + positionOfLastDataByte++; + data[pos + positionOfLastDataByte] = byte; + data[pos + 1] = positionOfLastDataByte; + } + + *t = track; + *s = sector; + + return true; +} + + +// +//! Accessing file and directory items +// + +void +D64File::markSectorAsUsed(Track track, Sector sector) +{ + // For each track and sector, there exists a single bit in the BAM. + // 1 = used, 0 = unused + + // First byte of BAM + int bam = offset(18, 0); + + // Select byte group correspondig to track + bam += (4 * track); + + // Select byte carrying the information for sector + int offset = 1 + (sector >> 3); + assert(offset >= 1 && offset <= 3); + + // Select bit for this sector + uint8_t bitmask = 0x01 << (sector & 0x07); + + if (data[bam+offset] & bitmask) { + // Clear bit + data[bam + offset] &= ~bitmask; + + // Descrease number of free sectors + assert(data[bam] > 0); + data[bam]--; + } +} + +void +D64File::writeBAM(const char *name) +{ + int pos; + + // 00/01: Track/Sector location of the first directory sector (should be 18/1) + markSectorAsUsed(18, 0); + pos = offset(18, 0); + data[pos++] = 18; + data[pos++] = 1; + + // 02: Disk DOS version type (see note below) + data[pos++] = 0x41; // "A" + + // 03: Unused + pos++; + + // 04-8F: BAM entries for each track, in groups of four bytes + for (unsigned k = 1; k <= 35; k++) { + if (k == 18) { + data[pos++] = 0; // no free sectors on directory track + data[pos++] = 0x00; + data[pos++] = 0x00; + data[pos++] = 0x00; + } else { + int sectors = numberOfSectorsInTrack(k); + data[pos++] = sectors; // Number of free sectors on this track + data[pos++] = 0xFF; // Occupation bitmap: 1 = sector is free + data[pos++] = 0xFF; + if (sectors == 21) data[pos++] = 0x1F; + else if (sectors == 19) data[pos++] = 0x07; + else if (sectors == 18) data[pos++] = 0x03; + else if (sectors == 17) data[pos++] = 0x01; + else assert(0); + } + } + assert(pos == offset(18, 0) + 0x90); + + // 90-9F: Disk Name (padded with $A0) + size_t len = strlen(name); + for (unsigned k = 0; k < 16; k++) + data[pos++] = (len > k) ? name[k] : 0xA0; + + assert(pos == offset(18, 0) + 0xA0); + + // A0-A1: Filled with $A0 + data[pos++] = 0xA0; + data[pos++] = 0xA0; + + // A2-A3: Disk ID + data[pos++] = 0x56; + data[pos++] = 0x54; + + // A4: Usually $A0 + data[pos++] = 0xA0; + + // A5-A6: DOS type + data[pos++] = 0x32; // "2" + data[pos++] = 0x41; // "A" + + // A7-AA: Filled with $A0 + data[pos++] = 0xA0; + data[pos++] = 0xA0; + data[pos++] = 0xA0; + data[pos++] = 0xA0; + + assert(pos == offset(18, 0) + 0xAB); +} + +void +D64File::scanDirectory(long *offsets, unsigned *noOfFiles, bool skipInvisibleFiles) +{ + // Directory starts on track 18 in sector 1 + long pos = offset(18, 1); + + // Does the directory continue in another sector? + bool last_sector = (data[pos] == 0x00); + + // Move to the beginning of the first directory entry + pos += 2; + + unsigned i = 0, item = 0; + while (i < 144 /* maximum number of files on disk */) { + + // Only proceed if the directory entry is not a null entry + const char nullEntry[] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + if (memcmp(&data[pos], nullEntry, 32) == 0) + break; + + // Skip invisble files if requested + if (!skipInvisibleFiles || itemIsVisible(data[pos])) + offsets[item++] = pos; + + // Jump to next directory item + if (++i % 8 == 0) { + + // Jump to the next sector + if (last_sector) + break; // Sorry, there is no next sector + + if (!jumpToNextSector(&pos)) + break; // Sorry, somebody wants to sent us off the cliff + + last_sector = (data[pos] == 0x00); + pos += 2; // Move to the beginning of the first directory entry + + } else { + pos += 0x20; // Jump to next directory entry inside current sector + } + } + + *noOfFiles = item; +} + + +long +D64File::findDirectoryEntry(long item, bool skipInvisibleFiles) +{ + long offsets[144]; + unsigned noOfFiles; + + scanDirectory(offsets, &noOfFiles, skipInvisibleFiles); + return (item < noOfFiles) ? offsets[item] : -1; +} + +bool +D64File::writeDirectoryEntry(unsigned nr, const char *name, + Track startTrack, Sector startSector, + size_t filesize) +{ + int pos; + + // Sector interleave pattern for the directory track + // 18,0 is the BAM, and the first 8 directory items are located at 18,1. + // After that, an interleave pattern of 3 is applied. + Sector secnr[] = { 0,1,4,7,10,13,16,2,5,8,11,14,17,3,6,9,12,15,18 }; + + if (nr >= 144) { + warn("Cannot write directory entry. Number of files is limited to 144\n"); + return false; + } + + // Determine sector and relative sector position for this entry + uint8_t sector = secnr[1 + (nr / 8)]; + uint8_t rel = (nr % 8) * 0x20; + + // Update BAM + markSectorAsUsed(18, sector); + + // Link to this sector if it is not the first + if (sector != 1) { + pos = offset(18, secnr[nr / 8]); + data[pos++] = 18; + data[pos] = sector; + } + + pos = offset(18, sector) + rel; + + // 00-01: Next directory sector (item 0) or 00 00 (other items) + if (nr == 0) { + pos++; // don't modify + pos++; // don't modify + } else { + data[pos++] = 0x00; + data[pos++] = 0x00; + } + + // 02: File type (0x82 = PRG) + data[pos++] = 0x82; + + // 03-04: Track/sector location of first sector of file + data[pos++] = (uint8_t)startTrack; + data[pos++] = (uint8_t)startSector; + + // 05-14: 16 character filename (in PETASCII, padded with $A0) + size_t len = strlen(name); + for (unsigned k = 0; k < 16; k++) + data[pos++] = (len > k) ? name[k] : 0xA0; + + assert(pos == offset(18, sector) + rel + 0x15); + + // 1E-1F: File size in sectors, low/high byte order + pos = offset(18, sector) + rel + 0x1E; + filesize += 2; // Each file stores 2 additional bytes containing the load address + uint16_t fileSizeInSectors = (filesize % 254 == 0) ? filesize / 254 : filesize / 254 + 1; + data[pos++] = LO_BYTE(fileSizeInSectors); + data[pos++] = HI_BYTE(fileSizeInSectors); + + return true; +} + + +// +// Debugging +// + +void +D64File::dumpSector(Track track, Sector sector) +{ + int pos = offset(track, sector); + + msg("Sector %d/%d\n", track, sector); + for (int i = 0; i < 256; i++) { + msg("%02X ", data[pos++]); + } +} + diff --git a/C64/FileFormats/D64File.h b/C64/FileFormats/D64File.h new file mode 100755 index 00000000..5543fd01 --- /dev/null +++ b/C64/FileFormats/D64File.h @@ -0,0 +1,297 @@ +/*! + * @header D64File.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _D64FILE_INC +#define _D64FILE_INC + +#include "AnyDisk.h" +#include "Disk.h" + +// Forward declarations +// class Disk; + +// D64 files come in six different sizes +#define D64_683_SECTORS 174848 +#define D64_683_SECTORS_ECC 175531 +#define D64_768_SECTORS 196608 +#define D64_768_SECTORS_ECC 197376 +#define D64_802_SECTORS 205312 +#define D64_802_SECTORS_ECC 206114 + + +/*! @class D64File + * @brief The D64File class declares the programmatic interface for a file + * in D64 format. + */ +class D64File : public AnyDisk { + +private: + + /*! @brief Number of the currently selected track + * @details 0, if no track is selected + */ + Halftrack selectedHalftrack = 0; + + /*! @brief Error information stored in the D64 archive. + */ + uint8_t errors[802]; + + /*! @brief Number of the currently selected item + * @details -1, if no item is selected + */ + long selectedItem = -1; + +public: + + // + //! @functiongroup Class methods + // + + //! @brief Returns true iff buffer contains a D64 file + static bool isD64Buffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff the specified file is a D64 file. + static bool isD64File(const char *filename); + + + // + //! @functiongroup Creating and destructing objects + // + + //! @brief Standard constructor + D64File(); + + //! @brief Custom constructor + D64File(unsigned tracks, bool ecc); + + //! @brief Factory method + static D64File *makeWithBuffer(const uint8_t *buffer, size_t length); + + //! @brief Factory method + static D64File *makeWithFile(const char *path); + + //! @brief Factory method + static D64File *makeWithAnyArchive(AnyArchive *otherArchive); + + //! @brief Factory method + static D64File *makeWithDisk(Disk *disk); + + + // + //! @functiongroup Methods from AnyC64File + // + + C64FileType type() { return D64_FILE; } + const char *typeAsString() { return "D64"; } + const char *getName(); + bool hasSameType(const char *filename) { return isD64File(filename); } + bool readFromBuffer(const uint8_t *buffer, size_t length); + + + // + //! @functiongroup Methods from AnyArchive + // + + int numberOfItems(); + void selectItem(unsigned n); + const char *getTypeOfItemAsString(); + const char *getNameOfItem(); + size_t getSizeOfItem(); + size_t getSizeOfItemInBlocks(); + void seekItem(long offset); + int readItem(); + uint16_t getDestinationAddrOfItem(); + + + // + //! @functiongroup Methods from AnyDisk + // + + int numberOfHalftracks(); + void selectHalftrack(Halftrack ht); + size_t getSizeOfHalftrack(); + void seekHalftrack(long offset); + + //! @brief Similar to selectHalftrack() + void selectTrackAndSector(Track t, Sector s); + + // + //! @functiongroup Accessing file attributes + // + + //! @brief Returns the first disk ID character + uint8_t diskId1() { return data[offset(18, 0) + 0xA2]; } + + //! @brief Returns the second disk ID character + uint8_t diskId2() { return data[offset(18, 0) + 0xA3]; } + + + // + //! @functiongroup Accessing single file items + // + +private: + + /*! @brief Returns the offset to the first data byte of an item. + * @return -1, if the item does not exist. + */ + long findItem(long item); + + /*! @brief Returns true iff item is a visible file + * @details Whether a file is visible or not is determined by the type + * character, a special byte stored inside the directory. The + * type character also determines how the file is displayed when + * the directory is loaded via LOAD "$",8. E.g., standard + * program files are listes as PRG. + * @param typeChar The type character of a file. + * @param extension If this argument is provided, an extension string + * is returned (e.g. "PRG"). Invisible files return "". + */ + bool itemIsVisible(uint8_t typeChar, const char **extension = NULL); + + + // + //! @functiongroup Accessing tracks and sectors + // + +public: + + //! @brief Returns the error for the specified sector. + /*! @note Returns 01 (no error) if the D64 file does not contain + * error codes. + */ + uint8_t errorCode(Track t, Sector s); + +private: + + //! @brief Translates a track and sector number into an offset. + //! @return -1, if an invalid track or sector number is provided. + int offset(Track track, Sector sector); + + //! @brief Returns true iff offset points to the last byte of a sector. + bool isLastByteOfSector(long offset) { return ((offset+1) % 256) == 0; } + + //! @brief Returns the next logical track number following this sector. + /*! @note The number is stored in the first byte of the current sector. + */ + int nextTrack(long offset) { return data[offset & (~0xFF)]; } + + //! @brief Returns the next sector number following this sector. + /*! @note The number is stored in the second byte of the current sector. + */ + int nextSector(long offset) { return data[(offset & (~0xFF)) + 1]; } + + //! @brief Returns the next physical track and sector. + bool nextTrackAndSector(Track track, Sector sector, + Track *nextTrack, Sector *nextSector, + bool skipDirectory = true); + + /*! @brief Jump to the beginning of the next sector + * @details pos is set to the beginning of the next sector. + * @result True if the jump to the next sector was successful; false if + * the current sector points to an invalid valid track/sector + * combination. In the failure case, pos remains untouched. + */ + bool jumpToNextSector(long *pos); + + /*! @brief Writes a byte to the specified track and sector + * @details If the sector overflows, the values of track and sector are + * overwritten with the next free sector. + * @result true if the byte was written successfully; false if there is + * no space left on disk. + */ + bool writeByteToSector(uint8_t byte, Track *track, Sector *sector); + + + // + //! @functiongroup Accessing file and directory items + // + +private: + + /*! @brief Marks a single sector as "used" + */ + void markSectorAsUsed(Track track, Sector sector); + + /*! @brief Writes the Block Availability Map (BAM) + * @details On a C64 diskette, the BAM is located ion track 18, sector 0. + * @param name Name of the disk + */ + void writeBAM(const char *name); + + /*! @brief Gathers data about all directory items + * @details This function scans all directory items and stores the relative + * start address of the first sector into the provided offsets + * array. Furthermore, the total number of files is written into + * variable noOfFiles. + * @param offsets Pointer to an array of type unsigned[MAX_FILES_ON_DISK] + * @param noOfFiles Pointer to a variable of type unsigned + * @param skipInvisibleFiles If set to true, only those files are + * considered that would show up when loading the directory via + * LOAD "$",8. Otherwise, all files are considered, i.e. those + * that are marked as deleted. + */ + void scanDirectory(long *offsets, unsigned *noOfFiles, bool skipInvisibleFiles = true); + + /*! @brief Looks up a directory item by number. + * @details This function searches the directory for the requested item. + * @param itemBumber Number of the item. The first item has number 0. + * @param skipInvisibleFiles If set to true, only those files are + * considered that would show up when loading the directory via + * LOAD "$",8. Otherwise, all files are considered, i.e. those + * that are marked as deleted. + * @return Offset to the first data sector of the requested file. If the + * file is not found, -1 is returned. + */ + long findDirectoryEntry(long item, bool skipInvisibleFiles = true); + + //! Returns the track number of the first file block + /*! Example usage: firstTrackOfFile(findDirectoryEntry(42)) */ + uint8_t firstTrackOfFile(unsigned dirEntry) { return data[dirEntry + 1]; } + + //! @brief Returns the sector number of the first file block + /*! @details Example usage: firstSectorOfFile(findDirectoryEntry(42)) + */ + uint8_t firstSectorOfFile(unsigned dirEntry) { return data[dirEntry + 2]; } + + /*! @brief Returns true iff offset points to the last byte of a file + */ + bool isEndOfFile(long offset) { + return nextTrack(offset) == 0 && nextSector(offset) == offset % 256; } + + /*! @brief Writes a directory item + * @details This function is used to convert other archive formats into + * the D64 format. + */ + bool writeDirectoryEntry(unsigned nr, const char *name, + Track startTrack, Sector startSector, + size_t filesize); + + + // + //! @functiongroup Debugging + // + +private: + + //! @brief Dumps the contents of a sector to stderr + void dumpSector(Track track, Sector sector); +}; +#endif diff --git a/C64/FileFormats/File_types.h b/C64/FileFormats/File_types.h new file mode 100755 index 00000000..ca1f8d0a --- /dev/null +++ b/C64/FileFormats/File_types.h @@ -0,0 +1,53 @@ +/*! + * @header File_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef FILE_TYPES_H +#define FILE_TYPES_H + +/*! @enum C64FileType + * @brief The type of a file + * @constant CRT_FILE A cartridge that can be plugged into the expansion port. + * @constant V64_FILE A snapshot file (contains a frozen C64 state). + * @constant D64_FILE A floppy disk image with multiply files. + * @constant T64_FILE A tape archive with multiple files. + * @constant PRG_FILE A program archive containing a single file. + * @constant P00_FILE A program archive containing a single file. + * @constant G64_FILE A collection of bit-streams resembling a floppy disk. + * @constant TAP_FILE A bit-stream resembling a datasette tape. + */ +typedef enum { + UNKNOWN_FILE_FORMAT = 0, + CRT_FILE, + V64_FILE, + D64_FILE, + T64_FILE, + PRG_FILE, + P00_FILE, + G64_FILE, + TAP_FILE, + BASIC_ROM_FILE, + CHAR_ROM_FILE, + KERNAL_ROM_FILE, + VC1541_ROM_FILE, +} C64FileType; + +#endif diff --git a/C64/FileFormats/G64File.cpp b/C64/FileFormats/G64File.cpp new file mode 100755 index 00000000..4f9de088 --- /dev/null +++ b/C64/FileFormats/G64File.cpp @@ -0,0 +1,207 @@ +/*! + * @file G64File.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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, org + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "G64File.h" + +const uint8_t /* "GCR-1541" */ +G64File::magicBytes[] = { 0x47, 0x43, 0x52, 0x2D, 0x31, 0x35, 0x34, 0x31 }; + +bool +G64File::isG64Buffer(const uint8_t *buffer, size_t length) +{ + if (length < 0x02AC) return false; + return matchingBufferHeader(buffer, magicBytes, sizeof(magicBytes)); +} + +bool +G64File::isG64File(const char *filename) +{ + assert(filename != NULL); + + if (!checkFileSuffix(filename, ".G64") && !checkFileSuffix(filename, ".g64")) + return false; + + if (!checkFileSize(filename, 0x02AC, -1)) + return false; + + if (!matchingFileHeader(filename, magicBytes, sizeof(magicBytes))) + return false; + + return true; +} + +G64File::G64File() +{ + setDescription("G64Archive"); +} + +G64File::G64File(size_t capacity) +{ + assert(capacity > 0); + assert(data == NULL); + + size = capacity; + data = new uint8_t[capacity]; +} + +G64File * +G64File::makeWithBuffer(const uint8_t *buffer, size_t length) +{ + G64File *archive = new G64File(); + + if (!archive->readFromBuffer(buffer, length)) { + delete archive; + return NULL; + } + + return archive; +} + +G64File * +G64File::makeWithFile(const char *filename) +{ + G64File *archive = new G64File(); + + if (!archive->readFromFile(filename)) { + delete archive; + return NULL; + } + + return archive; +} + +G64File * +G64File::makeWithDisk(Disk *disk) +{ + assert(disk != NULL); + + // Determine empty (half)tracks + bool empty[85]; + for (Halftrack ht = 1; ht <= 84; ht++) { + empty[ht] = disk->halftrackIsEmpty(ht); + } + + // Determine file offsets for all (half)tracks + uint32_t offset[85]; + unsigned pos = 0x015C; + for (Halftrack ht = 1; ht <= 84; ht++) { + if (empty[ht]) { + offset[ht] = 0; + } else { + offset[ht] = pos; + pos += 2 /* Length */ + maxBytesOnTrack /* Data */; + } + } + + // Allocate memory + size_t length = pos + 84 * 4; /* speed zones entries */ + uint8_t *buffer = new uint8_t[length]; + + // Write header, number of tracks, and track length + pos = 0; + memcpy(buffer, G64File::magicBytes, 9); + buffer[9] = 84; // 0x54 (Number of tracks) + buffer[10] = LO_BYTE(maxBytesOnTrack); // 0xF8 + buffer[11] = HI_BYTE(maxBytesOnTrack); // 0x1E + + // Write track offsets + pos = 12; + for (Halftrack ht = 1; ht <= 84; ht++) { + buffer[pos++] = offset[ht] & 0xFF; + buffer[pos++] = (offset[ht] >> 8) & 0xFF; + buffer[pos++] = (offset[ht] >> 16) & 0xFF; + buffer[pos++] = (offset[ht] >> 24) & 0xFF; + } + + // Dump track data + for (Halftrack ht = 1; ht <= 84; ht++) { + + if (!empty[ht]) { + + uint16_t numDataBytes = disk->lengthOfHalftrack(ht) / 8; + uint16_t numFillBytes = maxBytesOnTrack - numDataBytes; + + if (disk->lengthOfHalftrack(ht) % 8 != 0) { + printf("WARNING: Size of halftrack %d is not a multiple of 8\n", ht); + } + assert(pos == offset[ht]); + buffer[pos++] = LO_BYTE(numDataBytes); + buffer[pos++] = HI_BYTE(numDataBytes); + + for (unsigned i = 0; i < numDataBytes; i++) { + buffer[pos++] = disk->data.halftrack[ht][i]; + } + for (unsigned i = 0; i < numFillBytes; i++) { + buffer[pos++] = 0xFF; + } + } + } + + // Write speed zone area (32 bit, little endian) + for (Halftrack ht = 1; ht <= 84; ht++) { + buffer[pos++] = Disk::trackDefaults[(ht + 1) / 2].speedZone; + buffer[pos++] = 0; + buffer[pos++] = 0; + buffer[pos++] = 0; + } + assert(pos == length); + + return G64File::makeWithBuffer(buffer, length); +} + +void +G64File::selectHalftrack(Halftrack ht) +{ + assert(isHalftrackNumber(ht)); + + selectedHalftrack = ht; + tFp = getStartOfHalftrack(ht) + 2 /* length info */; + tEof = tFp + getSizeOfHalftrack(); +} + +void +G64File::seekHalftrack(long offset) +{ + assert(isHalftrackNumber(selectedHalftrack)); + + tFp = getStartOfHalftrack(selectedHalftrack) + 2 + offset; + + if (tFp >= size) + tFp = -1; +} + +size_t +G64File::getSizeOfHalftrack() +{ + assert(isHalftrackNumber(selectedHalftrack)); + + long offset = getStartOfHalftrack(selectedHalftrack); + return offset ? LO_HI(data[offset], data[offset+1]) : 0; +} + +long +G64File::getStartOfHalftrack(Halftrack ht) +{ + assert(isHalftrackNumber(ht)); + + int offset = 0x008 + (4 * ht); + return LO_LO_HI_HI(data[offset], data[offset+1], data[offset+2], data[offset+3]); +} + diff --git a/C64/FileFormats/G64File.h b/C64/FileFormats/G64File.h new file mode 100755 index 00000000..4216eefa --- /dev/null +++ b/C64/FileFormats/G64File.h @@ -0,0 +1,113 @@ +/*! + * @header G64File.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _G64FILE_INC +#define _G64FILE_INC + +#include "AnyDisk.h" +#include "Disk.h" + +/*! @class G64File + * @brief The G64File class declares the programmatic interface for a file + * in G64 format. + */ +class G64File : public AnyDisk { + +private: + + //! @brief Header signature + static const uint8_t magicBytes[]; + + /*! @brief Number of the currently selected halftrack + * @details 0, if no halftrack is selected + */ + Halftrack selectedHalftrack = 0; + +public: + + // + //! @functiongroup Class methods + // + + //! @brief Returns true iff buffer contains a G64 file + static bool isG64Buffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff the specified file is a G64 file + static bool isG64File(const char *filename); + + + // + //! @functiongroup Creating and destructing G64 archives + // + + //! @brief Standard constructor + G64File(); + + //! @brief Creates an empty G64 file with the specified capacity + G64File(size_t capacity); + + //! @brief Factory method + static G64File *makeWithBuffer(const uint8_t *buffer, size_t length); + + //! @brief Factory method + static G64File *makeWithFile(const char *path); + + //! @brief Factory method + static G64File *makeWithDisk(Disk *disk); + + + // + //! @functiongroup Methods from AnyC64File + // + + C64FileType type() { return G64_FILE; } + const char *typeAsString() { return "G64"; } + bool hasSameType(const char *filename) { return G64File::isG64File(filename); } + + + // + //! @functiongroup Methods from AnyArchive (API not supported by G64 format) + // + + int numberOfItems() { assert(false); return 0; }; + size_t getSizeOfItem() { assert(false); return 0; } + const char *getNameOfItem() { assert(false); return ""; } + const char *getTypeOfItem() { assert(false); return ""; } + uint16_t getDestinationAddrOfItem() { assert(false); return 0; } + void selectItem(unsigned n) { assert(false); } + uint32_t getStartOfItem(unsigned n) { assert(false); return 0; } + + + // + //! @functiongroup Methods from AnyDisk + // + + int numberOfHalftracks() { return 84; } + void selectHalftrack(Halftrack ht); + size_t getSizeOfHalftrack(); + void seekHalftrack(long offset); + +private: + + long getStartOfHalftrack(Halftrack ht); +}; + +#endif + diff --git a/C64/FileFormats/P00File.cpp b/C64/FileFormats/P00File.cpp new file mode 100755 index 00000000..1f019f71 --- /dev/null +++ b/C64/FileFormats/P00File.cpp @@ -0,0 +1,180 @@ +/*! + * @file P00File.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "P00File.h" +#include + +const uint8_t +P00File::magicBytes[] = { 0x43, 0x36, 0x34, 0x46, 0x69, 0x6C, 0x65 }; + +bool +P00File::isP00Buffer(const uint8_t *buffer, size_t length) +{ + if (length < 0x1A) return false; + return matchingBufferHeader(buffer, magicBytes, sizeof(magicBytes)); +} + +bool +P00File::isP00File(const char *filename) +{ + assert (filename != NULL); + + if (!checkFileSize(filename, 0x1A, -1)) + return false; + + if (!matchingFileHeader(filename, magicBytes, sizeof(magicBytes))) + return false; + + return true; +} + +P00File::P00File() +{ + setDescription("P00Archive"); +} + +P00File * +P00File::makeWithBuffer(const uint8_t *buffer, size_t length) +{ + P00File *archive = new P00File(); + + if (!archive->readFromBuffer(buffer, length)) { + delete archive; + return NULL; + } + + return archive; +} + +P00File * +P00File::makeWithFile(const char *filename) +{ + P00File *archive = new P00File(); + + if (!archive->readFromFile(filename)) { + delete archive; + return NULL; + } + + return archive; +} + +P00File * +P00File::makeWithAnyArchive(AnyArchive *otherArchive) +{ + if (otherArchive == NULL || otherArchive->numberOfItems() == 0) + return NULL; + + otherArchive->selectItem(0); + + P00File *archive = new P00File(); + archive->debug(1, "Creating P00 archive from %s archive...\n", otherArchive->typeAsString()); + + // Determine file size and allocate memory + try { + + archive->size = 8 + 17 + 1 + 2 + otherArchive->getSizeOfItem(); + archive->data = new uint8_t[archive->size]; + } + catch (std::bad_alloc&) { + + archive->warn("Failed to allocate %d bytes of memory\n", archive->size); + delete archive; + return NULL; + } + + // Magic bytes (8 bytes) + uint8_t *ptr = archive->data; + strcpy((char *)ptr, "C64File"); + ptr += 8; + + // Name in PET format (17 bytes) + strncpy((char *)ptr, (char *)otherArchive->getName(), 17); + for (unsigned i = 0; i < 17; i++, ptr++) + *ptr = ascii2pet(*ptr); + + // Record size (applies to REL files, only) (1 byte) + *ptr++ = 0; + + // Load address (2 bytes) + *ptr++ = LO_BYTE(otherArchive->getDestinationAddrOfItem()); + *ptr++ = HI_BYTE(otherArchive->getDestinationAddrOfItem()); + + // File data + int byte; + otherArchive->selectItem(0); + while ((byte = otherArchive->readItem()) != EOF) { + *ptr++ = (uint8_t)byte; + } + + return archive; +} + +const char * +P00File::getName() +{ + unsigned i; + + for (i = 0; i < 17; i++) { + name[i] = data[0x08+i]; + } + name[i] = 0x00; + return name; +} + +void +P00File::selectItem(unsigned item) +{ + if (item == 0) { + iFp = 0x1C; + iEof = size; + } else { + iFp = -1; + } +} + +const char * +P00File::getNameOfItem() +{ + unsigned i; + + for (i = 0; i < 17; i++) { + name[i] = data[0x08+i]; + } + name[i] = 0x00; + return name; +} + +void +P00File::seekItem(long offset) +{ + assert(iFp != -1); + + iFp = 0x1C + offset; + + if (iFp >= size) + iFp = -1; +} + +uint16_t +P00File::getDestinationAddrOfItem() +{ + return LO_HI(data[0x1A], data[0x1B]); +} diff --git a/C64/FileFormats/P00File.h b/C64/FileFormats/P00File.h new file mode 100755 index 00000000..6d4dc409 --- /dev/null +++ b/C64/FileFormats/P00File.h @@ -0,0 +1,91 @@ +/*! + * @header P00File.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _P00FILE_INC +#define _P00FILE_INC + +#include "AnyArchive.h" + +/*! @class P00File + * @brief The P00File class declares the programmatic interface for a file + * in P00 format. + */ +class P00File : public AnyArchive { + +private: + + //! @brief Header signature + static const uint8_t magicBytes[]; + +public: + + // + //! @functiongroup Class methods + // + + //! @brief Returns true iff buffer contains a P00 file. + static bool isP00Buffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff the specified file is a P00 file. + static bool isP00File(const char *filename); + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Standard constructor + P00File(); + + //! @brief Factory method + static P00File *makeWithBuffer(const uint8_t *buffer, size_t length); + + //! @brief Factory method + static P00File *makeWithFile(const char *path); + + /*! @brief Factory method + * @details otherArchive can be of any archive type + */ + static P00File *makeWithAnyArchive(AnyArchive *otherArchive); + + + // + //! @functiongroup Methods from AnyFile + // + + const char *getName(); + C64FileType type() { return P00_FILE; } + const char *typeAsString() { return "P00"; } + bool hasSameType(const char *filename) { return isP00File(filename); } + + + // + //! @functiongroup Methods from AnyArchive + // + + int numberOfItems() { return 1; } + void selectItem(unsigned item); + const char *getTypeOfItemAsString() { return "PRG"; } + const char *getNameOfItem(); + size_t getSizeOfItem() { return size - 0x1C; } + void seekItem(long offset); + uint16_t getDestinationAddrOfItem(); +}; +#endif diff --git a/C64/FileFormats/PRGFile.cpp b/C64/FileFormats/PRGFile.cpp new file mode 100755 index 00000000..576b793e --- /dev/null +++ b/C64/FileFormats/PRGFile.cpp @@ -0,0 +1,141 @@ +/*! + * @file PRGFile.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "PRGFile.h" + +bool +PRGFile::isPRGBuffer(const uint8_t *buffer, size_t length) +{ + return length >= 2; +} + +bool +PRGFile::isPRGFile(const char *filename) +{ + assert(filename != NULL); + + if (!checkFileSuffix(filename, ".PRG") && !checkFileSuffix(filename, ".prg")) + return false; + + if (!checkFileSize(filename, 2, -1)) + return false; + + return true; +} + +PRGFile::PRGFile() +{ + setDescription("PRGArchive"); +} + +PRGFile * +PRGFile::makeWithBuffer(const uint8_t *buffer, size_t length) +{ + PRGFile *archive = new PRGFile(); + + if (!archive->readFromBuffer(buffer, length)) { + + delete archive; + return NULL; + } + + return archive; +} + +PRGFile * +PRGFile::makeWithFile(const char *filename) +{ + PRGFile *archive = new PRGFile(); + + if (!archive->readFromFile(filename)) { + + delete archive; + return NULL; + } + + return archive; +} + +PRGFile * +PRGFile::makeWithAnyArchive(AnyArchive *otherArchive) { + + int exportItem = 0; + + if (otherArchive == NULL || otherArchive->numberOfItems() <= exportItem) + return NULL; + + PRGFile *archive = new PRGFile(); + archive->debug(1, "Creating PRG archive from %s archive...\n", + otherArchive->typeAsString()); + + otherArchive->selectItem(exportItem); + + // Determine file size and allocate memory + archive->size = 2 + otherArchive->getSizeOfItem(); + if ((archive->data = new uint8_t[archive->size]) == NULL) { + archive->warn("Failed to allocate %d bytes of memory\n", archive->size); + delete archive; + return NULL; + } + + // Load address + uint8_t* ptr = archive->data; + *ptr++ = LO_BYTE(otherArchive->getDestinationAddrOfItem()); + *ptr++ = HI_BYTE(otherArchive->getDestinationAddrOfItem()); + + // File data + int byte; + otherArchive->selectItem(exportItem); + while ((byte = otherArchive->readItem()) != EOF) { + *ptr++ = (uint8_t)byte; + } + + return archive; +} + +void +PRGFile::selectItem(unsigned item) +{ + if (item == 0) { + iFp = 2; + iEof = size; + } else { + iFp = -1; + } +} + +void +PRGFile::seekItem(long offset) +{ + assert(iFp != -1); + + iFp = 2 + offset; + + if (iFp >= size) + iFp = -1; +} + +uint16_t +PRGFile::getDestinationAddrOfItem() +{ + return LO_HI(data[0], data[1]); +} + + diff --git a/C64/FileFormats/PRGFile.h b/C64/FileFormats/PRGFile.h new file mode 100755 index 00000000..76de03a6 --- /dev/null +++ b/C64/FileFormats/PRGFile.h @@ -0,0 +1,90 @@ +/*! + * @header PRGFile.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PRGFILE_INC +#define _PRGFILE_INC + +#include "AnyArchive.h" + +/*! @class PRGFile + * @brief The PRGFile class declares the programmatic interface for a file + * in PRG format. + */ +class PRGFile : public AnyArchive { + +public: + + // + //! @functiongroup Class methods + // + + //! @brief Returns true if buffer contains a PRG file + /*! @details PRG files can only be determined by their suffix, so this + * function will return true unless you provide a buffer with + * less than two bytes. + */ + static bool isPRGBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff the specified file is a PRG file. + static bool isPRGFile(const char *filename); + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Standard constructor + PRGFile(); + + //! @brief Factory method + static PRGFile *makeWithBuffer(const uint8_t *buffer, size_t length); + + //! @brief Factory method + static PRGFile *makeWithFile(const char *path); + + /*! @brief Factory method + * @details otherArchive can be of any archive type + */ + static PRGFile *makeWithAnyArchive(AnyArchive *otherArchive); + + + // + //! @functiongroup Methods from AnyFile + // + + C64FileType type() { return PRG_FILE; } + const char *typeAsString() { return "PRG"; } + bool hasSameType(const char *filename) { return isPRGFile(filename); } + + + // + //! @functiongroup Methods from AnyArchive + // + + int numberOfItems() { return 1; } + void selectItem(unsigned item); + const char *getTypeOfItemAsString() { return "PRG"; } + const char *getNameOfItem() { return "FILE"; } + size_t getSizeOfItem() { return size - 2; } + void seekItem(long offset); + uint16_t getDestinationAddrOfItem(); +}; +#endif diff --git a/C64/FileFormats/ROMFile.cpp b/C64/FileFormats/ROMFile.cpp new file mode 100755 index 00000000..ce7a19b2 --- /dev/null +++ b/C64/FileFormats/ROMFile.cpp @@ -0,0 +1,238 @@ +/*! + * @file ROMFile.c + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +const uint8_t ROMFile::magicBasicRomBytes[basicRomSignatureCnt][3] = { + + { 0x94, 0xE3, 0x7B }, // Commodore ROM + { 0xB9, 0xA7, 0xC1 }, // MEGA65 project + { 0x63, 0xA6, 0xC1 } // MEGA65 project + +}; + +const uint8_t ROMFile::magicCharRomBytes[charRomSignatureCnt][4] = { + + { 0x3C, 0x66, 0x6E, 0x6E }, // Commodore ROM + { 0x00, 0x3C, 0x66, 0x6E }, // chargen-atari800 (1130C1CE287876DD) + { 0x70, 0x88, 0x08, 0x68 }, // chargen-msx (975546A5B6168FFD) + { 0x00, 0x3C, 0x4A, 0x56 }, // chargen-speccy (7C74107C9365F735) + { 0x7C, 0xC6, 0xDE, 0xDE }, // chargen-amstradcpc (AFFE8B0EE2176CBD) + { 0x7C, 0xC6, 0xDE, 0xDE }, // Amiga 500 Topaz (D14C5BE4FEE17705) + { 0x7C, 0xC6, 0xDE, 0xDE }, // Amiga 500 Topaz V2 (A2C6A6E2C0477981) + { 0x7C, 0xC6, 0xDE, 0xD6 }, // Amiga 1200 Topaz (3BF55C821EE80365) + { 0x7C, 0xC6, 0xDE, 0xD6 }, // Amiga 1200 Topaz V2 (19F0DD3F3F9C4FE9) + { 0x38, 0x44, 0x5C, 0x54 }, // Teletext (E527AD3E0DDE930D) +}; + +const uint8_t ROMFile::magicKernalRomBytes[kernalRomSignatureCnt][3] = { + + { 0x85, 0x56, 0x20 }, // Commodore ROM + { 0x20, 0x70, 0xA8 }, // MEGA65 project + { 0x20, 0x02, 0xBE } +}; + +const uint8_t ROMFile::magicVC1541RomBytes[vc1541RomSignatureCnt][3] = { + + { 0x97, 0xAA, 0xAA }, // Commodore ROM + { 0x97, 0xE0, 0x43 }, // Commodore ROM + { 0x97, 0x46, 0xAD }, // Commodore ROM + { 0x97, 0xDB, 0x43 } // Commodore ROM +}; + +bool +ROMFile::isRomBuffer(const uint8_t *buffer, size_t length) +{ + return + isBasicRomBuffer(buffer, length) || + isCharRomBuffer(buffer, length) || + isKernalRomBuffer(buffer, length) || + isVC1541RomBuffer(buffer, length); +} + +bool +ROMFile::isBasicRomBuffer(const uint8_t *buffer, size_t length) +{ + if (length != 0x2000) return false; + + for (int i = 0; i < basicRomSignatureCnt; i++) { + if (matchingBufferHeader(buffer, magicBasicRomBytes[i], sizeof(magicBasicRomBytes[i]))) { + return true; + } + } + + return false; +} + +bool +ROMFile::isCharRomBuffer(const uint8_t *buffer, size_t length) +{ + if (length != 0x1000) return false; + + for (int i = 0; i < charRomSignatureCnt; i++) { + if (matchingBufferHeader(buffer, magicCharRomBytes[i], sizeof(magicCharRomBytes[i]))) { + return true; + } + } + + return false; +} + +bool +ROMFile::isKernalRomBuffer(const uint8_t *buffer, size_t length) +{ + if (length != 0x2000) return false; + + for (int i = 0; i < kernalRomSignatureCnt; i++) { + if (matchingBufferHeader(buffer, magicKernalRomBytes[i], sizeof(magicKernalRomBytes[i]))) { + return true; + } + } + + return false; +} + +bool +ROMFile::isVC1541RomBuffer(const uint8_t *buffer, size_t length) +{ + if (length != 0x4000) return false; + + for (int i = 0; i < vc1541RomSignatureCnt; i++) { + if (matchingBufferHeader(buffer, magicVC1541RomBytes[i], sizeof(magicVC1541RomBytes[i]))) { + return true; + } + } + + return false; +} + +bool +ROMFile::isRomFile(const char *filename) +{ + return + isBasicRomFile(filename) || + isCharRomFile(filename) || + isKernalRomFile(filename) || + isVC1541RomFile(filename); +} + +bool +ROMFile::isBasicRomFile(const char *filename) +{ + if (!checkFileSize(filename, 0x2000, 0x2000)) return false; + + for (int i = 0; i < basicRomSignatureCnt; i++) { + if (matchingFileHeader(filename, magicBasicRomBytes[i], sizeof(magicBasicRomBytes[i]))) { + return true; + } + } + + return false; +} + +bool +ROMFile::isCharRomFile(const char *filename) +{ + if (!checkFileSize(filename, 0x1000, 0x1000)) return false; + + for (int i = 0; i < charRomSignatureCnt; i++) { + if (matchingFileHeader(filename, magicCharRomBytes[i], sizeof(magicCharRomBytes[i]))) { + return true; + } + } + + return false; +} + +bool +ROMFile::isKernalRomFile(const char *filename) +{ + if (!checkFileSize(filename, 0x2000, 0x2000)) return false; + + for (int i = 0; i < kernalRomSignatureCnt; i++) { + if (matchingFileHeader(filename, magicKernalRomBytes[i], sizeof(magicKernalRomBytes[i]))) { + return true; + } + } + + return false; +} + +bool +ROMFile::isVC1541RomFile(const char *filename) +{ + if (!checkFileSize(filename, 0x4000, 0x4000)) return false; + + for (int i = 0; i < vc1541RomSignatureCnt; i++) { + if (matchingFileHeader(filename, magicVC1541RomBytes[i], sizeof(magicVC1541RomBytes[i]))) { + return true; + } + } + + return false; +} + +ROMFile * +ROMFile::makeWithBuffer(const uint8_t *buffer, size_t length) +{ + ROMFile *rom = new ROMFile(); + + if (!rom->readFromBuffer(buffer, length)) { + delete rom; + return NULL; + } + + return rom; +} + +ROMFile * +ROMFile::makeWithFile(const char *filename) +{ + ROMFile *rom = new ROMFile(); + + if (!rom->readFromFile(filename)) { + delete rom; + return NULL; + } + + return rom; +} + +ROMFile::ROMFile() +{ + setDescription("ROMFile"); + romtype = UNKNOWN_FILE_FORMAT; +} + +bool +ROMFile::readFromBuffer(const uint8_t *buffer, size_t length) +{ + if (!AnyC64File::readFromBuffer(buffer, length)) + return false; + + romtype = + isBasicRomBuffer(buffer, length) ? BASIC_ROM_FILE : + isCharRomBuffer(buffer, length) ? CHAR_ROM_FILE : + isKernalRomBuffer(buffer, length) ? KERNAL_ROM_FILE : + isVC1541RomBuffer(buffer, length) ? VC1541_ROM_FILE : + UNKNOWN_FILE_FORMAT; + + return romtype != UNKNOWN_FILE_FORMAT; +} diff --git a/C64/FileFormats/ROMFile.h b/C64/FileFormats/ROMFile.h new file mode 100755 index 00000000..9bd1eb07 --- /dev/null +++ b/C64/FileFormats/ROMFile.h @@ -0,0 +1,109 @@ +/*! + * @header ROMFile.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ROMFILE_INC +#define _ROMFILE_INC + +#include "AnyC64File.h" + +/*! @class ROMFile + * @brief Represents a ROM image file. + */ +class ROMFile : public AnyC64File { + +private: + + //! @brief Header signatures + static const size_t basicRomSignatureCnt = 3; + static const size_t charRomSignatureCnt = 10; + static const size_t kernalRomSignatureCnt = 3; + static const size_t vc1541RomSignatureCnt = 4; + + static const uint8_t magicBasicRomBytes[basicRomSignatureCnt][3]; + static const uint8_t magicCharRomBytes[charRomSignatureCnt][4]; + static const uint8_t magicKernalRomBytes[kernalRomSignatureCnt][3]; + static const uint8_t magicVC1541RomBytes[vc1541RomSignatureCnt][3]; + + //! @brief ROM type (Basic ROM, Kernal ROM, etc.) + C64FileType romtype; + +public: + + // + //! @functiongroup Class methods + // + + //! @brief Returns true iff buffer contains a ROM image + static bool isRomBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff buffer contains a Basic ROM image + static bool isBasicRomBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff buffer contains a Character ROM image + static bool isCharRomBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff buffer contains a Kernal ROM image + static bool isKernalRomBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff buffer contains a VC1541 ROM image + static bool isVC1541RomBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff filename points to a ROM file + static bool isRomFile(const char *filename); + + //! @brief Returns true iff filename points to a Basic ROM file + static bool isBasicRomFile(const char *filename); + + //! @brief Returns true iff filename points to a Character ROM file + static bool isCharRomFile(const char *filename); + + //! @brief Returns true iff filename points to a Kernal ROM file + static bool isKernalRomFile(const char *filename); + + //! @brief Returns true iff filename points to a VC1541 ROM file + static bool isVC1541RomFile(const char *filename); + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Standard constructor + ROMFile(); + + //! @brief Factory method + static ROMFile *makeWithBuffer(const uint8_t *buffer, size_t length); + + //! @brief Factory method + static ROMFile *makeWithFile(const char *filename); + + + // + //! @functiongroup Methods from AnyC64File + // + + C64FileType type() { return romtype; } + const char *typeAsString() { return "ROM"; } + bool hasSameType(const char *filename) { return isRomFile(filename); } + bool readFromBuffer(const uint8_t *buffer, size_t length); + +}; +#endif diff --git a/C64/FileFormats/Snapshot.cpp b/C64/FileFormats/Snapshot.cpp new file mode 100755 index 00000000..3ff78c42 --- /dev/null +++ b/C64/FileFormats/Snapshot.cpp @@ -0,0 +1,182 @@ +/*! + * @file Snapshot.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +const uint8_t Snapshot::magicBytes[] = { 'V', 'C', '6', '4' }; + +bool +Snapshot::isSnapshot(const uint8_t *buffer, size_t length) +{ + assert(buffer != NULL); + + if (length < 0x15) return false; + return matchingBufferHeader(buffer, magicBytes, sizeof(magicBytes)); +} + +bool +Snapshot::isSnapshot(const uint8_t *buffer, size_t length, + uint8_t major, uint8_t minor, uint8_t subminor) +{ + if (!isSnapshot(buffer, length)) return false; + return buffer[4] == major && buffer[5] == minor && buffer[6] == subminor; +} + +bool +Snapshot::isSupportedSnapshot(const uint8_t *buffer, size_t length) +{ + return isSnapshot(buffer, length, V_MAJOR, V_MINOR, V_SUBMINOR); +} + +bool +Snapshot::isUnsupportedSnapshot(const uint8_t *buffer, size_t length) +{ + return isSnapshot(buffer, length) && !isSupportedSnapshot(buffer, length); +} + +bool +Snapshot::isSnapshotFile(const char *path) +{ + assert(path != NULL); + + if (!matchingFileHeader(path, magicBytes, sizeof(magicBytes))) + return false; + + return true; +} + +bool +Snapshot::isSnapshotFile(const char *path, uint8_t major, uint8_t minor, uint8_t subminor) +{ + uint8_t signature[] = { 'V', 'C', '6', '4', major, minor, subminor }; + + assert(path != NULL); + + if (!matchingFileHeader(path, signature, sizeof(signature))) + return false; + + return true; +} + +bool +Snapshot::isSupportedSnapshotFile(const char *path) +{ + return isSnapshotFile(path, V_MAJOR, V_MINOR, V_SUBMINOR); +} + +bool +Snapshot::isUnsupportedSnapshotFile(const char *path) +{ + return isSnapshotFile(path) && !isSupportedSnapshotFile(path); +} + +Snapshot::Snapshot() +{ + setDescription("Snapshot"); +} + +Snapshot::Snapshot(size_t capacity) +{ + size = capacity + sizeof(SnapshotHeader); + data = new uint8_t[size]; + + SnapshotHeader *header = (SnapshotHeader *)data; + header->magic[0] = magicBytes[0]; + header->magic[1] = magicBytes[1]; + header->magic[2] = magicBytes[2]; + header->magic[3] = magicBytes[3]; + header->major = V_MAJOR; + header->minor = V_MINOR; + header->subminor = V_SUBMINOR; + header->timestamp = time(NULL); +} + +Snapshot * +Snapshot::makeWithBuffer(const uint8_t *buffer, size_t length) +{ + Snapshot *snapshot; + + snapshot = new Snapshot(); + if (!snapshot->readFromBuffer(buffer, length)) { + delete snapshot; + return NULL; + } + return snapshot; +} + +Snapshot * +Snapshot::makeWithFile(const char *filename) +{ + Snapshot *snapshot; + + snapshot = new Snapshot(); + if (!snapshot->readFromFile(filename)) { + delete snapshot; + return NULL; + } + return snapshot; +} + +Snapshot * +Snapshot::makeWithC64(C64 *c64) +{ + Snapshot *snapshot; + + snapshot = new Snapshot(c64->stateSize()); + snapshot->takeScreenshot(c64); + uint8_t *ptr = snapshot->getData(); + c64->saveToBuffer(&ptr); + + return snapshot; +} + +bool +Snapshot::hasSameType(const char *filename) +{ + return Snapshot::isSnapshotFile(filename, V_MAJOR, V_MINOR, V_SUBMINOR); +} + +void +Snapshot::takeScreenshot(C64 *c64) +{ + SnapshotHeader *header = (SnapshotHeader *)data; + unsigned x_start, y_start; + + if (c64->vic.isPAL()) { + x_start = PAL_LEFT_BORDER_WIDTH - 36; + y_start = PAL_UPPER_BORDER_HEIGHT - 34; + header->screenshot.width = 36 + PAL_CANVAS_WIDTH + 36; + header->screenshot.height = 34 + PAL_CANVAS_HEIGHT + 34; + } else { + x_start = NTSC_LEFT_BORDER_WIDTH - 42; + y_start = NTSC_UPPER_BORDER_HEIGHT - 9; + header->screenshot.width = 36 + PAL_CANVAS_WIDTH + 36; + header->screenshot.height = 9 + PAL_CANVAS_HEIGHT + 9; + } + + uint32_t *source = (uint32_t *)c64->vic.screenBuffer(); + uint32_t *target = header->screenshot.screen; + source += x_start + y_start * NTSC_PIXELS; + for (unsigned i = 0; i < header->screenshot.height; i++) { + memcpy(target, source, header->screenshot.width * 4); + target += header->screenshot.width; + source += NTSC_PIXELS; + } +} diff --git a/C64/FileFormats/Snapshot.h b/C64/FileFormats/Snapshot.h new file mode 100755 index 00000000..7641d976 --- /dev/null +++ b/C64/FileFormats/Snapshot.h @@ -0,0 +1,164 @@ +/*! + * @header Snapshot.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef _SNAPSHOT_INC +#define _SNAPSHOT_INC + +#include "AnyC64File.h" + +// Forward declarations +class C64; + +// Snapshot header +typedef struct { + + //! @brief Magic bytes ('V','C','6','4') + char magic[4]; + + //! @brief Version number (V major.minor.subminor) + uint8_t major; + uint8_t minor; + uint8_t subminor; + + //! @brief Screenshot + struct { + + //! @brief Image width and height + uint16_t width, height; + + //! @brief Screen buffer data + uint32_t screen[PAL_RASTERLINES * NTSC_PIXELS]; + + } screenshot; + + //! @brief Date and time of snapshot creation + time_t timestamp; + +} SnapshotHeader; + + +/*! @class Snapshot + * @brief The Snapshot class declares the programmatic interface for a file + * in V64 format (VirtualC64 snapshot files). + */ +class Snapshot : public AnyC64File { + + private: + + //! @brief Header signature + static const uint8_t magicBytes[]; + + + // + //! @functiongroup Class methods + // + + public: + + //! @brief Returns true iff buffer contains a snapshot. + static bool isSnapshot(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff buffer contains a snapshot of a specific version. + static bool isSnapshot(const uint8_t *buffer, size_t length, + uint8_t major, uint8_t minor, uint8_t subminor); + + //! @brief Returns true iff buffer contains a snapshot with a supported version number. + static bool isSupportedSnapshot(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff buffer contains a snapshot with an outdated version number. + static bool isUnsupportedSnapshot(const uint8_t *buffer, size_t length); + + //! @brief Returns true if path points to a snapshot file. + static bool isSnapshotFile(const char *path); + + //! @brief Returns true if file points to a snapshot file of a specific version. + static bool isSnapshotFile(const char *path, uint8_t major, uint8_t minor, uint8_t subminor); + + //! @brief Returns true if file is a snapshot with a supported version number. + static bool isSupportedSnapshotFile(const char *path); + + //! @brief Returns true if file is a snapshot with an outdated version number. + static bool isUnsupportedSnapshotFile(const char *path); + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Standard Constructor + Snapshot(); + + //! @brief Custom Constructor + Snapshot(size_t capacity); + + //! @brief Allocates memory for storing the emulator state. + bool setCapacity(size_t size); + + //! @brief Factory method + static Snapshot *makeWithFile(const char *filename); + + //! @brief Factory method + static Snapshot *makeWithBuffer(const uint8_t *buffer, size_t size); + + //! @brief Factory method + static Snapshot *makeWithC64(C64 *c64); + + + // + //! @functiongroup Methods from AnyC64File + // + + C64FileType type() { return V64_FILE; } + const char *typeAsString() { return "V64"; } + bool hasSameType(const char *filename); + + + // + //! @functiongroup Accessing snapshot properties + // + + public: + + //! @brief Returns pointer to header data + SnapshotHeader *getHeader() { return (SnapshotHeader *)data; } + + //! @brief Returns pointer to core data + uint8_t *getData() { return data + sizeof(SnapshotHeader); } + + //! @brief Returns the timestamp + time_t getTimestamp() { return getHeader()->timestamp; } + + //! @brief Returns a pointer to the screenshot data. + unsigned char *getImageData() { return (unsigned char *)(getHeader()->screenshot.screen); } + + //! @brief Returns the screenshot image width + unsigned getImageWidth() { return getHeader()->screenshot.width; } + + //! @brief Returns the screenshot image height + unsigned getImageHeight() { return getHeader()->screenshot.height; } + + //! @brief Stores a screenshot inside this snapshot + void takeScreenshot(C64 *c64); + +}; + +#endif + diff --git a/C64/FileFormats/T64File.cpp b/C64/FileFormats/T64File.cpp new file mode 100755 index 00000000..6b7db247 --- /dev/null +++ b/C64/FileFormats/T64File.cpp @@ -0,0 +1,427 @@ +/*! + * @file T64File.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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, org + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "TAPFile.h" +#include "T64File.h" + +/* "Anmerkung: Der String muß nicht wortwörtlich so vorhanden sein. Man sollte nach den + * Substrings 'C64' und 'tape' suchen." [Power64 doc] + */ +const uint8_t T64File::magicBytes[] = { 0x43, 0x36, 0x34 }; + +bool +T64File::isT64Buffer(const uint8_t *buffer, size_t length) +{ + if (length < 0x40) + return false; + + if (TAPFile::isTAPBuffer(buffer, length)) // Note: TAP files have a very similar header + return false; + + return matchingBufferHeader(buffer, magicBytes, sizeof(magicBytes)); +} + +bool +T64File::isT64File(const char *path) +{ + assert(path != NULL); + + if (!checkFileSuffix(path, ".T64") && !checkFileSuffix(path, ".t64")) + return false; + + if (TAPFile::isTAPFile(path)) // Note: TAP files have a very similar header + return false; + + if (!checkFileSize(path, 0x40, -1)) + return false; + + if (!matchingFileHeader(path, magicBytes, sizeof(magicBytes))) + return false; + + return true; +} + +T64File::T64File() +{ + setDescription("T64Archive"); +} + +T64File * +T64File::makeWithBuffer(const uint8_t *buffer, size_t length) +{ + T64File *archive = new T64File(); + + if (!archive->readFromBuffer(buffer, length)) { + delete archive; + return NULL; + } + + return archive; +} + +T64File * +T64File::makeWithFile(const char *path) +{ + T64File *archive = new T64File(); + + if (!archive->readFromFile(path)) { + delete archive; + return NULL; + } + + return archive; +} + +T64File * +T64File::makeT64ArchiveWithAnyArchive(AnyArchive *otherArchive) +{ + if (otherArchive == NULL) + return NULL; + + T64File *archive = new T64File(); + + // Determine file size and allocate memory + unsigned currentFiles = otherArchive->numberOfItems(); + unsigned maxFiles = (currentFiles < 30) ? 30 : currentFiles; + archive->size = 64 /* header */ + maxFiles * 32 /* tape entries */; + + for (unsigned i = 0; i < currentFiles; i++) { + archive->selectItem(i); + archive->size += otherArchive->getSizeOfItem(); + } + + if ((archive->data = new uint8_t[archive->size]) == NULL) { + archive->warn("Failed to allocate %d bytes of memory\n", archive->size); + delete archive; + return NULL; + } + + // Magic bytes (32 bytes) + uint8_t *ptr = archive->data; + strncpy((char *)ptr, "C64 tape image file", 32); + ptr += 32; + + // Version (2 bytes) + *ptr++ = 0x00; + *ptr++ = 0x01; + + // Max files (2 bytes) + *ptr++ = LO_BYTE(maxFiles); + *ptr++ = HI_BYTE(maxFiles); + + // Current files (2 bytes) + *ptr++ = LO_BYTE(currentFiles); + *ptr++ = HI_BYTE(currentFiles); + + // Reserved (2 bytes) + *ptr++ = 0x00; + *ptr++ = 0x00; + + // User description (24 bytes) + strncpy((char *)ptr, (char *)otherArchive->getName(), 24); + for (unsigned i = 0; i < 24; i++, ptr++) + *ptr = ascii2pet(*ptr); + + // Tape entries + uint32_t tapePosition = 64 + maxFiles * 32; // data of item 0 starts here + memset(ptr, 0, 32 * maxFiles); + for (unsigned n = 0; n < maxFiles; n++) { + + if (n >= currentFiles) { + // Empty tape slot + ptr += 32; + continue; + } + + otherArchive->selectItem(n); + + // Entry used (1 byte) + *ptr++ = 0x01; + + // File type (1 byte) + *ptr++ = 0x82; + + // Start address (2 bytes) + uint16_t startAddr = otherArchive->getDestinationAddrOfItem(); + *ptr++ = LO_BYTE(startAddr); + *ptr++ = HI_BYTE(startAddr); + + // Start address (2 bytes) + uint16_t endAddr = startAddr + otherArchive->getSizeOfItem(); + *ptr++ = LO_BYTE(endAddr); + *ptr++ = HI_BYTE(endAddr); + + // Reserved (2 bytes) + ptr += 2; + + // Tape position (4 bytes) + *ptr++ = LO_BYTE(tapePosition); + *ptr++ = LO_BYTE(tapePosition >> 8); + *ptr++ = LO_BYTE(tapePosition >> 16); + *ptr++ = LO_BYTE(tapePosition >> 24); + tapePosition += otherArchive->getSizeOfItem(); + + // Reserved (4 bytes) + ptr += 4; + + // File name (16 bytes) + strncpy((char *)ptr, (char *)otherArchive->getNameOfItem(), 16); + for (unsigned i = 0; i < 16; i++, ptr++) + *ptr = ascii2pet(*ptr); + } + + // File data + for (unsigned n = 0; n < currentFiles; n++) { + + int byte; + otherArchive->selectItem(n); + while ((byte = otherArchive->readItem()) != EOF) { + *ptr++ = (uint8_t)byte; + } + + } + + otherArchive->dumpDirectory(); + archive->dumpDirectory(); + archive->debug(1, "T64 archive created with other archive of type %s.\n", + otherArchive->typeAsString()); + + return archive; +} + +const char * +T64File::getName() +{ + int i,j; + int first = 0x28; + int last = 0x40; + + for (j = 0, i = first; i < last; i++, j++) { + name[j] = (data[i] == 0x20 ? ' ' : data[i]); + if (j == 255) break; + } + name[j] = 0x00; + return name; +} + +bool +T64File::readFromBuffer(const uint8_t *buffer, size_t length) +{ + if (!AnyC64File::readFromBuffer(buffer, length)) + return false; + + // Some T64 archives contain incosistencies. We fix them asap + (void)repair(); + + return true; +} + +int +T64File::numberOfItems() +{ + return LO_HI(data[0x24], data[0x25]); +} + +void +T64File::selectItem(unsigned item) +{ + // Invalidate the file pointer if a non-existing item is requested. + if (item >= numberOfItems()) { + iFp = -1; + return; + } + + // Remember the selection + selectedItem = item; + + // Set file pointer and end of file index + unsigned i = 0x48 + (item * 0x20); + iFp = LO_LO_HI_HI(data[i], data[i+1], data[i+2], data[i+3]); + iEof = iFp + getSizeOfItem(); + + // Check for inconsistent values. As all inconsistencies should have + // been ruled out by repair(), the if condition should never hit. + if (iFp > size || iEof > size) { + assert(false); + } +} + +const char * +T64File::getTypeOfItemAsString() +{ + assert(selectedItem != -1); + + long i = 0x41 + (selectedItem * 0x20); + if (data[i] != 00) + return "PRG"; + if (data[i] == 00 && data[i-1] > 0x00) + return "FRZ"; + return "???"; +} + +const char * +T64File::getNameOfItem() +{ + assert(selectedItem != -1); + + long i,j; + long first = 0x50 + (selectedItem * 0x20); + long last = 0x60 + (selectedItem * 0x20); + + if (size < last) { + name[0] = 0; + } else { + for (j = 0, i = first; i < last; i++, j++) { + name[j] = (data[i] == 0x20 ? ' ' : data[i]); + if (j == 255) break; + } + name[j] = 0x00; + } + return name; +} + +size_t +T64File::getSizeOfItem() +{ + if (selectedItem < 0) + return 0; + + // Compute start and end of the selected item in memory + size_t offset = 0x42 + (selectedItem * 0x20); + uint16_t startAddrInMemory = LO_HI(data[offset], data[offset + 1]); + uint16_t endAddrInMemory = LO_HI(data[offset + 2], data[offset + 3]); + + return endAddrInMemory - startAddrInMemory; +} + +void +T64File::seekItem(long offset) +{ + size_t i = 0x48 + (selectedItem * 0x20); + + // Move file pointer the the start of the selected item + offset + iFp = LO_LO_HI_HI(data[i], data[i+1], data[i+2], data[i+3]) + offset; + + // Invalidate file pointer if it is out of range + if (iFp > size) + iFp = -1; +} + + + + + + +uint16_t +T64File::getDestinationAddrOfItem() +{ + long i = 0x42 + (selectedItem * 0x20); + uint16_t result = LO_HI(data[i], data[i+1]); + return result; +} + + + +bool +T64File::directoryItemIsPresent(int item) +{ + int first = 0x40 + (item * 0x20); + int last = 0x60 + (item * 0x20); + int i; + + // check for zeros... + if (last < size) + for (i = first; i < last; i++) + if (data[i] != 0) + return true; + + return false; +} + +bool +T64File::repair() +{ + unsigned i, n; + uint16_t noOfItems = numberOfItems(); + + // + // 1. Repair number of items, if this value is zero + // + + if (noOfItems == 0) { + + while (directoryItemIsPresent(noOfItems)) + noOfItems++; + + uint16_t noOfItemsStatedInHeader = numberOfItems(); + if (noOfItems != noOfItemsStatedInHeader) { + + debug(1, "Repairing corrupted T64 archive: Changing number of items from %d to %d.\n", + noOfItemsStatedInHeader, noOfItems); + + data[0x24] = LO_BYTE(noOfItems); + data[0x25] = HI_BYTE(noOfItems); + + } + assert(noOfItems == numberOfItems()); + } + + for (i = 0; i < noOfItems; i++) { + + // + // 2. Check relative offset information for each item + // + + // Compute start address in file + n = 0x48 + (i * 0x20); + uint16_t startAddrInContainer = LO_LO_HI_HI(data[n], data[n+1], data[n+2], data[n+3]); + + if (startAddrInContainer >= size) { + warn("T64 archive is corrupt (offset mismatch). Sorry, can't repair.\n"); + return false; + } + + // + // 3. Check for file end address mismatches (as created by CONVC64) + // + + // Compute start address in memory + n = 0x42 + (i * 0x20); + uint16_t startAddrInMemory = LO_HI(data[n], data[n+1]); + + // Compute end address in memory + n = 0x44 + (i * 0x20); + uint16_t endAddrInMemory = LO_HI(data[n], data[n+1]); + + if (endAddrInMemory == 0xC3C6) { + + // Let's assume that the rest of the file data belongs to this file ... + uint16_t fixedEndAddrInMemory = startAddrInMemory + (size - startAddrInContainer); + + debug(1, "Repairing corrupted T64 archive: Changing end address of item %d from %04X to %04X.\n", + i, endAddrInMemory, fixedEndAddrInMemory); + + data[n] = LO_BYTE(fixedEndAddrInMemory); + data[n+1] = HI_BYTE(fixedEndAddrInMemory); + } + } + + return 1; // Archive repaired successfully +} diff --git a/C64/FileFormats/T64File.h b/C64/FileFormats/T64File.h new file mode 100755 index 00000000..9f36c4e4 --- /dev/null +++ b/C64/FileFormats/T64File.h @@ -0,0 +1,121 @@ +/*! + * @header T64File.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _T64FILE_INC +#define _T64FILE_INC + +#include "AnyArchive.h" + +/*! @class T64File + * @brief The T64File class declares the programmatic interface for a file + * in T64 format. + */ +class T64File : public AnyArchive { + + //! @brief Header signature + static const uint8_t magicBytes[]; + + /*! @brief Number of the currently selected item + * @details -1, if no item is selected + */ + long selectedItem = -1; + +public: + + // + //! @functiongroup Class methods + // + + //! @brief Returns true iff buffer contains a T64 file + static bool isT64Buffer(const uint8_t *buffer, size_t length); + + //! Returns true of filename points to a valid file of that type + static bool isT64File(const char *filename); + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Standard constructor + T64File(); + + //! @brief Factory method + static T64File *makeWithBuffer(const uint8_t *buffer, size_t length); + + //! @brief Factory method + static T64File *makeWithFile(const char *path); + + /*! @brief Factory method + * @details otherArchive can be of any archive type + */ + static T64File *makeT64ArchiveWithAnyArchive(AnyArchive *otherArchive); + + + // + //! @functiongroup Methods from AnyC64File + // + + C64FileType type() { return T64_FILE; } + const char *typeAsString() { return "T64"; } + const char *getName(); + bool hasSameType(const char *filename) { return isT64File(filename); } + bool readFromBuffer(const uint8_t *buffer, size_t length); + + + // + //! @functiongroup Methods from AnyArchive + // + + int numberOfItems(); + void selectItem(unsigned n); + const char *getTypeOfItemAsString(); + const char *getNameOfItem(); + size_t getSizeOfItem(); + void seekItem(long offset); + uint16_t getDestinationAddrOfItem(); + + + // + // @functiongroup Scanning and repairing a T64 file + // + + //! @brief Checks if the header contains information at the specified location + bool directoryItemIsPresent(int n); + + /*! @brief Checks the file for inconsistencies and tries to repair it + * @details This method can eliminate the following inconsistencies: + * number of files: + * some archives state falsely in their header that zero + * files are present. This value will be fixed. + * end loading address: + * Archives that are created with CONVC64 often contain a + * value of 0xC3C6, which is wrong (e.g., paradrd.t64). This + * value will be changed such that getByte() will read until + * the end of the physical file. + * @result true, if archive was consistent or could be repaired. + * false, if an inconsistency has been detected that could not + * be repaired. + */ + bool repair(); +}; + +#endif + diff --git a/C64/FileFormats/TAPFile.cpp b/C64/FileFormats/TAPFile.cpp new file mode 100755 index 00000000..96e0548f --- /dev/null +++ b/C64/FileFormats/TAPFile.cpp @@ -0,0 +1,116 @@ +/*! + * @file TAPFile.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "TAPFile.h" + +const uint8_t TAPFile::magicBytes[] = { + 0x43, 0x36, 0x34, 0x2D, 0x54, 0x41, 0x50, 0x45, 0x2D, 0x52, 0x41, 0x57 }; + +TAPFile::TAPFile() +{ + setDescription("TAPFile"); + data = NULL; + dealloc(); +} + +TAPFile * +TAPFile::makeWithBuffer(const uint8_t *buffer, size_t length) +{ + TAPFile *tape = new TAPFile(); + + if (!tape->readFromBuffer(buffer, length)) { + delete tape; + return NULL; + } + + return tape; +} + +TAPFile * +TAPFile::makeWithFile(const char *filename) +{ + TAPFile *tape = new TAPFile(); + + if (!tape->readFromFile(filename)) { + delete tape; + return NULL; + } + + return tape; +} + +bool +TAPFile::isTAPBuffer(const uint8_t *buffer, size_t length) +{ + if (length < 0x15) return false; + return matchingBufferHeader(buffer, magicBytes, sizeof(magicBytes)); + // return checkBufferHeader(buffer, length, magicBytes); +} + +bool +TAPFile::isTAPFile(const char *filename) +{ + assert (filename != NULL); + + if (!checkFileSuffix(filename, ".TAP") && !checkFileSuffix(filename, ".tap") && + !checkFileSuffix(filename, ".T64") && !checkFileSuffix(filename, ".t64")) + return false; + + if (!checkFileSize(filename, 0x15, -1)) + return false; + + if (!matchingFileHeader(filename, magicBytes, sizeof(magicBytes))) + return false; + + return true; +} + +void +TAPFile::dealloc() +{ + fp = -1; +} + +const char * +TAPFile::getName() +{ + unsigned i; + + for (i = 0; i < 17; i++) { + name[i] = data[0x08+i]; + } + name[i] = 0x00; + return name; +} + +bool +TAPFile::readFromBuffer(const uint8_t *buffer, size_t length) +{ + if (!AnyC64File::readFromBuffer(buffer, length)) + return false; + + int l = LO_LO_HI_HI(data[0x10], data[0x11], data[0x12], data[0x13]); + if (l + 0x14 /* Header */ != size) { + warn("Size mismatch! Archive should have %d data bytes, found %d\n", l, size - 0x14); + } + + return true; +} + diff --git a/C64/FileFormats/TAPFile.h b/C64/FileFormats/TAPFile.h new file mode 100755 index 00000000..ba2cfc51 --- /dev/null +++ b/C64/FileFormats/TAPFile.h @@ -0,0 +1,89 @@ +/*! + * @header TAPFile.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _TAPFILE_INC +#define _TAPFILE_INC + +#include "AnyC64File.h" + +/*! @class TAPFile + * @brief Represents a file of the TAP format type (band tapes). + */ +class TAPFile : public AnyC64File { + +private: + + //! @brief Header signature + static const uint8_t magicBytes[]; + + /*! @brief File pointer + * @details An offset into the data array. + */ + int fp; + +public: + + // + //! @functiongroup Class methods + // + + //! @brief Returns true iff buffer contains a TAP file + static bool isTAPBuffer(const uint8_t *buffer, size_t length); + + //! @brief Returns true iff the specified file is a TAP file + static bool isTAPFile(const char *filename); + + + // + //! @functiongroup Creating and destructing + // + + //! @brief Constructor + TAPFile(); + + //! @brief Factory method + static TAPFile *makeWithBuffer(const uint8_t *buffer, size_t length); + + //! @brief Factory method + static TAPFile *makeWithFile(const char *filename); + + + // + //! @functiongroup Methods from AnyC64File + // + + void dealloc(); + C64FileType type() { return TAP_FILE; } + const char *typeAsString() { return "TAP"; } + const char *getName(); + bool hasSameType(const char *filename) { return isTAPFile(filename); } + bool readFromBuffer(const uint8_t *buffer, size_t length); + + + // + //! @functiongroup Retrieving tape information + // + + uint8_t TAPversion() { return data[0x000C]; } + uint8_t *getData() { return &data[0x0014]; } + size_t getSize() { return size - 0x14; } +}; + +#endif diff --git a/C64/General/MessageQueue.cpp b/C64/General/MessageQueue.cpp new file mode 100755 index 00000000..73ee2fbc --- /dev/null +++ b/C64/General/MessageQueue.cpp @@ -0,0 +1,111 @@ +/*! + * @file MessageQueue.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "MessageQueue.h" + +MessageQueue::MessageQueue() +{ + setDescription("MessageQueue"); + pthread_mutex_init(&lock, NULL); +} + +MessageQueue::~MessageQueue() +{ + pthread_mutex_destroy(&lock); +} + +void +MessageQueue::addListener(const void *listener, Callback *func) +{ + pthread_mutex_lock(&lock); + listeners.insert(pair (listener, func)); + pthread_mutex_unlock(&lock); + + // Distribute all pending messages + Message msg; + while ((msg = getMessage()).type != MSG_NONE) { + propagateMessage(&msg); + } +} + +void +MessageQueue::removeListener(const void *listener) +{ + pthread_mutex_lock(&lock); + listeners.erase(listener); + pthread_mutex_unlock(&lock); +} + +Message +MessageQueue::getMessage() +{ + Message result; + + pthread_mutex_lock(&lock); + + // Read message + if (r == w) { + result.type = MSG_NONE; // Queue is empty + result.data = 0; + } else { + result = queue[r]; + r = (r + 1) % capacity; + } + + pthread_mutex_unlock(&lock); + + return result; +} + +void +MessageQueue::putMessage(MessageType type, uint64_t data) +{ + pthread_mutex_lock(&lock); + + // Write data + Message msg; + msg.type = type; + msg.data = data; + queue[w] = msg; + + // Move write pointer to next location + w = (w + 1) % capacity; + + if (w == r) { + // debug(2, "Queue overflow. Oldest message is lost.\n"); + r = (r + 1) % capacity; + } + + // Serve registered callbacks + propagateMessage(&msg); + + pthread_mutex_unlock(&lock); +} + +void +MessageQueue::propagateMessage(Message *msg) +{ + map :: iterator i; + + for (i = listeners.begin(); i != listeners.end(); i++) { + i->second(i->first, msg->type, msg->data); + } +} diff --git a/C64/General/MessageQueue.h b/C64/General/MessageQueue.h new file mode 100755 index 00000000..f237f217 --- /dev/null +++ b/C64/General/MessageQueue.h @@ -0,0 +1,85 @@ +/*! + * @header MessageQueue.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MESSAGE_QUEUE_INC +#define _MESSAGE_QUEUE_INC + +#include "VC64Object.h" +#include "C64_types.h" +#include + +using namespace std; + +class MessageQueue : public VC64Object { + + private: + + //! @brief Maximum number of queued messages + const static size_t capacity = 64; + + //! @brief Message ring buffer + Message queue[capacity]; + + //! @brief The ring buffers read pointer + int r = 0; + + //! @brief The ring buffers write pointer + int w = 0; + + //! @brief Mutex for streamlining parallel read and write accesses + pthread_mutex_t lock; + + //! @brief A list of all registered listeners + map listeners; + + public: + + //! @brief Constructor + MessageQueue(); + + //! @brief Destructor + ~MessageQueue(); + + //! @brief Registers a listener together with it's callback function + void addListener(const void *listener, Callback *func); + + //! @brief Removes a listener + void removeListener(const void *listener); + + /*! @brief Returns the next pending message + * @return NULL, if the queue is empty + */ + Message getMessage(); + + /*! @brief Writes new message into the message queue + * @details Furthermore, the message is propaged to all registered + * listeners. + * @see propagateMessage + */ + void putMessage(MessageType type, uint64_t data = 0); + + private: + + //! @brief Propagates a single message to all registered listeners. + void propagateMessage(Message *msg); +}; + +#endif diff --git a/C64/General/TimeDelayed.cpp b/C64/General/TimeDelayed.cpp new file mode 100755 index 00000000..8ef36d88 --- /dev/null +++ b/C64/General/TimeDelayed.cpp @@ -0,0 +1,157 @@ +/*! + * @file TimeDelayed.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "basic.h" +#include "TimeDelayed.h" + +template +TimeDelayed::TimeDelayed(uint8_t delay, uint8_t capacity, uint64_t *clock) +{ + assert(delay < capacity); + + pipeline = new T[capacity]; + timeStamp = 0; + this->capacity = capacity; + this->delay = delay; + this->clock = (int64_t *)clock; + clear(); +} + +template TimeDelayed::TimeDelayed(uint8_t, uint8_t, uint64_t *); +template TimeDelayed::TimeDelayed(uint8_t, uint8_t, uint64_t *); +template TimeDelayed::TimeDelayed(uint8_t, uint8_t, uint64_t *); +template TimeDelayed::TimeDelayed(uint8_t, uint8_t, uint64_t *); +template TimeDelayed::TimeDelayed(uint8_t, uint8_t, uint64_t *); + +template +TimeDelayed::~TimeDelayed() +{ + assert(pipeline != NULL); + delete pipeline; +} +template TimeDelayed::~TimeDelayed(); +template TimeDelayed::~TimeDelayed(); +template TimeDelayed::~TimeDelayed(); +template TimeDelayed::~TimeDelayed(); +template TimeDelayed::~TimeDelayed(); + + +template +void TimeDelayed::writeWithDelay(T value, uint8_t waitCycles) +{ + int64_t referenceTime = *clock + waitCycles; + + // Shift pipeline + int64_t diff = referenceTime - timeStamp; + for (int i = this->capacity; i >= 0; i--) { + /* + if (!((i - diff <= 0) || (i - diff <= this->capacity))) { + printf("i = %d diff = %lld capacity = %d reference = %lld timeStamp = %lld\n", + i, diff, capacity, referenceTime, timeStamp); + } + */ + assert((i - diff <= 0) || (i - diff <= this->capacity)); + pipeline[i] = (i - diff > 0) ? pipeline[i - diff] : pipeline[0]; + } + + // Assign new value + timeStamp = referenceTime; + pipeline[0] = value; +} +template void TimeDelayed::writeWithDelay(bool, uint8_t); +template void TimeDelayed::writeWithDelay(uint8_t, uint8_t); +template void TimeDelayed::writeWithDelay(uint16_t, uint8_t); +template void TimeDelayed::writeWithDelay(uint32_t, uint8_t); +template void TimeDelayed::writeWithDelay(uint64_t, uint8_t); + +template +void TimeDelayed::debug() +{ + for (int i = delay; i >= 0; i--) { + printf("%llX ", (uint64_t)pipeline[i]); + } + printf("\n"); + + printf("delayed() = %llX\n", (uint64_t)delayed()); + for (int i = 0; i <= delay; i++) { + printf("readWithDelay(%d) = %llX\n", i, (uint64_t)readWithDelay(i)); + } + printf("timeStamp = %lld clock = %lld delay = %d\n", timeStamp, *clock, delay); +} +template void TimeDelayed::debug(); +template void TimeDelayed::debug(); +template void TimeDelayed::debug(); +template void TimeDelayed::debug(); +template void TimeDelayed::debug(); + + +template +size_t TimeDelayed::stateSize() +{ + return capacity * sizeof(uint64_t) + sizeof(timeStamp); +} +template size_t TimeDelayed::stateSize(); +template size_t TimeDelayed::stateSize(); +template size_t TimeDelayed::stateSize(); +template size_t TimeDelayed::stateSize(); +template size_t TimeDelayed::stateSize(); + + +template +void TimeDelayed::loadFromBuffer(uint8_t **buffer) +{ + uint8_t *old = *buffer; + + for (unsigned i = 0; i < capacity; i++) { + pipeline[i] = (T)read64(buffer); + } + timeStamp = read64(buffer); + + assert(*buffer - old == stateSize()); +} +template void TimeDelayed::loadFromBuffer(uint8_t **); +template void TimeDelayed::loadFromBuffer(uint8_t **); +template void TimeDelayed::loadFromBuffer(uint8_t **); +template void TimeDelayed::loadFromBuffer(uint8_t **); +template void TimeDelayed::loadFromBuffer(uint8_t **); + + +template +void TimeDelayed::saveToBuffer(uint8_t **buffer) +{ + uint8_t *old = *buffer; + + for (unsigned i = 0; i < capacity; i++) { + write64(buffer, (uint64_t)pipeline[i]); + } + write64(buffer, timeStamp); + /* + printf("SAVING: capacity = %d reference = %lld timeStamp = %lld\n", + capacity, *clock, timeStamp); + */ + + assert(*buffer - old == stateSize()); +} +template void TimeDelayed::saveToBuffer(uint8_t **); +template void TimeDelayed::saveToBuffer(uint8_t **); +template void TimeDelayed::saveToBuffer(uint8_t **); +template void TimeDelayed::saveToBuffer(uint8_t **); +template void TimeDelayed::saveToBuffer(uint8_t **); diff --git a/C64/General/TimeDelayed.h b/C64/General/TimeDelayed.h new file mode 100755 index 00000000..0f98eef1 --- /dev/null +++ b/C64/General/TimeDelayed.h @@ -0,0 +1,106 @@ +/*! + * @header TimeDelayed.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _TIME_DELAYED_INC +#define _TIME_DELAYED_INC + +template class TimeDelayed { + + private: + + /*! @brief Value pipeline (history buffer) + * @details Semantics: + * pipeline[0]: Value that was written at time timeStamp + * pipeline[n]: Value that was written at time timeStamp - n + */ + T *pipeline = NULL; + + //! @brief Number of elements hold in pipeline + uint8_t capacity = 0; + + //! @brief Remembers the time of the most recent call to write() + int64_t timeStamp = 0; + + //! @brief Number of cycles to elapse until a written value shows up + uint8_t delay = 0; + + //! @brief Pointer to reference clock + int64_t *clock = NULL; + + public: + + //! @brief Constructors + TimeDelayed(uint8_t delay, uint8_t capacity, uint64_t *clock); + TimeDelayed(uint8_t delay, uint8_t capacity) : TimeDelayed(delay, capacity, NULL) { }; + TimeDelayed(uint8_t delay, uint64_t *clock) : TimeDelayed(delay, delay + 1, clock) { }; + TimeDelayed(uint8_t delay) : TimeDelayed(delay, delay + 1, NULL) { }; + + //! @brief Destructor + ~TimeDelayed(); + + /*! @brief Sets the reference clock + * @param clock is either the clock of the C64 CPU or the clock of the + * a drive CPU. + */ + void setClock(uint64_t *clock) { this->clock = (int64_t *)clock; } + + //! @brief Overwrites all pipeline entries with a reset value. + void reset(T value) { + for (unsigned i = 0; i < capacity; i++) pipeline[i] = value; + timeStamp = 0; + } + + //! @brief Zeroes out all pipeline entries. + void clear() { reset((T)0); } + + //! @brief Write a value into the pipeline. + void write(T value) { writeWithDelay(value, 0); } + + //! @brief Work horse for writing a value. + void writeWithDelay(T value, uint8_t waitCycles); + + //! @brief Reads the most recent pipeline element. + T current() { return pipeline[0]; } + + //! @brief Reads a value from the pipeline with the standard delay. + // T delayed() { return pipeline[MAX(0, timeStamp - *clock + delay)]; } + T delayed() { + int64_t offset = timeStamp - *clock + delay; + if (__builtin_expect(offset <= 0, 1)) { + return pipeline[0]; + } else { + return pipeline[offset]; + } + } + + //! @brief Reads a value from the pipeline with a custom delay. + T readWithDelay(uint8_t delay) { + assert(delay <= this->capacity); + return pipeline[MAX(0, timeStamp - *clock + delay)]; + } + + size_t stateSize(); + void loadFromBuffer(uint8_t **buffer); + void saveToBuffer(uint8_t **buffer); + void debug(); +}; + +#endif diff --git a/C64/General/VC64Object.cpp b/C64/General/VC64Object.cpp new file mode 100755 index 00000000..88da4bbe --- /dev/null +++ b/C64/General/VC64Object.cpp @@ -0,0 +1,103 @@ +/*! + * @file VC64Object.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "VC64Object.h" + +bool +VC64Object::tracingEnabled() +{ + if (traceCounter == 0) + return false; + + if (traceCounter > 0) + traceCounter--; + + return true; +} + +#define VC64OBJ_PARSE \ + char buf[256]; \ + va_list ap; \ + va_start(ap, fmt); \ + vsnprintf(buf, sizeof(buf), fmt, ap); \ + va_end(ap); + +void +VC64Object::msg(const char *fmt, ...) +{ + VC64OBJ_PARSE; + fprintf(stderr, "%s", buf); +} + +void +VC64Object::msg(int level, const char *fmt, ...) +{ + if (level > debugLevel) + return; + + VC64OBJ_PARSE; + fprintf(stderr, "%s", buf); +} + +void +VC64Object::debug(const char *fmt, ...) +{ + VC64OBJ_PARSE; + if (description) + fprintf(stderr, "%s: %s", description, buf); + else + fprintf(stderr, "%s", buf); +} + +void +VC64Object::debug(int level, const char *fmt, ...) +{ + if (level > debugLevel) + return; + + VC64OBJ_PARSE; + if (description) + fprintf(stderr, "%s: %s", description, buf); + else + fprintf(stderr, "%s", buf); +} + +void +VC64Object::warn(const char *fmt, ...) +{ + VC64OBJ_PARSE; + if (description) + fprintf(stderr, "%s: WARNING: %s", description, buf); + else + fprintf(stderr, "WARNING: %s", buf); +} + +void +VC64Object::panic(const char *fmt, ...) +{ + VC64OBJ_PARSE; + if (description) + fprintf(stderr, "%s: PANIC: %s", description, buf); + else + fprintf(stderr, "PANIC: %s", buf); + + assert(0); +} diff --git a/C64/General/VC64Object.h b/C64/General/VC64Object.h new file mode 100755 index 00000000..52ee36c8 --- /dev/null +++ b/C64/General/VC64Object.h @@ -0,0 +1,117 @@ +/*! + * @header VC64Object.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _VC64OBJECT_INC +#define _VC64OBJECT_INC + +#include "basic.h" +#include "C64_types.h" + +/*! @brief Common functionality of all VirtualC64 objects. + * @details This class stores a textual description of the object and offers + * various functions for printing debug messages and warnings. + */ +class VC64Object { + +private: + + //! @brief Debug level for this component + unsigned debugLevel = DEBUG_LEVEL; + + /*! @brief Stores how many trace messages are left to be printed + * @details If positive, this value is decremented in tracingEnabled(). + * A negative value indicates that tracing should continue + * forever. + */ + int traceCounter = 0; + + /*! @brief Textual description of this object + * @details Most debug output methods preceed their output with this string. + * @note The default value is NULL. In that case, no prefix is printed. + */ + const char *description = NULL; + +public: + + // + //! @functiongroup Initializing the component + // + + //! @brief Changes the debug level for a specific object. + void setDebugLevel(unsigned level) { debugLevel = level; } + + //! @brief Returns the textual description. + const char *getDescription() { return description ? description : ""; } + + //! @brief Assigns a textual description. + void setDescription(const char *str) { description = strdup(str); } + + + // + //! @functiongroup Debugging the component + // + + //! @brief Returns true iff trace mode is enabled. + bool tracingEnabled(); + + //! @brief Starts tracing. + void startTracing(int counter = -1) { traceCounter = counter; } + + //! @brief Stops tracing. + void stopTracing() { traceCounter = 0; } + + + // + //! @functiongroup Printing messages to the console + // + + //! @brief Prints a message to console. + void msg(const char *fmt, ...); + + //! @brief Prints a message to the console if debug level is high enough. + void msg(int level, const char *fmt, ...); + + /*! @brief Prints a debug message to the console + * @details Debug messages are prefixed by a custom string naming the + * component. + */ + void debug(const char *fmt, ...); + + /*! @brief Prints a debug message if debug level is high enough. + * @details Debug messages are prefixed by a custom string naming the + * component. + */ + void debug(int level, const char *fmt, ...); + + /*! @brief Prints a warning message to the console. + * @details Warning messages are prefixed by a custom string naming the + * component. + */ + void warn(const char *fmt, ...); + + /*! @brief Prints a panic message to console or a log file. + * @details Panic messages are prefixed by a custom string naming the + * component. + */ + void panic(const char *fmt, ...); +}; + +#endif diff --git a/C64/General/VirtualComponent.cpp b/C64/General/VirtualComponent.cpp new file mode 100755 index 00000000..d1ec41d6 --- /dev/null +++ b/C64/General/VirtualComponent.cpp @@ -0,0 +1,251 @@ +/*! + * @file VirtualComponent.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" +#include + +VirtualComponent::~VirtualComponent() +{ + debug(3, "Terminated\n"); + + if (subComponents) + delete [] subComponents; + + if (snapshotItems) + delete [] snapshotItems; +} + +void +VirtualComponent::setC64(C64 *c64) +{ + assert(this->c64 == NULL); + assert(c64 != NULL); + + this->c64 = c64; + if (subComponents != NULL) + for (unsigned i = 0; subComponents[i] != NULL; i++) + subComponents[i]->setC64(c64); +} + +void +VirtualComponent::reset() +{ + // Reset all sub components + if (subComponents != NULL) + for (unsigned i = 0; subComponents[i] != NULL; i++) { + subComponents[i]->reset(); + } + + // Clear snapshot items marked with 'CLEAR_ON_RESET' + if (snapshotItems != NULL) + for (unsigned i = 0; snapshotItems[i].data != NULL; i++) + if (snapshotItems[i].flags & CLEAR_ON_RESET) + memset(snapshotItems[i].data, 0, snapshotItems[i].size); + + debug(3, "Resetting...\n"); +} + +void +VirtualComponent::ping() +{ + // Ping all sub components + if (subComponents != NULL) + for (unsigned i = 0; subComponents[i] != NULL; i++) + subComponents[i]->ping(); +} + +void +VirtualComponent::setClockFrequency(uint32_t frequency) +{ + assert(frequency == PAL_CLOCK_FREQUENCY || frequency == NTSC_CLOCK_FREQUENCY); + + // Call method for all sub components + if (subComponents != NULL) + for (unsigned i = 0; subComponents[i] != NULL; i++) + subComponents[i]->setClockFrequency(frequency); +} + +void +VirtualComponent::suspend() +{ + c64->suspend(); +} + +void +VirtualComponent::resume() +{ + c64->resume(); +} + +void +VirtualComponent::registerSubComponents(VirtualComponent **components, unsigned length) { + + assert(components != NULL); + assert(length % sizeof(VirtualComponent *) == 0); + + unsigned numItems = length / sizeof(VirtualComponent *); + + // Allocate new array on heap and copy array data + subComponents = new VirtualComponent*[numItems]; + std::copy(components, components + numItems, &subComponents[0]); +} + +void +VirtualComponent::registerSnapshotItems(SnapshotItem *items, unsigned length) { + + assert(items != NULL); + assert(length % sizeof(SnapshotItem) == 0); + + unsigned i, numItems = length / sizeof(SnapshotItem); + + // Allocate new array on heap and copy array data + snapshotItems = new SnapshotItem[numItems]; + std::copy(items, items + numItems, &snapshotItems[0]); + + // Determine size of snapshot on disk + for (i = snapshotSize = 0; snapshotItems[i].data != NULL; i++) + snapshotSize += snapshotItems[i].size; +} + +size_t +VirtualComponent::stateSize() +{ + uint32_t result = snapshotSize; + + if (subComponents != NULL) + for (unsigned i = 0; subComponents[i] != NULL; i++) + result += subComponents[i]->stateSize(); + + return result; +} + +void +VirtualComponent::loadFromBuffer(uint8_t **buffer) +{ + uint8_t *old = *buffer; + + debug(3, " Loading internal state ...\n"); + + // Call delegation method + willLoadFromBuffer(buffer); + + // Load internal state of all sub components + if (subComponents != NULL) + for (unsigned i = 0; subComponents[i] != NULL; i++) + subComponents[i]->loadFromBuffer(buffer); + + // Load own internal state + void *data; size_t size; int flags; + for (unsigned i = 0; snapshotItems != NULL && snapshotItems[i].data != NULL; i++) { + + data = snapshotItems[i].data; + flags = snapshotItems[i].flags & 0x0F; + size = snapshotItems[i].size; + + if (flags == 0) { // Auto detect size + + switch (snapshotItems[i].size) { + case 1: *(uint8_t *)data = read8(buffer); break; + case 2: *(uint16_t *)data = read16(buffer); break; + case 4: *(uint32_t *)data = read32(buffer); break; + case 8: *(uint64_t *)data = read64(buffer); break; + default: readBlock(buffer, (uint8_t *)data, size); + } + + } else { // Format is specified manually + + switch (flags) { + case BYTE_ARRAY: readBlock(buffer, (uint8_t *)data, size); break; + case WORD_ARRAY: readBlock16(buffer, (uint16_t *)data, size); break; + case DWORD_ARRAY: readBlock32(buffer, (uint32_t *)data, size); break; + case QWORD_ARRAY: readBlock64(buffer, (uint64_t *)data, size); break; + default: assert(0); + } + } + } + + // Call delegation method + didLoadFromBuffer(buffer); + + // Verify that the number of read bytes matches the state size + if (*buffer - old != stateSize()) { + panic("loadFromBuffer: Snapshot size is wrong. Got %d, expected %d.", + *buffer - old, stateSize()); + assert(false); + } +} + +void +VirtualComponent::saveToBuffer(uint8_t **buffer) +{ + uint8_t *old = *buffer; + + debug(3, " Saving internal state ...\n"); + + // Call delegation method + willSaveToBuffer(buffer); + + // Save internal state of all sub components + if (subComponents != NULL) { + for (unsigned i = 0; subComponents[i] != NULL; i++) + subComponents[i]->saveToBuffer(buffer); + } + + // Save own internal state + void *data; size_t size; int flags; + for (unsigned i = 0; snapshotItems != NULL && snapshotItems[i].data != NULL; i++) { + + data = snapshotItems[i].data; + flags = snapshotItems[i].flags & 0x0F; + size = snapshotItems[i].size; + + if (flags == 0) { // Auto detect size + + switch (snapshotItems[i].size) { + case 1: write8(buffer, *(uint8_t *)data); break; + case 2: write16(buffer, *(uint16_t *)data); break; + case 4: write32(buffer, *(uint32_t *)data); break; + case 8: write64(buffer, *(uint64_t *)data); break; + default: writeBlock(buffer, (uint8_t *)data, size); + } + + } else { // Format is specified manually + + switch (flags) { + case BYTE_ARRAY: writeBlock(buffer, (uint8_t *)data, size); break; + case WORD_ARRAY: writeBlock16(buffer, (uint16_t *)data, size); break; + case DWORD_ARRAY: writeBlock32(buffer, (uint32_t *)data, size); break; + case QWORD_ARRAY: writeBlock64(buffer, (uint64_t *)data, size); break; + default: assert(0); + } + } + } + + // Call delegation method + didSaveToBuffer(buffer); + + // Verify that the number of written bytes matches the state size + if (*buffer - old != stateSize()) { + panic("saveToBuffer: Snapshot size is wrong. Got %d, expected %d.", + *buffer - old, stateSize()); + assert(false); + } +} diff --git a/C64/General/VirtualComponent.h b/C64/General/VirtualComponent.h new file mode 100755 index 00000000..492ab9a5 --- /dev/null +++ b/C64/General/VirtualComponent.h @@ -0,0 +1,228 @@ +/*! + * @header VirtualComponent.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _VIRTUAL_COMPONENT_INC +#define _VIRTUAL_COMPONENT_INC + +#include "VC64Object.h" + +// Forward declarations +class C64; + + +/*! @brief Base class for all virtual hardware components + * @details This class defines the base functionality of all virtual + * components. The class comprises functions for resetting, + * suspending and resuming the component, as well as functions for + * loading and saving snapshots. + */ +class VirtualComponent : public VC64Object { + +protected: + + /*! @brief Type and behavior of a snapshot item + * @details The reset flags indicate whether the snapshot item should be + * set to 0 automatically during a reset. The format flags are + * important when big chunks of data are specified. They are + * needed loadBuffer and saveBuffer to correctly converting little + * endian to big endian format. + */ + enum { + KEEP_ON_RESET = 0x00, //! Don't touch item during a reset + CLEAR_ON_RESET = 0x10, //! Reset to zero during a reset + + BYTE_ARRAY = 0x01, //! Data chunk is an array of bytes + WORD_ARRAY = 0x02, //! Data chunk is an array of words + DWORD_ARRAY = 0x04, //! Data chunk is an array of double words + QWORD_ARRAY = 0x08 //! Data chunk is an array of quad words + }; + + /*! @brief Fingerprint of a snapshot item + */ + typedef struct { + + void *data; + size_t size; + uint8_t flags; + + } SnapshotItem; + +public: + + /*! @brief Reference to the virtual C64 top-level object. + * @details This reference is setup for all hardware components in the + * constructor of the C64 class. + */ + C64 *c64 = NULL; + +protected: + + //! @brief Sub components of this component + VirtualComponent **subComponents = NULL; + + //! @brief List of snapshot items of this component + SnapshotItem *snapshotItems = NULL; + + //! @brief Snapshot size on disk (in bytes) + unsigned snapshotSize = 0; + +public: + + //! @brief Destructor + virtual ~VirtualComponent(); + + + // + //! @functiongroup Initializing the component + // + + /*! @brief Assign top-level C64 object. + * @details The provided reference is propagated automatically to all + * sub components. + */ + virtual void setC64(C64 *c64); + + /*! @brief Reset component to its initial state. + * @details By default, each component also resets its sub components. + */ + virtual void reset(); + + //! @brief Asks the component to inform the GUI about its current state. + /*! @details The GUI invokes this function to update its visual elements, + * e.g., after loading a snapshot file. Only some components + * overwrite this function. + */ + virtual void ping(); + + //! @brief Informs the component about a clock frequency change. + /*! @details This delegation method is called on startup and whenever the + * CPU clock frequency changes (i.e., when switching between + * PAL and NTSC). Some components overwrite this function to + * update clock dependent lookup tables. + * @param frequency Frequency of the C64 CPU in Hz. + * Must be either PAL_CLOCK_FREQUENCY_PAL or NTSC_CLOCK_FREQUENCY. + */ + virtual void setClockFrequency(uint32_t frequency); + + + // + //! @functiongroup Controlling the emulation thread + // + + /*! @brief Freezes the emulation thread. + * @details If the internal state of the emulator is changed from outside + * the emulation thread, the change must be embedded in a + * suspend / resume block as follows: + * + * suspend(); + * do something with the internal state; + * resume(); + * + * @note Multiple suspend / resume blocks can be nested. + * @see resume + */ + virtual void suspend(); + + /*! @brief Continues the emulation thread. + * @details This functions concludes a suspend operation. + * @see suspend + */ + virtual void resume(); + + + // + //! @functiongroup Debugging the component + // + + //! @brief Print info about the internal state. + /*! @details This functions is intended for debugging purposes only. Any + * derived component should override this method and print out + * useful debugging information. + */ + virtual void dump() { }; + + + // + //! @functiongroup Registering snapshot items and sub components + // + + /*! @brief Registers all sub components for this component + * @abstract Sub components are usually registered in the constructor of + * a virtual component. + * @param items Pointer to the first element of a VirtualComponet* array. + * The end of the array is marked by a NULL pointer. + * @param legth Size of the subComponent array in bytes. + */ + void registerSubComponents(VirtualComponent **subComponents, unsigned length); + + /*! @brief Registers all snapshot items for this component + * @abstract Snaphshot items are usually registered in the constructor of + * a virtual component. + * @param items Pointer to the first element of a SnapshotItem* array. + * The end of the array is marked by a NULL pointer. + * @param legth Size of the SnapshotItem array in bytes. + */ + void registerSnapshotItems(SnapshotItem *items, unsigned length); + + +public: + + // + //! @functiongroup Loading and saving snapshots + // + + //! @brief Returns the size of the internal state in bytes + virtual size_t stateSize(); + + /*! @brief Load internal state from memory buffer + * @note Snapshot items of size 2, 4, or 8 are converted to big endian + * format automatically. Otherwise, a byte array is assumed. + * @param buffer Pointer to next byte to read + * @seealso WORD_ARRAY, DWORD_ARRAY, QWORD_ARRAY + */ + void loadFromBuffer(uint8_t **buffer); + + /*! @brief Delegation methods called inside loadFromBuffer() + * @details Some components overwrite this method to add custom behavior + * such as loading items that cannot be handled by the default + * implementation. + */ + virtual void willLoadFromBuffer(uint8_t **buffer) { }; + virtual void didLoadFromBuffer(uint8_t **buffer) { }; + + /*! @brief Save internal state to memory buffer + * @note Snapshot items of size 2, 4, or 8 are converted to big endian + * format automatically. Otherwise, a byte array is assumed. + * @param buffer Pointer to next byte to read + * @seealso WORD_ARRAY, DWORD_ARRAY, QWORD_ARRAY + */ + void saveToBuffer(uint8_t **buffer); + + /*! @brief Delegation methods called inside saveToBuffer() + * @details Some components overwrite this method to add custom behavior + * such as saving items that cannot be handled by the default + * implementation. + */ + virtual void willSaveToBuffer(uint8_t **buffer) { }; + virtual void didSaveToBuffer(uint8_t **buffer) { }; +}; + +#endif diff --git a/C64/General/basic.cpp b/C64/General/basic.cpp new file mode 100755 index 00000000..cbdea0b8 --- /dev/null +++ b/C64/General/basic.cpp @@ -0,0 +1,379 @@ +/*! + * @file basic.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "basic.h" + +struct timeval t; +// long tv_base = ((void)gettimeofday(&t,NULL), t.tv_sec); + +void translateToUnicode(const char *petscii, uint16_t *unichars, uint16_t base, size_t max) +{ + assert(petscii != NULL); + assert(unichars != NULL); + + unsigned i; + size_t len = MIN(strlen(petscii), max); + + for (i = 0; i < len; i++) { + unichars[i] = base + (uint16_t)petscii[i]; + } + unichars[i] = 0; +} + +size_t +strlen16(const uint16_t *unichars) +{ + size_t count = 0; + + if (unichars) + while(unichars[count]) count++; + + return count; +} + +uint8_t +petscii2printable(uint8_t c, uint8_t subst) +{ + if (c >= 0x20 /*' '*/ && c <= 0x7E /* ~ */) return c; + return subst; +} + +uint8_t +ascii2pet(uint8_t asciichar) +{ + if (asciichar == 0x00) + return 0x00; + + asciichar = toupper(asciichar); + + if (asciichar >= 0x20 && asciichar <= 0x5D) { + return asciichar; + } else { + return ' '; + } +} + +void +ascii2petStr(char *str) +{ + assert(str != NULL); + for (; *str != 0; str++) { + *str = ascii2pet(*str); + } +} + +void +sprint8d(char *s, uint8_t value) +{ + for (int i = 2; i >= 0; i--) { + uint8_t digit = value % 10; + s[i] = '0' + digit; + value /= 10; + } + s[3] = 0; +} + +void +sprint8x(char *s, uint8_t value) +{ + for (int i = 1; i >= 0; i--) { + uint8_t digit = value % 16; + s[i] = (digit <= 9) ? ('0' + digit) : ('A' + digit - 10); + value /= 16; + } + s[2] = 0; +} + +void +sprint8b(char *s, uint8_t value) +{ + for (int i = 7; i >= 0; i--) { + s[i] = (value & 0x01) ? '1' : '0'; + value >>= 1; + } + s[8] = 0; +} + +void +sprint16d(char *s, uint16_t value) +{ + for (int i = 4; i >= 0; i--) { + uint8_t digit = value % 10; + s[i] = '0' + digit; + value /= 10; + } + s[5] = 0; +} + +void +sprint16x(char *s, uint16_t value) +{ + for (int i = 3; i >= 0; i--) { + uint8_t digit = value % 16; + s[i] = (digit <= 9) ? ('0' + digit) : ('A' + digit - 10); + value /= 16; + } + s[4] = 0; +} + +void +sprint16b(char *s, uint16_t value) +{ + for (int i = 15; i >= 0; i--) { + s[i] = (value & 0x01) ? '1' : '0'; + value >>= 1; + } + s[16] = 0; +} + +char * +extractFilename(const char *path) +{ + assert(path != NULL); + + const char *pos = strrchr(path, '/'); + return pos ? strdup(pos + 1) : strdup(path); +} + +char * +extractSuffix(const char *path) +{ + assert(path != NULL); + + const char *pos = strrchr(path, '.'); + return pos ? strdup(pos + 1) : strdup(""); +} + +char * +extractFilenameWithoutSuffix(const char *path) +{ + assert(path != NULL); + + char *result; + char *filename = extractFilename(path); + char *suffix = extractSuffix(filename); + + if (strlen(suffix) == 0) + result = strdup(filename); + else + result = strndup(filename, strlen(filename) - strlen(suffix) - 1); + + free(filename); + free(suffix); + return result; +} + +bool +checkFileSuffix(const char *filename, const char *suffix) +{ + assert(filename != NULL); + assert(suffix != NULL); + + if (strlen(suffix) > strlen(filename)) + return false; + + filename += (strlen(filename) - strlen(suffix)); + if (strcmp(filename, suffix) == 0) + return true; + else + return false; +} + +long +getSizeOfFile(const char *filename) +{ + struct stat fileProperties; + + if (filename == NULL) + return -1; + + if (stat(filename, &fileProperties) != 0) + return -1; + + return fileProperties.st_size; +} + +bool +checkFileSize(const char *filename, long min, long max) +{ + long filesize = getSizeOfFile(filename); + + if (filesize == -1) + return false; + + if (min > 0 && filesize < min) + return false; + + if (max > 0 && filesize > max) + return false; + + return true; +} + +bool +matchingFileHeader(const char *path, const uint8_t *header, size_t length) +{ + assert(path != NULL); + assert(header != NULL); + + bool result = true; + FILE *file; + + if ((file = fopen(path, "r")) == NULL) + return false; + + for (size_t i = 0; i < length; i++) { + int c = fgetc(file); + if (c != (int)header[i]) { + result = false; + break; + } + } + + fclose(file); + return result; +} + + +bool +matchingBufferHeader(const uint8_t *buffer, const uint8_t *header, size_t length) +{ + assert(buffer != NULL); + assert(header != NULL); + + for (size_t i = 0; i < length; i++) { + if (header[i] != buffer[i]) + return false; + } + + return true; +} + +#if 0 +bool +checkFileHeader(const char *filename, const uint8_t *header) +{ + int i, c; + bool result = true; + FILE *file; + + assert(filename != NULL); + assert(header != NULL); + + if ((file = fopen(filename, "r")) == NULL) + return false; + + for (i = 0; header[i] != 0; i++) { + c = fgetc(file); + if (c != (int)header[i]) { + result = false; + break; + } + } + + fclose(file); + return result; +} +#endif + +uint8_t +localTimeSec() +{ + time_t t = time(NULL); + struct tm *loctime = localtime(&t); + return (uint8_t)loctime->tm_sec; +} + +uint8_t +localTimeMinute() +{ + time_t t = time(NULL); + struct tm *loctime = localtime(&t); + return (uint8_t)loctime->tm_min; +} + +uint8_t +localTimeHour() +{ + time_t t = time(NULL); + struct tm *loctime = localtime(&t); + return (uint8_t)loctime->tm_hour; +} + + +void +sleepMicrosec(unsigned usec) +{ + if (usec > 0 && usec < 1000000) { + usleep(usec); + } +} + +int64_t +sleepUntil(uint64_t kernelTargetTime, uint64_t kernelEarlyWakeup) +{ + uint64_t now = mach_absolute_time(); + int64_t jitter; + + if (now > kernelTargetTime) + return 0; + + // Sleep + // printf("Sleeping for %d\n", kernelTargetTime - now); + mach_wait_until(kernelTargetTime - kernelEarlyWakeup); + + // Count some sheep to increase precision + unsigned sheep = 0; + do { + jitter = mach_absolute_time() - kernelTargetTime; + sheep++; + } while (jitter < 0); + + return jitter; +} + +uint32_t +fnv_1a_32(uint8_t *addr, size_t size) +{ + if (addr == NULL || size == 0) return 0; + + uint32_t hash = fnv_1a_init32(); + + for (size_t i = 0; i < size; i++) { + hash = fnv_1a_it32(hash, (uint32_t)addr[i]); + } + + return hash; +} + +uint64_t +fnv_1a_64(uint8_t *addr, size_t size) +{ + if (addr == NULL || size == 0) return 0; + + uint64_t hash = fnv_1a_init64(); + + for (size_t i = 0; i < size; i++) { + hash = fnv_1a_it64(hash, (uint64_t)addr[i]); + } + + return hash; +} diff --git a/C64/General/basic.h b/C64/General/basic.h new file mode 100755 index 00000000..5e463808 --- /dev/null +++ b/C64/General/basic.h @@ -0,0 +1,372 @@ +/*! + * @header basic.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _BASIC_INC +#define _BASIC_INC + +// General Includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Configure.h" + +//! @brief Two bit binary value +typedef uint8_t uint2_t; + +//! @brief Integrity check +inline bool is_uint2_t(uint2_t value) { return value < 4; } + +//! @brief Three bit binary value +typedef uint8_t uint3_t; + +//! @brief Integrity check +inline bool is_uint3_t(uint2_t value) { return value < 8; } + +//! @brief Four bit binary value +typedef uint8_t uint4_t; + +//! @brief Integrity check +inline bool is_uint4_t(uint4_t value) { return value < 16; } + +//! @brief Five bit binary value +typedef uint8_t uint5_t; + +//! @brief Integrity check +inline bool is_uint5_t(uint5_t value) { return value < 32; } + + +// +//! @functiongroup Handling low level data objects +// + +//! @brief Returns the high byte of a uint16_t value. +#define HI_BYTE(x) (uint8_t)((x) >> 8) + +//! @brief Returns the low byte of a uint16_t value. +#define LO_BYTE(x) (uint8_t)((x) & 0xFF) + +//! @brief Specifies a larger integer in little endian byte format +#define LO_HI(x,y) (uint16_t)((y) << 8 | (x)) +#define LO_LO_HI(x,y,z) (uint32_t)((z) << 16 | (y) << 8 | (x)) +#define LO_LO_HI_HI(x,y,z,w) (uint32_t)((w) << 24 | (z) << 16 | (y) << 8 | (x)) + +//! @brief Specifies a larger integer in big endian byte format +#define HI_LO(x,y) (uint16_t)((x) << 8 | (y)) +#define HI_HI_LO(x,y,z) (uint32_t)((x) << 16 | (y) << 8 | (z)) +#define HI_HI_LO_LO(x,y,z,w) (uint32_t)((x) << 24 | (y) << 16 | (z) << 8 | (w)) + +//! @brief Returns a certain byte of a larger integer +#define BYTE0(x) LO_BYTE(x) +#define BYTE1(x) LO_BYTE((x) >> 8) +#define BYTE2(x) LO_BYTE((x) >> 16) +#define BYTE3(x) LO_BYTE((x) >> 24) + +//! @brief Returns a non-zero value if the n-th bit is set in x. +#define GET_BIT(x,nr) ((x) & (1 << (nr))) + +//! @brief Sets a single bit. +#define SET_BIT(x,nr) ((x) |= (1 << (nr))) + +//! @brief Clears a single bit. +#define CLR_BIT(x,nr) ((x) &= ~(1 << (nr))) + +//! @brief Toggles a single bit. +#define TOGGLE_BIT(x,nr) ((x) ^= (1 << (nr))) + +//! @brief Sets a single bit to 0 (value == 0) or 1 (value != 0) +#define WRITE_BIT(x,nr,value) ((value) ? SET_BIT(x, nr) : CLR_BIT(x, nr)) + +//! @brief Copies a single bit from x to y. +#define COPY_BIT(x,y,nr) ((y) = ((y) & ~(1 << (nr)) | ((x) & (1 << (nr))))) + +//! @brief Returns true if value is rising when switching from x to y +#define RISING_EDGE(x,y) (!(x) && (y)) + +//! @brief Returns true if bit n is rising when switching from x to y +#define RISING_EDGE_BIT(x,y,n) (!((x) & (1 << (n))) && ((y) & (1 << (n)))) + +//! @brief Returns true if value is falling when switching from x to y +#define FALLING_EDGE(x,y) ((x) && !(y)) + +//! @brief Returns true if bit n is falling when switching from x to y +#define FALLING_EDGE_BIT(x,y,n) (((x) & (1 << (n))) && !((y) & (1 << (n)))) + + +// +//! @functiongroup Handling buffers +// + +//! @brief Writes a byte value into a buffer. +inline void write8(uint8_t **ptr, uint8_t value) { *((*ptr)++) = value; } + +//! @brief Writes a word value into a buffer in big endian format. +inline void write16(uint8_t **ptr, uint16_t value) { + write8(ptr, (uint8_t)(value >> 8)); write8(ptr, (uint8_t)value); } + +//! @brief Writes a double byte value into a buffer in big endian format. +inline void write32(uint8_t **ptr, uint32_t value) { + write16(ptr, (uint16_t)(value >> 16)); write16(ptr, (uint16_t)value); } + +//! @brief Writes a quad word value into a buffer in big endian format. +inline void write64(uint8_t **ptr, uint64_t value) { + write32(ptr, (uint32_t)(value >> 32)); write32(ptr, (uint32_t)value); } + +//! @brief Writes a memory block into a buffer in big endian format. +inline void writeBlock(uint8_t **ptr, uint8_t *values, size_t length) { + memcpy(*ptr, values, length); *ptr += length; } + +//! @brief Writes a word memory block into a buffer in big endian format. +inline void writeBlock16(uint8_t **ptr, uint16_t *values, size_t length) { + for (unsigned i = 0; i < length / sizeof(uint16_t); i++) write16(ptr, values[i]); } + +//! @brief Writes a double word memory block into a buffer in big endian format. +inline void writeBlock32(uint8_t **ptr, uint32_t *values, size_t length) { + for (unsigned i = 0; i < length / sizeof(uint32_t); i++) write32(ptr, values[i]); } + +//! @brief Writes a quad word memory block into a buffer in big endian format. +inline void writeBlock64(uint8_t **ptr, uint64_t *values, size_t length) { + for (unsigned i = 0; i < length / sizeof(uint64_t); i++) write64(ptr, values[i]); } + + +//! @brief Reads a byte value from a buffer. +inline uint8_t read8(uint8_t **ptr) { return (uint8_t)(*((*ptr)++)); } + +//! @brief Reads a word value from a buffer in big endian format. +inline uint16_t read16(uint8_t **ptr) { + return ((uint16_t)read8(ptr) << 8) | (uint16_t)read8(ptr); } + +//! @brief Reads a double word value from a buffer in big endian format. +inline uint32_t read32(uint8_t **ptr) { + return ((uint32_t)read16(ptr) << 16) | (uint32_t)read16(ptr); } + +//! @brief Reads a quad word value from a buffer in big endian format. +inline uint64_t read64(uint8_t **ptr) { + return ((uint64_t)read32(ptr) << 32) | (uint64_t)read32(ptr); } + +//! @brief Reads a memory block from a buffer. +inline void readBlock(uint8_t **ptr, uint8_t *values, size_t length) { + memcpy(values, *ptr, length); *ptr += length; } + +//! @brief Reads a word block from a buffer in big endian format. +inline void readBlock16(uint8_t **ptr, uint16_t *values, size_t length) { + for (unsigned i = 0; i < length / sizeof(uint16_t); i++) values[i] = read16(ptr); } + +//! @brief Reads a double word block from a buffer in big endian format. +inline void readBlock32(uint8_t **ptr, uint32_t *values, size_t length) { + for (unsigned i = 0; i < length / sizeof(uint32_t); i++) values[i] = read32(ptr); } + +//! @brief Reads a quad word block from a buffer in big endian format. +inline void readBlock64(uint8_t **ptr, uint64_t *values, size_t length) { + for (unsigned i = 0; i < length / sizeof(uint64_t); i++) values[i] = read64(ptr); } + + +// +//! @functiongroup Converting low level data objects +// + +/*! @brief Translates a PETSCII string to a unichar array. + * @details This functions creates unicode characters compatible with the + * C64ProMono font. The target font supports four different mapping + * tables starting at different base addresses: + * + * 0xE000 : Unshifted (only upper case characters) + * 0xE100 : Shifted (upper and lower case characters) + * 0xE200 : Unshifted, reversed + * 0xE300 : Shifted, reversed + * + * @note A maximum of max characters are translated. + * The unicode array will always be terminated by a NULL character. + */ +void translateToUnicode(const char *petscii, uint16_t *unichars, + uint16_t base, size_t max); + +//! @brief Returns the number of characters in a null terminated unichar array +size_t strlen16(const uint16_t *unichars); + +/*! @brief Converts a PETSCII character to a printable character. + * @details Replaces all unprintable characters by subst. + */ +uint8_t petscii2printable(uint8_t c, uint8_t subst); + +/*! @brief Converts an ASCII character to a PETSCII character. + * @details This function translates into the unshifted PET character set. + * I.e., lower case characters are converted to uppercase characters. + * @result Returns ' ' for ASCII characters with no PETSCII representation. + */ +uint8_t ascii2pet(uint8_t asciichar); + +//! @brief Converts an ASCII string into a PETSCII string. +/*! @details Applies function ascii2pet to all characters of a string. + */ +void ascii2petStr(char *str); + +//! @brief Writes an uint8_t value into a string in decimal format +void sprint8d(char *s, uint8_t value); + +//! @brief Writes an uint8_t value into a string in hexadecimal format +void sprint8x(char *s, uint8_t value); + +//! @brief Writes an uint8_t value into a string in binary format +void sprint8b(char *s, uint8_t value); + +//! @brief Writes an uint16_t value into a string in decimal format +void sprint16d(char *s, uint16_t value); + +//! @brief Writes an uint16_t value into a string in hexadecimal format +void sprint16x(char *s, uint16_t value); + +//! @brief Writes an uint16_t value into a string in binary format +void sprint16b(char *s, uint16_t value); + + +// +//! @functiongroup Handling file and path names +// + +/*! @brief Extracts filename from a path + * @details Returns a newly created string. You need to delete it manually. + */ +char *extractFilename(const char *path); + +/*! @brief Extracts file suffix from a path + * @details Returns a newly created string. You need to delete it manually. + */ +char *extractSuffix(const char *path); + +/*! @brief Extracts filename from a path without its suffix + * @details Returns a newly created string. You need to delete it manually. + */ +char *extractFilenameWithoutSuffix(const char *path); + +/*! @brief Check file suffix + * @details The function is used for determining the type of a file. + */ +bool checkFileSuffix(const char *filename, const char *suffix); + +//! @brief Returns the size of a file in bytes +long getSizeOfFile(const char *filename); + +/*! @brief Checks the size of a file + * @details The function is used for validating the size of a file. + * @param filename Path and name of the file to investigate + * @param min Expected minimum size (-1 if no lower bound exists) + * @param max Expected maximum size (-1 if no upper bound exists) + */ +bool checkFileSize(const char *filename, long min, long max); + +/*! @brief Checks the header signature (magic bytes) of a file. + * @details This function is used for determining the type of a file. + * @param File name, must not be Null + * @param Expected byte sequence + * @param Length of the expected byte sequence in bytes + */ +bool matchingFileHeader(const char *path, const uint8_t *header, size_t length); + +/*! @brief Checks the header signature (magic bytes) of a buffer. + * @details This function is used for determining the type of a file. + * @param Pointer to buffer, must not be NULL + * @param Expected byte sequence + * @param Length of the expected byte sequence in bytes + */ +bool matchingBufferHeader(const uint8_t *buffer, const uint8_t *header, size_t length); + +/*! @brief Checks the magic bytes of a file. + * @details The function is used for determining the type of a file. + * @param filename Path and name of the file to investigate. + * @param header Expected byte sequence, terminated by 0x00. + * @return Returns true iff magic bytes match. + * @deprecated Use matchingFileHeader() instead. +*/ +// bool checkFileHeader(const char *filename, const uint8_t *header); + + +// +//! @functiongroup Managing time +// + +/*! @brief Application launch time in seconds + * @details The value is read by function msec for computing the elapsed + * number of microseconds. + */ +// extern long tv_base; + +//! @brief Return the number of elapsed microseconds since program launch. +// uint64_t usec(); + +//! @brief Reads the real-time clock (1/10th seconds). +uint8_t localTimeSecFrac(); + +//! @brief Reads the real-time clock (seconds). +uint8_t localTimeSec(); + +//! @brief Reads the real-time clock (minutes). +uint8_t localTimeMin(); + +//! @brief Reads the real-time clock (hours). +uint8_t localTimeHour(); + +//! @brief Put the current thread to sleep for a certain amount of time. +void sleepMicrosec(unsigned usec); + +/*! @brief Sleeps until kernel timer reaches kernelTargetTime + * @param kernelEarlyWakeup: To increase timing precision, the function + * wakes up the thread earlier by this amount and waits actively in + * a delay loop until the deadline is reached. + * @return Overshoot time (jitter), measured in kernel time. Smaller values + * are better, 0 is best. + */ +int64_t sleepUntil(uint64_t kernelTargetTime, uint64_t kernelEarlyWakeup); + + +// +//! @functiongroup Computing checksums +// + +//! @brief Returns the FNV-1a seed value. +inline uint32_t fnv_1a_init32() { return 0x811c9dc5; } +inline uint64_t fnv_1a_init64() { return 0xcbf29ce484222325; } + +//! @brief Performs a single iteration of the FNV-1a hash algorithm. +inline uint32_t fnv_1a_it32(uint32_t prev, uint32_t value) { return (prev ^ value) * 0x1000193; } +inline uint64_t fnv_1a_it64(uint64_t prev, uint64_t value) { return (prev ^ value) * 0x100000001b3; } + +//! @brief Computes a FNV-1a for a given buffer +uint32_t fnv_1a_32(uint8_t *addr, size_t size); +uint64_t fnv_1a_64(uint8_t *addr, size_t size); + +#endif diff --git a/C64/Memory/C64Memory.cpp b/C64/Memory/C64Memory.cpp new file mode 100755 index 00000000..6aa25364 --- /dev/null +++ b/C64/Memory/C64Memory.cpp @@ -0,0 +1,551 @@ +/*! + * @file C64Memory.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +C64Memory::C64Memory() +{ + setDescription("C64 memory"); + + debug (3, " Creating main memory at address %p...\n", this); + + memset(rom, 0, sizeof(rom)); + stack = &ram[0x0100]; + + // Register snapshot items + SnapshotItem items[] = { + + { ram, sizeof(ram), KEEP_ON_RESET }, + { colorRam, sizeof(colorRam), KEEP_ON_RESET }, + { &rom[0xA000], 0x2000, KEEP_ON_RESET }, /* Basic ROM */ + { &rom[0xD000], 0x1000, KEEP_ON_RESET }, /* Character ROM */ + { &rom[0xE000], 0x2000, KEEP_ON_RESET }, /* Kernal ROM */ + { &ramInitPattern, sizeof(ramInitPattern), KEEP_ON_RESET }, + { &peekSrc, sizeof(peekSrc), KEEP_ON_RESET }, + { &pokeTarget, sizeof(pokeTarget), KEEP_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); + + ramInitPattern = INIT_PATTERN_C64; + + // Setup the C64's memory bank map + + // If x = (EXROM, GAME, CHAREN, HIRAM, LORAM), then + // map[x][0] = mapping for range $1000 - $7FFF + // map[x][1] = mapping for range $8000 - $9FFF + // map[x][2] = mapping for range $A000 - $BFFF + // map[x][3] = mapping for range $C000 - $CFFF + // map[x][4] = mapping for range $D000 - $DFFF + // map[x][5] = mapping for range $E000 - $FFFF + MemoryType map[32][6] = { + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_CRTHI, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_CRTLO, M_CRTHI, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_RAM}, + {M_RAM, M_RAM, M_CRTHI, M_RAM, M_IO, M_KERNAL}, + {M_RAM, M_CRTLO, M_CRTHI, M_RAM, M_IO, M_KERNAL}, + + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_CHAR, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_CRTLO, M_BASIC, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_KERNAL}, + {M_RAM, M_CRTLO, M_BASIC, M_RAM, M_IO, M_KERNAL}, + + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_CHAR, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_RAM, M_BASIC, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_KERNAL}, + {M_RAM, M_RAM, M_BASIC, M_RAM, M_IO, M_KERNAL} + }; + + for (unsigned i = 0; i < 32; i++) { + bankMap[i][0x0] = M_PP; + bankMap[i][0x1] = map[i][0]; + bankMap[i][0x2] = map[i][0]; + bankMap[i][0x3] = map[i][0]; + bankMap[i][0x4] = map[i][0]; + bankMap[i][0x5] = map[i][0]; + bankMap[i][0x6] = map[i][0]; + bankMap[i][0x7] = map[i][0]; + bankMap[i][0x8] = map[i][1]; + bankMap[i][0x9] = map[i][1]; + bankMap[i][0xA] = map[i][2]; + bankMap[i][0xB] = map[i][2]; + bankMap[i][0xC] = map[i][3]; + bankMap[i][0xD] = map[i][4]; + bankMap[i][0xE] = map[i][5]; + bankMap[i][0xF] = map[i][5]; + } + + // Initialize peekSource and pokeTarket tables + peekSrc[0x0] = pokeTarget[0x0] = M_PP; + for (unsigned i = 0x1; i <= 0xF; i++) { + peekSrc[i] = pokeTarget[i] = M_RAM; + } +} + +C64Memory::~C64Memory() +{ + debug(3, " Releasing main memory at address %p...\n", this); +} + +void +C64Memory::reset() +{ + VirtualComponent::reset(); + + // Erase RAM + eraseWithPattern(ramInitPattern); + + // Initialize color RAM with random numbers + srand(1000); + for (unsigned i = 0; i < sizeof(colorRam); i++) { + colorRam[i] = (rand() & 0xFF); + } +} + +void +C64Memory::dump() +{ + msg("C64 Memory:\n"); + msg("-----------\n"); + msg(" Basic ROM: %s loaded\n", basicRomIsLoaded() ? "" : " not"); + msg("Character ROM: %s loaded\n", characterRomIsLoaded() ? "" : " not"); + msg(" Kernal ROM: %s loaded\n", kernalRomIsLoaded() ? "" : " not"); + + for (uint16_t addr = 0; addr < 0xFFFF; addr++) { + if (c64->cpu.hardBreakpoint(addr)) + msg("Hard breakpoint at %04X\n", addr); + if (c64->cpu.softBreakpoint(addr)) + msg("Soft breakpoint at %04X\n", addr); + } + msg("\n"); + + /* + for (uint16_t addr = 0x1000; addr < 0xB000; addr += 0x400) { + msg("%04X: ", addr); + for (unsigned i = 0; i < 30; i++) { + msg("%02X ", ram[addr + i]); + } + msg("\n"); + } + */ +} + +void +C64Memory::eraseWithPattern(RamInitPattern pattern) +{ + if (!isRamInitPattern(pattern)) { + warn("Unknown RAM init pattern. Assuming INIT_PATTERN_C64\n"); + pattern = INIT_PATTERN_C64; + } + + if (pattern == INIT_PATTERN_C64) { + for (unsigned i = 0; i < sizeof(ram); i++) + ram[i] = (i & 0x40) ? 0xFF : 0x00; + } else { + for (unsigned i = 0; i < sizeof(ram); i++) + ram[i] = (i & 0x80) ? 0x00 : 0xFF; + } + + // Make the screen look nice on startup + memset(&ram[0x400], 0x01, 40*25); +} + +void +C64Memory::updatePeekPokeLookupTables() +{ + // Read game line, exrom line, and processor port bits + uint8_t game = c64->expansionport.getGameLine() ? 0x08 : 0x00; + uint8_t exrom = c64->expansionport.getExromLine() ? 0x10 : 0x00; + uint8_t index = (c64->processorPort.read() & 0x07) | exrom | game; + + // Set ultimax flag + c64->setUltimax(exrom && !game); + + // Update table entries + for (unsigned bank = 1; bank < 16; bank++) { + peekSrc[bank] = pokeTarget[bank] = bankMap[index][bank]; + } + + // Call the Cartridge's delegation method + c64->expansionport.updatePeekPokeLookupTables(); +} + +uint8_t +C64Memory::peek(uint16_t addr, MemoryType source) +{ + switch(source) { + + case M_RAM: + return ram[addr]; + + case M_ROM: + return rom[addr]; + + case M_IO: + return peekIO(addr); + + case M_CRTLO: + case M_CRTHI: + return c64->expansionport.peek(addr); + + case M_PP: + if (likely(addr >= 0x02)) { + return ram[addr]; + } else if (addr == 0x00) { + return c64->processorPort.readDirection(); + } else { + return c64->processorPort.read(); + } + + case M_NONE: + return c64->vic.getDataBusPhi1(); + + default: + assert(0); + return 0; + } +} + +uint8_t +C64Memory::peek(uint16_t addr, bool gameLine, bool exromLine) +{ + uint8_t game = gameLine ? 0x08 : 0x00; + uint8_t exrom = exromLine ? 0x10 : 0x00; + uint8_t index = (c64->processorPort.read() & 0x07) | exrom | game; + + return peek(addr, bankMap[index][addr >> 12]); +} + +uint8_t +C64Memory::peekZP(uint8_t addr) +{ + if (likely(addr >= 0x02)) { + return ram[addr]; + } else if (addr == 0x00) { + return c64->processorPort.readDirection(); + } else { + return c64->processorPort.read(); + } +} + +uint8_t +C64Memory::peekIO(uint16_t addr) +{ + assert(addr >= 0xD000 && addr <= 0xDFFF); + + switch ((addr >> 8) & 0xF) { + + case 0x0: // VIC + case 0x1: // VIC + case 0x2: // VIC + case 0x3: // VIC + + // Only the lower 6 bits are used for adressing the VIC I/O space. + // As a result, VIC's I/O memory repeats every 64 bytes. + return c64->vic.peek(addr & 0x003F); + + case 0x4: // SID + case 0x5: // SID + case 0x6: // SID + case 0x7: // SID + + // Only the lower 5 bits are used for adressing the SID I/O space. + // As a result, SID's I/O memory repeats every 32 bytes. + return c64->sid.peek(addr & 0x001F); + + case 0x8: // Color RAM + case 0x9: // Color RAM + case 0xA: // Color RAM + case 0xB: // Color RAM + + return + (colorRam[addr - 0xD800] & 0x0F) | + (c64->vic.getDataBusPhi1() & 0xF0); + + case 0xC: // CIA 1 + + // Only the lower 4 bits are used for adressing the CIA I/O space. + // As a result, CIA's I/O memory repeats every 16 bytes. + return c64->cia1.peek(addr & 0x000F); + + case 0xD: // CIA 2 + + return c64->cia2.peek(addr & 0x000F); + + case 0xE: // I/O space 1 + + return c64->expansionport.peekIO1(addr); + + case 0xF: // I/O space 2 + + return c64->expansionport.peekIO2(addr); + } + + assert(false); + return 0; +} + +uint8_t +C64Memory::spypeek(uint16_t addr, MemoryType source) +{ + switch(source) { + + case M_RAM: + return ram[addr]; + + case M_ROM: + return rom[addr]; + + case M_IO: + return spypeekIO(addr); + + case M_CRTLO: + case M_CRTHI: + return c64->expansionport.spypeek(addr); + + case M_PP: + return peek(addr, M_PP); + + case M_NONE: + return ram[addr]; + + default: + assert(0); + return 0; + } +} + +uint8_t +C64Memory::spypeekIO(uint16_t addr) +{ + assert(addr >= 0xD000 && addr <= 0xDFFF); + + switch ((addr >> 8) & 0xF) { + + case 0x0: // VIC + case 0x1: // VIC + case 0x2: // VIC + case 0x3: // VIC + + return c64->vic.spypeek(addr & 0x003F); + + case 0x4: // SID + case 0x5: // SID + case 0x6: // SID + case 0x7: // SID + + return c64->sid.spypeek(addr & 0x001F); + + case 0xC: // CIA 1 + + // Only the lower 4 bits are used for adressing the CIA I/O space. + // As a result, CIA's I/O memory repeats every 16 bytes. + return c64->cia1.spypeek(addr & 0x000F); + + case 0xD: // CIA 2 + + return c64->cia2.spypeek(addr & 0x000F); + + case 0xE: // I/O space 1 + + return c64->expansionport.spypeekIO1(addr); + + case 0xF: // I/O space 2 + + return c64->expansionport.spypeekIO2(addr); + + default: + + return peek(addr); + } +} + +void +C64Memory::poke(uint16_t addr, uint8_t value, MemoryType target) +{ + switch(target) { + + case M_RAM: + case M_ROM: + ram[addr] = value; + return; + + case M_IO: + pokeIO(addr, value); + return; + + case M_CRTLO: + case M_CRTHI: + c64->expansionport.poke(addr, value); + return; + + case M_PP: + if (likely(addr >= 0x02)) { + ram[addr] = value; + } else if (addr == 0x00) { + c64->processorPort.writeDirection(value); + } else { + c64->processorPort.write(value); + } + return; + + case M_NONE: + return; + + default: + assert(0); + return; + } +} + +void +C64Memory::poke(uint16_t addr, uint8_t value, bool gameLine, bool exromLine) +{ + uint8_t game = gameLine ? 0x08 : 0x00; + uint8_t exrom = exromLine ? 0x10 : 0x00; + uint8_t index = (c64->processorPort.read() & 0x07) | exrom | game; + + poke(addr, value, bankMap[index][addr >> 12]); +} + +void +C64Memory::pokeZP(uint8_t addr, uint8_t value) +{ + if (likely(addr >= 0x02)) { + ram[addr] = value; + } else if (addr == 0x00) { + c64->processorPort.writeDirection(value); + } else { + c64->processorPort.write(value); + } +} + +void +C64Memory::pokeIO(uint16_t addr, uint8_t value) +{ + assert(addr >= 0xD000 && addr <= 0xDFFF); + + switch ((addr >> 8) & 0xF) { + + case 0x0: // VIC + case 0x1: // VIC + case 0x2: // VIC + case 0x3: // VIC + + // Only the lower 6 bits are used for adressing the VIC I/O space. + // As a result, VIC's I/O memory repeats every 64 bytes. + c64->vic.poke(addr & 0x003F, value); + return; + + case 0x4: // SID + case 0x5: // SID + case 0x6: // SID + case 0x7: // SID + + // Only the lower 5 bits are used for adressing the SID I/O space. + // As a result, SID's I/O memory repeats every 32 bytes. + c64->sid.poke(addr & 0x001F, value); + return; + + case 0x8: // Color RAM + case 0x9: // Color RAM + case 0xA: // Color RAM + case 0xB: // Color RAM + + colorRam[addr - 0xD800] = (value & 0x0F) | (rand() & 0xF0); + return; + + case 0xC: // CIA 1 + + // Only the lower 4 bits are used for adressing the CIA I/O space. + // As a result, CIA's I/O memory repeats every 16 bytes. + c64->cia1.poke(addr & 0x000F, value); + return; + + case 0xD: // CIA 2 + + c64->cia2.poke(addr & 0x000F, value); + return; + + case 0xE: // I/O space 1 + + c64->expansionport.pokeIO1(addr, value); + return; + + case 0xF: // I/O space 2 + + c64->expansionport.pokeIO2(addr, value); + return; + } + + assert(false); +} + +uint16_t +C64Memory::nmiVector() { + + if (peekSrc[0xF] != M_ROM || kernalRomIsLoaded()) { + return LO_HI(peek(0xFFFA), peek(0xFFFB)); + } else { + return 0xFE43; + } +} + +uint16_t +C64Memory::irqVector() { + + if (peekSrc[0xF] != M_ROM || kernalRomIsLoaded()) { + return LO_HI(peek(0xFFFE), peek(0xFFFF)); + } else { + return 0xFF48; + } +} + +uint16_t +C64Memory::resetVector() { + + if (peekSrc[0xF] != M_ROM || kernalRomIsLoaded()) { + debug("Grabbing reset vector from source %d\n", peekSrc[0xF]); + return LO_HI(peek(0xFFFC), peek(0xFFFD)); + } else { + return 0xFCE2; + } +} + diff --git a/C64/Memory/C64Memory.h b/C64/Memory/C64Memory.h new file mode 100755 index 00000000..3f02cd21 --- /dev/null +++ b/C64/Memory/C64Memory.h @@ -0,0 +1,216 @@ +/*! + * @header C64Memory.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _C64MEMORY_INC +#define _C64MEMORY_INC + +#include "Memory.h" + +/*! @brief This class represents RAM and ROM of the virtual C64 + * @details Due to the limited address space, RAM, ROM, and I/O memory are + * superposed, wich means that they share the same memory locations. + * The currently visible memory is determined by the contents of the + * processor port (memory address 1) and the current values of the + * Exrom and Game line. + */ +class C64Memory : public Memory { + +public: + + /*! @brief C64 bank mapping + * @details BankMap[index][range] where + * index = (EXROM, GAME, CHAREN, HIRAM, LORAM) + * range = upper four bits of address + */ + MemoryType bankMap[32][16]; + + /* + const MemoryType BankMap[32][6] = { + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_CRTHI, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_CRTLO, M_CRTHI, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_RAM}, + {M_RAM, M_RAM, M_CRTHI, M_RAM, M_IO, M_KERNAL}, + {M_RAM, M_CRTLO, M_CRTHI, M_RAM, M_IO, M_KERNAL}, + + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_CHAR, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_CRTLO, M_BASIC, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_KERNAL}, + {M_RAM, M_CRTLO, M_BASIC, M_RAM, M_IO, M_KERNAL}, + + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + {M_NONE, M_CRTLO, M_NONE, M_NONE, M_IO, M_CRTHI}, + + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_CHAR, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_RAM, M_BASIC, M_RAM, M_CHAR, M_KERNAL}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_RAM, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_RAM}, + {M_RAM, M_RAM, M_RAM, M_RAM, M_IO, M_KERNAL}, + {M_RAM, M_RAM, M_BASIC, M_RAM, M_IO, M_KERNAL} + }; + */ + + //! @brief Random Access Memory + uint8_t ram[65536]; + + /*! @brief Color RAM + * @details The color RAM is located in the I/O space, starting at $D800 and + * ending at $DBFF. Only the lower four bits are accessible, the upper + * four bits are open and can show any value. + */ + uint8_t colorRam[1024]; + + //! @brief Read Only Memory + /*! @details Only specific memory cells are valid ROM locations. In total, the C64 + * has three ROMs that are located at different addresses. + * Note, that the ROMs do not span over the whole 64KB range. Therefore, + * only some addresses are valid ROM addresses. + */ + uint8_t rom[65536]; + + //! @brief RAM init pattern type + RamInitPattern ramInitPattern; + + //! @brief Peek source lookup table + MemoryType peekSrc[16]; + + //! @brief Poke target lookup table + MemoryType pokeTarget[16]; + +public: + + //! @brief Constructor + C64Memory(); + + //! @brief Destructor + ~C64Memory(); + + //! @brief Method from VirtualComponent + void reset(); + + //! @brief Restors initial state, but keeps RAM alive + // void resetWithoutRAM(); + + //! @brief Method from VirtualComponent + void dump(); + + //! @brief Returns true, iff the Basic ROM has been loaded + bool basicRomIsLoaded() { return (rom[0xA000] | rom[0xA001]) != 0x00; } + + //! @brief Deletes the Basic ROM from memory + void deleteBasicRom() { memset(rom + 0xA000, 0, 0x2000); } + + //! @brief Returns true, iff the Character ROM has been loaded + bool characterRomIsLoaded() { return (rom[0xD000] | rom[0xD001]) != 0x00; } + + //! @brief Deletes the Character ROM from memory + void deleteCharacterRom() { memset(rom + 0xD000, 0, 0x1000); } + + //! @brief Returns true, iff the Kernal ROM has been loaded + bool kernalRomIsLoaded() { return (rom[0xE000] | rom[0xE001]) != 0x00; } + + //! @brief Deletes the Kernal ROM from memory + void deleteKernalRom() { memset(rom + 0xE000, 0, 0x2000); } + + /*! @brief Computes a 64-bit fingerprint for the Basic ROM. + * @return fingerprint or 0, if no Basic ROM is installed. + */ + uint64_t basicRomFingerprint() { + return basicRomIsLoaded() ? fnv_1a_64(rom + 0xA000, 0x2000) : 0; } + + /*! @brief Computes a 64-bit fingerprint for the Character ROM. + * @return fingerprint or 0, if no Basic ROM is installed. + */ + uint64_t characterRomFingerprint() { + return characterRomIsLoaded() ? fnv_1a_64(rom + 0xD000, 0x1000) : 0; } + + /*! @brief Computes a 64-bit fingerprint for the Kernal ROM. + * @return fingerprint or 0, if no Basic ROM is installed. + */ + uint64_t kernalRomFingerprint() { + return kernalRomIsLoaded() ? fnv_1a_64(rom + 0xE000, 0x2000) : 0; } + +public: + + //! @brief Returns the currently used RAM init pattern. + RamInitPattern getRamInitPattern() { return ramInitPattern; } + + //! @brief Sets the RAM init pattern type. + void setRamInitPattern(RamInitPattern pattern) { ramInitPattern = pattern; } + + //! @brief Erases the memory with the provided init pattern + void eraseWithPattern(RamInitPattern pattern); + + /*! @brief Updates the peek and poke lookup tables. + * @details The lookup values depend on three processor port bits + * and the cartridge exrom and game lines. + */ + void updatePeekPokeLookupTables(); + + //! @brief Returns the current peek source of the specified memory address + MemoryType getPeekSource(uint16_t addr) { return peekSrc[addr >> 12]; } + + //! @brief Returns the current poke target of the specified memory address + MemoryType getPokeTarget(uint16_t addr) { return pokeTarget[addr >> 12]; } + + // Reading from memory + uint8_t peek(uint16_t addr, MemoryType source); + uint8_t peek(uint16_t addr, bool gameLine, bool exromLine); + uint8_t peek(uint16_t addr) { return peek(addr, peekSrc[addr >> 12]); } + uint8_t peekZP(uint8_t addr); + uint8_t peekIO(uint16_t addr); + + // Reading from memory without side effects + uint8_t spypeek(uint16_t addr, MemoryType source); + uint8_t spypeek(uint16_t addr) { return spypeek(addr, peekSrc[addr >> 12]); } + uint8_t spypeekIO(uint16_t addr); + + // Writing into memory + void poke(uint16_t addr, uint8_t value, MemoryType target); + void poke(uint16_t addr, uint8_t value, bool gameLine, bool exromLine); + void poke(uint16_t addr, uint8_t value) { poke(addr, value, pokeTarget[addr >> 12]); } + void pokeZP(uint8_t addr, uint8_t value); + void pokeIO(uint16_t addr, uint8_t value); + + //! @brief Reads the NMI vector from memory. + uint16_t nmiVector(); + + //! @brief Reads the IRQ vector from memory. + uint16_t irqVector(); + + //! @brief Reads the Reset vector from memory. + uint16_t resetVector(); +}; + +#endif diff --git a/C64/Memory/Memory.h b/C64/Memory/Memory.h new file mode 100755 index 00000000..92c9103b --- /dev/null +++ b/C64/Memory/Memory.h @@ -0,0 +1,75 @@ +/*! + * @header Memory.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MEMORY_INC +#define _MEMORY_INC + +#include "basic.h" +#include "VirtualComponent.h" +#include "Memory_types.h" + + +//! @brief Common interface for C64 memory and VC1541 memory +class Memory : public VirtualComponent { + + friend class CPU; + +protected: + + /*! @brief Pointer to the first byte of the stack memory + * @details This value is used by peekStack and pokeStack, only. + */ + uint8_t *stack = NULL; + +private: + + /*! @brief Peeks a byte from memory. + * @details Emulates a native read access including side effects. + * The value is read is from the currently visible memory. + * @seealso spypeek + */ + virtual uint8_t peek(uint16_t addr) = 0; + + //! @brief Peeks a byte from the zero page. + virtual uint8_t peekZP(uint8_t addr) = 0; + + //! @brief Peeks a byte from the stack. + virtual uint8_t peekStack(uint8_t sp) { return stack[sp]; } + +public: + + /*! @brief Peeks a byte from memory without causing side effects. + * seealso peek + */ + virtual uint8_t spypeek(uint16_t addr) = 0; + + /*! @brief Pokes a byte into memory. + * @details Emulates a native write access including all side effects. + */ + virtual void poke(uint16_t addr, uint8_t value) = 0; + + //! @brief Pokes a byte into the zero page. + virtual void pokeZP(uint8_t addr, uint8_t value) = 0; + + //! @brief Pokes a byte onto the stack. + virtual void pokeStack(uint8_t sp, uint8_t value) { stack[sp] = value; } +}; + +#endif diff --git a/C64/Memory/Memory_types.h b/C64/Memory/Memory_types.h new file mode 100755 index 00000000..1b5d4d0e --- /dev/null +++ b/C64/Memory/Memory_types.h @@ -0,0 +1,51 @@ +/*! + * @header Memory_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MEMORY_TYPES_H +#define MEMORY_TYPES_H + +//! @brief Memory source identifiers +/*! @details The identifiers are used inside the peek and poke lookup tables + * to indicate the source and target of a peek or poke operation. + */ +typedef enum { + M_RAM = 1, + M_ROM, + M_CHAR = M_ROM, + M_KERNAL = M_ROM, + M_BASIC = M_ROM, + M_IO, + M_CRTLO, + M_CRTHI, + M_PP, + M_NONE +} MemoryType; + +//! @brief RAM init pattern type +typedef enum { + INIT_PATTERN_C64 = 0, + INIT_PATTERN_C64C = 1 +} RamInitPattern; + +inline bool isRamInitPattern(RamInitPattern pattern) { + return (pattern == INIT_PATTERN_C64) || (pattern == INIT_PATTERN_C64C); +} + +#endif diff --git a/C64/Mouse/Mouse.cpp b/C64/Mouse/Mouse.cpp new file mode 100755 index 00000000..b9b1878d --- /dev/null +++ b/C64/Mouse/Mouse.cpp @@ -0,0 +1,180 @@ +/*! + * @file Mouse.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +Mouse::Mouse() +{ + setDescription("Mouse"); + debug(3, "Creating %s at address %p\n", getDescription()); + + // Register sub components + VirtualComponent *subcomponents[] = { &mouse1350, &mouse1351, &mouseNeos, NULL }; + registerSubComponents(subcomponents, sizeof(subcomponents)); +} + +Mouse::~Mouse() +{ +} + +void Mouse::reset() +{ + VirtualComponent::reset(); + targetX = 0; + targetY = 0; +} +void +Mouse::setModel(MouseModel model) +{ + c64->suspend(); + this->model = model; + reset(); + c64->resume(); +} + +void +Mouse::connectMouse(unsigned portNr) +{ + // debug("Connecting mouse to port %d\n", portNr); + + assert(portNr <= 2); + port = portNr; +} + +void +Mouse::setXY(int64_t x, int64_t y) +{ + targetX = x; + targetY = y; +} + +void +Mouse::setLeftButton(bool value) +{ + switch(model) { + case MOUSE1350: + mouse1350.setLeftMouseButton(value); + break; + case MOUSE1351: + mouse1351.setLeftMouseButton(value); + break; + case NEOSMOUSE: + mouseNeos.setLeftMouseButton(value); + break; + default: + assert(false); + } +} + +void +Mouse::setRightButton(bool value) +{ + switch(model) { + case MOUSE1350: + mouse1350.setRightMouseButton(value); + break; + case MOUSE1351: + mouse1351.setRightMouseButton(value); + break; + case NEOSMOUSE: + mouseNeos.setRightMouseButton(value); + break; + default: + assert(false); + } +} + +uint8_t +Mouse::readPotX() +{ + if (port > 0) { + switch(model) { + case MOUSE1350: + return mouse1350.readPotX(); + case MOUSE1351: + mouse1351.executeX(targetX); + return mouse1351.readPotX(); + case NEOSMOUSE: + return mouseNeos.readPotX(); + default: + assert(false); + } + } + return 0xFF; +} + +uint8_t +Mouse::readPotY() +{ + if (port > 0) { + switch(model) { + case MOUSE1350: + return mouse1350.readPotY(); + case MOUSE1351: + mouse1351.executeY(targetY); + return mouse1351.readPotY(); + case NEOSMOUSE: + return mouseNeos.readPotY(); + default: + assert(false); + } + } + return 0xFF; +} + +uint8_t +Mouse::readControlPort(unsigned portNr) +{ + // debug("port = %d portNr = %d\n", port, portNr); + + if (port == portNr) { + switch(model) { + case MOUSE1350: + return mouse1350.readControlPort(); + case MOUSE1351: + return mouse1351.readControlPort(); + case NEOSMOUSE: + return mouseNeos.readControlPort(targetX, targetY); + default: + assert(false); + } + } + return 0xFF; +} + +void +Mouse::execute() +{ + if (port) { + switch(model) { + case MOUSE1350: + mouse1350.execute(targetX, targetY); + break; + case MOUSE1351: + // Coordinates are updated in readPotX() and readPotY() + break; + case NEOSMOUSE: + // Coordinates are updated in latchPosition() + break; + default: + assert(false); + } + } +} diff --git a/C64/Mouse/Mouse.h b/C64/Mouse/Mouse.h new file mode 100755 index 00000000..448554ee --- /dev/null +++ b/C64/Mouse/Mouse.h @@ -0,0 +1,112 @@ +/*! + * @header Mouse.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MOUSE_H +#define MOUSE_H + +#include "Mouse_types.h" +#include "Mouse1350.h" +#include "Mouse1351.h" +#include "NeosMouse.h" + +//! @brief An external mouse plugged into the control port +class Mouse : public VirtualComponent { + + //! @brief A Commdore 1350 (digital) mouse + Mouse1350 mouse1350; + + //! @brief A Commdore 1351 (analog) mouse + Mouse1351 mouse1351; + + //! @brief A Neos (analog) mouse + NeosMouse mouseNeos; + + //! @brief Emulated mouse model + MouseModel model = MOUSE1350; + + /*! @brief The port the mouse is connected to + * @details 0 = unconnected, 1 = port 1, 2 = port 2 + */ + unsigned port = 0; + + /*! @brief Target mouse position + * @details In order to achieve a smooth mouse movement, a new mouse + * coordinate is not written directly into mouseX and mouseY. + * Instead, these variables are set. In execute(), mouseX and + * mouseY are shifted smoothly towards the target positions. + */ + int64_t targetX; + int64_t targetY; + +public: + + //! @brief Constructor + Mouse(); + + //! @brief Destructor + ~Mouse(); + + //! @brief Reset + void reset(); + + //! @brief Returns the model of this mouse. + MouseModel getModel() { return model; } + + //! @brief Sets the emulated mouse model. + void setModel(MouseModel model); + + //! @brief Returns the port the mouse is connected to (0 = unconnected). + unsigned getPort() { return port; } + + //! @brief Connects the mouse to the specified port. + void connectMouse(unsigned port); + + //! @brief Disconnects the mouse. + void disconnectMouse() { connectMouse(0); } + + //! @brief Emulates a mouse movement event. + void setXY(int64_t x, int64_t y); + + //! @brief Emulates a mouse button event. + void setLeftButton(bool value); + void setRightButton(bool value); + + //! @brief Triggers a state change (Neos mouse only). + void risingStrobe(int portNr) { + mouseNeos.risingStrobe(portNr, targetX, targetY); } + + //! @brief Triggers a state change (Neos mouse only). + void fallingStrobe(int portNr) { + mouseNeos.fallingStrobe(portNr, targetX, targetY); } + + //! @brief Returns the pot X bits as set by the mouse. + uint8_t readPotX(); + + //! @brief Returns the pot Y bits as set by the mouse. + uint8_t readPotY(); + + //! @brief Returns the control port bits as set by the mouse. + uint8_t readControlPort(unsigned portNr); + + //! @brief Performs periodic actions for this device. + void execute(); +}; + +#endif diff --git a/C64/Mouse/Mouse1350.cpp b/C64/Mouse/Mouse1350.cpp new file mode 100755 index 00000000..dd342b96 --- /dev/null +++ b/C64/Mouse/Mouse1350.cpp @@ -0,0 +1,100 @@ +/*! + * @file Mouse1350.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" +#include "Mouse1350.h" + +Mouse1350::Mouse1350() { + + setDescription("Mouse1350"); + debug(3, " Creating Mouse1350 at address %p...\n", this); +} + +Mouse1350::~Mouse1350() +{ +} + +void +Mouse1350::reset() +{ + VirtualComponent::reset(); + + leftButton = false; + rightButton = false; + mouseX = 0; + mouseY = 0; + controlPort = 0xFF; + + for (unsigned i = 0; i < 3; i++) + latchedX[i] = latchedY[i] = 0; +} + +uint8_t +Mouse1350::readPotX() +{ + return rightButton ? 0x00 : 0xFF; +} + +uint8_t +Mouse1350::readPotY() +{ + return 0xFF; +} + +uint8_t +Mouse1350::readControlPort() +{ + return controlPort & (leftButton ? 0xEF : 0xFF); +} + +void +Mouse1350::execute(int64_t targetX, int64_t targetY) +{ + mouseX = targetX / dividerX; + mouseY = targetY / dividerY; + + debug("targetX = %d targetY = %d\n", targetX, targetY); + + controlPort = 0xFF; + + double deltaX = (mouseX - latchedX[0]); + double deltaY = (latchedY[0] - mouseY); + double absDeltaX = abs(deltaX); + double absDeltaY = abs(deltaY); + double max = (absDeltaX > absDeltaY) ? absDeltaX : absDeltaY; + + if (max > 0) { + deltaX /= max; + deltaY /= max; + if (deltaY < -0.5) { CLR_BIT(controlPort, 0); } // UP + if (deltaY > 0.5) { CLR_BIT(controlPort, 1); } // DOWN + if (deltaX < -0.5) { CLR_BIT(controlPort, 2); } // LEFT + if (deltaX > 0.5) { CLR_BIT(controlPort, 3); } // RIGHT + } + + // Update latch pipeline + for (unsigned i = 0; i < 2; i++) { + latchedX[i] = latchedX[i+1]; + latchedY[i] = latchedY[i+1]; + } + latchedX[2] = mouseX; + latchedY[2] = mouseY; +} + diff --git a/C64/Mouse/Mouse1350.h b/C64/Mouse/Mouse1350.h new file mode 100755 index 00000000..8293852e --- /dev/null +++ b/C64/Mouse/Mouse1350.h @@ -0,0 +1,79 @@ +/*! + * @header Mouse1350.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MOUSE1350_H +#define MOUSE1350_H + +#include "VirtualComponent.h" + +class Mouse1350 : public VirtualComponent { + +private: + + //! @brief Mouse position + int64_t mouseX; + int64_t mouseY; + + //! @brief Mouse button states + bool leftButton; + bool rightButton; + + //! @brief Dividers applied to raw coordinates in setXY() + int dividerX = 64; + int dividerY = 64; + + //! @brief Latched mouse positions + int64_t latchedX[3]; + int64_t latchedY[3]; + + //! @brief Control port bits + uint8_t controlPort; + +public: + + //! @brief Constructor + Mouse1350(); + + //! @brief Destructor + ~Mouse1350(); + + //! @brief Methods from VirtualComponent class + void reset(); + + //! @brief Updates the button state + void setLeftMouseButton(bool value) { leftButton = value; } + void setRightMouseButton(bool value) { rightButton = value; } + + //! @brief Returns the pot X bits as set by the mouse + uint8_t readPotX(); + + //! @brief Returns the pot Y bits as set by the mouse + uint8_t readPotY(); + + //! @brief Returns the control port bits triggered by the mouse + uint8_t readControlPort(); + + /*! @brief Execution function + * @details Translates movement deltas into joystick events. + */ + void execute(int64_t targetX, int64_t targetY); +}; + +#endif diff --git a/C64/Mouse/Mouse1351.cpp b/C64/Mouse/Mouse1351.cpp new file mode 100755 index 00000000..127c92a6 --- /dev/null +++ b/C64/Mouse/Mouse1351.cpp @@ -0,0 +1,92 @@ +/*! + * @file Mouse1351.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" +#include "Mouse1351.h" + +Mouse1351::Mouse1351() { + + setDescription("Mouse1351"); + debug(3, " Creating Mouse1351 at address %p...\n", this); +} + +Mouse1351::~Mouse1351() +{ +} + +void +Mouse1351::reset() +{ + VirtualComponent::reset(); + + leftButton = false; + rightButton = false; + mouseX = 0; + mouseY = 0; +} + +uint8_t +Mouse1351::readPotX() +{ + return mouseXBits(); +} + +uint8_t +Mouse1351::readPotY() +{ + return mouseYBits(); +} + +uint8_t +Mouse1351::readControlPort() +{ + uint8_t result = 0xFF; + + if (leftButton) CLR_BIT(result, 4); + if (rightButton) CLR_BIT(result, 0); + + return result; +} + +void +Mouse1351::executeX(int64_t targetX) +{ + targetX /= dividerX; + + // Jump directly to target coordinates if they are more than 8 shifts away. + if (abs(targetX - mouseX) / 8 > shiftX) mouseX = targetX; + + // Move mouse coordinates towards target coordinates + if (targetX < mouseX) mouseX -= MIN(mouseX - targetX, shiftX); + else if (targetX > mouseX) mouseX += MIN(targetX - mouseX, shiftX); +} + +void +Mouse1351::executeY(int64_t targetY) +{ + targetY /= dividerY; + + // Jump directly to target coordinates if they are more than 8 shifts away. + if (abs(targetY - mouseY) / 8 > shiftY) mouseY = targetY; + + // Move mouse coordinates towards target coordinates + if (targetY < mouseY) mouseY -= MIN(mouseY - targetY, shiftY); + else if (targetY > mouseY) mouseY += MIN(targetY - mouseY, shiftY); +} diff --git a/C64/Mouse/Mouse1351.h b/C64/Mouse/Mouse1351.h new file mode 100755 index 00000000..6b680407 --- /dev/null +++ b/C64/Mouse/Mouse1351.h @@ -0,0 +1,81 @@ +/*! + * @header Mouse1351.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MOUSE1351_H +#define MOUSE1351_H + +#include "VirtualComponent.h" + +class Mouse1351 : public VirtualComponent { + + //! @brief Mouse position + int64_t mouseX; + int64_t mouseY; + + //! @brief Mouse button states + bool leftButton; + bool rightButton; + + //! @brief Dividers applied to raw coordinates in setXY() + int dividerX = 256; + int dividerY = 256; + + //! @brief Mouse movement in pixels per execution step + int64_t shiftX = 31; + int64_t shiftY = 31; + +public: + + //! @brief Constructor + Mouse1351(); + + //! @brief Destructor + ~Mouse1351(); + + //! @brief Methods from VirtualComponent class + void reset(); + + //! @brief Updates the button state + void setLeftMouseButton(bool value) { leftButton = value; } + void setRightMouseButton(bool value) { rightButton = value; } + + //! @brief Returns the pot X bits as set by the mouse + uint8_t readPotX(); + + //! @brief Returns the pot Y bits as set by the mouse + uint8_t readPotY(); + + //! @brief Returns the control port bits triggered by the mouse + uint8_t readControlPort(); + + /*! @brief Execution function + * @details Shifts mouseX and mouseY smoothly towards targetX and targetX. + */ + void executeX(int64_t targetX); + void executeY(int64_t targetY); + + //! @brief Returns the mouse X bits as they show up in the SID register + uint8_t mouseXBits() { return (mouseX & 0x3F) << 1; } + + //! @brief Returns the mouse Y bits as they show up in the SID register + uint8_t mouseYBits() { return (mouseY & 0x3F) << 1; } +}; + +#endif diff --git a/C64/Mouse/Mouse_types.h b/C64/Mouse/Mouse_types.h new file mode 100755 index 00000000..11b0801e --- /dev/null +++ b/C64/Mouse/Mouse_types.h @@ -0,0 +1,32 @@ +/*! + * @header Mouse_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MOUSE_TYPES_H +#define MOUSE_TYPES_H + +/*! @brief Supportes mouse models + */ +typedef enum { + MOUSE1350 = 0, + MOUSE1351, + NEOSMOUSE +} MouseModel; + +#endif diff --git a/C64/Mouse/NeosMouse.cpp b/C64/Mouse/NeosMouse.cpp new file mode 100755 index 00000000..719dce17 --- /dev/null +++ b/C64/Mouse/NeosMouse.cpp @@ -0,0 +1,188 @@ +/*! + * @file NeosMouse.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" +#include "NeosMouse.h" + +NeosMouse::NeosMouse() { + + setDescription("NeosMouse"); + debug(3, " Creating NeosMouse at address %p...\n", this); +} + +NeosMouse::~NeosMouse() +{ +} + +void +NeosMouse::reset() +{ + VirtualComponent::reset(); + + leftButton = false; + rightButton = false; + mouseX = 0; + mouseY = 0; + state = 0; + triggerCycle = 0; + latchedX = 0; + latchedY = 0; + deltaX = 0; + deltaY = 0; +} + +uint8_t +NeosMouse::readPotX() +{ + return rightButton ? 0xFF : 0x00; +} + +uint8_t +NeosMouse::readPotY() +{ + return 0xFF; +} + +uint8_t +NeosMouse::readControlPort(int64_t targetX, int64_t targetY) +{ + uint8_t result = leftButton ? 0xE0 : 0xF0; + + // Check for time out + if (state != 0 && c64->cpu.cycle > (triggerCycle + 232) /* from VICE */) { + state = 0; + latchPosition(targetX, targetY); + } + + switch (state) { + + case 0: // Transmit X_HIGH + result |= (((uint8_t)deltaX >> 4) & 0x0F); + break; + + case 1: // Transmit X_LOW + result |= ((uint8_t)deltaX & 0x0F); + break; + + case 2: // Transmit Y_HIGH + result |= (((uint8_t)deltaY >> 4) & 0x0F); + break; + + case 3: // Transmit Y_LOW + result |= ((uint8_t)deltaY & 0x0F); + break; + + default: + assert(false); + } + + return result; +} + +/* +void +NeosMouse::execute(int64_t targetX, int64_t targetY) +{ + targetX /= dividerX; + targetY /= dividerY; + + // Jump directly to target coordinates if they are more than 8 shifts away. + if (abs(targetX - mouseX) / 8 > shiftX) mouseX = targetX; + if (abs(targetY - mouseY) / 8 > shiftY) mouseY = targetY; + + // Move mouse coordinates towards target coordinates + if (targetX < mouseX) mouseX -= MIN(mouseX - targetX, shiftX); + else if (targetX > mouseX) mouseX += MIN(targetX - mouseX, shiftX); + if (targetY < mouseY) mouseY -= MIN(mouseY - targetY, shiftY); + else if (targetY > mouseY) mouseY += MIN(targetY - mouseY, shiftY); +} +*/ + +void +NeosMouse::risingStrobe(int portNr, int64_t targetX, int64_t targetY) +{ + // Perform rising edge state changes + switch (state) { + case 0: /* X_HIGH -> X_LOW */ + state = 1; + break; + + case 2: /* Y_HIGH -> Y_LOW */ + state = 3; + break; + } + + // Remember trigger cycle + triggerCycle = c64->cpu.cycle; +} + +void +NeosMouse::fallingStrobe(int portNr, int64_t targetX, int64_t targetY) +{ + // Perform falling edge state changes + switch (state) { + case 1: /* X_LOW -> Y_HIGH */ + state = 2; + break; + + case 3: /* Y_LOW -> X_HIGH */ + state = 0; + latchPosition(targetX, targetY); + break; + } + + // Remember trigger cycle + triggerCycle = c64->cpu.cycle; +} + +void +NeosMouse::latchPosition(int64_t targetX, int64_t targetY) +{ + // + // Shift mouseX and mouseY towards targetX and targetY + // + + targetX /= dividerX; + targetY /= dividerY; + + // Jump directly to target coordinates if they are more than 8 shifts away. + if (abs(targetX - mouseX) / 8 > shiftX) mouseX = targetX; + if (abs(targetY - mouseY) / 8 > shiftY) mouseY = targetY; + + // Move mouse coordinates towards target coordinates + if (targetX < mouseX) mouseX -= MIN(mouseX - targetX, shiftX); + else if (targetX > mouseX) mouseX += MIN(targetX - mouseX, shiftX); + if (targetY < mouseY) mouseY -= MIN(mouseY - targetY, shiftY); + else if (targetY > mouseY) mouseY += MIN(targetY - mouseY, shiftY); + + // + // Compute deltas and latch values + // + + int64_t dx = MAX(MIN((latchedX - mouseX), 127), -128); + int64_t dy = MAX(MIN((mouseY - latchedY), 127), -128); + + deltaX = (uint8_t)dx; + deltaY = (uint8_t)dy; + + latchedX = mouseX; + latchedY = mouseY; +} + diff --git a/C64/Mouse/NeosMouse.h b/C64/Mouse/NeosMouse.h new file mode 100755 index 00000000..771b46a0 --- /dev/null +++ b/C64/Mouse/NeosMouse.h @@ -0,0 +1,108 @@ +/*! + * @header NeosMouse.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NEOSMOUSE_H +#define NEOSMOUSE_H + +#include "VirtualComponent.h" + +class NeosMouse : public VirtualComponent { + + //! @brief Mouse position + int64_t mouseX; + int64_t mouseY; + + //! @brief Mouse button states + bool leftButton; + bool rightButton; + + //! @brief Dividers applied to raw coordinates in setXY() + int dividerX = 512; + int dividerY = 256; + + //! @brief Mouse movement in pixels per execution step + int64_t shiftX = 127; + int64_t shiftY = 127; + + //! @brief Mouse state + /*! @details When the mouse switches to state 0, the current mouse + * position is latched and the deltaX and deltaY are computed. + * After that, the mouse cycles through the other states and + * writes the delta values onto the control port, nibble by nibble. + */ + uint8_t state; + + //! @brief CPU cycle of the most recent trigger event + uint64_t triggerCycle; + + //! @brief Latched horizontal mouse position + int64_t latchedX; + + //! @brief Latched vertical mouse position + int64_t latchedY; + + //! @brief The least signifanct value is transmitted to the C64 + int8_t deltaX; + + //! @brief The least signifanct value is transmitted to the C64 + int8_t deltaY; + +public: + + //! @brief Constructor + NeosMouse(); + + //! @brief Destructor + ~NeosMouse(); + + //! @brief Methods from VirtualComponent class + void reset(); + + //! @brief Updates the button state + void setLeftMouseButton(bool value) { leftButton = value; } + void setRightMouseButton(bool value) { rightButton = value; } + + //! @brief Returns the pot X bits as set by the mouse + uint8_t readPotX(); + + //! @brief Returns the pot Y bits as set by the mouse + uint8_t readPotY(); + + //! @brief Returns the control port bits triggered by the mouse + uint8_t readControlPort(int64_t targetX, int64_t targetY); + + /*! @brief Execution function + * @details Shifts mouseX and mouseY smoothly towards targetX and targetX. + */ + // void execute(int64_t targetX, int64_t targetY); + + //! @brief Triggers a state change (rising edge on control port line) + void risingStrobe(int portNr, int64_t targetX, int64_t targetY); + + //! @brief Triggers a state change (falling edge on control port line) + void fallingStrobe(int portNr, int64_t targetX, int64_t targetY); + +private: + + //! @brief Latches the current mouse position and computed deltas + void latchPosition(int64_t targetX, int64_t targetY); +}; + +#endif diff --git a/C64/SID/ReSID.cpp b/C64/SID/ReSID.cpp new file mode 100755 index 00000000..62c2d3bc --- /dev/null +++ b/C64/SID/ReSID.cpp @@ -0,0 +1,295 @@ +/* + * (C) 2011 - 2018 Dirk W. Hoffmann. All rights reserved. + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +ReSID::ReSID() +{ + setDescription("ReSID"); + debug(3, " Creating ReSID at address %p...\n", this); + + model = MOS_6581; + emulateFilter = true; + sampleRate = 44100; + + sid = new reSID::SID(); + sid->set_chip_model(reSID::MOS6581); + sid->set_sampling_parameters((double)PAL_CLOCK_FREQUENCY, + reSID::SAMPLE_FAST, + (double)sampleRate); + sid->enable_filter(emulateFilter); + + // Register snapshot items + SnapshotItem items[] = { + + // Configuration items + { &sampleRate, sizeof(sampleRate), KEEP_ON_RESET }, + { &emulateFilter, sizeof(emulateFilter), KEEP_ON_RESET }, + + // ReSID state + { st.sid_register, sizeof(st.sid_register), KEEP_ON_RESET }, + { &st.bus_value, sizeof(st.bus_value), KEEP_ON_RESET }, + { &st.bus_value_ttl, sizeof(st.bus_value_ttl), KEEP_ON_RESET }, + { &st.write_pipeline, sizeof(st.write_pipeline), KEEP_ON_RESET }, + { &st.write_address, sizeof(st.write_address), KEEP_ON_RESET }, + { &st.voice_mask, sizeof(st.voice_mask), KEEP_ON_RESET }, + { &st.accumulator[0], sizeof(st.accumulator[0]), KEEP_ON_RESET }, + { &st.accumulator[1], sizeof(st.accumulator[1]), KEEP_ON_RESET }, + { &st.accumulator[2], sizeof(st.accumulator[2]), KEEP_ON_RESET }, + { &st.shift_register[0], sizeof(st.shift_register[0]), KEEP_ON_RESET }, + { &st.shift_register[1], sizeof(st.shift_register[1]), KEEP_ON_RESET }, + { &st.shift_register[2], sizeof(st.shift_register[2]), KEEP_ON_RESET }, + { &st.shift_register_reset[0], sizeof(st.shift_register_reset[0]), KEEP_ON_RESET }, + { &st.shift_register_reset[1], sizeof(st.shift_register_reset[1]), KEEP_ON_RESET }, + { &st.shift_register_reset[2], sizeof(st.shift_register_reset[2]), KEEP_ON_RESET }, + { &st.shift_pipeline[0], sizeof(st.shift_pipeline[0]), KEEP_ON_RESET }, + { &st.shift_pipeline[1], sizeof(st.shift_pipeline[1]), KEEP_ON_RESET }, + { &st.shift_pipeline[2], sizeof(st.shift_pipeline[2]), KEEP_ON_RESET }, + { &st.pulse_output[0], sizeof(st.pulse_output[0]), KEEP_ON_RESET }, + { &st.pulse_output[1], sizeof(st.pulse_output[1]), KEEP_ON_RESET }, + { &st.pulse_output[2], sizeof(st.pulse_output[2]), KEEP_ON_RESET }, + { &st.floating_output_ttl[0], sizeof(st.floating_output_ttl[0]), KEEP_ON_RESET }, + { &st.floating_output_ttl[1], sizeof(st.floating_output_ttl[1]), KEEP_ON_RESET }, + { &st.floating_output_ttl[2], sizeof(st.floating_output_ttl[2]), KEEP_ON_RESET }, + { &st.rate_counter[0], sizeof(st.rate_counter[0]), KEEP_ON_RESET }, + { &st.rate_counter[1], sizeof(st.rate_counter[1]), KEEP_ON_RESET }, + { &st.rate_counter[2], sizeof(st.rate_counter[2]), KEEP_ON_RESET }, + { &st.rate_counter_period[0], sizeof(st.rate_counter_period[0]), KEEP_ON_RESET }, + { &st.rate_counter_period[1], sizeof(st.rate_counter_period[1]), KEEP_ON_RESET }, + { &st.rate_counter_period[2], sizeof(st.rate_counter_period[2]), KEEP_ON_RESET }, + { &st.exponential_counter[0], sizeof(st.exponential_counter[0]), KEEP_ON_RESET }, + { &st.exponential_counter[1], sizeof(st.exponential_counter[1]), KEEP_ON_RESET }, + { &st.exponential_counter[2], sizeof(st.exponential_counter[2]), KEEP_ON_RESET }, + { &st.exponential_counter_period[0],sizeof(st.exponential_counter_period[0]), KEEP_ON_RESET }, + { &st.exponential_counter_period[1],sizeof(st.exponential_counter_period[1]), KEEP_ON_RESET }, + { &st.exponential_counter_period[2],sizeof(st.exponential_counter_period[2]), KEEP_ON_RESET }, + { &st.envelope_counter[0], sizeof(st.envelope_counter[0]), KEEP_ON_RESET }, + { &st.envelope_counter[1], sizeof(st.envelope_counter[1]), KEEP_ON_RESET }, + { &st.envelope_counter[2], sizeof(st.envelope_counter[2]), KEEP_ON_RESET }, + { &st.envelope_state[0], sizeof(st.envelope_state[0]), KEEP_ON_RESET }, + { &st.envelope_state[1], sizeof(st.envelope_state[1]), KEEP_ON_RESET }, + { &st.envelope_state[2], sizeof(st.envelope_state[2]), KEEP_ON_RESET }, + { &st.hold_zero[0], sizeof(st.hold_zero[0]), KEEP_ON_RESET }, + { &st.hold_zero[1], sizeof(st.hold_zero[1]), KEEP_ON_RESET }, + { &st.hold_zero[2], sizeof(st.hold_zero[2]), KEEP_ON_RESET }, + { &st.envelope_pipeline[0], sizeof(st.envelope_pipeline[0]), KEEP_ON_RESET }, + { &st.envelope_pipeline[1], sizeof(st.envelope_pipeline[1]), KEEP_ON_RESET }, + { &st.envelope_pipeline[2], sizeof(st.envelope_pipeline[2]), KEEP_ON_RESET }, + + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +ReSID::~ReSID() +{ + delete sid; +} + +void +ReSID::reset() +{ + VirtualComponent::reset(); + + // Create new reSID object + // Note: We don't use reSID::reset() which only performs a soft reset + assert(sid != NULL); + delete sid; + sid = new reSID::SID(); + + // Reconfigure reSID + sid->set_chip_model((reSID::chip_model)model); + sid->set_sampling_parameters((double)clockFrequency, + (reSID::sampling_method)samplingMethod, + (double)sampleRate); + sid->enable_filter(emulateFilter); +} + +void +ReSID::setModel(SIDModel m) +{ + assert(m == 0 || m == 1); + model = m; + + suspend(); + sid->set_chip_model((reSID::chip_model)m); + resume(); + + // MOS8580 emulation seems to be problematic when combined with filters. + // TODO: Disable filters in combination with this chip + + assert((SIDModel)sid->sid_model == model); + debug("Emulating SID model %s.\n", + (model == MOS_6581) ? "MOS6581" : + (model == MOS_8580) ? "MOS8580" : "?"); +} + +void +ReSID::setClockFrequency(uint32_t value) +{ + clockFrequency = value; + + suspend(); + sid->set_sampling_parameters((double)clockFrequency, + (reSID::sampling_method)samplingMethod, + (double)sampleRate); + resume(); + + assert((uint32_t)sid->clock_frequency == clockFrequency); + debug("Setting clock frequency to %d cycles per second.\n", clockFrequency); +} + +void +ReSID::setSampleRate(uint32_t value) +{ + sampleRate = value; + + // suspend(); + sid->set_sampling_parameters((double)clockFrequency, + (reSID::sampling_method)samplingMethod, + (double)sampleRate); + // resume(); + + debug("Setting sample rate to %d samples per second.\n", sampleRate); +} + +void +ReSID::setAudioFilter(bool value) +{ + emulateFilter = value; + + suspend(); + sid->enable_filter(value); + // sid->filter._reset(); + resume(); + + debug("%s audio filter emulation.\n", value ? "Enabling" : "Disabling"); +} + +void +ReSID::setSamplingMethod(SamplingMethod value) +{ + switch(value) { + case SID_SAMPLE_FAST: + // warn("SID_SAMPLE_FAST not supported. Using SAMPLE_INTERPOLATE.\n"); + // value = SID_SAMPLE_INTERPOLATE; + debug("Using sampling method SAMPLE_FAST.\n"); + break; + case SID_SAMPLE_INTERPOLATE: + debug("Using sampling method SAMPLE_INTERPOLATE.\n"); + break; + case SID_SAMPLE_RESAMPLE: + debug("Using sampling method SAMPLE_RESAMPLE.\n"); + break; + case SID_SAMPLE_RESAMPLE_FASTMEM: + warn("SAMPLE_RESAMPLE_FASTMEM not supported. Using SAMPLE_INTERPOLATE.\n"); + value = SID_SAMPLE_INTERPOLATE; + break; + default: + warn("Unknown sampling method: %d\n", value); + } + + samplingMethod = value; + + suspend(); + sid->set_sampling_parameters((double)clockFrequency, + (reSID::sampling_method)samplingMethod, + (double)sampleRate); + resume(); + + assert((SamplingMethod)sid->sampling == samplingMethod); +} + +uint8_t +ReSID::peek(uint16_t addr) +{ + return sid->read(addr); +} + +void +ReSID::poke(uint16_t addr, uint8_t value) +{ + sid->write(addr, value); +} + +void +ReSID::execute(uint64_t elapsedCycles) +{ + short buf[2049]; + int buflength = 2048; + + if (elapsedCycles > PAL_CYCLES_PER_SECOND) { + warn("Number of missing SID cycles is far too large.\n"); + elapsedCycles = PAL_CYCLES_PER_SECOND; + } + + reSID::cycle_count delta_t = (reSID::cycle_count)elapsedCycles; + int bufindex = 0; + + // Let reSID compute some sound samples + while (delta_t) { + bufindex += sid->clock(delta_t, buf + bufindex, buflength - bufindex); + } + + // Write samples into ringbuffer + if (bufindex) { + bridge->writeData(buf, bufindex); + } +} + +SIDInfo +ReSID::getInfo() +{ + SIDInfo info; + reSID::SID::State state = sid->read_state(); + uint8_t *reg = (uint8_t *)state.sid_register; + + info.volume = reg[0x18] & 0x0F; + info.filterModeBits = reg[0x18] & 0xF0; + info.filterType = reg[0x18] & 0x70; + info.filterCutoff = (reg[0x16] << 3) | (reg[0x15] & 0x07); + info.filterResonance = reg[0x17] >> 4; + info.filterEnableBits = reg[0x17] & 0x0F; + return info; +} + +VoiceInfo +ReSID::getVoiceInfo(unsigned voice) +{ + VoiceInfo info; + reSID::SID::State state = sid->read_state(); + uint8_t *sidreg = (uint8_t *)state.sid_register + (voice * 7); + + for (unsigned j = 0; j < 7; j++) info.reg[j] = sidreg[j]; + info.frequency = HI_LO(sidreg[0x01], sidreg[0x00]); + info.pulseWidth = ((sidreg[3] & 0x0F) << 8) | sidreg[0x02]; + info.waveform = sidreg[0x04] & 0xF0; + info.ringMod = (sidreg[0x04] & 0x04) != 0; + info.hardSync = (sidreg[0x04] & 0x02) != 0; + info.gateBit = (sidreg[0x04] & 0x01) != 0; + info.testBit = (sidreg[0x04] & 0x08) != 0; + info.attackRate = sidreg[0x05] >> 4; + info.decayRate = sidreg[0x05] & 0x0F; + info.sustainRate = sidreg[0x06] >> 4; + info.releaseRate = sidreg[0x06] & 0x0F; + // info.filterOn = GET_BIT(state->sid_register[0x17], voice) != 0; + + return info; +} + + diff --git a/C64/SID/ReSID.h b/C64/SID/ReSID.h new file mode 100755 index 00000000..8b6b4448 --- /dev/null +++ b/C64/SID/ReSID.h @@ -0,0 +1,143 @@ +/*! + * @header ReSID.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright 2011 - 2015 Dirk W. Hoffmann + * @brief Declares ReSID wrapper class + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// List of modifications applied to reSID +// 1. Changed visibility of some objects from protected to public + +// Good candidate for testing sound emulation: INTERNAT.P00 + +#ifndef _RESID_INC +#define _RESID_INC + +#include "VirtualComponent.h" +#include "resid/sid.h" + +class ReSID : public VirtualComponent { + +public: + + // ReSID object + reSID::SID *sid; + +public: + + void clock() { sid->clock(); } + +private: + + //! ReSID state + reSID::SID::State st; + + //! @brief The emulated chip model + SIDModel model; + + //! @brief Clock frequency + /*! @details Either PAL_CLOCK_FREQUENCY or NTSC_CLOCK_FREQUENCY + */ + uint32_t clockFrequency; + + //! @brief Sample rate (usually set to 44.1 kHz) + uint32_t sampleRate; + + //! @brief Sampling method + SamplingMethod samplingMethod; + + //! @brief Switches filter emulation on or off. + bool emulateFilter; + +public: + + //! Pointer to bridge object + SIDBridge *bridge; + + //! Constructor. + ReSID(); + + //! Destructor. + ~ReSID(); + + //! @functiongroup Methods from VirtualComponent + void reset(); + void didLoadFromBuffer(uint8_t **buffer) { sid->write_state(st); } + void willSaveToBuffer(uint8_t **buffer) { st = sid->read_state(); } + + //! @brief Gathers all values that are displayed in the debugger + SIDInfo getInfo(); + + //! @brief Gathers all debug information for a specific voice + VoiceInfo getVoiceInfo(unsigned voice); + + //! Special peek function for the I/O memory range. + uint8_t peek(uint16_t addr); + + //! Special poke function for the I/O memory range. + void poke(uint16_t addr, uint8_t value); + + /*! @brief Execute SID + * @details Runs reSID for the specified amount of CPU cycles and writes + * the generated sound samples into the internal ring buffer. + */ + void execute(uint64_t cycles); + + + // Configuring + + //! Returns the chip model + SIDModel getModel() { + assert((SIDModel)sid->sid_model == model); + return model; + } + + //! Sets the chip model + void setModel(SIDModel m); + + //! Returns the clock frequency + uint32_t getClockFrequency() { + assert((uint32_t)sid->clock_frequency == clockFrequency); + return (uint32_t)sid->clock_frequency; + } + + //! Sets the clock frequency + void setClockFrequency(uint32_t frequency); + + //! Returns the sample rate + uint32_t getSampleRate() { return sampleRate; } + + //! Sets the sample rate + void setSampleRate(uint32_t rate); + + //! Returns true iff audio filters should be emulated. + bool getAudioFilter() { return emulateFilter; } + + //! Enable or disable audio filter emulation + void setAudioFilter(bool enable); + + //! Get sampling method + SamplingMethod getSamplingMethod() { + assert((SamplingMethod)sid->sampling == samplingMethod); + return samplingMethod; + } + + //! Set sampling method + void setSamplingMethod(SamplingMethod value); +}; + +#endif diff --git a/C64/SID/SIDBridge.cpp b/C64/SID/SIDBridge.cpp new file mode 100755 index 00000000..af1a0d50 --- /dev/null +++ b/C64/SID/SIDBridge.cpp @@ -0,0 +1,485 @@ +/*! + * @file SIDBridge.cpp + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +SIDBridge::SIDBridge() +{ + setDescription("SIDBridge"); + + fastsid.bridge = this; + resid.bridge = this; + + // Register sub components + VirtualComponent *subcomponents[] = { &resid, &fastsid, NULL }; + registerSubComponents(subcomponents, sizeof(subcomponents)); + + // Register snapshot items + SnapshotItem items[] = { + + // Configuration items + { &useReSID, sizeof(useReSID), KEEP_ON_RESET }, + + // Internal state + { &cycles, sizeof(cycles), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); + + useReSID = true; +} + +SIDBridge::~SIDBridge() +{ +} + +void +SIDBridge::reset() +{ + VirtualComponent::reset(); + + clearRingbuffer(); + resid.reset(); + fastsid.reset(); + + volume = 100000; + targetVolume = 100000; +} + +void +SIDBridge::setClockFrequency(uint32_t frequency) +{ + debug("Setting clock frequency to %d\n", frequency); + resid.setClockFrequency(frequency); + fastsid.setClockFrequency(frequency); +} + +void +SIDBridge::setReSID(bool enable) +{ + useReSID = enable; +} + +void +SIDBridge::dump(SIDInfo info) +{ + uint8_t ft = info.filterType; + msg(" Volume: %d\n", info.volume); + msg(" Filter type: %s\n", + (ft == FASTSID_LOW_PASS) ? "LOW PASS" : + (ft == FASTSID_HIGH_PASS) ? "HIGH PASS" : + (ft == FASTSID_BAND_PASS) ? "BAND PASS" : "NONE"); + msg("Filter cut off: %d\n\n", info.filterCutoff); + msg("Filter resonance: %d\n\n", info.filterResonance); + msg("Filter enable bits: %d\n\n", info.filterEnableBits); + + for (unsigned i = 0; i < 3; i++) { + VoiceInfo vinfo = getVoiceInfo(i); + uint8_t wf = vinfo.waveform; + msg("Voice %d: Frequency: %d\n", i, vinfo.frequency); + msg(" Pulse width: %d\n", vinfo.pulseWidth); + msg(" Waveform: %s\n", + (wf == FASTSID_NOISE) ? "NOISE" : + (wf == FASTSID_PULSE) ? "PULSE" : + (wf == FASTSID_SAW) ? "SAW" : + (wf == FASTSID_TRIANGLE) ? "TRIANGLE" : "NONE"); + msg(" Ring modulation: %s\n", vinfo.ringMod ? "yes" : "no"); + msg(" Hard sync: %s\n", vinfo.hardSync ? "yes" : "no"); + msg(" Attack rate: %d\n", vinfo.attackRate); + msg(" Decay rate: %d\n", vinfo.decayRate); + msg(" Sustain rate: %d\n", vinfo.sustainRate); + msg(" Release rate: %d\n", vinfo.releaseRate); + } +} + +void +SIDBridge::dump() +{ + msg("ReSID:\n"); + msg("------\n"); + dump(resid.getInfo()); + + msg("FastSID:\n"); + msg("--------\n"); + msg(" Chip model: %s\n", + (fastsid.getModel() == MOS_6581) ? "6581" : + (fastsid.getModel() == MOS_8580) ? "8580" : "???"); + msg(" Sampling rate: %d\n", fastsid.getSampleRate()); + msg(" CPU frequency: %d\n", fastsid.getClockFrequency()); + msg("Emulate filter: %s\n", fastsid.getAudioFilter() ? "yes" : "no"); + msg("\n"); + dump(fastsid.getInfo()); + + resid.sid->voice[0].wave.reset(); // reset_shift_register(); + resid.sid->voice[1].wave.reset(); // reset_shift_register(); + resid.sid->voice[2].wave.reset(); // reset_shift_register(); + resid.sid->voice[0].envelope.reset(); + resid.sid->voice[1].envelope.reset(); + resid.sid->voice[2].envelope.reset(); +} + +SIDInfo +SIDBridge::getInfo() +{ + SIDInfo info = useReSID ? resid.getInfo() : fastsid.getInfo(); + info.potX = c64->mouse.readPotX(); + info.potY = c64->mouse.readPotY(); + return info; +} + +VoiceInfo +SIDBridge::getVoiceInfo(unsigned voice) +{ + return useReSID ? resid.getVoiceInfo(voice) : fastsid.getVoiceInfo(voice); +} + +uint8_t +SIDBridge::peek(uint16_t addr) +{ + assert(addr <= 0x1F); + + // Get SID up to date + executeUntil(c64->cpu.cycle); + + if (addr == 0x19) { + return c64->mouse.readPotX(); + } + if (addr == 0x1A) { + return c64->mouse.readPotY(); + } + + if (useReSID) { + return resid.peek(addr); + } else { + return fastsid.peek(addr); + } +} + +uint8_t +SIDBridge::spypeek(uint16_t addr) +{ + assert(addr <= 0x1F); + return peek(addr); +} + +void +SIDBridge::poke(uint16_t addr, uint8_t value) +{ + // Get SID up to date + executeUntil(c64->cpu.cycle); + + // Keep both SID implementations up to date + resid.poke(addr, value); + fastsid.poke(addr, value); + + // Run ReSID for at least one cycle to make pipelined writes work + if (!useReSID) resid.clock(); +} + +void +SIDBridge::executeUntil(uint64_t targetCycle) +{ + uint64_t missingCycles = targetCycle - cycles; + + if (missingCycles > PAL_CYCLES_PER_SECOND) { + debug("Far too many SID cycles are missing.\n"); + missingCycles = PAL_CYCLES_PER_SECOND; + } + + execute(missingCycles); + cycles = targetCycle; +} + +void +SIDBridge::execute(uint64_t numCycles) +{ + // debug("Execute SID for %lld cycles (%d samples in buffer)\n", numCycles, samplesInBuffer()); + if (numCycles == 0) + return; + + if (useReSID) { + resid.execute(numCycles); + } else { + fastsid.execute(numCycles); + } +} + +void +SIDBridge::run() +{ + clearRingbuffer(); +} + +void +SIDBridge::halt() +{ + clearRingbuffer(); +} + +bool +SIDBridge::getAudioFilter() +{ + if (useReSID) { + return resid.getAudioFilter(); + } else { + return fastsid.getAudioFilter(); + } +} + +void +SIDBridge::setAudioFilter(bool value) +{ + resid.setAudioFilter(value); + fastsid.setAudioFilter(value); +} + +SamplingMethod +SIDBridge::getSamplingMethod() +{ + // Option is ReSID only + return resid.getSamplingMethod(); +} + +void +SIDBridge::setSamplingMethod(SamplingMethod value) +{ + // Option is ReSID only + resid.setSamplingMethod(value); +} + +SIDModel +SIDBridge::getModel() +{ + if (useReSID) { + return resid.getModel(); + } else { + return fastsid.getModel(); + } +} + +void +SIDBridge::setModel(SIDModel m) +{ + if (m != MOS_6581 && m != MOS_8580) { + warn("Unknown SID model (%d). Using MOS8580\n", m); + m = MOS_8580; + } + + suspend(); + resid.setModel(m); + fastsid.setModel(m); + resume(); +} + +uint32_t +SIDBridge::getSampleRate() +{ + if (useReSID) { + return resid.getSampleRate(); + } else { + return fastsid.getSampleRate(); + } +} + +void +SIDBridge::setSampleRate(uint32_t rate) +{ + debug("Changing sample rate from %d to %d\n", getSampleRate(), rate); + resid.setSampleRate(rate); + fastsid.setSampleRate(rate); +} + +uint32_t +SIDBridge::getClockFrequency() +{ + if (useReSID) { + return resid.getClockFrequency(); + } else { + return fastsid.getClockFrequency(); + } +} + +void +SIDBridge::clearRingbuffer() +{ + debug(4,"Clearing ringbuffer\n"); + + // Reset ringbuffer contents + for (unsigned i = 0; i < bufferSize; i++) { + ringBuffer[i] = 0.0f; + } + + // Put the write pointer ahead of the read pointer + alignWritePtr(); +} + +float +SIDBridge::readData() +{ + // Read sound sample + float value = ringBuffer[readPtr]; + + // Adjust volume + if (volume != targetVolume) { + if (volume < targetVolume) { + volume += MIN(volumeDelta, targetVolume - volume); + } else { + volume -= MIN(volumeDelta, volume - targetVolume); + } + } + // float divider = 75000.0f; // useReSID ? 100000.0f : 150000.0f; + float divider = 40000.0f; + value = (volume <= 0) ? 0.0f : value * (float)volume / divider; + + // Advance read pointer + advanceReadPtr(); + + return value; +} + +float +SIDBridge::ringbufferData(size_t offset) +{ + return ringBuffer[(readPtr + offset) % bufferSize]; +} + +void +SIDBridge::readMonoSamples(float *target, size_t n) +{ + // Check for buffer underflow + if (samplesInBuffer() < n) { + handleBufferUnderflow(); + } + + // Read samples + for (size_t i = 0; i < n; i++) { + float value = readData(); + target[i] = value; + } +} + +void +SIDBridge::readStereoSamples(float *target1, float *target2, size_t n) +{ + // debug("read: %d write: %d Reading %d\n", readPtr, writePtr, n); + + // Check for buffer underflow + if (samplesInBuffer() < n) { + handleBufferUnderflow(); + } + + // Read samples + for (unsigned i = 0; i < n; i++) { + float value = readData(); + target1[i] = target2[i] = value; + } +} + +void +SIDBridge::readStereoSamplesInterleaved(float *target, size_t n) +{ + // Check for buffer underflow + if (samplesInBuffer() < n) { + handleBufferUnderflow(); + } + + // Read samples + for (unsigned i = 0; i < n; i++) { + float value = readData(); + target[i*2] = value; + target[i*2+1] = value; + } +} + +void +SIDBridge::writeData(short *data, size_t count) +{ + // debug(" read: %d write: %d Writing %d (%d)\n", readPtr, writePtr, count, bufferCapacity()); + + // Check for buffer overflow + if (bufferCapacity() < count) { + handleBufferOverflow(); + } + + // Convert sound samples to floating point values and write into ringbuffer + for (unsigned i = 0; i < count; i++) { + ringBuffer[writePtr] = float(data[i]) * scale; + advanceWritePtr(); + } +} + +void +SIDBridge::handleBufferUnderflow() +{ + // There are two common scenarios in which buffer underflows occur: + // + // (1) The consumer runs slightly faster than the producer. + // (2) The producer is halted or not startet yet. + + debug(2, "SID RINGBUFFER UNDERFLOW (r: %ld w: %ld)\n", readPtr, writePtr); + + // Determine the elapsed seconds since the last pointer adjustment. + uint64_t now = mach_absolute_time(); + double elapsedTime = (double)(now - lastAlignment) / 1000000000.0; + lastAlignment = now; + + // Adjust the sample rate, if condition (1) holds. + if (elapsedTime > 10.0) { + + bufferUnderflows++; + + // Increase the sample rate based on what we've measured. + int offPerSecond = (int)(samplesAhead / elapsedTime); + setSampleRate(getSampleRate() + offPerSecond); + } + + // Reset the write pointer + alignWritePtr(); +} + +void +SIDBridge::handleBufferOverflow() +{ + // There are two common scenarios in which buffer overflows occur: + // + // (1) The consumer runs slightly slower than the producer. + // (2) The consumer is halted or not startet yet. + + debug(2, "SID RINGBUFFER OVERFLOW (r: %ld w: %ld)\n", readPtr, writePtr); + + // Determine the elapsed seconds since the last pointer adjustment. + uint64_t now = mach_absolute_time(); + double elapsedTime = (double)(now - lastAlignment) / 1000000000.0; + lastAlignment = now; + + // Adjust the sample rate, if condition (1) holds. + if (elapsedTime > 10.0) { + + bufferOverflows++; + + // Decrease the sample rate based on what we've measured. + int offPerSecond = (int)(samplesAhead / elapsedTime); + setSampleRate(getSampleRate() - offPerSecond); + } + + // Reset the write pointer + alignWritePtr(); +} diff --git a/C64/SID/SIDBridge.h b/C64/SID/SIDBridge.h new file mode 100755 index 00000000..5d6dc363 --- /dev/null +++ b/C64/SID/SIDBridge.h @@ -0,0 +1,316 @@ +/*! + * @header SIDBridge.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _SIDBRIDGE_H +#define _SIDBRIDGE_H + +#include "VirtualComponent.h" +#include "FastSID.h" +#include "ReSID.h" +#include "SID_types.h" + +class SIDBridge : public VirtualComponent { + + friend C64Memory; + +private: + + //! @brief FastSID (Adapted from VICE 3.1) + FastSID fastsid; + + //! @brief ReSID (Taken from VICE 3.1) + ReSID resid; + + //! @brief SID selector + bool useReSID; + + //! @brief CPU cycle at the last call to executeUntil() + uint64_t cycles; + + //! @brief Time stamp of the last write pointer alignment + uint64_t lastAlignment = 0; + +public: + + //! @brief Number of buffer underflows since power up + uint64_t bufferUnderflows; + + //! @brief Number of buffer overflows since power up + uint64_t bufferOverflows; + +private: + + // + // Audio ringbuffer + // + + //! @brief Number of sound samples stored in ringbuffer + static constexpr size_t bufferSize = 12288; + + /*! @brief The audio sample ringbuffer. + * @details This ringbuffer serves as the data interface between the + * emulation code and the audio API (CoreAudio on Mac OS X). + */ + float ringBuffer[bufferSize]; + + /*! @brief Scaling value for sound samples + * @details All sound samples produced by reSID are scaled by this + * value before they are written into the ringBuffer. + */ + static constexpr float scale = 0.000005f; + + /*! @brief Ring buffer read pointer + */ + uint32_t readPtr; + + /*! @brief Ring buffer write pointer + */ + uint32_t writePtr; + + /*! @brief Current volume + * @note A value of 0 or below silences the audio playback. + */ + int32_t volume; + + /*! @brief Target volume + * @details Whenever an audio sample is written, the volume is + * increased or decreased by volumeDelta to make it reach + * the target volume eventually. This feature simulates a + * fading effect. + */ + int32_t targetVolume; + + /*! @brief Maximum volume + */ + const static int32_t maxVolume = 100000; + + /*! @brief Volume offset + * @details If the current volume does not match the target volume, + * it is increased or decreased by the specified amount. The + * increase or decrease takes place whenever an audio sample + * is generated. + */ + int32_t volumeDelta; + +public: + + //! @brief Constructor + SIDBridge(); + + //! @brief Destructor + ~SIDBridge(); + + //! @functiongroup Methods from VirtualComponent + void reset(); + void dump(); + void setClockFrequency(uint32_t frequency); + void didLoadFromBuffer(uint8_t **buffer) { clearRingbuffer(); } + + //! @brief Prints debug information + void dump(SIDInfo info); + + //! @brief Gathers all values that are displayed in the debugger + SIDInfo getInfo(); + + //! @brief Gathers all debug information for a specific voice + VoiceInfo getVoiceInfo(unsigned voice); + + + // + // Configuring the device + // + + //! @brief Returns true, whether ReSID or the old implementation should be used. + bool getReSID() { return useReSID; } + + //! @brief Enables or disables the ReSID library. + void setReSID(bool enable); + + //! @brief Returns the simulated chip model. + SIDModel getModel(); + + //! @brief Sets chip model + void setModel(SIDModel m); + + //! @brief Returns true iff audio filters are enabled. + bool getAudioFilter(); + + //! @brief Enables or disables filters of SID. + void setAudioFilter(bool enable); + + //! @brief Returns the sampling method. + SamplingMethod getSamplingMethod(); + + //! @brief Sets the sampling method (ReSID only). + void setSamplingMethod(SamplingMethod value); + + //! @brief Returns the sample rate. + uint32_t getSampleRate(); + + //! @brief Sets the samplerate of SID and it's 3 voices. + void setSampleRate(uint32_t sr); + + //! @brief Returns the clock frequency. + uint32_t getClockFrequency(); + + // + // Running the device + // + + //! Notifies the SID chip that the emulator has started + void run(); + + //! Notifies the SID chip that the emulator has started + void halt(); + + // + // Volume control + // + + /*! @brief Sets the current volume + */ + void setVolume(int32_t vol) { volume = vol; } + + /*! @brief Triggers volume ramp up phase + * @details Configures volume and targetVolume to simulate a smooth audio fade in + */ + void rampUp() { targetVolume = maxVolume; volumeDelta = 3; ignoreNextUnderOrOverflow(); } + void rampUpFromZero() { volume = 0; rampUp(); } + + /*! @brief Triggers volume ramp down phase + * @details Configures volume and targetVolume to simulate a quick audio fade out + */ + void rampDown() { targetVolume = 0; volumeDelta = 50; ignoreNextUnderOrOverflow(); } + + // + // Ringbuffer handling + // + + //! @brief Returns the size of the ringbuffer + size_t ringbufferSize() { return bufferSize; } + + //! @brief Returns the position of the read pointer + uint32_t getReadPtr() { return readPtr; } + + //! @brief Returns the position of the write pointer + uint32_t getWritePtr() { return writePtr; } + + //! @brief Clears the ringbuffer and resets the read and write pointer + void clearRingbuffer(); + + //! @brief Reads a single audio sample from the ringbuffer + float readData(); + + //! @brief Reads a single audio sample without moving the read pointer + float ringbufferData(size_t offset); + + /*! @brief Reads a certain amount of samples from ringbuffer + * @details Samples are stored in a single mono stream + */ + void readMonoSamples(float *target, size_t n); + + /*! @brief Reads a certain amount of samples from ringbuffer + * @details Samples are stored in two seperate mono streams + */ + void readStereoSamples(float *target1, float *target2, size_t n); + + /*! @brief Reads a certain amount of samples from ringbuffer + * @details Samples are stored in an interleaved stereo stream + */ + void readStereoSamplesInterleaved(float *target, size_t n); + + /*! @brief Writes a certain number of audio samples into ringbuffer + */ + void writeData(short *data, size_t count); + + /*! @brief Handles a buffer underflow condition. + * @details A buffer underflow occurs when the computer's audio device + * needs sound samples than SID hasn't produced, yet. + */ + void handleBufferUnderflow(); + + /*! @brief Handles a buffer overflow condition + * @details A buffer overflow occurs when SID is producing more samples + * than the computer's audio device is able to consume. + */ + void handleBufferOverflow(); + + //! @brief Signals to ignore the next underflow or overflow condition. + void ignoreNextUnderOrOverflow() { lastAlignment = mach_absolute_time(); } + + //! @brief Moves read pointer one position forward + void advanceReadPtr() { readPtr = (readPtr + 1) % bufferSize; } + + //! @brief Moves read pointer forward or backward + void advanceReadPtr(int steps) { readPtr = (readPtr + bufferSize + steps) % bufferSize; } + + //! @brief Moves write pointer one position forward + void advanceWritePtr() { writePtr = (writePtr + 1) % bufferSize; } + + //! @brief Moves write pointer forward or backward + void advanceWritePtr(int steps) { writePtr = (writePtr + bufferSize + steps) % bufferSize; } + + //! @brief Returns number of stored samples in ringbuffer + unsigned samplesInBuffer() { return (writePtr + bufferSize - readPtr) % bufferSize; } + + //! @brief Returns remaining storage capacity of ringbuffer + unsigned bufferCapacity() { return (readPtr + bufferSize - writePtr) % bufferSize; } + + //! @brief Returns the fill level as a percentage value + double fillLevel() { return (double)samplesInBuffer() / (double)bufferSize; } + + /*! @brief Aligns the write pointer. + * @details This function puts the write pointer somewhat ahead of the + * read pointer. With a standard sample rate of 44100 Hz, 735 + * samples is 1/60 sec. + */ + const uint32_t samplesAhead = 8 * 735; + void alignWritePtr() { writePtr = (readPtr + samplesAhead) % bufferSize; } + +public: + + /*! @brief Executes SID until a certain cycle is reached + * @param cycle The target cycle + */ + void executeUntil(uint64_t targetCycle); + + /*! @brief Executes SID for a certain number of cycles + * @param cycles Number of cycles to execute + */ + void execute(uint64_t numCycles); + + + // + // Accessig device properties + // + +public: + + //! @brief Special peek function for the I/O memory range. + uint8_t peek(uint16_t addr); + + //! @brief Same as peek, but without side effects. + uint8_t spypeek(uint16_t addr); + + //! @brief Special poke function for the I/O memory range. + void poke(uint16_t addr, uint8_t value); +}; + +#endif diff --git a/C64/SID/SID_types.h b/C64/SID/SID_types.h new file mode 100755 index 00000000..cabb21ea --- /dev/null +++ b/C64/SID/SID_types.h @@ -0,0 +1,77 @@ +/*! + * @header SID_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SID_TYPES_H +#define SID_TYPES_H + +/*! @brief Sound chip models + * @details This enum reflects enum "chip_model" used by reSID. + */ +typedef enum { + MOS_6581, + MOS_8580 +} SIDModel; + +/*! @brief Sampling method + * @details This enum reflects enum "sampling_method" used by reSID. + */ +typedef enum { + SID_SAMPLE_FAST, + SID_SAMPLE_INTERPOLATE, + SID_SAMPLE_RESAMPLE, + SID_SAMPLE_RESAMPLE_FASTMEM +} SamplingMethod; + + + +/*! @brief Voice info + * @details Part of SIDInfo + */ +typedef struct { + uint8_t reg[7]; + uint16_t frequency; + uint16_t pulseWidth; + uint8_t waveform; + bool ringMod; + bool hardSync; + bool gateBit; + bool testBit; + uint8_t attackRate; + uint8_t decayRate; + uint8_t sustainRate; + uint8_t releaseRate; + bool filterEnableBit; +} VoiceInfo; + +/*! @brief SID info + * @details Used by SIDBridge::getInfo() to collect debug information + */ +typedef struct { + uint8_t volume; + uint16_t filterCutoff; + uint8_t filterResonance; + uint8_t filterModeBits; + uint8_t filterType; + uint8_t filterEnableBits; + uint8_t potX; + uint8_t potY; +} SIDInfo; + +#endif diff --git a/C64/SID/fastsid/FastSID.cpp b/C64/SID/fastsid/FastSID.cpp new file mode 100755 index 00000000..0bdb0b1c --- /dev/null +++ b/C64/SID/fastsid/FastSID.cpp @@ -0,0 +1,502 @@ +/* + * This file belongs to the FastSID implementation of VirtualC64, + * an adaption of the code used in VICE 3.1, the Versatile Commodore Emulator. + * + * Original code written by + * Teemu Rantanen + * Michael Schwendt + * Ettore Perazzoli + * + * Adapted for VirtualC64 by + * Dirk Hoffmann + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + +#include "C64.h" + +FastSID::FastSID() +{ + setDescription("FastSID"); + debug(3, " Creating FastSID at address %p...\n", this); + + // Register sub components + VirtualComponent *subcomponents[] = { &voice[0], &voice[1], &voice[2], NULL }; + registerSubComponents(subcomponents, sizeof(subcomponents)); + + // Register snapshot items + SnapshotItem items[] = { + { &sidreg, sizeof(sidreg), CLEAR_ON_RESET }, + { &speed1, sizeof(speed1), CLEAR_ON_RESET }, + { &model, sizeof(model), KEEP_ON_RESET }, + { &cpuFrequency, sizeof(cpuFrequency), KEEP_ON_RESET }, + { &sampleRate, sizeof(sampleRate), KEEP_ON_RESET }, + { &samplesPerCycle, sizeof(samplesPerCycle), CLEAR_ON_RESET }, + { &executedCycles, sizeof(executedCycles), CLEAR_ON_RESET }, + { &computedSamples, sizeof(computedSamples), CLEAR_ON_RESET }, + { &emulateFilter, sizeof(emulateFilter), KEEP_ON_RESET }, + { &latchedDataBus, sizeof(latchedDataBus), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + registerSnapshotItems(items, sizeof(items)); + + // Initialize wave and noise tables + FastVoice::initWaveTables(); + + // Initialize voices + voice[0].init(this, 0, &voice[3]); + voice[1].init(this, 1, &voice[0]); + voice[2].init(this, 2, &voice[1]); +} + +FastSID::~FastSID() +{ + debug(3, " Releasing FastSID...\n"); +} + +void +FastSID::reset() +{ + VirtualComponent::reset(); + init(sampleRate, cpuFrequency); +} + +void +FastSID::setClockFrequency(uint32_t frequency) +{ + cpuFrequency = frequency; + + // Recompute frequency dependent data structures + init(sampleRate, cpuFrequency); +} + +void +FastSID::dump() +{ + SIDInfo info = getInfo(); + + msg(" Chip model: %s\n", + (model == MOS_6581) ? "6581" : + (model == MOS_8580) ? "8580" : "???"); + msg(" Sampling rate: %d\n", sampleRate); + msg(" CPU frequency: %d\n", cpuFrequency); + msg("Emulate filter: %s\n", emulateFilter ? "yes" : "no"); + + uint8_t ft = info.filterType; + msg(" Volume: %d\n", info.volume); + msg(" Filter type: %s\n", + (ft == FASTSID_LOW_PASS) ? "LOW PASS" : + (ft == FASTSID_HIGH_PASS) ? "HIGH PASS" : + (ft == FASTSID_BAND_PASS) ? "BAND PASS" : "NONE"); + msg("Filter cut off: %d\n", info.filterCutoff); + msg("Filter resonance: %d\n", info.filterResonance); + msg("Filter enable bits: %X\n\n", info.filterEnableBits); + + for (unsigned i = 0; i < 3; i++) { + VoiceInfo vinfo = getVoiceInfo(i); + uint8_t wf = vinfo.waveform; + msg("Voice %d: Frequency: %d\n", i, vinfo.frequency); + msg(" Pulse width: %d\n", vinfo.pulseWidth); + msg(" Waveform: %s\n", + (wf == FASTSID_NOISE) ? "NOISE" : + (wf == FASTSID_PULSE) ? "PULSE" : + (wf == FASTSID_SAW) ? "SAW" : + (wf == FASTSID_TRIANGLE) ? "TRIANGLE" : "NONE"); + msg(" Ring modulation: %s\n", vinfo.ringMod ? "yes" : "no"); + msg(" Hard sync: %s\n", vinfo.hardSync ? "yes" : "no"); + msg(" Attack rate: %d\n", vinfo.attackRate); + msg(" Decay rate: %d\n", vinfo.decayRate); + msg(" Sustain rate: %d\n", vinfo.sustainRate); + msg(" Release rate: %d\n", vinfo.releaseRate); + } +} + +SIDInfo +FastSID::getInfo() +{ + SIDInfo info; + + info.volume = sidVolume(); + info.filterModeBits = sidreg[0x18] & 0xF0; + info.filterType = filterType(); + info.filterCutoff = filterCutoff(); + info.filterResonance = filterResonance(); + info.filterEnableBits = sidreg[0x17] & 0x0F; + + return info; +} + +VoiceInfo +FastSID::getVoiceInfo(unsigned i) +{ + VoiceInfo info; + for (unsigned j = 0; j < 7; j++) { + info.reg[j] = sidreg[7*i+j]; + } + info.frequency = voice[i].frequency(); + info.pulseWidth = voice[i].pulseWidth(); + info.waveform = voice[i].waveform(); + info.ringMod = voice[i].ringModBit(); + info.hardSync = voice[i].syncBit(); + info.gateBit = voice[i].gateBit(); + info.testBit = voice[i].testBit(); + info.attackRate = voice[i].attackRate(); + info.decayRate = voice[i].decayRate(); + info.sustainRate = voice[i].sustainRate(); + info.releaseRate = voice[i].releaseRate(); + + return info; +} + +void +FastSID::setModel(SIDModel m) +{ + model = m; + + // Switch wave tables according to new model + voice[0].updateWaveTablePtr(); + voice[1].updateWaveTablePtr(); + voice[2].updateWaveTablePtr(); +} + +void +FastSID::setSampleRate(uint32_t rate) +{ + debug("Setting sample rate to %d\n", rate); + + sampleRate = rate; + + // Recompute sample rate dependent data structures + init(sampleRate, cpuFrequency); +} + +//! Special peek function for the I/O memory range. +uint8_t +FastSID::peek(uint16_t addr) +{ + switch (addr) { + + case 0x19: // POTX + case 0x1A: // POTY + + return 0xFF; + + case 0x1B: // OSC 3/RANDOM + + // This register allows the microprocessor to read the + // upper 8 output bits of oscillator 3. + // debug("doosc = %d\n", voice[2].doosc()); + // return (uint8_t)(voice[2].doosc() >> 7); + return (uint8_t)rand(); + + case 0x1C: + + // This register allows the microprocessor to read the + // output of the voice 3 envelope generator. + // return (uint8_t)(voice[2].adsr >> 23); + return (uint8_t)rand(); + + default: + + return latchedDataBus; + } +} + +//! Special poke function for the I/O memory range. +void +FastSID::poke(uint16_t addr, uint8_t value) +{ + bool gateBitFlipped = false; + + switch (addr) { + + // Voice 1 registers + case 0x04: + gateBitFlipped = (sidreg[0x04] ^ value) & 1; + // Fallthrough + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x05: + case 0x06: + voice[0].updateInternals(gateBitFlipped); + break; + + // Voice 2 registers + case 0x0B: + gateBitFlipped = (sidreg[0x0B] ^ value) & 1; + // Fallthrough + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0C: + case 0x0D: + voice[1].updateInternals(gateBitFlipped); + break; + + // Voice 3 registers + case 0x12: + gateBitFlipped = (sidreg[0x12] ^ value) & 1; + // Fallthrough + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x13: + case 0x14: + voice[2].updateInternals(gateBitFlipped); + break; + + default: // Voice independent registers + updateInternals(); + } + + sidreg[addr] = value; + latchedDataBus = value; +} + +/*! @brief Execute SID + * @details Runs reSID for the specified amount of CPU cycles and writes + * the generated sound samples into the internal ring buffer. + */ +void +FastSID::execute(uint64_t cycles) +{ + int16_t buf[2049]; + int buflength = 2048; + + executedCycles += cycles; + + // Compute how many sound samples should have been computed + uint64_t shouldHave = (uint64_t)(executedCycles * samplesPerCycle); + + // How many sound samples are missing? + uint64_t numSamples = shouldHave - computedSamples; + computedSamples = shouldHave; + + // Do some consistency checking + if (numSamples > buflength) { + debug("Number of missing sound samples exceeds buffer size\n"); + numSamples = buflength; + } + + // Compute missing samples + for (unsigned i = 0; i < numSamples; i++) { + buf[i] = calculateSingleSample(); + } + + // Write samples into ringbuffer + bridge->writeData(buf, numSamples); +} + +void +FastSID::init(int sampleRate, int cycles_per_sec) +{ + uint32_t i; + + // Recompute sample/cycle ratio and reset counters + samplesPerCycle = (double)sampleRate / (double)cpuFrequency; + executedCycles = 0LL; + computedSamples = 0LL; + + // Table for internal ADSR counter step calculations + uint16_t adrtable[16] = { + 1, 4, 8, 12, 19, 28, 34, 40, 50, 125, 250, 400, 500, 1500, 2500, 4000 + }; + + speed1 = (cycles_per_sec << 8) / sampleRate; + for (i = 0; i < 16; i++) { + adrs[i] = 500 * 8 * speed1 / adrtable[i]; + sz[i] = 0x8888888 * i; + } + + initFilter(sampleRate); + updateInternals(); + voice[0].updateInternals(false); + voice[1].updateInternals(false); + voice[2].updateInternals(false); +} + +void +FastSID::initFilter(int sampleRate) +{ + uint16_t uk; + float rk; + long int si; + + const float filterRefFreq = 44100.0; + + float yMax = 1.0; + float yMin = (float)0.01; + float resDyMax = 1.0; + float resDyMin = 2.0; + float resDy = resDyMin; + + float yAdd, yTmp; + + float filterFs = 400.0; + float filterFm = 60.0; + float filterFt = (float)0.05; + float filterAmpl = 1.0; + + // Low pass lookup table + for (uk = 0, rk = 0; rk < 0x800; rk++, uk++) { + + float h = (float)((((exp(rk / 2048 * log(filterFs)) / filterFm) + filterFt) * filterRefFreq) / sampleRate); + if (h < yMin) { + h = yMin; + } + if (h > yMax) { + h = yMax; + } + lowPassParam[uk] = h; + } + + // Band pass lookup table + yMax = (float)0.22; + yMin = (float)0.002; + yAdd = (float)((yMax - yMin) / 2048.0); + yTmp = yMin; + for (uk = 0, rk = 0; rk < 0x800; rk++, uk++) { + bandPassParam[uk] = (yTmp * filterRefFreq) / sampleRate; + yTmp += yAdd; + } + + // Resonance lookup table + for (uk = 0; uk < 16; uk++) { + filterResTable[uk] = resDy; + resDy -= ((resDyMin - resDyMax ) / 15); + } + filterResTable[0] = resDyMin; + filterResTable[15] = resDyMax; + filterAmpl = emulateFilter ? 0.7 : 1.0; + + // Amplifier lookup table + for (uk = 0, si = 0; si < 256; si++, uk++) { + ampMod1x8[uk] = (signed char)((si - 0x80) * filterAmpl); + } +} + +void +FastSID::updateInternals() +{ + uint8_t type = filterType(); + uint8_t res = filterResonance(); + uint16_t cutoff = filterCutoff(); + + for (unsigned i = 0; i < 3; i++) { + + voice[i].setFilterType(type); + + if (type == FASTSID_BAND_PASS) { + voice[i].filterDy = bandPassParam[cutoff]; + } else { + voice[i].filterDy = lowPassParam[cutoff]; + } + voice[i].filterResDy = MAX(filterResTable[res] - voice[i].filterDy, 1.0); + } +} + +int16_t +FastSID::calculateSingleSample() +{ + uint32_t osc0, osc1, osc2; + FastVoice *v0 = &voice[0]; + FastVoice *v1 = &voice[1]; + FastVoice *v2 = &voice[2]; + bool sync0 = false; + bool sync1 = false; + bool sync2 = false; + + // Advance wavetable counters + v0->waveTableCounter += v0->step; + v1->waveTableCounter += v1->step; + v2->waveTableCounter += v2->step; + + // Check for counter overflows (waveform loops) + if (v0->waveTableCounter < v0->step) { + v0->lsfr = NSHIFT(v0->lsfr, 16); + sync1 = v1->syncBit(); + } + if (v1->waveTableCounter < v1->step) { + v1->lsfr = NSHIFT(v1->lsfr, 16); + sync2 = v2->syncBit(); + } + if (v2->waveTableCounter < v2->step) { + v2->lsfr = NSHIFT(v2->lsfr, 16); + sync0 = v0->syncBit(); + } + + // Perform hard sync + if (sync0) { + v0->lsfr = NSHIFT(v0->lsfr, v0->waveTableCounter >> 28); + v0->waveTableCounter = 0; + } + if (sync1) { + v1->lsfr = NSHIFT(v1->lsfr, v1->waveTableCounter >> 28); + v1->waveTableCounter = 0; + } + if (sync2) { + v2->lsfr = NSHIFT(v2->lsfr, v2->waveTableCounter >> 28); + v2->waveTableCounter = 0; + } + + // Advance ADSR counters + v0->adsr += v0->adsrInc; + v1->adsr += v1->adsrInc; + v2->adsr += v2->adsrInc; + + // Check if we need to perform state changes + if (v0->adsr + 0x80000000 < v0->adsrCmp + 0x80000000) { + v0->trigger_adsr(); + } + if (v1->adsr + 0x80000000 < v1->adsrCmp + 0x80000000) { + v1->trigger_adsr(); + } + if (v2->adsr + 0x80000000 < v2->adsrCmp + 0x80000000) { + v2->trigger_adsr(); + } + + // Oscillators + osc0 = (v0->adsr >> 16) * v0->doosc(); + osc1 = (v1->adsr >> 16) * v1->doosc(); + osc2 = (v2->adsr >> 16) * v2->doosc(); + + // Silence voice 3 if it is disconnected from the output + if (voiceThreeDisconnected()) { + osc2 = 0; + } + + // Apply filter + if (emulateFilter) { + v0->filterIO = ampMod1x8[(osc0 >> 22)]; + if (filterOn(0)) v0->applyFilter(); + osc0 = ((uint32_t)(v0->filterIO) + 0x80) << (7 + 15); + + v1->filterIO = ampMod1x8[(osc1 >> 22)]; + if (filterOn(1)) v1->applyFilter(); + osc1 = ((uint32_t)(v1->filterIO) + 0x80) << (7 + 15); + + v2->filterIO = ampMod1x8[(osc2 >> 22)]; + if (filterOn(2)) v2->applyFilter(); + osc2 = ((uint32_t)(v2->filterIO) + 0x80) << (7 + 15); + } + + return (int16_t)(((int32_t)((osc0 + osc1 + osc2) >> 20) - 0x600) * sidVolume() * 0.5); +} diff --git a/C64/SID/fastsid/FastSID.h b/C64/SID/fastsid/FastSID.h new file mode 100755 index 00000000..23302b0c --- /dev/null +++ b/C64/SID/fastsid/FastSID.h @@ -0,0 +1,223 @@ +/* + * This file belongs to the FastSID implementation of VirtualC64, + * an adaption of the code used in VICE 3.1, the Versatile Commodore Emulator. + * + * Original code written by + * Teemu Rantanen + * + * Adapted for VirtualC64 by + * Dirk Hoffmann + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + +#ifndef _FASTSID_INC +#define _FASTSID_INC + +#include "VirtualComponent.h" +#include "FastVoice.h" + + +//! The virtual sound interface device (SID) +/*! SID is the sound chip of the Commodore 64. + The SID chip occupied the memory mapped I/O space from address 0xD400 to 0xD7FF. +*/ +class FastSID : public VirtualComponent { + +public: + + //! Pointer to bridge object + class SIDBridge *bridge; + + //! @brief SID registers + uint8_t sidreg[32]; + + //! @brief Internal constant used for sample rate dependent calculations + uint32_t speed1; + +private: + + //! @brief The three SID voices + FastVoice voice[3]; + + //! @brief Chip model. + SIDModel model = MOS_6581; + + //! @brief Current CPU frequency + uint32_t cpuFrequency = PAL_CLOCK_FREQUENCY; + + //! @brief Sample rate (44.1 kHz per default) + uint32_t sampleRate = 44100; + + //! @brief Ratio between sample rate and cpu frequency + double samplesPerCycle; + + //! @brief Stores for how many cycles FastSID was executed so far + uint64_t executedCycles; + + //! @brief Stores how many sound samples were computed so far + uint64_t computedSamples; + + //! @brief Switches filter emulation on or off. + bool emulateFilter = true; + + //! @brief Last value on the data bus + uint8_t latchedDataBus; + +public: + + //! @brief ADSR counter step lookup table + int32_t adrs[16]; + + //! @brief Sustain comparison values loopup table + uint32_t sz[16]; + +private: + + //! @brief Low pass filter lookup table + /*! @details Needs to be updated when the sample rate changes + */ + float lowPassParam[0x800]; + + //! @brief Band pass filter lookup table + /*! @details Needs to be updated when the sample rate changes + */ + float bandPassParam[0x800]; + + //! @brief Filter resonance lookup table + /*! @details Needs to be updated when the sample rate changes + */ + float filterResTable[16]; + + //! @brief Amplifier lookup table + signed char ampMod1x8[256]; + +public: + + //! Constructor. + FastSID(); + + //! Destructor. + ~FastSID(); + + //! Method from VirtualComponent + void reset(); + + //! Sets the clock frequency + void setClockFrequency(uint32_t frequency); + + //! Dump internal state to console + void dump(); + + //! @brief Gathers all values that are displayed in the debugger + SIDInfo getInfo(); + + //! @brief Gathers all debug information for a specific voice + VoiceInfo getVoiceInfo(unsigned voice); + + //! Special peek function for the I/O memory range. + uint8_t peek(uint16_t addr); + + //! Special poke function for the I/O memory range. + void poke(uint16_t addr, uint8_t value); + + /*! @brief Execute SID + * @details Runs reSID for the specified amount of CPU cycles and writes + * the generated sound samples into the internal ring buffer. + */ + void execute(uint64_t cycles); + + //! @brief Computes a single sound sample + int16_t calculateSingleSample(); + + + // + //! @functiongroup Configuring the device + // + + //! Returns the chip model + SIDModel getModel() { return model; } + + //! Sets the chip model + void setModel(SIDModel m); + + //! Returns the clock frequency + uint32_t getClockFrequency() { return cpuFrequency; } + + //! Returns the sample rate + uint32_t getSampleRate() { return sampleRate; } + + //! Sets the sample rate + void setSampleRate(uint32_t rate); + + //! Returns true iff audio filters should be emulated. + bool getAudioFilter() { return emulateFilter; } + + //! Enable or disable audio filter emulation + void setAudioFilter(bool value) { emulateFilter = value; } + +private: + + //! @brief Initializes SID + void init(int sampleRate, int cycles_per_sec); + + //! @brief Initializes filter lookup tables + void initFilter(int sampleRate); + + + // + //! @functiongroup Accessing device properties + // + + //! @brief Returns the currently set SID volume + uint8_t sidVolume() { return sidreg[0x18] & 0x0F; } + + //! @brief Returns true iff voice 3 is disconnected from the audio output + /*! @details Setting voice 3 to bypass the filter (FILT3 = 0) and setting + * bit 7 in the Mod/Vol register to one prevents voice 3 from + * reaching the audio output. + */ + bool voiceThreeDisconnected() { return filterOff(2) && (sidreg[0x18] & 0x80); } + + // Filter related configuration items + + //! @brief Returns the filter cutoff frequency (11 bit value) + uint16_t filterCutoff() { return (sidreg[0x16] << 3) | (sidreg[0x15] & 0x07); } + + //! @brief Returns the filter resonance (4 bit value) + uint8_t filterResonance() { return sidreg[0x17] >> 4; } + + //! @brief Returns true iff the specified voice schould be filtered + bool filterOn(unsigned voice) { return GET_BIT(sidreg[0x17], voice) != 0; } + + //! @brief Returns true iff the specified voice schould not be filtered + bool filterOff(unsigned voice) { return GET_BIT(sidreg[0x17], voice) == 0; } + + //! @brief Returns true iff the external filter bit is set + bool filterExtBit() { return GET_BIT(sidreg[0x17], 7) != 0; } + + //! @brief Returns the currently set filter type + uint8_t filterType() { return sidreg[0x18] & 0x70; } + + + //! @brief Updates internal data structures + //! @details This method is called on each filter related register change + void updateInternals(); +}; + +#endif diff --git a/C64/SID/fastsid/FastVoice.cpp b/C64/SID/fastsid/FastVoice.cpp new file mode 100755 index 00000000..8af1b143 --- /dev/null +++ b/C64/SID/fastsid/FastVoice.cpp @@ -0,0 +1,440 @@ +/* + * This file belongs to the FastSID implementation of VirtualC64, + * an adaption of the code used in VICE 3.1, the Versatile Commodore Emulator. + * + * Original code written by + * Teemu Rantanen + * Michael Schwendt + * Ettore Perazzoli + * + * Adapted for VirtualC64 by + * Dirk Hoffmann + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + +#include "FastSID.h" +#include "waves.h" + +uint16_t FastVoice::wavetable10[2][4096]; +uint16_t FastVoice::wavetable20[2][4096]; +uint16_t FastVoice::wavetable30[2][4096]; +uint16_t FastVoice::wavetable40[2][8192]; +uint16_t FastVoice::wavetable50[2][8192]; +uint16_t FastVoice::wavetable60[2][8192]; +uint16_t FastVoice::wavetable70[2][8192]; +uint8_t FastVoice::noiseMSB[256]; +uint8_t FastVoice::noiseMID[256]; +uint8_t FastVoice::noiseLSB[256]; + + +// Table for pseudo-exponential ADSR calculations +static uint32_t exptable[6] = +{ + 0x30000000, 0x1c000000, 0x0e000000, 0x08000000, 0x04000000, 0x00000000 +}; + +FastVoice::FastVoice() +{ + setDescription("Voice"); + debug(3, " Creating Voice at address %p...\n", this); + + // Register snapshot items + SnapshotItem items[] = { + { &waveTableOffset, sizeof(waveTableOffset), CLEAR_ON_RESET }, + { &waveTableCounter, sizeof(waveTableCounter), CLEAR_ON_RESET }, + { &step, sizeof(step), CLEAR_ON_RESET }, + { &ringmod, sizeof(ringmod), CLEAR_ON_RESET }, + { &adsrm, sizeof(adsrm), CLEAR_ON_RESET }, + { &adsr, sizeof(adsr), CLEAR_ON_RESET }, + { &adsrInc, sizeof(adsrInc), CLEAR_ON_RESET }, + { &adsrCmp, sizeof(adsrCmp), CLEAR_ON_RESET }, + { &lsfr, sizeof(lsfr), CLEAR_ON_RESET }, + { &filterIO, sizeof(filterIO), CLEAR_ON_RESET }, + { &filterType, sizeof(filterType), CLEAR_ON_RESET }, + { &filterLow, sizeof(filterLow), CLEAR_ON_RESET }, + { &filterRef, sizeof(filterRef), CLEAR_ON_RESET }, + { &filterDy, sizeof(filterDy), CLEAR_ON_RESET }, + { &filterResDy, sizeof(filterResDy), CLEAR_ON_RESET }, + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +FastVoice::~FastVoice() +{ + debug(3, " Releasing FastVoice %d...\n", nr); +} + +void +FastVoice::reset() +{ + VirtualComponent::reset(); + updateWaveTablePtr(); + lsfr = NSEED; +} + +void +FastVoice::initWaveTables() +{ + // Most tables are the same for SID6581 and SID8580, so let's initialize both. + for (unsigned m = 0; m < 2; m++) { + for (unsigned i = 0; i < 4096; i++) { + wavetable10[m][i] = (uint16_t)(i < 2048 ? i << 4 : 0xffff - (i << 4)); + wavetable20[m][i] = (uint16_t)(i << 3); + wavetable30[m][i] = waveform30_8580[i] << 7; + wavetable40[m][i + 4096] = 0x7fff; + wavetable50[m][i + 4096] = waveform50_6581[i >> 3] << 7; + wavetable60[m][i + 4096] = 0; + wavetable70[m][i + 4096] = 0; + } + } + + // Modify some tables for SID8580 + for (unsigned i = 0; i < 4096; i++) { + wavetable50[1][i + 4096] = waveform50_8580[i] << 7; + wavetable60[1][i + 4096] = waveform60_8580[i] << 7; + wavetable70[1][i + 4096] = waveform70_8580[i] << 7; + } + + // Noise tables are the same for both SID models + for (unsigned i = 0; i < 256; i++) { + noiseLSB[i] = (uint8_t)((((i >> (7 - 2)) & 0x04) | ((i >> (4 - 1)) & 0x02) + | ((i >> (2 - 0)) & 0x01))); + noiseMID[i] = (uint8_t)((((i >> (13 - 8 - 4)) & 0x10) + | ((i << (3 - (11 - 8))) & 0x08))); + noiseMSB[i] = (uint8_t)((((i << (7 - (22 - 16))) & 0x80) + | ((i << (6 - (20 - 16))) & 0x40) + | ((i << (5 - (16 - 16))) & 0x20))); + } +} + +void +FastVoice::init(FastSID *owner, unsigned voiceNr, FastVoice *prevVoice) +{ + assert(prevVoice != NULL); + + nr = voiceNr; + fastsid = owner; + prev = prevVoice; + sidreg = owner->sidreg + (voiceNr * 7); +} + +void +FastVoice::updateWaveTablePtr() +{ + SIDModel chipModel = fastsid->getModel(); + assert(chipModel == MOS_6581 || chipModel == MOS_8580); + + unsigned offset; + switch (waveform()) { + + case FASTSID_TRIANGLE: + wavetable = wavetable10[chipModel]; + break; + + case FASTSID_SAW: + wavetable = wavetable20[chipModel]; + break; + + case FASTSID_SAW | FASTSID_TRIANGLE: + wavetable = wavetable30[chipModel]; + break; + + case FASTSID_PULSE: + offset = testBit() ? 0 : pulseWidth(); + wavetable = wavetable40[chipModel] + (4096 - offset); + break; + + case FASTSID_PULSE | FASTSID_TRIANGLE: + offset = 4096 - pulseWidth(); + wavetable = wavetable50[chipModel] + offset; + break; + + case FASTSID_PULSE | FASTSID_SAW: + offset = 4096 - pulseWidth(); + wavetable = wavetable60[chipModel] + offset; + break; + + case FASTSID_PULSE | FASTSID_SAW | FASTSID_TRIANGLE: + offset = 4096 - pulseWidth(); + wavetable = wavetable70[chipModel] + offset; + break; + + default: + wavetable = NULL; + } +} + +void +FastVoice::updateInternals(bool gateBitFlipped) +{ + updateWaveTablePtr(); + + if (testBit()) { + waveTableCounter = 0; + step = 0; + lsfr = NSEED; + } else { + step = fastsid->speed1 * frequency(); + } + + unsigned offset; + switch (waveform()) { + + case 0: + ringmod = false; + break; + + case FASTSID_TRIANGLE: + waveTableOffset = 0; + ringmod = ringModBit(); + break; + + case FASTSID_SAW: + waveTableOffset = 0; + ringmod = false; + break; + + case FASTSID_SAW | FASTSID_TRIANGLE: + waveTableOffset = 0; + ringmod = ringModBit(); + break; + + case FASTSID_PULSE: + offset = testBit() ? 0 : pulseWidth(); + waveTableOffset = 0; + ringmod = false; + break; + + case FASTSID_PULSE | FASTSID_TRIANGLE: + offset = 4096 - pulseWidth(); + waveTableOffset = offset << 20; + ringmod = ringModBit(); + break; + + case FASTSID_PULSE | FASTSID_SAW: + offset = 4096 - pulseWidth(); + waveTableOffset = offset << 20; + ringmod = false; + break; + + case FASTSID_PULSE | FASTSID_SAW | FASTSID_TRIANGLE: + offset = 4096 - pulseWidth(); + waveTableOffset = offset << 20; + ringmod = ringModBit(); + break; + + case FASTSID_NOISE: + ringmod = false; + break; + + default: + lsfr = 0; + ringmod = false; + } + + switch (adsrm) { + + case FASTSID_ATTACK: + case FASTSID_DECAY: + case FASTSID_SUSTAIN: + + if (gateBit()) { + + // Initiate attack phase + set_adsr((uint8_t)(gateBitFlipped ? FASTSID_ATTACK : adsrm)); + } else { + + // Proceed immediately to release phase + set_adsr(FASTSID_RELEASE); + } + break; + + case FASTSID_RELEASE: + case FASTSID_IDLE: + + if (gateBit()) { + set_adsr(FASTSID_ATTACK); + } else { + set_adsr(adsrm); + } + break; + } +} + +void +FastVoice::setFilterType(uint8_t type) +{ + if (filterType == type) + return; + + filterType = type; + filterLow = 0.0; + filterRef = 0.0; +} + +void +FastVoice::set_adsr(uint8_t phase) +{ + int i; + + adsrm = phase; + + switch (phase) { + + case FASTSID_ATTACK: + adsrInc = fastsid->adrs[attackRate()]; + adsrCmp = 0; + return; + + case FASTSID_DECAY: + if (adsr <= fastsid->sz[sustainRate()]) { + set_adsr(FASTSID_SUSTAIN); + } else { + for (i = 0; adsr < exptable[i]; i++) {} + adsrInc = -fastsid->adrs[decayRate()] >> i; + adsrCmp = fastsid->sz[sustainRate()]; + if (exptable[i] > adsrCmp) { + adsrCmp = exptable[i]; + } + } + return; + + case FASTSID_SUSTAIN: + if (adsr > fastsid->sz[sustainRate()]) { + set_adsr(FASTSID_DECAY); + } else { + adsrInc = 0; + adsrCmp = 0; + } + return; + + case FASTSID_RELEASE: + if (!adsr) { + set_adsr(FASTSID_IDLE); + } else { + for (i = 0; adsr < exptable[i]; i++) {} + adsrInc = -fastsid->adrs[releaseRate()] >> i; + adsrCmp = exptable[i]; + } + return; + + default: + assert(phase == FASTSID_IDLE); + adsrInc = 0; + adsrCmp = 0; + return; + } +} + +void +FastVoice::trigger_adsr() +{ + switch (adsrm) { + + case FASTSID_ATTACK: + adsr = 0x7fffffff; + set_adsr(FASTSID_DECAY); + break; + + case FASTSID_DECAY: + case FASTSID_RELEASE: + if (adsr >= 0x80000000) { + adsr = 0; + } + set_adsr(adsrm); + break; + } +} + +uint32_t +FastVoice::doosc() +{ + if (waveform() == FASTSID_NOISE) { + return ((uint32_t)NVALUE(NSHIFT(lsfr, waveTableCounter >> 28))) << 7; + } + + if (wavetable) { + uint32_t index = (waveTableCounter + waveTableOffset) >> 20; /* 12 bit value */ + if (ringmod) { + if ((prev->waveTableCounter >> 31) == 1) { + return wavetable[index] ^ 0x7FFF; + } + } + return wavetable[index]; + } + + return 0; +} + +void +FastVoice::applyFilter() +{ + float sample, sample2; + + if (filterType == 0) { + filterIO = 0; + return; + } + + if (filterType == FASTSID_BAND_PASS) { + filterLow += filterRef * filterDy; + filterRef += (filterIO - filterLow - (filterRef * filterResDy)) * filterDy; + filterIO = (signed char)(filterRef - filterLow / 4); + return; + } + + if (filterType == FASTSID_HIGH_PASS) { + filterLow += filterRef * filterDy * 0.1; + filterRef += (filterIO - filterLow - (filterRef * filterResDy)) * filterDy; + sample = filterRef - (filterIO / 8); + sample = MAX(sample, -128); + sample = MIN(sample, 127); + filterIO = (signed char)sample; + return; + } + + filterLow += filterRef * filterDy; + sample = filterIO; + sample2 = sample - filterLow; + int tmp = (int)sample2; + sample2 -= filterRef * filterResDy; + filterRef += sample2 * filterDy; + + switch (filterType) { + + case FASTSID_LOW_PASS: + case FASTSID_BAND_PASS | FASTSID_LOW_PASS: + filterIO = (signed char)filterLow; + break; + + case FASTSID_HIGH_PASS | FASTSID_LOW_PASS: + case FASTSID_HIGH_PASS | FASTSID_BAND_PASS | FASTSID_LOW_PASS: + filterIO = (signed char)((int)(sample) - (tmp >> 1)); + break; + + case FASTSID_HIGH_PASS | FASTSID_BAND_PASS: + filterIO = (signed char)tmp; + break; + + default: + assert(false); + filterIO = 0; + } +} diff --git a/C64/SID/fastsid/FastVoice.h b/C64/SID/fastsid/FastVoice.h new file mode 100755 index 00000000..e7e16617 --- /dev/null +++ b/C64/SID/fastsid/FastVoice.h @@ -0,0 +1,295 @@ +/* + * This file belongs to the FastSID implementation of VirtualC64, + * an adaption of the code used in VICE 3.1, the Versatile Commodore Emulator. + * + * Original code written by + * Teemu Rantanen + * + * Adapted for VirtualC64 by + * Dirk Hoffmann + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + + +#ifndef FASTSIDVOICE_H +#define FASTSIDVOICE_H + +#include "VirtualComponent.h" + +// Waveform types +#define FASTSID_TRIANGLE 0x10 +#define FASTSID_SAW 0x20 +#define FASTSID_PULSE 0x40 +#define FASTSID_NOISE 0x80 + +// Filter types +#define FASTSID_HIGH_PASS 0x40 +#define FASTSID_BAND_PASS 0x20 +#define FASTSID_LOW_PASS 0x10 + +// ADSR state (Attack, Decay, Sustain, Release) +#define FASTSID_ATTACK 0 +#define FASTSID_DECAY 1 +#define FASTSID_SUSTAIN 2 +#define FASTSID_RELEASE 3 +#define FASTSID_IDLE 4 + +// Noise magic + +// The noise waveform of the SID is generated by a simple 23 bit LFSR. +// On shifting, bit 0 is filled with bit 22 EXOR bit 17. +#define NSHIFT(v, n) \ +(((v) << (n)) \ +| ((((v) >> (23 - (n))) ^ (v >> (18 - (n)))) & ((1 << (n)) - 1))) + +#define NVALUE(v) \ +(noiseLSB[v & 0xff] | noiseMID[(v >> 8) & 0xff] \ +| noiseMSB[(v >> 16) & 0xff]) + +#define NSEED 0x7ffff8 + + +class FastVoice : public VirtualComponent { + + friend class FastSID; + +private: + + //! @brief Wave tables + //! @details The first index determines the chip model (0 = old, 1 = new). + static uint16_t wavetable10[2][4096]; + static uint16_t wavetable20[2][4096]; + static uint16_t wavetable30[2][4096]; + static uint16_t wavetable40[2][8192]; + static uint16_t wavetable50[2][8192]; + static uint16_t wavetable60[2][8192]; + static uint16_t wavetable70[2][8192]; + + //! @brief Noise tables + static uint8_t noiseMSB[256]; + static uint8_t noiseMID[256]; + static uint8_t noiseLSB[256]; + + //! @brief The SID voice which is represented by this object (1,2, or 3) + uint8_t nr; + + //! @brief Pointer to parent SID object + class FastSID *fastsid; + + //! @brief Pointer to previous voice + FastVoice *prev; + + //! @brief Pointer to SID registers controlling this voice + uint8_t *sidreg; + + + // + // Wave tables + // + + //! @brief Pointer to the active wavetable + uint16_t *wavetable; + + //! @brief Wavetable offset + /*! @details This 32-bit offset is added to the counter before + * referencing the wavetable. It is used when other + * waveforms are combined with pulse + */ + uint32_t waveTableOffset; + + //! @brief Counter value + uint32_t waveTableCounter; + + //! @brief Counter steps + /*! @details After each sample, the counter is incremented by this amount. + */ + uint32_t step; + + //! @brief Set to true if the oscillator should ring modulate + bool ringmod; + + // + // Waveform generator + // + + //! @brief Current envelope phase (ATTACK, DECAY, SUSTAIN, RELEASE, or IDLE) + uint8_t adsrm; + + //! @brief 31-bit adsr counter + uint32_t adsr; + + //! @brief adsr counter step per sample + int32_t adsrInc; + + //! @brief adsr sustain level comparision value + uint32_t adsrCmp; + + // + // Noise generator + // + + //! @brief Noise shift register + /*! @details The Noise waveform is created using a 23-bit pseudo-random + * sequence generator (Linear Feedback Shift Register, LSFR) + */ + uint32_t lsfr; + + // + // Filter + // + + //! @brief Filter related variables + signed char filterIO; + uint8_t filterType; + float filterLow; + float filterRef; + float filterDy; + float filterResDy; + +public: + + //! @brief Constructor + FastVoice(); + + //! @brief Destructor + ~FastVoice(); + + //! @functiongroup Methods from VirtualComponent + void reset(); + void didLoadFromBuffer(uint8_t **buffer) { updateWaveTablePtr(); } + + //! @brief Initializes the wave tables + /*! @details Needs to be called once prior to using this class + */ + static void initWaveTables(); + + //! @brief Initialize + //! @details Needs to be called once for each voice object + void init(FastSID *owner, unsigned voiceNr, FastVoice *prevVoice); + + //! @brief Updates the wavetable pointer + void updateWaveTablePtr(); + + //! @brief Updates internal data structures + //! @details This method is called on each voice related register change + void updateInternals(bool gateBitFlipped); + + //! @brief Sets the current filter type + void setFilterType(uint8_t type); + + //! @brief Change ADSR state and all related variables + void set_adsr(uint8_t fm); + + //! @brief ADSR counter triggered state change + void trigger_adsr(); + + // 15-bit oscillator value + uint32_t doosc(); + + //! @brief Apply filter effect + void applyFilter(); + + // + // Querying configuration items + // + + //! @brief Returns the currently set oscillator frequency + uint16_t frequency() { return HI_LO(sidreg[0x01], sidreg[0x00]); } + + //! @brief Returns the pulse width of the pulse waveform + /*! @details The pulse width is a 12-bit number which linearly controls + * the pulse width (duty cycle) of the pulse waveform. + */ + uint16_t pulseWidth() { return ((sidreg[3] & 0x0F) << 8) | sidreg[0x02]; } + + //! @brief Returns the GATE bit for this voice + /*! @details The gate bit controls the Envelope Generator. When this + * bit is set to a one, the Envelope Generator is Gated + * (triggered) and the attack/decay/sustain cycle is initiated. + * When the bit is reset to a zero, the release cycle begins. + */ + bool gateBit() { return sidreg[0x04] & 0x01; } + + //! @brief Returns the SYNC bit for this voice + /*! @details If this bit is set, hard sync effects are produced. + * Hard sync is where one waveform plays at its own frequency, + * but gets reset back to its start every time the second waveform + * loops. It is responsible for the rising modulating sound in + * Ben Daglish's Wilderness music from The Last Ninja and the + * ludicrous intro noise in Martin Galway's Roland's Rat Race music. + */ + bool syncBit() { return (sidreg[0x04] & 0x02) != 0; } + + //! @brief Returns the RING MOD bit of the control register + /*! @details The RING MOD bit, when set to a one, replaces the + * Triangle waveform output of Oscillator 1 with a + * “Ring Modulated” combination of Oscillators 1 and 3. + */ + bool ringModBit() { return (sidreg[0x04] & 0x04) != 0; } + + //! @brief Returns the TEST bit of the control register + /*! @details The TEST bit, when set to a one, resets and + * locks Oscillator 1 at zero until the TEST bit + * is cleared. The Noise waveform output of + * Oscillator 1 is also reset and the Pulse waveform + * output is held at a DC level. + */ + bool testBit() { return (sidreg[0x04] & 0x08) != 0; } + + //! @brief Returns the waveform bits of the control register + uint8_t waveform() { return sidreg[0x04] & 0xF0; } + + //! @brief Returns the attack rate for the envelope generator + /*! @details The attack rate is a 4 bit value which determines how rapidly + * the output of the voice rises from zero to peak amplitude when + * the envelope generator is gated. + */ + uint8_t attackRate() { return sidreg[0x05] >> 4; } + + //! @brief Returns the decay rate for the envelope generator + /*! @details The decay cycle follows the attack cycle and the decay rate + * determines how rapidly the output falls from the peak amplitude + * to the selected sustain level. + */ + uint8_t decayRate() { return sidreg[0x05] & 0x0F; } + + //! @brief Returns the decay rate for the envelope generator + /*! @details The sustain cycle follows the decay cycle and the output of + * the voice will remain at the selected sustain amplitude as + * long as the gate bit remains set. The sustain levels range + * from zero to peak amplitude in 16 linear steps, with a sustain + * value of 0 selecting zero amplitude and a sustain value of 15 + * selecting the peak amplitude. + * A sustain value of 8 would cause the voice to sustain at an + * amplitude one-half the peak amplitude reached by the attack + * cycle. + */ + uint8_t sustainRate() { return sidreg[0x06] >> 4; } + + //! @brief Returns the release rate for the envelope generator + /*! @details The release cycle follows the sustain cycle when the Gate bit is + * reset to zero. At this time, the output of Voice 1 will fall + * from the sustain amplitude to zero amplitude at the selected + * release rate. The 16 release rates are identical to the decay + * rates. + */ + uint8_t releaseRate() { return sidreg[0x06] & 0x0F; } +}; + +#endif diff --git a/C64/SID/fastsid/waves.h b/C64/SID/fastsid/waves.h new file mode 100755 index 00000000..ee7dd32e --- /dev/null +++ b/C64/SID/fastsid/waves.h @@ -0,0 +1,1460 @@ +/* + * This file belongs to the FastSID implementation of VirtualC64, + * an adaption of the code used in VICE 3.1, the Versatile Commodore Emulator. + * + * Original code written by + * Michael Schwendt + * + * Adapted for VirtualC64 by + * Dirk Hoffmann + */ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + */ + +#ifndef WAVES_H +#define WAVES_H + +/* + * MOS-6581 R4 + */ + +static uint8_t waveform50_6581[] = +{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x80, 0x80, 0xb7, 0x0, 0x80, 0x80, 0xbb, + 0x80, 0xbd, 0xbe, 0xbf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x80, 0x80, + 0xd8, 0x0, 0x0, 0x0, 0x80, 0x80, 0xe0, 0x0, 0x80, 0x80, 0xe0, 0x80, 0xe0, 0xe0, 0xef, 0x0, + 0x80, 0xc0, 0xf0, 0xc0, 0xf0, 0xf0, 0xf7, 0xc0, 0xf0, 0xf8, 0xfb, 0xf8, 0xfd, 0xfe, 0xfd, 0xf8, + 0xfb, 0xf8, 0xf0, 0xc0, 0xf7, 0xf0, 0xf0, 0xe0, 0xf3, 0xe0, 0xe0, 0x80, 0xef, 0xee, 0xed, 0xe0, + 0xeb, 0xe0, 0xc0, 0x80, 0xe7, 0xc0, 0xc0, 0x0, 0xc0, 0x0, 0xdf, 0xc0, 0xc0, 0x80, 0xc0, 0x80, + 0x0, 0x0, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x78, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 +}; + +/* MOS-8580 R5 waveforms $30,$50,$60,$70 + * Created with Deadman's Raw Data to C Header converter + */ + +static uint8_t waveform30_8580[4096] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x07, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x07, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3e, 0x3e, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x07, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x1f, 0x1f, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x81, 0x83, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x87, 0x87, 0x87, 0x8f, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe3, 0xe3, 0xe0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf1, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; + +static uint8_t waveform50_8580[4096 + 4096] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x3f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x6f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0x60, 0x60, 0x77, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x60, + 0x00, 0x40, 0x40, 0x60, 0x40, 0x70, 0x70, 0x7b, 0x00, 0x00, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x70, 0x40, 0x60, 0x60, 0x70, 0x60, 0x70, 0x78, 0x7c, + 0x60, 0x70, 0x70, 0x78, 0x70, 0x78, 0x7c, 0x7e, 0x78, 0x7c, 0x7c, 0x7f, + 0x7e, 0x7f, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x9f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xb7, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xa0, + 0x80, 0xa0, 0xb0, 0xbb, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xa0, + 0x80, 0x80, 0x80, 0xb0, 0xa0, 0xb0, 0xb0, 0xbc, 0x80, 0xa0, 0xa0, 0xb0, + 0xb0, 0xb8, 0xb8, 0xbe, 0xb8, 0xbc, 0xbc, 0xbf, 0xbc, 0xbf, 0xbf, 0xbf, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xcf, 0x00, 0x00, 0x80, 0x80, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd7, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xd8, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xd0, 0xdc, + 0xc0, 0xc0, 0xc0, 0xd0, 0xd0, 0xd8, 0xd8, 0xde, 0xd0, 0xd8, 0xdc, 0xdf, + 0xdc, 0xdf, 0xdf, 0xdf, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x80, + 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0x80, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, + 0xc0, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xe7, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xc0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe8, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xec, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe8, 0xee, 0xe0, 0xe8, 0xec, 0xee, 0xec, 0xef, 0xef, 0xef, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xc0, 0xc0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, + 0xe0, 0xe0, 0xe0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf4, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf4, 0xf0, 0xf0, 0xf0, 0xf6, + 0xf4, 0xf7, 0xf7, 0xf7, 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf8, 0xf8, 0xf8, 0xf0, 0xf0, 0xf0, 0xf8, + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xfa, 0xf8, 0xfb, 0xfb, 0xfb, + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xfc, 0xfc, 0xfc, 0xf8, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfc, 0xfc, 0xfc, + 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xf8, 0xfc, 0xfc, 0xfc, 0xf8, + 0xf8, 0xf8, 0xf8, 0xf8, 0xfb, 0xfb, 0xfb, 0xf8, 0xfa, 0xf8, 0xf8, 0xf8, + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf0, 0xf0, 0xf0, 0xf8, 0xf8, 0xf8, 0xf0, + 0xf8, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, + 0xf7, 0xf7, 0xf7, 0xf4, 0xf6, 0xf0, 0xf0, 0xf0, 0xf4, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf4, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, + 0xf0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, 0xf0, 0xf0, 0xe0, + 0xf0, 0xe0, 0xe0, 0xe0, 0xf0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xe0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xef, 0xef, 0xef, 0xec, 0xee, 0xec, 0xe8, 0xe0, + 0xee, 0xe8, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xec, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, + 0xe8, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xe0, 0xe0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0xe0, 0xe0, 0xc0, + 0xe0, 0xc0, 0xc0, 0xc0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, + 0xc0, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, + 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0xdf, 0xdf, 0xdf, 0xdc, 0xdf, 0xdc, 0xdc, 0xd0, 0xde, 0xd8, 0xd8, 0xd0, + 0xd0, 0xc0, 0xc0, 0xc0, 0xdc, 0xd0, 0xd0, 0xc0, 0xd0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd8, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0xd7, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0xcf, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0xbf, 0xbf, 0xbc, 0xbf, 0xbc, 0xbc, 0xb8, + 0xbe, 0xb8, 0xb8, 0xb0, 0xb8, 0xa0, 0xa0, 0x80, 0xbc, 0xb0, 0xb0, 0xa0, + 0xb0, 0x80, 0x80, 0x80, 0xa0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0xbb, 0xb0, 0xa0, 0x80, 0xa0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xb7, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xaf, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7c, 0x7c, 0x78, 0x7e, 0x7c, 0x78, 0x70, + 0x78, 0x70, 0x70, 0x60, 0x7c, 0x78, 0x70, 0x60, 0x70, 0x60, 0x60, 0x40, + 0x70, 0x60, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x7b, 0x70, 0x70, 0x40, + 0x60, 0x40, 0x40, 0x00, 0x60, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x77, 0x60, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x40, 0x40, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x1c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +static uint8_t waveform60_8580[4096 + 4096] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, + 0x00, 0x3f, 0x3f, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x5f, 0x5f, 0x5f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x6f, + 0x40, 0x6f, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x40, 0x77, 0x00, 0x40, 0x40, 0x77, 0x40, 0x77, 0x77, 0x77, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x79, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x78, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x78, 0x40, 0x60, 0x60, 0x78, + 0x60, 0x7b, 0x7b, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x60, + 0x00, 0x40, 0x40, 0x60, 0x40, 0x60, 0x60, 0x7c, 0x40, 0x40, 0x40, 0x60, + 0x40, 0x70, 0x70, 0x7c, 0x60, 0x70, 0x70, 0x7c, 0x70, 0x7c, 0x7d, 0x7d, + 0x40, 0x60, 0x60, 0x70, 0x60, 0x70, 0x78, 0x7e, 0x70, 0x78, 0x78, 0x7e, + 0x78, 0x7e, 0x7e, 0x7e, 0x78, 0x7c, 0x7c, 0x7f, 0x7c, 0x7f, 0x7f, 0x7f, + 0x7c, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x9d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x9e, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x9f, 0x80, 0x80, 0x80, 0x9f, + 0x80, 0x9f, 0x9f, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x87, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x80, 0x80, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x85, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, + 0x80, 0x80, 0x80, 0xae, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xaf, + 0x80, 0x80, 0x80, 0xaf, 0x80, 0xaf, 0xaf, 0xaf, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xa3, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xa1, 0x00, 0x00, 0x80, 0x80, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xb0, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xb0, 0x80, 0x80, 0x80, 0xb5, + 0x80, 0xb7, 0xb7, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xb1, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0xb8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xb8, + 0x80, 0x80, 0x80, 0xb8, 0x80, 0xb8, 0xbb, 0xbb, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xbc, + 0x80, 0x80, 0x80, 0xa0, 0x80, 0xa0, 0xa0, 0xbc, 0x80, 0xa0, 0xa0, 0xbc, + 0xb0, 0xbc, 0xbc, 0xbd, 0x80, 0x80, 0x80, 0xb0, 0x80, 0xb0, 0xb0, 0xbc, + 0xa0, 0xb0, 0xb0, 0xbc, 0xb8, 0xbc, 0xbe, 0xbe, 0xb0, 0xb8, 0xb8, 0xbe, + 0xbc, 0xbe, 0xbf, 0xbf, 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc7, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc3, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc5, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xce, + 0x80, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xcf, 0x80, 0xc0, 0xc0, 0xcf, + 0xc0, 0xcf, 0xcf, 0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0xc3, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0xc0, 0xc0, 0xc1, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xc0, 0xd5, 0xd7, 0xd7, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd1, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xc0, 0xc0, 0xc0, 0xd8, + 0xc0, 0xd8, 0xd9, 0xdb, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd8, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xdc, 0xc0, 0xc0, 0xc0, 0xdc, 0xd0, 0xdc, 0xdc, 0xdd, + 0xc0, 0xc0, 0xc0, 0xd0, 0xc0, 0xd0, 0xd0, 0xdc, 0xc0, 0xd0, 0xd0, 0xdc, + 0xd0, 0xdc, 0xde, 0xde, 0xd0, 0xd8, 0xd8, 0xde, 0xd8, 0xde, 0xde, 0xdf, + 0xdc, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe3, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, 0xc0, + 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe1, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe1, 0xe7, 0xe7, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe1, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xc0, 0xc0, 0xc0, 0xe0, + 0xc0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe8, 0xe8, 0xeb, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe8, + 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe8, 0xe0, 0xe0, 0xe0, 0xec, + 0xe0, 0xec, 0xec, 0xed, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xec, + 0xe0, 0xe0, 0xe0, 0xec, 0xe0, 0xec, 0xec, 0xee, 0xe0, 0xe8, 0xe8, 0xee, + 0xe8, 0xee, 0xee, 0xef, 0xec, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, + 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, 0xc0, 0xc0, 0xc0, 0xe0, + 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, 0xe0, 0xe0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf3, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, + 0xe0, 0xe0, 0xe0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, 0xf0, + 0xe0, 0xf0, 0xf0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf4, 0xf5, + 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf4, 0xf0, 0xf0, 0xf0, 0xf4, + 0xf0, 0xf4, 0xf4, 0xf6, 0xf0, 0xf0, 0xf0, 0xf4, 0xf0, 0xf4, 0xf6, 0xf7, + 0xf4, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xe0, 0xe0, 0xe0, 0xf0, + 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf8, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf0, 0xf8, 0xf8, + 0xf8, 0xf8, 0xf8, 0xf9, 0xf0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf8, 0xf8, 0xf8, + 0xf0, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xfa, 0xf8, 0xf8, 0xf8, 0xf8, + 0xf8, 0xf8, 0xfa, 0xfb, 0xf8, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xfc, 0xf8, 0xf8, 0xf8, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xf8, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfd, + 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfe, + 0xfc, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; + +static uint8_t waveform70_8580[4096 + 4096] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x20, 0x70, 0x70, 0x7c, 0x7c, 0x7e, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x3f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x9f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xcf, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe3, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, + 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, 0xf0, 0xf0, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0, 0xe0, 0xe0, 0xe0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf8, 0xf8, 0xf0, 0xf0, 0xf0, 0xf8, + 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xfc, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, + 0xff, 0xff, 0xff, 0xff +}; + + +#endif diff --git a/C64/SID/resid/dac.cc b/C64/SID/resid/dac.cc new file mode 100755 index 00000000..bde75f40 --- /dev/null +++ b/C64/SID/resid/dac.cc @@ -0,0 +1,136 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#define RESID_DAC_CC + +#ifdef _M_ARM +#undef _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE +#define _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE 1 +#endif + +#include "dac.h" +#include + +#ifdef __IBMC__ +#include +#define INFINITY _INF +#endif + +#ifndef INFINITY +union MSVC_EVIL_FLOAT_HACK +{ + unsigned char Bytes[4]; + float Value; +}; +static union MSVC_EVIL_FLOAT_HACK INFINITY_HACK = {{0x00, 0x00, 0x80, 0x7F}}; +#define INFINITY (INFINITY_HACK.Value) +#endif + +namespace reSID +{ + +// ---------------------------------------------------------------------------- +// Calculation of lookup tables for SID DACs. +// ---------------------------------------------------------------------------- + +// The SID DACs are built up as follows: +// +// n n-1 2 1 0 VGND +// | | | | | | Termination +// 2R 2R 2R 2R 2R 2R only for +// | | | | | | MOS 8580 +// Vo --R---R--...--R---R-- --- +// +// +// All MOS 6581 DACs are missing a termination resistor at bit 0. This causes +// pronounced errors for the lower 4 - 5 bits (e.g. the output for bit 0 is +// actually equal to the output for bit 1), resulting in DAC discontinuities +// for the lower bits. +// In addition to this, the 6581 DACs exhibit further severe discontinuities +// for higher bits, which may be explained by a less than perfect match between +// the R and 2R resistors, or by output impedance in the NMOS transistors +// providing the bit voltages. A good approximation of the actual DAC output is +// achieved for 2R/R ~ 2.20. +// +// The MOS 8580 DACs, on the other hand, do not exhibit any discontinuities. +// These DACs include the correct termination resistor, and also seem to have +// very accurately matched R and 2R resistors (2R/R = 2.00). + +void build_dac_table(unsigned short* dac, int bits, double _2R_div_R, bool term) +{ + // FIXME: No variable length arrays in ISO C++, hardcoding to max 12 bits. + // double vbit[bits]; + double vbit[12]; + + // Calculate voltage contribution by each individual bit in the R-2R ladder. + for (int set_bit = 0; set_bit < bits; set_bit++) { + int bit; + + double Vn = 1.0; // Normalized bit voltage. + double R = 1.0; // Normalized R + double _2R = _2R_div_R*R; // 2R + double Rn = term ? // Rn = 2R for correct termination, + _2R : INFINITY; // INFINITY for missing termination. + + // Calculate DAC "tail" resistance by repeated parallel substitution. + for (bit = 0; bit < set_bit; bit++) { + if (Rn == INFINITY) { + Rn = R + _2R; + } + else { + Rn = R + _2R*Rn/(_2R + Rn); // R + 2R || Rn + } + } + + // Source transformation for bit voltage. + if (Rn == INFINITY) { + Rn = _2R; + } + else { + Rn = _2R*Rn/(_2R + Rn); // 2R || Rn + Vn = Vn*Rn/_2R; + } + + // Calculate DAC output voltage by repeated source transformation from + // the "tail". + for (++bit; bit < bits; bit++) { + Rn += R; + double I = Vn/Rn; + Rn = _2R*Rn/(_2R + Rn); // 2R || Rn + Vn = Rn*I; + } + + vbit[set_bit] = Vn; + } + + // Calculate the voltage for any combination of bits by superpositioning. + for (int i = 0; i < (1 << bits); i++) { + int x = i; + double Vo = 0; + for (int j = 0; j < bits; j++) { + Vo += (x & 0x1)*vbit[j]; + x >>= 1; + } + + // Scale maximum output to 2^bits - 1. + dac[i] = (unsigned short)(((1 << bits) - 1)*Vo + 0.5); + } +} + +} // namespace reSID diff --git a/C64/SID/resid/dac.h b/C64/SID/resid/dac.h new file mode 100755 index 00000000..17aa2dbc --- /dev/null +++ b/C64/SID/resid/dac.h @@ -0,0 +1,30 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_DAC_H +#define RESID_DAC_H + +namespace reSID +{ + +void build_dac_table(unsigned short* dac, int bits, double _2R_div_R, bool term); + +} // namespace reSID + +#endif // not RESID_DAC_H diff --git a/C64/SID/resid/envelope.cc b/C64/SID/resid/envelope.cc new file mode 100755 index 00000000..be055f21 --- /dev/null +++ b/C64/SID/resid/envelope.cc @@ -0,0 +1,278 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#define RESID_ENVELOPE_CC + +#include "envelope.h" +#include "dac.h" + +namespace reSID +{ + +// Rate counter periods are calculated from the Envelope Rates table in +// the Programmer's Reference Guide. The rate counter period is the number of +// cycles between each increment of the envelope counter. +// The rates have been verified by sampling ENV3. +// +// The rate counter is a 16 bit register which is incremented each cycle. +// When the counter reaches a specific comparison value, the envelope counter +// is incremented (attack) or decremented (decay/release) and the +// counter is zeroed. +// +// NB! Sampling ENV3 shows that the calculated values are not exact. +// It may seem like most calculated values have been rounded (.5 is rounded +// down) and 1 has beed added to the result. A possible explanation for this +// is that the SID designers have used the calculated values directly +// as rate counter comparison values, not considering a one cycle delay to +// zero the counter. This would yield an actual period of comparison value + 1. +// +// The time of the first envelope count can not be exactly controlled, except +// possibly by resetting the chip. Because of this we cannot do cycle exact +// sampling and must devise another method to calculate the rate counter +// periods. +// +// The exact rate counter periods can be determined e.g. by counting the number +// of cycles from envelope level 1 to envelope level 129, and dividing the +// number of cycles by 128. CIA1 timer A and B in linked mode can perform +// the cycle count. This is the method used to find the rates below. +// +// To avoid the ADSR delay bug, sampling of ENV3 should be done using +// sustain = release = 0. This ensures that the attack state will not lower +// the current rate counter period. +// +// The ENV3 sampling code below yields a maximum timing error of 14 cycles. +// lda #$01 +// l1: cmp $d41c +// bne l1 +// ... +// lda #$ff +// l2: cmp $d41c +// bne l2 +// +// This yields a maximum error for the calculated rate period of 14/128 cycles. +// The described method is thus sufficient for exact calculation of the rate +// periods. +// +reg16 EnvelopeGenerator::rate_counter_period[] = { + 8, // 2ms*1.0MHz/256 = 7.81 + 31, // 8ms*1.0MHz/256 = 31.25 + 62, // 16ms*1.0MHz/256 = 62.50 + 94, // 24ms*1.0MHz/256 = 93.75 + 148, // 38ms*1.0MHz/256 = 148.44 + 219, // 56ms*1.0MHz/256 = 218.75 + 266, // 68ms*1.0MHz/256 = 265.63 + 312, // 80ms*1.0MHz/256 = 312.50 + 391, // 100ms*1.0MHz/256 = 390.63 + 976, // 250ms*1.0MHz/256 = 976.56 + 1953, // 500ms*1.0MHz/256 = 1953.13 + 3125, // 800ms*1.0MHz/256 = 3125.00 + 3906, // 1 s*1.0MHz/256 = 3906.25 + 11719, // 3 s*1.0MHz/256 = 11718.75 + 19531, // 5 s*1.0MHz/256 = 19531.25 + 31250 // 8 s*1.0MHz/256 = 31250.00 +}; + + +// For decay and release, the clock to the envelope counter is sequentially +// divided by 1, 2, 4, 8, 16, 30, 1 to create a piece-wise linear approximation +// of an exponential. The exponential counter period is loaded at the envelope +// counter values 255, 93, 54, 26, 14, 6, 0. The period can be different for the +// same envelope counter value, depending on whether the envelope has been +// rising (attack -> release) or sinking (decay/release). +// +// Since it is not possible to reset the rate counter (the test bit has no +// influence on the envelope generator whatsoever) a method must be devised to +// do cycle exact sampling of ENV3 to do the investigation. This is possible +// with knowledge of the rate period for A=0, found above. +// +// The CPU can be synchronized with ENV3 by first synchronizing with the rate +// counter by setting A=0 and wait in a carefully timed loop for the envelope +// counter _not_ to change for 9 cycles. We can then wait for a specific value +// of ENV3 with another timed loop to fully synchronize with ENV3. +// +// At the first period when an exponential counter period larger than one +// is used (decay or relase), one extra cycle is spent before the envelope is +// decremented. The envelope output is then delayed one cycle until the state +// is changed to attack. Now one cycle less will be spent before the envelope +// is incremented, and the situation is normalized. +// The delay is probably caused by the comparison with the exponential counter, +// and does not seem to affect the rate counter. This has been verified by +// timing 256 consecutive complete envelopes with A = D = R = 1, S = 0, using +// CIA1 timer A and B in linked mode. If the rate counter is not affected the +// period of each complete envelope is +// (255 + 162*1 + 39*2 + 28*4 + 12*8 + 8*16 + 6*30)*32 = 756*32 = 32352 +// which corresponds exactly to the timed value divided by the number of +// complete envelopes. +// NB! This one cycle delay is only modeled for single cycle clocking. + + +// From the sustain levels it follows that both the low and high 4 bits of the +// envelope counter are compared to the 4-bit sustain value. +// This has been verified by sampling ENV3. +// +reg8 EnvelopeGenerator::sustain_level[] = { + 0x00, + 0x11, + 0x22, + 0x33, + 0x44, + 0x55, + 0x66, + 0x77, + 0x88, + 0x99, + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0xee, + 0xff, +}; + + +// DAC lookup tables. +unsigned short EnvelopeGenerator::model_dac[2][1 << 8] = { + {0}, + {0}, +}; + + +// ---------------------------------------------------------------------------- +// Constructor. +// ---------------------------------------------------------------------------- +EnvelopeGenerator::EnvelopeGenerator() +{ + static bool class_init; + + if (!class_init) { + // Build DAC lookup tables for 8-bit DACs. + // MOS 6581: 2R/R ~ 2.20, missing termination resistor. + build_dac_table(model_dac[0], 8, 2.20, false); + // MOS 8580: 2R/R ~ 2.00, correct termination. + build_dac_table(model_dac[1], 8, 2.00, true); + + class_init = true; + } + + set_chip_model(MOS6581); + + // Counter's odd bits are high on powerup + envelope_counter = 0xaa; + + // just to avoid uninitialized access with delta clocking + next_state = RELEASE; + + reset(); +} + +// ---------------------------------------------------------------------------- +// SID reset. +// ---------------------------------------------------------------------------- +void EnvelopeGenerator::reset() +{ + // counter is not changed on reset + envelope_pipeline = 0; + exponential_pipeline = 0; + + state_pipeline = 0; + + attack = 0; + decay = 0; + sustain = 0; + release = 0; + + gate = 0; + + rate_counter = 0; + exponential_counter = 0; + exponential_counter_period = 1; + new_exponential_counter_period = 0; + reset_rate_counter = false; + + state = RELEASE; + rate_period = rate_counter_period[release]; + hold_zero = false; +} + + +// ---------------------------------------------------------------------------- +// Set chip model. +// ---------------------------------------------------------------------------- +void EnvelopeGenerator::set_chip_model(chip_model model) +{ + sid_model = model; +} + + +// ---------------------------------------------------------------------------- +// Register functions. +// ---------------------------------------------------------------------------- +void EnvelopeGenerator::writeCONTROL_REG(reg8 control) +{ + reg8 gate_next = control & 0x01; + + // The rate counter is never reset, thus there will be a delay before the + // envelope counter starts counting up (attack) or down (release). + + if (gate != gate_next) { + // Gate bit on: Start attack, decay, sustain. + // Gate bit off: Start release. + next_state = gate_next ? ATTACK : RELEASE; + if (next_state == ATTACK) { + // The decay register is "accidentally" activated during first cycle of attack phase + state = DECAY_SUSTAIN; + rate_period = rate_counter_period[decay]; + state_pipeline = 2; + if (reset_rate_counter || exponential_pipeline == 2) { + envelope_pipeline = exponential_counter_period == 1 || exponential_pipeline == 2 ? 2 : 4; + } + else if (exponential_pipeline == 1) { state_pipeline = 3; } + } + else if(!hold_zero){state_pipeline = envelope_pipeline > 0 ? 3 : 2;} + gate = gate_next; + } +} + +void EnvelopeGenerator::writeATTACK_DECAY(reg8 attack_decay) +{ + attack = (attack_decay >> 4) & 0x0f; + decay = attack_decay & 0x0f; + if (state == ATTACK) { + rate_period = rate_counter_period[attack]; + } + else if (state == DECAY_SUSTAIN) { + rate_period = rate_counter_period[decay]; + } +} + +void EnvelopeGenerator::writeSUSTAIN_RELEASE(reg8 sustain_release) +{ + sustain = (sustain_release >> 4) & 0x0f; + release = sustain_release & 0x0f; + if (state == RELEASE) { + rate_period = rate_counter_period[release]; + } +} + +reg8 EnvelopeGenerator::readENV() +{ + return env3; +} + +} // namespace reSID diff --git a/C64/SID/resid/envelope.h b/C64/SID/resid/envelope.h new file mode 100755 index 00000000..ab4d7e8c --- /dev/null +++ b/C64/SID/resid/envelope.h @@ -0,0 +1,425 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_ENVELOPE_H +#define RESID_ENVELOPE_H + +#include "resid-config.h" + +namespace reSID +{ + +// ---------------------------------------------------------------------------- +// A 15 bit counter is used to implement the envelope rates, in effect +// dividing the clock to the envelope counter by the currently selected rate +// period. +// In addition, another counter is used to implement the exponential envelope +// decay, in effect further dividing the clock to the envelope counter. +// The period of this counter is set to 1, 2, 4, 8, 16, 30 at the envelope +// counter values 255, 93, 54, 26, 14, 6, respectively. +// ---------------------------------------------------------------------------- +class EnvelopeGenerator +{ +public: + EnvelopeGenerator(); + + enum State { ATTACK, DECAY_SUSTAIN, RELEASE, FREEZED }; + + void set_chip_model(chip_model model); + + void clock(); + void clock(cycle_count delta_t); + void reset(); + + void writeCONTROL_REG(reg8); + void writeATTACK_DECAY(reg8); + void writeSUSTAIN_RELEASE(reg8); + reg8 readENV(); + + // 8-bit envelope output. + short output(); + +protected: + void set_exponential_counter(); + + void state_change(); + + reg16 rate_counter; + reg16 rate_period; + reg8 exponential_counter; + reg8 exponential_counter_period; + reg8 new_exponential_counter_period; + reg8 envelope_counter; + reg8 env3; + // Emulation of pipeline delay for envelope decrement. + cycle_count envelope_pipeline; + cycle_count exponential_pipeline; + cycle_count state_pipeline; + bool hold_zero; + bool reset_rate_counter; + + reg4 attack; + reg4 decay; + reg4 sustain; + reg4 release; + + reg8 gate; + + State state; + State next_state; + + chip_model sid_model; + + // Lookup table to convert from attack, decay, or release value to rate + // counter period. + static reg16 rate_counter_period[]; + + // The 16 selectable sustain levels. + static reg8 sustain_level[]; + + // DAC lookup tables. + static unsigned short model_dac[2][1 << 8]; + +friend class SID; +}; + + +// ---------------------------------------------------------------------------- +// Inline functions. +// The following functions are defined inline because they are called every +// time a sample is calculated. +// ---------------------------------------------------------------------------- + +#if RESID_INLINING || defined(RESID_ENVELOPE_CC) + +// ---------------------------------------------------------------------------- +// SID clocking - 1 cycle. +// ---------------------------------------------------------------------------- +RESID_INLINE +void EnvelopeGenerator::clock() +{ + // The ENV3 value is sampled at the first phase of the clock + env3 = envelope_counter; + + if (unlikely(state_pipeline)) { + state_change(); + } + + // If the exponential counter period != 1, the envelope decrement is delayed + // 1 cycle. This is only modeled for single cycle clocking. + if (unlikely(envelope_pipeline != 0) && (--envelope_pipeline == 0)) { + if (likely(!hold_zero)) { + if (state == ATTACK) { + ++envelope_counter &= 0xff; + if (unlikely(envelope_counter == 0xff)) { + state = DECAY_SUSTAIN; + rate_period = rate_counter_period[decay]; + } + } + else if ((state == DECAY_SUSTAIN) || (state == RELEASE)) { + --envelope_counter &= 0xff; + } + + set_exponential_counter(); + } + } + + if (unlikely(exponential_pipeline != 0) && (--exponential_pipeline == 0)) { + exponential_counter = 0; + + if (((state == DECAY_SUSTAIN) && (envelope_counter != sustain_level[sustain])) + || (state == RELEASE)) { + // The envelope counter can flip from 0x00 to 0xff by changing state to + // attack, then to release. The envelope counter will then continue + // counting down in the release state. + // This has been verified by sampling ENV3. + + envelope_pipeline = 1; + } + } + else if (unlikely(reset_rate_counter)) { + rate_counter = 0; + reset_rate_counter = false; + + if (state == ATTACK) { + // The first envelope step in the attack state also resets the exponential + // counter. This has been verified by sampling ENV3. + exponential_counter = 0; // NOTE this is actually delayed one cycle, not modeled + + // The envelope counter can flip from 0xff to 0x00 by changing state to + // release, then to attack. The envelope counter is then frozen at + // zero; to unlock this situation the state must be changed to release, + // then to attack. This has been verified by sampling ENV3. + + envelope_pipeline = 2; + } + else { + if ((!hold_zero) && ++exponential_counter == exponential_counter_period) { + exponential_pipeline = exponential_counter_period != 1 ? 2 : 1; + } + } + } + + // Check for ADSR delay bug. + // If the rate counter comparison value is set below the current value of the + // rate counter, the counter will continue counting up until it wraps around + // to zero at 2^15 = 0x8000, and then count rate_period - 1 before the + // envelope can finally be stepped. + // This has been verified by sampling ENV3. + // + if (likely(rate_counter != rate_period)) { + if (unlikely(++rate_counter & 0x8000)) { + ++rate_counter &= 0x7fff; + } + } + else + reset_rate_counter = true; +} + + +// ---------------------------------------------------------------------------- +// SID clocking - delta_t cycles. +// ---------------------------------------------------------------------------- +RESID_INLINE +void EnvelopeGenerator::clock(cycle_count delta_t) +{ + // NB! Any pipelined envelope counter decrement from single cycle clocking + // will be lost. It is not worth the trouble to flush the pipeline here. + + if (unlikely(state_pipeline)) { + if (next_state == ATTACK) { + state = ATTACK; + hold_zero = false; + rate_period = rate_counter_period[attack]; + } else if (next_state == RELEASE) { + state = RELEASE; + rate_period = rate_counter_period[release]; + } else if (next_state == FREEZED) { + hold_zero = true; + } + state_pipeline = 0; + } + + // Check for ADSR delay bug. + // If the rate counter comparison value is set below the current value of the + // rate counter, the counter will continue counting up until it wraps around + // to zero at 2^15 = 0x8000, and then count rate_period - 1 before the + // envelope can finally be stepped. + // This has been verified by sampling ENV3. + // + + // NB! This requires two's complement integer. + int rate_step = rate_period - rate_counter; + if (unlikely(rate_step <= 0)) { + rate_step += 0x7fff; + } + + while (delta_t) { + if (delta_t < rate_step) { + // likely (~65%) + rate_counter += delta_t; + if (unlikely(rate_counter & 0x8000)) { + ++rate_counter &= 0x7fff; + } + return; + } + + rate_counter = 0; + delta_t -= rate_step; + + // The first envelope step in the attack state also resets the exponential + // counter. This has been verified by sampling ENV3. + // + if (state == ATTACK || ++exponential_counter == exponential_counter_period) { + // likely (~50%) + exponential_counter = 0; + + // Check whether the envelope counter is frozen at zero. + if (unlikely(hold_zero)) { + rate_step = rate_period; + continue; + } + + switch (state) { + case ATTACK: + // The envelope counter can flip from 0xff to 0x00 by changing state to + // release, then to attack. The envelope counter is then frozen at + // zero; to unlock this situation the state must be changed to release, + // then to attack. This has been verified by sampling ENV3. + // + ++envelope_counter &= 0xff; + if (unlikely(envelope_counter == 0xff)) { + state = DECAY_SUSTAIN; + rate_period = rate_counter_period[decay]; + } + break; + case DECAY_SUSTAIN: + if (likely(envelope_counter != sustain_level[sustain])) { + --envelope_counter; + } + break; + case RELEASE: + // The envelope counter can flip from 0x00 to 0xff by changing state to + // attack, then to release. The envelope counter will then continue + // counting down in the release state. + // This has been verified by sampling ENV3. + // NB! The operation below requires two's complement integer. + // + --envelope_counter &= 0xff; + break; + case FREEZED: + // we should never get here + break; + } + + // Check for change of exponential counter period. + set_exponential_counter(); + if (unlikely(new_exponential_counter_period > 0)) { + exponential_counter_period = new_exponential_counter_period; + new_exponential_counter_period = 0; + if (next_state == FREEZED) { + hold_zero = true; + } + } + } + + rate_step = rate_period; + } +} + +/** + * This is what happens on chip during state switching, + * based on die reverse engineering and transistor level + * emulation. + * + * Attack + * + * 0 - Gate on + * 1 - Counting direction changes + * During this cycle the decay rate is "accidentally" activated + * 2 - Counter is being inverted + * Now the attack rate is correctly activated + * Counter is enabled + * 3 - Counter will be counting upward from now on + * + * Decay + * + * 0 - Counter == $ff + * 1 - Counting direction changes + * The attack state is still active + * 2 - Counter is being inverted + * During this cycle the decay state is activated + * 3 - Counter will be counting downward from now on + * + * Release + * + * 0 - Gate off + * 1 - During this cycle the release state is activated if coming from sustain/decay + * *2 - Counter is being inverted, the release state is activated + * *3 - Counter will be counting downward from now on + * + * (* only if coming directly from Attack state) + * + * Freeze + * + * 0 - Counter == $00 + * 1 - Nothing + * 2 - Counter is disabled + */ +RESID_INLINE +void EnvelopeGenerator::state_change() +{ + state_pipeline--; + + switch (next_state) { + case ATTACK: + if (state_pipeline == 0) { + state = ATTACK; + // The attack register is correctly activated during second cycle of attack phase + rate_period = rate_counter_period[attack]; + hold_zero = false; + } + break; + case DECAY_SUSTAIN: + break; + case RELEASE: + if (((state == ATTACK) && (state_pipeline == 0)) + || ((state == DECAY_SUSTAIN) && (state_pipeline == 1))) { + state = RELEASE; + rate_period = rate_counter_period[release]; + } + break; + case FREEZED: + break; + } +} + + +// ---------------------------------------------------------------------------- +// Read the envelope generator output. +// ---------------------------------------------------------------------------- +RESID_INLINE +short EnvelopeGenerator::output() +{ + // DAC imperfections are emulated by using envelope_counter as an index + // into a DAC lookup table. readENV() uses envelope_counter directly. + return model_dac[sid_model][envelope_counter]; +} + +RESID_INLINE +void EnvelopeGenerator::set_exponential_counter() +{ + // Check for change of exponential counter period. + switch (envelope_counter) { + case 0xff: + exponential_counter_period = 1; + break; + case 0x5d: + exponential_counter_period = 2; + break; + case 0x36: + exponential_counter_period = 4; + break; + case 0x1a: + exponential_counter_period = 8; + break; + case 0x0e: + exponential_counter_period = 16; + break; + case 0x06: + exponential_counter_period = 30; + break; + case 0x00: + // TODO write a test to verify that 0x00 really changes the period + // e.g. set R = 0xf, gate on to 0x06, gate off to 0x00, gate on to 0x04, + // gate off, sample. + exponential_counter_period = 1; + + // When the envelope counter is changed to zero, it is frozen at zero. + // This has been verified by sampling ENV3. + hold_zero = true; + break; + } +} + +#endif // RESID_INLINING || defined(RESID_ENVELOPE_CC) + +} // namespace reSID + +#endif // not RESID_ENVELOPE_H diff --git a/C64/SID/resid/extfilt.cc b/C64/SID/resid/extfilt.cc new file mode 100755 index 00000000..eee058d1 --- /dev/null +++ b/C64/SID/resid/extfilt.cc @@ -0,0 +1,65 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#define RESID_EXTFILT_CC + +#include "extfilt.h" + +namespace reSID +{ + +// ---------------------------------------------------------------------------- +// Constructor. +// ---------------------------------------------------------------------------- +ExternalFilter::ExternalFilter() +{ + reset(); + enable_filter(true); + + // Low-pass: R = 10 kOhm, C = 1000 pF; w0l = 1/RC = 1/(1e4*1e-9) = 100 000 + // High-pass: R = 1 kOhm, C = 10 uF; w0h = 1/RC = 1/(1e3*1e-5) = 100 + + // Assume a 1MHz clock. + // Cutoff frequency accuracy (4 bits) is traded off for filter signal + // accuracy (27 bits). This is crucial since w0lp and w0hp are so far apart. + w0lp_1_s7 = int(100000*1.0e-6*(1 << 7) + 0.5); + w0hp_1_s17 = int(100*1.0e-6*(1 << 17) + 0.5); +} + + +// ---------------------------------------------------------------------------- +// Enable filter. +// ---------------------------------------------------------------------------- +void ExternalFilter::enable_filter(bool enable) +{ + enabled = enable; +} + + +// ---------------------------------------------------------------------------- +// SID reset. +// ---------------------------------------------------------------------------- +void ExternalFilter::reset() +{ + // State of filter. + Vlp = 0; + Vhp = 0; +} + +} // namespace reSID diff --git a/C64/SID/resid/extfilt.h b/C64/SID/resid/extfilt.h new file mode 100755 index 00000000..19cce80b --- /dev/null +++ b/C64/SID/resid/extfilt.h @@ -0,0 +1,164 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_EXTFILT_H +#define RESID_EXTFILT_H + +#include "resid-config.h" + +namespace reSID +{ + +// ---------------------------------------------------------------------------- +// The audio output stage in a Commodore 64 consists of two STC networks, +// a low-pass filter with 3-dB frequency 16kHz followed by a high-pass +// filter with 3-dB frequency 1.6Hz (the latter provided an audio equipment +// input impedance of 10kOhm). +// The STC networks are connected with a BJT supposedly meant to act as +// a unity gain buffer, which is not really how it works. A more elaborate +// model would include the BJT, however DC circuit analysis yields BJT +// base-emitter and emitter-base impedances sufficiently low to produce +// additional low-pass and high-pass 3dB-frequencies in the order of hundreds +// of kHz. This calls for a sampling frequency of several MHz, which is far +// too high for practical use. +// ---------------------------------------------------------------------------- +class ExternalFilter +{ +public: + ExternalFilter(); + + void enable_filter(bool enable); + + void clock(short Vi); + void clock(cycle_count delta_t, short Vi); + void reset(); + + // Audio output (16 bits). + short output(); + +protected: + // Filter enabled. + bool enabled; + + // State of filters (27 bits). + int Vlp; // lowpass + int Vhp; // highpass + + // Cutoff frequencies. + int w0lp_1_s7; + int w0hp_1_s17; + +friend class SID; +}; + + +// ---------------------------------------------------------------------------- +// Inline functions. +// The following functions are defined inline because they are called every +// time a sample is calculated. +// ---------------------------------------------------------------------------- + +#if RESID_INLINING || defined(RESID_EXTFILT_CC) + +// ---------------------------------------------------------------------------- +// SID clocking - 1 cycle. +// ---------------------------------------------------------------------------- +RESID_INLINE +void ExternalFilter::clock(short Vi) +{ + // This is handy for testing. + if (unlikely(!enabled)) { + // Vo = Vlp - Vhp; + Vlp = Vi << 11; + Vhp = 0; + return; + } + + // Calculate filter outputs. + // Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t; + // Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t; + // Vo = Vlp - Vhp; + + int dVlp = w0lp_1_s7*int((unsigned(Vi) << 11) - unsigned(Vlp)) >> 7; + int dVhp = w0hp_1_s17*(Vlp - Vhp) >> 17; + Vlp += dVlp; + Vhp += dVhp; +} + +// ---------------------------------------------------------------------------- +// SID clocking - delta_t cycles. +// ---------------------------------------------------------------------------- +RESID_INLINE +void ExternalFilter::clock(cycle_count delta_t, short Vi) +{ + // This is handy for testing. + if (unlikely(!enabled)) { + // Vo = Vlp - Vhp; + Vlp = Vi << 11; + Vhp = 0; + return; + } + + // Maximum delta cycles for the external filter to work satisfactorily + // is approximately 8. + cycle_count delta_t_flt = 8; + + while (delta_t) { + if (unlikely(delta_t < delta_t_flt)) { + delta_t_flt = delta_t; + } + + // Calculate filter outputs. + // Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t; + // Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t; + // Vo = Vlp - Vhp; + + int dVlp = (w0lp_1_s7*delta_t_flt >> 3)*((Vi << 11) - Vlp) >> 4; + int dVhp = (w0hp_1_s17*delta_t_flt >> 3)*(Vlp - Vhp) >> 14; + Vlp += dVlp; + Vhp += dVhp; + + delta_t -= delta_t_flt; + } +} + + +// ---------------------------------------------------------------------------- +// Audio output (16 bits). +// ---------------------------------------------------------------------------- +RESID_INLINE +short ExternalFilter::output() +{ + // Saturated arithmetics to guard against 16 bit sample overflow. + const int half = 1 << 15; + int Vo = (Vlp - Vhp) >> 11; + if (Vo >= half) { + Vo = half - 1; + } + else if (Vo < -half) { + Vo = -half; + } + return Vo; +} + +#endif // RESID_INLINING || defined(RESID_EXTFILT_CC) + +} // namespace reSID + +#endif // not RESID_EXTFILT_H diff --git a/C64/SID/resid/filter.cc b/C64/SID/resid/filter.cc new file mode 100755 index 00000000..86421a48 --- /dev/null +++ b/C64/SID/resid/filter.cc @@ -0,0 +1,780 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#define RESID_FILTER_CC + +#ifdef _M_ARM +#undef _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE +#define _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE 1 +#endif + +#include "filter.h" +#include "dac.h" +#include "spline.h" +#include + +namespace reSID +{ + +// This is the SID 6581 op-amp voltage transfer function, measured on +// CAP1B/CAP1A on a chip marked MOS 6581R4AR 0687 14. +// All measured chips have op-amps with output voltages (and thus input +// voltages) within the range of 0.81V - 10.31V. + +static double_point opamp_voltage_6581[] = { + { 0.81, 10.31 }, // Approximate start of actual range + { 0.81, 10.31 }, // Repeated point + { 2.40, 10.31 }, + { 2.60, 10.30 }, + { 2.70, 10.29 }, + { 2.80, 10.26 }, + { 2.90, 10.17 }, + { 3.00, 10.04 }, + { 3.10, 9.83 }, + { 3.20, 9.58 }, + { 3.30, 9.32 }, + { 3.50, 8.69 }, + { 3.70, 8.00 }, + { 4.00, 6.89 }, + { 4.40, 5.21 }, + { 4.54, 4.54 }, // Working point (vi = vo) + { 4.60, 4.19 }, + { 4.80, 3.00 }, + { 4.90, 2.30 }, // Change of curvature + { 4.95, 2.03 }, + { 5.00, 1.88 }, + { 5.05, 1.77 }, + { 5.10, 1.69 }, + { 5.20, 1.58 }, + { 5.40, 1.44 }, + { 5.60, 1.33 }, + { 5.80, 1.26 }, + { 6.00, 1.21 }, + { 6.40, 1.12 }, + { 7.00, 1.02 }, + { 7.50, 0.97 }, + { 8.50, 0.89 }, + { 10.00, 0.81 }, + { 10.31, 0.81 }, // Approximate end of actual range + { 10.31, 0.81 } // Repeated end point +}; + +// This is the SID 8580 op-amp voltage transfer function, measured on +// CAP1B/CAP1A on a chip marked CSG 8580R5 1690 25. +static double_point opamp_voltage_8580[] = { + { 1.30, 8.91 }, // Approximate start of actual range + { 1.30, 8.91 }, // Repeated end point + { 4.76, 8.91 }, + { 4.77, 8.90 }, + { 4.78, 8.88 }, + { 4.785, 8.86 }, + { 4.79, 8.80 }, + { 4.795, 8.60 }, + { 4.80, 8.25 }, + { 4.805, 7.50 }, + { 4.81, 6.10 }, + { 4.815, 4.05 }, // Change of curvature + { 4.82, 2.27 }, + { 4.825, 1.65 }, + { 4.83, 1.55 }, + { 4.84, 1.47 }, + { 4.85, 1.43 }, + { 4.87, 1.37 }, + { 4.90, 1.34 }, + { 5.00, 1.30 }, + { 5.10, 1.30 }, + { 8.91, 1.30 }, // Approximate end of actual range + { 8.91, 1.30 } // Repeated end point +}; + + +/* + * R1 = 15.3*Ri + * R2 = 7.3*Ri + * R3 = 4.7*Ri + * Rf = 1.4*Ri + * R4 = 1.4*Ri + * R8 = 2.0*Ri + * RC = 2.8*Ri + * + * res feedback input + * --- -------- ----- + * 0 Rf Ri + * 1 Rf|R1 Ri + * 2 Rf|R2 Ri + * 3 Rf|R3 Ri + * 4 Rf R4 + * 5 Rf|R1 R4 + * 6 Rf|R2 R4 + * 7 Rf|R3 R4 + * 8 Rf R8 + * 9 Rf|R1 R8 + * A Rf|R2 R8 + * B Rf|R3 R8 + * C Rf RC + * D Rf|R1 RC + * E Rf|R2 RC + * F Rf|R3 RC + */ +static int resGain[16] = +{ + (int)((1<<7)*(1.4/1.0)), // Rf/Ri 1.4 + (int)((1<<7)*(((1.4*15.3)/(1.4+15.3))/1.0)), // (Rf|R1)/Ri 1.28263 + (int)((1<<7)*(((1.4*7.3)/(1.4+7.3))/1.0)), // (Rf|R2)/Ri 1.17471 + (int)((1<<7)*(((1.4*4.7)/(1.4+4.7))/1.0)), // (Rf|R3)/Ri 1.07869 + (int)((1<<7)*(1.4/1.4)), // Rf/R4 1.0 + (int)((1<<7)*(((1.4*15.3)/(1.4+15.3))/1.4)), // (Rf|R1)/R4 0.916168 + (int)((1<<7)*(((1.4*7.3)/(1.4+7.3))/1.4)), // (Rf|R2)/R4 0.83908 + (int)((1<<7)*(((1.4*4.7)/(1.4+4.7))/1.4)), // (Rf|R3)/R4 0.770492 + (int)((1<<7)*(1.4/2.0)), // Rf/R8 0.7 + (int)((1<<7)*(((1.4*15.3)/(1.4+15.3))/2.0)), // (Rf|R1)/R8 0.641317 + (int)((1<<7)*(((1.4*7.3)/(1.4+7.3))/2.0)), // (Rf|R2)/R8 0.587356 + (int)((1<<7)*(((1.4*4.7)/(1.4+4.7))/2.0)), // (Rf|R3)/R8 0.539344 + (int)((1<<7)*(1.4/2.8)), // Rf/RC 0.5 + (int)((1<<7)*(((1.4*15.3)/(1.4+15.3))/2.8)), // (Rf|R1)/RC 0.458084 + (int)((1<<7)*(((1.4*7.3)/(1.4+7.3))/2.8)), // (Rf|R2)/RC 0.41954 + (int)((1<<7)*(((1.4*4.7)/(1.4+4.7))/2.8)), // (Rf|R3)/RC 0.385246 +}; + +typedef struct { + // Op-amp transfer function. + double_point* opamp_voltage; + int opamp_voltage_size; + // Voice output characteristics. + double voice_voltage_range; + double voice_DC_voltage; + // Capacitor value. + double C; + // Transistor parameters. + double Vdd; + double Vth; // Threshold voltage + double Ut; // Thermal voltage: Ut = k*T/q = 8.61734315e-5*T ~ 26mV + double k; // Gate coupling coefficient: K = Cox/(Cox+Cdep) ~ 0.7 + double uCox; // u*Cox + double WL_vcr; // W/L for VCR + double WL_snake; // W/L for "snake" + // DAC parameters. + double dac_zero; + double dac_scale; + double dac_2R_div_R; + bool dac_term; +} model_filter_init_t; + +static model_filter_init_t model_filter_init[2] = { + { + opamp_voltage_6581, + sizeof(opamp_voltage_6581)/sizeof(*opamp_voltage_6581), + // The dynamic analog range of one voice is approximately 1.5V, + 1.5, + // riding at a DC level of approximately 5.0V. + 5.0, + // Capacitor value. + 470e-12, + // Transistor parameters. + 12.18, + 1.31, + 26.0e-3, + 1.0, + 20e-6, + 9.0/1.0, + 1.0/115, + // DAC parameters. + 6.65, + 2.63, + 2.20, + false + }, + { + opamp_voltage_8580, + sizeof(opamp_voltage_8580)/sizeof(*opamp_voltage_8580), + // FIXME: Measure for the 8580. + 0.4, + // The 4.75V voltage for the virtual ground is generated by a PolySi resistor divider + 4.75, + // Capacitor value. + 22e-9, + // Transistor parameters. + 9.09, + 0.80, + 26.0e-3, + 1.0, + 50e-6, // 10e-6, + // FIXME: 6581 only + 0, + 0, + 0, + 0, + 2.00, + true + } +}; + +unsigned short Filter::resonance[16][1 << 16]; +unsigned short Filter::vcr_kVg[1 << 16]; +unsigned short Filter::vcr_n_Ids_term[1 << 16]; +int Filter::n_snake; +int Filter::n_param; + +#if defined(__amiga__) && defined(__mc68000__) +#undef HAS_LOG1P +#endif + +#ifndef HAS_LOG1P +static double log1p(double x) +{ + return log(1 + x) - (((1 + x) - 1) - x) / (1 + x); +} +#endif + +Filter::model_filter_t Filter::model_filter[2]; + + +// ---------------------------------------------------------------------------- +// Constructor. +// ---------------------------------------------------------------------------- +Filter::Filter() +{ + static bool class_init; + + if (!class_init) { + double tmp_n_param[2]; + + // Temporary tables for op-amp transfer function. + unsigned int* voltages = new unsigned int[1 << 16]; + opamp_t* opamp = new opamp_t[1 << 16]; + + for (int m = 0; m < 2; m++) { + model_filter_init_t& fi = model_filter_init[m]; + model_filter_t& mf = model_filter[m]; + + // Convert op-amp voltage transfer to 16 bit values. + double vmin = fi.opamp_voltage[0][0]; + double opamp_max = fi.opamp_voltage[0][1]; + double kVddt = fi.k*(fi.Vdd - fi.Vth); + double vmax = kVddt < opamp_max ? opamp_max : kVddt; + double denorm = vmax - vmin; + double norm = 1.0/denorm; + + // Scaling and translation constants. + double N16 = norm*((1u << 16) - 1); + double N30 = norm*((1u << 30) - 1); + double N31 = norm*((1u << 31) - 1); + mf.vo_N16 = N16; + + // The "zero" output level of the voices. + // The digital range of one voice is 20 bits; create a scaling term + // for multiplication which fits in 11 bits. + double N14 = norm*(1u << 14); + mf.voice_scale_s14 = (int)(N14*fi.voice_voltage_range); + mf.voice_DC = (int)(N16*(fi.voice_DC_voltage - vmin)); + + // Vdd - Vth, normalized so that translated values can be subtracted: + // k*Vddt - x = (k*Vddt - t) - (x - t) + mf.kVddt = (int)(N16*(kVddt - vmin) + 0.5); + + tmp_n_param[m] = denorm*(1 << 13)*(fi.uCox/(2*fi.k)*1.0e-6/fi.C); + + // Create lookup table mapping op-amp voltage across output and input + // to input voltage: vo - vx -> vx + // FIXME: No variable length arrays in ISO C++, hardcoding to max 50 + // points. + // double_point scaled_voltage[fi.opamp_voltage_size]; + double_point scaled_voltage[50]; + + for (int i = 0; i < fi.opamp_voltage_size; i++) { + // The target output range is 16 bits, in order to fit in an unsigned + // short. + // + // The y axis is temporarily scaled to 31 bits for maximum accuracy in + // the calculated derivative. + // + // Values are normalized using + // + // x_n = m*2^N*(x - xmin) + // + // and are translated back later (for fixed point math) using + // + // m*2^N*x = x_n - m*2^N*xmin + // + scaled_voltage[fi.opamp_voltage_size - 1 - i][0] = int((N16*(fi.opamp_voltage[i][1] - fi.opamp_voltage[i][0]) + (1 << 16))/2 + 0.5); + scaled_voltage[fi.opamp_voltage_size - 1 - i][1] = N31*(fi.opamp_voltage[i][0] - vmin); + } + + // Clamp x to 16 bits (rounding may cause overflow). + if (scaled_voltage[fi.opamp_voltage_size - 1][0] >= (1 << 16)) { + // The last point is repeated. + scaled_voltage[fi.opamp_voltage_size - 1][0] = + scaled_voltage[fi.opamp_voltage_size - 2][0] = (1 << 16) - 1; + } + + interpolate(scaled_voltage, scaled_voltage + fi.opamp_voltage_size - 1, + PointPlotter(voltages), 1.0); + + // Store both fn and dfn in the same table. + mf.ak = (int)scaled_voltage[0][0]; + mf.bk = (int)scaled_voltage[fi.opamp_voltage_size - 1][0]; + int j; + for (j = 0; j < mf.ak; j++) { + opamp[j].vx = 0; + opamp[j].dvx = 0; + } + unsigned int f = voltages[j]; + for (; j <= mf.bk; j++) { + unsigned int fp = f; + f = voltages[j]; // Scaled by m*2^31 + // m*2^31*dy/1 = (m*2^31*dy)/(m*2^16*dx) = 2^15*dy/dx + int df = f - fp; // Scaled by 2^15 + + // 16 bits unsigned: m*2^16*(fn - xmin) + opamp[j].vx = f > (0xffff << 15) ? 0xffff : f >> 15; + // 16 bits (15 bits + sign bit): 2^11*dfn + opamp[j].dvx = df >> (15 - 11); + } + for (; j < (1 << 16); j++) { + opamp[j].vx = 0; + opamp[j].dvx = 0; + } + + // We don't have the differential for the first point so just assume + // it's the same as the second point's + opamp[mf.ak].dvx = opamp[mf.ak+1].dvx; + + // Create lookup tables for gains / summers. + + // 4 bit "resistor" ladders in the bandpass resonance gain and the audio + // output gain necessitate 16 gain tables. + // From die photographs of the bandpass and volume "resistor" ladders + // it follows that gain ~ vol/8 and 1/Q ~ ~res/8 (assuming ideal + // op-amps and ideal "resistors"). + for (int n8 = 0; n8 < 16; n8++) { + int n = n8 << 4; // Scaled by 2^7 + int x = mf.ak; + for (int vi = 0; vi < (1 << 16); vi++) { + mf.gain[n8][vi] = solve_gain(opamp, n, vi, x, mf); + } + } + + // The filter summer operates at n ~ 1, and has 5 fundamentally different + // input configurations (2 - 6 input "resistors"). + // + // Note that all "on" transistors are modeled as one. This is not + // entirely accurate, since the input for each transistor is different, + // and transistors are not linear components. However modeling all + // transistors separately would be extremely costly. + int offset = 0; + int size; + for (int k = 0; k < 5; k++) { + int idiv = 2 + k; // 2 - 6 input "resistors". + int n_idiv = idiv << 7; // n*idiv, scaled by 2^7 + size = idiv << 16; + int x = mf.ak; + for (int vi = 0; vi < size; vi++) { + mf.summer[offset + vi] = + solve_gain(opamp, n_idiv, vi/idiv, x, mf); + } + offset += size; + } + + // The audio mixer operates at n ~ 8/6, and has 8 fundamentally different + // input configurations (0 - 7 input "resistors"). + // + // All "on", transistors are modeled as one - see comments above for + // the filter summer. + offset = 0; + size = 1; // Only one lookup element for 0 input "resistors". + for (int l = 0; l < 8; l++) { + int idiv = l; // 0 - 7 input "resistors". + int n_idiv = (idiv << 7)*8/6; // n*idiv, scaled by 2^7 + if (idiv == 0) { + // Avoid division by zero; the result will be correct since + // n_idiv = 0. + idiv = 1; + } + int x = mf.ak; + for (int vi = 0; vi < size; vi++) { + mf.mixer[offset + vi] = + solve_gain(opamp, n_idiv, vi/idiv, x, mf); + } + offset += size; + size = (l + 1) << 16; + } + + // Create lookup table mapping capacitor voltage to op-amp input voltage: + // vc -> vx + for (int m = 0; m < (1 << 16); m++) { + mf.opamp_rev[m] = opamp[m].vx; + } + + mf.vc_max = (int)(N30*(fi.opamp_voltage[0][1] - fi.opamp_voltage[0][0])); + mf.vc_min = (int)(N30*(fi.opamp_voltage[fi.opamp_voltage_size - 1][1] - fi.opamp_voltage[fi.opamp_voltage_size - 1][0])); + } + + // Free temporary table. + delete[] voltages; + + unsigned int dac_bits = 11; + + { + // 8580 only + for (int n8 = 0; n8 < 16; n8++) { + int x = model_filter[1].ak; + for (int vi = 0; vi < (1 << 16); vi++) { + resonance[n8][vi] = solve_gain(opamp, resGain[n8], vi, x, model_filter[1]); + } + } + + // scaled 5 bits + n_param = (int)(tmp_n_param[1] * 32 + 0.5); + + model_filter_init_t& fi = model_filter_init[1]; + model_filter_t& f = model_filter[1]; + + double Vgt = fi.k * ((4.75 * 1.6) - fi.Vth); + kVgt = (int)(f.vo_N16 * (Vgt - fi.opamp_voltage[0][0]) + 0.5); + + // DAC table. + // W/L ratio for frequency DAC, bits are proportional. + // scaled 5 bits + unsigned short dacWL = 3; // 0,0029296875 * 1024 (actual value is ~= 0.003075) + f.f0_dac[0] = dacWL; + for (int n = 1; n < (1 << dac_bits); n++) { + // Calculate W/L ratio for parallel NMOS resistances + unsigned short wl = 0; + for (unsigned int i = 0; i < dac_bits; i++) { + unsigned int bitmask = 1 << i; + if (n & bitmask) { + wl += dacWL * (bitmask<<1); + } + } + f.f0_dac[n] = wl; + } + } + + // Free temporary table. + delete[] opamp; + + { + // 6581 only + model_filter_init_t& fi = model_filter_init[0]; + model_filter_t& f = model_filter[0]; + double N16 = f.vo_N16; + double vmin = fi.opamp_voltage[0][0]; + + Vw_bias = 0; + + // Normalized snake current factor, 1 cycle at 1MHz. + // Fit in 5 bits. + n_snake = (int)(fi.WL_snake * tmp_n_param[0] + 0.5); + + // DAC table. + build_dac_table(f.f0_dac, dac_bits, fi.dac_2R_div_R, fi.dac_term); + for (int n = 0; n < (1 << dac_bits); n++) { + f.f0_dac[n] = (unsigned short)(N16*(fi.dac_zero + f.f0_dac[n]*fi.dac_scale/(1 << dac_bits) - vmin) + 0.5); + } + + // VCR table. + double k = fi.k; + double kVddt = N16*(k*(fi.Vdd - fi.Vth)); + vmin *= N16; + + for (int i = 0; i < (1 << 16); i++) { + // The table index is right-shifted 16 times in order to fit in + // 16 bits; the argument to sqrt is thus multiplied by (1 << 16). + // + // The returned value must be corrected for translation. Vg always + // takes part in a subtraction as follows: + // + // k*Vg - Vx = (k*Vg - t) - (Vx - t) + // + // I.e. k*Vg - t must be returned. + double Vg = kVddt - sqrt((double)i*(1 << 16)); + vcr_kVg[i] = (unsigned short)(k*Vg - vmin + 0.5); + } + + /* + EKV model: + + Ids = Is*(if - ir) + Is = 2*u*Cox*Ut^2/k*W/L + if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut)) + ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut)) + */ + double kVt = fi.k*fi.Vth; + double Ut = fi.Ut; + double Is = 2*fi.uCox*Ut*Ut/fi.k*fi.WL_vcr; + // Normalized current factor for 1 cycle at 1MHz. + double N15 = N16/2; + double n_Is = N15*1.0e-6/fi.C*Is; + + // kVg_Vx = k*Vg - Vx + // I.e. if k != 1.0, Vg must be scaled accordingly. + for (int kVg_Vx = 0; kVg_Vx < (1 << 16); kVg_Vx++) { + double log_term = log1p(exp((kVg_Vx/N16 - kVt)/(2*Ut))); + // Scaled by m*2^15 + vcr_n_Ids_term[kVg_Vx] = (unsigned short)(n_Is*log_term*log_term); + } + } + + class_init = true; + } + + enable_filter(true); + set_chip_model(MOS6581); + set_voice_mask(0x07); + input(0); + reset(); +} + + +// ---------------------------------------------------------------------------- +// Enable filter. +// ---------------------------------------------------------------------------- +void Filter::enable_filter(bool enable) +{ + enabled = enable; + set_sum_mix(); +} + + +// ---------------------------------------------------------------------------- +// Adjust the DAC bias parameter of the filter. +// This gives user variable control of the exact CF -> center frequency +// mapping used by the filter. +// ---------------------------------------------------------------------------- +void Filter::adjust_filter_bias(double dac_bias) +{ + Vw_bias = int(dac_bias*model_filter[0].vo_N16); + set_w0(); + + // Gate voltage is controlled by the switched capacitor voltage divider + // Ua = Ue * v = 4.75v 1> 4) & 0x0f; + set_Q(); + + filt = res_filt & 0x0f; + set_sum_mix(); +} + +void Filter::writeMODE_VOL(reg8 mode_vol) +{ + mode = mode_vol & 0xf0; + set_sum_mix(); + + vol = mode_vol & 0x0f; +} + +// Set filter cutoff frequency. +void Filter::set_w0() +{ + { + // MOS 6581 + model_filter_t& f = model_filter[0]; + int Vw = Vw_bias + f.f0_dac[fc]; + Vddt_Vw_2 = unsigned(f.kVddt - Vw)*unsigned(f.kVddt - Vw) >> 1; + } + + { + // MOS 8580 cutoff: 0 - 12.5kHz. + model_filter_t& f = model_filter[1]; + n_dac = (n_param * f.f0_dac[fc]) >> 15; + } +} + +/* +Set filter resonance. + +In the MOS 6581, 1/Q is controlled linearly by res. From die photographs +of the resonance "resistor" ladder it follows that 1/Q ~ ~res/8 +(assuming an ideal op-amp and ideal "resistors"). This implies that Q +ranges from 0.533 (res = 0) to 8 (res = E). For res = F, Q is actually +theoretically unlimited, which is quite unheard of in a filter +circuit. + +To obtain Q ~ 1/sqrt(2) = 0.707 for maximally flat frequency response, +res should be set to 4: Q = 8/~4 = 8/11 = 0.7272 (again assuming an ideal +op-amp and ideal "resistors"). + +Q as low as 0.707 is not achievable because of low gain op-amps; res = 0 +should yield the flattest possible frequency response at Q ~ 0.8 - 1.0 +in the op-amp's pseudo-linear range (high amplitude signals will be +clipped). As resonance is increased, the filter must be clocked more +often to keep it stable. + +In the MOS 8580, the resonance "resistor" ladder above the bp feedback +op-amp is split in two parts; one ladder for the op-amp input and one +ladder for the op-amp feedback. + +input: feedback: + + Rf +Ri R4 RC R8 R3 + R2 + R1 + + +The "resistors" are switched in as follows by bits in register $17: + +feedback: +R1: bit4&!bit5 +R2: !bit4&bit5 +R3: bit4&bit5 +Rf: always on + +input: +R4: bit6&!bit7 +R8: !bit6&bit7 +RC: bit6&bit7 +Ri: !(R4|R8|RC) = !(bit6|bit7) = !bit6&!bit7 + + +The relative "resistor" values are approximately (using channel length): + +R1 = 15.3*Ri +R2 = 7.3*Ri +R3 = 4.7*Ri +Rf = 1.4*Ri +R4 = 1.4*Ri +R8 = 2.0*Ri +RC = 2.8*Ri + + +Approximate values for 1/Q can now be found as follows (assuming an +ideal op-amp): + +res feedback input -gain (1/Q) +--- -------- ----- ---------- + 0 Rf Ri Rf/Ri = 1/(Ri*(1/Rf)) = 1/0.71 + 1 Rf|R1 Ri (Rf|R1)/Ri = 1/(Ri*(1/Rf+1/R1)) = 1/0.78 + 2 Rf|R2 Ri (Rf|R2)/Ri = 1/(Ri*(1/Rf+1/R2)) = 1/0.85 + 3 Rf|R3 Ri (Rf|R3)/Ri = 1/(Ri*(1/Rf+1/R3)) = 1/0.92 + 4 Rf R4 Rf/R4 = 1/(R4*(1/Rf)) = 1/1.00 + 5 Rf|R1 R4 (Rf|R1)/R4 = 1/(R4*(1/Rf+1/R1)) = 1/1.10 + 6 Rf|R2 R4 (Rf|R2)/R4 = 1/(R4*(1/Rf+1/R2)) = 1/1.20 + 7 Rf|R3 R4 (Rf|R3)/R4 = 1/(R4*(1/Rf+1/R3)) = 1/1.30 + 8 Rf R8 Rf/R8 = 1/(R8*(1/Rf)) = 1/1.43 + 9 Rf|R1 R8 (Rf|R1)/R8 = 1/(R8*(1/Rf+1/R1)) = 1/1.56 + A Rf|R2 R8 (Rf|R2)/R8 = 1/(R8*(1/Rf+1/R2)) = 1/1.70 + B Rf|R3 R8 (Rf|R3)/R8 = 1/(R8*(1/Rf+1/R3)) = 1/1.86 + C Rf RC Rf/RC = 1/(RC*(1/Rf)) = 1/2.00 + D Rf|R1 RC (Rf|R1)/RC = 1/(RC*(1/Rf+1/R1)) = 1/2.18 + E Rf|R2 RC (Rf|R2)/RC = 1/(RC*(1/Rf+1/R2)) = 1/2.38 + F Rf|R3 RC (Rf|R3)/RC = 1/(RC*(1/Rf+1/R3)) = 1/2.60 + + +These data indicate that the following function for 1/Q has been +modeled in the MOS 8580: + + 1/Q = 2^(1/2)*2^(-x/8) = 2^(1/2 - x/8) = 2^((4 - x)/8) + +*/ +void Filter::set_Q() +{ + // Cutoff for MOS 6581. + // The coefficient 8 is dispensed of later by right-shifting 3 times + // (2 ^ 3 = 8). + _8_div_Q = ~res & 0x0f; +} + +// Set input routing bits. +void Filter::set_sum_mix() +{ + // NB! voice3off (mode bit 7) only affects voice 3 if it is routed directly + // to the mixer. + sum = (enabled ? filt : 0x00) & voice_mask; + mix = + (enabled ? (mode & 0x70) | ((~(filt | (mode & 0x80) >> 5)) & 0x0f) : 0x0f) + & voice_mask; +} + +} // namespace reSID diff --git a/C64/SID/resid/filter.h b/C64/SID/resid/filter.h new file mode 100755 index 00000000..b6780269 --- /dev/null +++ b/C64/SID/resid/filter.h @@ -0,0 +1,1792 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_FILTER_H +#define RESID_FILTER_H + +#include "resid-config.h" + +namespace reSID +{ + +// ---------------------------------------------------------------------------- +// The SID filter is modeled with a two-integrator-loop biquadratic filter, +// which has been confirmed by Bob Yannes to be the actual circuit used in +// the SID chip. +// +// Measurements show that excellent emulation of the SID filter is achieved, +// except when high resonance is combined with high sustain levels. +// In this case the SID op-amps are performing less than ideally and are +// causing some peculiar behavior of the SID filter. This however seems to +// have more effect on the overall amplitude than on the color of the sound. +// +// The theory for the filter circuit can be found in "Microelectric Circuits" +// by Adel S. Sedra and Kenneth C. Smith. +// The circuit is modeled based on the explanation found there except that +// an additional inverter is used in the feedback from the bandpass output, +// allowing the summer op-amp to operate in single-ended mode. This yields +// filter outputs with levels independent of Q, which corresponds with the +// results obtained from a real SID. +// +// We have been able to model the summer and the two integrators of the circuit +// to form components of an IIR filter. +// Vhp is the output of the summer, Vbp is the output of the first integrator, +// and Vlp is the output of the second integrator in the filter circuit. +// +// According to Bob Yannes, the active stages of the SID filter are not really +// op-amps. Rather, simple NMOS inverters are used. By biasing an inverter +// into its region of quasi-linear operation using a feedback resistor from +// input to output, a MOS inverter can be made to act like an op-amp for +// small signals centered around the switching threshold. +// +// In 2008, Michael Huth facilitated closer investigation of the SID 6581 +// filter circuit by publishing high quality microscope photographs of the die. +// Tommi Lempinen has done an impressive work on re-vectorizing and annotating +// the die photographs, substantially simplifying further analysis of the +// filter circuit. +// +// The filter schematics below are reverse engineered from these re-vectorized +// and annotated die photographs. While the filter first depicted in reSID 0.9 +// is a correct model of the basic filter, the schematics are now completed +// with the audio mixer and output stage, including details on intended +// relative resistor values. Also included are schematics for the NMOS FET +// voltage controlled resistors (VCRs) used to control cutoff frequency, the +// DAC which controls the VCRs, the NMOS op-amps, and the output buffer. +// +// +// SID 6581 filter / mixer / output +// -------------------------------- +// +// --------------------------------------------------- +// | | +// | --1R1-- \-- D7 | +// | ---R1-- | | | +// | | | |--2R1-- \--| D6 | +// | --------------|--Rw-----[A>--|--Rw-----[A>--| +// ve (EXT IN) | | | | +// D3 \ ---------------R8--| | | (CAP2A) | (CAP1A) +// | v3 | | vhp | vbp | vlp +// D2 | \ -----------R8--| ----- | | +// | | v2 | | | | +// D1 | | \ -------R8--| | ---------------- | +// | | | v1 | | | | +// D0 | | | \ ---R8-- | | --------------------------- +// | | | | | | | +// R6 R6 R6 R6 R6 R6 R6 +// | | | | $18 | | | $18 +// | \ | | D7: 1=open \ \ \ D6 - D4: 0=open +// | | | | | | | +// --------------------------------- 12V +// | +// | D3 --/ --1R2-- | +// | ---R8-- | | ---R2-- | +// | | | D2 |--/ --2R2--| | | ||-- +// ------[A>---------| |-----[A>-----|| +// D1 |--/ --4R2--| (4.25R2) ||-- +// $18 | | | +// 0=open D0 --/ --8R2-- (8.75R2) | +// +// vo (AUDIO +// OUT) +// +// +// v1 - voice 1 +// v2 - voice 2 +// v3 - voice 3 +// ve - ext in +// vhp - highpass output +// vbp - bandpass output +// vlp - lowpass output +// vo - audio out +// [A> - single ended inverting op-amp (self-biased NMOS inverter) +// Rn - "resistors", implemented with custom NMOS FETs +// Rw - cutoff frequency resistor (VCR) +// C - capacitor +// +// Notes: +// +// R2 ~ 2.0*R1 +// R6 ~ 6.0*R1 +// R8 ~ 8.0*R1 +// R24 ~ 24.0*R1 +// +// The Rn "resistors" in the circuit are implemented with custom NMOS FETs, +// probably because of space constraints on the SID die. The silicon substrate +// is laid out in a narrow strip or "snake", with a strip length proportional +// to the intended resistance. The polysilicon gate electrode covers the entire +// silicon substrate and is fixed at 12V in order for the NMOS FET to operate +// in triode mode (a.k.a. linear mode or ohmic mode). +// +// Even in "linear mode", an NMOS FET is only an approximation of a resistor, +// as the apparant resistance increases with increasing drain-to-source +// voltage. If the drain-to-source voltage should approach the gate voltage +// of 12V, the NMOS FET will enter saturation mode (a.k.a. active mode), and +// the NMOS FET will not operate anywhere like a resistor. +// +// +// +// NMOS FET voltage controlled resistor (VCR) +// ------------------------------------------ +// +// Vw +// +// | +// | +// R1 +// | +// --R1--| +// | __|__ +// | ----- +// | | | +// vi ---------- -------- vo +// | | +// ----R24---- +// +// +// vi - input +// vo - output +// Rn - "resistors", implemented with custom NMOS FETs +// Vw - voltage from 11-bit DAC (frequency cutoff control) +// +// Notes: +// +// An approximate value for R24 can be found by using the formula for the +// filter cutoff frequency: +// +// FCmin = 1/(2*pi*Rmax*C) +// +// Assuming that a the setting for minimum cutoff frequency in combination with +// a low level input signal ensures that only negligible current will flow +// through the transistor in the schematics above, values for FCmin and C can +// be substituted in this formula to find Rmax. +// Using C = 470pF and FCmin = 220Hz (measured value), we get: +// +// FCmin = 1/(2*pi*Rmax*C) +// Rmax = 1/(2*pi*FCmin*C) = 1/(2*pi*220*470e-12) ~ 1.5MOhm +// +// From this it follows that: +// R24 = Rmax ~ 1.5MOhm +// R1 ~ R24/24 ~ 64kOhm +// R2 ~ 2.0*R1 ~ 128kOhm +// R6 ~ 6.0*R1 ~ 384kOhm +// R8 ~ 8.0*R1 ~ 512kOhm +// +// Note that these are only approximate values for one particular SID chip, +// due to process variations the values can be substantially different in +// other chips. +// +// +// +// Filter frequency cutoff DAC +// --------------------------- +// +// +// 12V 10 9 8 7 6 5 4 3 2 1 0 VGND +// | | | | | | | | | | | | | Missing +// 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R termination +// | | | | | | | | | | | | | +// Vw ----R---R---R---R---R---R---R---R---R---R---R-- --- +// +// Bit on: 12V +// Bit off: 5V (VGND) +// +// As is the case with all MOS 6581 DACs, the termination to (virtual) ground +// at bit 0 is missing. +// +// Furthermore, the control of the two VCRs imposes a load on the DAC output +// which varies with the input signals to the VCRs. This can be seen from the +// VCR figure above. +// +// +// +// "Op-amp" (self-biased NMOS inverter) +// ------------------------------------ +// +// +// 12V +// +// | +// -----------| +// | | +// | ------| +// | | | +// | | ||-- +// | --|| +// | ||-- +// ||-- | +// vi -----|| |--------- vo +// ||-- | | +// | ||-- | +// |-------|| | +// | ||-- | +// ||-- | | +// --|| | | +// | ||-- | | +// | | | | +// | -----------| | +// | | | +// | | +// | GND | +// | | +// ---------------------- +// +// +// vi - input +// vo - output +// +// Notes: +// +// The schematics above are laid out to show that the "op-amp" logically +// consists of two building blocks; a saturated load NMOS inverter (on the +// right hand side of the schematics) with a buffer / bias input stage +// consisting of a variable saturated load NMOS inverter (on the left hand +// side of the schematics). +// +// Provided a reasonably high input impedance and a reasonably low output +// impedance, the "op-amp" can be modeled as a voltage transfer function +// mapping input voltage to output voltage. +// +// +// +// Output buffer (NMOS voltage follower) +// ------------------------------------- +// +// +// 12V +// +// | +// | +// ||-- +// vi -----|| +// ||-- +// | +// |------ vo +// | (AUDIO +// Rext OUT) +// | +// | +// +// GND +// +// vi - input +// vo - output +// Rext - external resistor, 1kOhm +// +// Notes: +// +// The external resistor Rext is needed to complete the NMOS voltage follower, +// this resistor has a recommended value of 1kOhm. +// +// Die photographs show that actually, two NMOS transistors are used in the +// voltage follower. However the two transistors are coupled in parallel (all +// terminals are pairwise common), which implies that we can model the two +// transistors as one. +// +// ---------------------------------------------------------------------------- +// +// SID 8580 filter / mixer / output +// -------------------------------- +// +// +---------------------------------------------------+ +// | $17 +----Rf-+ | +// | | | | +// | D4&!D5 o- \-R3-o | +// | | | $17 | +// | !D4&!D5 o- \-R2-o | +// | | | +---R8-- \--+ !D6&D7 | +// | D4&!D5 o- \-R1-o | | | +// | | | o---RC-- \--o D6&D7 | +// | +---------o----o--Rfc-o--[A>--o--Rfc-o--[A>--o +// ve (EXT IN) | | | | +// D3 \ ---------------R8--o | | (CAP2A) | (CAP1A) +// | v3 | | vhp | vbp | vlp +// D2 | \ -----------R8--o +-----+ | | +// | | v2 | | | | +// D1 | | \ -------R8--o | +----------------+ | +// | | | v1 | | | | +// D0 | | | \ ---R8--+ | | +---------------------------+ +// | | | | | | | +// R6 R6 R6 R6 R6 R6 R6 +// | | | | $18 | | | $18 +// | \ | | D7: 1=open \ \ \ D6 - D4: 0=open +// | | | | | | | +// +---o---o---o-------------o---o---+ +// | +// | D3 +--/ --1R2--+ +// | +---R8--+ | | +---R2--+ +// | | | D2 o--/ --2R2--o | | +// +---o--[A>--o------o o--o--[A>--o-- vo (AUDIO OUT) +// D1 o--/ --4R2--o (4.25R2) +// $18 | | +// 0=open D0 +--/ --8R2--+ (8.75R2) +// +// +// +// +// R1 = 15.3*Ri +// R2 = 7.3*Ri +// R3 = 4.7*Ri +// Rf = 1.4*Ri +// R4 = 1.4*Ri +// R8 = 2.0*Ri +// RC = 2.8*Ri +// +// +// +// Op-amps +// ------- +// Unlike the 6581, the 8580 has real OpAmps. +// +// Temperature compensated differential amplifier: +// +// 9V +// +// | +// +-------o-o-o-------+ +// | | | | +// | R R | +// +--|| | | ||--+ +// ||---o o---|| +// +--|| | | ||--+ +// | | | | +// o-----+ | | o--- Va +// | | | | | +// +--|| | | | ||--+ +// ||-o-+---+---|| +// +--|| | | ||--+ +// | | | | +// | | +// GND | | GND +// ||--+ +--|| +// in- -----|| ||------ in+ +// ||----o----|| +// | +// 8 Current sink +// | +// +// GND +// +// Inverter + non-inverting output amplifier: +// +// Va ---o---||-------------------o--------------------+ +// | | 9V | +// | +----------+----------+ | | +// | 9V | | 9V | ||--+ | +// | | | 9V | | +-|| | +// | R | | | ||--+ ||--+ | +// | | | ||--+ +--|| o---o--- Vout +// | o---o---|| ||--+ ||--+ +// | | ||--+ o-----|| +// | ||--+ | ||--+ ||--+ +// +-----|| o-----|| | +// ||--+ | ||--+ +// | R | GND +// | +// GND GND +// GND +// +// +// +// Virtual ground +// -------------- +// A PolySi resitive voltage divider provides the voltage +// for the non-inverting input of the filter op-amps. +// +// 5V +// +----------+ +// | | |\ | +// R1 +---|-\ | +// 5V | |A >---o--- Vref +// o-------|+/ +// | | |/ +// R10 R4 +// | | +// o---+ +// | +// R10 +// | +// +// GND +// +// Rn = n*R1 +// +// +// +// Rfc - freq control DAC resistance ladder +// ---------------------------------------- +// The resistance for the bandpass and lowpass integrator stages of the filter are determined +// by an 11 bit DAC driven by the FC register. +// If all 11 bits are '0', the impedance of the DAC would be "infinitely high". +// To get around this, there is an 11 input NOR gate below the DAC sensing those 11 bits. +// If they are all 0, the NOR gate gives the gate control voltage to the 12 bit DAC LSB. +// +// +// +// Crystal stabilized precision switched capacitor voltage divider +// --------------------------------------------------------------- +// There is a FET working as a temperature sensor close to the DACs which changes the gate voltage +// of the frequency control DACs according to the temperature, to reduce its effects on the filter curve. +// An asynchronous 3 bit binary counter, running at the speed of PHI2, drives two big capacitors +// which AC resistance is then used as a voltage divider. +// This implicates that frequency difference between PAL and NTSC might shift the filter curve by 4% or such. +// +// https://en.wikipedia.org/wiki/Switched_capacitor +// +// |\ OpAmp has a smaller capacitor +// Vref ---|+\ than the other OPs +// |A >---o--- Vdac +// o-------|-/ | +// | |/ | +// | | +// C1 | C2 | +// +---||---o---+ +---o-----||-------o +// | | | | | | +// o----+ | ----- | | +// | | | ----- +----+ +-----+ +// | ----- | | | | +// | ----- | ----- | +// | | | ----- | +// | +-----------+ | | +// | /Q Q | +-------+ +// GND +-----------+ FET close to DAC +// | clk/8 | working as temperature sensor +// +-----------+ +// | | +// clk1 clk2 +// + +// Compile-time computation of op-amp summer and mixer table offsets. + +// The highpass summer has 2 - 6 inputs (bandpass, lowpass, and 0 - 4 voices). +template +struct summer_offset +{ + enum { value = summer_offset::value + ((2 + i - 1) << 16) }; +}; + +template<> +struct summer_offset<0> +{ + enum { value = 0 }; +}; + +// The mixer has 0 - 7 inputs (0 - 4 voices and 0 - 3 filter outputs). +template +struct mixer_offset +{ + enum { value = mixer_offset::value + ((i - 1) << 16) }; +}; + +template<> +struct mixer_offset<1> +{ + enum { value = 1 }; +}; + +template<> +struct mixer_offset<0> +{ + enum { value = 0 }; +}; + + +class Filter +{ +public: + Filter(); + + void enable_filter(bool enable); + void adjust_filter_bias(double dac_bias); + void set_chip_model(chip_model model); + void set_voice_mask(reg4 mask); + + void clock(int voice1, int voice2, int voice3); + void clock(cycle_count delta_t, int voice1, int voice2, int voice3); + void reset(); + + // Write registers. + void writeFC_LO(reg8); + void writeFC_HI(reg8); + void writeRES_FILT(reg8); + void writeMODE_VOL(reg8); + + // SID audio input (16 bits). + void input(short sample); + + // SID audio output (16 bits). + short output(); + +protected: + void set_sum_mix(); + void set_w0(); + void set_Q(); + + // Filter enabled. + bool enabled; + + // Filter cutoff frequency. + reg12 fc; + + // Filter resonance. + reg8 res; + + // Selects which voices to route through the filter. + reg8 filt; + + // Selects which filter outputs to route into the mixer. + reg4 mode; + + // Output master volume. + reg4 vol; + + // Used to mask out EXT IN if not connected, and for test purposes + // (voice muting). + reg8 voice_mask; + + // Select which inputs to route into the summer / mixer. + // These are derived from filt, mode, and voice_mask. + reg8 sum; + reg8 mix; + + // State of filter. + int Vhp; // highpass + int Vbp; // bandpass + int Vbp_x, Vbp_vc; + int Vlp; // lowpass + int Vlp_x, Vlp_vc; + // Filter / mixer inputs. + int ve; + int v3; + int v2; + int v1; + + chip_model sid_model; + + typedef struct { + unsigned short vx; + short dvx; + } opamp_t; + + typedef struct { + int kVddt; // K*(Vdd - Vth) + int voice_scale_s14; + int voice_DC; + int ak; + int bk; + int vc_min; + int vc_max; + double vo_N16; // Fixed point scaling for 16 bit op-amp output. + + // Reverse op-amp transfer function. + unsigned short opamp_rev[1 << 16]; + // Lookup tables for gain and summer op-amps in output stage / filter. + unsigned short summer[summer_offset<5>::value]; + unsigned short gain[16][1 << 16]; + unsigned short mixer[mixer_offset<8>::value]; + // Cutoff frequency DAC output voltage table. FC is an 11 bit register. + unsigned short f0_dac[1 << 11]; + } model_filter_t; + + // 6581 only + // Cutoff frequency DAC voltage, resonance. + int Vddt_Vw_2, Vw_bias; + int _8_div_Q; + + static int n_snake; + + // 8580 only + int n_dac; + + static int n_param; + + // DAC gate voltage + int kVgt; + + // Lookup tables for resonance + static unsigned short resonance[16][1 << 16]; + + int solve_gain(opamp_t* opamp, int n, int vi_t, int& x, model_filter_t& mf); + int solve_integrate_6581(int dt, int vi_t, int& x, int& vc, model_filter_t& mf); + int solve_integrate_8580(int dt, int vi_t, int& x, int& vc, model_filter_t& mf); + + // VCR - 6581 only. + static unsigned short vcr_kVg[1 << 16]; + static unsigned short vcr_n_Ids_term[1 << 16]; + // Common parameters. + static model_filter_t model_filter[2]; + +friend class SID; +}; + + +// ---------------------------------------------------------------------------- +// Inline functions. +// The following functions are defined inline because they are called every +// time a sample is calculated. +// ---------------------------------------------------------------------------- + +#if RESID_INLINING || defined(RESID_FILTER_CC) + +// ---------------------------------------------------------------------------- +// SID clocking - 1 cycle. +// ---------------------------------------------------------------------------- +RESID_INLINE +void Filter::clock(int voice1, int voice2, int voice3) +{ + model_filter_t& f = model_filter[sid_model]; + + v1 = (voice1*f.voice_scale_s14 >> 18) + f.voice_DC; + v2 = (voice2*f.voice_scale_s14 >> 18) + f.voice_DC; + v3 = (voice3*f.voice_scale_s14 >> 18) + f.voice_DC; + + // Sum inputs routed into the filter. + int Vi = 0; + int offset = 0; + + switch (sum & 0xf) { + case 0x0: + Vi = 0; + offset = summer_offset<0>::value; + break; + case 0x1: + Vi = v1; + offset = summer_offset<1>::value; + break; + case 0x2: + Vi = v2; + offset = summer_offset<1>::value; + break; + case 0x3: + Vi = v2 + v1; + offset = summer_offset<2>::value; + break; + case 0x4: + Vi = v3; + offset = summer_offset<1>::value; + break; + case 0x5: + Vi = v3 + v1; + offset = summer_offset<2>::value; + break; + case 0x6: + Vi = v3 + v2; + offset = summer_offset<2>::value; + break; + case 0x7: + Vi = v3 + v2 + v1; + offset = summer_offset<3>::value; + break; + case 0x8: + Vi = ve; + offset = summer_offset<1>::value; + break; + case 0x9: + Vi = ve + v1; + offset = summer_offset<2>::value; + break; + case 0xa: + Vi = ve + v2; + offset = summer_offset<2>::value; + break; + case 0xb: + Vi = ve + v2 + v1; + offset = summer_offset<3>::value; + break; + case 0xc: + Vi = ve + v3; + offset = summer_offset<2>::value; + break; + case 0xd: + Vi = ve + v3 + v1; + offset = summer_offset<3>::value; + break; + case 0xe: + Vi = ve + v3 + v2; + offset = summer_offset<3>::value; + break; + case 0xf: + Vi = ve + v3 + v2 + v1; + offset = summer_offset<4>::value; + break; + } + + // Calculate filter outputs. + if (sid_model == 0) { + // MOS 6581. + Vlp = solve_integrate_6581(1, Vbp, Vlp_x, Vlp_vc, f); + Vbp = solve_integrate_6581(1, Vhp, Vbp_x, Vbp_vc, f); + Vhp = f.summer[offset + f.gain[_8_div_Q][Vbp] + Vlp + Vi]; + } + else { + // MOS 8580. + Vlp = solve_integrate_8580(1, Vbp, Vlp_x, Vlp_vc, f); + Vbp = solve_integrate_8580(1, Vhp, Vbp_x, Vbp_vc, f); + Vhp = f.summer[offset + resonance[res][Vbp] + Vlp + Vi]; + } +} + +// ---------------------------------------------------------------------------- +// SID clocking - delta_t cycles. +// ---------------------------------------------------------------------------- +RESID_INLINE +void Filter::clock(cycle_count delta_t, int voice1, int voice2, int voice3) +{ + model_filter_t& f = model_filter[sid_model]; + + v1 = (voice1*f.voice_scale_s14 >> 18) + f.voice_DC; + v2 = (voice2*f.voice_scale_s14 >> 18) + f.voice_DC; + v3 = (voice3*f.voice_scale_s14 >> 18) + f.voice_DC; + + // Enable filter on/off. + // This is not really part of SID, but is useful for testing. + // On slow CPUs it may be necessary to bypass the filter to lower the CPU + // load. + if (unlikely(!enabled)) { + return; + } + + // Sum inputs routed into the filter. + int Vi = 0; + int offset = 0; + + switch (sum & 0xf) { + case 0x0: + Vi = 0; + offset = summer_offset<0>::value; + break; + case 0x1: + Vi = v1; + offset = summer_offset<1>::value; + break; + case 0x2: + Vi = v2; + offset = summer_offset<1>::value; + break; + case 0x3: + Vi = v2 + v1; + offset = summer_offset<2>::value; + break; + case 0x4: + Vi = v3; + offset = summer_offset<1>::value; + break; + case 0x5: + Vi = v3 + v1; + offset = summer_offset<2>::value; + break; + case 0x6: + Vi = v3 + v2; + offset = summer_offset<2>::value; + break; + case 0x7: + Vi = v3 + v2 + v1; + offset = summer_offset<3>::value; + break; + case 0x8: + Vi = ve; + offset = summer_offset<1>::value; + break; + case 0x9: + Vi = ve + v1; + offset = summer_offset<2>::value; + break; + case 0xa: + Vi = ve + v2; + offset = summer_offset<2>::value; + break; + case 0xb: + Vi = ve + v2 + v1; + offset = summer_offset<3>::value; + break; + case 0xc: + Vi = ve + v3; + offset = summer_offset<2>::value; + break; + case 0xd: + Vi = ve + v3 + v1; + offset = summer_offset<3>::value; + break; + case 0xe: + Vi = ve + v3 + v2; + offset = summer_offset<3>::value; + break; + case 0xf: + Vi = ve + v3 + v2 + v1; + offset = summer_offset<4>::value; + break; + } + + // Maximum delta cycles for filter fixpoint iteration to converge + // is approximately 3. + cycle_count delta_t_flt = 3; + + if (sid_model == 0) { + // MOS 6581. + while (delta_t) { + if (unlikely(delta_t < delta_t_flt)) { + delta_t_flt = delta_t; + } + + // Calculate filter outputs. + Vlp = solve_integrate_6581(delta_t_flt, Vbp, Vlp_x, Vlp_vc, f); + Vbp = solve_integrate_6581(delta_t_flt, Vhp, Vbp_x, Vbp_vc, f); + Vhp = f.summer[offset + f.gain[_8_div_Q][Vbp] + Vlp + Vi]; + + delta_t -= delta_t_flt; + } + } + else { + // MOS 8580. + while (delta_t) { + if (unlikely(delta_t < delta_t_flt)) { + delta_t_flt = delta_t; + } + + // Calculate filter outputs. + Vlp = solve_integrate_8580(delta_t_flt, Vbp, Vlp_x, Vlp_vc, f); + Vbp = solve_integrate_8580(delta_t_flt, Vhp, Vbp_x, Vbp_vc, f); + Vhp = f.summer[offset + resonance[res][Vbp] + Vlp + Vi]; + + delta_t -= delta_t_flt; + } + } +} + + +// ---------------------------------------------------------------------------- +// SID audio input (16 bits). +// ---------------------------------------------------------------------------- +RESID_INLINE +void Filter::input(short sample) +{ + // Scale to three times the peak-to-peak for one voice and add the op-amp + // "zero" DC level. + // NB! Adding the op-amp "zero" DC level is a (wildly inaccurate) + // approximation of feeding the input through an AC coupling capacitor. + // This could be implemented as a separate filter circuit, however the + // primary use of the emulator is not to process external signals. + // The upside is that the MOS8580 "digi boost" works without a separate (DC) + // input interface. + // Note that the input is 16 bits, compared to the 20 bit voice output. + model_filter_t& f = model_filter[sid_model]; + ve = (sample*f.voice_scale_s14*3 >> 14) + f.mixer[0]; +} + + +// ---------------------------------------------------------------------------- +// SID audio output (16 bits). +// ---------------------------------------------------------------------------- +RESID_INLINE +short Filter::output() +{ + model_filter_t& f = model_filter[sid_model]; + + // Writing the switch below manually would be tedious and error-prone; + // it is rather generated by the following Perl program: + + /* +my @i = qw(v1 v2 v3 ve Vlp Vbp Vhp); +for my $mix (0..2**@i-1) { + print sprintf(" case 0x%02x:\n", $mix); + my @sum; + for (@i) { + unshift(@sum, $_) if $mix & 0x01; + $mix >>= 1; + } + my $sum = join(" + ", @sum) || "0"; + print " Vi = $sum;\n"; + print " offset = mixer_offset<" . @sum . ">::value;\n"; + print " break;\n"; +} + */ + + // Sum inputs routed into the mixer. + int Vi = 0; + int offset = 0; + + switch (mix & 0x7f) { + case 0x00: + Vi = 0; + offset = mixer_offset<0>::value; + break; + case 0x01: + Vi = v1; + offset = mixer_offset<1>::value; + break; + case 0x02: + Vi = v2; + offset = mixer_offset<1>::value; + break; + case 0x03: + Vi = v2 + v1; + offset = mixer_offset<2>::value; + break; + case 0x04: + Vi = v3; + offset = mixer_offset<1>::value; + break; + case 0x05: + Vi = v3 + v1; + offset = mixer_offset<2>::value; + break; + case 0x06: + Vi = v3 + v2; + offset = mixer_offset<2>::value; + break; + case 0x07: + Vi = v3 + v2 + v1; + offset = mixer_offset<3>::value; + break; + case 0x08: + Vi = ve; + offset = mixer_offset<1>::value; + break; + case 0x09: + Vi = ve + v1; + offset = mixer_offset<2>::value; + break; + case 0x0a: + Vi = ve + v2; + offset = mixer_offset<2>::value; + break; + case 0x0b: + Vi = ve + v2 + v1; + offset = mixer_offset<3>::value; + break; + case 0x0c: + Vi = ve + v3; + offset = mixer_offset<2>::value; + break; + case 0x0d: + Vi = ve + v3 + v1; + offset = mixer_offset<3>::value; + break; + case 0x0e: + Vi = ve + v3 + v2; + offset = mixer_offset<3>::value; + break; + case 0x0f: + Vi = ve + v3 + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x10: + Vi = Vlp; + offset = mixer_offset<1>::value; + break; + case 0x11: + Vi = Vlp + v1; + offset = mixer_offset<2>::value; + break; + case 0x12: + Vi = Vlp + v2; + offset = mixer_offset<2>::value; + break; + case 0x13: + Vi = Vlp + v2 + v1; + offset = mixer_offset<3>::value; + break; + case 0x14: + Vi = Vlp + v3; + offset = mixer_offset<2>::value; + break; + case 0x15: + Vi = Vlp + v3 + v1; + offset = mixer_offset<3>::value; + break; + case 0x16: + Vi = Vlp + v3 + v2; + offset = mixer_offset<3>::value; + break; + case 0x17: + Vi = Vlp + v3 + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x18: + Vi = Vlp + ve; + offset = mixer_offset<2>::value; + break; + case 0x19: + Vi = Vlp + ve + v1; + offset = mixer_offset<3>::value; + break; + case 0x1a: + Vi = Vlp + ve + v2; + offset = mixer_offset<3>::value; + break; + case 0x1b: + Vi = Vlp + ve + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x1c: + Vi = Vlp + ve + v3; + offset = mixer_offset<3>::value; + break; + case 0x1d: + Vi = Vlp + ve + v3 + v1; + offset = mixer_offset<4>::value; + break; + case 0x1e: + Vi = Vlp + ve + v3 + v2; + offset = mixer_offset<4>::value; + break; + case 0x1f: + Vi = Vlp + ve + v3 + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x20: + Vi = Vbp; + offset = mixer_offset<1>::value; + break; + case 0x21: + Vi = Vbp + v1; + offset = mixer_offset<2>::value; + break; + case 0x22: + Vi = Vbp + v2; + offset = mixer_offset<2>::value; + break; + case 0x23: + Vi = Vbp + v2 + v1; + offset = mixer_offset<3>::value; + break; + case 0x24: + Vi = Vbp + v3; + offset = mixer_offset<2>::value; + break; + case 0x25: + Vi = Vbp + v3 + v1; + offset = mixer_offset<3>::value; + break; + case 0x26: + Vi = Vbp + v3 + v2; + offset = mixer_offset<3>::value; + break; + case 0x27: + Vi = Vbp + v3 + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x28: + Vi = Vbp + ve; + offset = mixer_offset<2>::value; + break; + case 0x29: + Vi = Vbp + ve + v1; + offset = mixer_offset<3>::value; + break; + case 0x2a: + Vi = Vbp + ve + v2; + offset = mixer_offset<3>::value; + break; + case 0x2b: + Vi = Vbp + ve + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x2c: + Vi = Vbp + ve + v3; + offset = mixer_offset<3>::value; + break; + case 0x2d: + Vi = Vbp + ve + v3 + v1; + offset = mixer_offset<4>::value; + break; + case 0x2e: + Vi = Vbp + ve + v3 + v2; + offset = mixer_offset<4>::value; + break; + case 0x2f: + Vi = Vbp + ve + v3 + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x30: + Vi = Vbp + Vlp; + offset = mixer_offset<2>::value; + break; + case 0x31: + Vi = Vbp + Vlp + v1; + offset = mixer_offset<3>::value; + break; + case 0x32: + Vi = Vbp + Vlp + v2; + offset = mixer_offset<3>::value; + break; + case 0x33: + Vi = Vbp + Vlp + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x34: + Vi = Vbp + Vlp + v3; + offset = mixer_offset<3>::value; + break; + case 0x35: + Vi = Vbp + Vlp + v3 + v1; + offset = mixer_offset<4>::value; + break; + case 0x36: + Vi = Vbp + Vlp + v3 + v2; + offset = mixer_offset<4>::value; + break; + case 0x37: + Vi = Vbp + Vlp + v3 + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x38: + Vi = Vbp + Vlp + ve; + offset = mixer_offset<3>::value; + break; + case 0x39: + Vi = Vbp + Vlp + ve + v1; + offset = mixer_offset<4>::value; + break; + case 0x3a: + Vi = Vbp + Vlp + ve + v2; + offset = mixer_offset<4>::value; + break; + case 0x3b: + Vi = Vbp + Vlp + ve + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x3c: + Vi = Vbp + Vlp + ve + v3; + offset = mixer_offset<4>::value; + break; + case 0x3d: + Vi = Vbp + Vlp + ve + v3 + v1; + offset = mixer_offset<5>::value; + break; + case 0x3e: + Vi = Vbp + Vlp + ve + v3 + v2; + offset = mixer_offset<5>::value; + break; + case 0x3f: + Vi = Vbp + Vlp + ve + v3 + v2 + v1; + offset = mixer_offset<6>::value; + break; + case 0x40: + Vi = Vhp; + offset = mixer_offset<1>::value; + break; + case 0x41: + Vi = Vhp + v1; + offset = mixer_offset<2>::value; + break; + case 0x42: + Vi = Vhp + v2; + offset = mixer_offset<2>::value; + break; + case 0x43: + Vi = Vhp + v2 + v1; + offset = mixer_offset<3>::value; + break; + case 0x44: + Vi = Vhp + v3; + offset = mixer_offset<2>::value; + break; + case 0x45: + Vi = Vhp + v3 + v1; + offset = mixer_offset<3>::value; + break; + case 0x46: + Vi = Vhp + v3 + v2; + offset = mixer_offset<3>::value; + break; + case 0x47: + Vi = Vhp + v3 + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x48: + Vi = Vhp + ve; + offset = mixer_offset<2>::value; + break; + case 0x49: + Vi = Vhp + ve + v1; + offset = mixer_offset<3>::value; + break; + case 0x4a: + Vi = Vhp + ve + v2; + offset = mixer_offset<3>::value; + break; + case 0x4b: + Vi = Vhp + ve + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x4c: + Vi = Vhp + ve + v3; + offset = mixer_offset<3>::value; + break; + case 0x4d: + Vi = Vhp + ve + v3 + v1; + offset = mixer_offset<4>::value; + break; + case 0x4e: + Vi = Vhp + ve + v3 + v2; + offset = mixer_offset<4>::value; + break; + case 0x4f: + Vi = Vhp + ve + v3 + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x50: + Vi = Vhp + Vlp; + offset = mixer_offset<2>::value; + break; + case 0x51: + Vi = Vhp + Vlp + v1; + offset = mixer_offset<3>::value; + break; + case 0x52: + Vi = Vhp + Vlp + v2; + offset = mixer_offset<3>::value; + break; + case 0x53: + Vi = Vhp + Vlp + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x54: + Vi = Vhp + Vlp + v3; + offset = mixer_offset<3>::value; + break; + case 0x55: + Vi = Vhp + Vlp + v3 + v1; + offset = mixer_offset<4>::value; + break; + case 0x56: + Vi = Vhp + Vlp + v3 + v2; + offset = mixer_offset<4>::value; + break; + case 0x57: + Vi = Vhp + Vlp + v3 + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x58: + Vi = Vhp + Vlp + ve; + offset = mixer_offset<3>::value; + break; + case 0x59: + Vi = Vhp + Vlp + ve + v1; + offset = mixer_offset<4>::value; + break; + case 0x5a: + Vi = Vhp + Vlp + ve + v2; + offset = mixer_offset<4>::value; + break; + case 0x5b: + Vi = Vhp + Vlp + ve + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x5c: + Vi = Vhp + Vlp + ve + v3; + offset = mixer_offset<4>::value; + break; + case 0x5d: + Vi = Vhp + Vlp + ve + v3 + v1; + offset = mixer_offset<5>::value; + break; + case 0x5e: + Vi = Vhp + Vlp + ve + v3 + v2; + offset = mixer_offset<5>::value; + break; + case 0x5f: + Vi = Vhp + Vlp + ve + v3 + v2 + v1; + offset = mixer_offset<6>::value; + break; + case 0x60: + Vi = Vhp + Vbp; + offset = mixer_offset<2>::value; + break; + case 0x61: + Vi = Vhp + Vbp + v1; + offset = mixer_offset<3>::value; + break; + case 0x62: + Vi = Vhp + Vbp + v2; + offset = mixer_offset<3>::value; + break; + case 0x63: + Vi = Vhp + Vbp + v2 + v1; + offset = mixer_offset<4>::value; + break; + case 0x64: + Vi = Vhp + Vbp + v3; + offset = mixer_offset<3>::value; + break; + case 0x65: + Vi = Vhp + Vbp + v3 + v1; + offset = mixer_offset<4>::value; + break; + case 0x66: + Vi = Vhp + Vbp + v3 + v2; + offset = mixer_offset<4>::value; + break; + case 0x67: + Vi = Vhp + Vbp + v3 + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x68: + Vi = Vhp + Vbp + ve; + offset = mixer_offset<3>::value; + break; + case 0x69: + Vi = Vhp + Vbp + ve + v1; + offset = mixer_offset<4>::value; + break; + case 0x6a: + Vi = Vhp + Vbp + ve + v2; + offset = mixer_offset<4>::value; + break; + case 0x6b: + Vi = Vhp + Vbp + ve + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x6c: + Vi = Vhp + Vbp + ve + v3; + offset = mixer_offset<4>::value; + break; + case 0x6d: + Vi = Vhp + Vbp + ve + v3 + v1; + offset = mixer_offset<5>::value; + break; + case 0x6e: + Vi = Vhp + Vbp + ve + v3 + v2; + offset = mixer_offset<5>::value; + break; + case 0x6f: + Vi = Vhp + Vbp + ve + v3 + v2 + v1; + offset = mixer_offset<6>::value; + break; + case 0x70: + Vi = Vhp + Vbp + Vlp; + offset = mixer_offset<3>::value; + break; + case 0x71: + Vi = Vhp + Vbp + Vlp + v1; + offset = mixer_offset<4>::value; + break; + case 0x72: + Vi = Vhp + Vbp + Vlp + v2; + offset = mixer_offset<4>::value; + break; + case 0x73: + Vi = Vhp + Vbp + Vlp + v2 + v1; + offset = mixer_offset<5>::value; + break; + case 0x74: + Vi = Vhp + Vbp + Vlp + v3; + offset = mixer_offset<4>::value; + break; + case 0x75: + Vi = Vhp + Vbp + Vlp + v3 + v1; + offset = mixer_offset<5>::value; + break; + case 0x76: + Vi = Vhp + Vbp + Vlp + v3 + v2; + offset = mixer_offset<5>::value; + break; + case 0x77: + Vi = Vhp + Vbp + Vlp + v3 + v2 + v1; + offset = mixer_offset<6>::value; + break; + case 0x78: + Vi = Vhp + Vbp + Vlp + ve; + offset = mixer_offset<4>::value; + break; + case 0x79: + Vi = Vhp + Vbp + Vlp + ve + v1; + offset = mixer_offset<5>::value; + break; + case 0x7a: + Vi = Vhp + Vbp + Vlp + ve + v2; + offset = mixer_offset<5>::value; + break; + case 0x7b: + Vi = Vhp + Vbp + Vlp + ve + v2 + v1; + offset = mixer_offset<6>::value; + break; + case 0x7c: + Vi = Vhp + Vbp + Vlp + ve + v3; + offset = mixer_offset<5>::value; + break; + case 0x7d: + Vi = Vhp + Vbp + Vlp + ve + v3 + v1; + offset = mixer_offset<6>::value; + break; + case 0x7e: + Vi = Vhp + Vbp + Vlp + ve + v3 + v2; + offset = mixer_offset<6>::value; + break; + case 0x7f: + Vi = Vhp + Vbp + Vlp + ve + v3 + v2 + v1; + offset = mixer_offset<7>::value; + break; + } + + // Sum the inputs in the mixer and run the mixer output through the gain. + return (short)(f.gain[vol][f.mixer[offset + Vi]] - (1 << 15)); +} + + +/* +Find output voltage in inverting gain and inverting summer SID op-amp +circuits, using a combination of Newton-Raphson and bisection. + + ---R2-- + | | + vi ---R1-----[A>----- vo + vx + +From Kirchoff's current law it follows that + + IR1f + IR2r = 0 + +Substituting the triode mode transistor model K*W/L*(Vgst^2 - Vgdt^2) +for the currents, we get: + + n*((Vddt - vx)^2 - (Vddt - vi)^2) + (Vddt - vx)^2 - (Vddt - vo)^2 = 0 + +Our root function f can thus be written as: + + f = (n + 1)*(Vddt - vx)^2 - n*(Vddt - vi)^2 - (Vddt - vo)^2 = 0 + +We are using the mapping function x = vo - vx -> vx. We thus substitute +for vo = vx + x and get: + + f = (n + 1)*(Vddt - vx)^2 - n*(Vddt - vi)^2 - (Vddt - (vx + x))^2 = 0 + +Using substitution constants + + a = n + 1 + b = Vddt + c = n*(Vddt - vi)^2 + +the equations for the root function and its derivative can be written as: + + f = a*(b - vx)^2 - c - (b - (vx + x))^2 + df = 2*((b - (vx + x))*(dvx + 1) - a*(b - vx)*dvx) +*/ +RESID_INLINE +int Filter::solve_gain(opamp_t* opamp, int n, int vi, int& x, model_filter_t& mf) +{ + // Note that all variables are translated and scaled in order to fit + // in 16 bits. It is not necessary to explicitly translate the variables here, + // since they are all used in subtractions which cancel out the translation: + // (a - t) - (b - t) = a - b + + // Start off with an estimate of x and a root bracket [ak, bk]. + // f is increasing, so that f(ak) < 0 and f(bk) > 0. + int ak = mf.ak, bk = mf.bk; + + int a = n + (1 << 7); // Scaled by 2^7 + int b = mf.kVddt; // Scaled by m*2^16 + int b_vi = b - vi; // Scaled by m*2^16 + if (b_vi < 0) b_vi = 0; + int c = n*int(unsigned(b_vi)*unsigned(b_vi) >> 12); // Scaled by m^2*2^27 + + for (;;) { + int xk = x; + + // Calculate f and df. + int vx = opamp[x].vx; // Scaled by m*2^16 + int dvx = opamp[x].dvx; // Scaled by 2^11 + + // f = a*(b - vx)^2 - c - (b - vo)^2 + // df = 2*((b - vo)*(dvx + 1) - a*(b - vx)*dvx) + // + int vo = vx + (x << 1) - (1 << 16); + if (vo >= (1 << 16)) { + vo = (1 << 16) - 1; + } + else if (vo < 0) { + vo = 0; + } + int b_vx = b - vx; + if (b_vx < 0) b_vx = 0; + int b_vo = b - vo; + if (b_vo < 0) b_vo = 0; + // The dividend is scaled by m^2*2^27. + int f = a*int(unsigned(b_vx)*unsigned(b_vx) >> 12) - c - int(unsigned(b_vo)*unsigned(b_vo) >> 5); + // The divisor is scaled by m*2^11. + int df = (b_vo*(dvx + (1 << 11)) - a*(b_vx*dvx >> 7)) >> 15; + // The resulting quotient is thus scaled by m*2^16. + + // Newton-Raphson step: xk1 = xk - f(xk)/f'(xk) + // If f(xk) or f'(xk) are zero then we can't improve further. + if (df) { + x -= f/df; + } + if (unlikely(x == xk)) { + // No further root improvement possible. + return vo; + } + + // Narrow down root bracket. + if (f < 0) { + // f(xk) < 0 + ak = xk; + } + else { + // f(xk) > 0 + bk = xk; + } + + if (unlikely(x <= ak) || unlikely(x >= bk)) { + // Bisection step (ala Dekker's method). + x = (ak + bk) >> 1; + if (unlikely(x == ak)) { + // No further bisection possible. + return vo; + } + } + } +} + + +/* +Find output voltage in inverting integrator SID op-amp circuits, using a +single fixpoint iteration step. + +A circuit diagram of a MOS 6581 integrator is shown below. + + ---C--- + | | + vi -----Rw-------[A>----- vo + | | vx + --Rs-- + +From Kirchoff's current law it follows that + + IRw + IRs + ICr = 0 + +Using the formula for current through a capacitor, i = C*dv/dt, we get + + IRw + IRs + C*(vc - vc0)/dt = 0 + dt/C*(IRw + IRs) + vc - vc0 = 0 + vc = vc0 - n*(IRw(vi,vx) + IRs(vi,vx)) + +which may be rewritten as the following iterative fixpoint function: + + vc = vc0 - n*(IRw(vi,g(vc)) + IRs(vi,g(vc))) + +To accurately calculate the currents through Rs and Rw, we need to use +transistor models. Rs has a gate voltage of Vdd = 12V, and can be +assumed to always be in triode mode. For Rw, the situation is rather +more complex, as it turns out that this transistor will operate in +both subthreshold, triode, and saturation modes. + +The Shichman-Hodges transistor model routinely used in textbooks may +be written as follows: + + Ids = 0 , Vgst < 0 (subthreshold mode) + Ids = K/2*W/L*(2*Vgst - Vds)*Vds , Vgst >= 0, Vds < Vgst (triode mode) + Ids = K/2*W/L*Vgst^2 , Vgst >= 0, Vds >= Vgst (saturation mode) + + where + K = u*Cox (conductance) + W/L = ratio between substrate width and length + Vgst = Vg - Vs - Vt (overdrive voltage) + +This transistor model is also called the quadratic model. + +Note that the equation for the triode mode can be reformulated as +independent terms depending on Vgs and Vgd, respectively, by the +following substitution: + + Vds = Vgst - (Vgst - Vds) = Vgst - Vgdt + + Ids = K*W/L*(2*Vgst - Vds)*Vds + = K*W/L*(2*Vgst - (Vgst - Vgdt)*(Vgst - Vgdt) + = K*W/L*(Vgst + Vgdt)*(Vgst - Vgdt) + = K*W/L*(Vgst^2 - Vgdt^2) + +This turns out to be a general equation which covers both the triode +and saturation modes (where the second term is 0 in saturation mode). +The equation is also symmetrical, i.e. it can calculate negative +currents without any change of parameters (since the terms for drain +and source are identical except for the sign). + +FIXME: Subthreshold as function of Vgs, Vgd. + Ids = I0*e^(Vgst/(n*VT)) , Vgst < 0 (subthreshold mode) + +The remaining problem with the textbook model is that the transition +from subthreshold the triode/saturation is not continuous. + +Realizing that the subthreshold and triode/saturation modes may both +be defined by independent (and equal) terms of Vgs and Vds, +respectively, the corresponding terms can be blended into (equal) +continuous functions suitable for table lookup. + +The EKV model (Enz, Krummenacher and Vittoz) essentially performs this +blending using an elegant mathematical formulation: + + Ids = Is*(if - ir) + Is = 2*u*Cox*Ut^2/k*W/L + if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut)) + ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut)) + +For our purposes, the EKV model preserves two important properties +discussed above: + +- It consists of two independent terms, which can be represented by + the same lookup table. +- It is symmetrical, i.e. it calculates current in both directions, + facilitating a branch-free implementation. + +Rw in the circuit diagram above is a VCR (voltage controlled resistor), +as shown in the circuit diagram below. + + Vw + + | + Vdd | + |---| + _|_ | + -- --| Vg + | __|__ + | ----- Rw + | | | + vi ------------ -------- vo + + +In order to calculalate the current through the VCR, its gate voltage +must be determined. + +Assuming triode mode and applying Kirchoff's current law, we get the +following equation for Vg: + +u*Cox/2*W/L*((Vddt - Vg)^2 - (Vddt - vi)^2 + (Vddt - Vg)^2 - (Vddt - Vw)^2) = 0 +2*(Vddt - Vg)^2 - (Vddt - vi)^2 - (Vddt - Vw)^2 = 0 +(Vddt - Vg) = sqrt(((Vddt - vi)^2 + (Vddt - Vw)^2)/2) + +Vg = Vddt - sqrt(((Vddt - vi)^2 + (Vddt - Vw)^2)/2) + +*/ +RESID_INLINE +int Filter::solve_integrate_6581(int dt, int vi, int& vx, int& vc, model_filter_t& mf) +{ + // Note that all variables are translated and scaled in order to fit + // in 16 bits. It is not necessary to explicitly translate the variables here, + // since they are all used in subtractions which cancel out the translation: + // (a - t) - (b - t) = a - b + + int kVddt = mf.kVddt; // Scaled by m*2^16 + + // "Snake" voltages for triode mode calculation. + unsigned int Vgst = kVddt - vx; + unsigned int Vgdt = kVddt - vi; + unsigned int Vgdt_2 = Vgdt*Vgdt; + + // "Snake" current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 + int n_I_snake = n_snake*(int(Vgst*Vgst - Vgdt_2) >> 15); + + // VCR gate voltage. // Scaled by m*2^16 + // Vg = Vddt - sqrt(((Vddt - Vw)^2 + Vgdt^2)/2) + int kVg = vcr_kVg[(Vddt_Vw_2 + (Vgdt_2 >> 1)) >> 16]; + + // VCR voltages for EKV model table lookup. + int Vgs = kVg - vx; + if (Vgs < 0) Vgs = 0; + int Vgd = kVg - vi; + if (Vgd < 0) Vgd = 0; + + // VCR current, scaled by m*2^15*2^15 = m*2^30 + int n_I_vcr = int(unsigned(vcr_n_Ids_term[Vgs] - vcr_n_Ids_term[Vgd]) << 15); + + // Change in capacitor charge. + vc -= (n_I_snake + n_I_vcr)*dt; + +/* + // FIXME: Determine whether this check is necessary. + if (vc < mf.vc_min) { + vc = mf.vc_min; + } + else if (vc > mf.vc_max) { + vc = mf.vc_max; + } +*/ + + // vx = g(vc) + vx = mf.opamp_rev[(vc >> 15) + (1 << 15)]; + + // Return vo. + return vx + (vc >> 14); +} + +/* +The 8580 integrator is similar to those found in 6581 +but the resistance is formed by multiple NMOS transistors +in parallel controlled by the fc bits where the gate voltage +is driven by a temperature dependent voltage divider. + + ---C--- + | | + vi -----Rfc------[A>----- vo + vx + + IRfc + ICr = 0 + IRfc + C*(vc - vc0)/dt = 0 + dt/C*(IRfc) + vc - vc0 = 0 + vc = vc0 - n*(IRfc(vi,vx)) + vc = vc0 - n*(IRfc(vi,g(vc))) + +IRfc = K*W/L*(Vgst^2 - Vgdt^2) = n*((Vgt - vx)^2 - (Vgt - vi)^2) +*/ +RESID_INLINE +int Filter::solve_integrate_8580(int dt, int vi, int& vx, int& vc, model_filter_t& mf) +{ + // Note that all variables are translated and scaled in order to fit + // in 16 bits. It is not necessary to explicitly translate the variables here, + // since they are all used in subtractions which cancel out the translation: + // (a - t) - (b - t) = a - b + + // Dac voltages. + unsigned int Vgst = kVgt - vx; + unsigned int Vgdt = (vi < kVgt) ? kVgt - vi : 0; // triode/saturation mode + + // Dac current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30 + int n_I_rfc = n_dac*(int(Vgst*Vgst - Vgdt*Vgdt) >> 15); + + // Change in capacitor charge. + vc -= n_I_rfc*dt; + + // vx = g(vc) + vx = mf.opamp_rev[(vc >> 15) + (1 << 15)]; + + // Return vo. + return vx + (vc >> 14); +} + +#endif // RESID_INLINING || defined(RESID_FILTER_CC) + +} // namespace reSID + +#endif // not RESID_FILTER_H diff --git a/C64/SID/resid/pot.cc b/C64/SID/resid/pot.cc new file mode 100755 index 00000000..71f88cb2 --- /dev/null +++ b/C64/SID/resid/pot.cc @@ -0,0 +1,31 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#include "pot.h" + +namespace reSID +{ + +reg8 Potentiometer::readPOT() +{ + // NB! Not modeled. + return 0xff; +} + +} // namespace reSID diff --git a/C64/SID/resid/pot.h b/C64/SID/resid/pot.h new file mode 100755 index 00000000..72f59cd8 --- /dev/null +++ b/C64/SID/resid/pot.h @@ -0,0 +1,36 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_POT_H +#define RESID_POT_H + +#include "resid-config.h" + +namespace reSID +{ + +class Potentiometer +{ +public: + reg8 readPOT(); +}; + +} // namespace reSID + +#endif diff --git a/C64/SID/resid/resid-config.h b/C64/SID/resid/resid-config.h new file mode 100755 index 00000000..193fca1c --- /dev/null +++ b/C64/SID/resid/resid-config.h @@ -0,0 +1,29 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_CONFIG_H +#define RESID_CONFIG_H + +#ifdef IDE_COMPILE +#include "ide-siddefs.h" +#else +#include "siddefs.h" +#endif + +#endif diff --git a/C64/SID/resid/sid.cc b/C64/SID/resid/sid.cc new file mode 100755 index 00000000..98e6cb3a --- /dev/null +++ b/C64/SID/resid/sid.cc @@ -0,0 +1,1027 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#define RESID_SID_CC + +#ifdef _M_ARM +#undef _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE +#define _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE 1 +#endif + +#include "sid.h" +#include + +#ifndef round +#define round(x) (x>=0.0?floor(x+0.5):ceil(x-0.5)) +#endif + +namespace reSID +{ + +// ---------------------------------------------------------------------------- +// Constructor. +// ---------------------------------------------------------------------------- +SID::SID() +{ + // Initialize pointers. + sample = 0; + fir = 0; + fir_N = 0; + fir_RES = 0; + fir_beta = 0; + fir_f_cycles_per_sample = 0; + fir_filter_scale = 0; + + sid_model = MOS6581; + voice[0].set_sync_source(&voice[2]); + voice[1].set_sync_source(&voice[0]); + voice[2].set_sync_source(&voice[1]); + + set_sampling_parameters(985248, SAMPLE_FAST, 44100); + + bus_value = 0; + bus_value_ttl = 0; + write_pipeline = 0; + + databus_ttl = 0; +} + + +// ---------------------------------------------------------------------------- +// Destructor. +// ---------------------------------------------------------------------------- +SID::~SID() +{ + delete[] sample; + delete[] fir; +} + + +// ---------------------------------------------------------------------------- +// Set chip model. +// ---------------------------------------------------------------------------- +void SID::set_chip_model(chip_model model) +{ + sid_model = model; + + /* + results from real C64 (testprogs/SID/bitfade/delayfrq0.prg): + + (new SID) (250469/8580R5) (250469/8580R5) + delayfrq0 ~7a000 ~108000 + + (old SID) (250407/6581) + delayfrq0 ~01d00 + + */ + databus_ttl = sid_model == MOS8580 ? 0xa2000 : 0x1d00; + + for (int i = 0; i < 3; i++) { + voice[i].set_chip_model(model); + } + + filter.set_chip_model(model); +} + + +// ---------------------------------------------------------------------------- +// SID reset. +// ---------------------------------------------------------------------------- +void SID::reset() +{ + for (int i = 0; i < 3; i++) { + voice[i].reset(); + } + filter.reset(); + extfilt.reset(); + + bus_value = 0; + bus_value_ttl = 0; +} + + +// ---------------------------------------------------------------------------- +// Write 16-bit sample to audio input. +// Note that to mix in an external audio signal, the signal should be +// resampled to 1MHz first to avoid sampling noise. +// ---------------------------------------------------------------------------- +void SID::input(short sample) +{ + // The input can be used to simulate the MOS8580 "digi boost" hardware hack. + filter.input(sample); +} + + +// ---------------------------------------------------------------------------- +// Read registers. +// +// Reading a write only register returns the last byte written to any SID +// register. The individual bits in this value start to fade down towards +// zero after a few cycles. All bits reach zero within approximately +// $2000 - $4000 cycles. +// It has been claimed that this fading happens in an orderly fashion, however +// sampling of write only registers reveals that this is not the case. +// NB! This is not correctly modeled. +// The actual use of write only registers has largely been made in the belief +// that all SID registers are readable. To support this belief the read +// would have to be done immediately after a write to the same register +// (remember that an intermediate write to another register would yield that +// value instead). With this in mind we return the last value written to +// any SID register for $4000 cycles without modeling the bit fading. +// ---------------------------------------------------------------------------- +reg8 SID::read(reg8 offset) +{ + switch (offset) { + case 0x19: + bus_value = potx.readPOT(); + bus_value_ttl = databus_ttl; + break; + case 0x1a: + bus_value = poty.readPOT(); + bus_value_ttl = databus_ttl; + break; + case 0x1b: + bus_value = voice[2].wave.readOSC(); + bus_value_ttl = databus_ttl; + break; + case 0x1c: + bus_value = voice[2].envelope.readENV(); + bus_value_ttl = databus_ttl; + break; + } + return bus_value; +} + + +// ---------------------------------------------------------------------------- +// Write registers. +// Writes are one cycle delayed on the MOS8580. This is only modeled for +// single cycle clocking. +// ---------------------------------------------------------------------------- +void SID::write(reg8 offset, reg8 value) +{ + write_address = offset; + bus_value = value; + bus_value_ttl = databus_ttl; + + if (unlikely(sampling == SAMPLE_FAST) && (sid_model == MOS8580)) { + // Fake one cycle pipeline delay on the MOS8580 + // when using non cycle accurate emulation. + // This will make the SID detection method work. + write_pipeline = 1; + } + else { + write(); + } +} + + +// ---------------------------------------------------------------------------- +// Write registers. +// ---------------------------------------------------------------------------- +void SID::write() +{ + switch (write_address) { + case 0x00: + voice[0].wave.writeFREQ_LO(bus_value); + break; + case 0x01: + voice[0].wave.writeFREQ_HI(bus_value); + break; + case 0x02: + voice[0].wave.writePW_LO(bus_value); + break; + case 0x03: + voice[0].wave.writePW_HI(bus_value); + break; + case 0x04: + voice[0].writeCONTROL_REG(bus_value); + break; + case 0x05: + voice[0].envelope.writeATTACK_DECAY(bus_value); + break; + case 0x06: + voice[0].envelope.writeSUSTAIN_RELEASE(bus_value); + break; + case 0x07: + voice[1].wave.writeFREQ_LO(bus_value); + break; + case 0x08: + voice[1].wave.writeFREQ_HI(bus_value); + break; + case 0x09: + voice[1].wave.writePW_LO(bus_value); + break; + case 0x0a: + voice[1].wave.writePW_HI(bus_value); + break; + case 0x0b: + voice[1].writeCONTROL_REG(bus_value); + break; + case 0x0c: + voice[1].envelope.writeATTACK_DECAY(bus_value); + break; + case 0x0d: + voice[1].envelope.writeSUSTAIN_RELEASE(bus_value); + break; + case 0x0e: + voice[2].wave.writeFREQ_LO(bus_value); + break; + case 0x0f: + voice[2].wave.writeFREQ_HI(bus_value); + break; + case 0x10: + voice[2].wave.writePW_LO(bus_value); + break; + case 0x11: + voice[2].wave.writePW_HI(bus_value); + break; + case 0x12: + voice[2].writeCONTROL_REG(bus_value); + break; + case 0x13: + voice[2].envelope.writeATTACK_DECAY(bus_value); + break; + case 0x14: + voice[2].envelope.writeSUSTAIN_RELEASE(bus_value); + break; + case 0x15: + filter.writeFC_LO(bus_value); + break; + case 0x16: + filter.writeFC_HI(bus_value); + break; + case 0x17: + filter.writeRES_FILT(bus_value); + break; + case 0x18: + filter.writeMODE_VOL(bus_value); + break; + default: + break; + } + + // Tell clock() that the pipeline is empty. + write_pipeline = 0; +} + + +// ---------------------------------------------------------------------------- +// Constructor. +// ---------------------------------------------------------------------------- +SID::State::State() +{ + int i; + + for (i = 0; i < 0x20; i++) { + sid_register[i] = 0; + } + + bus_value = 0; + bus_value_ttl = 0; + write_pipeline = 0; + write_address = 0; + voice_mask = 0xff; + + for (i = 0; i < 3; i++) { + accumulator[i] = 0; + shift_register[i] = 0x7fffff; + shift_register_reset[i] = 0; + shift_pipeline[i] = 0; + pulse_output[i] = 0; + floating_output_ttl[i] = 0; + + rate_counter[i] = 0; + rate_counter_period[i] = 9; + exponential_counter[i] = 0; + exponential_counter_period[i] = 1; + envelope_counter[i] = 0; + envelope_state[i] = EnvelopeGenerator::RELEASE; + hold_zero[i] = true; + envelope_pipeline[i] = 0; + } +} + + +// ---------------------------------------------------------------------------- +// Read state. +// ---------------------------------------------------------------------------- +SID::State SID::read_state() +{ + State state; + int i, j; + + for (i = 0, j = 0; i < 3; i++, j += 7) { + WaveformGenerator& wave = voice[i].wave; + EnvelopeGenerator& envelope = voice[i].envelope; + state.sid_register[j + 0] = wave.freq & 0xff; + state.sid_register[j + 1] = wave.freq >> 8; + state.sid_register[j + 2] = wave.pw & 0xff; + state.sid_register[j + 3] = wave.pw >> 8; + state.sid_register[j + 4] = + (wave.waveform << 4) + | (wave.test ? 0x08 : 0) + | (wave.ring_mod ? 0x04 : 0) + | (wave.sync ? 0x02 : 0) + | (envelope.gate ? 0x01 : 0); + state.sid_register[j + 5] = (envelope.attack << 4) | envelope.decay; + state.sid_register[j + 6] = (envelope.sustain << 4) | envelope.release; + } + + state.sid_register[j++] = filter.fc & 0x007; + state.sid_register[j++] = filter.fc >> 3; + state.sid_register[j++] = (filter.res << 4) | filter.filt; + state.sid_register[j++] = filter.mode | filter.vol; + + // These registers are superfluous, but are included for completeness. + for (; j < 0x1d; j++) { + state.sid_register[j] = read(j); + } + for (; j < 0x20; j++) { + state.sid_register[j] = 0; + } + + state.bus_value = bus_value; + state.bus_value_ttl = bus_value_ttl; + state.write_pipeline = write_pipeline; + state.write_address = write_address; + state.voice_mask = filter.voice_mask; + + for (i = 0; i < 3; i++) { + state.accumulator[i] = voice[i].wave.accumulator; + state.shift_register[i] = voice[i].wave.shift_register; + state.shift_register_reset[i] = voice[i].wave.shift_register_reset; + state.shift_pipeline[i] = voice[i].wave.shift_pipeline; + state.pulse_output[i] = voice[i].wave.pulse_output; + state.floating_output_ttl[i] = voice[i].wave.floating_output_ttl; + + state.rate_counter[i] = voice[i].envelope.rate_counter; + state.rate_counter_period[i] = voice[i].envelope.rate_period; + state.exponential_counter[i] = voice[i].envelope.exponential_counter; + state.exponential_counter_period[i] = voice[i].envelope.exponential_counter_period; + state.envelope_counter[i] = voice[i].envelope.envelope_counter; + state.envelope_state[i] = voice[i].envelope.state; + state.hold_zero[i] = voice[i].envelope.hold_zero; + state.envelope_pipeline[i] = voice[i].envelope.envelope_pipeline; + } + + return state; +} + + +// ---------------------------------------------------------------------------- +// Write state. +// ---------------------------------------------------------------------------- +void SID::write_state(const State& state) +{ + int i; + + for (i = 0; i <= 0x18; i++) { + write(i, state.sid_register[i]); + } + + bus_value = state.bus_value; + bus_value_ttl = state.bus_value_ttl; + write_pipeline = state.write_pipeline; + write_address = state.write_address; + filter.set_voice_mask(state.voice_mask); + + for (i = 0; i < 3; i++) { + voice[i].wave.accumulator = state.accumulator[i]; + voice[i].wave.shift_register = state.shift_register[i]; + voice[i].wave.shift_register_reset = state.shift_register_reset[i]; + voice[i].wave.shift_pipeline = state.shift_pipeline[i]; + voice[i].wave.pulse_output = state.pulse_output[i]; + voice[i].wave.floating_output_ttl = state.floating_output_ttl[i]; + + voice[i].envelope.rate_counter = state.rate_counter[i]; + voice[i].envelope.rate_period = state.rate_counter_period[i]; + voice[i].envelope.exponential_counter = state.exponential_counter[i]; + voice[i].envelope.exponential_counter_period = state.exponential_counter_period[i]; + voice[i].envelope.envelope_counter = state.envelope_counter[i]; + voice[i].envelope.state = state.envelope_state[i]; + voice[i].envelope.hold_zero = state.hold_zero[i]; + voice[i].envelope.envelope_pipeline = state.envelope_pipeline[i]; + } +} + + +// ---------------------------------------------------------------------------- +// Mask for voices routed into the filter / audio output stage. +// Used to physically connect/disconnect EXT IN, and for test purposed +// (voice muting). +// ---------------------------------------------------------------------------- +void SID::set_voice_mask(reg4 mask) +{ + filter.set_voice_mask(mask); +} + + +// ---------------------------------------------------------------------------- +// Enable filter. +// ---------------------------------------------------------------------------- +void SID::enable_filter(bool enable) +{ + filter.enable_filter(enable); +} + + +// ---------------------------------------------------------------------------- +// Adjust the DAC bias parameter of the filter. +// This gives user variable control of the exact CF -> center frequency +// mapping used by the filter. +// The setting is currently only effective for 6581. +// ---------------------------------------------------------------------------- +void SID::adjust_filter_bias(double dac_bias) { + filter.adjust_filter_bias(dac_bias); +} + + +// ---------------------------------------------------------------------------- +// Enable external filter. +// ---------------------------------------------------------------------------- +void SID::enable_external_filter(bool enable) +{ + extfilt.enable_filter(enable); +} + + +// ---------------------------------------------------------------------------- +// I0() computes the 0th order modified Bessel function of the first kind. +// This function is originally from resample-1.5/filterkit.c by J. O. Smith. +// ---------------------------------------------------------------------------- +double SID::I0(double x) +{ + // Max error acceptable in I0. + const double I0e = 1e-6; + + double sum, u, halfx, temp; + int n; + + sum = u = n = 1; + halfx = x/2.0; + + do { + temp = halfx/n++; + u *= temp*temp; + sum += u; + } while (u >= I0e*sum); + + return sum; +} + + +// ---------------------------------------------------------------------------- +// Setting of SID sampling parameters. +// +// Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64. +// The default end of passband frequency is pass_freq = 0.9*sample_freq/2 +// for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample +// frequencies. +// +// For resampling, the ratio between the clock frequency and the sample +// frequency is limited as follows: +// 125*clock_freq/sample_freq < 16384 +// E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not +// be set lower than ~ 8kHz. A lower sample frequency would make the +// resampling code overfill its 16k sample ring buffer. +// +// The end of passband frequency is also limited: +// pass_freq <= 0.9*sample_freq/2 + +// E.g. for a 44.1kHz sampling rate the end of passband frequency is limited +// to slightly below 20kHz. This constraint ensures that the FIR table is +// not overfilled. +// ---------------------------------------------------------------------------- +bool SID::set_sampling_parameters(double clock_freq, sampling_method method, + double sample_freq, double pass_freq, double filter_scale) +{ + // Check resampling constraints. + if (method == SAMPLE_RESAMPLE || method == SAMPLE_RESAMPLE_FASTMEM) + { + // Check whether the sample ring buffer would overfill. + if (FIR_N*clock_freq/sample_freq >= RINGSIZE) { + return false; + } + + // The default passband limit is 0.9*sample_freq/2 for sample + // frequencies below ~ 44.1kHz, and 20kHz for higher sample frequencies. + if (pass_freq < 0) { + pass_freq = 20000; + if (2*pass_freq/sample_freq >= 0.9) { + pass_freq = 0.9*sample_freq/2; + } + } + // Check whether the FIR table would overfill. + else if (pass_freq > 0.9*sample_freq/2) { + return false; + } + + // The filter scaling is only included to avoid clipping, so keep + // it sane. + if (filter_scale < 0.9 || filter_scale > 1.0) { + return false; + } + } + + clock_frequency = clock_freq; + sampling = method; + + cycles_per_sample = + cycle_count(clock_freq/sample_freq*(1 << FIXP_SHIFT) + 0.5); + + sample_offset = 0; + sample_prev = 0; + sample_now = 0; + + // FIR initialization is only necessary for resampling. + if (method != SAMPLE_RESAMPLE && method != SAMPLE_RESAMPLE_FASTMEM) + { + delete[] sample; + delete[] fir; + sample = 0; + fir = 0; + return true; + } + + // Allocate sample buffer. + if (!sample) { + sample = new short[RINGSIZE*2]; + } + // Clear sample buffer. + for (int j = 0; j < RINGSIZE*2; j++) { + sample[j] = 0; + } + sample_index = 0; + + const double pi = 3.1415926535897932385; + + // 16 bits -> -96dB stopband attenuation. + const double A = -20*log10(1.0/(1 << 16)); + // A fraction of the bandwidth is allocated to the transition band, + double dw = (1 - 2*pass_freq/sample_freq)*pi*2; + // The cutoff frequency is midway through the transition band (nyquist) + double wc = pi; + + // For calculation of beta and N see the reference for the kaiserord + // function in the MATLAB Signal Processing Toolbox: + // http://www.mathworks.com/access/helpdesk/help/toolbox/signal/kaiserord.html + const double beta = 0.1102*(A - 8.7); + const double I0beta = I0(beta); + + // The filter order will maximally be 124 with the current constraints. + // N >= (96.33 - 7.95)/(2.285*0.1*pi) -> N >= 123 + // The filter order is equal to the number of zero crossings, i.e. + // it should be an even number (sinc is symmetric about x = 0). + int N = int((A - 7.95)/(2.285*dw) + 0.5); + N += N & 1; + + double f_samples_per_cycle = sample_freq/clock_freq; + double f_cycles_per_sample = clock_freq/sample_freq; + + // The filter length is equal to the filter order + 1. + // The filter length must be an odd number (sinc is symmetric about x = 0). + int fir_N_new = int(N*f_cycles_per_sample) + 1; + fir_N_new |= 1; + + // We clamp the filter table resolution to 2^n, making the fixed point + // sample_offset a whole multiple of the filter table resolution. + int res = method == SAMPLE_RESAMPLE ? + FIR_RES : FIR_RES_FASTMEM; + int n = (int)ceil(log(res/f_cycles_per_sample)/log(2.0f)); + int fir_RES_new = 1 << n; + + /* Determine if we need to recalculate table, or whether we can reuse earlier cached copy. + * This pays off on slow hardware such as current Android devices. + */ + if (fir && fir_RES_new == fir_RES && fir_N_new == fir_N && beta == fir_beta && f_cycles_per_sample == fir_f_cycles_per_sample && fir_filter_scale == filter_scale) { + return true; + } + fir_RES = fir_RES_new; + fir_N = fir_N_new; + fir_beta = beta; + fir_f_cycles_per_sample = f_cycles_per_sample; + fir_filter_scale = filter_scale; + + // Allocate memory for FIR tables. + delete[] fir; + fir = new short[fir_N*fir_RES]; + + // Calculate fir_RES FIR tables for linear interpolation. + for (int i = 0; i < fir_RES; i++) { + int fir_offset = i*fir_N + fir_N/2; + double j_offset = double(i)/fir_RES; + // Calculate FIR table. This is the sinc function, weighted by the + // Kaiser window. + for (int j = -fir_N/2; j <= fir_N/2; j++) { + double jx = j - j_offset; + double wt = wc*jx/f_cycles_per_sample; + double temp = jx/(fir_N/2); + double Kaiser = fabs(temp) <= 1 ? I0(beta*sqrt(1 - temp*temp))/I0beta : 0; + double sincwt = fabs(wt) >= 1e-6 ? sin(wt)/wt : 1; + double val = (1 << FIR_SHIFT)*filter_scale*f_samples_per_cycle*wc/pi*sincwt*Kaiser; + fir[fir_offset + j] = (short)round(val); + } + } + + return true; +} + + +// ---------------------------------------------------------------------------- +// Adjustment of SID sampling frequency. +// +// In some applications, e.g. a C64 emulator, it can be desirable to +// synchronize sound with a timer source. This is supported by adjustment of +// the SID sampling frequency. +// +// NB! Adjustment of the sampling frequency may lead to noticeable shifts in +// frequency, and should only be used for interactive applications. Note also +// that any adjustment of the sampling frequency will change the +// characteristics of the resampling filter, since the filter is not rebuilt. +// ---------------------------------------------------------------------------- +void SID::adjust_sampling_frequency(double sample_freq) +{ + cycles_per_sample = + cycle_count(clock_frequency/sample_freq*(1 << FIXP_SHIFT) + 0.5); +} + + +// ---------------------------------------------------------------------------- +// SID clocking - delta_t cycles. +// ---------------------------------------------------------------------------- +void SID::clock(cycle_count delta_t) +{ + int i; + + // Pipelined writes on the MOS8580. + if (unlikely(write_pipeline) && likely(delta_t > 0)) { + // Step one cycle by a recursive call to ourselves. + write_pipeline = 0; + clock(1); + write(); + delta_t -= 1; + } + + if (unlikely(delta_t <= 0)) { + return; + } + + // Age bus value. + bus_value_ttl -= delta_t; + if (unlikely(bus_value_ttl <= 0)) { + bus_value = 0; + bus_value_ttl = 0; + } + + // Clock amplitude modulators. + for (i = 0; i < 3; i++) { + voice[i].envelope.clock(delta_t); + } + + // Clock and synchronize oscillators. + // Loop until we reach the current cycle. + cycle_count delta_t_osc = delta_t; + while (delta_t_osc) { + cycle_count delta_t_min = delta_t_osc; + + // Find minimum number of cycles to an oscillator accumulator MSB toggle. + // We have to clock on each MSB on / MSB off for hard sync to operate + // correctly. + for (i = 0; i < 3; i++) { + WaveformGenerator& wave = voice[i].wave; + + // It is only necessary to clock on the MSB of an oscillator that is + // a sync source and has freq != 0. + if (likely(!(wave.sync_dest->sync && wave.freq))) { + continue; + } + + reg16 freq = wave.freq; + reg24 accumulator = wave.accumulator; + + // Clock on MSB off if MSB is on, clock on MSB on if MSB is off. + reg24 delta_accumulator = + (accumulator & 0x800000 ? 0x1000000 : 0x800000) - accumulator; + + cycle_count delta_t_next = delta_accumulator/freq; + if (likely(delta_accumulator%freq)) { + ++delta_t_next; + } + + if (unlikely(delta_t_next < delta_t_min)) { + delta_t_min = delta_t_next; + } + } + + // Clock oscillators. + for (i = 0; i < 3; i++) { + voice[i].wave.clock(delta_t_min); + } + + // Synchronize oscillators. + for (i = 0; i < 3; i++) { + voice[i].wave.synchronize(); + } + + delta_t_osc -= delta_t_min; + } + + // Calculate waveform output. + for (i = 0; i < 3; i++) { + voice[i].wave.set_waveform_output(delta_t); + } + + // Clock filter. + filter.clock(delta_t, voice[0].output(), voice[1].output(), voice[2].output()); + + // Clock external filter. + extfilt.clock(delta_t, filter.output()); +} + + +// ---------------------------------------------------------------------------- +// SID clocking with audio sampling. +// Fixed point arithmetics are used. +// +// The example below shows how to clock the SID a specified amount of cycles +// while producing audio output: +// +// while (delta_t) { +// bufindex += sid.clock(delta_t, buf + bufindex, buflength - bufindex); +// write(dsp, buf, bufindex*2); +// bufindex = 0; +// } +// +// ---------------------------------------------------------------------------- +int SID::clock(cycle_count& delta_t, short* buf, int n, int interleave) +{ + switch (sampling) { + default: + case SAMPLE_FAST: + return clock_fast(delta_t, buf, n, interleave); + case SAMPLE_INTERPOLATE: + return clock_interpolate(delta_t, buf, n, interleave); + case SAMPLE_RESAMPLE: + return clock_resample(delta_t, buf, n, interleave); + case SAMPLE_RESAMPLE_FASTMEM: + return clock_resample_fastmem(delta_t, buf, n, interleave); + } +} + + +// ---------------------------------------------------------------------------- +// SID clocking with audio sampling - delta clocking picking nearest sample. +// ---------------------------------------------------------------------------- +int SID::clock_fast(cycle_count& delta_t, short* buf, int n, int interleave) +{ + int s; + + for (s = 0; s < n; s++) { + cycle_count next_sample_offset = sample_offset + cycles_per_sample + (1 << (FIXP_SHIFT - 1)); + cycle_count delta_t_sample = next_sample_offset >> FIXP_SHIFT; + + if (delta_t_sample > delta_t) { + delta_t_sample = delta_t; + } + + clock(delta_t_sample); + + if ((delta_t -= delta_t_sample) == 0) { + sample_offset -= delta_t_sample << FIXP_SHIFT; + break; + } + + sample_offset = (next_sample_offset & FIXP_MASK) - (1 << (FIXP_SHIFT - 1)); + buf[s*interleave] = output(); + } + + return s; +} + + +// ---------------------------------------------------------------------------- +// SID clocking with audio sampling - cycle based with linear sample +// interpolation. +// +// Here the chip is clocked every cycle. This yields higher quality +// sound since the samples are linearly interpolated, and since the +// external filter attenuates frequencies above 16kHz, thus reducing +// sampling noise. +// ---------------------------------------------------------------------------- +int SID::clock_interpolate(cycle_count& delta_t, short* buf, int n, int interleave) +{ + int s; + + for (s = 0; s < n; s++) { + cycle_count next_sample_offset = sample_offset + cycles_per_sample; + cycle_count delta_t_sample = next_sample_offset >> FIXP_SHIFT; + + if (delta_t_sample > delta_t) { + delta_t_sample = delta_t; + } + + for (int i = delta_t_sample; i > 0; i--) { + clock(); + if (unlikely(i <= 2)) { + sample_prev = sample_now; + sample_now = output(); + } + } + + if ((delta_t -= delta_t_sample) == 0) { + sample_offset -= delta_t_sample << FIXP_SHIFT; + break; + } + + sample_offset = next_sample_offset & FIXP_MASK; + + buf[s*interleave] = + sample_prev + (sample_offset*(sample_now - sample_prev) >> FIXP_SHIFT); + } + + return s; +} + + +// ---------------------------------------------------------------------------- +// SID clocking with audio sampling - cycle based with audio resampling. +// +// This is the theoretically correct (and computationally intensive) audio +// sample generation. The samples are generated by resampling to the specified +// sampling frequency. The work rate is inversely proportional to the +// percentage of the bandwidth allocated to the filter transition band. +// +// This implementation is based on the paper "A Flexible Sampling-Rate +// Conversion Method", by J. O. Smith and P. Gosset, or rather on the +// expanded tutorial on the "Digital Audio Resampling Home Page": +// http://www-ccrma.stanford.edu/~jos/resample/ +// +// By building shifted FIR tables with samples according to the +// sampling frequency, the implementation below dramatically reduces the +// computational effort in the filter convolutions, without any loss +// of accuracy. The filter convolutions are also vectorizable on +// current hardware. +// +// Further possible optimizations are: +// * An equiripple filter design could yield a lower filter order, see +// http://www.mwrf.com/Articles/ArticleID/7229/7229.html +// * The Convolution Theorem could be used to bring the complexity of +// convolution down from O(n*n) to O(n*log(n)) using the Fast Fourier +// Transform, see http://en.wikipedia.org/wiki/Convolution_theorem +// * Simply resampling in two steps can also yield computational +// savings, since the transition band will be wider in the first step +// and the required filter order is thus lower in this step. +// Laurent Ganier has found the optimal intermediate sampling frequency +// to be (via derivation of sum of two steps): +// 2 * pass_freq + sqrt [ 2 * pass_freq * orig_sample_freq +// * (dest_sample_freq - 2 * pass_freq) / dest_sample_freq ] +// +// NB! the result of right shifting negative numbers is really +// implementation dependent in the C++ standard. +// ---------------------------------------------------------------------------- +int SID::clock_resample(cycle_count& delta_t, short* buf, int n, int interleave) +{ + int s; + + for (s = 0; s < n; s++) { + cycle_count next_sample_offset = sample_offset + cycles_per_sample; + cycle_count delta_t_sample = next_sample_offset >> FIXP_SHIFT; + + if (delta_t_sample > delta_t) { + delta_t_sample = delta_t; + } + + for (int i = 0; i < delta_t_sample; i++) { + clock(); + sample[sample_index] = sample[sample_index + RINGSIZE] = output(); + ++sample_index &= RINGMASK; + } + + if ((delta_t -= delta_t_sample) == 0) { + sample_offset -= delta_t_sample << FIXP_SHIFT; + break; + } + + sample_offset = next_sample_offset & FIXP_MASK; + + int fir_offset = sample_offset*fir_RES >> FIXP_SHIFT; + int fir_offset_rmd = sample_offset*fir_RES & FIXP_MASK; + short* fir_start = fir + fir_offset*fir_N; + short* sample_start = sample + sample_index - fir_N - 1 + RINGSIZE; + + // Convolution with filter impulse response. + int v1 = 0; + for (int j = 0; j < fir_N; j++) { + v1 += sample_start[j]*fir_start[j]; + } + + // Use next FIR table, wrap around to first FIR table using + // next sample. + if (unlikely(++fir_offset == fir_RES)) { + fir_offset = 0; + ++sample_start; + } + fir_start = fir + fir_offset*fir_N; + + // Convolution with filter impulse response. + int v2 = 0; + for (int k = 0; k < fir_N; k++) { + v2 += sample_start[k]*fir_start[k]; + } + + // Linear interpolation. + // fir_offset_rmd is equal for all samples, it can thus be factorized out: + // sum(v1 + rmd*(v2 - v1)) = sum(v1) + rmd*(sum(v2) - sum(v1)) + int v = v1 + int((unsigned(fir_offset_rmd)*unsigned(v2 - v1)) >> FIXP_SHIFT); + + v >>= FIR_SHIFT; + + // Saturated arithmetics to guard against 16 bit sample overflow. + const int half = 1 << 15; + if (v >= half) { + v = half - 1; + } + else if (v < -half) { + v = -half; + } + + buf[s*interleave] = v; + } + + return s; +} + + +// ---------------------------------------------------------------------------- +// SID clocking with audio sampling - cycle based with audio resampling. +// ---------------------------------------------------------------------------- +int SID::clock_resample_fastmem(cycle_count& delta_t, short* buf, int n, int interleave) +{ + int s; + + for (s = 0; s < n; s++) { + cycle_count next_sample_offset = sample_offset + cycles_per_sample; + cycle_count delta_t_sample = next_sample_offset >> FIXP_SHIFT; + + if (delta_t_sample > delta_t) { + delta_t_sample = delta_t; + } + + for (int i = 0; i < delta_t_sample; i++) { + clock(); + sample[sample_index] = sample[sample_index + RINGSIZE] = output(); + ++sample_index &= RINGMASK; + } + + if ((delta_t -= delta_t_sample) == 0) { + sample_offset -= delta_t_sample << FIXP_SHIFT; + break; + } + + sample_offset = next_sample_offset & FIXP_MASK; + + int fir_offset = sample_offset*fir_RES >> FIXP_SHIFT; + short* fir_start = fir + fir_offset*fir_N; + short* sample_start = sample + sample_index - fir_N + RINGSIZE; + + // Convolution with filter impulse response. + int v = 0; + for (int j = 0; j < fir_N; j++) { + v += sample_start[j]*fir_start[j]; + } + + v >>= FIR_SHIFT; + + // Saturated arithmetics to guard against 16 bit sample overflow. + const int half = 1 << 15; + if (v >= half) { + v = half - 1; + } + else if (v < -half) { + v = -half; + } + + buf[s*interleave] = v; + } + + return s; +} + +} // namespace reSID diff --git a/C64/SID/resid/sid.h b/C64/SID/resid/sid.h new file mode 100755 index 00000000..5c0a94e8 --- /dev/null +++ b/C64/SID/resid/sid.h @@ -0,0 +1,234 @@ +/*! \file resid/sid.h */ + +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_SID_H +#define RESID_SID_H + +#include "resid-config.h" +#include "voice.h" +#include "filter.h" +#include "extfilt.h" +#include "pot.h" + +namespace reSID +{ + +class SID +{ +public: + SID(); + ~SID(); + + void set_chip_model(chip_model model); + void set_voice_mask(reg4 mask); + void enable_filter(bool enable); + void adjust_filter_bias(double dac_bias); + void enable_external_filter(bool enable); + bool set_sampling_parameters(double clock_freq, sampling_method method, + double sample_freq, double pass_freq = -1, + double filter_scale = 0.97); + void adjust_sampling_frequency(double sample_freq); + + void clock(); + void clock(cycle_count delta_t); + int clock(cycle_count& delta_t, short* buf, int n, int interleave = 1); + void reset(); + + // Read/write registers. + reg8 read(reg8 offset); + void write(reg8 offset, reg8 value); + + // Read/write state. + class State + { + public: + State(); + + char sid_register[0x20]; + + reg8 bus_value; + cycle_count bus_value_ttl; + cycle_count write_pipeline; + reg8 write_address; + reg4 voice_mask; + + reg24 accumulator[3]; + reg24 shift_register[3]; + cycle_count shift_register_reset[3]; + cycle_count shift_pipeline[3]; + reg16 pulse_output[3]; + cycle_count floating_output_ttl[3]; + + reg16 rate_counter[3]; + reg16 rate_counter_period[3]; + reg16 exponential_counter[3]; + reg16 exponential_counter_period[3]; + reg8 envelope_counter[3]; + EnvelopeGenerator::State envelope_state[3]; + bool hold_zero[3]; + cycle_count envelope_pipeline[3]; + }; + + State read_state(); + void write_state(const State& state); + + // 16-bit input (EXT IN). + void input(short sample); + + // 16-bit output (AUDIO OUT). + short output(); + + public: + static double I0(double x); + int clock_fast(cycle_count& delta_t, short* buf, int n, int interleave); + int clock_interpolate(cycle_count& delta_t, short* buf, int n, int interleave); + int clock_resample(cycle_count& delta_t, short* buf, int n, int interleave); + int clock_resample_fastmem(cycle_count& delta_t, short* buf, int n, int interleave); + void write(); + + chip_model sid_model; + Voice voice[3]; + Filter filter; + ExternalFilter extfilt; + Potentiometer potx; + Potentiometer poty; + + reg8 bus_value; + cycle_count bus_value_ttl; + + // The data bus TTL for the selected chip model + cycle_count databus_ttl; + + // Pipeline for writes on the MOS8580. + cycle_count write_pipeline; + reg8 write_address; + + double clock_frequency; + + enum { + // Resampling constants. + // The error in interpolated lookup is bounded by 1.234/L^2, + // while the error in non-interpolated lookup is bounded by + // 0.7854/L + 0.4113/L^2, see + // http://www-ccrma.stanford.edu/~jos/resample/Choice_Table_Size.html + // For a resolution of 16 bits this yields L >= 285 and L >= 51473, + // respectively. + FIR_N = 125, + FIR_RES = 285, + FIR_RES_FASTMEM = 51473, + FIR_SHIFT = 15, + + RINGSIZE = 1 << 14, + RINGMASK = RINGSIZE - 1, + + // Fixed point constants (16.16 bits). + FIXP_SHIFT = 16, + FIXP_MASK = 0xffff + }; + + // Sampling variables. + sampling_method sampling; + cycle_count cycles_per_sample; + cycle_count sample_offset; + int sample_index; + short sample_prev, sample_now; + int fir_N; + int fir_RES; + double fir_beta; + double fir_f_cycles_per_sample; + double fir_filter_scale; + + // Ring buffer with overflow for contiguous storage of RINGSIZE samples. + short* sample; + + // FIR_RES filter tables (FIR_N*FIR_RES). + short* fir; +}; + + +// ---------------------------------------------------------------------------- +// Inline functions. +// The following functions are defined inline because they are called every +// time a sample is calculated. +// ---------------------------------------------------------------------------- + +#if RESID_INLINING || defined(RESID_SID_CC) + +// ---------------------------------------------------------------------------- +// Read 16-bit sample from audio output. +// ---------------------------------------------------------------------------- +RESID_INLINE +short SID::output() +{ + return extfilt.output(); +} + + +// ---------------------------------------------------------------------------- +// SID clocking - 1 cycle. +// ---------------------------------------------------------------------------- +RESID_INLINE +void SID::clock() +{ + int i; + + // Clock amplitude modulators. + for (i = 0; i < 3; i++) { + voice[i].envelope.clock(); + } + + // Clock oscillators. + for (i = 0; i < 3; i++) { + voice[i].wave.clock(); + } + + // Synchronize oscillators. + for (i = 0; i < 3; i++) { + voice[i].wave.synchronize(); + } + + // Calculate waveform output. + for (i = 0; i < 3; i++) { + voice[i].wave.set_waveform_output(); + } + + // Clock filter. + filter.clock(voice[0].output(), voice[1].output(), voice[2].output()); + + // Clock external filter. + extfilt.clock(filter.output()); + + // Pipelined writes on the MOS8580. + if (unlikely(write_pipeline)) { + write(); + } + + // Age bus value. + if (unlikely(!--bus_value_ttl)) { + bus_value = 0; + } +} + +#endif // RESID_INLINING || defined(RESID_SID_CC) + +} // namespace reSID + +#endif // not RESID_SID_H diff --git a/C64/SID/resid/siddefs.h b/C64/SID/resid/siddefs.h new file mode 100755 index 00000000..493eb0e6 --- /dev/null +++ b/C64/SID/resid/siddefs.h @@ -0,0 +1,87 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_SIDDEFS_H +#define RESID_SIDDEFS_H + +// Compilation configuration. +#define RESID_INLINING 1 +#define RESID_INLINE inline +#define RESID_BRANCH_HINTS 1 + +// Compiler specifics. +#define HAVE_BOOL 1 +#define HAVE_BUILTIN_EXPECT 1 +#define HAVE_LOG1P 1 + +// Define bool, true, and false for C++ compilers that lack these keywords. +#if !HAVE_BOOL +typedef int bool; +const bool true = 1; +const bool false = 0; +#endif + +#if HAVE_LOG1P +#define HAS_LOG1P +#endif + +// Branch prediction macros, lifted off the Linux kernel. +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +namespace reSID { + +// We could have used the smallest possible data type for each SID register, +// however this would give a slower engine because of data type conversions. +// An int is assumed to be at least 32 bits (necessary in the types reg24 +// and cycle_count). GNU does not support 16-bit machines +// (GNU Coding Standards: Portability between CPUs), so this should be +// a valid assumption. + +typedef unsigned int reg4; +typedef unsigned int reg8; +typedef unsigned int reg12; +typedef unsigned int reg16; +typedef unsigned int reg24; + +typedef int cycle_count; +typedef short short_point[2]; +typedef double double_point[2]; + +enum chip_model { MOS6581, MOS8580 }; + +enum sampling_method { + SAMPLE_FAST, + SAMPLE_INTERPOLATE, + SAMPLE_RESAMPLE, + SAMPLE_RESAMPLE_FASTMEM +}; + +} // namespace reSID + +extern "C" +{ +#ifndef RESID_VERSION_CC +extern const char* resid_version_string; +#else +const char* resid_version_string = "VICE 3.2"; +#endif +} + +#endif // not RESID_SIDDEFS_H diff --git a/C64/SID/resid/spline.h b/C64/SID/resid/spline.h new file mode 100755 index 00000000..c623d330 --- /dev/null +++ b/C64/SID/resid/spline.h @@ -0,0 +1,276 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_SPLINE_H +#define RESID_SPLINE_H + +namespace reSID +{ + +// Our objective is to construct a smooth interpolating single-valued function +// y = f(x). +// +// Catmull-Rom splines are widely used for interpolation, however these are +// parametric curves [x(t) y(t) ...] and can not be used to directly calculate +// y = f(x). +// For a discussion of Catmull-Rom splines see Catmull, E., and R. Rom, +// "A Class of Local Interpolating Splines", Computer Aided Geometric Design. +// +// Natural cubic splines are single-valued functions, and have been used in +// several applications e.g. to specify gamma curves for image display. +// These splines do not afford local control, and a set of linear equations +// including all interpolation points must be solved before any point on the +// curve can be calculated. The lack of local control makes the splines +// more difficult to handle than e.g. Catmull-Rom splines, and real-time +// interpolation of a stream of data points is not possible. +// For a discussion of natural cubic splines, see e.g. Kreyszig, E., "Advanced +// Engineering Mathematics". +// +// Our approach is to approximate the properties of Catmull-Rom splines for +// piecewice cubic polynomials f(x) = ax^3 + bx^2 + cx + d as follows: +// Each curve segment is specified by four interpolation points, +// p0, p1, p2, p3. +// The curve between p1 and p2 must interpolate both p1 and p2, and in addition +// f'(p1.x) = k1 = (p2.y - p0.y)/(p2.x - p0.x) and +// f'(p2.x) = k2 = (p3.y - p1.y)/(p3.x - p1.x). +// +// The constraints are expressed by the following system of linear equations +// +// [ 1 xi xi^2 xi^3 ] [ d ] [ yi ] +// [ 1 2*xi 3*xi^2 ] * [ c ] = [ ki ] +// [ 1 xj xj^2 xj^3 ] [ b ] [ yj ] +// [ 1 2*xj 3*xj^2 ] [ a ] [ kj ] +// +// Solving using Gaussian elimination and back substitution, setting +// dy = yj - yi, dx = xj - xi, we get +// +// a = ((ki + kj) - 2*dy/dx)/(dx*dx); +// b = ((kj - ki)/dx - 3*(xi + xj)*a)/2; +// c = ki - (3*xi*a + 2*b)*xi; +// d = yi - ((xi*a + b)*xi + c)*xi; +// +// Having calculated the coefficients of the cubic polynomial we have the +// choice of evaluation by brute force +// +// for (x = x1; x <= x2; x += res) { +// y = ((a*x + b)*x + c)*x + d; +// plot(x, y); +// } +// +// or by forward differencing +// +// y = ((a*x1 + b)*x1 + c)*x1 + d; +// dy = (3*a*(x1 + res) + 2*b)*x1*res + ((a*res + b)*res + c)*res; +// d2y = (6*a*(x1 + res) + 2*b)*res*res; +// d3y = 6*a*res*res*res; +// +// for (x = x1; x <= x2; x += res) { +// plot(x, y); +// y += dy; dy += d2y; d2y += d3y; +// } +// +// See Foley, Van Dam, Feiner, Hughes, "Computer Graphics, Principles and +// Practice" for a discussion of forward differencing. +// +// If we have a set of interpolation points p0, ..., pn, we may specify +// curve segments between p0 and p1, and between pn-1 and pn by using the +// following constraints: +// f''(p0.x) = 0 and +// f''(pn.x) = 0. +// +// Substituting the results for a and b in +// +// 2*b + 6*a*xi = 0 +// +// we get +// +// ki = (3*dy/dx - kj)/2; +// +// or by substituting the results for a and b in +// +// 2*b + 6*a*xj = 0 +// +// we get +// +// kj = (3*dy/dx - ki)/2; +// +// Finally, if we have only two interpolation points, the cubic polynomial +// will degenerate to a straight line if we set +// +// ki = kj = dy/dx; +// + + +#if SPLINE_BRUTE_FORCE +#define interpolate_segment interpolate_brute_force +#else +#define interpolate_segment interpolate_forward_difference +#endif + + +// ---------------------------------------------------------------------------- +// Calculation of coefficients. +// ---------------------------------------------------------------------------- +inline +void cubic_coefficients(double x1, double y1, double x2, double y2, + double k1, double k2, + double& a, double& b, double& c, double& d) +{ + double dx = x2 - x1, dy = y2 - y1; + + a = ((k1 + k2) - 2*dy/dx)/(dx*dx); + b = ((k2 - k1)/dx - 3*(x1 + x2)*a)/2; + c = k1 - (3*x1*a + 2*b)*x1; + d = y1 - ((x1*a + b)*x1 + c)*x1; +} + +// ---------------------------------------------------------------------------- +// Evaluation of cubic polynomial by brute force. +// ---------------------------------------------------------------------------- +template +inline +void interpolate_brute_force(double x1, double y1, double x2, double y2, + double k1, double k2, + PointPlotter plot, double res) +{ + double a, b, c, d; + cubic_coefficients(x1, y1, x2, y2, k1, k2, a, b, c, d); + + // Calculate each point. + for (double x = x1; x <= x2; x += res) { + double y = ((a*x + b)*x + c)*x + d; + plot(x, y); + } +} + +// ---------------------------------------------------------------------------- +// Evaluation of cubic polynomial by forward differencing. +// ---------------------------------------------------------------------------- +template +inline +void interpolate_forward_difference(double x1, double y1, double x2, double y2, + double k1, double k2, + PointPlotter plot, double res) +{ + double a, b, c, d; + cubic_coefficients(x1, y1, x2, y2, k1, k2, a, b, c, d); + + double y = ((a*x1 + b)*x1 + c)*x1 + d; + double dy = (3*a*(x1 + res) + 2*b)*x1*res + ((a*res + b)*res + c)*res; + double d2y = (6*a*(x1 + res) + 2*b)*res*res; + double d3y = 6*a*res*res*res; + + // Calculate each point. + for (double x = x1; x <= x2; x += res) { + plot(x, y); + y += dy; dy += d2y; d2y += d3y; + } +} + +template +inline +double x(PointIter p) +{ + return (*p)[0]; +} + +template +inline +double y(PointIter p) +{ + return (*p)[1]; +} + +// ---------------------------------------------------------------------------- +// Evaluation of complete interpolating function. +// Note that since each curve segment is controlled by four points, the +// end points will not be interpolated. If extra control points are not +// desirable, the end points can simply be repeated to ensure interpolation. +// Note also that points of non-differentiability and discontinuity can be +// introduced by repeating points. +// ---------------------------------------------------------------------------- +template +inline +void interpolate(PointIter p0, PointIter pn, PointPlotter plot, double res) +{ + double k1, k2; + + // Set up points for first curve segment. + PointIter p1 = p0; ++p1; + PointIter p2 = p1; ++p2; + PointIter p3 = p2; ++p3; + + // Draw each curve segment. + for (; p2 != pn; ++p0, ++p1, ++p2, ++p3) { + // p1 and p2 equal; single point. + if (x(p1) == x(p2)) { + continue; + } + // Both end points repeated; straight line. + if (x(p0) == x(p1) && x(p2) == x(p3)) { + k1 = k2 = (y(p2) - y(p1))/(x(p2) - x(p1)); + } + // p0 and p1 equal; use f''(x1) = 0. + else if (x(p0) == x(p1)) { + k2 = (y(p3) - y(p1))/(x(p3) - x(p1)); + k1 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k2)/2; + } + // p2 and p3 equal; use f''(x2) = 0. + else if (x(p2) == x(p3)) { + k1 = (y(p2) - y(p0))/(x(p2) - x(p0)); + k2 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k1)/2; + } + // Normal curve. + else { + k1 = (y(p2) - y(p0))/(x(p2) - x(p0)); + k2 = (y(p3) - y(p1))/(x(p3) - x(p1)); + } + + interpolate_segment(x(p1), y(p1), x(p2), y(p2), k1, k2, plot, res); + } +} + +// ---------------------------------------------------------------------------- +// Class for plotting integers into an array. +// ---------------------------------------------------------------------------- +template +class PointPlotter +{ + protected: + F* f; + + public: + PointPlotter(F* arr) : f(arr) + { + } + + void operator ()(double x, double y) + { + // Clamp negative values to zero. + if (y < 0) { + y = 0; + } + + f[int(x)] = F(y + 0.5); + } +}; + +} // namespace reSID + +#endif // not RESID_SPLINE_H diff --git a/C64/SID/resid/version.cc b/C64/SID/resid/version.cc new file mode 100755 index 00000000..069d8b1d --- /dev/null +++ b/C64/SID/resid/version.cc @@ -0,0 +1,22 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#define RESID_VERSION_CC + +#include "resid-config.h" diff --git a/C64/SID/resid/voice.cc b/C64/SID/resid/voice.cc new file mode 100755 index 00000000..eca230f0 --- /dev/null +++ b/C64/SID/resid/voice.cc @@ -0,0 +1,127 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2004 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#define RESID_VOICE_CC + +#include "voice.h" + +namespace reSID +{ + +// ---------------------------------------------------------------------------- +// Constructor. +// ---------------------------------------------------------------------------- +Voice::Voice() +{ + set_chip_model(MOS6581); +} + +// ---------------------------------------------------------------------------- +// Set chip model. +// ---------------------------------------------------------------------------- +void Voice::set_chip_model(chip_model model) +{ + wave.set_chip_model(model); + envelope.set_chip_model(model); + + if (model == MOS6581) { + // The waveform D/A converter introduces a DC offset in the signal + // to the envelope multiplying D/A converter. The "zero" level of + // the waveform D/A converter can be found as follows: + // + // Measure the "zero" voltage of voice 3 on the SID audio output + // pin, routing only voice 3 to the mixer ($d417 = $0b, $d418 = + // $0f, all other registers zeroed). + // + // Then set the sustain level for voice 3 to maximum and search for + // the waveform output value yielding the same voltage as found + // above. This is done by trying out different waveform output + // values until the correct value is found, e.g. with the following + // program: + // + // lda #$08 + // sta $d412 + // lda #$0b + // sta $d417 + // lda #$0f + // sta $d418 + // lda #$f0 + // sta $d414 + // lda #$21 + // sta $d412 + // lda #$01 + // sta $d40e + // + // ldx #$00 + // lda #$38 ; Tweak this to find the "zero" level + //l cmp $d41b + // bne l + // stx $d40e ; Stop frequency counter - freeze waveform output + // brk + // + // The waveform output range is 0x000 to 0xfff, so the "zero" + // level should ideally have been 0x800. In the measured chip, the + // waveform output "zero" level was found to be 0x380 (i.e. $d41b + // = 0x38) at an audio output voltage of 5.94V. + // + // With knowledge of the mixer op-amp characteristics, further estimates + // of waveform voltages can be obtained by sampling the EXT IN pin. + // From EXT IN samples, the corresponding waveform output can be found by + // using the model for the mixer. + // + // Such measurements have been done on a chip marked MOS 6581R4AR + // 0687 14, and the following results have been obtained: + // * The full range of one voice is approximately 1.5V. + // * The "zero" level rides at approximately 5.0V. + // + wave_zero = 0x380; + } + else { + // No DC offsets in the MOS8580. + wave_zero = 0x9e0; + } +} + +// ---------------------------------------------------------------------------- +// Set sync source. +// ---------------------------------------------------------------------------- +void Voice::set_sync_source(Voice* source) +{ + wave.set_sync_source(&source->wave); +} + +// ---------------------------------------------------------------------------- +// Register functions. +// ---------------------------------------------------------------------------- +void Voice::writeCONTROL_REG(reg8 control) +{ + wave.writeCONTROL_REG(control); + envelope.writeCONTROL_REG(control); +} + +// ---------------------------------------------------------------------------- +// SID reset. +// ---------------------------------------------------------------------------- +void Voice::reset() +{ + wave.reset(); + envelope.reset(); +} + +} // namespace reSID diff --git a/C64/SID/resid/voice.h b/C64/SID/resid/voice.h new file mode 100755 index 00000000..0ad9c9b9 --- /dev/null +++ b/C64/SID/resid/voice.h @@ -0,0 +1,109 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_VOICE_H +#define RESID_VOICE_H + +#include "resid-config.h" +#include "wave.h" +#include "envelope.h" + +namespace reSID +{ + +class Voice +{ +public: + Voice(); + + void set_chip_model(chip_model model); + void set_sync_source(Voice*); + void reset(); + + void writeCONTROL_REG(reg8); + + // Amplitude modulated waveform output. + // Range [-2048*255, 2047*255]. + int output(); + + WaveformGenerator wave; + EnvelopeGenerator envelope; + +protected: + // Waveform D/A zero level. + short wave_zero; + +friend class SID; +}; + + +// ---------------------------------------------------------------------------- +// Inline functions. +// The following function is defined inline because it is called every +// time a sample is calculated. +// ---------------------------------------------------------------------------- + +#if RESID_INLINING || defined(RESID_VOICE_CC) + +// ---------------------------------------------------------------------------- +// Amplitude modulated waveform output (20 bits). +// Ideal range [-2048*255, 2047*255]. +// ---------------------------------------------------------------------------- + +// The output for a voice is produced by a multiplying DAC, where the +// waveform output modulates the envelope output. +// +// As noted by Bob Yannes: "The 8-bit output of the Envelope Generator was then +// sent to the Multiplying D/A converter to modulate the amplitude of the +// selected Oscillator Waveform (to be technically accurate, actually the +// waveform was modulating the output of the Envelope Generator, but the result +// is the same)". +// +// 7 6 5 4 3 2 1 0 VGND +// | | | | | | | | | Missing +// 2R 2R 2R 2R 2R 2R 2R 2R 2R termination +// | | | | | | | | | +// --R---R---R---R---R---R---R-- --- +// | _____ +// __|__ __|__ | +// ----- ===== | +// | | | | | +// 12V --- ----- ------- GND +// | +// vout +// +// Bit on: wout (see figure in wave.h) +// Bit off: 5V (VGND) +// +// As is the case with all MOS 6581 DACs, the termination to (virtual) ground +// at bit 0 is missing. The MOS 8580 has correct termination. +// + +RESID_INLINE +int Voice::output() +{ + // Multiply oscillator output with envelope output. + return (wave.output() - wave_zero)*envelope.output(); +} + +#endif // RESID_INLINING || defined(RESID_VOICE_CC) + +} // namespace reSID + +#endif // not RESID_VOICE_H diff --git a/C64/SID/resid/wave.cc b/C64/SID/resid/wave.cc new file mode 100755 index 00000000..b7a4118f --- /dev/null +++ b/C64/SID/resid/wave.cc @@ -0,0 +1,294 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#define RESID_WAVE_CC + +#include "wave.h" +#include "dac.h" + +namespace reSID +{ + +// Number of cycles after which the shift register is reset +// when the test bit is set. +const cycle_count SHIFT_REGISTER_RESET_6581 = 0x8000; +const cycle_count SHIFT_REGISTER_RESET_8580 = 0x950000; + +// Waveform lookup tables. +unsigned short WaveformGenerator::model_wave[2][8][1 << 12] = { + { + {0}, + {0}, + {0}, +#include "wave6581__ST.h" + {0}, +#include "wave6581_P_T.h" +#include "wave6581_PS_.h" +#include "wave6581_PST.h" + }, + { + {0}, + {0}, + {0}, +#include "wave8580__ST.h" + {0}, +#include "wave8580_P_T.h" +#include "wave8580_PS_.h" +#include "wave8580_PST.h" + } +}; + + +// DAC lookup tables. +unsigned short WaveformGenerator::model_dac[2][1 << 12] = { + {0}, + {0}, +}; + + +// ---------------------------------------------------------------------------- +// Constructor. +// ---------------------------------------------------------------------------- +WaveformGenerator::WaveformGenerator() +{ + static bool class_init; + + if (!class_init) { + // Calculate tables for normal waveforms. + accumulator = 0; + for (int i = 0; i < (1 << 12); i++) { + reg24 msb = accumulator & 0x800000; + + // Noise mask, triangle, sawtooth, pulse mask. + // The triangle calculation is made branch-free, just for the hell of it. + model_wave[0][0][i] = model_wave[1][0][i] = 0xfff; + model_wave[0][1][i] = model_wave[1][1][i] = ((accumulator ^ -!!msb) >> 11) & 0xffe; + model_wave[0][2][i] = model_wave[1][2][i] = accumulator >> 12; + model_wave[0][4][i] = model_wave[1][4][i] = 0xfff; + + accumulator += 0x1000; + } + + // Build DAC lookup tables for 12-bit DACs. + // MOS 6581: 2R/R ~ 2.20, missing termination resistor. + build_dac_table(model_dac[0], 12, 2.20, false); + // MOS 8580: 2R/R ~ 2.00, correct termination. + build_dac_table(model_dac[1], 12, 2.00, true); + + class_init = true; + } + + sync_source = this; + + sid_model = MOS6581; + + // Accumulator's even bits are high on powerup + accumulator = 0x555555; + + tri_saw_pipeline = 0x555; + + reset(); +} + + +// ---------------------------------------------------------------------------- +// Set sync source. +// ---------------------------------------------------------------------------- +void WaveformGenerator::set_sync_source(WaveformGenerator* source) +{ + sync_source = source; + source->sync_dest = this; +} + + +// ---------------------------------------------------------------------------- +// Set chip model. +// ---------------------------------------------------------------------------- +void WaveformGenerator::set_chip_model(chip_model model) +{ + sid_model = model; + wave = model_wave[model][waveform & 0x7]; +} + + +// ---------------------------------------------------------------------------- +// Register functions. +// ---------------------------------------------------------------------------- +void WaveformGenerator::writeFREQ_LO(reg8 freq_lo) +{ + freq = (freq & 0xff00) | (freq_lo & 0x00ff); +} + +void WaveformGenerator::writeFREQ_HI(reg8 freq_hi) +{ + freq = ((freq_hi << 8) & 0xff00) | (freq & 0x00ff); +} + +void WaveformGenerator::writePW_LO(reg8 pw_lo) +{ + pw = (pw & 0xf00) | (pw_lo & 0x0ff); + // Push next pulse level into pulse level pipeline. + pulse_output = (accumulator >> 12) >= pw ? 0xfff : 0x000; +} + +void WaveformGenerator::writePW_HI(reg8 pw_hi) +{ + pw = ((pw_hi << 8) & 0xf00) | (pw & 0x0ff); + // Push next pulse level into pulse level pipeline. + pulse_output = (accumulator >> 12) >= pw ? 0xfff : 0x000; +} + +bool do_pre_writeback(reg8 waveform_prev, reg8 waveform, bool is6581) +{ + // no writeback without combined waveforms + if (likely(waveform_prev <= 0x8)) + return false; + // This need more investigation + if (waveform == 8) + return false; + // What's happening here? + if (is6581 && + ((((waveform_prev & 0x3) == 0x1) && ((waveform & 0x3) == 0x2)) + || (((waveform_prev & 0x3) == 0x2) && ((waveform & 0x3) == 0x1)))) + return false; + // ok do the writeback + return true; +} + +void WaveformGenerator::writeCONTROL_REG(reg8 control) +{ + reg8 waveform_prev = waveform; + reg8 test_prev = test; + waveform = (control >> 4) & 0x0f; + test = control & 0x08; + ring_mod = control & 0x04; + sync = control & 0x02; + + // Set up waveform table. + wave = model_wave[sid_model][waveform & 0x7]; + + // Substitution of accumulator MSB when sawtooth = 0, ring_mod = 1. + ring_msb_mask = ((~control >> 5) & (control >> 2) & 0x1) << 23; + + // no_noise and no_pulse are used in set_waveform_output() as bitmasks to + // only let the noise or pulse influence the output when the noise or pulse + // waveforms are selected. + no_noise = waveform & 0x8 ? 0x000 : 0xfff; + no_noise_or_noise_output = no_noise | noise_output; + no_pulse = waveform & 0x4 ? 0x000 : 0xfff; + + // Test bit rising. + // The accumulator is cleared, while the the shift register is prepared for + // shifting by interconnecting the register bits. The internal SRAM cells + // start to slowly rise up towards one. The SRAM cells reach one within + // approximately $8000 cycles, yielding a shift register value of + // 0x7fffff. + if (!test_prev && test) { + // Reset accumulator. + accumulator = 0; + + // Flush shift pipeline. + shift_pipeline = 0; + + // Set reset time for shift register. + shift_register_reset = (sid_model == MOS6581) ? SHIFT_REGISTER_RESET_6581 : SHIFT_REGISTER_RESET_8580; + + // The test bit sets pulse high. + pulse_output = 0xfff; + } + else if (test_prev && !test) { + // When the test bit is falling, the second phase of the shift is + // completed by enabling SRAM write. + + // During first phase of the shift the bits are interconnected + // and the output of each bit is latched into the following. + // The output may overwrite the latched value. + if (do_pre_writeback(waveform_prev, waveform, sid_model == MOS6581)) { + write_shift_register(); + } + + // bit0 = (bit22 | test) ^ bit17 = 1 ^ bit17 = ~bit17 + reg24 bit0 = (~shift_register >> 17) & 0x1; + shift_register = ((shift_register << 1) | bit0) & 0x7fffff; + + // Set new noise waveform output. + set_noise_output(); + } + + if (waveform) { + // Set new waveform output. + set_waveform_output(); + } + else if (waveform_prev) { + // Change to floating DAC input. + // Reset fading time for floating DAC input. + // + // We have two SOAS/C samplings showing that floating DAC + // keeps its state for at least 0x14000 cycles. + // + // This can't be found via sampling OSC3, it seems that + // the actual analog output must be sampled and timed. + floating_output_ttl = 0x14000; + } + + // The gate bit is handled by the EnvelopeGenerator. +} + +reg8 WaveformGenerator::readOSC() +{ + return osc3 >> 4; +} + +// ---------------------------------------------------------------------------- +// SID reset. +// ---------------------------------------------------------------------------- +void WaveformGenerator::reset() +{ + // accumulator is not changed on reset + freq = 0; + pw = 0; + + msb_rising = false; + + waveform = 0; + test = 0; + ring_mod = 0; + sync = 0; + + wave = model_wave[sid_model][0]; + + ring_msb_mask = 0; + no_noise = 0xfff; + no_pulse = 0xfff; + pulse_output = 0xfff; + + // reset shift register + // when reset is released the shift register is clocked once + shift_register = 0x7ffffe; + shift_register_reset = 0; + set_noise_output(); + + shift_pipeline = 0; + + waveform_output = 0; + osc3 = 0; + floating_output_ttl = 0; +} + +} // namespace reSID diff --git a/C64/SID/resid/wave.h b/C64/SID/resid/wave.h new file mode 100755 index 00000000..b1b4d238 --- /dev/null +++ b/C64/SID/resid/wave.h @@ -0,0 +1,587 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +#ifndef RESID_WAVE_H +#define RESID_WAVE_H + +#include "resid-config.h" + +namespace reSID +{ + +// ---------------------------------------------------------------------------- +// A 24 bit accumulator is the basis for waveform generation. FREQ is added to +// the lower 16 bits of the accumulator each cycle. +// The accumulator is set to zero when TEST is set, and starts counting +// when TEST is cleared. +// The noise waveform is taken from intermediate bits of a 23 bit shift +// register. This register is clocked by bit 19 of the accumulator. +// ---------------------------------------------------------------------------- +class WaveformGenerator +{ +public: + WaveformGenerator(); + + void set_sync_source(WaveformGenerator*); + void set_chip_model(chip_model model); + + void clock(); + void clock(cycle_count delta_t); + void synchronize(); + void reset(); + + void writeFREQ_LO(reg8); + void writeFREQ_HI(reg8); + void writePW_LO(reg8); + void writePW_HI(reg8); + void writeCONTROL_REG(reg8); + reg8 readOSC(); + + // 12-bit waveform output. + short output(); + + // Calculate and set waveform output value. + void set_waveform_output(); + void set_waveform_output(cycle_count delta_t); + +protected: + void clock_shift_register(); + void write_shift_register(); + void reset_shift_register(); + void set_noise_output(); + + const WaveformGenerator* sync_source; + WaveformGenerator* sync_dest; + + reg24 accumulator; + + // Tell whether the accumulator MSB was set high on this cycle. + bool msb_rising; + + // Fout = (Fn*Fclk/16777216)Hz + // reg16 freq; + reg24 freq; + // PWout = (PWn/40.95)% + reg12 pw; + + reg24 shift_register; + + // Remaining time to fully reset shift register. + cycle_count shift_register_reset; + // Emulation of pipeline causing bit 19 to clock the shift register. + cycle_count shift_pipeline; + + // Helper variables for waveform table lookup. + reg24 ring_msb_mask; + unsigned short no_noise; + unsigned short noise_output; + unsigned short no_noise_or_noise_output; + unsigned short no_pulse; + unsigned short pulse_output; + + // The control register right-shifted 4 bits; used for waveform table lookup. + reg8 waveform; + + // 8580 tri/saw pipeline + reg12 tri_saw_pipeline; + reg12 osc3; + + // The remaining control register bits. + reg8 test; + reg8 ring_mod; + reg8 sync; + // The gate bit is handled by the EnvelopeGenerator. + + // DAC input. + reg12 waveform_output; + // Fading time for floating DAC input (waveform 0). + cycle_count floating_output_ttl; + + chip_model sid_model; + + // Sample data for waveforms, not including noise. + unsigned short* wave; + static unsigned short model_wave[2][8][1 << 12]; + // DAC lookup tables. + static unsigned short model_dac[2][1 << 12]; + +friend class Voice; +friend class SID; +}; + + +// ---------------------------------------------------------------------------- +// Inline functions. +// The following functions are defined inline because they are called every +// time a sample is calculated. +// ---------------------------------------------------------------------------- + +#if RESID_INLINING || defined(RESID_WAVE_CC) + +// ---------------------------------------------------------------------------- +// SID clocking - 1 cycle. +// ---------------------------------------------------------------------------- +RESID_INLINE +void WaveformGenerator::clock() +{ + if (unlikely(test)) { + // Count down time to fully reset shift register. + if (unlikely(shift_register_reset) && unlikely(!--shift_register_reset)) { + reset_shift_register(); + } + + // The test bit sets pulse high. + pulse_output = 0xfff; + } + else { + // Calculate new accumulator value; + reg24 accumulator_next = (accumulator + freq) & 0xffffff; + reg24 accumulator_bits_set = ~accumulator & accumulator_next; + accumulator = accumulator_next; + + // Check whether the MSB is set high. This is used for synchronization. + msb_rising = (accumulator_bits_set & 0x800000) ? true : false; + + // Shift noise register once for each time accumulator bit 19 is set high. + // The shift is delayed 2 cycles. + if (unlikely(accumulator_bits_set & 0x080000)) { + // Pipeline: Detect rising bit, shift phase 1, shift phase 2. + shift_pipeline = 2; + } + else if (unlikely(shift_pipeline) && !--shift_pipeline) { + clock_shift_register(); + } + } +} + +// ---------------------------------------------------------------------------- +// SID clocking - delta_t cycles. +// ---------------------------------------------------------------------------- +RESID_INLINE +void WaveformGenerator::clock(cycle_count delta_t) +{ + if (unlikely(test)) { + // Count down time to fully reset shift register. + if (shift_register_reset) { + shift_register_reset -= delta_t; + if (unlikely(shift_register_reset <= 0)) { + reset_shift_register(); + } + } + + // The test bit sets pulse high. + pulse_output = 0xfff; + } + else { + // Calculate new accumulator value; + reg24 delta_accumulator = delta_t*freq; + reg24 accumulator_next = (accumulator + delta_accumulator) & 0xffffff; + reg24 accumulator_bits_set = ~accumulator & accumulator_next; + accumulator = accumulator_next; + + // Check whether the MSB is set high. This is used for synchronization. + msb_rising = (accumulator_bits_set & 0x800000) ? true : false; + + // NB! Any pipelined shift register clocking from single cycle clocking + // will be lost. It is not worth the trouble to flush the pipeline here. + + // Shift noise register once for each time accumulator bit 19 is set high. + // Bit 19 is set high each time 2^20 (0x100000) is added to the accumulator. + reg24 shift_period = 0x100000; + + while (delta_accumulator) { + if (likely(delta_accumulator < shift_period)) { + shift_period = delta_accumulator; + // Determine whether bit 19 is set on the last period. + // NB! Requires two's complement integer. + if (likely(shift_period <= 0x080000)) { + // Check for flip from 0 to 1. + if (((accumulator - shift_period) & 0x080000) || !(accumulator & 0x080000)) + { + break; + } + } + else { + // Check for flip from 0 (to 1 or via 1 to 0) or from 1 via 0 to 1. + if (((accumulator - shift_period) & 0x080000) && !(accumulator & 0x080000)) + { + break; + } + } + } + + // Shift the noise/random register. + // NB! The two-cycle pipeline delay is only modeled for 1 cycle clocking. + clock_shift_register(); + + delta_accumulator -= shift_period; + } + + // Calculate pulse high/low. + // NB! The one-cycle pipeline delay is only modeled for 1 cycle clocking. + pulse_output = (accumulator >> 12) >= pw ? 0xfff : 0x000; + } +} + + +// ---------------------------------------------------------------------------- +// Synchronize oscillators. +// This must be done after all the oscillators have been clock()'ed since the +// oscillators operate in parallel. +// Note that the oscillators must be clocked exactly on the cycle when the +// MSB is set high for hard sync to operate correctly. See SID::clock(). +// ---------------------------------------------------------------------------- +RESID_INLINE +void WaveformGenerator::synchronize() +{ + // A special case occurs when a sync source is synced itself on the same + // cycle as when its MSB is set high. In this case the destination will + // not be synced. This has been verified by sampling OSC3. + if (unlikely(msb_rising) && sync_dest->sync && !(sync && sync_source->msb_rising)) { + sync_dest->accumulator = 0; + } +} + + +// ---------------------------------------------------------------------------- +// Waveform output. +// The output from SID 8580 is delayed one cycle compared to SID 6581; +// this is only modeled for single cycle clocking (see sid.cc). +// ---------------------------------------------------------------------------- + +// No waveform: +// When no waveform is selected, the DAC input is floating. +// + +// Triangle: +// The upper 12 bits of the accumulator are used. +// The MSB is used to create the falling edge of the triangle by inverting +// the lower 11 bits. The MSB is thrown away and the lower 11 bits are +// left-shifted (half the resolution, full amplitude). +// Ring modulation substitutes the MSB with MSB EOR NOT sync_source MSB. +// + +// Sawtooth: +// The output is identical to the upper 12 bits of the accumulator. +// + +// Pulse: +// The upper 12 bits of the accumulator are used. +// These bits are compared to the pulse width register by a 12 bit digital +// comparator; output is either all one or all zero bits. +// The pulse setting is delayed one cycle after the compare; this is only +// modeled for single cycle clocking. +// +// The test bit, when set to one, holds the pulse waveform output at 0xfff +// regardless of the pulse width setting. +// + +// Noise: +// The noise output is taken from intermediate bits of a 23-bit shift register +// which is clocked by bit 19 of the accumulator. +// The shift is delayed 2 cycles after bit 19 is set high; this is only +// modeled for single cycle clocking. +// +// Operation: Calculate EOR result, shift register, set bit 0 = result. +// +// reset ------------------------------------------- +// | | | +// test--OR-->EOR<-- | +// | | | +// 2 2 2 1 1 1 1 1 1 1 1 1 1 | +// Register bits: 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 <--- +// | | | | | | | | +// Waveform bits: 1 1 9 8 7 6 5 4 +// 1 0 +// +// The low 4 waveform bits are zero (grounded). +// + +RESID_INLINE void WaveformGenerator::clock_shift_register() +{ + // bit0 = (bit22 | test) ^ bit17 + reg24 bit0 = ((shift_register >> 22) ^ (shift_register >> 17)) & 0x1; + shift_register = ((shift_register << 1) | bit0) & 0x7fffff; + + // New noise waveform output. + set_noise_output(); +} + +RESID_INLINE void WaveformGenerator::write_shift_register() +{ + // Write changes to the shift register output caused by combined waveforms + // back into the shift register. + // A bit once set to zero cannot be changed, hence the and'ing. + // FIXME: Write test program to check the effect of 1 bits and whether + // neighboring bits are affected. + + shift_register &= + ~((1<<20)|(1<<18)|(1<<14)|(1<<11)|(1<<9)|(1<<5)|(1<<2)|(1<<0)) | + ((waveform_output & 0x800) << 9) | // Bit 11 -> bit 20 + ((waveform_output & 0x400) << 8) | // Bit 10 -> bit 18 + ((waveform_output & 0x200) << 5) | // Bit 9 -> bit 14 + ((waveform_output & 0x100) << 3) | // Bit 8 -> bit 11 + ((waveform_output & 0x080) << 2) | // Bit 7 -> bit 9 + ((waveform_output & 0x040) >> 1) | // Bit 6 -> bit 5 + ((waveform_output & 0x020) >> 3) | // Bit 5 -> bit 2 + ((waveform_output & 0x010) >> 4); // Bit 4 -> bit 0 + + noise_output &= waveform_output; + no_noise_or_noise_output = no_noise | noise_output; +} + +RESID_INLINE void WaveformGenerator::reset_shift_register() +{ + shift_register = 0x7fffff; + shift_register_reset = 0; + + // New noise waveform output. + set_noise_output(); +} + +RESID_INLINE void WaveformGenerator::set_noise_output() +{ + noise_output = + ((shift_register & 0x100000) >> 9) | + ((shift_register & 0x040000) >> 8) | + ((shift_register & 0x004000) >> 5) | + ((shift_register & 0x000800) >> 3) | + ((shift_register & 0x000200) >> 2) | + ((shift_register & 0x000020) << 1) | + ((shift_register & 0x000004) << 3) | + ((shift_register & 0x000001) << 4); + + no_noise_or_noise_output = no_noise | noise_output; +} + +// Combined waveforms: +// By combining waveforms, the bits of each waveform are effectively short +// circuited. A zero bit in one waveform will result in a zero output bit +// (thus the infamous claim that the waveforms are AND'ed). +// However, a zero bit in one waveform may also affect the neighboring bits +// in the output. +// +// Example: +// +// 1 1 +// Bit # 1 0 9 8 7 6 5 4 3 2 1 0 +// ----------------------- +// Sawtooth 0 0 0 1 1 1 1 1 1 0 0 0 +// +// Triangle 0 0 1 1 1 1 1 1 0 0 0 0 +// +// AND 0 0 0 1 1 1 1 1 0 0 0 0 +// +// Output 0 0 0 0 1 1 1 0 0 0 0 0 +// +// +// Re-vectorized die photographs reveal the mechanism behind this behavior. +// Each waveform selector bit acts as a switch, which directly connects +// internal outputs into the waveform DAC inputs as follows: +// +// * Noise outputs the shift register bits to DAC inputs as described above. +// Each output is also used as input to the next bit when the shift register +// is shifted. +// * Pulse connects a single line to all DAC inputs. The line is connected to +// either 5V (pulse on) or 0V (pulse off) at bit 11, and ends at bit 0. +// * Triangle connects the upper 11 bits of the (MSB EOR'ed) accumulator to the +// DAC inputs, so that DAC bit 0 = 0, DAC bit n = accumulator bit n - 1. +// * Sawtooth connects the upper 12 bits of the accumulator to the DAC inputs, +// so that DAC bit n = accumulator bit n. Sawtooth blocks out the MSB from +// the EOR used to generate the triangle waveform. +// +// We can thus draw the following conclusions: +// +// * The shift register may be written to by combined waveforms. +// * The pulse waveform interconnects all bits in combined waveforms via the +// pulse line. +// * The combination of triangle and sawtooth interconnects neighboring bits +// of the sawtooth waveform. +// +// This behavior would be quite difficult to model exactly, since the short +// circuits are not binary, but are subject to analog effects. Tests show that +// minor (1 bit) differences can actually occur in the output from otherwise +// identical samples from OSC3 when waveforms are combined. To further +// complicate the situation the output changes slightly with time (more +// neighboring bits are successively set) when the 12-bit waveform +// registers are kept unchanged. +// +// The output is instead approximated by using the upper bits of the +// accumulator as an index to look up the combined output in a table +// containing actual combined waveform samples from OSC3. +// These samples are 8 bit, so 4 bits of waveform resolution is lost. +// All OSC3 samples are taken with FREQ=0x1000, adding a 1 to the upper 12 +// bits of the accumulator each cycle for a sample period of 4096 cycles. +// +// Sawtooth+Triangle: +// The accumulator is used to look up an OSC3 sample. +// +// Pulse+Triangle: +// The accumulator is used to look up an OSC3 sample. When ring modulation is +// selected, the accumulator MSB is substituted with MSB EOR NOT sync_source MSB. +// +// Pulse+Sawtooth: +// The accumulator is used to look up an OSC3 sample. +// The sample is output if the pulse output is on. +// +// Pulse+Sawtooth+Triangle: +// The accumulator is used to look up an OSC3 sample. +// The sample is output if the pulse output is on. +// +// Combined waveforms including noise: +// All waveform combinations including noise output zero after a few cycles, +// since the waveform bits are and'ed into the shift register via the shift +// register outputs. + +RESID_INLINE +void WaveformGenerator::set_waveform_output() +{ + // Set output value. + if (likely(waveform)) { + // The bit masks no_pulse and no_noise are used to achieve branch-free + // calculation of the output value. + int ix = (accumulator ^ (~sync_source->accumulator & ring_msb_mask)) >> 12; + + waveform_output = wave[ix] & (no_pulse | pulse_output) & no_noise_or_noise_output; + + // Triangle/Sawtooth output is delayed half cycle on 8580. + // This will appear as a one cycle delay on OSC3 as it is + // latched in the first phase of the clock. + if ((waveform & 3) && (sid_model == MOS8580)) + { + osc3 = tri_saw_pipeline & (no_pulse | pulse_output) & no_noise_or_noise_output; + tri_saw_pipeline = wave[ix]; + } + else + { + osc3 = waveform_output; + } + + if ((waveform & 0x2) && unlikely(waveform & 0xd) && (sid_model == MOS6581)) { + // In the 6581 the top bit of the accumulator may be driven low by combined waveforms + // when the sawtooth is selected + accumulator &= (waveform_output << 12) | 0x7fffff; + } + + if (unlikely(waveform > 0x8) && likely(!test) && likely(shift_pipeline != 1)) { + // Combined waveforms write to the shift register. + write_shift_register(); + } + } + else { + // Age floating DAC input. + if (likely(floating_output_ttl) && unlikely(!--floating_output_ttl)) { + waveform_output = 0; + } + } + + // The pulse level is defined as (accumulator >> 12) >= pw ? 0xfff : 0x000. + // The expression -((accumulator >> 12) >= pw) & 0xfff yields the same + // results without any branching (and thus without any pipeline stalls). + // NB! This expression relies on that the result of a boolean expression + // is either 0 or 1, and furthermore requires two's complement integer. + // A few more cycles may be saved by storing the pulse width left shifted + // 12 bits, and dropping the and with 0xfff (this is valid since pulse is + // used as a bit mask on 12 bit values), yielding the expression + // -(accumulator >= pw24). However this only results in negligible savings. + + // The result of the pulse width compare is delayed one cycle. + // Push next pulse level into pulse level pipeline. + pulse_output = -((accumulator >> 12) >= pw) & 0xfff; +} + +RESID_INLINE +void WaveformGenerator::set_waveform_output(cycle_count delta_t) +{ + // Set output value. + if (likely(waveform)) { + // The bit masks no_pulse and no_noise are used to achieve branch-free + // calculation of the output value. + int ix = (accumulator ^ (~sync_source->accumulator & ring_msb_mask)) >> 12; + waveform_output = + wave[ix] & (no_pulse | pulse_output) & no_noise_or_noise_output; + // Triangle/Sawtooth output delay for the 8580 is not modeled + osc3 = waveform_output; + + if ((waveform & 0x2) && unlikely(waveform & 0xd) && (sid_model == MOS6581)) { + accumulator &= (waveform_output << 12) | 0x7fffff; + } + + if (unlikely(waveform > 0x8) && likely(!test)) { + // Combined waveforms write to the shift register. + // NB! Since cycles are skipped in delta_t clocking, writes will be + // missed. Single cycle clocking must be used for 100% correct operation. + write_shift_register(); + } + } + else { + if (likely(floating_output_ttl)) { + // Age floating D/A output. + floating_output_ttl -= delta_t; + if (unlikely(floating_output_ttl <= 0)) { + floating_output_ttl = 0; + waveform_output = 0; + } + } + } +} + + +// ---------------------------------------------------------------------------- +// Waveform output (12 bits). +// ---------------------------------------------------------------------------- + +// The digital waveform output is converted to an analog signal by a 12-bit +// DAC. Re-vectorized die photographs reveal that the DAC is an R-2R ladder +// built up as follows: +// +// 12V 11 10 9 8 7 6 5 4 3 2 1 0 GND +// Strange | | | | | | | | | | | | | | Missing +// part 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R term. +// (bias) | | | | | | | | | | | | | | +// --R- --R---R---R---R---R---R---R---R---R---R---R-- --- +// | _____ +// __|__ __|__ | +// ----- ===== | +// | | | | | +// 12V --- ----- ------- GND +// | +// wout +// +// Bit on: 5V +// Bit off: 0V (GND) +// +// As is the case with all MOS 6581 DACs, the termination to (virtual) ground +// at bit 0 is missing. The MOS 8580 has correct termination, and has also +// done away with the bias part on the left hand side of the figure above. +// + +RESID_INLINE +short WaveformGenerator::output() +{ + // DAC imperfections are emulated by using waveform_output as an index + // into a DAC lookup table. readOSC() uses waveform_output directly. + return model_dac[sid_model][waveform_output]; +} + +#endif // RESID_INLINING || defined(RESID_WAVE_CC) + +} // namespace reSID + +#endif // not RESID_WAVE_H diff --git a/C64/SID/resid/wave6581_PST.h b/C64/SID/resid/wave6581_PST.h new file mode 100755 index 00000000..781b053a --- /dev/null +++ b/C64/SID/resid/wave6581_PST.h @@ -0,0 +1,533 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +{ +/* 0x000: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x008: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x010: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x018: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x020: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x028: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x030: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x038: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x040: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x048: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x050: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x058: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x060: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x068: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x070: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x078: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x080: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x088: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x090: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x098: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x100: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x108: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x110: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x118: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x120: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x128: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x130: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x138: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x140: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x148: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x150: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x158: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x160: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x168: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x170: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x178: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x180: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x188: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x190: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x198: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x200: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x208: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x210: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x218: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x220: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x228: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x230: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x238: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x240: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x248: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x250: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x258: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x260: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x268: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x270: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x278: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x280: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x288: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x290: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x298: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x300: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x308: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x310: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x318: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x320: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x328: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x330: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x338: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x340: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x348: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x350: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x358: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x360: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x368: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x370: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x378: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x380: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x388: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x390: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x398: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3f0, +/* 0x400: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x408: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x410: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x418: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x420: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x428: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x430: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x438: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x440: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x448: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x450: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x458: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x460: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x468: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x470: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x478: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x480: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x488: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x490: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x498: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x500: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x508: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x510: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x518: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x520: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x528: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x530: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x538: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x540: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x548: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x550: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x558: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x560: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x568: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x570: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x578: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x580: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x588: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x590: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x598: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x600: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x608: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x610: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x618: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x620: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x628: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x630: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x638: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x640: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x648: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x650: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x658: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x660: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x668: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x670: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x678: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x680: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x688: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x690: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x698: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x700: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x708: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x710: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x718: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x720: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x728: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x730: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x738: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x740: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x748: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x750: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x758: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x760: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x768: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x770: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x778: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x780: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x788: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x790: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x798: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x200, +/* 0x7f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x300, +/* 0x7f8: */ 0x000, 0x000, 0x000, 0x780, 0x780, 0x7e0, 0x7f0, 0x7f0, +/* 0x800: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x808: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x810: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x818: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x820: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x828: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x830: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x838: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x840: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x848: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x850: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x858: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x860: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x868: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x870: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x878: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x880: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x888: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x890: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x898: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x900: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x908: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x910: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x918: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x920: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x928: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x930: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x938: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x940: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x948: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x950: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x958: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x960: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x968: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x970: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x978: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x980: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x988: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x990: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x998: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3f0, +/* 0xc00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xec0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xec8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xee0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xee8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xef0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xef8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x200, +/* 0xff0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x300, +/* 0xff8: */ 0x000, 0x000, 0x000, 0x780, 0x780, 0x7e0, 0x7f0, 0x7f0, +}, diff --git a/C64/SID/resid/wave6581_PS_.h b/C64/SID/resid/wave6581_PS_.h new file mode 100755 index 00000000..8efb6f09 --- /dev/null +++ b/C64/SID/resid/wave6581_PS_.h @@ -0,0 +1,533 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +{ +/* 0x000: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x008: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x010: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x018: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x020: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x028: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x030: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x038: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x040: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x048: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x050: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x058: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x060: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x068: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x070: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x078: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x080: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x088: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x090: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x098: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x070, +/* 0x100: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x108: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x110: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x118: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x120: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x128: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x130: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x138: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x140: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x148: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x150: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x158: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x160: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x168: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x170: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x178: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x180: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x188: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x190: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x198: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x1c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x020, 0x1f0, +/* 0x200: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x208: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x210: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x218: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x220: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x228: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x230: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x238: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x240: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x248: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x250: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x258: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x260: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x268: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x270: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x278: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x280: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x288: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x290: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x298: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x2c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x2f0, +/* 0x300: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x308: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x310: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x318: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x320: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x328: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x330: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x338: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x340: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x348: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x350: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x358: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x360: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x368: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x370: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x378: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x370, +/* 0x380: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x388: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x390: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x398: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3b0, +/* 0x3c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3d0, +/* 0x3e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3e0, +/* 0x3f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x300, 0x3f0, +/* 0x3f8: */ 0x000, 0x300, 0x380, 0x3f0, 0x3e0, 0x3f0, 0x3f0, 0x3f0, +/* 0x400: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x408: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x410: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x418: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x420: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x428: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x430: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x438: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x440: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x448: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x450: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x458: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x460: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x468: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x470: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x478: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x480: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x488: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x490: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x498: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x4f0, +/* 0x500: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x508: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x510: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x518: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x520: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x528: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x530: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x538: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x540: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x548: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x550: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x558: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x560: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x568: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x570: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x578: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x570, +/* 0x580: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x588: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x590: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x598: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x5b0, +/* 0x5c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x5d0, +/* 0x5e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x5e0, +/* 0x5f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x5f0, +/* 0x5f8: */ 0x000, 0x400, 0x400, 0x5f0, 0x5c0, 0x5f0, 0x5f0, 0x5f0, +/* 0x600: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x608: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x610: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x618: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x620: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x628: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x630: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x638: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x640: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x648: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x650: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x658: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x660: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x668: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x670: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x678: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x670, +/* 0x680: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x688: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x690: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x698: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x6b0, +/* 0x6c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x400, 0x6d0, +/* 0x6e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x6e8: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x400, 0x400, 0x6e0, +/* 0x6f0: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x600, 0x600, 0x6f0, +/* 0x6f8: */ 0x000, 0x600, 0x600, 0x6f0, 0x600, 0x6f0, 0x6f0, 0x6f0, +/* 0x700: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x708: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x710: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x718: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x720: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x728: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x730: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x738: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x400, 0x600, 0x730, +/* 0x740: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x748: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x750: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x758: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x600, 0x600, 0x750, +/* 0x760: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0x768: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x600, 0x600, 0x760, +/* 0x770: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x600, 0x600, 0x770, +/* 0x778: */ 0x000, 0x700, 0x700, 0x770, 0x700, 0x770, 0x770, 0x770, +/* 0x780: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x788: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0x790: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0x798: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x600, 0x600, 0x790, +/* 0x7a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0x7a8: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x700, 0x700, 0x7a0, +/* 0x7b0: */ 0x000, 0x000, 0x000, 0x700, 0x000, 0x700, 0x700, 0x7b0, +/* 0x7b8: */ 0x400, 0x700, 0x700, 0x7b0, 0x780, 0x7b0, 0x7b0, 0x7b0, +/* 0x7c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x700, +/* 0x7c8: */ 0x000, 0x000, 0x000, 0x700, 0x000, 0x700, 0x700, 0x7c0, +/* 0x7d0: */ 0x000, 0x000, 0x000, 0x700, 0x400, 0x700, 0x700, 0x7d0, +/* 0x7d8: */ 0x400, 0x700, 0x780, 0x7d0, 0x780, 0x7d0, 0x7d0, 0x7d0, +/* 0x7e0: */ 0x000, 0x400, 0x400, 0x780, 0x600, 0x780, 0x780, 0x7e0, +/* 0x7e8: */ 0x600, 0x780, 0x780, 0x7e0, 0x7c0, 0x7e0, 0x7e0, 0x7e0, +/* 0x7f0: */ 0x700, 0x7c0, 0x7c0, 0x7f0, 0x7e0, 0x7f0, 0x7f0, 0x7f0, +/* 0x7f8: */ 0x7e0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, +/* 0x800: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x808: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x810: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x818: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x820: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x828: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x830: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x838: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x840: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x848: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x850: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x858: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x860: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x868: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x870: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x878: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x880: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x888: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x890: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x898: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x070, +/* 0x900: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x908: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x910: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x918: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x920: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x928: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x930: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x938: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x940: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x948: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x950: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x958: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x960: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x968: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x970: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x978: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x980: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x988: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x990: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x998: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x9c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x020, 0x1f0, +/* 0xa00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0xa80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xac0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x2f0, +/* 0xb00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x370, +/* 0xb80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3b0, +/* 0xbc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3d0, +/* 0xbe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3e0, +/* 0xbf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x300, 0x3f0, +/* 0xbf8: */ 0x000, 0x300, 0x380, 0x3f0, 0x3e0, 0x3f0, 0x3f0, 0x3f0, +/* 0xc00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0xc80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x4f0, +/* 0xd00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x570, +/* 0xd80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x5b0, +/* 0xdc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x5d0, +/* 0xde0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x5e0, +/* 0xdf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x5f0, +/* 0xdf8: */ 0x000, 0x400, 0x400, 0x5f0, 0x5c0, 0x5f0, 0x5f0, 0x5f0, +/* 0xe00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x670, +/* 0xe80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x6b0, +/* 0xec0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xec8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x6d0, +/* 0xee0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0xee8: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x400, 0x400, 0x6e0, +/* 0xef0: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x600, 0x600, 0x6f0, +/* 0xef8: */ 0x000, 0x600, 0x600, 0x6f0, 0x600, 0x6f0, 0x6f0, 0x6f0, +/* 0xf00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0xf20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0xf30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0xf38: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x400, 0x600, 0x730, +/* 0xf40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0xf50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0xf58: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x600, 0x600, 0x750, +/* 0xf60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0xf68: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x600, 0x600, 0x760, +/* 0xf70: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x600, 0x600, 0x770, +/* 0xf78: */ 0x000, 0x700, 0x700, 0x770, 0x700, 0x770, 0x770, 0x770, +/* 0xf80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0xf90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0xf98: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x600, 0x600, 0x790, +/* 0xfa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0xfa8: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x700, 0x700, 0x7a0, +/* 0xfb0: */ 0x000, 0x000, 0x000, 0x700, 0x000, 0x700, 0x700, 0x7b0, +/* 0xfb8: */ 0x400, 0x700, 0x700, 0x7b0, 0x780, 0x7b0, 0x7b0, 0x7b0, +/* 0xfc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x700, +/* 0xfc8: */ 0x000, 0x000, 0x000, 0x700, 0x000, 0x700, 0x700, 0x7c0, +/* 0xfd0: */ 0x000, 0x000, 0x000, 0x700, 0x400, 0x700, 0x700, 0x7d0, +/* 0xfd8: */ 0x400, 0x700, 0x780, 0x7d0, 0x780, 0x7d0, 0x7d0, 0x7d0, +/* 0xfe0: */ 0x000, 0x400, 0x400, 0x780, 0x600, 0x780, 0x780, 0x7e0, +/* 0xfe8: */ 0x600, 0x780, 0x780, 0x7e0, 0x7c0, 0x7e0, 0x7e0, 0x7e0, +/* 0xff0: */ 0x700, 0x7c0, 0x7c0, 0x7f0, 0x7c0, 0x7f0, 0x7f0, 0x7f0, +/* 0xff8: */ 0x7e0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, +}, diff --git a/C64/SID/resid/wave6581_P_T.h b/C64/SID/resid/wave6581_P_T.h new file mode 100755 index 00000000..93667e57 --- /dev/null +++ b/C64/SID/resid/wave6581_P_T.h @@ -0,0 +1,533 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +{ +/* 0x000: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x008: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x010: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x018: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x020: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x028: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x030: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x038: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x040: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x048: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x050: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x058: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x060: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x068: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x070: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x078: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x080: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x088: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x090: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x098: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x100: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x108: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x110: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x118: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x120: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x128: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x130: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x138: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x140: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x148: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x150: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x158: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x160: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x168: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x170: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x178: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x180: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x188: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x190: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x198: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x200, 0x380, 0x3f0, +/* 0x200: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x208: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x210: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x218: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x220: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x228: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x230: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x238: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x240: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x248: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x250: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x258: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x260: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x268: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x270: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x278: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x280: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x288: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x290: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x298: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f8: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x400, 0x400, 0x5f0, +/* 0x300: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x308: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x310: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x318: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x320: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x328: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x330: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x338: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x340: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x348: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x350: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x358: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x360: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x368: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x370: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x378: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x600, 0x600, 0x6f0, +/* 0x380: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x388: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x390: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x398: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x3a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0x3b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0x3b8: */ 0x000, 0x000, 0x000, 0x600, 0x000, 0x600, 0x700, 0x770, +/* 0x3c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0x3d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x600, +/* 0x3d8: */ 0x000, 0x000, 0x000, 0x700, 0x400, 0x700, 0x700, 0x7b0, +/* 0x3e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x700, +/* 0x3e8: */ 0x000, 0x400, 0x400, 0x700, 0x600, 0x700, 0x780, 0x7d0, +/* 0x3f0: */ 0x000, 0x400, 0x600, 0x780, 0x600, 0x780, 0x780, 0x7e0, +/* 0x3f8: */ 0x700, 0x7c0, 0x7c0, 0x7f0, 0x7e0, 0x7f0, 0x7f0, 0x7f0, +/* 0x400: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x408: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x410: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x418: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x420: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x428: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x430: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x438: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x440: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x448: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x450: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x458: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x460: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x468: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x470: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x478: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x480: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x488: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x490: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x498: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x4c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x4e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x4f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x4f8: */ 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0x9f0, +/* 0x500: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x508: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x510: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x518: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x520: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x528: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x530: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x538: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x540: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x548: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x550: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x558: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x560: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x568: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x570: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x578: */ 0x000, 0x800, 0x800, 0x800, 0x800, 0xa00, 0xa00, 0xaf0, +/* 0x580: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x588: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x590: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x598: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x5a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, +/* 0x5b0: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0xa00, +/* 0x5b8: */ 0x000, 0x800, 0x800, 0xa00, 0x800, 0xa00, 0xb00, 0xb70, +/* 0x5c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x5c8: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0xa00, +/* 0x5d0: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0xa00, +/* 0x5d8: */ 0x000, 0x800, 0x800, 0xa00, 0x800, 0xb00, 0xb00, 0xbb0, +/* 0x5e0: */ 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0xb00, +/* 0x5e8: */ 0x800, 0x800, 0x800, 0xb00, 0x800, 0xb00, 0xb80, 0xbd0, +/* 0x5f0: */ 0x800, 0x800, 0x800, 0xb80, 0xa00, 0xb80, 0xb80, 0xbe0, +/* 0x5f8: */ 0xa00, 0xb80, 0xbc0, 0xbf0, 0xbe0, 0xbf0, 0xbf0, 0xbf0, +/* 0x600: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x608: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x610: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x618: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x620: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x628: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x630: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x638: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0xc00, +/* 0x640: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x648: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x650: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x658: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, 0x800, 0xc00, +/* 0x660: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x668: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0xc00, +/* 0x670: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0xc00, +/* 0x678: */ 0x000, 0x800, 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xcf0, +/* 0x680: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x688: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x690: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x698: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0xc00, +/* 0x6a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x6a8: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0xc00, +/* 0x6b0: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0xc00, 0xc00, +/* 0x6b8: */ 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xd00, 0xd70, +/* 0x6c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x6c8: */ 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, 0xc00, 0xc00, +/* 0x6d0: */ 0x000, 0x800, 0x800, 0xc00, 0x800, 0xc00, 0xc00, 0xc00, +/* 0x6d8: */ 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xd00, 0xd00, 0xdb0, +/* 0x6e0: */ 0x000, 0x800, 0x800, 0xc00, 0x800, 0xc00, 0xc00, 0xd00, +/* 0x6e8: */ 0x800, 0xc00, 0xc00, 0xd00, 0xc00, 0xd00, 0xd80, 0xdd0, +/* 0x6f0: */ 0xc00, 0xc00, 0xc00, 0xd00, 0xc00, 0xd80, 0xd80, 0xde0, +/* 0x6f8: */ 0xc00, 0xd80, 0xdc0, 0xdf0, 0xdc0, 0xdf0, 0xdf0, 0xdf0, +/* 0x700: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x708: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x710: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x718: */ 0x000, 0x000, 0x000, 0x800, 0x800, 0xc00, 0xc00, 0xe00, +/* 0x720: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x728: */ 0x000, 0x800, 0x800, 0xc00, 0x800, 0xc00, 0xc00, 0xe00, +/* 0x730: */ 0x000, 0x800, 0x800, 0xc00, 0x800, 0xc00, 0xc00, 0xe00, +/* 0x738: */ 0x800, 0xc00, 0xc00, 0xe00, 0xc00, 0xe00, 0xe00, 0xe70, +/* 0x740: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0xc00, +/* 0x748: */ 0x000, 0x800, 0x800, 0xc00, 0x800, 0xc00, 0xc00, 0xe00, +/* 0x750: */ 0x000, 0x800, 0x800, 0xc00, 0x800, 0xc00, 0xc00, 0xe00, +/* 0x758: */ 0xc00, 0xc00, 0xc00, 0xe00, 0xe00, 0xe00, 0xe00, 0xeb0, +/* 0x760: */ 0x800, 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xe00, +/* 0x768: */ 0xc00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xed0, +/* 0x770: */ 0xc00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe80, 0xe80, 0xee0, +/* 0x778: */ 0xe00, 0xe80, 0xec0, 0xef0, 0xec0, 0xef0, 0xef0, 0xef0, +/* 0x780: */ 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0xc00, +/* 0x788: */ 0x800, 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xf00, +/* 0x790: */ 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xe00, 0xe00, 0xf00, +/* 0x798: */ 0xc00, 0xe00, 0xe00, 0xf00, 0xe00, 0xf00, 0xf00, 0xf30, +/* 0x7a0: */ 0x800, 0xc00, 0xc00, 0xe00, 0xc00, 0xe00, 0xe00, 0xf00, +/* 0x7a8: */ 0xc00, 0xe00, 0xe00, 0xf00, 0xe00, 0xf00, 0xf00, 0xf50, +/* 0x7b0: */ 0xe00, 0xe00, 0xe00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf60, +/* 0x7b8: */ 0xf00, 0xf00, 0xf40, 0xf70, 0xf40, 0xf70, 0xf70, 0xf70, +/* 0x7c0: */ 0xc00, 0xc00, 0xc00, 0xe00, 0xe00, 0xe00, 0xe00, 0xf00, +/* 0x7c8: */ 0xe00, 0xe00, 0xe00, 0xf80, 0xf00, 0xf80, 0xf80, 0xf90, +/* 0x7d0: */ 0xe00, 0xf00, 0xf00, 0xf80, 0xf00, 0xf80, 0xf80, 0xfa0, +/* 0x7d8: */ 0xf00, 0xf80, 0xf80, 0xfb0, 0xf80, 0xfb0, 0xfb0, 0xfb0, +/* 0x7e0: */ 0xe00, 0xf00, 0xf00, 0xf80, 0xf00, 0xf80, 0xfc0, 0xfc0, +/* 0x7e8: */ 0xf80, 0xfc0, 0xfc0, 0xfd0, 0xfc0, 0xfd0, 0xfd0, 0xfd0, +/* 0x7f0: */ 0xf80, 0xfc0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, +/* 0x7f8: */ 0xfe0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, +/* 0x800: */ 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xfe0, +/* 0x808: */ 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfc0, 0xf80, +/* 0x810: */ 0xfd0, 0xfd0, 0xfd0, 0xfc0, 0xfd0, 0xfc0, 0xfc0, 0xf80, +/* 0x818: */ 0xfc0, 0xfc0, 0xfc0, 0xf00, 0xf80, 0xf00, 0xf00, 0xe00, +/* 0x820: */ 0xfb0, 0xfb0, 0xfb0, 0xf80, 0xfb0, 0xf80, 0xf80, 0xf00, +/* 0x828: */ 0xfa0, 0xf80, 0xf80, 0xf00, 0xf80, 0xf00, 0xf00, 0xe00, +/* 0x830: */ 0xf90, 0xf80, 0xf80, 0xf00, 0xf80, 0xf00, 0xe00, 0xe00, +/* 0x838: */ 0xf00, 0xe00, 0xe00, 0xe00, 0xe00, 0xc00, 0xc00, 0xc00, +/* 0x840: */ 0xf70, 0xf70, 0xf70, 0xf40, 0xf70, 0xf40, 0xf00, 0xf00, +/* 0x848: */ 0xf60, 0xf00, 0xf00, 0xf00, 0xf00, 0xe00, 0xe00, 0xe00, +/* 0x850: */ 0xf50, 0xf00, 0xf00, 0xe00, 0xf00, 0xe00, 0xe00, 0xc00, +/* 0x858: */ 0xf00, 0xe00, 0xe00, 0xc00, 0xe00, 0xc00, 0xc00, 0x800, +/* 0x860: */ 0xf30, 0xf00, 0xf00, 0xe00, 0xf00, 0xe00, 0xe00, 0xc00, +/* 0x868: */ 0xf00, 0xe00, 0xe00, 0xc00, 0xc00, 0xc00, 0xc00, 0x800, +/* 0x870: */ 0xf00, 0xe00, 0xc00, 0xc00, 0xc00, 0xc00, 0x800, 0x800, +/* 0x878: */ 0xc00, 0x800, 0x800, 0x800, 0x800, 0x000, 0x000, 0x000, +/* 0x880: */ 0xef0, 0xef0, 0xef0, 0xec0, 0xef0, 0xec0, 0xe80, 0xe00, +/* 0x888: */ 0xee0, 0xe80, 0xe80, 0xe00, 0xe00, 0xe00, 0xe00, 0xc00, +/* 0x890: */ 0xed0, 0xe80, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xc00, +/* 0x898: */ 0xe00, 0xe00, 0xc00, 0xc00, 0xc00, 0xc00, 0x800, 0x800, +/* 0x8a0: */ 0xeb0, 0xe00, 0xe00, 0xe00, 0xe00, 0xc00, 0xc00, 0xc00, +/* 0x8a8: */ 0xe00, 0xc00, 0xc00, 0x800, 0xc00, 0x800, 0x800, 0x000, +/* 0x8b0: */ 0xe00, 0xc00, 0xc00, 0x800, 0xc00, 0x800, 0x800, 0x000, +/* 0x8b8: */ 0xc00, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0x8c0: */ 0xe70, 0xe00, 0xe00, 0xc00, 0xe00, 0xc00, 0xc00, 0x800, +/* 0x8c8: */ 0xe00, 0xc00, 0xc00, 0x800, 0xc00, 0x800, 0x800, 0x000, +/* 0x8d0: */ 0xe00, 0xc00, 0xc00, 0x800, 0xc00, 0x800, 0x800, 0x000, +/* 0x8d8: */ 0x800, 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e0: */ 0xe00, 0xc00, 0xc00, 0x800, 0x800, 0x000, 0x000, 0x000, +/* 0x8e8: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f0: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x900: */ 0xdf0, 0xdf0, 0xdf0, 0xdc0, 0xdf0, 0xdc0, 0xd80, 0xc00, +/* 0x908: */ 0xde0, 0xd80, 0xd80, 0xc00, 0xd80, 0xc00, 0xc00, 0xc00, +/* 0x910: */ 0xdd0, 0xd80, 0xd00, 0xc00, 0xd00, 0xc00, 0xc00, 0x800, +/* 0x918: */ 0xd00, 0xc00, 0xc00, 0x800, 0xc00, 0x800, 0x800, 0x000, +/* 0x920: */ 0xdb0, 0xd00, 0xd00, 0xc00, 0xc00, 0xc00, 0xc00, 0x800, +/* 0x928: */ 0xc00, 0xc00, 0xc00, 0x800, 0xc00, 0x800, 0x800, 0x000, +/* 0x930: */ 0xc00, 0xc00, 0x800, 0x800, 0x800, 0x000, 0x000, 0x000, +/* 0x938: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x940: */ 0xd70, 0xd00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0x800, +/* 0x948: */ 0xc00, 0xc00, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0x950: */ 0xc00, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0x958: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x960: */ 0xc00, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0x968: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x970: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x978: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x980: */ 0xcf0, 0xc00, 0xc00, 0xc00, 0xc00, 0x800, 0x800, 0x000, +/* 0x988: */ 0xc00, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0x990: */ 0xc00, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0x998: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a0: */ 0xc00, 0x800, 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c0: */ 0xc00, 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa00: */ 0xbf0, 0xbf0, 0xbf0, 0xbe0, 0xbf0, 0xbc0, 0xbc0, 0xa00, +/* 0xa08: */ 0xbe0, 0xbc0, 0xb80, 0xa00, 0xb80, 0xa00, 0x800, 0x800, +/* 0xa10: */ 0xbd0, 0xb80, 0xb00, 0x800, 0xb00, 0x800, 0x800, 0x800, +/* 0xa18: */ 0xb00, 0x800, 0x800, 0x800, 0x800, 0x000, 0x000, 0x000, +/* 0xa20: */ 0xbb0, 0xb00, 0xb00, 0x800, 0xa00, 0x800, 0x800, 0x000, +/* 0xa28: */ 0xa00, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0xa30: */ 0xa00, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0xa38: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa40: */ 0xb70, 0xb00, 0xa00, 0x800, 0xa00, 0x800, 0x800, 0x000, +/* 0xa48: */ 0xa00, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0xa50: */ 0x800, 0x800, 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa60: */ 0x800, 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa80: */ 0xaf0, 0xa00, 0xa00, 0x800, 0x800, 0x800, 0x800, 0x000, +/* 0xa88: */ 0x800, 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa90: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa0: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac0: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb00: */ 0x9f0, 0x900, 0x800, 0x800, 0x800, 0x000, 0x000, 0x000, +/* 0xb08: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb10: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb20: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb40: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb80: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc00: */ 0x7f0, 0x7f0, 0x7f0, 0x7e0, 0x7f0, 0x7c0, 0x7c0, 0x700, +/* 0xc08: */ 0x7e0, 0x7c0, 0x780, 0x600, 0x780, 0x600, 0x600, 0x000, +/* 0xc10: */ 0x7d0, 0x780, 0x780, 0x600, 0x700, 0x400, 0x400, 0x000, +/* 0xc18: */ 0x700, 0x400, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc20: */ 0x7b0, 0x780, 0x700, 0x400, 0x700, 0x400, 0x000, 0x000, +/* 0xc28: */ 0x600, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc30: */ 0x600, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc40: */ 0x770, 0x700, 0x700, 0x000, 0x600, 0x000, 0x000, 0x000, +/* 0xc48: */ 0x600, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc50: */ 0x600, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc60: */ 0x400, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc80: */ 0x6f0, 0x600, 0x600, 0x000, 0x600, 0x000, 0x000, 0x000, +/* 0xc88: */ 0x400, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc90: */ 0x400, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd00: */ 0x5f0, 0x580, 0x400, 0x000, 0x400, 0x000, 0x000, 0x000, +/* 0xd08: */ 0x400, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe00: */ 0x3f0, 0x3c0, 0x300, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xec0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xec8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xee0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xee8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xef0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xef8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xff0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xff8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +}, diff --git a/C64/SID/resid/wave6581__ST.h b/C64/SID/resid/wave6581__ST.h new file mode 100755 index 00000000..dc578b71 --- /dev/null +++ b/C64/SID/resid/wave6581__ST.h @@ -0,0 +1,533 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +{ +/* 0x000: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x008: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x010: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x018: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x020: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x028: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x030: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x038: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x040: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x048: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x050: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x058: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x060: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x068: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x070: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x078: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x080: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x088: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x090: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x098: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x0c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x100: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x108: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x110: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x118: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x120: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x128: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x130: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x138: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x140: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x148: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x150: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x158: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x160: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x168: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x170: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x178: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x180: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x188: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x190: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x198: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x1c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f8: */ 0x0e0, 0x0e0, 0x0e0, 0x0e0, 0x0f0, 0x0f0, 0x0f0, 0x0f0, +/* 0x200: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x208: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x210: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x218: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x220: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x228: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x230: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x238: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x240: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x248: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x250: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x258: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x260: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x268: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x270: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x278: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x280: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x288: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x290: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x298: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x2c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x300: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x308: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x310: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x318: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x320: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x328: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x330: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x338: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x340: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x348: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x350: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x358: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x360: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x368: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x370: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x378: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x380: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x388: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x390: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x398: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x3c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3f0: */ 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, +/* 0x3f8: */ 0x1e0, 0x1e0, 0x1e0, 0x1e0, 0x1f0, 0x1f0, 0x3f0, 0x3f0, +/* 0x400: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x408: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x410: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x418: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x420: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x428: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x430: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x438: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x440: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x448: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x450: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x458: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x460: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x468: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x470: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x478: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x480: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x488: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x490: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x498: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x4c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x500: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x508: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x510: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x518: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x520: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x528: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x530: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x538: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x540: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x548: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x550: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x558: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x560: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x568: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x570: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x578: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x580: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x588: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x590: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x598: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x5c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5f8: */ 0x0e0, 0x0e0, 0x0e0, 0x0e0, 0x0f0, 0x0f0, 0x0f0, 0x1f0, +/* 0x600: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x608: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x610: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x618: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x620: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x628: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x630: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x638: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x640: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x648: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x650: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x658: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x660: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x668: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x670: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x678: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x680: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x688: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x690: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x698: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x6c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x700: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x708: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x710: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x718: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x720: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x728: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x730: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x738: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x740: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x748: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x750: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x758: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x760: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x768: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x770: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x778: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x780: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x788: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x790: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x798: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x7c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7e0: */ 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, +/* 0x7e8: */ 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, +/* 0x7f0: */ 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, +/* 0x7f8: */ 0x3e0, 0x3e0, 0x3f0, 0x3f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, +/* 0x800: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x808: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x810: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x818: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x820: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x828: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x830: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x838: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x840: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x848: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x850: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x858: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x860: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x868: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x870: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x878: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x880: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x888: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x890: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x898: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x8c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x900: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x908: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x910: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x918: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x920: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x928: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x930: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x938: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x940: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x948: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x950: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x958: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x960: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x968: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x970: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x978: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x980: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x988: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x990: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x998: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x9c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f8: */ 0x0e0, 0x0e0, 0x0e0, 0x0e0, 0x0f0, 0x0f0, 0x0f0, 0x0f0, +/* 0xa00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xa80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xac0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0xb00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xb40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xb80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xbc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf0: */ 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, +/* 0xbf8: */ 0x1e0, 0x1e0, 0x1e0, 0x1e0, 0x1f0, 0x1f0, 0x3f0, 0x3f0, +/* 0xc00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xc80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xcc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0xd00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xd40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xd80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xdc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf8: */ 0x0e0, 0x0e0, 0x0e0, 0x0e0, 0x0f0, 0x0f0, 0x0f0, 0x1f0, +/* 0xe00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xe80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xec0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xec8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xee0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xee8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xef0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xef8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0xf00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xf40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xf80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xfc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfe0: */ 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, +/* 0xfe8: */ 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, +/* 0xff0: */ 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, +/* 0xff8: */ 0x3e0, 0x3e0, 0x3f0, 0x3f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, +}, diff --git a/C64/SID/resid/wave8580_PST.h b/C64/SID/resid/wave8580_PST.h new file mode 100755 index 00000000..93015d5a --- /dev/null +++ b/C64/SID/resid/wave8580_PST.h @@ -0,0 +1,533 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +{ +/* 0x000: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x008: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x010: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x018: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x020: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x028: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x030: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x038: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x040: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x048: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x050: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x058: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x060: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x068: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x070: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x078: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x080: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x088: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x090: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x098: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x100: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x108: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x110: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x118: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x120: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x128: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x130: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x138: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x140: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x148: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x150: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x158: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x160: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x168: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x170: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x178: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x180: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x188: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x190: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x198: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x200: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x208: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x210: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x218: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x220: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x228: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x230: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x238: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x240: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x248: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x250: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x258: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x260: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x268: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x270: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x278: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x280: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x288: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x290: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x298: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x300: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x308: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x310: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x318: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x320: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x328: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x330: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x338: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x340: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x348: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x350: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x358: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x360: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x368: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x370: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x378: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x380: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x388: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x390: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x398: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x1f0, +/* 0x400: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x408: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x410: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x418: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x420: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x428: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x430: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x438: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x440: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x448: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x450: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x458: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x460: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x468: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x470: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x478: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x480: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x488: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x490: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x498: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x500: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x508: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x510: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x518: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x520: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x528: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x530: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x538: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x540: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x548: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x550: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x558: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x560: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x568: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x570: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x578: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x580: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x588: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x590: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x598: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x600: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x608: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x610: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x618: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x620: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x628: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x630: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x638: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x640: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x648: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x650: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x658: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x660: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x668: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x670: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x678: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x680: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x688: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x690: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x698: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x700: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x708: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x710: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x718: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x720: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x728: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x730: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x738: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x740: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x748: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x750: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x758: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x760: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x768: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x770: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x778: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x780: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x788: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x790: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x798: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x200, 0x700, +/* 0x7f0: */ 0x600, 0x200, 0x700, 0x700, 0x700, 0x700, 0x700, 0x780, +/* 0x7f8: */ 0x780, 0x780, 0x7c0, 0x7c0, 0x7e0, 0x7e0, 0x7f0, 0x7f0, +/* 0x800: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x808: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x810: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x818: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x820: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x828: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x830: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x838: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x840: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x848: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x850: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x858: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x860: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x868: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x870: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x878: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x880: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x888: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x890: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x898: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x900: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x908: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x910: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x918: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x920: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x928: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x930: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x938: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x940: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x948: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x950: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x958: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x960: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x968: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x970: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x978: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x980: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x988: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x990: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x998: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x080, 0x1e0, 0x3f0, +/* 0xc00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0xdf8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x8c0, 0x9f0, +/* 0xe00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, 0x800, +/* 0xe40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, 0x800, +/* 0xe60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, 0x000, +/* 0xe68: */ 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xe70: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xe78: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xe80: */ 0x000, 0x000, 0x800, 0x000, 0x000, 0x800, 0x800, 0x800, +/* 0xe88: */ 0x800, 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xe90: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xe98: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xea0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xea8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xeb0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xeb8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xec0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xec8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xed0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xed8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xee0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xee8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, 0xc00, +/* 0xef0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xef8: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xcf0, +/* 0xf00: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf08: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf10: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf18: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf20: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf28: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf30: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf38: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf40: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf48: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf50: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf58: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf60: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf68: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xe00, +/* 0xf70: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf78: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe30, +/* 0xf80: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf88: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf90: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf98: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xfa0: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xfa8: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xfb0: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xf00, 0xf00, +/* 0xfb8: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xfc0: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xfc8: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xfd0: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xfd8: */ 0xf00, 0xf00, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0xfe0: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0xfe8: */ 0xf80, 0xf80, 0xf80, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, +/* 0xff0: */ 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfe0, 0xfe0, 0xfe0, +/* 0xff8: */ 0xfe0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, +}, diff --git a/C64/SID/resid/wave8580_PS_.h b/C64/SID/resid/wave8580_PS_.h new file mode 100755 index 00000000..19af9501 --- /dev/null +++ b/C64/SID/resid/wave8580_PS_.h @@ -0,0 +1,533 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +{ +/* 0x000: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x008: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x010: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x018: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x020: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x028: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x030: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x038: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x040: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x048: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x050: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x058: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x060: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x068: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x070: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x078: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x080: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x088: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x090: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x098: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x0c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x0f0, +/* 0x100: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x108: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x110: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x118: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x120: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x128: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x130: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x138: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x140: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x148: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x150: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x158: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x160: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x168: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x170: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x178: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x070, +/* 0x180: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x188: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x190: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x198: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x1c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x1e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x1f0, +/* 0x200: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x208: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x210: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x218: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x220: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x228: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x230: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x238: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x240: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x248: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x250: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x258: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x260: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x268: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x270: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x278: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x280: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x288: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x290: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x298: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x2c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x2e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, 0x0f0, +/* 0x300: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x308: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x310: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x318: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x320: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x328: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x330: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x338: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x340: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x348: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x350: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x358: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x360: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x368: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x370: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x378: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x170, +/* 0x380: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x388: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x390: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x398: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3b0, +/* 0x3c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3d0, +/* 0x3e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3e0, +/* 0x3f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x3f0, +/* 0x3f8: */ 0x000, 0x0c0, 0x1c0, 0x3f0, 0x1e0, 0x3f0, 0x3f0, 0x3f0, +/* 0x400: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x408: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x410: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x418: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x420: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x428: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x430: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x438: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x440: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x448: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x450: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x458: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x460: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x468: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x470: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x478: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x480: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x488: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x490: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x498: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x4c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x0f0, +/* 0x500: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x508: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x510: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x518: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x520: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x528: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x530: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x538: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x540: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x548: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x550: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x558: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x560: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x568: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x570: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x578: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x070, +/* 0x580: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x588: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x590: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x598: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x0b0, +/* 0x5c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x0a0, +/* 0x5e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x5e0, +/* 0x5f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x5f0, +/* 0x5f8: */ 0x000, 0x000, 0x000, 0x5f0, 0x0c0, 0x5f0, 0x5f0, 0x5f0, +/* 0x600: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x608: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x610: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x618: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x620: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x628: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x630: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x638: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x640: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x648: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x650: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x658: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x660: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x668: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x670: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x678: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x470, +/* 0x680: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x688: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x690: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x698: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x430, +/* 0x6c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x650, +/* 0x6e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x6e0, +/* 0x6f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x6f0, +/* 0x6f8: */ 0x000, 0x400, 0x400, 0x6f0, 0x400, 0x6f0, 0x6f0, 0x6f0, +/* 0x700: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x708: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x710: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x718: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x720: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x728: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x730: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x738: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x630, +/* 0x740: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x748: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x750: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x758: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x610, +/* 0x760: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x768: */ 0x000, 0x000, 0x000, 0x400, 0x000, 0x400, 0x400, 0x700, +/* 0x770: */ 0x000, 0x000, 0x400, 0x400, 0x400, 0x400, 0x400, 0x700, +/* 0x778: */ 0x400, 0x600, 0x600, 0x770, 0x600, 0x770, 0x770, 0x770, +/* 0x780: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x788: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x790: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x600, +/* 0x798: */ 0x000, 0x400, 0x400, 0x600, 0x400, 0x600, 0x600, 0x790, +/* 0x7a0: */ 0x000, 0x400, 0x400, 0x400, 0x400, 0x400, 0x400, 0x600, +/* 0x7a8: */ 0x400, 0x400, 0x400, 0x600, 0x600, 0x600, 0x600, 0x780, +/* 0x7b0: */ 0x400, 0x600, 0x600, 0x600, 0x600, 0x600, 0x600, 0x780, +/* 0x7b8: */ 0x600, 0x700, 0x700, 0x780, 0x700, 0x790, 0x7b0, 0x7b0, +/* 0x7c0: */ 0x600, 0x600, 0x600, 0x600, 0x600, 0x600, 0x600, 0x700, +/* 0x7c8: */ 0x600, 0x600, 0x600, 0x700, 0x600, 0x700, 0x700, 0x7c0, +/* 0x7d0: */ 0x600, 0x700, 0x700, 0x700, 0x700, 0x700, 0x700, 0x7c0, +/* 0x7d8: */ 0x700, 0x780, 0x780, 0x7c0, 0x780, 0x7c0, 0x7c0, 0x7d0, +/* 0x7e0: */ 0x700, 0x780, 0x780, 0x780, 0x780, 0x780, 0x780, 0x7c0, +/* 0x7e8: */ 0x780, 0x7c0, 0x7c0, 0x7e0, 0x7c0, 0x7e0, 0x7e0, 0x7e0, +/* 0x7f0: */ 0x7c0, 0x7c0, 0x7c0, 0x7e0, 0x7e0, 0x7f0, 0x7f0, 0x7f0, +/* 0x7f8: */ 0x7e0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0xff0, +/* 0x800: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x808: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x810: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x818: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x820: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x828: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x830: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x838: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x840: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x848: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x850: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x858: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x860: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x868: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x870: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x878: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, +/* 0x880: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x888: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x890: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x898: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x8c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x8f0, +/* 0x900: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x908: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x910: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x918: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x920: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x928: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x930: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x938: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x940: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x948: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x950: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x958: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x960: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x968: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x970: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x978: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x870, +/* 0x980: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x988: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x990: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x998: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x830, +/* 0x9c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, 0x8d0, +/* 0x9e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x9e8: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0x8e0, +/* 0x9f0: */ 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0x8f0, +/* 0x9f8: */ 0x800, 0x800, 0x800, 0x9f0, 0x800, 0x9f0, 0x9f0, 0x9f0, +/* 0xa00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xa40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0xa70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0xa78: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0x870, +/* 0xa80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0xaa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0xab0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0xab8: */ 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0x830, +/* 0xac0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0xad0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, +/* 0xad8: */ 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x810, +/* 0xae0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xae8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x840, +/* 0xaf0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x870, +/* 0xaf8: */ 0x800, 0x800, 0x800, 0x870, 0x800, 0x8f0, 0xaf0, 0xaf0, +/* 0xb00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0xb10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0xb18: */ 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb20: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x000, 0x800, 0x800, +/* 0xb28: */ 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb30: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb38: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x830, +/* 0xb40: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb48: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb50: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb58: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x810, +/* 0xb60: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb68: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xa00, +/* 0xb70: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xa00, +/* 0xb78: */ 0x800, 0x800, 0x800, 0xa00, 0x800, 0xa30, 0xb70, 0xb70, +/* 0xb80: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb88: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb90: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb98: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xb10, +/* 0xba0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xba8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xb00, +/* 0xbb0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xb00, +/* 0xbb8: */ 0x800, 0xa00, 0xa00, 0xb00, 0xa00, 0xb80, 0xb90, 0xbb0, +/* 0xbc0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xa00, +/* 0xbc8: */ 0x800, 0x800, 0x800, 0xa00, 0x800, 0xa00, 0xa00, 0xb80, +/* 0xbd0: */ 0x800, 0xa00, 0xa00, 0xa00, 0xa00, 0xa00, 0xa00, 0xb80, +/* 0xbd8: */ 0xa00, 0xb00, 0xb00, 0xb80, 0xb00, 0xbc0, 0xbc0, 0xbd0, +/* 0xbe0: */ 0xa00, 0xb00, 0xb00, 0xb00, 0xb00, 0xb80, 0xb80, 0xbc0, +/* 0xbe8: */ 0xb00, 0xb80, 0xb80, 0xbc0, 0xb80, 0xbc0, 0xbe0, 0xbe0, +/* 0xbf0: */ 0xb80, 0xbc0, 0xbc0, 0xbe0, 0xbc0, 0xbe0, 0xbe0, 0xbf0, +/* 0xbf8: */ 0xbe0, 0xbf0, 0xbf0, 0xbf0, 0xbf0, 0xbf0, 0xbf0, 0xbf0, +/* 0xc00: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x000, 0x000, 0x800, +/* 0xc08: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x000, 0x000, 0x800, +/* 0xc10: */ 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc18: */ 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc20: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc28: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc30: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc38: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x810, +/* 0xc40: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc48: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc50: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc58: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc60: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc68: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc70: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc78: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc70, +/* 0xc80: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc88: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc90: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xc98: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xca0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xca8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xcb0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xcb8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, 0xc30, +/* 0xcc0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xcc8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, +/* 0xcd0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, +/* 0xcd8: */ 0x800, 0x800, 0x800, 0xc00, 0x800, 0xc00, 0xc00, 0xc10, +/* 0xce0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, +/* 0xce8: */ 0x800, 0x800, 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xcf0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc70, +/* 0xcf8: */ 0xc00, 0xc00, 0xc00, 0xc70, 0xc00, 0xcf0, 0xcf0, 0xcf0, +/* 0xd00: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xd08: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xd10: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xd18: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, +/* 0xd20: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xd28: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, +/* 0xd30: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, 0xc00, +/* 0xd38: */ 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc30, +/* 0xd40: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, +/* 0xd48: */ 0x800, 0x800, 0x800, 0xc00, 0x800, 0xc00, 0xc00, 0xc00, +/* 0xd50: */ 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xd58: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc10, +/* 0xd60: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xd68: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xd70: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xd78: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc10, 0xc70, 0xd70, +/* 0xd80: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xd88: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xd90: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xd98: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xda0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xda8: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xd00, +/* 0xdb0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xd00, +/* 0xdb8: */ 0xc00, 0xc00, 0xc00, 0xd00, 0xc00, 0xd00, 0xd80, 0xdb0, +/* 0xdc0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xdc8: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xd80, +/* 0xdd0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xd80, +/* 0xdd8: */ 0xc00, 0xc00, 0xc00, 0xd80, 0xd00, 0xd80, 0xd80, 0xdd0, +/* 0xde0: */ 0xc00, 0xc00, 0xc00, 0xd00, 0xc00, 0xd00, 0xd00, 0xdc0, +/* 0xde8: */ 0xd00, 0xd80, 0xd80, 0xdc0, 0xd80, 0xdc0, 0xdc0, 0xde0, +/* 0xdf0: */ 0xd80, 0xdc0, 0xdc0, 0xde0, 0xdc0, 0xde0, 0xde0, 0xdf0, +/* 0xdf8: */ 0xde0, 0xdf0, 0xdf0, 0xdf0, 0xdf0, 0xdf0, 0xdf0, 0xdf0, +/* 0xe00: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xe08: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xe10: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xe18: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xe20: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xe28: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xe30: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xe38: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xe30, +/* 0xe40: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xe48: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xe50: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xe00, +/* 0xe58: */ 0xc00, 0xc00, 0xc00, 0xe00, 0xc00, 0xe00, 0xe00, 0xe10, +/* 0xe60: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xe00, +/* 0xe68: */ 0xc00, 0xc00, 0xc00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xe70: */ 0xc00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xe78: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe10, 0xe30, 0xe70, +/* 0xe80: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xe00, +/* 0xe88: */ 0xc00, 0xc00, 0xc00, 0xe00, 0xc00, 0xe00, 0xe00, 0xe00, +/* 0xe90: */ 0xc00, 0xc00, 0xc00, 0xe00, 0xc00, 0xe00, 0xe00, 0xe00, +/* 0xe98: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xea0: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xea8: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xeb0: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xeb8: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xeb0, +/* 0xec0: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xec8: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xed0: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xed8: */ 0xe00, 0xe00, 0xe00, 0xe80, 0xe00, 0xe80, 0xe80, 0xed0, +/* 0xee0: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xec0, +/* 0xee8: */ 0xe00, 0xe00, 0xe00, 0xec0, 0xe80, 0xec0, 0xec0, 0xee0, +/* 0xef0: */ 0xe80, 0xe80, 0xe80, 0xec0, 0xec0, 0xee0, 0xee0, 0xef0, +/* 0xef8: */ 0xec0, 0xef0, 0xef0, 0xef0, 0xef0, 0xef0, 0xef0, 0xef0, +/* 0xf00: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf08: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf10: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf18: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xf00, +/* 0xf20: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xf00, +/* 0xf28: */ 0xe00, 0xe00, 0xe00, 0xf00, 0xe00, 0xf00, 0xf00, 0xf00, +/* 0xf30: */ 0xe00, 0xe00, 0xe00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xf38: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf30, +/* 0xf40: */ 0xe00, 0xe00, 0xe00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xf48: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xf50: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xf58: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf50, +/* 0xf60: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xf68: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf40, 0xf40, 0xf60, +/* 0xf70: */ 0xf00, 0xf00, 0xf00, 0xf40, 0xf00, 0xf40, 0xf60, 0xf70, +/* 0xf78: */ 0xf40, 0xf60, 0xf60, 0xf70, 0xf70, 0xf70, 0xf70, 0xf70, +/* 0xf80: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf80, +/* 0xf88: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf80, 0xf80, 0xf80, +/* 0xf90: */ 0xf00, 0xf00, 0xf00, 0xf80, 0xf00, 0xf80, 0xf80, 0xf80, +/* 0xf98: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf90, +/* 0xfa0: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0xfa8: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xfa0, +/* 0xfb0: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xfb0, +/* 0xfb8: */ 0xf80, 0xfa0, 0xfa0, 0xfb0, 0xfb0, 0xfb0, 0xfb0, 0xfb0, +/* 0xfc0: */ 0xf80, 0xf80, 0xf80, 0xfc0, 0xf80, 0xfc0, 0xfc0, 0xfc0, +/* 0xfc8: */ 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, +/* 0xfd0: */ 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfd0, +/* 0xfd8: */ 0xfc0, 0xfc0, 0xfc0, 0xfd0, 0xfd0, 0xfd0, 0xfd0, 0xfd0, +/* 0xfe0: */ 0xfc0, 0xfc0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, +/* 0xfe8: */ 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, +/* 0xff0: */ 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, +/* 0xff8: */ 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, +}, diff --git a/C64/SID/resid/wave8580_P_T.h b/C64/SID/resid/wave8580_P_T.h new file mode 100755 index 00000000..bcca37ff --- /dev/null +++ b/C64/SID/resid/wave8580_P_T.h @@ -0,0 +1,533 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +{ +/* 0x000: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x008: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x010: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x018: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x020: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x028: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x030: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x038: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x040: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x048: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x050: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x058: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x060: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x068: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x070: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x078: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x080: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x088: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x090: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x098: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x070, +/* 0x100: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x108: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x110: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x118: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x120: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x128: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x130: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x138: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x140: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x148: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x150: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x158: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x160: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x168: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x170: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x178: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x180: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x188: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x190: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x198: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f8: */ 0x000, 0x000, 0x000, 0x1c0, 0x000, 0x3c0, 0x3f0, 0x3f0, +/* 0x200: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x208: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x210: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x218: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x220: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x228: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x230: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x238: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x240: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x248: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x250: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x258: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x260: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x268: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x270: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x278: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x280: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x288: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x290: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x298: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x0c0, 0x5e0, 0x5f0, +/* 0x300: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x308: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x310: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x318: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x320: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x328: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x330: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x338: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x340: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x348: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x350: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x358: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x360: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x368: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x370: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x378: */ 0x000, 0x000, 0x000, 0x400, 0x400, 0x600, 0x600, 0x6f0, +/* 0x380: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x388: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x390: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x398: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, +/* 0x3a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x400, +/* 0x3b0: */ 0x000, 0x000, 0x000, 0x400, 0x400, 0x400, 0x400, 0x600, +/* 0x3b8: */ 0x400, 0x400, 0x600, 0x600, 0x600, 0x600, 0x700, 0x770, +/* 0x3c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x400, 0x400, 0x400, +/* 0x3c8: */ 0x400, 0x400, 0x400, 0x400, 0x400, 0x600, 0x600, 0x600, +/* 0x3d0: */ 0x400, 0x400, 0x400, 0x600, 0x600, 0x600, 0x600, 0x700, +/* 0x3d8: */ 0x600, 0x600, 0x600, 0x700, 0x700, 0x700, 0x780, 0x7b0, +/* 0x3e0: */ 0x600, 0x600, 0x600, 0x700, 0x600, 0x700, 0x700, 0x700, +/* 0x3e8: */ 0x700, 0x700, 0x700, 0x780, 0x780, 0x780, 0x780, 0x7c0, +/* 0x3f0: */ 0x780, 0x780, 0x780, 0x7c0, 0x780, 0x7c0, 0x7c0, 0x7e0, +/* 0x3f8: */ 0x7c0, 0x7e0, 0x7e0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, +/* 0x400: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x408: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x410: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x418: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x420: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x428: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x430: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x438: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x440: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x448: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x450: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x458: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x460: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x468: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x470: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x478: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x480: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x488: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x490: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x498: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, +/* 0x4c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x4d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x4d8: */ 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x4e0: */ 0x000, 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x4e8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x4f0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x4f8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x8e0, 0x9f0, +/* 0x500: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x508: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x510: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x518: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x800, +/* 0x520: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x528: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0x800, +/* 0x530: */ 0x000, 0x000, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x538: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x540: */ 0x000, 0x000, 0x000, 0x800, 0x000, 0x800, 0x800, 0x800, +/* 0x548: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x550: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x558: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x560: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x568: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x570: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x578: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xaf0, +/* 0x580: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x588: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x590: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x598: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x5a0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x5a8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x5b0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x5b8: */ 0x800, 0x800, 0x800, 0xa00, 0xa00, 0xa00, 0xa00, 0xb70, +/* 0x5c0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x5c8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xa00, +/* 0x5d0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xa00, 0xa00, +/* 0x5d8: */ 0xa00, 0xa00, 0xa00, 0xb00, 0xa00, 0xb00, 0xb00, 0xbb0, +/* 0x5e0: */ 0xa00, 0xa00, 0xa00, 0xa00, 0xa00, 0xa00, 0xb00, 0xb00, +/* 0x5e8: */ 0xa00, 0xb00, 0xb00, 0xb80, 0xb00, 0xb80, 0xb80, 0xbc0, +/* 0x5f0: */ 0xb00, 0xb80, 0xb80, 0xb80, 0xb80, 0xbc0, 0xbc0, 0xbe0, +/* 0x5f8: */ 0xbc0, 0xbc0, 0xbe0, 0xbf0, 0xbe0, 0xbf0, 0xbf0, 0xbf0, +/* 0x600: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x608: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x610: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x618: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x620: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x628: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x630: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x638: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, 0xc00, +/* 0x640: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x648: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x650: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, +/* 0x658: */ 0x800, 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x660: */ 0x800, 0x800, 0x800, 0xc00, 0x800, 0xc00, 0xc00, 0xc00, +/* 0x668: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x670: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x678: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xcf0, +/* 0x680: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0xc00, 0xc00, +/* 0x688: */ 0xc00, 0x800, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x690: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x698: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x6a0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x6a8: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x6b0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x6b8: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xd70, +/* 0x6c0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x6c8: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x6d0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x6d8: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xd00, 0xd00, 0xd90, +/* 0x6e0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xd00, +/* 0x6e8: */ 0xc00, 0xd00, 0xd00, 0xd00, 0xd00, 0xd80, 0xd80, 0xdc0, +/* 0x6f0: */ 0xd00, 0xd00, 0xd80, 0xd80, 0xd80, 0xdc0, 0xdc0, 0xde0, +/* 0x6f8: */ 0xdc0, 0xdc0, 0xde0, 0xdf0, 0xde0, 0xdf0, 0xdf0, 0xdf0, +/* 0x700: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x708: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x710: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x718: */ 0xc00, 0xc00, 0xc00, 0xe00, 0xc00, 0xe00, 0xe00, 0xe00, +/* 0x720: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xe00, +/* 0x728: */ 0xc00, 0xc00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x730: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x738: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe70, +/* 0x740: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x748: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x750: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x758: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe80, +/* 0x760: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x768: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe80, 0xec0, +/* 0x770: */ 0xe00, 0xe00, 0xe00, 0xe80, 0xe80, 0xe80, 0xec0, 0xee0, +/* 0x778: */ 0xec0, 0xec0, 0xec0, 0xee0, 0xee0, 0xef0, 0xef0, 0xef0, +/* 0x780: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x788: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xf00, 0xf00, 0xf00, +/* 0x790: */ 0xe00, 0xe00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0x798: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0x7a0: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0x7a8: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf40, +/* 0x7b0: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf40, +/* 0x7b8: */ 0xf00, 0xf40, 0xf40, 0xf60, 0xf60, 0xf70, 0xf70, 0xf70, +/* 0x7c0: */ 0xf00, 0xf00, 0xf00, 0xf80, 0xf00, 0xf80, 0xf80, 0xf80, +/* 0x7c8: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0x7d0: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0x7d8: */ 0xf80, 0xf80, 0xf80, 0xfa0, 0xfa0, 0xfb0, 0xfb0, 0xfb0, +/* 0x7e0: */ 0xf80, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, +/* 0x7e8: */ 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfd0, 0xfd0, 0xfd0, +/* 0x7f0: */ 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, +/* 0x7f8: */ 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, +/* 0x800: */ 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, +/* 0x808: */ 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfc0, +/* 0x810: */ 0xfd0, 0xfd0, 0xfd0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, +/* 0x818: */ 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xf80, +/* 0x820: */ 0xfb0, 0xfb0, 0xfb0, 0xfa0, 0xfa0, 0xf80, 0xf80, 0xf80, +/* 0x828: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0x830: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0x838: */ 0xf80, 0xf80, 0xf80, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0x840: */ 0xf70, 0xf70, 0xf70, 0xf60, 0xf60, 0xf40, 0xf40, 0xf00, +/* 0x848: */ 0xf40, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0x850: */ 0xf40, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0x858: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0x860: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0x868: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xe00, 0xe00, +/* 0x870: */ 0xf00, 0xf00, 0xf00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x878: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x880: */ 0xef0, 0xef0, 0xef0, 0xee0, 0xee0, 0xec0, 0xec0, 0xe80, +/* 0x888: */ 0xee0, 0xec0, 0xe80, 0xe80, 0xe80, 0xe00, 0xe00, 0xe00, +/* 0x890: */ 0xec0, 0xe80, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x898: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x8a0: */ 0xe80, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x8a8: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x8b0: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x8b8: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x8c0: */ 0xe70, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x8c8: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0x8d0: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xc00, 0xc00, +/* 0x8d8: */ 0xe00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x8e0: */ 0xe00, 0xe00, 0xe00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x8e8: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x8f0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x8f8: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x900: */ 0xdf0, 0xdf0, 0xdf0, 0xde0, 0xdf0, 0xde0, 0xdc0, 0xdc0, +/* 0x908: */ 0xde0, 0xdc0, 0xdc0, 0xd80, 0xd80, 0xd80, 0xd00, 0xd00, +/* 0x910: */ 0xdc0, 0xd80, 0xd80, 0xd00, 0xd00, 0xd00, 0xd00, 0xc00, +/* 0x918: */ 0xd00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x920: */ 0xd90, 0xd00, 0xd00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x928: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x930: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x938: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x940: */ 0xd70, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x948: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x950: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x958: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x960: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x968: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x970: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0x800, +/* 0x978: */ 0xc00, 0xc00, 0xc00, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x980: */ 0xcf0, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x988: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x990: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0x998: */ 0xc00, 0xc00, 0xc00, 0x800, 0xc00, 0x800, 0x800, 0x800, +/* 0x9a0: */ 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0x800, 0x800, +/* 0x9a8: */ 0xc00, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9b0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9b8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9c0: */ 0xc00, 0xc00, 0xc00, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9c8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9d0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9d8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9e0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9e8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9f0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0x9f8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa00: */ 0xbf0, 0xbf0, 0xbf0, 0xbe0, 0xbf0, 0xbe0, 0xbc0, 0xbc0, +/* 0xa08: */ 0xbe0, 0xbc0, 0xbc0, 0xb80, 0xb80, 0xb80, 0xb80, 0xb00, +/* 0xa10: */ 0xbc0, 0xb80, 0xb80, 0xb00, 0xb80, 0xb00, 0xb00, 0xb00, +/* 0xa18: */ 0xb00, 0xb00, 0xa00, 0xa00, 0xa00, 0xa00, 0xa00, 0xa00, +/* 0xa20: */ 0xbb0, 0xb00, 0xb00, 0xa00, 0xb00, 0xa00, 0xa00, 0xa00, +/* 0xa28: */ 0xa00, 0xa00, 0xa00, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa30: */ 0xa00, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa38: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa40: */ 0xb70, 0xb00, 0xa00, 0xa00, 0xa00, 0x800, 0x800, 0x800, +/* 0xa48: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa50: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa58: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa60: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa68: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa70: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa78: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa80: */ 0xaf0, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa88: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa90: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xa98: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xaa0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xaa8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xab0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xab8: */ 0x800, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0xac0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xac8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x000, 0x000, +/* 0xad0: */ 0x800, 0x800, 0x000, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0xad8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae0: */ 0x800, 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb00: */ 0x9f0, 0x9e0, 0x880, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb08: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb10: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xb18: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x000, 0x000, 0x000, +/* 0xb20: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x000, 0x000, 0x000, +/* 0xb28: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb30: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb40: */ 0x800, 0x800, 0x800, 0x000, 0x800, 0x000, 0x000, 0x000, +/* 0xb48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb80: */ 0x800, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc00: */ 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7e0, 0x7e0, 0x7c0, +/* 0xc08: */ 0x7e0, 0x7c0, 0x7c0, 0x780, 0x7c0, 0x780, 0x780, 0x780, +/* 0xc10: */ 0x7c0, 0x780, 0x780, 0x780, 0x780, 0x700, 0x700, 0x700, +/* 0xc18: */ 0x780, 0x700, 0x700, 0x600, 0x700, 0x600, 0x600, 0x600, +/* 0xc20: */ 0x7b0, 0x780, 0x700, 0x700, 0x700, 0x600, 0x600, 0x600, +/* 0xc28: */ 0x700, 0x600, 0x600, 0x600, 0x600, 0x400, 0x400, 0x400, +/* 0xc30: */ 0x600, 0x600, 0x600, 0x400, 0x400, 0x400, 0x400, 0x400, +/* 0xc38: */ 0x400, 0x400, 0x400, 0x000, 0x400, 0x000, 0x000, 0x000, +/* 0xc40: */ 0x770, 0x700, 0x600, 0x600, 0x600, 0x600, 0x400, 0x400, +/* 0xc48: */ 0x600, 0x400, 0x400, 0x400, 0x400, 0x000, 0x000, 0x000, +/* 0xc50: */ 0x400, 0x400, 0x400, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc60: */ 0x400, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc80: */ 0x6f0, 0x640, 0x600, 0x400, 0x400, 0x000, 0x000, 0x000, +/* 0xc88: */ 0x400, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd00: */ 0x5f0, 0x5e0, 0x4c0, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe00: */ 0x3f0, 0x3f0, 0x3e0, 0x000, 0x1c0, 0x000, 0x000, 0x000, +/* 0xe08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xea8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xeb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xec0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xec8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xed8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xee0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xee8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xef0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xef8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf00: */ 0x070, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xf98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xfe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xff0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xff8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +}, diff --git a/C64/SID/resid/wave8580__ST.h b/C64/SID/resid/wave8580__ST.h new file mode 100755 index 00000000..05fb3c88 --- /dev/null +++ b/C64/SID/resid/wave8580__ST.h @@ -0,0 +1,533 @@ +// --------------------------------------------------------------------------- +// This file is part of reSID, a MOS6581 SID emulator engine. +// Copyright (C) 2010 Dag Lem +// +// This program 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. +// +// This program 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 this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// --------------------------------------------------------------------------- + +{ +/* 0x000: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x008: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x010: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x018: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x020: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x028: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x030: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x038: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x040: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x048: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x050: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x058: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x060: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x068: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x070: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x078: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x080: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x088: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x090: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x098: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x0f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x100: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x108: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x110: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x118: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x120: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x128: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x130: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x138: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x140: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x148: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x150: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x158: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x160: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x168: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x170: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x178: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x180: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x188: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x190: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x198: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x1f8: */ 0x0e0, 0x0e0, 0x0e0, 0x0e0, 0x0f0, 0x0f0, 0x0f0, 0x0f0, +/* 0x200: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x208: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x210: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x218: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x220: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x228: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x230: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x238: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x240: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x248: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x250: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x258: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x260: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x268: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x270: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x278: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x280: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x288: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x290: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x298: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x2f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x300: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x308: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x310: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x318: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x320: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x328: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x330: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x338: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x340: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x348: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x350: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x358: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x360: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x368: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x370: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x378: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x380: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x388: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x390: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x398: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x3c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x3f0: */ 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, +/* 0x3f8: */ 0x1e0, 0x1e0, 0x1f0, 0x1f0, 0x1f0, 0x1f0, 0x1f0, 0x1f0, +/* 0x400: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x408: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x410: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x418: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x420: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x428: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x430: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x438: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x440: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x448: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x450: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x458: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x460: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x468: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x470: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x478: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x480: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x488: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x490: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x498: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x4f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x500: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x508: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x510: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x518: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x520: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x528: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x530: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x538: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x540: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x548: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x550: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x558: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x560: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x568: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x570: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x578: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x580: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x588: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x590: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x598: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x5f8: */ 0x0e0, 0x0e0, 0x0e0, 0x0e0, 0x0f0, 0x0f0, 0x0f0, 0x1f0, +/* 0x600: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x608: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x610: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x618: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x620: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x628: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x630: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x638: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x640: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x648: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x650: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x658: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x660: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x668: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x670: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x678: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x680: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x688: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x690: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x698: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x6f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x700: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x708: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x710: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x718: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x720: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x728: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x730: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x738: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x740: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x748: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x750: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x758: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x760: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x768: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x770: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x778: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x780: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x788: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x790: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x798: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0x7c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x7e0: */ 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, +/* 0x7e8: */ 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, 0x380, +/* 0x7f0: */ 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3c0, 0x3e0, +/* 0x7f8: */ 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, 0x7f0, +/* 0x800: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x808: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x810: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x818: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x820: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x828: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x830: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x838: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x840: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x848: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x850: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x858: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x860: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x868: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x870: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x878: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x880: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x888: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x890: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x898: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x8f8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0x900: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x908: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x910: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x918: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x920: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x928: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x930: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x938: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x940: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x948: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x950: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x958: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x960: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x968: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x970: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x978: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0x980: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x988: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x990: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x998: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9a8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9b8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9c8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9d8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9e8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0x9f8: */ 0x0e0, 0x0e0, 0x0e0, 0x0e0, 0x0f0, 0x0f0, 0x0f0, 0x0f0, +/* 0xa00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xa80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xa98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaa8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xab8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xac8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xad8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xae8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xaf8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0xb00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xb80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xb98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xba8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xbc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbe8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xbf0: */ 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, 0x1c0, +/* 0xbf8: */ 0x1e0, 0x1e0, 0x1f0, 0x1f0, 0x1f0, 0x1f0, 0x3f0, 0x3f0, +/* 0xc00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xc80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xc98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xca8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xce8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xcf8: */ 0x000, 0x000, 0x000, 0x000, 0x070, 0x070, 0x070, 0x070, +/* 0xd00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd78: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x030, 0x030, +/* 0xd80: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd88: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd90: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xd98: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xda8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdb8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x010, +/* 0xdc0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdc8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdd8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xde8: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf0: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xdf8: */ 0x0e0, 0x0e0, 0x0e0, 0x0e0, 0x0f0, 0x0f0, 0x1f0, 0x1f0, +/* 0xe00: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe08: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe10: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe18: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe20: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe28: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe30: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe38: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe40: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe48: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe50: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe58: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe60: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe68: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe70: */ 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, +/* 0xe78: */ 0x000, 0x000, 0x000, 0x000, 0x800, 0x000, 0x830, 0x830, +/* 0xe80: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xe88: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xe90: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xe98: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xea0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xea8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xeb0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xeb8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xec0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xec8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xed0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xed8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xee0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xee8: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xef0: */ 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800, +/* 0xef8: */ 0x800, 0x800, 0x800, 0x800, 0x870, 0x870, 0x870, 0x8f0, +/* 0xf00: */ 0xc00, 0xe00, 0xe00, 0xc00, 0xc00, 0xe00, 0xe00, 0xe00, +/* 0xf08: */ 0xe00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf10: */ 0xc00, 0xe00, 0xe00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf18: */ 0xe00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, 0xc00, +/* 0xf20: */ 0xc00, 0xe00, 0xe00, 0xc00, 0xc00, 0xe00, 0xe00, 0xe00, +/* 0xf28: */ 0xe00, 0xe00, 0xe00, 0xc00, 0xe00, 0xc00, 0xe00, 0xe00, +/* 0xf30: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf38: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf40: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf48: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf50: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf58: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf60: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf68: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf70: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, +/* 0xf78: */ 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe00, 0xe30, 0xe30, +/* 0xf80: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xf88: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xf90: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xf98: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xfa0: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xfa8: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xfb0: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, +/* 0xfb8: */ 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf00, 0xf10, +/* 0xfc0: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0xfc8: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0xfd0: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0xfd8: */ 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, 0xf80, +/* 0xfe0: */ 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, +/* 0xfe8: */ 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, 0xfc0, +/* 0xff0: */ 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, 0xfe0, +/* 0xff8: */ 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, 0xff0, +}, diff --git a/C64/VICII/VIC.cpp b/C64/VICII/VIC.cpp new file mode 100755 index 00000000..d030842b --- /dev/null +++ b/C64/VICII/VIC.cpp @@ -0,0 +1,886 @@ +/* + * Author: Dirk W. Hoffmann + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Cycle accurate VIC II emulation. + Mostly based on the extensive VIC II documentation by Christian Bauer ([C.B.]) + Many thanks, Christian! +*/ + +#include "C64.h" + +#define SPR0 0x01 +#define SPR1 0x02 +#define SPR2 0x04 +#define SPR3 0x08 +#define SPR4 0x10 +#define SPR5 0x20 +#define SPR6 0x40 +#define SPR7 0x80 + +VIC::VIC() +{ + setDescription("VIC"); + debug(3, " Creating VIC at address %p...\n", this); + + markIRQLines = false; + markDMALines = false; + emulateGrayDotBug = true; + palette = COLOR_PALETTE; + + // Register snapshot items + SnapshotItem items[] = { + + // Configuration items + { &model, sizeof(model), KEEP_ON_RESET }, + { &glueLogic, sizeof(glueLogic), KEEP_ON_RESET }, + { &emulateGrayDotBug, sizeof(emulateGrayDotBug), KEEP_ON_RESET }, + + // Internal state + { ®, sizeof(reg), CLEAR_ON_RESET }, + { &rasterIrqLine, sizeof(rasterIrqLine), CLEAR_ON_RESET }, + { &latchedLightPenX, sizeof(latchedLightPenX), CLEAR_ON_RESET }, + { &latchedLightPenY, sizeof(latchedLightPenY), CLEAR_ON_RESET }, + { &memSelect, sizeof(memSelect), CLEAR_ON_RESET }, + { &irr, sizeof(irr), CLEAR_ON_RESET }, + { &imr, sizeof(imr), CLEAR_ON_RESET }, + + { &refreshCounter, sizeof(refreshCounter), CLEAR_ON_RESET }, + { &xCounter, sizeof(xCounter), CLEAR_ON_RESET }, + { &yCounter, sizeof(yCounter), CLEAR_ON_RESET }, + { &vc, sizeof(vc), CLEAR_ON_RESET }, + { &vcBase, sizeof(vcBase), CLEAR_ON_RESET }, + { &rc, sizeof(rc), CLEAR_ON_RESET }, + { &videoMatrix, sizeof(videoMatrix), CLEAR_ON_RESET }, + { &colorLine, sizeof(colorLine), CLEAR_ON_RESET }, + { &vmli, sizeof(vmli), CLEAR_ON_RESET }, + { &sr, sizeof(sr), CLEAR_ON_RESET }, + { &spriteSr, sizeof(spriteSr), CLEAR_ON_RESET }, + { &spriteSrActive, sizeof(spriteSrActive), CLEAR_ON_RESET }, + { &spriteSpriteCollision, sizeof(spriteSpriteCollision), CLEAR_ON_RESET }, + { &spriteBackgroundColllision, sizeof(spriteBackgroundColllision), CLEAR_ON_RESET }, + + { &flipflops, sizeof(flipflops), CLEAR_ON_RESET }, + { &verticalFrameFFsetCond, sizeof(verticalFrameFFsetCond), CLEAR_ON_RESET }, + { &leftComparisonVal, sizeof(leftComparisonVal), CLEAR_ON_RESET }, + { &rightComparisonVal, sizeof(rightComparisonVal), CLEAR_ON_RESET }, + { &upperComparisonVal, sizeof(upperComparisonVal), CLEAR_ON_RESET }, + { &lowerComparisonVal, sizeof(lowerComparisonVal), CLEAR_ON_RESET }, + + { &isVisibleColumn, sizeof(isVisibleColumn), CLEAR_ON_RESET }, + { &yCounterEqualsIrqRasterline, sizeof(yCounterEqualsIrqRasterline), CLEAR_ON_RESET }, + { &vblank, sizeof(vblank), CLEAR_ON_RESET }, + { &badLine, sizeof(badLine), CLEAR_ON_RESET }, + { &DENwasSetInRasterline30, sizeof(DENwasSetInRasterline30), CLEAR_ON_RESET }, + { &displayState, sizeof(displayState), CLEAR_ON_RESET }, + + { &mc, sizeof(mc), CLEAR_ON_RESET }, + { &mcbase, sizeof(mcbase), CLEAR_ON_RESET }, + { spritePtr, sizeof(spritePtr), CLEAR_ON_RESET }, + { &isFirstDMAcycle, sizeof(isFirstDMAcycle), CLEAR_ON_RESET }, + { &isSecondDMAcycle, sizeof(isSecondDMAcycle), CLEAR_ON_RESET }, + { &spriteDisplay, sizeof(spriteDisplay), CLEAR_ON_RESET }, + { &spriteDisplayDelayed, sizeof(spriteDisplayDelayed), CLEAR_ON_RESET }, + { &spriteDmaOnOff, sizeof(spriteDmaOnOff), CLEAR_ON_RESET }, + { &expansionFF, sizeof(expansionFF), CLEAR_ON_RESET }, + { &cleared_bits_in_d017, sizeof(cleared_bits_in_d017), CLEAR_ON_RESET }, + + { &lpLine, sizeof(lpLine), CLEAR_ON_RESET }, + { &lightpenIRQhasOccured, sizeof(lightpenIRQhasOccured), CLEAR_ON_RESET }, + + { &memSrc, sizeof(memSrc), KEEP_ON_RESET }, + { &ultimax, sizeof(ultimax), CLEAR_ON_RESET }, + { &dataBusPhi1, sizeof(dataBusPhi1), CLEAR_ON_RESET }, + { &dataBusPhi2, sizeof(dataBusPhi2), CLEAR_ON_RESET }, + { &addrBus, sizeof(addrBus), CLEAR_ON_RESET }, + { &bankAddr, sizeof(bankAddr), CLEAR_ON_RESET }, + + { &delay, sizeof(delay), CLEAR_ON_RESET }, + { &bufferoffset, sizeof(bufferoffset), CLEAR_ON_RESET }, + + { NULL, 0, 0 }}; + + registerSnapshotItems(items, sizeof(items)); +} + +VIC::~VIC() +{ +} + +void +VIC::setC64(C64 *c64) +{ + VirtualComponent::setC64(c64); + + // Assign reference clock to all time delayed variables + baLine.setClock(&c64->cpu.cycle); + gAccessResult.setClock(&c64->cpu.cycle); +} + +void +VIC::reset() +{ + VirtualComponent::reset(); + + yCounter = PAL_HEIGHT; + + // Reset timed delay variables + baLine.clear(); + gAccessResult.clear(); + + // Reset the memory source lookup table + setUltimax(false); + + // Reset sprite logic + expansionFF = 0xFF; + + // Preset some video parameters to show a blank blue sreen on power up + memSelect = 0x10; + // memset(&c64->mem.ram[0x400], 32, 40*25); + reg.delayed.ctrl1 = 0x10; + reg.current.ctrl1 = 0x10; + reg.delayed.colors[COLREG_BORDER] = VICII_LIGHT_BLUE; + reg.current.colors[COLREG_BORDER] = VICII_LIGHT_BLUE; + reg.delayed.colors[COLREG_BG0] = VICII_BLUE; + reg.current.colors[COLREG_BG0] = VICII_BLUE; + + // Frame flipflops + leftComparisonVal = leftComparisonValue(); + rightComparisonVal = rightComparisonValue(); + upperComparisonVal = upperComparisonValue(); + lowerComparisonVal = lowerComparisonValue(); + + // Disable cheating by default + hideSprites = false; + spriteSpriteCollisionEnabled = 0xFF; + spriteBackgroundCollisionEnabled = 0xFF; + + // Screen buffer + currentScreenBuffer = screenBuffer1; + pixelBuffer = currentScreenBuffer; +} + +void +VIC::ping() +{ + VirtualComponent::ping(); + c64->putMessage(isPAL() ? MSG_PAL : MSG_NTSC); +} + +void +VIC::dump() +{ + uint8_t ctrl1 = reg.current.ctrl1; + uint8_t ctrl2 = reg.current.ctrl2; + int yscroll = ctrl1 & 0x07; + int xscroll = ctrl2 & 0x07; + DisplayMode mode = (DisplayMode)((ctrl1 & 0x60) | (ctrl2 & 0x10)); + + msg("VIC\n"); + msg("---\n\n"); + msg(" Chip model : %d\n", model); + msg(" PAL : %s\n", isPAL() ? "yes" : "no"); + msg(" NTSC : %s\n", isNTSC() ? "yes" : "no"); + msg(" Glue logic : %d\n", glueLogic); + msg(" Gray dot bug : %s\n", emulateGrayDotBug ? "yes" : "no"); + msg(" is656x, is856x : %d %d\n", is656x(), is856x()); + msg(" Bank address : %04X\n", bankAddr, bankAddr); + msg(" Screen memory : %04X\n", VM13VM12VM11VM10() << 6); + msg(" Character memory : %04X\n", (CB13CB12CB11() << 10) % 0x4000); + msg("X/Y raster scroll : %d / %d\n", xscroll, yscroll); + msg(" Control reg 1 : %02X\n", reg.current.ctrl1); + msg(" Control reg 2 : %02X\n", reg.current.ctrl2); + msg(" Display mode : "); + switch (mode) { + case STANDARD_TEXT: + msg("Standard character mode\n"); + break; + case MULTICOLOR_TEXT: + msg("Multicolor character mode\n"); + break; + case STANDARD_BITMAP: + msg("Standard bitmap mode\n"); + break; + case MULTICOLOR_BITMAP: + msg("Multicolor bitmap mode\n"); + break; + case EXTENDED_BACKGROUND_COLOR: + msg("Extended background color mode\n"); + break; + default: + msg("Invalid\n"); + } + msg(" (X,Y) : (%d,%d) %s %s\n", xCounter, yCounter, badLine ? "(DMA line)" : "", DENwasSetInRasterline30 ? "" : "(DMA lines disabled, no DEN bit in rasterline 30)"); + msg(" VC : %02X\n", vc); + msg(" VCBASE : %02X\n", vcBase); + msg(" RC : %02X\n", rc); + msg(" VMLI : %02X\n", vmli); + msg(" BA line : %s\n", baLine.current() ? "low" : "high"); + msg(" MainFrameFF : %d\n", flipflops.current.main); + msg(" VerticalFrameFF : %d\n", flipflops.current.vertical); + msg(" DisplayState : %s\n", displayState ? "on" : "off"); + msg(" SpriteDisplay : %02X (%02X)\n", spriteDisplay, spriteDisplayDelayed); + msg(" SpriteDma : %02X ( ", spriteDmaOnOff); + for (int i = 0; i < 8; i++) + msg("%d ", (spriteDmaOnOff & (1 << i)) != 0 ); + msg(")\n"); + msg(" Y expansion : %02X ( ", expansionFF); + for (int i = 0; i < 8; i++) + msg("%d ", (expansionFF & (1 << i)) != 0); + msg(")\n"); +} + +size_t +VIC::stateSize() +{ + return VirtualComponent::stateSize() + + baLine.stateSize() + + gAccessResult.stateSize(); +} + +void +VIC::didLoadFromBuffer(uint8_t **buffer) +{ + baLine.loadFromBuffer(buffer); + gAccessResult.loadFromBuffer(buffer); +} + +void +VIC::didSaveToBuffer(uint8_t **buffer) +{ + baLine.saveToBuffer(buffer); + gAccessResult.saveToBuffer(buffer); +} + +void +VIC::setModel(VICModel m) +{ + debug(2, "VIC::setModel(%d)\n", m); + + if (!isVICChhipModel(m)) { + warn("Unknown VICII model (%d). Assuming a MOS8565.\n", m); + m = PAL_8565; + } + + suspend(); + + model = m; + updatePalette(); + resetScreenBuffers(); + c64->updateVicFunctionTable(); + + switch(model) { + + case PAL_6569_R1: + case PAL_6569_R3: + case PAL_8565: + c64->setClockFrequency(PAL_CLOCK_FREQUENCY); + c64->putMessage(MSG_PAL); + break; + + case NTSC_6567: + case NTSC_6567_R56A: + case NTSC_8562: + c64->setClockFrequency(NTSC_CLOCK_FREQUENCY); + c64->putMessage(MSG_NTSC); + break; + + default: + assert(false); + } + + resume(); +} + +void +VIC::setVideoPalette(VICPalette type) +{ + if (!isVICPalette(type)) { + warn("Unknown palette type (%d). Assuming color palette.\n", type); + type = COLOR_PALETTE; + } + + palette = type; + updatePalette(); +} + +void +VIC::setGlueLogic(GlueLogic type) +{ + debug(2, "VIC::setGlueLogic(%d)\n", type); + + if (!isGlueLogic(type)) { + warn("Unknown glue logic type (%d). Assuming discrete logic.\n", type); + type = GLUE_DISCRETE; + } + + suspend(); + glueLogic = type; + resume(); +} + +unsigned +VIC::getClockFrequency() +{ + switch (model) { + + case NTSC_6567: + case NTSC_8562: + case NTSC_6567_R56A: + return NTSC_CLOCK_FREQUENCY; + + default: + return PAL_CLOCK_FREQUENCY; + } +} + +unsigned +VIC::getCyclesPerRasterline() +{ + switch (model) { + + case NTSC_6567_R56A: + return 64; + + case NTSC_6567: + case NTSC_8562: + return 65; + + default: + return 63; + } +} + +bool +VIC::isLastCycleInRasterline(unsigned cycle) +{ + return cycle >= getCyclesPerRasterline(); +} + +unsigned +VIC::getRasterlinesPerFrame() +{ + switch (model) { + + case NTSC_6567_R56A: + return 262; + + case NTSC_6567: + case NTSC_8562: + return 263; + + default: + return 312; + } +} + +bool +VIC::isVBlankLine(unsigned rasterline) +{ + switch (model) { + + case NTSC_6567_R56A: + return rasterline < 16 || rasterline >= 16 + 234; + + case NTSC_6567: + case NTSC_8562: + return rasterline < 16 || rasterline >= 16 + 235; + + default: + return rasterline < 16 || rasterline >= 16 + 284; + } +} + +void * +VIC::screenBuffer() { + if (currentScreenBuffer == screenBuffer1) { + return screenBuffer2; + } else { + return screenBuffer1; + } +} + +void +VIC::resetScreenBuffers() +{ + for (unsigned line = 0; line < PAL_RASTERLINES; line++) { + for (unsigned i = 0; i < NTSC_PIXELS; i++) { + screenBuffer1[line * NTSC_PIXELS + i] = + screenBuffer2[line * NTSC_PIXELS + i] = + (line % 2) ? rgbaTable[8] : rgbaTable[9]; + } + } +} + +uint16_t +VIC::rasterline() +{ + return c64->rasterLine; +} + +uint8_t +VIC::rastercycle() +{ + return c64->rasterCycle; +} + + +// +// Frame flipflops +// + +void +VIC::checkVerticalFrameFF() +{ + + // Check for upper border + if (yCounter == upperComparisonVal) { + + if (DENbit()) { + + // Clear immediately + setVerticalFrameFF(false); + } + + } else if (yCounter == lowerComparisonVal) { + + // Set later, in cycle 1 + verticalFrameFFsetCond = true; + } +} + +void +VIC::checkFrameFlipflopsLeft(uint16_t comparisonValue) +{ + /* "6. If the X coordinate reaches the left comparison value and the + * vertical border flip flop is not set, the main flip flop is reset." + */ + if (comparisonValue == leftComparisonVal) { + + // Note that the main frame flipflop can not be cleared when the + // vertical border flipflop is set. + if (!flipflops.current.vertical && !verticalFrameFFsetCond) { + setMainFrameFF(false); + } + } +} + +void +VIC::checkFrameFlipflopsRight(uint16_t comparisonValue) +{ + /* "1. If the X coordinate reaches the right comparison value, the main + * border flip flop is set." [C.B.] + */ + if (comparisonValue == rightComparisonVal) { + setMainFrameFF(true); + } +} + +void +VIC::setVerticalFrameFF(bool value) +{ + if (value != flipflops.delayed.vertical) { + flipflops.current.vertical = value; + delay |= VICUpdateFlipflops; + } +} + +void +VIC::setMainFrameFF(bool value) +{ + if (value != flipflops.delayed.main) { + flipflops.current.main = value; + delay |= VICUpdateFlipflops; + } +} + +bool +VIC::badLineCondition() { + + /* A Bad Line Condition is given at any arbitrary clock cycle, if at the + * negative edge of ø0 at the beginning of the cycle + * [1] RASTER >= $30 and RASTER <= $f7 and + * [2] the lower three bits of RASTER are equal to YSCROLL and + * [3] if the DEN bit was set during an arbitrary cycle of + * raster line $30." [C.B.] + */ + return + (yCounter >= 0x30 && yCounter <= 0xf7) && /* [1] */ + (yCounter & 0x07) == (reg.current.ctrl1 & 0x07) && /* [2] */ + DENwasSetInRasterline30; /* [3] */ +} + +void +VIC::updateBA(uint8_t value) +{ + if (value != baLine.current()) { + + if (value) { + baLine.write(value); + } else { + baLine.clear(); + } + + c64->cpu.setRDY(value == 0); + } +} + +void +VIC::triggerIrq(uint8_t source) +{ + assert(source == 1 || source == 2 || source == 4 || source == 8); + + irr |= source; + delay |= VICUpdateIrqLine; +} + +uint16_t +VIC::lightpenX() +{ + uint8_t cycle = c64->rasterCycle; + + switch (model) { + + case PAL_6569_R1: + case PAL_6569_R3: + + return 4 + (cycle < 14 ? 392 + (8 * cycle) : (cycle - 14) * 8); + + case PAL_8565: + + return 2 + (cycle < 14 ? 392 + (8 * cycle) : (cycle - 14) * 8); + + case NTSC_6567: + case NTSC_6567_R56A: + + return 4 + (cycle < 14 ? 400 + (8 * cycle) : (cycle - 14) * 8); + + case NTSC_8562: + + return 2 + (cycle < 14 ? 400 + (8 * cycle) : (cycle - 14) * 8); + + default: + assert(false); + } +} + +uint16_t +VIC::lightpenY() +{ + return yCounter; +} + +void +VIC::setLP(bool value) +{ + // A negative transition on LP triggers a lightpen event. + if (FALLING_EDGE(lpLine, value)) { + delay |= VICLpTransition; + } + + lpLine = value; +} + +void +VIC::checkForLightpenIrq() +{ + uint8_t vicCycle = c64->rasterCycle; + // debug("Negative LP transition at rastercycle %d\n", vicCycle); + + // An interrupt is suppressed if ... + + // ... a previous interrupt has occured in the current frame. + if (lightpenIRQhasOccured) + return; + + // ... we are in the last PAL rasterline and not in cycle 1. + if (yCounter == PAL_HEIGHT - 1 && vicCycle != 1) + return; + + // Latch coordinates + latchedLightPenX = lightpenX() / 2; + latchedLightPenY = lightpenY(); + + // Newer VIC models trigger an interrupt immediately + if (!delayedLightPenIrqs()) triggerIrq(8); + + // Lightpen interrupts can only occur once per frame + lightpenIRQhasOccured = true; +} + +void +VIC::checkForLightpenIrqAtStartOfFrame() +{ + // This function is called at the beginning of a frame, only. + assert(c64->rasterLine == 0); + assert(c64->rasterCycle == 2); + + // Latch coordinate (values according to VICE 3.1) + switch (model) { + + case PAL_6569_R1: + case PAL_6569_R3: + case PAL_8565: + + latchedLightPenX = 209; + latchedLightPenY = 0; + break; + + case NTSC_6567: + case NTSC_6567_R56A: + case NTSC_8562: + + latchedLightPenX = 213; + latchedLightPenY = 0; + break; + } + + // Trigger interrupt + triggerIrq(8); + + // Lightpen interrupts can only occur once per frame + lightpenIRQhasOccured = true; +} + + +// +// Sprites +// + +uint8_t +VIC::spriteDepth(uint8_t nr) +{ + return + GET_BIT(reg.delayed.sprPriority, nr) ? + (SPRITE_LAYER_BG_DEPTH | nr) : + (SPRITE_LAYER_FG_DEPTH | nr); +} + +uint8_t +VIC::compareSpriteY() +{ + uint8_t result = 0; + + for (unsigned i = 0; i < 8; i++) { + result |= (reg.current.sprY[i] == (yCounter & 0xFF)) << i; + } + + return result; +} + +void +VIC::turnSpriteDmaOff() +{ + /* "7. In the first phase of cycle 16, [1] it is checked if the expansion + * flip flop is set. If so, [2] MCBASE load from MC (MC->MCBASE), [3] + * unless the CPU cleared the Y expansion bit in $d017 in the second + * phase of cycle 15, in which case [4] MCBASE is set to + * + * X = (101010 & (MCBASE & MC)) | (010101 & (MCBASE | MC)). + * + * After the MCBASE update, [5] the VIC checks if MCBASE is equal to 63 + * and [6] turns off the DMA of the sprite if it is." [VIC Addendum] + */ + for (unsigned i = 0; i < 8; i++) { + + if (GET_BIT(expansionFF,i)) { /* [1] */ + if (GET_BIT(cleared_bits_in_d017,i)) { /* [3] */ + mcbase[i] = + (0b101010 & (mcbase[i] & mc[i])) | + (0b010101 & (mcbase[i] | mc[i])); /* [4] */ + } else { + mcbase[i] = mc[i]; /* [2] */ + } + if (mcbase[i] == 63) { /* [5] */ + CLR_BIT(spriteDmaOnOff,i); /* [6] */ + } + } + } +} + +void +VIC::turnSpriteDmaOn() +{ + /* "In the first phases of cycle 55 and 56, the VIC checks for every sprite + * if the corresponding MxE bit in register $d015 is set and the Y + * coordinate of the sprite (odd registers $d001-$d00f) match the lower 8 + * bits of RASTER. If this is the case and the DMA for the sprite is still + * off, the DMA is switched on, MCBASE is cleared, and if the MxYE bit is + * set the expansion flip flip is reset." [C.B.] + */ + uint8_t risingEdges = ~spriteDmaOnOff & (reg.current.sprEnable & compareSpriteY()); + + for (unsigned i = 0; i < 8; i++) { + if (GET_BIT(risingEdges,i)) + mcbase[i] = 0; + } + spriteDmaOnOff |= risingEdges; + expansionFF |= risingEdges; +} + +void +VIC::turnSpritesOnOrOff() +{ + /* "In the first phase of cycle 58, the MC of every sprite is loaded from + * its belonging MCBASE (MCBASE->MC) and it is checked [1] if the DMA for + * the sprite is turned on and [2] the Y coordinate of the sprite matches + * the lower 8 bits of RASTER. If this is the case, the display of the + * sprite is turned on." + */ + for (unsigned i = 0; i < 8; i++) { + mc[i] = mcbase[i]; + } + + spriteDisplay |= reg.current.sprEnable & compareSpriteY(); + spriteDisplay &= spriteDmaOnOff; +} + +void +VIC::updateSpriteShiftRegisters() { + if (isSecondDMAcycle) { + for (unsigned sprite = 0; sprite < 8; sprite++) { + if (GET_BIT(isSecondDMAcycle, sprite)) { + loadShiftRegister(sprite); + } + } + } +} + +void +VIC::beginFrame() +{ + lightpenIRQhasOccured = false; + + /* "The VIC does five read accesses in every raster line for the refresh of + * the dynamic RAM. An 8 bit refresh counter (REF) is used to generate 256 + * DRAM row addresses. The counter is reset to $ff in raster line 0 and + * decremented by 1 after each refresh access." [C.B.] + */ + refreshCounter = 0xFF; + + /* "Once somewhere outside of the range of raster lines $30-$f7 (i.e. + * outside of the Bad Line range), VCBASE is reset to zero. This is + * presumably done in raster line 0, the exact moment cannot be determined + * and is irrelevant." [C.B.] + */ + vcBase = 0; +} + +void +VIC::endFrame() +{ + // Switch active screen buffer + bool first = (currentScreenBuffer == screenBuffer1); + currentScreenBuffer = first ? screenBuffer2 : screenBuffer1; + pixelBuffer = currentScreenBuffer; +} + +void +VIC::beginRasterline(uint16_t line) +{ + verticalFrameFFsetCond = false; + + // Determine if we're inside the VBLANK area (nothing is drawn there). + vblank = isVBlankLine(line); + + // Increase yCounter. The overflow case is handled in cycle 2. + if (!yCounterOverflow()) yCounter++; + + // Check the DEN bit in rasterline 30. + // Note: The value might change later if control register 1 is written to. + if (line == 0x30) DENwasSetInRasterline30 = DENbit(); + + // Check if this line is a DMA line (bad line) + // Note: The value might change later if control register 1 is written to. + if ((badLine = badLineCondition())) { + delay |= VICSetDisplayState; + } + + // We adjust the position of the first pixel in the pixel buffer to make + // sure that the screen always appears centered. + if (c64->vic.isPAL()) { + bufferoffset = PAL_LEFT_BORDER_WIDTH - 32; + } else { + bufferoffset = NTSC_LEFT_BORDER_WIDTH - 32; + } +} + +void +VIC::endRasterline() +{ + // Set vertical flipflop if condition was hit + // Do we need to do this here? It is handled in cycle 1 as well. + if (verticalFrameFFsetCond) { + setVerticalFrameFF(true); + } + + // Draw debug markers + if (markIRQLines && yCounter == rasterInterruptLine()) + markLine(VICII_WHITE); + if (markDMALines && badLine) + markLine(VICII_RED); + + if (!vblank) { + + // Make the border look nice (evetually, we should get rid of this) + expandBorders(); + + // + // Experimental code for RF modulator effect + // + /* + int8_t oldR = (pixelBuffer[0] >> 0) & 0xFF; + int8_t oldG = (pixelBuffer[0] >> 8) & 0xFF; + int8_t oldB = (pixelBuffer[0] >> 16) & 0xFF; + + for (unsigned i = 1; i < NTSC_PIXELS; i++) { + + int8_t newR = (pixelBuffer[i] >> 0) & 0xFF; + int8_t newG = (pixelBuffer[i] >> 8) & 0xFF; + int8_t newB = (pixelBuffer[i] >> 16) & 0xFF; + + int diffR = newR - oldR; + int diffG = newG - oldG; + int diffB = newB - oldB; + + if (diffR > 0) diffR /= 2; + if (diffG > 0) diffG /= 2; + if (diffB > 0) diffB /= 2; + + oldR = oldR + diffR; + oldG = oldG + diffG; + oldB = oldB + diffB; + + pixelBuffer[i] = (oldR << 0) | (oldG << 8) | (oldB << 16); + } + */ + + // Advance pixelBuffer + uint16_t nextline = c64->rasterLine - PAL_UPPER_VBLANK + 1; + if (nextline < PAL_RASTERLINES) { + pixelBuffer = currentScreenBuffer + (nextline * NTSC_PIXELS); + } + } +} + + + + + + + + diff --git a/C64/VICII/VIC.h b/C64/VICII/VIC.h new file mode 100755 index 00000000..e4b314b6 --- /dev/null +++ b/C64/VICII/VIC.h @@ -0,0 +1,1619 @@ +/*! + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _VIC_INC +#define _VIC_INC + +#include "VirtualComponent.h" +#include "C64_types.h" +#include "TimeDelayed.h" + +// Sprite bit masks +#define SPR0 0x01 +#define SPR1 0x02 +#define SPR2 0x04 +#define SPR3 0x08 +#define SPR4 0x10 +#define SPR5 0x20 +#define SPR6 0x40 +#define SPR7 0x80 + +// Depth of different drawing layers +#define BORDER_LAYER_DEPTH 0x10 // In front of everything +#define SPRITE_LAYER_FG_DEPTH 0x20 // Behind border +#define FOREGROUND_LAYER_DEPTH 0x30 // Behind sprite 1 layer +#define SPRITE_LAYER_BG_DEPTH 0x40 // Behind foreground +#define BACKGROUD_LAYER_DEPTH 0x50 // Behind sprite 2 layer +#define BEHIND_BACKGROUND_DEPTH 0x60 // Behind background + +// Event flags +#define VICUpdateIrqLine (1ULL << 0) // Sets or releases the IRQ line +#define VICLpTransition (1ULL << 1) // Triggers a lightpen event +#define VICUpdateFlipflops (1ULL << 2) // Updates the flipflop value pipeline +#define VICUpdateRegisters (1ULL << 3) // Updates the register value pipeline +#define VICUpdateBankAddr (1ULL << 4) // Updates the bank address +#define VICSetDisplayState (1ULL << 5) // Flagged when control reg 1 changes +#define VICClrSprSprCollReg (1ULL << 6) // Resets the sprite-sprite coll reg +#define VICClrSprBgCollReg (1ULL << 7) // Resets the sprite-background coll reg + +#define VICClearanceMask ~((1ULL << 8) | VICUpdateIrqLine | VICLpTransition | VICUpdateFlipflops | VICUpdateRegisters | VICUpdateBankAddr | VICSetDisplayState | VICClrSprSprCollReg | VICClrSprBgCollReg); + + +// Forward declarations +class C64Memory; + + +//! @brief Virtual Video Controller (VICII) +class VIC : public VirtualComponent { + + friend C64Memory; + + // + // Configuration options + // + +private: + + //! @brief Selected chip model + VICModel model; + + //! @brief Color palette type + VICPalette palette; + + //! @brief Glue logic type + GlueLogic glueLogic; + +public: + + /*! @brief Indicates if the gray dot bug should be emulated + * @note The gray mode bug only affects the newer VICII models 856x + */ + bool emulateGrayDotBug; + + + // + // I/O space (CPU accessible) + // + +private: + + /*! @brief Piped I/O register state. + * @details When an I/O register is written to, the corresponding value + * in variable current is changed and a flag is set in variable + * delay. Function processDelayedActions() reads the flag and, if + * set to true, updates the delayed values. + * @see processDelayedActions() + */ + struct { + VICIIRegisters current; + VICIIRegisters delayed; + } reg; + + //! @brief Raster interrupt line ($D012) + uint8_t rasterIrqLine; + + //! @brief Latched lightpen X coordinate ($D013) + uint8_t latchedLightPenX; + + //! @brief Latched lightpen Y coordinate ($D014) + uint8_t latchedLightPenY; + + //! @brief Memory address register ($D018) + uint8_t memSelect; + + //! @brief Interrupt Request Register ($D019) + uint8_t irr; + + //! @brief Interrupt Mask Register ($D01A) + uint8_t imr; + + + // + // Chip internals + // + + // IRQ <---------------------------------+ + // (1) | + // +---------------+ +-----------------+ + // |Refresh counter| | Interrupt logic |<----------------------+ + // +---------------+ +-----------------+ | + // +-+ | ^ | + // A |M| v (2),(3) | (4),(5) | + // d |e| +-+ +--------------+ +-------+ | + // d |m| |A| |Raster counter|->| VC/RC | | + // r |o| |d| +->| X/Y | +-------+ | + // . <==>|r| |d| | +--------------+ | | + // + |y| |r| | | | | | (6),(7) | + // d | | |.|<--------+----------------+ +------------------------+ | + // a |i| |g|===========================>|40×12 bit video matrix-/| | + // t |n|<=>|e| | | | | color line | | + // a |t| |n| | | | +------------------------+ | + // |e| |e| | | | (8) || | + // |r| |r| | | | +----------------+ || | + // BA <--|f| |a|============>|8×24 bit sprite | || | + // |a| |t|<----+ | | | data buffers | || | + // AEC <--|c| |o| | | v | +----------------+ || | + // |e| |r| | +-----+ | || || | + // +-+ +-+ | |MC0-7| | (10) \/ (11) \/ | + // | +-----+ | +--------------+ +--------------+ | + // | (9) | | Sprite data | |Graphics data | | + // +---------------+ | | sequencer | | sequencer | | + // RAS <--| | | +--------------+ +--------------+ | + // CAS <--|Clock generator| | | | | + // ø0 <--| | | v v | + // +---------------+ | +-----------------------+ | + // ^ | | MUX | | + // | | | Sprite priorities and |-----------+ + // øIN -----------+ | | collision detection | + // | +-----------------------+ (12) + // VC: Video Matrix Counter | | + // (14) | (13) v + // RC: Row Counter | +-------------+ + // (15) +----------->| Border unit | + // MC: MOB Data Counter | +-------------+ + // (16) | | + // v v + // +----------------+ +----------------+ + // |Sync generation | |Color generation|<------- øCOLOR + // +----------------+ +----------------+ + // | | + // v v + // Video output + // (S/LUM and COLOR) [C.B.] + + + /*! @brief Refresh counter (1) + * @details "The VIC does five read accesses in every raster line for the + * refresh of the dynamic RAM. An 8 bit refresh counter (REF) + * is used to generate 256 DRAM row addresses. The counter is + * reset to $ff in raster line 0 and decremented by 1 after each + * refresh access." [C.B.] + * @seealso rAccess() + */ + uint8_t refreshCounter; + + /*! @brief Raster counter X (2) + * @details Defines the sprite coordinate system. + */ + uint16_t xCounter; + + /*! @brief Y raster counter (3) + * @details The rasterline counter is usually incremented in cycle 1. The + * only exception is the overflow condition which is handled in + * cycle 2. + */ + uint32_t yCounter; + + /*! @brief Video counter (14) + * @details A 10 bit counter that can be loaded with the value from + * vcBase. + */ + uint16_t vc; + + /*! @brief Video counter base + * @details A 10 bit data register with reset input that can be loaded + * with the value from vc. + */ + uint16_t vcBase; + + /*! @brief Row counter (15) + * @details A 3 bit counter with reset input. + */ + uint8_t rc; + + /*! @brief Video matrix (6) + * @details Every 8th rasterline, the VIC chips performs a c-access and + * fills this array with character information. + */ + uint8_t videoMatrix[40]; + + /*! @brief Color line (7) + * @details Every 8th rasterline, the VIC chips performs a c-access and + * fills the array with color information. + */ + uint8_t colorLine[40]; + + /*! @brief Video matrix line index + * @details "Besides this, there is a 6 bit counter with reset input that + * keeps track of the position within the internal 40×12 bit + * video matrix/color line where read character pointers are + * stored resp. read again. I will call this 'VMLI' (video + * matrix line index) here. + */ + uint8_t vmli; + + + /*! @brief Graphics data sequencer (10) + * @details An 8 bit shift register to synthesize canvas pixels. + */ + struct { + + //! @brief Shift register data + uint8_t data; + + /*! @brief Indicates whether the shift register can load data + * @details If true, the register is loaded when the current x scroll + * offset matches the current pixel number. + */ + bool canLoad; + + /*! @brief Multi-color synchronization flipflop + * @details Whenever the shift register is loaded, the synchronization + * flipflop is also set. It is toggled with each pixel and + * used to synchronize the synthesis of multi-color pixels. + */ + bool mcFlop; + + /*! @brief Latched character info + * @details Whenever the shift register is loaded, the current + * character value (which was once read during a gAccess) is + * latched. This value is used until the shift register loads + * again. + */ + uint8_t latchedCharacter; + + /*! @brief Latched color info + * @details Whenever the shift register is loaded, the current color + * value (which was once read during a gAccess) is latched. + * This value is used until the shift register loads again. + */ + uint8_t latchedColor; + + /*! @brief Color bits + * @details Every second pixel (as synchronized with mcFlop), the + * multi-color bits are remembered. + */ + uint8_t colorbits; + + /*! @brief Remaining bits to be pumped out + * @details Makes sure no more than 8 pixels are outputted. + */ + int remainingBits; + + } sr; + + + + + /*! @brief Sprite data sequencer (11) + * @details The VIC chip has a 24 bit (3 byte) shift register for each + * sprite. It stores the sprite for one rasterline. If a sprite + * is a display candidate in the current rasterline, its shift + * register is activated when the raster X coordinate matches + * the sprites X coordinate. The comparison is done in method + * drawSprite(). Once a shift register is activated, it remains + * activated until the beginning of the next rasterline. However, + * after an activated shift register has dumped out its 24 pixels, + * it can't draw anything else than transparent pixels (which is + * the same as not to draw anything). An exception is during DMA + * cycles. When a shift register is activated during such a cycle, + * it freezes a short period of time in which it repeats the + * previous drawn pixel. + */ + struct { + + //! @brief Shift register data (24 bit) + uint32_t data; + + //! @brief The shift register data is read in three chunks + uint8_t chunk1, chunk2, chunk3; + + /*! @brief Multi-color synchronization flipflop + * @details Whenever the shift register is loaded, the synchronization + * flipflop is also set. It is toggled with each pixel and + * used to synchronize the synthesis of multi-color pixels. + */ + bool mcFlop; + + //! @brief x expansion synchronization flipflop + bool expFlop; + + /*! @brief Color bits of the currently processed pixel + * @details In single-color mode, these bits are updated every cycle + * In multi-color mode, these bits are updated every second + * cycle (synchronized with mcFlop). + */ + uint8_t colBits; + + } spriteSr[8]; + + /*! @brief Indicates for each sprite if the shift register is active. + * @details Once the shift register is started, it runs as long + * it contains at least one '1' bit (data != 0). + */ + uint8_t spriteSrActive; + + //! @brief Sprite-sprite collision register (12) + uint8_t spriteSpriteCollision; + + //! @brief Sprite-background collision register (12) + uint8_t spriteBackgroundColllision; + + + // + // Border flipflops + // + + /*! @brief Piped frame flipflops state (13) + * @details When a flipflop toggles, the corresponding value + * in variable current is changed and a flag is set in variable + * delay. Function processDelayedActions() reads the flag and if + * set to true, updates the delayed values with the current ones. + * @see processDelayedActions() + */ + struct { + FrameFlipflops current; + FrameFlipflops delayed; + } flipflops; + + /*! @brief Vertical frame flipflop set condition + * @details Indicates whether the vertical frame flipflop needs to be set + * in the current rasterline. + */ + bool verticalFrameFFsetCond; + + /*! @brief First coordinate where the main frame flipflop is checked. + * @details Either 24 or 31, dependend on the CSEL bit. + */ + uint16_t leftComparisonVal; + + /*! @brief Second coordinate where the main frame flipflop is checked. + * @details Either 344 or 335, dependend on the CSEL bit. + */ + uint16_t rightComparisonVal; + + /*! @brief First coordinate where the vertical frame flipflop is checked. + * @details Either 51 or 55, dependend on the RSEL bit. + */ + uint16_t upperComparisonVal; + + /*! @brief Second coordinate where the vertical frame flipflop is checked. + * @details Either 251 or 247, dependend on the RSEL bit. + */ + uint16_t lowerComparisonVal; + + + // + // Housekeeping information + // + + /*! @brief Indicates wether we are in a visible display column or not + * @details The visible columns comprise canvas columns and border + * columns. The first visible column is drawn in cycle 14 (first + * left border column) and the last in cycle 61 (fourth right + * border column). + */ + bool isVisibleColumn; + + /*! @brief Set to true in cycle 1, cycle 63 (65) iff yCounter matches D012 + * @details Variable is needed to determine if a rasterline should be + * issued in cycle 1 or 2. + * @deprecated Will be replaced by rasterlineMatchesIrqLine + */ + bool yCounterEqualsIrqRasterline; + + //! @brief True if the current rasterline belongs to the VBLANK area. + bool vblank; + +public: // REMOVE + //! @brief Indicates if the current rasterline is a DMA line (bad line). + bool badLine; + + /*! @brief True, if DMA lines can occurr within the current frame. + * @details Bad lines can occur only if the DEN bit was set during an + * arbitary cycle in rasterline 30. The DEN bit is located in + * control register 1 (0x11). + */ + bool DENwasSetInRasterline30; + + /*! @brief Display State + * @details "The text/bitmap display logic in the VIC is in one of two + * states at any time: The idle state and the display state. + * + * - In display state, c- and g-accesses take place, the + * addresses and interpretation of the data depend on the + * selected display mode. + * + * - In idle state, only g-accesses occur. The VIC is either in + * idle or display state" [C.B.] + */ + bool displayState; + + + // + // Sprites + // + +private: + + /*! @brief MOB data counter (16). + * @details A 6 bit counter, one for each sprite. + */ + uint8_t mc[8]; + + /*! @brief MCBASE register. + * @details A 6 bit counter, one register for each sprite. + */ + uint8_t mcbase[8]; + + /*! @brief Sprite pointer fetched during a pAccess. + * @details Determines where the sprite data comes from. + */ + uint16_t spritePtr[8]; + + /*! @brief Flags the first DMA access for each sprite. + * @details Bit n corresponds to sprite n. + */ + uint8_t isFirstDMAcycle; + + /*! @brief Flags the second or third DMA access for each sprite. + * @details Bit n corresponds to sprite n. + */ + uint8_t isSecondDMAcycle; + + /*! @details Sprite display + * @details Determines if a sprite needs to be drawn in the current rasterline. + * Each bit represents a single sprite. + */ + uint8_t spriteDisplay; + + //! @details Value of spriteDisplay, delayed by one cycle + uint8_t spriteDisplayDelayed; + + /*! @brief Sprite DMA on off register + * @details Determines if sprite dma access is enabled or disabled. + * Each bit represents a single sprite. + */ + uint8_t spriteDmaOnOff; + + /*! @brief Expansion flipflop + * @details Used to handle Y sprite stretching, one bit for each sprite + */ + uint8_t expansionFF; + + /*! @brief Remembers which bits the CPU has cleared in the expansion Y register (D017) + * @details This value is set in pokeIO and cycle 15 and read in cycle 16 + */ + uint8_t cleared_bits_in_d017; + + + // + // Lightpen + // + + /*! @brief Current value of the LP pin + * @details A negative transition on this pin triggers a lightpen + * interrupt. + */ + bool lpLine; + + /*! @brief Indicates whether the lightpen has triggered + * @details This variable indicates whether a lightpen interrupt has + * occurred within the current frame. The variable is needed, + * because a lightpen interrupt can only occur once per frame. + */ + bool lightpenIRQhasOccured; + + + // + // CPU control and memory access + // + +private: + + /*! @brief Memory source lookup table + * @details If VICII is not running in Ultimax mode, it has access to RAM + * and the character Rom. In ultimax mode, VICII has access to + * ROMH and some portions of RAM. + */ + MemoryType memSrc[16]; + + /*! @brief Indicates whether VICII is running in ultimax mode. + * @details Ultimax mode can be enabled by external cartridges by pulling + * game line low and keeping exrom line high. In ultimax mode, + * VICII has access to ROMH and some portions of RAM. + */ + bool ultimax; + + /*! @brief Value on the data bus during the latest phi1 access + * @note Only VICII performs a memory access during phi1. + */ + uint8_t dataBusPhi1; + + /*! @brief Value on the data bus during the latest phi2 access + * @note VICII or the CPU can perform a memory access during phi2. + * If none of them does, 0xFF will be on the bus. + */ + uint8_t dataBusPhi2; + + /*! @brief Address bus + * @details Whenever VIC performs a memory read, the generated memory + * address is stored in this variable. + */ + uint16_t addrBus; + + /*! @brief Current value of the BA line + * @details Remember: Each CPU cycle is split into two phases: + * phi1 (First phase, LOW): VICII gets access to the bus + * phi2 (Second phase, HIGH): CPU gets access to the bus + * In rare cases, VIC needs access in the HIGH phase, too. To + * block the CPU, the BA line is pulled down. + * @note BA can be pulled down by multiple sources (wired AND) and + * this variable indicates which sources are holding the line + * low. + */ + TimeDelayedbaLine = TimeDelayed(3); + + /*! @brief Start address of the currently selected memory bank + * @details There are four banks in total since the VIC chip can only + * 'see' 16 KB of memory at the same time. Two bank select bits + * in the CIA I/O space determine which quarter of memory is + * currently seen. + */ + /* + * +-------+------+-------+----------+-------------------------+ + * | VALUE | BITS | BANK | STARTING | VIC-II CHIP RANGE | + * | OF A | | | LOCATION | | + * +-------+------+-------+----------+-------------------------+ + * | 0 | 00 | 3 | 49152 | ($C000-$FFFF) | + * | 1 | 01 | 2 | 32768 | ($8000-$BFFF) | + * | 2 | 10 | 1 | 16384 | ($4000-$7FFF) | + * | 3 | 11 | 0 | 0 | ($0000-$3FFF) (DEFAULT) | + * +-------+------+-------+----------+-------------------------+ + */ + uint16_t bankAddr; + + //! @brief Result of the lastest g-access + TimeDelayedgAccessResult = TimeDelayed(2); + + + // + // Color management (TODO: MOVE TO PIXEL ENGINE) + // + + //! @brief User adjustable brightness value used in palette computation + /*! @details Value may range from 0.0 to 100.0 + */ + double brightness = 50.0; + + //! @brief User adjustable contrast value used in palette computation + /*! @details Value may range from 0.0 to 100.0 + */ + double contrast = 100.0; + + //! @brief User adjustable saturation value used in palette computation + /*! @details Value may range from 0.0 to 100.0 + */ + double saturation = 50.0; + + + // + // Debugging and cheating + // + +public: + + /*! @brief Determines whether sprites are drawn or not + * @details During normal emulation, the value is always false. For + * debugging purposes, the value can be set to true. In this + * case, sprites are no longer drawn. + */ + bool hideSprites; + + /*! @brief Enables sprite-sprite collision + * @details If set to true, the virtual VIC chips checks for sprite-sprite + * collision as the original C64 does. For debugging purposes and + * cheating, collision detection can be disabled by setting the + * variabel to false. Collision detection can be enabled or + * disabled for each sprite seperately. Each bit is dedicated to + * a single sprite. + */ + uint8_t spriteSpriteCollisionEnabled; + + /*! @brief Enable sprite-background collision + * @details If set to true, the virtual VIC chips checks for sprite- + * background collision as the original C64 does. For debugging + * purposes and cheating, collision detection can be disabled by + * setting the variabel to false. Collision detection can be + * enabled or disabled for each sprite seperately. Each bit is + * dedicated to a single sprite. + */ + uint8_t spriteBackgroundCollisionEnabled; + + /*! @brief Determines whether IRQ lines will be made visible. + * @details Each rasterline that will potentially trigger a raster IRQ is + * highlighted. This feature is useful for debugging purposes as + * it visualizes how the screen is divided into multiple parts. + */ + bool markIRQLines; + + /*! @brief Determines whether DMA lines will be made visible. + * @details Each rasterline in which the vic will read additional data + * from the memory and stun the CPU is made visible. Note that + * partial DMA lines may not appear. + */ + bool markDMALines; + + +private: + + /*! @brief Event pipeline + * @details If a time delayed event needs to be performed, a flag is set + * inside this variable and executed at the beginning of the next + * cycle. + * @see processDelayedActions() + */ + uint64_t delay; + + + // + // Screen buffers and colors + // + +private: + + /*! @brief Currently used RGBA values for all sixteen C64 colors + * @see updatePalette() + */ + uint32_t rgbaTable[16]; + + /*! @brief First screen buffer + * @details The VIC chip writes its output into this buffer. The contents + * of the array is later copied into to texture RAM of your + * graphic card by the drawRect method in the GPU related code. + */ + int *screenBuffer1 = new int[PAL_RASTERLINES * NTSC_PIXELS]; + + /*! @brief Second screen buffer + * @details The VIC chip uses double buffering. Once a frame is drawn, the + * VIC chip writes the next frame to the second buffer. + */ + int *screenBuffer2 = new int [PAL_RASTERLINES * NTSC_PIXELS]; + + /*! @brief Target screen buffer for all rendering methods + * @details The variable points either to screenBuffer1 or screenBuffer2 + */ + int *currentScreenBuffer; + + /*! @brief Pointer to the beginning of the current rasterline + * @details This pointer is used by all rendering methods to write pixels. + * It always points to the beginning of a rasterline, either in + * screenBuffer1 or screenBuffer2. It is reset at the beginning + * of each frame and incremented at the beginning of each + * rasterline. + */ + int *pixelBuffer; + + /*! @brief Z buffer + * @details Depth buffering is used to determine pixel priority. In the + * various render routines, a color value is only retained, if it + * is closer to the view point. The depth of the closest pixel is + * kept in the z buffer. The lower the value, the closer it is to + * the viewer. + */ + uint8_t zBuffer[8]; + + /*! @brief Indicates the source of a drawn pixel + * @details Whenever a foreground pixel or sprite pixel is drawn, a + * distinct bit in the pixelSource array is set. The information + * is needed to detect sprite-sprite and sprite-background + * collisions. + */ + uint16_t pixelSource[8]; + + /*! @brief Offset into pixelBuffer + * @details Variable points to the first pixel of the currently drawn 8 + * pixel chunk. + */ + short bufferoffset; + + /*! @brief This is where loadColors() stores all retrieved colors + * @details [0] : color for '0' pixels in single color mode + * or '00' pixels in multicolor mode + * [1] : color for '1' pixels in single color mode + * or '01' pixels in multicolor mode + * [2] : color for '10' pixels in multicolor mode + * [3] : color for '11' pixels in multicolor mode + */ + uint8_t col[4]; + +public: + + //! @brief Constructor + VIC(); + + //! @brief Destructor + ~VIC(); + + //! @brief Methods from VirtualComponent + void reset(); + void setC64(C64 *c64); + void ping(); + void dump(); + size_t stateSize(); + void didLoadFromBuffer(uint8_t **buffer); + void didSaveToBuffer(uint8_t **buffer); + + + // + //! @functiongroup Accessing chip model related properties + // + + //! @brief Returns the currently plugged in chip model. + VICModel getModel() { return model; } + + //! @brief Sets the chip model. + void setModel(VICModel m); + + //! @brief Returns the currently used palette type. + VICPalette videoPalette() { return palette; } + + //! @brief Sets the palette type. + void setVideoPalette(VICPalette type); + + //! @brief Returns the emulated glue logic type. + GlueLogic getGlueLogic() { return glueLogic; } + + //! @brief Sets the glue logic type. + void setGlueLogic(GlueLogic type); + + //! @brief Returns true if a PAL chip is plugged in. + bool isPAL() { return model & (PAL_6569_R1 | PAL_6569_R3 | PAL_8565); } + + //! @brief Returns true if a NTSC chip is plugged in. + bool isNTSC() { return model & (NTSC_6567 | NTSC_6567_R56A | NTSC_8562); } + + //! @brief Returns true if a newer MOS 856x chip is plugged in. + bool is856x() { return model & (PAL_8565 | NTSC_8562); } + + //! @brief Returns true if an older MOS 656x chip is plugged in. + bool is656x() { return model & ~(PAL_8565 | NTSC_8562); } + + //! @brief Returns true if the emulated chip has the gray dot bug. + bool hasGrayDotBug() { return is856x(); } + + //! @brief Returns true if light pen interrupts are triggered with a delay. + bool delayedLightPenIrqs() { return model & (PAL_6569_R1 | NTSC_6567_R56A); } + + //! @brief Returns the clock frequencay of the selected VICII model. + unsigned getClockFrequency(); + + //! @brief Returns the number of CPU cycles performed per rasterline. + unsigned getCyclesPerRasterline(); + + //! @brief Returns true if the end of the rasterline has been reached. + bool isLastCycleInRasterline(unsigned cycle); + + //! @brief Returns the number of rasterlines drawn per frame. + unsigned getRasterlinesPerFrame(); + + //! @brief Returns true if rasterline belongs to the VBLANK area. + bool isVBlankLine(unsigned rasterline); + + //! @brief Returns the number of CPU cycles executed in one frame. + unsigned getCyclesPerFrame() { + return getRasterlinesPerFrame() * getCyclesPerRasterline(); } + + /*! @brief Returns the number of frames drawn per second. + * @note The result is returned as a floating point value, because + * Commodore did not manage to match the expected values exactly + * (50 Hz for PAL and 60 Hz for NTSC). E.g., a PAL C64 outputs + * 50.125 Hz. + */ + double getFramesPerSecond() { + return (double)getClockFrequency() / (double)getCyclesPerFrame(); + } + + //! @brief Returns the time interval between two frames in nanoseconds. + uint64_t getFrameDelay() { + return uint64_t(1000000000.0 / getFramesPerSecond()); + } + + + // + //! @functiongroup Accessing the screen buffer and display properties + // + + //! @brief Returns the currently stabel screen buffer. + void *screenBuffer(); + + //! @brief Initializes both screenBuffers + /*! @details This function is needed for debugging, only. It write some + * recognizable pattern into both buffers. + */ + void resetScreenBuffers(); + + /*! @brief Returns a C64 color from the current color palette. + * @return Color in 32 bit big endian RGBA format. + * @seealso updateColors + */ + uint32_t getColor(unsigned nr); + + /*! @brief Returns a C64 color from a specific color palette. + * @return Color in 32 bit big endian RGBA format. + */ + uint32_t getColor(unsigned nr, VICPalette palette); + + //! @brief Returns the brightness monitor parameter + double getBrightness() { return brightness; } + + //! @brief Sets the brightness monitor parameter + void setBrightness(double value); + + //! @brief Returns the contrast monitor parameter + double getContrast() { return contrast; } + + //! @brief Sets the contrast monitor parameter + void setContrast(double value); + + //! @brief Returns the saturation monitor parameter + double getSaturation() { return saturation; } + + //! @brief Sets the saturation monitor parameter + void setSaturation(double value); + +private: + + /*! @brief Updates the RGBA values for all sixteen C64 colors. + *! @details The base palette is determined by the selected VICII model. + */ + void updatePalette(); + + + // + //! @functiongroup Accessing memory (VIC_memory.cpp) + // + +public: + + //! @brief Peeks a value from a VIC register without side effects. + uint8_t spypeek(uint16_t addr); + + //! @brief Returns the ultimax flag + uint8_t getUltimax() { return ultimax; } + + //! @brief Sets the ultimax flag + void setUltimax(bool value); + + //! @brief Returns the latest value of the VICII's data bus during phi1. + uint8_t getDataBusPhi1() { return dataBusPhi1; } + + //! @brief Returns the latest value of the VICII's data bus during phi2. + uint8_t getDataBusPhi2() { return dataBusPhi2; } + + //! @brief Schedules the VICII bank to to switched + /*! @details This method is called if the bank switch is triggered by a + * change of register CIA2::PA or register CIA2::DDRA. + */ + void switchBank(uint16_t addr); + + //! @brief Schedules the VICII bank to to switched + /*! @details This method is called if the bank switch is triggered by a + * change of register CIA2::DDRA. + */ + // void switchBankDDRA(); + +private: + + //! @brief Updates the VICII bank address + /*! @details The new address is computed from the provided bank number + */ + void updateBankAddr(uint2_t bank) { assert(is_uint2_t(bank)); bankAddr = bank << 14; } + + //! @brief Updates the VICII bank address + /*! @details The new address is computed from the bits in CIA2::PA. + */ + void updateBankAddr(); + + //! @brief Peeks a value from a VIC register. + uint8_t peek(uint16_t addr); + + //! @brief Pokes a value into a VIC register. + void poke(uint16_t addr, uint8_t value); + + //! @brief Simulates a memory access via the address and data bus. + uint8_t memAccess(uint16_t addr); + + //! @brief Same as memAccess without side effects. + uint8_t memSpyAccess(uint16_t addr); + + //! @brief Returns true if memAccess will read from Character ROM + bool isCharRomAddr(uint16_t addr); + + /*! @brief Performs a DRAM refresh (r-access). + * @details r-accesses are performed in cycles 11 - 15 during phi1. + */ + void rAccess() { dataBusPhi1 = memAccess(0x3F00 | refreshCounter--); } + + /*! @brief Performs an idle access (i-access). + * @details Idle accesses are performed during phi1 if VICII needs no + * data. + * During an idle access, VICII reads from $3FFF, $7FFF, $BFFF, + * or $FFFF, depending on the selected memory bank. + */ + void iAccess() { dataBusPhi1 = memAccess(0x3FFF); } + + /*! @brief Performs a character access (c-access). + * @details During a c-access, the video matrix is read. + */ + void cAccess(); + + /*! @brief Performs a graphics access (g-access). + * @details During a g-access, graphics data (character or bitmap + * patterns) is read. + */ + void gAccess(); + + //! @brief Computes the g-access fetch address for newer VICIIs + uint16_t gAccessAddr85x(); + + //! @brief Computes the g-access fetch address for older VICIIs + uint16_t gAccessAddr65x(); + + /*! @brief Computes the g-access fetch address + * @details The fetch address is influences by both the BMM and ECM bit. + */ + uint16_t gAccessAddr(bool bmm, bool ecm); + + //! @brief Performs a sprite pointer access (p-access). + void pAccess(unsigned sprite); + + //! @brief Performs the first sprite data access. + void sFirstAccess(unsigned sprite); + + //! @brief Performs the second sprite data access. + void sSecondAccess(unsigned sprite); + + //! @brief Performs the third sprite data access. + void sThirdAccess(unsigned sprite); + + /*! @brief Finalizes the sprite data access + * @details This method is invoked one cycle after the second and third + * sprite DMA has occured. + */ + void sFinalize(unsigned sprite); + + + // + //! @functiongroup Handling the x and y counters + // + + /*! @brief Returns the current rasterline + * @note This value is not always identical to the yCounter, because + * the yCounter is incremented with a little delay. + */ + uint16_t rasterline(); + + //! @brief Returns the current rasterline cycle + uint8_t rastercycle(); + + /*! @brief Indicates if yCounter needs to be reset in this rasterline. + * @details PAL models reset the yCounter in cycle 2 in the first + * rasterline wheras NTSC models reset the yCounter in cycle 2 + * in the middle of the lower border area. + */ + bool yCounterOverflow() { return rasterline() == (isPAL() ? 0 : 238); } + + + // + //! @functiongroup Handling the border flip flops + // + + /* "Der VIC benutzt zwei Flipflops, um den Rahmen um das Anzeigefenster + * herum zu erzeugen: Ein Haupt-Rahmenflipflop und ein vertikales + * Rahmenflipflop. [...] + * + * The flip flops are switched according to the following rules: + * + * 1. If the X coordinate reaches the right comparison value, the main + * border flip flop is set. + * 2. If the Y coordinate reaches the bottom comparison value in cycle 63, + * the vertical border flip flop is set. + * 3. If the Y coordinate reaches the top comparison value in cycle 63 and + * the DEN bit in register $d011 is set, the vertical border flip flop + * is reset. + * 4. If the X coordinate reaches the left comparison value and the Y + * coordinate reaches the bottom one, the vertical border flip flop is + * set. + * 5. If the X coordinate reaches the left comparison value and the Y + * coordinate reaches the top one and the DEN bit in register $d011 is + * set, the vertical border flip flop is reset. + * 6. If the X coordinate reaches the left comparison value and the + * vertical border flip flop is not set, the main flip flop is reset." + * [C.B.] + */ + + /*! @brief Takes care of the vertical frame flipflop value. + * @details Invoked in each VIC II cycle + */ + void checkVerticalFrameFF(); + + //! @brief Checks frame fliplops at left border + void checkFrameFlipflopsLeft(uint16_t comparisonValue); + + //! @brief Checks frame fliplops at right border + void checkFrameFlipflopsRight(uint16_t comparisonValue); + + //! @brief Sets the vertical frame flipflop with a delay of one cycle. + void setVerticalFrameFF(bool value); + + //! @brief Sets the main frame flipflop with a delay of one cycle. + void setMainFrameFF(bool value); + + //! @brief Returns where the frame flipflop is checked for the left border. + uint16_t leftComparisonValue() { return isCSEL() ? 24 : 31; } + + //! @brief Returns where the frame flipflop is checked for the right border. + uint16_t rightComparisonValue() { return isCSEL() ? 344 : 335; } + + //! @brief Returns where the frame flipflop is checked for the upper border. + uint16_t upperComparisonValue() { return isRSEL() ? 51 : 55; } + + //! @brief Returns where the frame flipflop is checked for the lower border. + uint16_t lowerComparisonValue() { return isRSEL() ? 251 : 247; } + + + + + // + //! @functiongroup Querying the VICII registers + // + +public: + + /*! @brief Returns the current value of the DEN (Display ENabled) bit. + */ + bool DENbit() { return GET_BIT(reg.current.ctrl1, 4); } + + //! @brief Returns the number of the next interrupt rasterline. + uint16_t rasterInterruptLine() { + return ((reg.current.ctrl1 & 0x80) << 1) | rasterIrqLine; + } + + //! @brief Returns the masked CB13 bit. + uint8_t CB13() { return memSelect & 0x08; } + + //! @brief Returns the masked CB13/CB12/CB11 bits. + uint8_t CB13CB12CB11() { return memSelect & 0x0E; } + + //! @brief Returns the masked VM13/VM12/VM11/VM10 bits. + uint8_t VM13VM12VM11VM10() { return memSelect & 0xF0; } + + //! @brief Returns the state of the CSEL bit. + bool isCSEL() { return GET_BIT(reg.current.ctrl2, 3); } + + //! @brief Returns the state of the RSEL bit. + bool isRSEL() { return GET_BIT(reg.current.ctrl1, 3); } + + + // + //! @functiongroup Handling DMA lines and the display state + // + +private: + + //! @brief Returns true if the bad line condition holds. + bool badLineCondition(); + + + // + //! @functiongroup Interacting with the C64's CPU + // + +private: + + /*! @brief Sets the value of the BA line + * @details The BA line is connected to the CPU's RDY pin. + */ + void updateBA(uint8_t value); + + /*! @brief Indicates if a c-access can occur. + * @details A c-access can only be performed if the BA line is down for + * more than 2 cycles. + */ + bool BApulledDownForAtLeastThreeCycles() { return baLine.delayed(); } + + /*! @brief Triggers a VIC interrupt + * @param source is the interrupt source + * 1 : Rasterline interrupt + * 2 : Collision of a sprite with background pixels + * 4 : Collision between two sprites. + * 8 : Lightpen interrupt + */ + void triggerIrq(uint8_t source); + + + // + //! @functiongroup Handling lightpen events + // + +public: + + /*! @brief Sets the value of the LP pin + * @details The LP pin is connected to bit 4 of control port A. + * @seealso checkForLightpenIrq() + */ + void setLP(bool value); + +private: + + //! @brief Returns the X coordinate of a light pen event. + /*! @details The coordinate depends on the current rasterline cycle and + * differes slightly between the supported VICII models. + */ + uint16_t lightpenX(); + + //! @brief Returns the Y coordinate of a light pen event. + uint16_t lightpenY(); + + /*! @brief Trigger lightpen interrupt if conditions are met. + * @details This function is called on each negative transition of the + * LP pin. It latches the x and y coordinates and immediately + * triggers an interrupt if a newer VICII model is emulated. + * Older models trigger the interrupt later, at the beginning of + * a new frame. + * @seealso checkForLightpenIrqAtStartOfFrame() + */ + void checkForLightpenIrq(); + + /*! @brief Retriggers a lightpen interrupt if conditions are met. + * @details This function is called at the beginning of each frame. + * If the lp line is still low at this point of time, a lightpen + * interrupt is retriggered. Note that older VICII models trigger + * interrupts only at this point in time. + */ + void checkForLightpenIrqAtStartOfFrame(); + + + // + // Sprites + // + +private: + + /*! @brief Gets the depth of a sprite. + * @return depth value that can be written into the z buffer. + */ + uint8_t spriteDepth(uint8_t nr); + + /*! @brief Compares the Y coordinates of all sprites with the yCounter + * @return A bit pattern storing the result for each sprite. + */ + uint8_t compareSpriteY(); + + /*! @brief Turns off sprite dma if conditions are met. + * @details In cycle 16, the mcbase pointer is advanced three bytes for + * all dma enabled sprites. Advancing three bytes means that + * mcbase will then point to the next sprite line. When mcbase + * reached 63, all 21 sprite lines have been drawn and sprite dma + * is switched off. The whole operation is skipped when the y + * expansion flipflop is 0. This never happens for normal sprites + * (there is no skipping then), but happens every other cycle for + * vertically expanded sprites. Thus, mcbase advances for those + * sprites at half speed which actually causes the expansion. + */ + void turnSpriteDmaOff(); + + /*! @brief Turns on sprite dma accesses if conditions are met. + * @details This function is called in cycle 55 and cycle 56. + */ + void turnSpriteDmaOn(); + + /*! @brief Turns sprite display on or off. + * @details This function is called in cycle 58. + */ + void turnSpritesOnOrOff(); + + /*! @brief Loads a sprite shift register. + * @details The shift register is loaded with the three data bytes fetched + * in the previous sAccesses. + */ + void loadShiftRegister(unsigned nr) { + spriteSr[nr].data = LO_LO_HI(spriteSr[nr].chunk3, + spriteSr[nr].chunk2, + spriteSr[nr].chunk1); + } + + /*! @brief Updates the sprite shift registers. + * @details Checks if a sprite has completed it's last DMA fetch and + * calls loadShiftRegister accordingly. + */ + void updateSpriteShiftRegisters(); + + /*! @brief Toggles expansion flipflop for vertically stretched sprites. + * @details In cycle 56, register D017 is read and the flipflop gets + * inverted for all sprites with vertical stretching enabled. + * When the flipflop goes down, advanceMCBase() will have no + * effect in the next rasterline. This causes each sprite line + * to be drawn twice. + */ + void toggleExpansionFlipflop() { expansionFF ^= reg.current.sprExpandY; } + + + // + //! @functiongroup Running the device (VIC.cpp and VIC_cycles_xxx.cpp) + // + +public: + + /*! @brief Prepares VICII for drawing a new frame. + * @details This function is called prior to the first cycle of each frame. + */ + void beginFrame(); + + /*! @brief Prepares VICII for drawing a new rasterline. + * @details This function is called prior to the first cycle of each rasterline. + */ + void beginRasterline(uint16_t rasterline); + + /*! @brief Finishes up a rasterline. + * @details This function is called after the last cycle of each rasterline. + */ + void endRasterline(); + + /*! @brief Finishes up a frame. + * @details This function is called after the last cycle of each frame. + */ + void endFrame(); + + //! @brief Processes all time delayed actions. + /*! @details This function is called at the beginning of each VIC cycle. + */ + void processDelayedActions(); + + /*! @brief Executes a specific rasterline cycle + * @note The cycle specific actions differ depending on the selected + * chip model. + */ + void cycle1pal(); void cycle1ntsc(); + void cycle2pal(); void cycle2ntsc(); + void cycle3pal(); void cycle3ntsc(); + void cycle4pal(); void cycle4ntsc(); + void cycle5pal(); void cycle5ntsc(); + void cycle6pal(); void cycle6ntsc(); + void cycle7pal(); void cycle7ntsc(); + void cycle8pal(); void cycle8ntsc(); + void cycle9pal(); void cycle9ntsc(); + void cycle10pal(); void cycle10ntsc(); + void cycle11pal(); void cycle11ntsc(); + void cycle12(); + void cycle13(); + void cycle14(); + void cycle15(); + void cycle16(); + void cycle17(); + void cycle18(); + void cycle19to54(); + void cycle55pal(); void cycle55ntsc(); + void cycle56(); + void cycle57pal(); void cycle57ntsc(); + void cycle58pal(); void cycle58ntsc(); + void cycle59pal(); void cycle59ntsc(); + void cycle60pal(); void cycle60ntsc(); + void cycle61pal(); void cycle61ntsc(); + void cycle62pal(); void cycle62ntsc(); + void cycle63pal(); void cycle63ntsc(); + void cycle64ntsc(); + void cycle65ntsc(); + + #define DRAW_SPRITES if (spriteDisplay || isSecondDMAcycle) drawSprites(); + #define DRAW_SPRITES59 if (spriteDisplayDelayed || spriteDisplay || isSecondDMAcycle) drawSprites(); + + #define DRAW if (!vblank) draw(); DRAW_SPRITES; bufferoffset += 8; + #define DRAW17 if (!vblank) draw17(); DRAW_SPRITES; bufferoffset += 8; + #define DRAW55 if (!vblank) draw55(); DRAW_SPRITES; bufferoffset += 8; + #define DRAW59 if (!vblank) draw(); DRAW_SPRITES59; bufferoffset += 8; + #define DRAW_IDLE DRAW_SPRITES; +/* + #define DRAW_IDLE + for (unsigned i = 0; i < 8; i++) { zBuffer[i] = pixelSource[i] = 0; } \ + DRAW_SPRITES; +*/ + + #define C_ACCESS if (badLine) cAccess(); + + #define END_CYCLE \ + dataBusPhi2 = 0xFF; \ + xCounter += 8; \ + for (unsigned i = 0; i < 8; i++) { zBuffer[i] = pixelSource[i] = 0; } \ + if (unlikely(delay)) { processDelayedActions(); } + + #define END_VISIBLE_CYCLE \ + END_CYCLE + + /* + #define END_VISIBLE_CYCLE \ + // visibleColumnCnt++; \ + END_CYCLE + */ + + #define BA_LINE(x) updateBA(x); + + // + // Drawing routines (VIC_draw.cpp) + // + +private: + + /*! @brief Draws 8 pixels + * @details This is the main entry point to the VICII code and invoked in + * each drawing cycle. An exception are cycle 17 and cycle 55 + * which are handled seperately for speedup reasons. + */ + void draw(); + + //! @brief Special draw routine for cycle 17 + void draw17(); + + //! @brief Special draw routine for cycle 55 + void draw55(); + + + // + // Internal drawing routines (called by draw(), draw17(), and drae55()) + // + + /*! @brief Draws 8 border pixels + * @details Invoked inside draw() + */ + void drawBorder(); + + /*! @brief Draws the border pixels in cycle 17 + * @seealso draw17() + */ + void drawBorder17(); + + /*! @brief Draws the border pixels in cycle 55 + * @seealso draw55() + */ + void drawBorder55(); + + /*! @brief Draws 8 canvas pixels + * @seealso draw() + */ + void drawCanvas(); + + /*! @brief Draws a single canvas pixel + * @param pixel is the pixel number and must be in the range 0 to 7 + * @param mode is the display mode for this pixel + * @param d016 is the current value of register D016 + * @param loadShiftReg is true when the shift register needs to be + * reloaded + * @param updateColors is true when the four selectable colors + * need to be reloaded. + * @seealso drawCanvas() + */ + void drawCanvasPixel(uint8_t pixel, + uint8_t mode, + uint8_t d016, + bool loadShiftReg, + bool updateColors); + + /*! @brief Draws 8 sprite pixels + * @seealso draw() + */ + void drawSprites(); + + /*! @brief Draws a single sprite pixel for all sprites + * @param pixel Pixel number (0 to 7) + * @param enableBits are the spriteDisplay bits + * @param freezeBits If set to true, the sprites shift register will + * freeze temporarily + * @seealso drawSprites() + */ + void drawSpritePixel(unsigned pixel, + uint8_t enableBits, + uint8_t freezeBits); + + + // + // Mid level drawing (semantic pixel rendering) + // + + //! @brief Determines pixel colors accordig to the provided display mode + void loadColors(uint8_t mode); + + + // + // Low level drawing (pixel buffer access) + // + + //! @brief Writes a single color value into the screenbuffer + #define COLORIZE(pixel,color) \ + assert(bufferoffset + pixel < NTSC_PIXELS); \ + pixelBuffer[bufferoffset + pixel] = rgbaTable[color]; + + /*! @brief Sets a single frame pixel + *! @note The upper bit in pixelSource is cleared to prevent + * sprite/foreground collision detection in border area. + */ + #define SET_FRAME_PIXEL(pixel,color) { \ + COLORIZE(pixel, color); \ + zBuffer[pixel] = BORDER_LAYER_DEPTH; \ + pixelSource[pixel] &= (~0x100); } + + //! @brief Sets a single foreground pixel + #define SET_FOREGROUND_PIXEL(pixel,color) { \ + COLORIZE(pixel,color) \ + zBuffer[pixel] = FOREGROUND_LAYER_DEPTH; \ + pixelSource[pixel] = 0x100; } + + //! @brief Sets a single background pixel + #define SET_BACKGROUND_PIXEL(pixel,color) { \ + COLORIZE(pixel,color) \ + zBuffer[pixel] = BACKGROUD_LAYER_DEPTH; \ + pixelSource[pixel] = 0x00; } + + //! @brief Draw a single sprite pixel + void setSpritePixel(unsigned sprite, unsigned pixel, uint8_t color); + + /*! @brief Extend border to the left and right to look nice. + * @details This functions replicates the color of the leftmost and + * rightmost pixel + */ + void expandBorders(); + + /*! @brief Draw a horizontal colored line into the screen buffer + * @details This method is utilized for debugging purposes, only. + */ + void markLine(uint8_t color, unsigned start = 0, unsigned end = NTSC_PIXELS); + + + // + // The following functions are used by the GUI debugger, only + // + +public: + + // + //! @functiongroup Querying information (VIC_debug.cpp) + // + + //! @brief Gathers debug information. + VICInfo getInfo(); + + //! @brief Gathers debug information about a certain sprite. + SpriteInfo getSpriteInfo(unsigned i); + + + // + //! @functiongroup Thread-safe manipulation of the VICII state (VIC_debug.cpp) + // + + //! @brief Sets the memory bank start address + void setMemoryBankAddr(uint16_t addr); + + //! @brief Sets the screen memory address. + void setScreenMemoryAddr(uint16_t addr); + + //! @brief Sets the character memory address. + void setCharacterMemoryAddr(uint16_t addr); + + //! @brief Sets the display mode. + void setDisplayMode(DisplayMode m); + + //! @brief Sets the number of canvas rows. + void setNumberOfRows(unsigned rs); + + //! @brief Sets the number of canvas columns. + void setNumberOfColumns(unsigned cs); + + //! @brief Returns the current screen geometry. + ScreenGeometry getScreenGeometry(void); + + //! @brief Sets the current screen geometry. + void setScreenGeometry(ScreenGeometry mode); + + //! @brief Sets the vertical raster scroll offset. + void setVerticalRasterScroll(uint8_t offset); + + //! @brief Sets the horizontan raster scroll offset. + void setHorizontalRasterScroll(uint8_t offset); + + //! @brief Set interrupt rasterline + void setRasterInterruptLine(uint16_t line); + + //! @brief Enable or disable rasterline interrupts + void setRasterInterruptEnable(bool b); + + //! @brief Enable or disable rasterline interrupts + void toggleRasterInterruptFlag(); + + //! @brief Sets the color of a sprite. + void setSpriteColor(unsigned nr, uint8_t color); + + //! @brief Set the X coordinate of a sprite. + void setSpriteX(unsigned nr, uint16_t x); + + //! @brief Sets the Y coordinate of sprite. + void setSpriteY(unsigned nr, uint8_t y); + + //! @brief Sets the data source pointer of sprite. + void setSpritePtr(unsigned nr, uint8_t ptr); + + //! @brief Enables or disables a sprite. + void setSpriteEnabled(uint8_t nr, bool b); + + //! @brief Enables or disables a sprite. + void toggleSpriteEnabled(uint8_t nr); + + //! @brief Enables or disables IRQs on sprite/background collision + void setIrqOnSpriteBackgroundCollision(bool b); + + //! @brief Enables or disables IRQs on sprite/background collision + void toggleIrqOnSpriteBackgroundCollision(); + + //! @brief Enables or disables IRQs on sprite/sprite collision + void setIrqOnSpriteSpriteCollision(bool b); + + //! @brief Enables or disables IRQs on sprite/sprite collision + void toggleIrqOnSpriteSpriteCollision(); + + //! @brief Determines whether a sprite is drawn before or behind the scenary. + void setSpritePriority(unsigned nr, bool b); + + //! @brief Determines whether a sprite is drawn before or behind the scenary. + void toggleSpritePriority(unsigned nr); + + //! @brief Sets single color or multi color mode for sprite. + void setSpriteMulticolor(unsigned nr, bool b); + + //! @brief Switches between single color or multi color mode. + void toggleMulticolorFlag(unsigned nr); + + //! @brief Stretches or shrinks a sprite vertically. + void setSpriteStretchY(unsigned nr, bool b); + + //! @brief Stretches or shrinks a sprite vertically. + void spriteToggleStretchYFlag(unsigned nr); + + //! @brief Stretches or shrinks sprite horizontally. + void setSpriteStretchX(unsigned nr, bool b); + + //! @brief Stretches or shrinks sprite horizontally. + void spriteToggleStretchXFlag(unsigned nr); + + + // + //! @functiongroup Debugging and cheating (VIC_debug.cpp) + // + + //! @brief Shows or hides IRQ lines. + void setShowIrqLines(bool show); + + //! @brief Shows or hides DMA lines. + void setShowDmaLines(bool show); + + //! @brief Hides or shows sprites. + void setHideSprites(bool hide); + + //! @brief Enables or disables sprite-sprite collision detection. + void setSpriteSpriteCollisionFlag(bool b); + + //! @brief Toggles sprite-sprite collision detection. + void toggleSpriteSpriteCollisionFlag(); + + //! @brief Enables or disable sprite-background collision detection. + void setSpriteBackgroundCollisionFlag(bool b); + + //! @brief Toggles sprite-background collision detection. + void toggleSpriteBackgroundCollisionFlag(); +}; + +#endif + diff --git a/C64/VICII/VIC_colors.cpp b/C64/VICII/VIC_colors.cpp new file mode 100755 index 00000000..b7184ba1 --- /dev/null +++ b/C64/VICII/VIC_colors.cpp @@ -0,0 +1,464 @@ +/*! + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann, 2018 + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This implementation is mainly based on the following articles by pepto: +// http://www.pepto.de/projects/colorvic/ +// http://unusedino.de/ec64/technical/misc/vic656x/colors/ + +#include "VIC.h" + +double gammaCorrect(double value, double source, double target) +{ + // Reverse gamma correction of source + double factor = pow(255.0, 1.0 - source); + value = MIN(MAX(factor * pow(value, source), 0), 255); + + // Correct gamma for target + factor = pow(255.0, 1.0 - (1.0 / target)); + value = MIN(MAX(factor * pow(value, 1 / target), 0), 255); + + return round(value); +} + +uint32_t +VIC::getColor(unsigned nr) +{ + assert(nr < 16); + return rgbaTable[nr]; +} + +uint32_t +VIC::getColor(unsigned nr, VICPalette palette) +{ + double y, u, v; + + // LUMA levels (varies between VICII models) + #define LUMA_VICE(x,y,z) ((double)(x - y) * 256)/((double)(z - y)) + #define LUMA_COLORES(x) (x * 7.96875) + + double luma_vice_6569_r1[16] = { /* taken from VICE 3.2 */ + LUMA_VICE( 630,630,1850), LUMA_VICE(1850,630,1850), + LUMA_VICE( 900,630,1850), LUMA_VICE(1560,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE(1260,630,1850), + LUMA_VICE( 900,630,1850), LUMA_VICE(1560,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE( 900,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE( 900,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE(1560,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE(1560,630,1850) + }; + + double luma_vice_6569_r3[16] = { /* taken from VICE 3.2 */ + LUMA_VICE( 700,700,1850), LUMA_VICE(1850,700,1850), + LUMA_VICE(1090,700,1850), LUMA_VICE(1480,700,1850), + LUMA_VICE(1180,700,1850), LUMA_VICE(1340,700,1850), + LUMA_VICE(1020,700,1850), LUMA_VICE(1620,700,1850), + LUMA_VICE(1180,700,1850), LUMA_VICE(1020,700,1850), + LUMA_VICE(1340,700,1850), LUMA_VICE(1090,700,1850), + LUMA_VICE(1300,700,1850), LUMA_VICE(1620,700,1850), + LUMA_VICE(1300,700,1850), LUMA_VICE(1480,700,1850), + }; + + double luma_vice_6567[16] = { /* taken from VICE 3.2 */ + LUMA_VICE( 590,590,1825), LUMA_VICE(1825,590,1825), + LUMA_VICE( 950,590,1825), LUMA_VICE(1380,590,1825), + LUMA_VICE(1030,590,1825), LUMA_VICE(1210,590,1825), + LUMA_VICE( 860,590,1825), LUMA_VICE(1560,590,1825), + LUMA_VICE(1030,590,1825), LUMA_VICE( 860,590,1825), + LUMA_VICE(1210,590,1825), LUMA_VICE( 950,590,1825), + LUMA_VICE(1160,590,1825), LUMA_VICE(1560,590,1825), + LUMA_VICE(1160,590,1825), LUMA_VICE(1380,590,1825) + }; + + double luma_vice_6567_r65a[16] = { /* taken from VICE 3.2 */ + LUMA_VICE( 560,560,1825), LUMA_VICE(1825,560,1825), + LUMA_VICE( 840,560,1825), LUMA_VICE(1500,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE(1180,560,1825), + LUMA_VICE( 840,560,1825), LUMA_VICE(1500,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE( 840,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE( 840,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE(1500,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE(1500,560,1825), + }; + + double luma_pepto[16] = { /* taken from Pepto's Colodore palette */ + LUMA_COLORES(0), LUMA_COLORES(32), + LUMA_COLORES(10), LUMA_COLORES(20), + LUMA_COLORES(12), LUMA_COLORES(16), + LUMA_COLORES(8), LUMA_COLORES(24), + LUMA_COLORES(12), LUMA_COLORES(8), + LUMA_COLORES(16), LUMA_COLORES(10), + LUMA_COLORES(15), LUMA_COLORES(24), + LUMA_COLORES(15), LUMA_COLORES(20) + }; + + double *luma; + switch(model) { + case PAL_6569_R1: + luma = luma_vice_6569_r1; + break; + case PAL_6569_R3: + luma = luma_vice_6569_r3; + break; + case NTSC_6567: + luma = luma_vice_6567; + break; + case NTSC_6567_R56A: + luma = luma_vice_6567_r65a; + break; + case PAL_8565: + case NTSC_8562: + luma = luma_pepto; + break; + default: + assert(false); + } + + // Angles in the color plane + + #define ANGLE_PEPTO(x) (x * 22.5 * M_PI / 180.0) + #define ANGLE_COLORES(x) ((x * 22.5 + 11.5) * M_PI / 180.0) + + // Pepto's first approach + // http://unusedino.de/ec64/technical/misc/vic656x/colors/ + + /* + double angle[16] = { + NAN, NAN, + ANGLE_PEPTO(5), ANGLE_PEPTO(13), + ANGLE_PEPTO(2), ANGLE_PEPTO(10), + ANGLE_PEPTO(0), ANGLE_PEPTO(8), + ANGLE_PEPTO(6), ANGLE_PEPTO(7), + ANGLE_PEPTO(5), NAN, + NAN, ANGLE_PEPTO(10), + ANGLE_PEPTO(0), NAN + }; + */ + + // Pepto's second approach + // http://www.pepto.de/projects/colorvic/ + + double angle[16] = { + NAN, NAN, + ANGLE_COLORES(4), ANGLE_COLORES(12), + ANGLE_COLORES(2), ANGLE_COLORES(10), + ANGLE_COLORES(15), ANGLE_COLORES(7), + ANGLE_COLORES(5), ANGLE_COLORES(6), + ANGLE_COLORES(4), NAN, + NAN, ANGLE_COLORES(10), + ANGLE_COLORES(15), NAN + }; + + // + // Compute YUV values (adapted from Pepto) + // + + // Normalize + double brightness = this->brightness - 50.0; + double contrast = this->contrast / 100.0 + 0.2; + double saturation = this->saturation / 1.25; + + // debug("bri = %f con = %f sat = %f\n", brightness, contrast, saturation); + + // Compute Y, U, and V + double ang = angle[nr]; + y = luma[nr]; + u = isnan(ang) ? 0 : cos(ang) * saturation; + v = isnan(ang) ? 0 : sin(ang) * saturation; + + // Apply brightness and contrast + y *= contrast; + u *= contrast; + v *= contrast; + y += brightness; + // debug("%d: angle = %f y = %f u = %f v = %f\n", i, angle[i], y, u, v); + + // Translate to monochrome if applicable + switch(palette) { + + case BLACK_WHITE_PALETTE: + u = 0.0; + v = 0.0; + break; + + case PAPER_WHITE_PALETTE: + u = -128.0 + 120.0; + v = -128.0 + 133.0; + break; + + case GREEN_PALETTE: + u = -128.0 + 29.0; + v = -128.0 + 64.0; + break; + + case AMBER_PALETTE: + u = -128.0 + 24.0; + v = -128.0 + 178.0; + break; + + case SEPIA_PALETTE: + u = -128.0 + 97.0; + v = -128.0 + 154.0; + break; + + default: + assert(palette == COLOR_PALETTE); + } + + // Convert YUV value to RGB + double r = y + 1.140 * v; + double g = y - 0.396 * u - 0.581 * v; + double b = y + 2.029 * u; + r = MAX(MIN(r, 255), 0); + g = MAX(MIN(g, 255), 0); + b = MAX(MIN(b, 255), 0); + // debug("%d: r = %f g = %f b = %f\n", i, r, g, b); + + // Apply Gamma correction for PAL models + if (isPAL()) { + r = gammaCorrect(r, 2.8, 2.2); + g = gammaCorrect(g, 2.8, 2.2); + b = gammaCorrect(b, 2.8, 2.2); + } + + return LO_LO_HI_HI((uint8_t)r, (uint8_t)g, (uint8_t)b, 0xFF); +} + +void +VIC::setBrightness(double value) +{ + brightness = value; + updatePalette(); +} + +void +VIC::setContrast(double value) +{ + contrast = value; + updatePalette(); +} + +void +VIC::setSaturation(double value) +{ + saturation = value; + updatePalette(); +} + +void +VIC::updatePalette() +{ +#if 0 + double y[16], u[16], v[16]; + + // LUMA levels (varies between VICII models) + #define LUMA_VICE(x,y,z) ((double)(x - y) * 256)/((double)(z - y)) + #define LUMA_COLORES(x) (x * 7.96875) + + double luma_vice_6569_r1[16] = { /* taken from VICE 3.2 */ + LUMA_VICE( 630,630,1850), LUMA_VICE(1850,630,1850), + LUMA_VICE( 900,630,1850), LUMA_VICE(1560,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE(1260,630,1850), + LUMA_VICE( 900,630,1850), LUMA_VICE(1560,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE( 900,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE( 900,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE(1560,630,1850), + LUMA_VICE(1260,630,1850), LUMA_VICE(1560,630,1850) + }; + + double luma_vice_6569_r3[16] = { /* taken from VICE 3.2 */ + LUMA_VICE( 700,700,1850), LUMA_VICE(1850,700,1850), + LUMA_VICE(1090,700,1850), LUMA_VICE(1480,700,1850), + LUMA_VICE(1180,700,1850), LUMA_VICE(1340,700,1850), + LUMA_VICE(1020,700,1850), LUMA_VICE(1620,700,1850), + LUMA_VICE(1180,700,1850), LUMA_VICE(1020,700,1850), + LUMA_VICE(1340,700,1850), LUMA_VICE(1090,700,1850), + LUMA_VICE(1300,700,1850), LUMA_VICE(1620,700,1850), + LUMA_VICE(1300,700,1850), LUMA_VICE(1480,700,1850), + }; + + double luma_vice_6567[16] = { /* taken from VICE 3.2 */ + LUMA_VICE( 590,590,1825), LUMA_VICE(1825,590,1825), + LUMA_VICE( 950,590,1825), LUMA_VICE(1380,590,1825), + LUMA_VICE(1030,590,1825), LUMA_VICE(1210,590,1825), + LUMA_VICE( 860,590,1825), LUMA_VICE(1560,590,1825), + LUMA_VICE(1030,590,1825), LUMA_VICE( 860,590,1825), + LUMA_VICE(1210,590,1825), LUMA_VICE( 950,590,1825), + LUMA_VICE(1160,590,1825), LUMA_VICE(1560,590,1825), + LUMA_VICE(1160,590,1825), LUMA_VICE(1380,590,1825) + }; + + double luma_vice_6567_r65a[16] = { /* taken from VICE 3.2 */ + LUMA_VICE( 560,560,1825), LUMA_VICE(1825,560,1825), + LUMA_VICE( 840,560,1825), LUMA_VICE(1500,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE(1180,560,1825), + LUMA_VICE( 840,560,1825), LUMA_VICE(1500,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE( 840,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE( 840,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE(1500,560,1825), + LUMA_VICE(1180,560,1825), LUMA_VICE(1500,560,1825), + }; + + double luma_pepto[16] = { /* taken from Pepto's Colodore palette */ + LUMA_COLORES(0), LUMA_COLORES(32), + LUMA_COLORES(10), LUMA_COLORES(20), + LUMA_COLORES(12), LUMA_COLORES(16), + LUMA_COLORES(8), LUMA_COLORES(24), + LUMA_COLORES(12), LUMA_COLORES(8), + LUMA_COLORES(16), LUMA_COLORES(10), + LUMA_COLORES(15), LUMA_COLORES(24), + LUMA_COLORES(15), LUMA_COLORES(20) + }; + + double *luma; + switch(model) { + case PAL_6569_R1: + luma = luma_vice_6569_r1; + break; + case PAL_6569_R3: + luma = luma_vice_6569_r3; + break; + case NTSC_6567: + luma = luma_vice_6567; + break; + case NTSC_6567_R56A: + luma = luma_vice_6567_r65a; + break; + case PAL_8565: + case NTSC_8562: + luma = luma_pepto; + break; + default: + assert(false); + } + + // Angles in the color plane + + #define ANGLE_PEPTO(x) (x * 22.5 * M_PI / 180.0) + #define ANGLE_COLORES(x) ((x * 22.5 + 11.5) * M_PI / 180.0) + + // Pepto's first approach + // http://unusedino.de/ec64/technical/misc/vic656x/colors/ + + // Pepto's second approach + // http://www.pepto.de/projects/colorvic/ + + double angle[16] = { + NAN, NAN, + ANGLE_COLORES(4), ANGLE_COLORES(12), + ANGLE_COLORES(2), ANGLE_COLORES(10), + ANGLE_COLORES(15), ANGLE_COLORES(7), + ANGLE_COLORES(5), ANGLE_COLORES(6), + ANGLE_COLORES(4), NAN, + NAN, ANGLE_COLORES(10), + ANGLE_COLORES(15), NAN + }; + + // + // Compute YUV values (adapted from Pepto) + // + + // Normalize + double brightness = this->brightness - 50.0; + double contrast = this->contrast / 100.0 + 0.2; + double saturation = this->saturation / 1.25; + + // debug("bri = %f con = %f sat = %f\n", brightness, contrast, saturation); + + // Compute all sixteen colors + for (unsigned i = 0; i < 16; i++) { + + // Compute Y, U, and V + double ang = angle[i]; + y[i] = luma[i]; + u[i] = isnan(ang) ? 0 : cos(ang) * saturation; + v[i] = isnan(ang) ? 0 : sin(ang) * saturation; + + // Apply brightness and contrast + y[i] *= contrast; + u[i] *= contrast; + v[i] *= contrast; + y[i] += brightness; + // debug("%d: angle = %f y = %f u = %f v = %f\n", i, angle[i], y, u, v); + } + + // Translate to monochrome if applicable + for (unsigned i = 0; i < 16; i++) { + + switch(palette) { + + case BLACK_WHITE_PALETTE: + u[i] = 0.0; + v[i] = 0.0; + break; + + case PAPER_WHITE_PALETTE: + u[i] = -128.0 + 120.0; + v[i] = -128.0 + 133.0; + break; + + case GREEN_PALETTE: + u[i] = -128.0 + 29.0; + v[i] = -128.0 + 64.0; + break; + + case AMBER_PALETTE: + u[i] = -128.0 + 24.0; + v[i] = -128.0 + 178.0; + break; + + case SEPIA_PALETTE: + u[i] = -128.0 + 97.0; + v[i] = -128.0 + 154.0; + break; + + default: + assert(palette == COLOR_PALETTE); + } + } + + // Convert YUV values to RGB + for (unsigned i = 0; i < 16; i++) { + + double r = y[i] + 1.140 * v[i]; + double g = y[i] - 0.396 * u[i] - 0.581 * v[i]; + double b = y[i] + 2.029 * u[i]; + r = MAX(MIN(r, 255), 0); + g = MAX(MIN(g, 255), 0); + b = MAX(MIN(b, 255), 0); + // debug("%d: r = %f g = %f b = %f\n", i, r, g, b); + + // Apply Gamma correction for PAL models + if (isPAL()) { + r = gammaCorrect(r, 2.8, 2.2); + g = gammaCorrect(g, 2.8, 2.2); + b = gammaCorrect(b, 2.8, 2.2); + } + + // Store result + uint32_t rgba = LO_LO_HI_HI((uint8_t)r, (uint8_t)g, (uint8_t)b, 0xFF); + rgbaTable[i] = rgba; + } +#endif + + for (unsigned i = 0; i < 16; i++) { + rgbaTable[i] = getColor(i, palette); + } +} + + diff --git a/C64/VICII/VIC_cycles_ntsc.cpp b/C64/VICII/VIC_cycles_ntsc.cpp new file mode 100755 index 00000000..2962049f --- /dev/null +++ b/C64/VICII/VIC_cycles_ntsc.cpp @@ -0,0 +1,518 @@ +/*! + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +VIC::cycle1ntsc() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(3); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + if (verticalFrameFFsetCond) { + setVerticalFrameFF(true); + } + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(3); + + // Phi2.1 Rasterline interrupt (edge triggered) + bool edgeOnYCounter = (c64->rasterLine != 0); + bool edgeOnIrqCond = (yCounter == rasterInterruptLine() && !yCounterEqualsIrqRasterline); + if (edgeOnYCounter && edgeOnIrqCond) + triggerIrq(1); + yCounterEqualsIrqRasterline = (yCounter == rasterInterruptLine()); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR3 | SPR4 | SPR5)); + + END_CYCLE +} + +void +VIC::cycle2ntsc() +{ + // Check for lightpen IRQ in first rasterline + if (!lpLine && c64->rasterLine == 0) + checkForLightpenIrqAtStartOfFrame(); + + // Phi2.5 Fetch (previous cycle) + sThirdAccess(3); + + // Check for yCounter overflows + if (yCounterOverflow()) + yCounter = 0; + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(3); + pAccess(4); + + // Phi2.1 Rasterline interrupt (edge triggered) + bool edgeOnYCounter = (yCounter == 0); + bool edgeOnIrqCond = (yCounter == rasterInterruptLine() && !yCounterEqualsIrqRasterline); + if (edgeOnYCounter && edgeOnIrqCond) + triggerIrq(1); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR4 | SPR5)); + + END_CYCLE +} + +void +VIC::cycle3ntsc() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(4); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(4); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR4 | SPR5 | SPR6)); + + END_CYCLE +} + +void +VIC::cycle4ntsc() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(4); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(4); + pAccess(5); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR5 | SPR6)); + + END_CYCLE +} + +void +VIC::cycle5ntsc() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(5); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(5); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR5 | SPR6 | SPR7)); + + END_CYCLE +} + +void +VIC::cycle6ntsc() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(5); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(5); + pAccess(6); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR6 | SPR7)); + + END_CYCLE +} + +void +VIC::cycle7ntsc() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(6); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(6); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR6 | SPR7)); + + END_CYCLE +} + +void +VIC::cycle8ntsc() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(6); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(6); + pAccess(7); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & SPR7); + + END_CYCLE +} + +void +VIC::cycle9ntsc() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(7); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(7); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & SPR7); + + END_CYCLE +} + +void +VIC::cycle10ntsc() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(7); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(7); + iAccess(); + + // Phi2.4 BA logic + BA_LINE(false); + + END_CYCLE +} + +void +VIC::cycle11ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch (first out of five DRAM refreshs) + rAccess(); + + // Phi2.4 BA logic + BA_LINE(false); + + END_CYCLE +} + +void +VIC::cycle55ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch + gAccess(); + + // Phi2.2 Sprite logic + turnSpriteDmaOn(); + + // Phi2.4 BA logic + BA_LINE(false); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle57ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + checkFrameFlipflopsRight(344); + + // Phi1.2 Draw (border starts here) + DRAW + sr.canLoad = false; // Leaving canvas area + + // Phi1.3 Fetch + iAccess(); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & SPR0); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle58ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch + iAccess(); + + // Phi2.2 Sprite logic + spriteDisplayDelayed = spriteDisplay; + turnSpritesOnOrOff(); + + // Phi2.3 VC/RC logic + + /* "In the first phase of cycle 58, the VIC checks if RC=7. If so, the video + * logic goes to idle state and VCBASE is loaded from VC (VC->VCBASE)." + * [C.B.] + */ + if (rc == 7) { + displayState = badLine; + vcBase = vc; + } + + /* "If the video logic is in display state afterwards (this is always the + * case if there is a Bad Line Condition), RC is incremented." [C.B.] + */ + if (displayState) { + rc = (rc + 1) & 0x07; + } + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR0 | SPR1)); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle59ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW59 + + // Phi1.3 Fetch + pAccess(0); + + // Phi2.2 Sprite logic + spriteDisplayDelayed = spriteDisplay; + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR0 | SPR1)); + + // Phi2.5 Fetch + sFirstAccess(0); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle60ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch + sSecondAccess(0); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR0 | SPR1 | SPR2)); + + // Phi2.5 Fetch + sThirdAccess(0); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle61ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw the last visible column + DRAW + + // Phi1.3 Fetch + sFinalize(0); + pAccess(1); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR1 | SPR2)); + + // Phi2.5 Fetch + sFirstAccess(1); + + END_VISIBLE_CYCLE + isVisibleColumn = false; + // visibleColumnCnt = 0; +} + +void +VIC::cycle62ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(1); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR1 | SPR2 | SPR3)); + + // Phi2.5 Fetch + sThirdAccess(1); + + END_CYCLE +} + +void +VIC::cycle63ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(1); + pAccess(2); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR2 | SPR3)); + + // Phi2.5 Fetch + sFirstAccess(2); + + END_CYCLE +} + +void +VIC::cycle64ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + yCounterEqualsIrqRasterline = (yCounter == rasterInterruptLine()); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(2); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR2 | SPR3 | SPR4)); + + // Phi2.5 Fetch + sThirdAccess(2); + + END_CYCLE +} + +void +VIC::cycle65ntsc() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + yCounterEqualsIrqRasterline = (yCounter == rasterInterruptLine()); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(2); + pAccess(3); + + // Phi2.1 Rasterline interrupt + // Phi2.2 Sprite logic + // Phi2.3 VC/RC logic + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR3 | SPR4)); + + END_CYCLE +} + + + diff --git a/C64/VICII/VIC_cycles_pal.cpp b/C64/VICII/VIC_cycles_pal.cpp new file mode 100755 index 00000000..d052806e --- /dev/null +++ b/C64/VICII/VIC_cycles_pal.cpp @@ -0,0 +1,741 @@ +/*! + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann. All rights reserved. + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +/* All cycles are processed in this order: + * + * Phi2.5 Fetch (previous cycle) + * Phi1.1 Frame logic + * Phi1.2 Draw + * Phi1.3 Fetch + * Phi2.1 Rasterline interrupt + * Phi2.2 Sprite logic + * Phi2.3 VC/RC logic + * Phi2.4 BA logic + */ + +void +VIC::processDelayedActions() +{ + if (delay & VICUpdateIrqLine) { + if (irr & imr) { + c64->cpu.pullDownIrqLine(CPU::INTSRC_VIC); + } else { + c64->cpu.releaseIrqLine(CPU::INTSRC_VIC); + } + } + if (delay & VICUpdateFlipflops) { + flipflops.delayed = flipflops.current; + } + if (delay & VICSetDisplayState) { + displayState |= badLine; + } + if (delay & VICUpdateRegisters) { + reg.delayed = reg.current; + } + + // Less frequent actions + if (delay & (VICLpTransition | VICUpdateBankAddr | VICClrSprSprCollReg | VICClrSprBgCollReg)) { + + if (delay & VICLpTransition) { + checkForLightpenIrq(); + } + if (delay & VICUpdateBankAddr) { + updateBankAddr(); + // bankAddr = (~c64->cia2.getPA() & 0x03) << 14; + } + if (delay & VICClrSprSprCollReg) { + spriteSpriteCollision = 0; + } + if (delay & VICClrSprBgCollReg) { + spriteBackgroundColllision = 0; + } + } + + delay = (delay << 1) & VICClearanceMask; +} + +void +VIC::cycle1pal() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(2); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + if (verticalFrameFFsetCond) { + setVerticalFrameFF(true); + } + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(2); + pAccess(3); + + // Phi2.1 Rasterline interrupt (edge triggered) + bool edgeOnYCounter = (c64->rasterLine != 0); + bool edgeOnIrqCond = (yCounter == rasterInterruptLine() && !yCounterEqualsIrqRasterline); + if (edgeOnYCounter && edgeOnIrqCond) + triggerIrq(1); + yCounterEqualsIrqRasterline = (yCounter == rasterInterruptLine()); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR3 | SPR4)); + + END_CYCLE +} + +void +VIC::cycle2pal() +{ + // Check for lightpen IRQ in first rasterline + if (!lpLine && c64->rasterLine == 0) + checkForLightpenIrqAtStartOfFrame(); + + // Phi2.5 Fetch (previous cycle) + sFirstAccess(3); + + // Check for yCounter overflows + if (yCounterOverflow()) + yCounter = 0; + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(3); + + // Phi2.1 Rasterline interrupt (edge triggered) + bool edgeOnYCounter = (yCounter == 0); + bool edgeOnIrqCond = (yCounter == rasterInterruptLine() && !yCounterEqualsIrqRasterline); + if (edgeOnYCounter && edgeOnIrqCond) + triggerIrq(1); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR3 | SPR4 | SPR5)); + + END_CYCLE +} + +void +VIC::cycle3pal() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(3); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(3); + pAccess(4); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR4 | SPR5)); + + END_CYCLE +} + +void +VIC::cycle4pal() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(4); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(4); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR4 | SPR5 | SPR6)); + + END_CYCLE +} + + +void +VIC::cycle5pal() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(4); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(4); + pAccess(5); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR5 | SPR6)); + + END_CYCLE +} + +void +VIC::cycle6pal() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(5); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(5); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR5 | SPR6 | SPR7)); + + END_CYCLE +} + +void +VIC::cycle7pal() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(5); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(5); + pAccess(6); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR6 | SPR7)); + + END_CYCLE +} + +void +VIC::cycle8pal() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(6); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(6); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR6 | SPR7)); + + END_CYCLE +} + +void +VIC::cycle9pal() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(6); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(6); + pAccess(7); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & SPR7); + + END_CYCLE +} + +void +VIC::cycle10pal() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(7); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(7); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & SPR7); + + END_CYCLE +} + +void +VIC::cycle11pal() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(7); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch (first out of five DRAM refreshs) + sFinalize(7); + rAccess(); + + // Phi2.4 BA logic + BA_LINE(false); + + END_CYCLE +} + +void +VIC::cycle12() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch (second out of five DRAM refreshs) + rAccess(); + + // Phi2.4 BA logic + + /* "3. Liegt in den Zyklen 12-54 ein Bad-Line-Zustand vor, wird BA auf Low + gelegt und die c-Zugriffe gestartet. Einmal gestartet, findet in der + zweiten Phase jedes Taktzyklus im Bereich 15-54 ein c-Zugriff statt. Die + gelesenen Daten werden in der Videomatrix-/Farbzeile an der durch VMLI + angegebenen Position abgelegt. Bei jedem g-Zugriff im Display-Zustand + werden diese Daten ebenfalls an der durch VMLI spezifizierten Position + wieder intern gelesen." [C.B.] */ + + BA_LINE(badLine); + + END_CYCLE +} + +void +VIC::cycle13() // X Coordinate -3 - 4 (?) +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch (third out of five DRAM refreshs) + rAccess(); + + // Phi2.4 BA logic + BA_LINE(badLine); + + END_CYCLE +} + +void +VIC::cycle14() // SpriteX: 0 - 7 (?) +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw the first visible column + isVisibleColumn = true; + // visibleColumnCnt = 0; + DRAW + + // Phi1.3 Fetch (forth out of five DRAM refreshs) + rAccess(); + + // Phi2.3 VC/RC logic + + // "2. In der ersten Phase von Zyklus 14 jeder Zeile wird VC mit VCBASE geladen + // (VCBASE->VC) und VMLI gelöscht. Wenn zu diesem Zeitpunkt ein + // Bad-Line-Zustand vorliegt, wird zusätzlich RC auf Null gesetzt." [C.B.] + + vc = vcBase; + vmli = 0; + if (badLine) + rc = 0; + + // Phi2.4 BA logic + BA_LINE(badLine); + + END_VISIBLE_CYCLE + xCounter = 0; +} + +void +VIC::cycle15() // SpriteX: 8 - 15 (?) +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch (last DRAM refresh) + rAccess(); + + // Phi2.4 BA logic + BA_LINE(badLine); + + // Phi2.5 Fetch + C_ACCESS + + cleared_bits_in_d017 = 0; + END_VISIBLE_CYCLE +} + +void +VIC::cycle16() // SpriteX: 16 - 23 (?) +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch + gAccess(); + + // Phi2.2 Sprite logic + turnSpriteDmaOff(); + + // Phi2.4 BA logic + BA_LINE(badLine); + + // Phi2.5 Fetch + C_ACCESS + + END_VISIBLE_CYCLE +} + +void +VIC::cycle17() // SpriteX: 24 - 31 (?) +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + checkFrameFlipflopsLeft(24); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch + gAccess(); + + // Phi2.4 BA logic + BA_LINE(badLine); + + // Phi2.5 Fetch + C_ACCESS + + END_VISIBLE_CYCLE +} + +void +VIC::cycle18() // SpriteX: 32 - 39 +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + checkFrameFlipflopsLeft(31); + + // Phi1.2 Draw + sr.canLoad = true; // Entering canvas area + DRAW17 + + // Phi1.3 Fetch + gAccess(); + + // Phi2.4 BA logic + BA_LINE(badLine); + + // Phi2.5 Fetch + C_ACCESS + + END_VISIBLE_CYCLE +} + +void +VIC::cycle19to54() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch + gAccess(); + + // Phi2.4 BA logic + BA_LINE(badLine); + + // Phi2.5 Fetch + C_ACCESS + + END_VISIBLE_CYCLE +} + +void +VIC::cycle55pal() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch + gAccess(); + + // Phi2.2 Sprite logic + turnSpriteDmaOn(); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & SPR0); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle56() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + checkFrameFlipflopsRight(335); + + // Phi1.2 Draw + DRAW55 + + // Phi1.3 Fetch + iAccess(); + + // Phi2.2 Sprite logic + turnSpriteDmaOn(); + toggleExpansionFlipflop(); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & SPR0); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle57pal() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + checkFrameFlipflopsRight(344); + + // Phi1.2 Draw (border starts here) + DRAW + sr.canLoad = false; // Leaving canvas area + + // Phi1.3 Fetch + iAccess(); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR0 | SPR1)); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle58pal() +{ + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch + pAccess(0); + + // Phi2.2 Sprite logic + spriteDisplayDelayed = spriteDisplay; + turnSpritesOnOrOff(); + + // Phi2.3 VC/RC logic + + /* "In the first phase of cycle 58, the VIC checks if RC=7. If so, the video + * logic goes to idle state and VCBASE is loaded from VC (VC->VCBASE)." + * [C.B.] + */ + if (rc == 7) { + displayState = badLine; + vcBase = vc; + } + + /* "If the video logic is in display state afterwards (this is always the + * case if there is a Bad Line Condition), RC is incremented." [C.B.] + */ + if (displayState) { + rc = (rc + 1) & 0x07; + } + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR0 | SPR1)); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle59pal() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(0); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW59 + + // Phi1.3 Fetch + sSecondAccess(0); + + // Phi2.2 Sprite logic + spriteDisplayDelayed = spriteDisplay; + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR0 | SPR1 | SPR2)); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle60pal() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(0); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw + DRAW + + // Phi1.3 Fetch + sFinalize(0); + pAccess(1); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR1 | SPR2)); + + END_VISIBLE_CYCLE +} + +void +VIC::cycle61pal() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(1); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw the last visible column + DRAW + + // Phi1.3 Fetch + sSecondAccess(1); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR1 | SPR2 | SPR3)); + + END_VISIBLE_CYCLE + isVisibleColumn = false; + // visibleColumnCnt = 0; +} + +void +VIC::cycle62pal() +{ + // Phi2.5 Fetch (previous cycle) + sThirdAccess(1); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sFinalize(1); + pAccess(2); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR2 | SPR3)); + + END_CYCLE +} + +void +VIC::cycle63pal() +{ + // Phi2.5 Fetch (previous cycle) + sFirstAccess(2); + + // Phi1.1 Frame logic + checkVerticalFrameFF(); + yCounterEqualsIrqRasterline = (yCounter == rasterInterruptLine()); + + // Phi1.2 Draw sprites (invisible area) + DRAW_IDLE + + // Phi1.3 Fetch + sSecondAccess(2); + + // Phi2.4 BA logic + BA_LINE(spriteDmaOnOff & (SPR2 | SPR3 | SPR4)); + + END_CYCLE +} diff --git a/C64/VICII/VIC_debug.cpp b/C64/VICII/VIC_debug.cpp new file mode 100755 index 00000000..18b111d8 --- /dev/null +++ b/C64/VICII/VIC_debug.cpp @@ -0,0 +1,464 @@ +/*! + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann, 2018 + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +VICInfo +VIC::getInfo() +{ + VICInfo info; + + uint8_t ctrl1 = reg.current.ctrl1; + uint8_t ctrl2 = reg.current.ctrl2; + + info.rasterline = c64->rasterLine; + info.cycle = c64->rasterCycle; + info.xCounter = xCounter; + info.badLine = badLine; + info.ba = (baLine.current() == 0); + info.displayMode = (DisplayMode)((ctrl1 & 0x60) | (ctrl2 & 0x10)); + info.borderColor = reg.current.colors[COLREG_BORDER]; + info.backgroundColor0 = reg.current.colors[COLREG_BG0]; + info.backgroundColor1 = reg.current.colors[COLREG_BG1]; + info.backgroundColor2 = reg.current.colors[COLREG_BG2]; + info.backgroundColor3 = reg.current.colors[COLREG_BG3]; + info.screenGeometry = getScreenGeometry(); + info.dx = ctrl2 & 0x07; + info.dy = ctrl1 & 0x07; + info.verticalFrameFlipflop = flipflops.current.vertical; + info.horizontalFrameFlipflop = flipflops.current.main; + info.memoryBankAddr = bankAddr; + info.screenMemoryAddr = VM13VM12VM11VM10() << 6; + info.characterMemoryAddr = (CB13CB12CB11() << 10) % 0x4000; + info.imr = imr; + info.irr = irr; + info.spriteCollisionIrqEnabled = GET_BIT(imr, 2); + info.backgroundCollisionIrqEnabled = GET_BIT(imr, 1); + info.rasterIrqEnabled = GET_BIT(imr, 1); + info.irqRasterline = rasterInterruptLine(); + info.irqLine = (imr & irr) != 0; + + return info; +} + +SpriteInfo +VIC::getSpriteInfo(unsigned i) +{ + SpriteInfo info; + + info.enabled = GET_BIT(reg.current.sprEnable, i); + info.x = reg.current.sprX[i]; + info.y = reg.current.sprY[i]; + info.ptr = memSpyAccess((VM13VM12VM11VM10() << 6) | 0x03F8 | i); + info.color = reg.current.colors[COLREG_SPR0 + i]; + info.extraColor1 = reg.current.colors[COLREG_SPR_EX1]; + info.extraColor2 = reg.current.colors[COLREG_SPR_EX2]; + info.multicolor = GET_BIT(reg.current.sprMC, i); + info.expandX = GET_BIT(reg.current.sprExpandX, i); + info.expandY = GET_BIT(reg.current.sprExpandY, i); + info.priority = GET_BIT(reg.current.sprPriority, i); + info.collidesWithSprite = GET_BIT(spriteSpriteCollision, i); + info.collidesWithBackground = GET_BIT(spriteBackgroundColllision, i); + + return info; +} + +void +VIC::setMemoryBankAddr(uint16_t addr) +{ + assert(addr % 0x4000 == 0); + + suspend(); + bankAddr = addr; + resume(); +} + +void +VIC::setScreenMemoryAddr(uint16_t addr) +{ + assert((addr & ~0x3C00) == 0); + + suspend(); + addr >>= 6; + memSelect = (memSelect & ~0xF0) | (addr & 0xF0); + resume(); +} + +void +VIC::setCharacterMemoryAddr(uint16_t addr) +{ + assert((addr & ~0x3800) == 0); + + suspend(); + addr >>= 10; + memSelect = (memSelect & ~0x0E) | (addr & 0x0E); + resume(); +} + +void +VIC::setDisplayMode(DisplayMode m) +{ + suspend(); + reg.current.ctrl1 = (reg.current.ctrl1 & ~0x60) | (m & 0x60); + reg.current.ctrl2 = (reg.current.ctrl2 & ~0x10) | (m & 0x10); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setNumberOfRows(unsigned rs) +{ + assert(rs == 24 || rs == 25); + + suspend(); + uint8_t cntrl = reg.current.ctrl1; + WRITE_BIT(cntrl, 3, rs == 25); + poke(0x11, cntrl); + resume(); +} + +void +VIC::setNumberOfColumns(unsigned cs) +{ + assert(cs == 38 || cs == 40); + + suspend(); + uint8_t cntrl = reg.current.ctrl2; + WRITE_BIT(cntrl, 3, cs == 40); + poke(0x16, cntrl); + resume(); +} + +ScreenGeometry +VIC::getScreenGeometry(void) +{ + unsigned rows = GET_BIT(reg.current.ctrl1, 3) ? 25 : 24; + unsigned cols = GET_BIT(reg.current.ctrl2, 3) ? 40 : 38; + + if (cols == 40) { + return rows == 25 ? COL_40_ROW_25 : COL_40_ROW_24; + } else { + return rows == 25 ? COL_38_ROW_25 : COL_38_ROW_24; + } +} + +void +VIC::setScreenGeometry(ScreenGeometry mode) +{ + suspend(); + setNumberOfRows((mode == COL_40_ROW_25 || mode == COL_38_ROW_25) ? 25 : 24); + setNumberOfColumns((mode == COL_40_ROW_25 || mode == COL_40_ROW_24) ? 40 : 38); + resume(); +} + +void +VIC::setVerticalRasterScroll(uint8_t offset) +{ + assert(offset < 8); + + suspend(); + reg.current.ctrl1 = (reg.current.ctrl1 & 0xF8) | (offset & 0x07); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setHorizontalRasterScroll(uint8_t offset) +{ + assert(offset < 8); + + suspend(); + reg.current.ctrl2 = (reg.current.ctrl2 & 0xF8) | (offset & 0x07); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setRasterInterruptLine(uint16_t line) +{ + suspend(); + rasterIrqLine = line & 0xFF; + WRITE_BIT(reg.delayed.ctrl1, 7, line > 0xFF); + WRITE_BIT(reg.current.ctrl1, 7, line > 0xFF); + resume(); +} + +void +VIC::setRasterInterruptEnable(bool b) +{ + suspend(); + WRITE_BIT(imr, 1, b); + resume(); +} + +void +VIC::toggleRasterInterruptFlag() +{ + suspend(); + TOGGLE_BIT(imr, 1); + resume(); +} + + +// +// Sprites +// + +void +VIC::setSpriteX(unsigned nr, uint16_t x) +{ + assert(nr < 8); + x = MIN(x, 511); + + suspend(); + reg.current.sprX[nr] = x; + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setSpriteY(unsigned nr, uint8_t y) +{ + assert(nr < 8); + + suspend(); + reg.current.sprY[nr] = y; + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setSpritePtr(unsigned nr, uint8_t ptr) +{ + assert(nr < 8); + + debug("setSpritePtr(%d, %d)\n", nr, ptr); + + suspend(); + uint16_t addr = (VM13VM12VM11VM10() << 6) | 0x03F8 | nr; + c64->mem.ram[addr] = ptr; + resume(); +} + +void +VIC::setSpriteColor(unsigned nr, uint8_t color) +{ + assert(nr < 8); + + suspend(); + reg.current.colors[COLREG_SPR0 + nr] = color; + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setSpriteEnabled(uint8_t nr, bool b) +{ + suspend(); + WRITE_BIT(reg.current.sprEnable, nr, b); + resume(); +} + +void +VIC::toggleSpriteEnabled(uint8_t nr) +{ + suspend(); + TOGGLE_BIT(reg.current.sprEnable, nr); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setIrqOnSpriteBackgroundCollision(bool b) +{ + suspend(); + WRITE_BIT(imr, 1, b); + resume(); +} + +void +VIC::toggleIrqOnSpriteBackgroundCollision() +{ + suspend(); + TOGGLE_BIT(imr, 1); + resume(); +} + +void +VIC::setIrqOnSpriteSpriteCollision(bool b) +{ + suspend(); + WRITE_BIT(imr, 2, b); + resume(); +} + +void +VIC::toggleIrqOnSpriteSpriteCollision() +{ + suspend(); + TOGGLE_BIT(imr, 2); + resume(); +} + +void +VIC::setSpritePriority(unsigned nr, bool b) +{ + assert(nr < 8); + + suspend(); + WRITE_BIT(reg.current.sprPriority, nr, b); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::toggleSpritePriority(unsigned nr) +{ + assert(nr < 8); + + suspend(); + TOGGLE_BIT(reg.current.sprPriority, nr); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setSpriteMulticolor(unsigned nr, bool b) +{ + assert(nr < 8); + + suspend(); + WRITE_BIT(reg.current.sprMC, nr, b); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::toggleMulticolorFlag(unsigned nr) +{ + assert(nr < 8); + + suspend(); + TOGGLE_BIT(reg.current.sprMC, nr); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setSpriteStretchY(unsigned nr, bool b) +{ + assert(nr < 8); + + suspend(); + WRITE_BIT(reg.current.sprExpandY, nr, b); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::spriteToggleStretchYFlag(unsigned nr) +{ + assert(nr < 8); + + suspend(); + TOGGLE_BIT(reg.current.sprExpandY, nr); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setSpriteStretchX(unsigned nr, bool b) +{ + assert(nr < 8); + + suspend(); + WRITE_BIT(reg.current.sprExpandX, nr, b); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::spriteToggleStretchXFlag(unsigned nr) +{ + assert(nr < 8); + + suspend(); + TOGGLE_BIT(reg.current.sprExpandX, nr); + delay |= VICUpdateRegisters; + resume(); +} + +void +VIC::setShowIrqLines(bool show) +{ + suspend(); + markIRQLines = show; + resume(); +} + +void +VIC::setShowDmaLines(bool show) +{ + suspend(); + markDMALines = show; + resume(); +} + +void +VIC::setHideSprites(bool hide) +{ + suspend(); + hideSprites = hide; + resume(); +} + +void +VIC::setSpriteSpriteCollisionFlag(bool b) +{ + suspend(); + spriteSpriteCollisionEnabled = b; + resume(); +} + +void +VIC::toggleSpriteSpriteCollisionFlag() +{ + suspend(); + spriteSpriteCollisionEnabled = !spriteSpriteCollisionEnabled; + resume(); +} + +void +VIC::setSpriteBackgroundCollisionFlag(bool b) +{ + suspend(); + spriteBackgroundCollisionEnabled = b; + resume(); +} + +void +VIC::toggleSpriteBackgroundCollisionFlag() +{ + suspend(); + spriteBackgroundCollisionEnabled = !spriteBackgroundCollisionEnabled; + resume(); +} + + + diff --git a/C64/VICII/VIC_draw.cpp b/C64/VICII/VIC_draw.cpp new file mode 100755 index 00000000..91242c6b --- /dev/null +++ b/C64/VICII/VIC_draw.cpp @@ -0,0 +1,591 @@ +// +// PixelEngine.cpp +/* + * (C) 2015 Dirk W. Hoffmann. All rights reserved. + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +VIC::draw() +{ + drawCanvas(); + drawBorder(); +} + +void +VIC::draw17() +{ + drawCanvas(); + drawBorder17(); +} + +void +VIC::draw55() +{ + drawCanvas(); + drawBorder55(); +} + +void +VIC::drawBorder() +{ + if (flipflops.delayed.main) { + SET_FRAME_PIXEL(0, reg.delayed.colors[COLREG_BORDER]); + for (unsigned pixel = 1; pixel <= 7; pixel++) { + SET_FRAME_PIXEL(pixel, reg.current.colors[COLREG_BORDER]); + } + } +} + +void +VIC::drawBorder17() +{ + if (flipflops.delayed.main && !flipflops.current.main) { + + // 38 column mode (only pixels 0...6 are drawn) + SET_FRAME_PIXEL(0, reg.delayed.colors[COLREG_BORDER]); + for (unsigned pixel = 1; pixel <= 6; pixel++) { + SET_FRAME_PIXEL(pixel, reg.current.colors[COLREG_BORDER]); + } + + } else { + + // 40 column mode (all eight pixels are drawn) + drawBorder(); + } +} + +void +VIC::drawBorder55() +{ + if (!flipflops.delayed.main && flipflops.current.main) { + + // 38 column mode (border starts at pixel 7) + SET_FRAME_PIXEL(7, reg.delayed.colors[COLREG_BORDER]); + + } else { + + drawBorder(); + } +} + +void +VIC::drawCanvas() +{ + uint8_t d011, d016, newD016, mode, oldMode, xscroll; + + /* "The sequencer outputs the graphics data in every raster line in the area + * of the display column as long as the vertical border flip-flop is reset + * (see section 3.9.)." [C.B.] + */ + + if (flipflops.delayed.vertical) { + + /* "Outside of the display column and if the flip-flop is set, the last + * current background color is displayed (this area is normally covered + * by the border)." [C.B.] + */ + SET_BACKGROUND_PIXEL(0, col[0]); + for (unsigned pixel = 1; pixel < 8; pixel++) { + SET_BACKGROUND_PIXEL(pixel, col[0]); + } + return; + } + + /* "The graphics data sequencer is capable of 8 different graphics modes + * that are selected by the bits ECM, BMM and MCM (Extended Color Mode, + * Bit Map Mode and Multi Color Mode) in the registers $d011 and + * $d016." [C.B.] + */ + + d011 = reg.delayed.ctrl1; + d016 = reg.delayed.ctrl2; + xscroll = d016 & 0x07; + mode = (d011 & 0x60) | (d016 & 0x10); // -xxx ---- + + drawCanvasPixel(0, mode, d016, xscroll == 0, true); + + // After the first pixel, color register changes show up + reg.delayed.colors[COLREG_BG0] = reg.current.colors[COLREG_BG0]; + reg.delayed.colors[COLREG_BG1] = reg.current.colors[COLREG_BG1]; + reg.delayed.colors[COLREG_BG2] = reg.current.colors[COLREG_BG2]; + reg.delayed.colors[COLREG_BG3] = reg.current.colors[COLREG_BG3]; + + drawCanvasPixel(1, mode, d016, xscroll == 1, true); + drawCanvasPixel(2, mode, d016, xscroll == 2, false); + drawCanvasPixel(3, mode, d016, xscroll == 3, false); + + // After pixel 4, a change in D016 affects the display mode. + newD016 = reg.current.ctrl2; + + // In older VICIIs, the one bits of D011 show up, too. + if (is656x()) { + d011 |= reg.current.ctrl1; + } + oldMode = mode; + mode = (d011 & 0x60) | (newD016 & 0x10); + + drawCanvasPixel(4, mode, d016, xscroll == 4, oldMode != mode); + drawCanvasPixel(5, mode, d016, xscroll == 5, false); + + // In older VICIIs, the zero bits of D011 show up here. + if (is656x()) { + d011 = reg.current.ctrl1; + oldMode = mode; + mode = (d011 & 0x60) | (newD016 & 0x10); + } + + drawCanvasPixel(6, mode, d016, xscroll == 6, oldMode != mode); + + // Before the last pixel is drawn, a change is D016 is fully detected. + // If the multicolor bit get set, the mc flip flop is also reset. + if (d016 != newD016) { + if (RISING_EDGE(d016 & 0x10, newD016 & 0x10)) + sr.mcFlop = false; + d016 = newD016; + } + + drawCanvasPixel(7, mode, d016, xscroll == 7, false); +} + + +void +VIC::drawCanvasPixel(uint8_t pixel, + uint8_t mode, + uint8_t d016, + bool loadShiftReg, + bool updateColors) +{ + assert(pixel < 8); + + /* "The heart of the sequencer is an 8 bit shift register that is shifted by + * 1 bit every pixel and reloaded with new graphics data after every + * g-access. With XSCROLL from register $d016 the reloading can be delayed + * by 0-7 pixels, thus shifting the display up to 7 pixels to the right." + */ + if (loadShiftReg && sr.canLoad) { + + uint32_t result = gAccessResult.delayed(); + + // Load shift register + sr.data = BYTE0(result); + + // Remember how to synthesize pixels + sr.latchedCharacter = BYTE2(result); + sr.latchedColor = BYTE1(result); + + // Reset the multicolor synchronization flipflop + sr.mcFlop = true; + + // Make sure that colors get updated + updateColors = true; + + sr.remainingBits = 8; + } + + // Clear any outstanding multicolor bit that shouldn't actually be drawn + // TODO: VICE doesn't use a counter for this, but doesn't have the same issue, + // figure out what magic they are doing + if (!sr.remainingBits) { + sr.colorbits = 0; + } + + // Determine the render mode and the drawing mode for this pixel + bool multicolorDisplayMode = + (mode & 0x10) && ((mode & 0x20) || (sr.latchedColor & 0x8)); + + bool generateMulticolorPixel = + (d016 & 0x10) && ((mode & 0x20) || (sr.latchedColor & 0x8)); + + // Determine the colorbits + if (generateMulticolorPixel) { + if (sr.mcFlop) { + sr.colorbits = (sr.data >> 6) >> !multicolorDisplayMode; + } + } else { + sr.colorbits = (sr.data >> 7) << multicolorDisplayMode; + } + + // Load colors + if (updateColors) loadColors(mode); + + // Draw pixel + assert(sr.colorbits < 4); + if (multicolorDisplayMode) { + + // Set multi-color pixel + if (sr.colorbits & 0x02) { + SET_FOREGROUND_PIXEL(pixel, col[sr.colorbits]); + } else { + SET_BACKGROUND_PIXEL(pixel, col[sr.colorbits]); + } + + } else { + + // Set single-color pixel + if (sr.colorbits) { + SET_FOREGROUND_PIXEL(pixel, col[sr.colorbits]); + } else { + SET_BACKGROUND_PIXEL(pixel, col[sr.colorbits]); + } + } + + // Shift register and toggle multicolor flipflop + sr.data <<= 1; + sr.mcFlop = !sr.mcFlop; + sr.remainingBits -= 1; +} + +void +VIC::drawSprites() +{ + uint8_t firstDMA = isFirstDMAcycle; + uint8_t secondDMA = isSecondDMAcycle; + + // Pixel 0 + drawSpritePixel(0, spriteDisplayDelayed, secondDMA); + + // After the first pixel, color register changes show up + reg.delayed.colors[COLREG_SPR_EX1] = reg.current.colors[COLREG_SPR_EX1]; + reg.delayed.colors[COLREG_SPR_EX2] = reg.current.colors[COLREG_SPR_EX2]; + for (unsigned i = 0; i < 8; i++) { + reg.delayed.colors[COLREG_SPR0 + i] = reg.current.colors[COLREG_SPR0 + i]; + } + + // Pixel 1, Pixel 2, Pixel 3 + drawSpritePixel(1, spriteDisplayDelayed, secondDMA); + + // Stop shift register on the second DMA cycle + spriteSrActive &= ~secondDMA; + + drawSpritePixel(2, spriteDisplayDelayed, secondDMA); + drawSpritePixel(3, spriteDisplayDelayed, firstDMA | secondDMA); + + // If a shift register is loaded, the new data appears here. + updateSpriteShiftRegisters(); + + // Pixel 4, Pixel 5 + drawSpritePixel(4, spriteDisplay, firstDMA | secondDMA); + drawSpritePixel(5, spriteDisplay, firstDMA | secondDMA); + + // Changes of the X expansion bits and the priority bits show up here + reg.delayed.sprExpandX = reg.current.sprExpandX; + reg.delayed.sprPriority = reg.current.sprPriority; + + // Update multicolor bits if a new VICII is emulated + uint8_t toggle = reg.delayed.sprMC ^ reg.current.sprMC; + if (toggle && is856x()) { + + // VICE: + // BYTE next_mc_bits = vicii.regs[0x1c]; + // BYTE toggled = next_mc_bits ^ sprite_mc_bits; + // sbuf_mc_flops ^= toggled & (~sbuf_expx_flops); + // sprite_mc_bits = next_mc_bits; + + reg.delayed.sprMC = reg.current.sprMC; + for (unsigned i = 0; i < 8; i++) { + if (GET_BIT(toggle,i)) + spriteSr[i].mcFlop ^= !spriteSr[i].expFlop; + } + } + + // Pixel 6 + drawSpritePixel(6, spriteDisplay, firstDMA | secondDMA); + + // Update multicolor bits if an old VICII is emulated + if (toggle && is656x()) { + + reg.delayed.sprMC = reg.current.sprMC; + for (unsigned i = 0; i < 8; i++) { + if (GET_BIT(toggle,i)) + spriteSr[i].mcFlop = 0; + } + } + + // Pixel 7 + drawSpritePixel(7, spriteDisplay, firstDMA); + + // Check for collisions + for (unsigned i = 0; i < 8; i++) { + + // Check if two or more bits are set in pixelSource + if (pixelSource[i] & (pixelSource[i] - 1)) { + + // Is it a sprite/sprite collision? + if ((pixelSource[i] & 0xFF) & ((pixelSource[i] & 0xFF) - 1)) { + + // Trigger an IRQ if this is the first detected collision + if (!spriteSpriteCollision) { + triggerIrq(4); + } + spriteSpriteCollision |= (pixelSource[i] & 0xFF); + } + + // Is it a sprite/background collision? + if ((pixelSource[i] & 0x100) && spriteBackgroundCollisionEnabled) { + + // Trigger an IRQ if this is the first detected collision + if (!spriteBackgroundColllision) { + triggerIrq(2); + } + spriteBackgroundColllision |= (pixelSource[i] & 0xFF); + } + } + } +} + +void +VIC::drawSpritePixel(unsigned pixel, + uint8_t enableBits, + uint8_t freezeBits) +{ + // Quick exit condition + if (!enableBits && !spriteSrActive) { + return; + } + + // Iterate over all sprites + for (unsigned sprite = 0; sprite < 8; sprite++) { + + bool enable = GET_BIT(enableBits, sprite); + bool freeze = GET_BIT(freezeBits, sprite); + bool mCol = GET_BIT(reg.delayed.sprMC, sprite); + bool xExp = GET_BIT(reg.delayed.sprExpandX, sprite); + bool active = GET_BIT(spriteSrActive, sprite); + + // If a sprite is enabled, activate it's shift register if the + // horizontal trigger condition holds. + if (enable) { + if (!active && xCounter + pixel == reg.delayed.sprX[sprite] && !freeze) { + + SET_BIT(spriteSrActive, sprite); + active = true; + spriteSr[sprite].expFlop = true; + spriteSr[sprite].mcFlop = true; + } + } + + // Run shift register if it is activated + if (active && !freeze) { + + // Only proceed if the expansion flipflop is set + if (spriteSr[sprite].expFlop) { + + // Extract color bits from the shift register + if (mCol) { + + // In multi-color mode, get 2 bits every second pixel + if (spriteSr[sprite].mcFlop) { + spriteSr[sprite].colBits = (spriteSr[sprite].data >> 22) & 0x03; + /* + debug("f: %d l: %d c: %d Sprite %d loads mc colBits: %02X\n", + c64->frame, c64->rasterLine, c64->rasterCycle - 1, sprite, spriteSr[sprite].colBits); + */ + } + spriteSr[sprite].mcFlop = !spriteSr[sprite].mcFlop; + + } else { + + // In single-color mode, get a new bit for each pixel + spriteSr[sprite].colBits = (spriteSr[sprite].data >> 22) & 0x02; + } + + // Perform the shift operation + spriteSr[sprite].data <<= 1; + + // Inactivate shift register if everything is pumped out + if (!spriteSr[sprite].data && !spriteSr[sprite].colBits) { + active = false; + CLR_BIT(spriteSrActive, sprite); + } + } + + // Toggle expansion flipflop for horizontally stretched sprites + /* + if (xExp) + spriteSr[sprite].expFlop = !spriteSr[sprite].expFlop; + else + spriteSr[sprite].expFlop = true; + */ + spriteSr[sprite].expFlop = !spriteSr[sprite].expFlop || !xExp; + } + + // Draw pixel + if (active && !hideSprites) { + + switch (spriteSr[sprite].colBits) { + + case 0x01: + setSpritePixel(sprite, pixel, reg.delayed.colors[COLREG_SPR_EX1]); + break; + + case 0x02: + setSpritePixel(sprite, pixel, reg.delayed.colors[COLREG_SPR0 + sprite]); + break; + + case 0x03: + setSpritePixel(sprite, pixel, reg.delayed.colors[COLREG_SPR_EX2]); + break; + } + } + } +} + +void +VIC::loadColors(uint8_t mode) +{ + uint8_t character = sr.latchedCharacter; + uint8_t color = sr.latchedColor; + + switch (mode) { + + case STANDARD_TEXT: + + col[0] = reg.delayed.colors[COLREG_BG0]; + col[1] = color; + break; + + case MULTICOLOR_TEXT: + + if (color & 0x8 /* MC flag */) { + + col[0] = reg.delayed.colors[COLREG_BG0]; + col[1] = reg.delayed.colors[COLREG_BG1]; + col[2] = reg.delayed.colors[COLREG_BG2]; + col[3] = color & 0x07; + + } else { + + col[0] = reg.delayed.colors[COLREG_BG0]; + col[1] = color; + + } + break; + + case STANDARD_BITMAP: + + col[0] = character & 0xF; + col[1] = character >> 4; + break; + + case MULTICOLOR_BITMAP: + + col[0] = reg.delayed.colors[COLREG_BG0]; + col[1] = character >> 4; + col[2] = character & 0x0F; + col[3] = color; + break; + + case EXTENDED_BACKGROUND_COLOR: + + col[0] = reg.delayed.colors[COLREG_BG0 + (character >> 6)]; + col[1] = color; + break; + + case INVALID_TEXT: + case INVALID_STANDARD_BITMAP: + case INVALID_MULTICOLOR_BITMAP: + + col[0] = 0; + col[1] = 0; + col[2] = 0; + col[3] = 0; + break; + + default: + + assert(0); + break; + } +} + + +// +// Low level drawing (pixel buffer access) +// + +void +VIC::setSpritePixel(unsigned sprite, unsigned pixel, uint8_t color) +{ + uint8_t depth = spriteDepth(sprite); + uint8_t source = (1 << sprite); + + if (depth <= zBuffer[pixel]) { + + /* "the interesting case is when eg sprite 1 and sprite 0 overlap, and + * sprite 0 has the priority bit set (and sprite 1 has not). in this + * case 10/11 background bits show in front of whole sprite 0." + * Test program: VICII/spritePriorities + */ + if (!(pixelSource[pixel] & 0xFF)) { + if (isVisibleColumn) COLORIZE(pixel, color); + zBuffer[pixel] = depth; + } + } + pixelSource[pixel] |= source; +} + +void +VIC::expandBorders() +{ + int color, lastX; + unsigned leftPixelPos; + unsigned rightPixelPos; + + if (c64->vic.isPAL()) { + leftPixelPos = PAL_LEFT_BORDER_WIDTH - (4*8); + rightPixelPos = PAL_LEFT_BORDER_WIDTH + PAL_CANVAS_WIDTH + (4*8) - 1; + lastX = PAL_PIXELS; + } else { + leftPixelPos = NTSC_LEFT_BORDER_WIDTH - (4*8); + rightPixelPos = NTSC_LEFT_BORDER_WIDTH + NTSC_CANVAS_WIDTH + (4*8) - 1; + lastX = NTSC_PIXELS; + } + + // Make picked pixels visible for debugging + // pixelBuffer[leftPixelPos + 1] = colors[5]; + // pixelBuffer[rightPixelPos - 1] = colors[5]; + + color = pixelBuffer[leftPixelPos]; + for (unsigned i = 0; i < leftPixelPos; i++) { + pixelBuffer[i] = color; + // pixelBuffer[i] = colors[5]; // for debugging + } + color = pixelBuffer[rightPixelPos]; + for (unsigned i = rightPixelPos+1; i < lastX; i++) { + pixelBuffer[i] = color; + // pixelBuffer[i] = colors[5]; // for debugging + } + + /* + // Draw grid lines + for (unsigned i = 0; i < NTSC_PIXELS; i += 10) + pixelBuffer[i] = 0xFFFFFFFF; + */ +} + +void +VIC::markLine(uint8_t color, unsigned start, unsigned end) +{ + assert (end <= NTSC_PIXELS); + + int rgba = rgbaTable[color]; + for (unsigned i = start; i < end; i++) { + pixelBuffer[start + i] = rgba; + } +} diff --git a/C64/VICII/VIC_memory.cpp b/C64/VICII/VIC_memory.cpp new file mode 100755 index 00000000..eec7ef47 --- /dev/null +++ b/C64/VICII/VIC_memory.cpp @@ -0,0 +1,1003 @@ +/*! + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright Dirk W. Hoffmann, 2018 + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "C64.h" + +void +VIC::setUltimax(bool value) { + + // For details, see: + // VIC memory mapping (http://www.harries.dk/files/C64MemoryMaps.pdf) + + ultimax = value; + + if (value) { + + memSrc[0x0] = M_RAM; + memSrc[0x1] = M_RAM; + memSrc[0x2] = M_RAM; + memSrc[0x3] = M_CRTHI; + memSrc[0x4] = M_RAM; + memSrc[0x5] = M_RAM; + memSrc[0x6] = M_RAM; + memSrc[0x7] = M_CRTHI; + memSrc[0x8] = M_RAM; + memSrc[0x9] = M_RAM; + memSrc[0xA] = M_RAM; + memSrc[0xB] = M_CRTHI; + memSrc[0xC] = M_RAM; + memSrc[0xD] = M_RAM; + memSrc[0xE] = M_RAM; + memSrc[0xF] = M_CRTHI; + + } else { + + memSrc[0x0] = M_RAM; + memSrc[0x1] = M_CHAR; + memSrc[0x2] = M_RAM; + memSrc[0x3] = M_RAM; + memSrc[0x4] = M_RAM; + memSrc[0x5] = M_RAM; + memSrc[0x6] = M_RAM; + memSrc[0x7] = M_RAM; + memSrc[0x8] = M_RAM; + memSrc[0x9] = M_CHAR; + memSrc[0xA] = M_RAM; + memSrc[0xB] = M_RAM; + memSrc[0xC] = M_RAM; + memSrc[0xD] = M_RAM; + memSrc[0xE] = M_RAM; + memSrc[0xF] = M_RAM; + } +} + +void +VIC::switchBank(uint16_t addr) { + + if (glueLogic == GLUE_DISCRETE) { + + updateBankAddr(); // Switch immediately + return; + } + + // Switch table for custom IC glue logic and PA / DDRA register changes + // The tables have been derived from VICE test case fetchsplit.prg + uint2_t switchTablePA[4][4] = { + { 0, 1, 2, 3 }, // From bank 0 + { 0, 1, 3, 3 }, // From bank 1 + { 0, 3, 2, 3 }, // From bank 2 + { 0, 1, 2, 3 } // From bank 3 + }; + uint2_t switchTableDDRA[4][4] = { + { 0, 1, 2, 3 }, // From bank 0 + { 1, 1, 3, 3 }, // From bank 1 + { 2, 3, 2, 3 }, // From bank 2 + { 0, 3, 3, 3 } // From bank 3 + }; + + // Determine old and new video bank + uint2_t from = bankAddr >> 14; + uint2_t to = (~c64->cia2.getPA()) & 0x03; + + // Switch to the bank given by the switch table + switch (addr) { + + case 0xDD00: + + // Change was triggered by writing into CIA2::PA + updateBankAddr(switchTablePA[from][to]); + break; + + case 0xDD02: + + // Change was triggered by writing into CIA2::DDRA + updateBankAddr(switchTableDDRA[from][to]); + break; + + default: + assert(false); + } + + // Switch to final bank one cycle later + delay |= VICUpdateBankAddr; +} + +void +VIC::updateBankAddr() +{ + updateBankAddr(~c64->cia2.getPA() & 0x03); +} + +uint8_t +VIC::peek(uint16_t addr) +{ + uint8_t result; + + assert(addr <= 0x3F); + + switch(addr) { + case 0x00: // Sprite X (lower 8 bits) + case 0x02: + case 0x04: + case 0x06: + case 0x08: + case 0x0A: + case 0x0C: + case 0x0E: + + result = reg.current.sprX[addr >> 1]; + break; + + case 0x01: // Sprite Y + case 0x03: + case 0x05: + case 0x07: + case 0x09: + case 0x0B: + case 0x0D: + case 0x0F: + + result = reg.current.sprY[addr >> 1]; + break; + + case 0x10: // Sprite X (upper bits) + + result = + ((reg.current.sprX[0] & 0x100) ? 0b00000001 : 0) | + ((reg.current.sprX[1] & 0x100) ? 0b00000010 : 0) | + ((reg.current.sprX[2] & 0x100) ? 0b00000100 : 0) | + ((reg.current.sprX[3] & 0x100) ? 0b00001000 : 0) | + ((reg.current.sprX[4] & 0x100) ? 0b00010000 : 0) | + ((reg.current.sprX[5] & 0x100) ? 0b00100000 : 0) | + ((reg.current.sprX[6] & 0x100) ? 0b01000000 : 0) | + ((reg.current.sprX[7] & 0x100) ? 0b10000000 : 0); + break; + + case 0x11: // SCREEN CONTROL REGISTER #1 + + result = (reg.current.ctrl1 & 0x7f) | (yCounter > 0xFF ? 0x80 : 0); + break; + + case 0x12: // VIC_RASTER_READ_WRITE + + result = yCounter & 0xff; + break; + + case 0x13: // LIGHTPEN X + + result = latchedLightPenX; + break; + + case 0x14: // LIGHTPEN Y + + result = latchedLightPenY; + break; + + case 0x15: + + result = reg.current.sprEnable; + break; + + case 0x16: + + // The two upper bits always read back as '1' + result = (reg.current.ctrl2 & 0xFF) | 0xC0; + break; + + case 0x17: + + result = reg.current.sprExpandY; + break; + + case 0x18: + + result = memSelect | 0x01; // Bit 1 is unused (always 1) + break; + + case 0x19: // Interrupt Request Register (IRR) + + result = (irr & imr) ? (irr | 0xF0) : (irr | 0x70); + break; + + case 0x1A: // Interrupt Mask Register (IMR) + + result = imr | 0xF0; + break; + + case 0x1B: + + result = reg.current.sprPriority; + break; + + case 0x1C: + + result = reg.current.sprMC; + break; + + case 0x1D: // SPRITE_X_EXPAND + + result = reg.current.sprExpandX; + break; + + case 0x1E: // Sprite-to-sprite collision + + result = spriteSpriteCollision; + delay |= VICClrSprSprCollReg; + break; + + case 0x1F: // Sprite-to-background collision + + result = spriteBackgroundColllision; + delay |= VICClrSprBgCollReg; + break; + + case 0x20: // Border color + + result = reg.current.colors[COLREG_BORDER] | 0xF0; + break; + + case 0x21: // Background color 0 + + result = reg.current.colors[COLREG_BG0] | 0xF0; + break; + + case 0x22: // Background color 1 + + result = reg.current.colors[COLREG_BG1] | 0xF0; + break; + + case 0x23: // Background color 2 + + result = reg.current.colors[COLREG_BG2] | 0xF0; + break; + + case 0x24: // Background color 3 + + result = reg.current.colors[COLREG_BG3] | 0xF0; + break; + + case 0x25: // Sprite extra color 1 (for multicolor sprites) + + result = reg.current.colors[COLREG_SPR_EX1] | 0xF0; + break; + + case 0x26: // Sprite extra color 2 (for multicolor sprites) + + result = reg.current.colors[COLREG_SPR_EX2] | 0xF0; + break; + + case 0x27: // Sprite color 1 + case 0x28: // Sprite color 2 + case 0x29: // Sprite color 3 + case 0x2A: // Sprite color 4 + case 0x2B: // Sprite color 5 + case 0x2C: // Sprite color 6 + case 0x2D: // Sprite color 7 + case 0x2E: // Sprite color 8 + + result = reg.current.colors[COLREG_SPR0 + addr - 0x27] | 0xF0; + break; + + default: + + assert(addr >= 0x2F && addr <= 0x3F); + result = 0xFF; + } + + dataBusPhi2 = result; + return result; +} + +uint8_t +VIC::spypeek(uint16_t addr) +{ + assert(addr <= 0x3F); + + switch(addr) { + case 0x00: // Sprite X (lower 8 bits) + case 0x02: + case 0x04: + case 0x06: + case 0x08: + case 0x0A: + case 0x0C: + case 0x0E: + + return reg.current.sprX[addr >> 1]; + + case 0x01: // Sprite Y + case 0x03: + case 0x05: + case 0x07: + case 0x09: + case 0x0B: + case 0x0D: + case 0x0F: + + return reg.current.sprY[addr >> 1]; + + case 0x10: // Sprite X (upper bits) + + return + ((reg.current.sprX[0] & 0x100) ? 0b00000001 : 0) | + ((reg.current.sprX[1] & 0x100) ? 0b00000010 : 0) | + ((reg.current.sprX[2] & 0x100) ? 0b00000100 : 0) | + ((reg.current.sprX[3] & 0x100) ? 0b00001000 : 0) | + ((reg.current.sprX[4] & 0x100) ? 0b00010000 : 0) | + ((reg.current.sprX[5] & 0x100) ? 0b00100000 : 0) | + ((reg.current.sprX[6] & 0x100) ? 0b01000000 : 0) | + ((reg.current.sprX[7] & 0x100) ? 0b10000000 : 0); + + case 0x11: // SCREEN CONTROL REGISTER #1 + + return (reg.current.ctrl1 & 0x7f) | (yCounter > 0xFF ? 0x80 : 0); + + case 0x12: // VIC_RASTER_READ_WRITE + + return yCounter & 0xff; + + case 0x13: // LIGHTPEN X + + return latchedLightPenX; + + case 0x14: // LIGHTPEN Y + + return latchedLightPenY; + + case 0x15: + + return reg.current.sprEnable; + + case 0x16: + + // The two upper bits always read back as '1' + return (reg.current.ctrl2 & 0xFF) | 0xC0; + + case 0x17: + + return reg.current.sprExpandY; + + case 0x18: + + return memSelect | 0x01; // Bit 1 is unused (always 1) + + case 0x19: // Interrupt Request Register (IRR) + + return (irr & imr) ? (irr | 0xF0) : (irr | 0x70); + + case 0x1A: // Interrupt Mask Register (IMR) + + return imr | 0xF0; + + case 0x1B: + + return reg.current.sprPriority; + + case 0x1C: + + return reg.current.sprMC; + + case 0x1D: // SPRITE_X_EXPAND + + return reg.current.sprExpandX; + + case 0x1E: // Sprite-to-sprite collision + + return spriteSpriteCollision; + + case 0x1F: // Sprite-to-background collision + + return spriteBackgroundColllision; + + case 0x20: // Border color + + return reg.current.colors[COLREG_BORDER] | 0xF0; + + case 0x21: // Background color 0 + + return reg.current.colors[COLREG_BG0] | 0xF0; + + case 0x22: // Background color 1 + + return reg.current.colors[COLREG_BG1] | 0xF0; + + case 0x23: // Background color 2 + + return reg.current.colors[COLREG_BG2] | 0xF0; + + case 0x24: // Background color 3 + + return reg.current.colors[COLREG_BG3] | 0xF0; + + case 0x25: // Sprite extra color 1 (for multicolor sprites) + + return reg.current.colors[COLREG_SPR_EX1] | 0xF0; + + case 0x26: // Sprite extra color 2 (for multicolor sprites) + + return reg.current.colors[COLREG_SPR_EX2] | 0xF0; + + case 0x27: // Sprite color 1 + case 0x28: // Sprite color 2 + case 0x29: // Sprite color 3 + case 0x2A: // Sprite color 4 + case 0x2B: // Sprite color 5 + case 0x2C: // Sprite color 6 + case 0x2D: // Sprite color 7 + case 0x2E: // Sprite color 8 + + return reg.current.colors[COLREG_SPR0 + addr - 0x27] | 0xF0; + + default: + + assert(addr >= 0x2F && addr <= 0x3F); + return 0xFF; + } +} + +void +VIC::poke(uint16_t addr, uint8_t value) +{ + assert(addr < 0x40); + + dataBusPhi2 = value; + + switch(addr) { + case 0x00: // Sprite X (lower 8 bits) + case 0x02: + case 0x04: + case 0x06: + case 0x08: + case 0x0A: + case 0x0C: + case 0x0E: + + reg.current.sprX[addr >> 1] &= 0x100; + reg.current.sprX[addr >> 1] |= value; + break; + + case 0x01: // Sprite Y + case 0x03: + case 0x05: + case 0x07: + case 0x09: + case 0x0B: + case 0x0D: + case 0x0F: + + reg.current.sprY[addr >> 1] = value; + break; + + case 0x10: // Sprite X (upper bit) + + for (unsigned i = 0; i < 8; i++) { + reg.current.sprX[i] &= 0xFF; + reg.current.sprX[i] |= GET_BIT(value, i) ? 0x100 : 0; + } + break; + + case 0x11: // Control register 1 + + reg.current.ctrl1 = value; + + // Check the DEN bit. If it gets set somehwere in line 30, a bad + // line conditions occurs. + if (c64->rasterLine == 0x30 && (value & 0x10)) + DENwasSetInRasterline30 = true; + + if ((badLine = badLineCondition())) { + delay |= VICSetDisplayState; + } + upperComparisonVal = upperComparisonValue(); + lowerComparisonVal = lowerComparisonValue(); + break; + + case 0x12: // RASTER_COUNTER + + rasterIrqLine = value; + return; + + case 0x13: // Lightpen X + case 0x14: // Lightpen Y + + return; + + case 0x15: // SPRITE_ENABLED + + reg.current.sprEnable = value; + break; + + case 0x16: // CONTROL_REGISTER_2 + + reg.current.ctrl2 = value; + leftComparisonVal = leftComparisonValue(); + rightComparisonVal = rightComparisonValue(); + break; + + case 0x17: // SPRITE Y EXPANSION + + reg.current.sprExpandY = value; + cleared_bits_in_d017 = (~value) & (~expansionFF); + + /* "The expansion flip flip is set as long as the bit in MxYE in + * register $d017 corresponding to the sprite is cleared." [C.B.] + */ + expansionFF |= ~value; + break; + + case 0x18: // Memory address pointers + + // Inform the GUI if the second bit changes. It switches between + // upper case or lower case mode. + if ((value & 0x02) != (memSelect & 0x02)) { + memSelect = value; + c64->putMessage(MSG_CHARSET); + return; + } + + memSelect = value; + return; + + case 0x19: // Interrupt Request Register (IRR) + + // Bits are cleared by writing '1' + irr &= (~value) & 0x0F; + delay |= VICUpdateIrqLine; + /* + if (!(irr & imr)) { + releaseDelayedIRQ(); + } + */ + return; + + case 0x1A: // Interrupt Mask Register (IMR) + + imr = value & 0x0F; + delay |= VICUpdateIrqLine; + /* + if (irr & imr) { + triggerDelayedIRQ(1); + } else { + releaseDelayedIRQ(); + } + */ + return; + + case 0x1B: // Sprite priority + + reg.current.sprPriority = value; + break; + + case 0x1C: // Sprite multicolor + + /* + debug("f: %d l: %d c: %d POKE(MC, %02X)\n", + c64->frame, c64->rasterLine, c64->rasterCycle, value); + */ + reg.current.sprMC = value; + break; + + case 0x1D: // SPRITE_X_EXPAND + reg.current.sprExpandX = value; + break; + + case 0x1E: + case 0x1F: + // Writing has no effect + return; + + case 0x20: // Color registers + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + + reg.current.colors[addr - 0x20] = value & 0xF; + + // If enabled, emulate the gray dot bug + if (emulateGrayDotBug) { + reg.delayed.colors[addr - 0x20] = 0xF; + } + break; + } + + delay |= VICUpdateRegisters; +} + +uint8_t +VIC::memAccess(uint16_t addr) +{ + assert((addr & 0xC000) == 0); // 14 bit address + assert((bankAddr & 0x3FFF) == 0); // multiple of 16 KB + + addrBus = bankAddr | addr; + switch (memSrc[addrBus >> 12]) { + + case M_RAM: + return c64->mem.ram[addrBus]; + + case M_CHAR: + return c64->mem.rom[0xC000 + addr]; + + case M_CRTHI: + return c64->expansionport.peek(addrBus | 0xF000); + + default: + return c64->mem.ram[addrBus]; + } +} + +/* + uint8_t + VIC::memAccess(uint16_t addr) + { + uint8_t result; + + assert((addr & 0xC000) == 0); // 14 bit address + assert((bankAddr & 0x3FFF) == 0); // multiple of 16 KB + + addrBus = bankAddr | addr; + + // VIC memory mapping (http://www.harries.dk/files/C64MemoryMaps.pdf) + // Note: Final Cartridge III (freezer mode) only works when BLANK is replaced + // by RAM. So this mapping might not be 100% correct. + // + // Ultimax Standard + // 0xF000: ROMH RAM + // 0xE000: RAM RAM + // 0xD000: RAM RAM + // 0xC000: BLANK RAM + // -------------------------- + // 0xB000: ROMH RAM + // 0xA000: BLANK RAM + // 0x9000: RAM CHAR + // 0x8000: RAM RAM + // -------------------------- + // 0x7000: ROMH RAM + // 0x6000: BLANK RAM + // 0x5000: BLANK RAM + // 0x4000: BLANK RAM + // -------------------------- + // 0x3000: ROMH RAM + // 0x2000: BLANK RAM + // 0x1000: BLANK CHAR + // 0x0000: RAM RAM + + if (getUltimax()) { + + switch (addrBus >> 12) { + case 0xF: + case 0xB: + case 0x7: + case 0x3: + assert(memSrc[addrBus >> 12] == M_CRTHI); + result = c64->expansionport.peek(addrBus | 0xF000); + break; + case 0xE: + case 0xD: + case 0x9: + case 0x8: + case 0x0: + assert(memSrc[addrBus >> 12] == M_RAM); + result = c64->mem.ram[addrBus]; + break; + default: + assert(memSrc[addrBus >> 12] == M_RAM); + result = c64->mem.ram[addrBus]; + } + + } else { + + if (isCharRomAddr(addr)) { + assert(memSrc[addrBus >> 12] == M_CHAR); + result = c64->mem.rom[0xC000 + addr]; + } else { + assert(memSrc[addrBus >> 12] == M_RAM); + result = c64->mem.ram[addrBus]; + } + } + + return result; + } + */ + +uint8_t +VIC::memSpyAccess(uint16_t addr) +{ + uint8_t result; + + assert((addr & 0xC000) == 0); + assert((bankAddr & 0x3FFF) == 0); + + uint16_t addrBus = bankAddr | addr; + + if (getUltimax()) { + + switch (addrBus >> 12) { + case 0xF: + case 0xB: + case 0x7: + case 0x3: + result = c64->expansionport.spypeek(addrBus | 0xF000); + break; + case 0xE: + case 0xD: + case 0x9: + case 0x8: + case 0x0: + result = c64->mem.ram[addrBus]; + break; + default: + result = c64->mem.ram[addrBus]; + } + + } else { + + if (isCharRomAddr(addr)) { + result = c64->mem.rom[0xC000 + addr]; + } else { + result = c64->mem.ram[addrBus]; + } + } + + return result; +} + +bool +VIC::isCharRomAddr(uint16_t addr) +{ + addr = (addr | bankAddr) >> 12; + return addr == 1 || addr == 9; +} + +void +VIC::cAccess() +{ + // If BA is pulled down for at least three cycles, perform memory access + if (BApulledDownForAtLeastThreeCycles()) { + + // |VM13|VM12|VM11|VM10| VC9| VC8| VC7| VC6| VC5| VC4| VC3| VC2| VC1| VC0| + uint16_t addr = (VM13VM12VM11VM10() << 6) | vc; + + dataBusPhi2 = memAccess(addr); + videoMatrix[vmli] = dataBusPhi2; + colorLine[vmli] = c64->mem.colorRam[vc] & 0x0F; + } + + // VIC has no access, yet + else { + + /* "Nevertheless, the VIC accesses the video matrix, or at least it + * tries, because as long as AEC is still high in the second clock + * phase, the address and data bus drivers D0-D7 of the VIC are in + * tri-state and the VIC reads the value $ff from D0-D7 instead of the + * data from the video matrix in the first three cycles. The data lines + * D8-D13 of the VIC however don't have tri-state drivers and are + * always set to input. But the VIC doesn't get valid Color RAM data + * from there either, because as AEC is high, the 6510 is still + * considered the bus master and unless it doesn't by chance want to + * read the next opcode from the Color RAM, the chip select input of + * the Color RAM is not active. [...] + * To make a long story short: In the first three cycles after BA went + * low, the VIC reads $ff as character pointers and as color + * information the lower 4 bits of the opcode after the access to + * $d011. Not until then, regular video matrix data is read." [C.B.] + */ + dataBusPhi2 = 0xFF; + videoMatrix[vmli] = dataBusPhi2; + colorLine[vmli] = c64->mem.ram[c64->cpu.regPC] & 0x0F; + } +} + +void +VIC::gAccess() +{ + uint16_t addr; + + if (displayState) { + + /* "The address generator for the text/bitmap accesses (c- and + * g-accesses) has basically 3 modes for the g-accesses (the c-accesses + * always follow the same address scheme). In display state, the BMM + * bit selects either character generator accesses (BMM=0) or bitmap + * accesses (BMM=1). In idle state, the g-accesses are always done at + * video address $3fff. If the ECM bit is set, the address generator + * always holds the address lines 9 and 10 low without any other + * changes to the addressing scheme (e.g. the g-accesses in idle state + * then occur at address $39ff)." [C.B.] + */ + + // Get address + addr = is856x() ? gAccessAddr85x() : gAccessAddr65x(); + + // Fetch + dataBusPhi1 = memAccess(addr); + + // Store result + gAccessResult.write(LO_LO_HI(dataBusPhi1, // Character + colorLine[vmli], // Color + videoMatrix[vmli])); // Data + + + // "VC and VMLI are incremented after each g-access in display state." + vc = (vc + 1) & 0x3FF; + vmli = (vmli + 1) & 0x3F; + + } else { + + // Get address. In idle state, g-accesses read from $39FF or $3FFF, + // depending on the ECM bit. + if (is856x()) { + addr = GET_BIT(reg.delayed.ctrl1, 6) ? 0x39FF : 0x3FFF; + } else { + addr = GET_BIT(reg.current.ctrl1, 6) ? 0x39FF : 0x3FFF; + } + + // Fetch + dataBusPhi1 = memAccess(addr); + + // Store result + gAccessResult.write(dataBusPhi1); + } +} + +uint16_t +VIC::gAccessAddr85x() +{ + uint8_t oldBmm = GET_BIT(reg.delayed.ctrl1, 5); + uint8_t oldEcm = GET_BIT(reg.delayed.ctrl1, 6); + + return gAccessAddr(oldBmm, oldEcm); +} + +uint16_t +VIC::gAccessAddr65x() +{ + uint8_t oldBmm = GET_BIT(reg.delayed.ctrl1, 5); + uint8_t newBmm = GET_BIT(reg.current.ctrl1, 5); + uint8_t newEcm = GET_BIT(reg.current.ctrl1, 6); + + uint16_t result = gAccessAddr(oldBmm | newBmm, newEcm); + + // Check if BMM bit has just changed + if (oldBmm != newBmm) { + + uint8_t oldEcm = GET_BIT(reg.delayed.ctrl1, 6); + uint16_t oldAddr = gAccessAddr(oldBmm, oldEcm); + uint16_t newAddr = gAccessAddr(newBmm, newEcm); + + // Check if address changes to char ROM. In this case, the result + // is a mixture of oldAddr and newAddr (seen in VICE) + // Test case: VICII/split-tests/modesplit.prg + if (isCharRomAddr(newAddr) && !isCharRomAddr(oldAddr)) { + result = (newAddr & 0x3F00) | (oldAddr & 0x00FF); + } + } + + return result; +} + +uint16_t +VIC::gAccessAddr(bool bmm, bool ecm) +{ + uint16_t addr; + + /* Address source: + * BMM=1: |CB13| VC9| VC8|VC7|VC6|VC5|VC4|VC3|VC2|VC1|VC0|RC2|RC1|RC0| + * BMM=0: |CB13|CB12|CB11|D7 |D6 |D5 |D4 |D3 |D2 |D1 |D0 |RC2|RC1|RC0| + */ + if (bmm) { + addr = (CB13() << 10) | (vc << 3) | rc; + } else { + addr = (CB13CB12CB11() << 10) | (videoMatrix[vmli] << 3) | rc; + } + + /* "If the ECM bit is set, the address generator always holds the + * address lines 9 and 10 low without any other changes to the + * addressing scheme (e.g. the g-accesses in idle state then occur at + * address $39ff)." [C.B.] + */ + if (ecm) addr &= 0xF9FF; + + return addr; +} + +void +VIC::pAccess(unsigned sprite) +{ + assert(sprite < 8); + + // |VM13|VM12|VM11|VM10| 1 | 1 | 1 | 1 | 1 | 1 | 1 | Spr.-Nummer | + dataBusPhi1 = memAccess((VM13VM12VM11VM10() << 6) | 0x03F8 | sprite); + spritePtr[sprite] = dataBusPhi1 << 6; +} + +void +VIC::sFirstAccess(unsigned sprite) +{ + assert(sprite < 8); + + isFirstDMAcycle = (1 << sprite); + + if (spriteDmaOnOff & (1 << sprite)) { + + if (BApulledDownForAtLeastThreeCycles()) { + dataBusPhi2 = memAccess(spritePtr[sprite] | mc[sprite]); + } + + mc[sprite] = (mc[sprite] + 1) & 0x3F; + } + + spriteSr[sprite].chunk1 = dataBusPhi2; +} + +void +VIC::sSecondAccess(unsigned sprite) +{ + assert(sprite < 8); + + isFirstDMAcycle = 0; + isSecondDMAcycle = (1 << sprite); + + if (spriteDmaOnOff & (1 << sprite)) { + + assert(BApulledDownForAtLeastThreeCycles()); + dataBusPhi1 = memAccess(spritePtr[sprite] | mc[sprite]); + mc[sprite] = (mc[sprite] + 1) & 0x3F; + + } else { + + dataBusPhi1 = memAccess(0x3FFF); // Idle access + } + + spriteSr[sprite].chunk2 = dataBusPhi1; +} + +void +VIC::sThirdAccess(unsigned sprite) +{ + assert(sprite < 8); + + if (spriteDmaOnOff & (1 << sprite)) { + + assert(BApulledDownForAtLeastThreeCycles()); + dataBusPhi2 = memAccess(spritePtr[sprite] | mc[sprite]); + mc[sprite] = (mc[sprite] + 1) & 0x3F; + } + + spriteSr[sprite].chunk3 = dataBusPhi2; +} + +void VIC::sFinalize(unsigned sprite) +{ + assert(sprite < 8); + isSecondDMAcycle = 0; +} + + diff --git a/C64/VICII/VIC_types.h b/C64/VICII/VIC_types.h new file mode 100755 index 00000000..7f2ce959 --- /dev/null +++ b/C64/VICII/VIC_types.h @@ -0,0 +1,355 @@ +/*! + * @header VIC_types.h + * @author Dirk W. Hoffmann, www.dirkwhoffmann.de + * @copyright 2015 - 2018 Dirk W. Hoffmann + */ +/* This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef _VIC_TYPES_INC +#define _VIC_TYPES_INC + +// +// VICII colors +// + +//! @brief VIC colors +enum VICIIColors { + VICII_BLACK = 0x0, + VICII_WHITE = 0x1, + VICII_RED = 0x2, + VICII_CYAN = 0x3, + VICII_PURPLE = 0x4, + VICII_GREEN = 0x5, + VICII_BLUE = 0x6, + VICII_YELLOW = 0x7, + VICII_ORANGE = 0x8, + VICII_BROWN = 0x9, + VICII_LIGHT_RED = 0xA, + VICII_DARK_GREY = 0xB, + VICII_GREY = 0xC, + VICII_LIGHT_GREEN = 0xD, + VICII_LIGHT_BLUE = 0xE, + VICII_LIGHT_GREY = 0xF +}; + +//! @brief VIC color registers (D020 - D02E) +enum VICIIColorRegs { + COLREG_BORDER = 0x0, + COLREG_BG0 = 0x1, + COLREG_BG1 = 0x2, + COLREG_BG2 = 0x3, + COLREG_BG3 = 0x4, + COLREG_SPR_EX1 = 0x5, + COLREG_SPR_EX2 = 0x6, + COLREG_SPR0 = 0x7, + COLREG_SPR1 = 0x8, + COLREG_SPR2 = 0x9, + COLREG_SPR3 = 0xA, + COLREG_SPR4 = 0xB, + COLREG_SPR5 = 0xC, + COLREG_SPR6 = 0xD, + COLREG_SPR7 = 0xE +}; + +//! @brief Values of the two frame flipflops +typedef struct { + + bool vertical; + bool main; + +} FrameFlipflops; + +//! @brief Values of (piped) I/O registers +typedef struct { + + uint16_t sprX[8]; // D000, D002, ..., D00E, upper bits from D010 + uint8_t sprY[8]; // D001, D003, ..., D00F + uint8_t ctrl1; // D011 + uint8_t sprEnable; // D015 + uint8_t ctrl2; // D016 + uint8_t sprExpandY; // D017 + uint8_t sprPriority; // D01B + uint8_t sprMC; // D01C + uint8_t sprExpandX; // D01D + uint8_t colors[15]; // D020 - D02E + +} VICIIRegisters; + + +// +// NTSC constants +// + +//! @brief NTSC clock frequency in Hz +static const uint32_t NTSC_CLOCK_FREQUENCY = 1022727; + +//! @brief CPU cycles per second in NTSC mode +static const unsigned NTSC_CYCLES_PER_SECOND = NTSC_CLOCK_FREQUENCY; + +//! @brief Pixel aspect ratio in NTSC mode +static const double NTSC_PIXEL_ASPECT_RATIO = 0.75; + + +// Horizontal screen parameters + +//! @brief Width of left VBLANK area in NTSC mode +static const uint16_t NTSC_LEFT_VBLANK = 77; + +//! @brief Width of left border in NTSC mode +static const uint16_t NTSC_LEFT_BORDER_WIDTH = 55; + +//! @brief Width of canvas area in NTSC mode +static const uint16_t NTSC_CANVAS_WIDTH = 320; + +//! @brief Width of right border in NTSC mode +static const uint16_t NTSC_RIGHT_BORDER_WIDTH = 53; + +//! @brief Width of right VBLANK area in NTSC mode +static const uint16_t NTSC_RIGHT_VBLANK = 15; + +//! @brief Total width of a rasterline (including VBLANK) in NTSC mode +static const uint16_t NTSC_WIDTH = 520; // 77 + 55 + 320 + 53 + 15 + +//! @brief Number of drawn pixels per rasterline in NTSC mode +static const uint16_t NTSC_PIXELS = 428; // 55 + 320 + 53 + +//! @brief Number of viewable pixels per rasterline in NTSC mode +static const uint16_t NTSC_VISIBLE_PIXELS = 418; + + +// Vertical screen parameters + +//! @brief Number of VBLANK lines at top in NTSC mode +static const uint16_t NTSC_UPPER_VBLANK = 16; + +//! @brief Heigt of upper boder in NTSC mode +static const uint16_t NTSC_UPPER_BORDER_HEIGHT = 10; + +//! @brief Height of canvas area in NTSC mode +static const uint16_t NTSC_CANVAS_HEIGHT = 200; + +//! @brief Lower border height in NTSC mode +static const uint16_t NTSC_LOWER_BORDER_HEIGHT = 25; + +//! @brief Number of VBLANK lines at bottom in NTSC mode +static const uint16_t NTSC_LOWER_VBLANK = 12; + +//! @brief Total height of a frame (including VBLANK) in NTSC mode +static const uint16_t NTSC_HEIGHT = 263; // 16 + 10 + 200 + 25 + 12 + +//! @brief Number of drawn rasterlines per frame in NTSC mode +//! @deprecated because the value differes between NTSC models +static const uint16_t NTSC_RASTERLINES = 235; // 10 + 200 + 25 + +//! @brief Number of viewable rasterlines per frame in NTSC mode +//! @deprecated because the value differes between NTSC models +// static const uint16_t NTSC_VISIBLE_RASTERLINES = 235; + + +// +// PAL constants +// + +//! @brief PAL clock frequency in Hz +static const uint32_t PAL_CLOCK_FREQUENCY = 985249; + +//! @brief CPU cycles per second in PAL mode +static const unsigned PAL_CYCLES_PER_SECOND = PAL_CLOCK_FREQUENCY; + +//! @brief Pixel aspect ratio in PAL mode +static const double PAL_PIXEL_ASPECT_RATIO = 0.9365; + + +// Horizontal screen parameters + +//! @brief Width of left VBLANK area in PAL mode +static const uint16_t PAL_LEFT_VBLANK = 76; + +//! @brief Width of left border in PAL mode +static const uint16_t PAL_LEFT_BORDER_WIDTH = 48; + +//! @brief Width of canvas area in PAL mode +static const uint16_t PAL_CANVAS_WIDTH = 320; + +//! @brief Width of right border in PAL mode +static const uint16_t PAL_RIGHT_BORDER_WIDTH = 37; + +//! @brief Width of right VBLANK area in PAL mode +static const uint16_t PAL_RIGHT_VBLANK = 23; + +//! @brief Total width of a rasterline (including VBLANK) in PAL mode +static const uint16_t PAL_WIDTH = 504; // 76 + 48 + 320 + 37 + 23 + +//! @brief Number of drawn pixels per rasterline in PAL mode +static const uint16_t PAL_PIXELS = 405; // 48 + 320 + 37 + +//! @brief Number of viewable pixels per rasterline in PAL mode +// static const uint16_t PAL_VISIBLE_PIXELS = 403; + + +// Vertical screen parameters + +//! @brief Number of VBLANK lines at top in PAL mode +static const uint16_t PAL_UPPER_VBLANK = 16; + +//! @brief Heigt of upper boder in PAL mode +static const uint16_t PAL_UPPER_BORDER_HEIGHT = 35; + +//! @brief Height of canvas area in PAL mode +static const uint16_t PAL_CANVAS_HEIGHT = 200; + +//! @brief Lower border height in PAL mode +static const uint16_t PAL_LOWER_BORDER_HEIGHT = 49; + +//! @brief Number of VBLANK lines at bottom in PAL mode +static const uint16_t PAL_LOWER_VBLANK = 12; + +//! @brief Total height of a frame (including VBLANK) in PAL mode +static const uint16_t PAL_HEIGHT = 312; // 16 + 35 + 200 + 49 + 12 + +//! @brief Number of drawn rasterlines per frame in PAL mode +static const uint16_t PAL_RASTERLINES = 284; // 35 + 200 + 49 + +//! @brief Number of viewable rasterlines per frame in PAL mode +static const uint16_t PAL_VISIBLE_RASTERLINES = 284; // was 292 + + +// +// Types +// + +//! @brief VICII chip model +typedef enum { + PAL_6569_R1 = 1, + PAL_6569_R3 = 2, + PAL_8565 = 4, + NTSC_6567_R56A = 8, + NTSC_6567 = 16, + NTSC_8562 = 32 +} VICModel; + +inline bool isVICChhipModel(VICModel model) { + return + (model == PAL_6569_R1) || + (model == PAL_6569_R3) || + (model == PAL_8565) || + (model == NTSC_6567) || + (model == NTSC_6567_R56A) || + (model == NTSC_8562); +} + +//! @brief Color palette type +/*! @details Used to emulate monochrome displays + */ +typedef enum { + COLOR_PALETTE = 0, + BLACK_WHITE_PALETTE, + PAPER_WHITE_PALETTE, + GREEN_PALETTE, + AMBER_PALETTE, + SEPIA_PALETTE +} VICPalette; + +inline bool isVICPalette(VICPalette model) { + return model >= COLOR_PALETTE && model <= SEPIA_PALETTE; +} + +//! @brief Glue logic type +typedef enum { + GLUE_DISCRETE = 0, + GLUE_CUSTOM_IC = 1 +} GlueLogic; + +inline bool isGlueLogic(GlueLogic type) { + return + (type == GLUE_DISCRETE) || + (type == GLUE_CUSTOM_IC); +} + +//! @brief Screen geometries +typedef enum { + COL_40_ROW_25 = 0x01, + COL_38_ROW_25 = 0x02, + COL_40_ROW_24 = 0x03, + COL_38_ROW_24 = 0x04 +} ScreenGeometry; + +//! Display mode +typedef enum { + STANDARD_TEXT = 0x00, + MULTICOLOR_TEXT = 0x10, + STANDARD_BITMAP = 0x20, + MULTICOLOR_BITMAP = 0x30, + EXTENDED_BACKGROUND_COLOR = 0x40, + INVALID_TEXT = 0x50, + INVALID_STANDARD_BITMAP = 0x60, + INVALID_MULTICOLOR_BITMAP = 0x70 +} DisplayMode; + +/*! @brief VIC info + * @details Used by VIC::getInfo() to collect debug information + */ +typedef struct { + uint16_t rasterline; + uint8_t cycle; + uint16_t xCounter; + bool badLine; + bool ba; + DisplayMode displayMode; + uint8_t borderColor; + uint8_t backgroundColor0; + uint8_t backgroundColor1; + uint8_t backgroundColor2; + uint8_t backgroundColor3; + ScreenGeometry screenGeometry; + uint8_t dx; + uint8_t dy; + bool verticalFrameFlipflop; + bool horizontalFrameFlipflop; + uint16_t memoryBankAddr; + uint16_t screenMemoryAddr; + uint16_t characterMemoryAddr; + uint8_t imr; + uint8_t irr; + bool spriteCollisionIrqEnabled; + bool backgroundCollisionIrqEnabled; + bool rasterIrqEnabled; + uint16_t irqRasterline; + bool irqLine; +} VICInfo; + +/*! @brief Sprite info + * @details Used by VIC::getSpriteInfo() to collect debug information + */ +typedef struct { + + bool enabled; + uint16_t x; + uint8_t y; + uint8_t ptr; + uint8_t color; + bool multicolor; + uint8_t extraColor1; + uint8_t extraColor2; + bool expandX; + bool expandY; + bool priority; + bool collidesWithSprite; + bool collidesWithBackground; +} SpriteInfo; + +#endif diff --git a/README.md b/README.md index deaf87fa..63b0893a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # virtualc64web VirtualC64 web edition + +the base is a copy of virtualC64 without gui v3.4b1 master branch March 3 2020 latest commit point fe1629c \ No newline at end of file