1 /*
2  * This file is part of libsidplayfp, a SID player engine.
3  *
4  * Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
5  * Copyright 2007-2010 Antti Lankila
6  * Copyright 2001 Simon White
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21  */
22 
23 
24 // --------------------------------------------------------
25 // The code here is use to support the PSID Version 2NG
26 // (proposal B) file format for player relocation support.
27 // --------------------------------------------------------
28 #include "psiddrv.h"
29 
30 #include "sidplayfp/SidTuneInfo.h"
31 
32 #include "sidendian.h"
33 #include "sidmemory.h"
34 #include "reloc65.h"
35 #include "c64/CPU/mos6510.h"
36 
37 namespace libsidplayfp
38 {
39 
40 // Error Strings
41 const char ERR_PSIDDRV_NO_SPACE[]  = "ERROR: No space to install psid driver in C64 ram";
42 const char ERR_PSIDDRV_RELOC[]     = "ERROR: Failed whilst relocating psid driver";
43 
44 uint8_t psid_driver[] =
45 {
46 #  include "psiddrv.bin"
47 };
48 
49 const uint8_t POWERON[] =
50 {
51 #  include "poweron.bin"
52 };
53 
54 
55 /**
56  * Copy in power on settings. These were created by running
57  * the kernel reset routine and storing the useful values
58  * from $0000-$03ff. Format is:
59  * - offset byte (bit 7 indicates presence rle byte)
60  * - rle count byte (bit 7 indicates compression used)
61  * - data (single byte) or quantity represented by uncompressed count
62  * all counts and offsets are 1 less than they should be
63  */
copyPoweronPattern(sidmemory & mem)64 void copyPoweronPattern(sidmemory& mem)
65 {
66     uint_least16_t addr = 0;
67     for (unsigned int i = 0; i < sizeof(POWERON);)
68     {
69         uint8_t off   = POWERON[i++];
70         uint8_t count = 0;
71         bool compressed = false;
72 
73         // Determine data count/compression
74         if (off & 0x80)
75         {
76             // fixup offset
77             off  &= 0x7f;
78             count = POWERON[i++];
79             if (count & 0x80)
80             {
81                 // fixup count
82                 count &= 0x7f;
83                 compressed = true;
84             }
85         }
86 
87         // Fix count off by ones (see format details)
88         count++;
89         addr += off;
90 
91         if (compressed)
92         {
93             // Extract compressed data
94             const uint8_t data = POWERON[i++];
95             while (count-- > 0)
96             {
97                 mem.writeMemByte(addr++, data);
98             }
99         }
100         else
101         {
102             // Extract uncompressed data
103             while (count-- > 0)
104             {
105                 mem.writeMemByte(addr++, POWERON[i++]);
106             }
107         }
108     }
109 }
110 
iomap(uint_least16_t addr) const111 uint8_t psiddrv::iomap(uint_least16_t addr) const
112 {
113     // Force Real C64 Compatibility
114     if (m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_R64
115         || m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_BASIC
116         || addr == 0)
117     {
118         // Special case, set to 0x37 by the psid driver
119         return 0;
120     }
121 
122     /*
123      * $34 for init/play in $d000 - $dfff
124      * $35 for init/play in $e000 - $ffff
125      * $36 for load end/play in $a000 - $ffff
126      * $37 for the rest
127      */
128     if (addr < 0xa000)
129         return 0x37;  // Basic-ROM, Kernal-ROM, I/O
130     if (addr < 0xd000)
131         return 0x36;  // Kernal-ROM, I/O
132     if (addr >= 0xe000)
133         return 0x35;  // I/O only
134 
135     return 0x34;  // RAM only
136 }
137 
drvReloc()138 bool psiddrv::drvReloc()
139 {
140     const int startlp = m_tuneInfo->loadAddr() >> 8;
141     const int endlp   = (m_tuneInfo->loadAddr() + (m_tuneInfo->c64dataLen() - 1)) >> 8;
142 
143     uint_least8_t relocStartPage = m_tuneInfo->relocStartPage();
144     uint_least8_t relocPages = m_tuneInfo->relocPages();
145 
146     if (m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_BASIC)
147     {
148         // The psiddrv is only used for initialisation and to
149         // autorun basic tunes as running the kernel falls
150         // into a manual load/run mode
151         relocStartPage = 0x04;
152         relocPages     = 0x03;
153     }
154 
155     // Check for free space in tune
156     if (relocStartPage == 0xff)
157         relocPages = 0;
158     // Check if we need to find the reloc addr
159     else if (relocStartPage == 0)
160     {
161         relocPages = 0;
162         // find area where to dump the driver in.
163         // It's only 1 block long, so any free block we can find
164         // between $0400 and $d000 will do.
165         for (int i = 4; i < 0xd0; i ++)
166         {
167             if (i >= startlp && i <= endlp)
168                 continue;
169 
170             if (i >= 0xa0 && i <= 0xbf)
171                 continue;
172 
173             relocStartPage = i;
174             relocPages = 1;
175             break;
176         }
177     }
178 
179     if (relocPages < 1)
180     {
181         m_errorString = ERR_PSIDDRV_NO_SPACE;
182         return false;
183     }
184 
185     // Place psid driver into ram
186     const uint_least16_t relocAddr = relocStartPage << 8;
187 
188     reloc_driver = psid_driver;
189     reloc_size   = sizeof(psid_driver);
190 
191     reloc65 relocator(relocAddr - 10);
192     if (!relocator.reloc(&reloc_driver, &reloc_size))
193     {
194         m_errorString = ERR_PSIDDRV_RELOC;
195         return false;
196     }
197 
198     // Adjust size to not included initialisation data.
199     reloc_size -= 10;
200 
201     m_driverAddr   = relocAddr;
202     m_driverLength = static_cast<uint_least16_t>(reloc_size);
203     // Round length to end of page
204     m_driverLength += 0xff;
205     m_driverLength &= 0xff00;
206 
207     return true;
208 }
209 
install(sidmemory & mem,uint8_t video) const210 void psiddrv::install(sidmemory& mem, uint8_t video) const
211 {
212     mem.fillRam(0, static_cast<uint8_t>(0), 0x3ff);
213 
214     if (m_tuneInfo->compatibility() >= SidTuneInfo::COMPATIBILITY_R64)
215     {
216         copyPoweronPattern(mem);
217     }
218 
219     // Set PAL/NTSC switch
220     mem.writeMemByte(0x02a6, video);
221 
222     mem.installResetHook(endian_little16(reloc_driver));
223 
224     // If not a basic tune then the psiddrv must install
225     // interrupt hooks and trap programs trying to restart basic
226     if (m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_BASIC)
227     {
228         // Install hook to set subtune number for basic
229         mem.setBasicSubtune((uint8_t)(m_tuneInfo->currentSong() - 1));
230         mem.installBasicTrap(0xbf53);
231     }
232     else
233     {
234         // Only install irq handle for RSID tunes
235         mem.fillRam(0x0314, &reloc_driver[2], m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_R64 ? 2 : 6);
236 
237         // Experimental restart basic trap
238         const uint_least16_t addr = endian_little16(&reloc_driver[8]);
239         mem.installBasicTrap(0xffe1);
240         mem.writeMemWord(0x0328, addr);
241     }
242 
243     int pos = m_driverAddr;
244 
245     // Install driver to ram
246     mem.fillRam(pos, &reloc_driver[10], reloc_size);
247 
248     // Set song number
249     mem.writeMemByte(pos, (uint8_t) (m_tuneInfo->currentSong() - 1));
250     pos++;
251 
252     // Set tunes speed (VIC/CIA)
253     mem.writeMemByte(pos, m_tuneInfo->songSpeed() == SidTuneInfo::SPEED_VBI ? 0 : 1);
254     pos++;
255 
256     // Set init address
257     mem.writeMemWord(pos, m_tuneInfo->compatibility() == SidTuneInfo::COMPATIBILITY_BASIC ?
258                      0xbf55 : m_tuneInfo->initAddr());
259     pos += 2;
260 
261     // Set play address
262     mem.writeMemWord(pos, m_tuneInfo->playAddr());
263     pos += 2;
264 
265     mem.writeMemWord(pos, m_powerOnDelay);
266     pos += 2;
267 
268     // Set init address io bank value
269     mem.writeMemByte(pos, iomap(m_tuneInfo->initAddr()));
270     pos++;
271 
272     // Set play address io bank value
273     mem.writeMemByte(pos, iomap(m_tuneInfo->playAddr()));
274     pos++;
275 
276     // Set PAL/NTSC flag
277     mem.writeMemByte(pos, video);
278     pos++;
279 
280     // Set the required tune clock speed
281     uint8_t clockSpeed;
282     switch (m_tuneInfo->clockSpeed())
283     {
284     case SidTuneInfo::CLOCK_PAL:
285         clockSpeed = 1;
286         break;
287     case SidTuneInfo::CLOCK_NTSC:
288         clockSpeed = 0;
289         break;
290     default: // UNKNOWN or ANY
291         clockSpeed = video;
292         break;
293     }
294     mem.writeMemByte(pos, clockSpeed);
295     pos++;
296 
297     // Set default processor register flags on calling init
298     mem.writeMemByte(pos, m_tuneInfo->compatibility() >= SidTuneInfo::COMPATIBILITY_R64 ? 0 : 1 << MOS6510::SR_INTERRUPT);
299 }
300 
301 }
302