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