1 /*
2 * This file is part of libsidplayfp, a SID player engine.
3 *
4 * Copyright 2011-2021 Leandro Nini <drfiemost@users.sourceforge.net>
5 * Copyright 2007-2010 Antti Lankila
6 * Copyright 2000 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 #include "SidTuneBase.h"
24
25 #include <cstring>
26 #include <climits>
27 #include <iostream>
28 #include <iomanip>
29 #include <algorithm>
30 #include <iterator>
31 #include <fstream>
32
33 #include "SmartPtr.h"
34 #include "SidTuneTools.h"
35 #include "SidTuneInfoImpl.h"
36 #include "sidendian.h"
37 #include "sidmemory.h"
38 #include "stringutils.h"
39
40 #include "MUS.h"
41 #include "p00.h"
42 #include "prg.h"
43 #include "PSID.h"
44
45 namespace libsidplayfp
46 {
47
48 // Error and status message strings.
49 const char ERR_EMPTY[] = "SIDTUNE ERROR: No data to load";
50 const char ERR_UNRECOGNIZED_FORMAT[] = "SIDTUNE ERROR: Could not determine file format";
51 const char ERR_CANT_LOAD_FILE[] = "SIDTUNE ERROR: Could not load input file";
52 const char ERR_CANT_OPEN_FILE[] = "SIDTUNE ERROR: Could not open file for binary input";
53 const char ERR_FILE_TOO_LONG[] = "SIDTUNE ERROR: Input data too long";
54 const char ERR_DATA_TOO_LONG[] = "SIDTUNE ERROR: Size of music data exceeds C64 memory";
55 const char ERR_BAD_ADDR[] = "SIDTUNE ERROR: Bad address data";
56 const char ERR_BAD_RELOC[] = "SIDTUNE ERROR: Bad reloc data";
57 const char ERR_CORRUPT[] = "SIDTUNE ERROR: File is incomplete or corrupt";
58 //const char ERR_NOT_ENOUGH_MEMORY[] = "SIDTUNE ERROR: Not enough free memory";
59
60 const char SidTuneBase::ERR_TRUNCATED[] = "SIDTUNE ERROR: File is most likely truncated";
61 const char SidTuneBase::ERR_INVALID[] = "SIDTUNE ERROR: File contains invalid data";
62
63 /**
64 * Petscii to Ascii conversion table (0x01 = no output).
65 */
66 const char CHR_tab[256] =
67 {
68 0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0d,0x01,0x01,
69 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
70 0x20,0x21,0x01,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
71 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
72 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
73 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x24,0x5d,0x20,0x20,
74 // alternative: CHR$(92=0x5c) => ISO Latin-1(0xa3)
75 0x2d,0x23,0x7c,0x2d,0x2d,0x2d,0x2d,0x7c,0x7c,0x5c,0x5c,0x2f,0x5c,0x5c,0x2f,0x2f,
76 0x5c,0x23,0x5f,0x23,0x7c,0x2f,0x58,0x4f,0x23,0x7c,0x23,0x2b,0x7c,0x7c,0x26,0x5c,
77 // 0x80-0xFF
78 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
79 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
80 0x20,0x7c,0x23,0x2d,0x2d,0x7c,0x23,0x7c,0x23,0x2f,0x7c,0x7c,0x2f,0x5c,0x5c,0x2d,
81 0x2f,0x2d,0x2d,0x7c,0x7c,0x7c,0x7c,0x2d,0x2d,0x2d,0x2f,0x5c,0x5c,0x2f,0x2f,0x23,
82 0x2d,0x23,0x7c,0x2d,0x2d,0x2d,0x2d,0x7c,0x7c,0x5c,0x5c,0x2f,0x5c,0x5c,0x2f,0x2f,
83 0x5c,0x23,0x5f,0x23,0x7c,0x2f,0x58,0x4f,0x23,0x7c,0x23,0x2b,0x7c,0x7c,0x26,0x5c,
84 0x20,0x7c,0x23,0x2d,0x2d,0x7c,0x23,0x7c,0x23,0x2f,0x7c,0x7c,0x2f,0x5c,0x5c,0x2d,
85 0x2f,0x2d,0x2d,0x7c,0x7c,0x7c,0x7c,0x2d,0x2d,0x2d,0x2f,0x5c,0x5c,0x2f,0x2f,0x23
86 };
87
88 /// The Commodore 64 memory size
89 const uint_least32_t MAX_MEMORY = 65536;
90
91 /// C64KB + LOAD + PSID
92 const uint_least32_t MAX_FILELEN = MAX_MEMORY + 2 + 0x7C;
93
94 /// Minimum load address for real c64 only tunes
95 const uint_least16_t SIDTUNE_R64_MIN_LOAD_ADDR = 0x07e8;
96
load(const char * fileName,const char ** fileNameExt,bool separatorIsSlash)97 SidTuneBase* SidTuneBase::load(const char* fileName, const char **fileNameExt,
98 bool separatorIsSlash)
99 {
100 return load(nullptr, fileName, fileNameExt, separatorIsSlash);
101 }
102
load(LoaderFunc loader,const char * fileName,const char ** fileNameExt,bool separatorIsSlash)103 SidTuneBase* SidTuneBase::load(LoaderFunc loader, const char* fileName,
104 const char **fileNameExt, bool separatorIsSlash)
105 {
106 if (fileName == nullptr)
107 return nullptr;
108
109 #if !defined(SIDTUNE_NO_STDIN_LOADER)
110 // Filename "-" is used as a synonym for standard input.
111 if (strcmp(fileName, "-") == 0)
112 return getFromStdIn();
113 #endif
114 return getFromFiles(loader, fileName, fileNameExt, separatorIsSlash);
115 }
116
read(const uint_least8_t * sourceBuffer,uint_least32_t bufferLen)117 SidTuneBase* SidTuneBase::read(const uint_least8_t* sourceBuffer, uint_least32_t bufferLen)
118 {
119 return getFromBuffer(sourceBuffer, bufferLen);
120 }
121
getInfo() const122 const SidTuneInfo* SidTuneBase::getInfo() const
123 {
124 return info.get();
125 }
126
getInfo(unsigned int songNum)127 const SidTuneInfo* SidTuneBase::getInfo(unsigned int songNum)
128 {
129 selectSong(songNum);
130 return info.get();
131 }
132
selectSong(unsigned int selectedSong)133 unsigned int SidTuneBase::selectSong(unsigned int selectedSong)
134 {
135 // Check whether selected song is valid, use start song if not
136 const unsigned int song = (selectedSong == 0 || selectedSong > info->m_songs) ? info->m_startSong : selectedSong;
137
138 // Copy any song-specific variable information
139 // such a speed/clock setting to the info structure.
140 info->m_currentSong = song;
141
142 // Retrieve song speed definition.
143 switch (info->m_compatibility)
144 {
145 case SidTuneInfo::COMPATIBILITY_R64:
146 info->m_songSpeed = SidTuneInfo::SPEED_CIA_1A;
147 break;
148 case SidTuneInfo::COMPATIBILITY_PSID:
149 // This does not take into account the PlaySID bug upon evaluating the
150 // SPEED field. It would most likely break compatibility to lots of
151 // sidtunes, which have been converted from .SID format and vice versa.
152 // The .SID format does the bit-wise/song-wise evaluation of the SPEED
153 // value correctly, like it is described in the PlaySID documentation.
154 info->m_songSpeed = songSpeed[(song - 1) & 31];
155 break;
156 default:
157 info->m_songSpeed = songSpeed[song - 1];
158 break;
159 }
160
161 info->m_clockSpeed = clockSpeed[song - 1];
162
163 return info->m_currentSong;
164 }
165
166 // ------------------------------------------------- private member functions
167
placeSidTuneInC64mem(sidmemory & mem)168 void SidTuneBase::placeSidTuneInC64mem(sidmemory& mem)
169 {
170 // The Basic ROM sets these values on loading a file.
171 // Program end address
172 const uint_least16_t start = info->m_loadAddr;
173 const uint_least16_t end = start + info->m_c64dataLen;
174 mem.writeMemWord(0x2d, end); // Variables start
175 mem.writeMemWord(0x2f, end); // Arrays start
176 mem.writeMemWord(0x31, end); // Strings start
177 mem.writeMemWord(0xac, start);
178 mem.writeMemWord(0xae, end);
179
180 // Copy data from cache to the correct destination.
181 mem.fillRam(info->m_loadAddr, &cache[fileOffset], info->m_c64dataLen);
182 }
183
loadFile(const char * fileName,buffer_t & bufferRef)184 void SidTuneBase::loadFile(const char* fileName, buffer_t& bufferRef)
185 {
186 std::ifstream inFile(fileName, std::ifstream::binary);
187
188 if (!inFile.is_open())
189 {
190 throw loadError(ERR_CANT_OPEN_FILE);
191 }
192
193 inFile.seekg(0, inFile.end);
194 const int fileLen = inFile.tellg();
195
196 if (fileLen <= 0)
197 {
198 throw loadError(ERR_EMPTY);
199 }
200
201 inFile.seekg(0, inFile.beg);
202
203 buffer_t fileBuf;
204 fileBuf.reserve(fileLen);
205
206 try
207 {
208 fileBuf.assign(std::istreambuf_iterator<char>(inFile), std::istreambuf_iterator<char>());
209 }
210 catch (std::exception &ex)
211 {
212 throw loadError(ex.what());
213 }
214
215 if (inFile.bad())
216 {
217 throw loadError(ERR_CANT_LOAD_FILE);
218 }
219
220 inFile.close();
221
222 bufferRef.swap(fileBuf);
223 }
224
SidTuneBase()225 SidTuneBase::SidTuneBase() :
226 info(new SidTuneInfoImpl()),
227 fileOffset(0)
228 {
229 // Initialize the object with some safe defaults.
230 for (unsigned int si = 0; si < MAX_SONGS; si++)
231 {
232 songSpeed[si] = info->m_songSpeed;
233 clockSpeed[si] = info->m_clockSpeed;
234 }
235 }
236
237 #if !defined(SIDTUNE_NO_STDIN_LOADER)
238
getFromStdIn()239 SidTuneBase* SidTuneBase::getFromStdIn()
240 {
241 buffer_t fileBuf;
242
243 // We only read as much as fits in the buffer.
244 // This way we avoid choking on huge data.
245 char datb;
246 while (std::cin.get(datb) && fileBuf.size() < MAX_FILELEN)
247 {
248 fileBuf.push_back((uint_least8_t)datb);
249 }
250
251 return getFromBuffer(&fileBuf.front(), fileBuf.size());
252 }
253
254 #endif
255
getFromBuffer(const uint_least8_t * const buffer,uint_least32_t bufferLen)256 SidTuneBase* SidTuneBase::getFromBuffer(const uint_least8_t* const buffer, uint_least32_t bufferLen)
257 {
258 if (buffer == nullptr || bufferLen == 0)
259 {
260 throw loadError(ERR_EMPTY);
261 }
262
263 if (bufferLen > MAX_FILELEN)
264 {
265 throw loadError(ERR_FILE_TOO_LONG);
266 }
267
268 buffer_t buf1(buffer, buffer + bufferLen);
269
270 // Here test for the possible single file formats.
271 std::unique_ptr<SidTuneBase> s(PSID::load(buf1));
272 if (s.get() == nullptr) s.reset(MUS::load(buf1, true));
273 if (s.get() == nullptr) throw loadError(ERR_UNRECOGNIZED_FORMAT);
274
275 s->acceptSidTune("-", "-", buf1, false);
276 return s.release();
277 }
278
acceptSidTune(const char * dataFileName,const char * infoFileName,buffer_t & buf,bool isSlashedFileName)279 void SidTuneBase::acceptSidTune(const char* dataFileName, const char* infoFileName,
280 buffer_t& buf, bool isSlashedFileName)
281 {
282 // Make a copy of the data file name and path, if available.
283 if (dataFileName != nullptr)
284 {
285 const size_t fileNamePos = isSlashedFileName ?
286 SidTuneTools::slashedFileNameWithoutPath(dataFileName) :
287 SidTuneTools::fileNameWithoutPath(dataFileName);
288 info->m_path = std::string(dataFileName, fileNamePos);
289 info->m_dataFileName = std::string(dataFileName + fileNamePos);
290 }
291
292 // Make a copy of the info file name, if available.
293 if (infoFileName != nullptr)
294 {
295 const size_t fileNamePos = isSlashedFileName ?
296 SidTuneTools::slashedFileNameWithoutPath(infoFileName) :
297 SidTuneTools::fileNameWithoutPath(infoFileName);
298 info->m_infoFileName = std::string(infoFileName + fileNamePos);
299 }
300
301 // Fix bad sidtune set up.
302 if (info->m_songs > MAX_SONGS)
303 {
304 info->m_songs = MAX_SONGS;
305 }
306 else if (info->m_songs == 0)
307 {
308 info->m_songs = 1;
309 }
310
311 if (info->m_startSong == 0
312 || info->m_startSong > info->m_songs)
313 {
314 info->m_startSong = 1;
315 }
316
317 info->m_dataFileLen = buf.size();
318 info->m_c64dataLen = buf.size() - fileOffset;
319
320 // Calculate any remaining addresses and then
321 // confirm all the file details are correct
322 resolveAddrs(&buf[fileOffset]);
323
324 if (checkRelocInfo() == false)
325 {
326 throw loadError(ERR_BAD_RELOC);
327 }
328 if (checkCompatibility() == false)
329 {
330 throw loadError(ERR_BAD_ADDR);
331 }
332
333 if (info->m_dataFileLen >= 2)
334 {
335 // We only detect an offset of two. Some position independent
336 // sidtunes contain a load address of 0xE000, but are loaded
337 // to 0x0FFE and call player at 0x1000.
338 info->m_fixLoad = (endian_little16(&buf[fileOffset])==(info->m_loadAddr+2));
339 }
340
341 // Check the size of the data.
342 if (info->m_c64dataLen > MAX_MEMORY)
343 {
344 throw loadError(ERR_DATA_TOO_LONG);
345 }
346 else if (info->m_c64dataLen == 0)
347 {
348 throw loadError(ERR_EMPTY);
349 }
350
351 cache.swap(buf);
352 }
353
createNewFileName(std::string & destString,const char * sourceName,const char * sourceExt)354 void SidTuneBase::createNewFileName(std::string& destString,
355 const char* sourceName,
356 const char* sourceExt)
357 {
358 destString.assign(sourceName);
359 destString.erase(destString.find_last_of('.'));
360 destString.append(sourceExt);
361 }
362
363 // Initializing the object based upon what we find in the specified file.
364
getFromFiles(const char * fileName,const char ** fileNameExtensions,bool separatorIsSlash)365 SidTuneBase* SidTuneBase::getFromFiles(const char* fileName, const char **fileNameExtensions, bool separatorIsSlash)
366 {
367 return getFromFiles(nullptr, fileName, fileNameExtensions, separatorIsSlash);
368 }
369
getFromFiles(LoaderFunc loader,const char * fileName,const char ** fileNameExtensions,bool separatorIsSlash)370 SidTuneBase* SidTuneBase::getFromFiles(LoaderFunc loader, const char* fileName, const char **fileNameExtensions, bool separatorIsSlash)
371 {
372 buffer_t fileBuf1;
373
374 if (loader == nullptr)
375 loader = (LoaderFunc) loadFile;
376
377 loader(fileName, fileBuf1);
378
379 // File loaded. Now check if it is in a valid single-file-format.
380 std::unique_ptr<SidTuneBase> s(PSID::load(fileBuf1));
381 if (s.get() == nullptr)
382 {
383 // Try some native C64 file formats
384 s.reset(MUS::load(fileBuf1, true));
385 if (s.get() != nullptr)
386 {
387 // Try to find second file.
388 std::string fileName2;
389 int n = 0;
390 while (fileNameExtensions[n] != nullptr)
391 {
392 createNewFileName(fileName2, fileName, fileNameExtensions[n]);
393 // 1st data file was loaded into "fileBuf1",
394 // so we load the 2nd one into "fileBuf2".
395 // Do not load the first file again if names are equal.
396 if (!stringutils::equal(fileName, fileName2.data(), fileName2.size()))
397 {
398 try
399 {
400 buffer_t fileBuf2;
401
402 loader(fileName2.c_str(), fileBuf2);
403 // Check if tunes in wrong order and therefore swap them here
404 if (stringutils::equal(fileNameExtensions[n], ".mus"))
405 {
406 std::unique_ptr<SidTuneBase> s2(MUS::load(fileBuf2, fileBuf1, 0, true));
407 if (s2.get() != nullptr)
408 {
409 s2->acceptSidTune(fileName2.c_str(), fileName, fileBuf2, separatorIsSlash);
410 return s2.release();
411 }
412 }
413 else
414 {
415 std::unique_ptr<SidTuneBase> s2(MUS::load(fileBuf1, fileBuf2, 0, true));
416 if (s2.get() != nullptr)
417 {
418 s2->acceptSidTune(fileName, fileName2.c_str(), fileBuf1, separatorIsSlash);
419 return s2.release();
420 }
421 }
422 // The first tune loaded ok, so ignore errors on the
423 // second tune, may find an ok one later
424 }
425 catch (loadError const &) {}
426 }
427 n++;
428 }
429 }
430 }
431 if (s.get() == nullptr) s.reset(p00::load(fileName, fileBuf1));
432 if (s.get() == nullptr) s.reset(prg::load(fileName, fileBuf1));
433 if (s.get() == nullptr) throw loadError(ERR_UNRECOGNIZED_FORMAT);
434
435 s->acceptSidTune(fileName, nullptr, fileBuf1, separatorIsSlash);
436 return s.release();
437 }
438
convertOldStyleSpeedToTables(uint_least32_t speed,SidTuneInfo::clock_t clock)439 void SidTuneBase::convertOldStyleSpeedToTables(uint_least32_t speed, SidTuneInfo::clock_t clock)
440 {
441 // Create the speed/clock setting tables.
442 //
443 // This routine implements the PSIDv2NG compliant speed conversion. All tunes
444 // above 32 use the same song speed as tune 32
445 // NOTE: The cast here is used to avoid undefined references
446 // as the std::min function takes its parameters by reference
447 const unsigned int toDo = std::min(info->m_songs, static_cast<unsigned int>(MAX_SONGS));
448 for (unsigned int s = 0; s < toDo; s++)
449 {
450 clockSpeed[s] = clock;
451 songSpeed[s] = (speed & 1) ? SidTuneInfo::SPEED_CIA_1A : SidTuneInfo::SPEED_VBI;
452
453 if (s < 31)
454 {
455 speed >>= 1;
456 }
457 }
458 }
459
checkRelocInfo()460 bool SidTuneBase::checkRelocInfo()
461 {
462 // Fix relocation information
463 if (info->m_relocStartPage == 0xFF)
464 {
465 info->m_relocPages = 0;
466 return true;
467 }
468 else if (info->m_relocPages == 0)
469 {
470 info->m_relocStartPage = 0;
471 return true;
472 }
473
474 // Calculate start/end page
475 const uint_least8_t startp = info->m_relocStartPage;
476 const uint_least8_t endp = (startp + info->m_relocPages - 1) & 0xff;
477 if (endp < startp)
478 {
479 return false;
480 }
481
482 { // Check against load range
483 const uint_least8_t startlp = (uint_least8_t) (info->m_loadAddr >> 8);
484 const uint_least8_t endlp = startlp + (uint_least8_t) ((info->m_c64dataLen - 1) >> 8);
485
486 if (((startp <= startlp) && (endp >= startlp))
487 || ((startp <= endlp) && (endp >= endlp)))
488 {
489 return false;
490 }
491 }
492
493 // Check that the relocation information does not use the following
494 // memory areas: 0x0000-0x03FF, 0xA000-0xBFFF and 0xD000-0xFFFF
495 if ((startp < 0x04)
496 || ((0xa0 <= startp) && (startp <= 0xbf))
497 || (startp >= 0xd0)
498 || ((0xa0 <= endp) && (endp <= 0xbf))
499 || (endp >= 0xd0))
500 {
501 return false;
502 }
503
504 return true;
505 }
506
resolveAddrs(const uint_least8_t * c64data)507 void SidTuneBase::resolveAddrs(const uint_least8_t *c64data)
508 {
509 // Originally used as a first attempt at an RSID
510 // style format. Now reserved for future use
511 if (info->m_playAddr == 0xffff)
512 {
513 info->m_playAddr = 0;
514 }
515
516 // loadAddr = 0 means, the address is stored in front of the C64 data.
517 if (info->m_loadAddr == 0)
518 {
519 if (info->m_c64dataLen < 2)
520 {
521 throw loadError(ERR_CORRUPT);
522 }
523
524 info->m_loadAddr = endian_16(*(c64data+1), *c64data);
525 fileOffset += 2;
526 info->m_c64dataLen -= 2;
527 }
528
529 if (info->m_compatibility == SidTuneInfo::COMPATIBILITY_BASIC)
530 {
531 if (info->m_initAddr != 0)
532 {
533 throw loadError(ERR_BAD_ADDR);
534 }
535 }
536 else if (info->m_initAddr == 0)
537 {
538 info->m_initAddr = info->m_loadAddr;
539 }
540 }
541
checkCompatibility()542 bool SidTuneBase::checkCompatibility()
543 {
544 if (info->m_compatibility == SidTuneInfo::COMPATIBILITY_R64)
545 {
546 // Check valid init address
547 switch (info->m_initAddr >> 12)
548 {
549 case 0x0A:
550 case 0x0B:
551 case 0x0D:
552 case 0x0E:
553 case 0x0F:
554 return false;
555 default:
556 if ((info->m_initAddr < info->m_loadAddr)
557 || (info->m_initAddr > (info->m_loadAddr + info->m_c64dataLen - 1)))
558 {
559 return false;
560 }
561 }
562
563 // Check tune is loadable on a real C64
564 if (info->m_loadAddr < SIDTUNE_R64_MIN_LOAD_ADDR)
565 {
566 return false;
567 }
568 }
569
570 return true;
571 }
572
petsciiToAscii(SmartPtr_sidtt<const uint8_t> & spPet)573 std::string SidTuneBase::petsciiToAscii(SmartPtr_sidtt<const uint8_t>& spPet)
574 {
575 std::string buffer;
576
577 do
578 {
579 const uint8_t petsciiChar = *spPet;
580 spPet++;
581
582 if ((petsciiChar == 0x00) || (petsciiChar == 0x0d))
583 break;
584
585 // If character is 0x9d (left arrow key) then move back.
586 if ((petsciiChar == 0x9d) && (!buffer.empty()))
587 {
588 buffer.resize(buffer.size() - 1);
589 }
590 else
591 {
592 // ASCII CHR$ conversion
593 const char asciiChar = CHR_tab[petsciiChar];
594 if ((asciiChar >= 0x20) && (buffer.length() <= 31))
595 buffer.push_back(asciiChar);
596 }
597 }
598 while (!spPet.fail());
599
600 return buffer;
601 }
602
603 }
604