/* * This file is part of libsidplayfp, a SID player engine. * * Copyright 2011-2015 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2001 Simon White * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // -------------------------------------------------------- // The code here is use to support the PSID Version 2NG // (proposal B) file format for player relocation support. // -------------------------------------------------------- #include "psiddrv.h" #include "sidplayfp/SidTuneInfo.h" #include "sidendian.h" #include "sidmemory.h" #include "reloc65.h" #include "c64/CPU/mos6510.h" namespace libsidplayfp { // Error Strings const char ERR_PSIDDRV_NO_SPACE[] = "ERROR: No space to install psid driver in C64 ram"; const char ERR_PSIDDRV_RELOC[] = "ERROR: Failed whilst relocating psid driver"; uint8_t psid_driver[] = { # include "psiddrv.bin" }; const uint8_t POWERON[] = { # include "poweron.bin" }; /** * Copy in power on settings. These were created by running * the kernel reset routine and storing the useful values * from $0000-$03ff. Format is: * - offset byte (bit 7 indicates presence rle byte) * - rle count byte (bit 7 indicates compression used) * - data (single byte) or quantity represented by uncompressed count * all counts and offsets are 1 less than they should be */ void copyPoweronPattern(sidmemory& mem) { uint_least16_t addr = 0; for (unsigned int i = 0; i < sizeof(POWERON);) { uint8_t off = POWERON[i++]; uint8_t count = 0; bool compressed = false; // Determine data count/compression if (off & 0x80) { // fixup offset off &= 0x7f; count = POWERON[i++]; if (count & 0x80) { // fixup count count &= 0x7f; compressed = true; } } // Fix count off by ones (see format details) count++; addr += off; if (compressed) { // Extract compressed data const uint8_t data = POWERON[i++]; while (count-- > 0) { mem.writeMemByte(addr++, data); } } else { // Extract uncompressed data while (count-- > 0) { mem.writeMemByte(addr++, POWERON[i++]); } } } } uint8_t psiddrv::iomap(uint_least16_t addr) const { // Force Real C64 Compatibility if (m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_R64 || m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_BASIC || addr == 0) { // Special case, set to 0x37 by the psid driver return 0; } /* * $34 for init/play in $d000 - $dfff * $35 for init/play in $e000 - $ffff * $36 for load end/play in $a000 - $ffff * $37 for the rest */ if (addr < 0xa000) return 0x37; // Basic-ROM, Kernal-ROM, I/O if (addr < 0xd000) return 0x36; // Kernal-ROM, I/O if (addr >= 0xe000) return 0x35; // I/O only return 0x34; // RAM only } bool psiddrv::drvReloc() { const int startlp = m_tuneInfo->loadAddr() >> 8; const int endlp = (m_tuneInfo->loadAddr() + (m_tuneInfo->c64dataLen() - 1)) >> 8; uint_least8_t relocStartPage = m_tuneInfo->relocStartPage(); uint_least8_t relocPages = m_tuneInfo->relocPages(); if (m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_BASIC) { // The psiddrv is only used for initialisation and to // autorun basic tunes as running the kernel falls // into a manual load/run mode relocStartPage = 0x04; relocPages = 0x03; } // Check for free space in tune if (relocStartPage == 0xff) relocPages = 0; // Check if we need to find the reloc addr else if (relocStartPage == 0) { relocPages = 0; // find area where to dump the driver in. // It's only 1 block long, so any free block we can find // between $0400 and $d000 will do. for (int i = 4; i < 0xd0; i ++) { if (i >= startlp && i <= endlp) continue; if (i >= 0xa0 && i <= 0xbf) continue; relocStartPage = i; relocPages = 1; break; } } if (relocPages < 1) { m_errorString = ERR_PSIDDRV_NO_SPACE; return false; } // Place psid driver into ram const uint_least16_t relocAddr = relocStartPage << 8; reloc_driver = psid_driver; reloc_size = sizeof(psid_driver); reloc65 relocator; relocator.setReloc(reloc65::TEXT, relocAddr - 10); relocator.setExtract(reloc65::TEXT); if (!relocator.reloc(&reloc_driver, &reloc_size)) { m_errorString = ERR_PSIDDRV_RELOC; return false; } // Adjust size to not included initialisation data. reloc_size -= 10; m_driverAddr = relocAddr; m_driverLength = static_cast(reloc_size); // Round length to end of page m_driverLength += 0xff; m_driverLength &= 0xff00; return true; } void psiddrv::install(sidmemory& mem, uint8_t video) const { mem.fillRam(0, static_cast(0), 0x3ff); if (m_tuneInfo->compatibility() >= SidTuneInfo::COMPATIBILITY_R64) { copyPoweronPattern(mem); } // Set PAL/NTSC switch mem.writeMemByte(0x02a6, video); mem.installResetHook(endian_little16(reloc_driver)); // If not a basic tune then the psiddrv must install // interrupt hooks and trap programs trying to restart basic if (m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_BASIC) { // Install hook to set subtune number for basic mem.setBasicSubtune((uint8_t)(m_tuneInfo->currentSong() - 1)); mem.installBasicTrap(0xbf53); } else { // Only install irq handle for RSID tunes mem.fillRam(0x0314, &reloc_driver[2], m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_R64 ? 2 : 6); // Experimental restart basic trap const uint_least16_t addr = endian_little16(&reloc_driver[8]); mem.installBasicTrap(0xffe1); mem.writeMemWord(0x0328, addr); } int pos = m_driverAddr; // Install driver to ram mem.fillRam(pos, &reloc_driver[10], reloc_size); // Set song number mem.writeMemByte(pos, (uint8_t) (m_tuneInfo->currentSong() - 1)); pos++; // Set tunes speed (VIC/CIA) mem.writeMemByte(pos, m_tuneInfo->songSpeed() == SidTuneInfo::SPEED_VBI ? 0 : 1); pos++; // Set init address mem.writeMemWord(pos, m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_BASIC ? 0xbf55 : m_tuneInfo->initAddr()); pos += 2; // Set play address mem.writeMemWord(pos, m_tuneInfo->playAddr()); pos += 2; // Set init address io bank value mem.writeMemByte(pos, iomap(m_tuneInfo->initAddr())); pos++; // Set play address io bank value mem.writeMemByte(pos, iomap(m_tuneInfo->playAddr())); pos++; // Set PAL/NTSC flag mem.writeMemByte(pos, video); pos++; // Set the required tune clock speed uint8_t clockSpeed; switch (m_tuneInfo->clockSpeed()) { case SidTuneInfo::CLOCK_PAL: clockSpeed = 1; break; case SidTuneInfo::CLOCK_NTSC: clockSpeed = 0; break; default: // UNKNOWN or ANY clockSpeed = video; break; } mem.writeMemByte(pos, clockSpeed); pos++; // Set default processor register flags on calling init mem.writeMemByte(pos, m_tuneInfo->compatibility() >= SidTuneInfo::COMPATIBILITY_R64 ? 0 : 1 << MOS6510::SR_INTERRUPT); } }