cog/Frameworks/libsidplay/sidplay-residfp-code/.svn/pristine/fb/fb96c23ab63ceb33fe308870131...

611 lines
18 KiB
Plaintext

/*
* This file is part of libsidplayfp, a SID player engine.
*
* Copyright 2011-2014 Leandro Nini <drfiemost@users.sourceforge.net>
* Copyright 2007-2010 Antti Lankila
* Copyright 2000 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.
*/
#include "SidTuneBase.h"
#include <cstring>
#include <climits>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <iterator>
#include <fstream>
#include "SmartPtr.h"
#include "SidTuneTools.h"
#include "SidTuneInfoImpl.h"
#include "sidendian.h"
#include "sidmemory.h"
#include "stringutils.h"
#include "sidcxx11.h"
#include "MUS.h"
#include "p00.h"
#include "prg.h"
#include "PSID.h"
namespace libsidplayfp
{
// Error and status message strings.
const char ERR_EMPTY[] = "SIDTUNE ERROR: No data to load";
const char ERR_UNRECOGNIZED_FORMAT[] = "SIDTUNE ERROR: Could not determine file format";
const char ERR_NOT_ENOUGH_MEMORY[] = "SIDTUNE ERROR: Not enough free memory";
const char ERR_CANT_LOAD_FILE[] = "SIDTUNE ERROR: Could not load input file";
const char ERR_CANT_OPEN_FILE[] = "SIDTUNE ERROR: Could not open file for binary input";
const char ERR_FILE_TOO_LONG[] = "SIDTUNE ERROR: Input data too long";
const char ERR_DATA_TOO_LONG[] = "SIDTUNE ERROR: Size of music data exceeds C64 memory";
const char ERR_BAD_ADDR[] = "SIDTUNE ERROR: Bad address data";
const char ERR_BAD_RELOC[] = "SIDTUNE ERROR: Bad reloc data";
const char ERR_CORRUPT[] = "SIDTUNE ERROR: File is incomplete or corrupt";
const char SidTuneBase::ERR_TRUNCATED[] = "SIDTUNE ERROR: File is most likely truncated";
const char SidTuneBase::ERR_INVALID[] = "SIDTUNE ERROR: File contains invalid data";
/**
* Petscii to Ascii conversion table.
*
* CHR$ conversion table (0x01 = no output)
*/
static const char CHR_tab[256] =
{
0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, 0xd,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x20,0x21,0x01,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x24,0x5d,0x20,0x20,
// alternative: CHR$(92=0x5c) => ISO Latin-1(0xa3)
0x2d,0x23,0x7c,0x2d,0x2d,0x2d,0x2d,0x7c,0x7c,0x5c,0x5c,0x2f,0x5c,0x5c,0x2f,0x2f,
0x5c,0x23,0x5f,0x23,0x7c,0x2f,0x58,0x4f,0x23,0x7c,0x23,0x2b,0x7c,0x7c,0x26,0x5c,
// 0x80-0xFF
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x20,0x7c,0x23,0x2d,0x2d,0x7c,0x23,0x7c,0x23,0x2f,0x7c,0x7c,0x2f,0x5c,0x5c,0x2d,
0x2f,0x2d,0x2d,0x7c,0x7c,0x7c,0x7c,0x2d,0x2d,0x2d,0x2f,0x5c,0x5c,0x2f,0x2f,0x23,
0x2d,0x23,0x7c,0x2d,0x2d,0x2d,0x2d,0x7c,0x7c,0x5c,0x5c,0x2f,0x5c,0x5c,0x2f,0x2f,
0x5c,0x23,0x5f,0x23,0x7c,0x2f,0x58,0x4f,0x23,0x7c,0x23,0x2b,0x7c,0x7c,0x26,0x5c,
0x20,0x7c,0x23,0x2d,0x2d,0x7c,0x23,0x7c,0x23,0x2f,0x7c,0x7c,0x2f,0x5c,0x5c,0x2d,
0x2f,0x2d,0x2d,0x7c,0x7c,0x7c,0x7c,0x2d,0x2d,0x2d,0x2f,0x5c,0x5c,0x2f,0x2f,0x23
};
/// C64KB + LOAD + PSID
const uint_least32_t MAX_FILELEN = 65536 + 2 + 0x7C;
const uint_least32_t MAX_MEMORY = 65536;
SidTuneBase* SidTuneBase::load(const char* fileName, const char **fileNameExt,
bool separatorIsSlash)
{
if (!fileName)
return nullptr;
#if !defined(SIDTUNE_NO_STDIN_LOADER)
// Filename "-" is used as a synonym for standard input.
if (strcmp(fileName, "-") == 0)
return getFromStdIn();
#endif
return getFromFiles(fileName, fileNameExt, separatorIsSlash);
}
SidTuneBase* SidTuneBase::read(const uint_least8_t* sourceBuffer, uint_least32_t bufferLen)
{
return getFromBuffer(sourceBuffer, bufferLen);
}
const SidTuneInfo* SidTuneBase::getInfo() const
{
return info.get();
}
const SidTuneInfo* SidTuneBase::getInfo(unsigned int songNum)
{
selectSong(songNum);
return info.get();
}
unsigned int SidTuneBase::selectSong(unsigned int selectedSong)
{
// First, check whether selected song is valid.
if (selectedSong > info->m_songs || selectedSong > MAX_SONGS)
{
return info->m_currentSong;
}
// Determine and set starting song number.
const unsigned int song = (selectedSong == 0) ? info->m_startSong : selectedSong;
// Copy any song-specific variable information
// such a speed/clock setting to the info structure.
info->m_currentSong = song;
// Retrieve song speed definition.
switch (info->m_compatibility)
{
case SidTuneInfo::COMPATIBILITY_R64:
info->m_songSpeed = SidTuneInfo::SPEED_CIA_1A;
break;
case SidTuneInfo::COMPATIBILITY_PSID:
// This does not take into account the PlaySID bug upon evaluating the
// SPEED field. It would most likely break compatibility to lots of
// sidtunes, which have been converted from .SID format and vice versa.
// The .SID format does the bit-wise/song-wise evaluation of the SPEED
// value correctly, like it is described in the PlaySID documentation.
info->m_songSpeed = songSpeed[(song-1)&31];
break;
default:
info->m_songSpeed = songSpeed[song-1];
break;
}
info->m_clockSpeed = clockSpeed[song-1];
return info->m_currentSong;
}
// ------------------------------------------------- private member functions
bool SidTuneBase::placeSidTuneInC64mem(sidmemory* mem)
{
if (mem != nullptr)
{
// The Basic ROM sets these values on loading a file.
// Program end address
const uint_least16_t start = info->m_loadAddr;
const uint_least16_t end = start + info->m_c64dataLen;
mem->writeMemWord(0x2d, end); // Variables start
mem->writeMemWord(0x2f, end); // Arrays start
mem->writeMemWord(0x31, end); // Strings start
mem->writeMemWord(0xac, start);
mem->writeMemWord(0xae, end);
// Copy data from cache to the correct destination.
mem->fillRam(info->m_loadAddr, &cache[fileOffset], info->m_c64dataLen);
return true;
}
return false;
}
void SidTuneBase::loadFile(const char* fileName, buffer_t& bufferRef)
{
std::ifstream inFile(fileName, std::ifstream::binary);
if (!inFile.is_open())
{
throw loadError(ERR_CANT_OPEN_FILE);
}
inFile.seekg(0, inFile.end);
const int fileLen = inFile.tellg();
if (fileLen <= 0)
{
throw loadError(ERR_EMPTY);
}
inFile.seekg(0, inFile.beg);
buffer_t fileBuf;
fileBuf.reserve(fileLen);
try
{
fileBuf.assign(std::istreambuf_iterator<char>(inFile), std::istreambuf_iterator<char>());
}
catch (std::exception &ex)
{
throw loadError(ex.what());
}
if (inFile.bad())
{
throw loadError(ERR_CANT_LOAD_FILE);
}
inFile.close();
bufferRef.swap(fileBuf);
}
SidTuneBase::SidTuneBase() :
info(new SidTuneInfoImpl()),
fileOffset(0)
{
// Initialize the object with some safe defaults.
for (unsigned int si = 0; si < MAX_SONGS; si++)
{
songSpeed[si] = info->m_songSpeed;
clockSpeed[si] = info->m_clockSpeed;
}
}
#if !defined(SIDTUNE_NO_STDIN_LOADER)
SidTuneBase* SidTuneBase::getFromStdIn()
{
buffer_t fileBuf;
// We only read as much as fits in the buffer.
// This way we avoid choking on huge data.
char datb;
while (std::cin.get(datb) && fileBuf.size() < MAX_FILELEN)
{
fileBuf.push_back((uint_least8_t)datb);
}
return getFromBuffer(&fileBuf.front(), fileBuf.size());
}
#endif
SidTuneBase* SidTuneBase::getFromBuffer(const uint_least8_t* const buffer, uint_least32_t bufferLen)
{
if (buffer == nullptr || bufferLen == 0)
{
throw loadError(ERR_EMPTY);
}
if (bufferLen > MAX_FILELEN)
{
throw loadError(ERR_FILE_TOO_LONG);
}
buffer_t buf1(buffer, buffer+bufferLen);
// Here test for the possible single file formats.
std::unique_ptr<SidTuneBase> s(PSID::load(buf1));
if (s.get() == nullptr)
{
buffer_t buf2; // empty
s.reset(MUS::load(buf1, buf2, 0, true));
}
if (s.get() != nullptr)
{
s->acceptSidTune("-", "-", buf1, false);
return s.release();
}
throw loadError(ERR_UNRECOGNIZED_FORMAT);
}
void SidTuneBase::acceptSidTune(const char* dataFileName, const char* infoFileName,
buffer_t& buf, bool isSlashedFileName)
{
// Make a copy of the data file name and path, if available.
if (dataFileName != nullptr)
{
const size_t fileNamePos = isSlashedFileName ?
SidTuneTools::slashedFileNameWithoutPath(dataFileName) :
SidTuneTools::fileNameWithoutPath(dataFileName);
info->m_path = std::string(dataFileName, fileNamePos);
info->m_dataFileName = std::string(dataFileName + fileNamePos);
}
// Make a copy of the info file name, if available.
if (infoFileName != nullptr)
{
const size_t fileNamePos = isSlashedFileName ?
SidTuneTools::slashedFileNameWithoutPath(infoFileName) :
SidTuneTools::fileNameWithoutPath(infoFileName);
info->m_infoFileName = std::string(infoFileName + fileNamePos);
}
// Fix bad sidtune set up.
if (info->m_songs > MAX_SONGS)
{
info->m_songs = MAX_SONGS;
}
else if (info->m_songs == 0)
{
info->m_songs++;
}
if (info->m_startSong > info->m_songs)
{
info->m_startSong = 1;
}
else if (info->m_startSong == 0)
{
info->m_startSong++;
}
info->m_dataFileLen = buf.size();
info->m_c64dataLen = buf.size() - fileOffset;
// Calculate any remaining addresses and then
// confirm all the file details are correct
resolveAddrs(&buf[fileOffset]);
if (checkRelocInfo() == false)
{
throw loadError(ERR_BAD_RELOC);
}
if (checkCompatibility() == false)
{
throw loadError(ERR_BAD_ADDR);
}
if (info->m_dataFileLen >= 2)
{
// We only detect an offset of two. Some position independent
// sidtunes contain a load address of 0xE000, but are loaded
// to 0x0FFE and call player at 0x1000.
info->m_fixLoad = (endian_little16(&buf[fileOffset])==(info->m_loadAddr+2));
}
// Check the size of the data.
if (info->m_c64dataLen > MAX_MEMORY)
{
throw loadError(ERR_DATA_TOO_LONG);
}
else if (info->m_c64dataLen == 0)
{
throw loadError(ERR_EMPTY);
}
cache.swap(buf);
}
void SidTuneBase::createNewFileName(std::string& destString,
const char* sourceName,
const char* sourceExt)
{
destString.assign(sourceName);
destString.erase(destString.find_last_of('.'));
destString.append(sourceExt);
}
// Initializing the object based upon what we find in the specified file.
SidTuneBase* SidTuneBase::getFromFiles(const char* fileName, const char **fileNameExtensions, bool separatorIsSlash)
{
buffer_t fileBuf1;
loadFile(fileName, fileBuf1);
// File loaded. Now check if it is in a valid single-file-format.
std::unique_ptr<SidTuneBase> s(PSID::load(fileBuf1));
if (s.get() == nullptr)
{
buffer_t fileBuf2;
// Try some native C64 file formats
s.reset(MUS::load(fileBuf1, fileBuf2, 0, true));
if (s.get() != nullptr)
{
// Try to find second file.
std::string fileName2;
int n = 0;
while (fileNameExtensions[n] != nullptr)
{
createNewFileName(fileName2, fileName, fileNameExtensions[n]);
// 1st data file was loaded into "fileBuf1",
// so we load the 2nd one into "fileBuf2".
// Do not load the first file again if names are equal.
if (!stringutils::equal(fileName, fileName2.data(), fileName2.size()))
{
try
{
loadFile(fileName2.c_str(), fileBuf2);
// Check if tunes in wrong order and therefore swap them here
if (stringutils::equal(fileNameExtensions[n], ".mus"))
{
std::unique_ptr<SidTuneBase> s2(MUS::load(fileBuf2, fileBuf1, 0, true));
if (s2.get())
{
s2->acceptSidTune(fileName2.c_str(), fileName, fileBuf2, separatorIsSlash);
return s2.release();
}
}
else
{
std::unique_ptr<SidTuneBase> s2(MUS::load(fileBuf1, fileBuf2, 0, true));
if (s2.get() != nullptr)
{
s2->acceptSidTune(fileName, fileName2.c_str(), fileBuf1, separatorIsSlash);
return s2.release();
}
}
// The first tune loaded ok, so ignore errors on the
// second tune, may find an ok one later
}
catch (loadError const &) {}
}
n++;
}
s->acceptSidTune(fileName, 0, fileBuf1, separatorIsSlash);
return s.release();
}
}
if (s.get() == nullptr) s.reset(p00::load(fileName, fileBuf1));
if (s.get() == nullptr) s.reset(prg::load(fileName, fileBuf1));
if (s.get() != nullptr)
{
s->acceptSidTune(fileName, 0, fileBuf1, separatorIsSlash);
return s.release();
}
throw loadError(ERR_UNRECOGNIZED_FORMAT);
}
void SidTuneBase::convertOldStyleSpeedToTables(uint_least32_t speed, SidTuneInfo::clock_t clock)
{
// Create the speed/clock setting tables.
//
// This routine implements the PSIDv2NG compliant speed conversion. All tunes
// above 32 use the same song speed as tune 32
const unsigned int toDo = std::min(info->m_songs, MAX_SONGS);
for (unsigned int s = 0; s < toDo; s++)
{
clockSpeed[s] = clock;
songSpeed[s] = (speed & 1) ? SidTuneInfo::SPEED_CIA_1A : SidTuneInfo::SPEED_VBI;
if (s < 31)
{
speed >>= 1;
}
}
}
bool SidTuneBase::checkRelocInfo()
{
// Fix relocation information
if (info->m_relocStartPage == 0xFF)
{
info->m_relocPages = 0;
return true;
}
else if (info->m_relocPages == 0)
{
info->m_relocStartPage = 0;
return true;
}
// Calculate start/end page
const uint_least8_t startp = info->m_relocStartPage;
const uint_least8_t endp = (startp + info->m_relocPages - 1) & 0xff;
if (endp < startp)
{
return false;
}
{ // Check against load range
const uint_least8_t startlp = (uint_least8_t) (info->m_loadAddr >> 8);
const uint_least8_t endlp = startlp + (uint_least8_t) ((info->m_c64dataLen - 1) >> 8);
if (((startp <= startlp) && (endp >= startlp))
|| ((startp <= endlp) && (endp >= endlp)))
{
return false;
}
}
// Check that the relocation information does not use the following
// memory areas: 0x0000-0x03FF, 0xA000-0xBFFF and 0xD000-0xFFFF
if ((startp < 0x04)
|| ((0xa0 <= startp) && (startp <= 0xbf))
|| (startp >= 0xd0)
|| ((0xa0 <= endp) && (endp <= 0xbf))
|| (endp >= 0xd0))
{
return false;
}
return true;
}
void SidTuneBase::resolveAddrs(const uint_least8_t *c64data)
{
// Originally used as a first attempt at an RSID
// style format. Now reserved for future use
if (info->m_playAddr == 0xffff)
{
info->m_playAddr = 0;
}
// loadAddr = 0 means, the address is stored in front of the C64 data.
if (info->m_loadAddr == 0)
{
if (info->m_c64dataLen < 2)
{
throw loadError(ERR_CORRUPT);
}
info->m_loadAddr = endian_16(*(c64data+1), *c64data);
fileOffset += 2;
info->m_c64dataLen -= 2;
}
if (info->m_compatibility == SidTuneInfo::COMPATIBILITY_BASIC)
{
if (info->m_initAddr != 0)
{
throw loadError(ERR_BAD_ADDR);
}
}
else if (info->m_initAddr == 0)
{
info->m_initAddr = info->m_loadAddr;
}
}
bool SidTuneBase::checkCompatibility()
{
if (info->m_compatibility == SidTuneInfo::COMPATIBILITY_R64)
{
// Check valid init address
switch (info->m_initAddr >> 12)
{
case 0x0F:
case 0x0E:
case 0x0D:
case 0x0B:
case 0x0A:
return false;
default:
if ((info->m_initAddr < info->m_loadAddr)
|| (info->m_initAddr > (info->m_loadAddr + info->m_c64dataLen - 1)))
{
return false;
}
}
// Check tune is loadable on a real C64
if (info->m_loadAddr < SIDTUNE_R64_MIN_LOAD_ADDR)
{
return false;
}
}
return true;
}
const char* SidTuneBase::PetsciiToAscii::convert(SmartPtr_sidtt<const uint8_t>& spPet)
{
char c;
do
{
c = CHR_tab[*spPet]; // ASCII CHR$ conversion
if ((c >= 0x20) && (buffer.length() <= 31))
buffer.push_back(c); // copy to info string
// If character is 0x9d (left arrow key) then move back.
if ((*spPet == 0x9d) && (!buffer.empty()))
{
buffer.resize(buffer.size() - 1);
}
spPet++;
}
while (!((c == 0x0D) || (c == 0x00) || spPet.fail()));
return buffer.c_str();
}
}