cog/Frameworks/libsidplay/sidplay-residfp-code/.svn/pristine/2f/2fd27cbde35ba05bd63c2bb319a...

432 lines
13 KiB
Plaintext

/*
* This file is part of libsidplayfp, a SID player engine.
*
* Copyright 2012-2015 Leandro Nini <drfiemost@users.sourceforge.net>
* Copyright 2007-2010 Antti Lankila
* Copyright 2000-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.
*/
#include "PSID.h"
#include <cstring>
#include <string>
#include <memory>
#include "sidplayfp/SidTuneInfo.h"
#include "sidendian.h"
#include "sidmd5.h"
namespace libsidplayfp
{
const int PSID_MAXSTRLEN = 32;
// Header has been extended for 'RSID' format
// The following changes are present:
// id = 'RSID'
// version = 2, 3 or 4
// play, load and speed reserved 0
// psidspecific flag is called C64BASIC flag
// init cannot be under ROMS/IO memory area
// load address cannot be less than $07E8
// info strings may be 32 characters long without trailing zero
struct psidHeader // all values are big-endian
{
uint32_t id; // 'PSID' or 'RSID' (ASCII)
uint16_t version; // 1, 2, 3 or 4
uint16_t data; // 16-bit offset to binary data in file
uint16_t load; // 16-bit C64 address to load file to
uint16_t init; // 16-bit C64 address of init subroutine
uint16_t play; // 16-bit C64 address of play subroutine
uint16_t songs; // number of songs
uint16_t start; // start song out of [1..256]
uint32_t speed; // 32-bit speed info
// bit: 0=50 Hz, 1=CIA 1 Timer A (default: 60 Hz)
char name[PSID_MAXSTRLEN]; // ASCII strings, 31 characters long and
char author[PSID_MAXSTRLEN]; // terminated by a trailing zero
char released[PSID_MAXSTRLEN]; //
uint16_t flags; // only version >= 2
uint8_t relocStartPage; // only version >= 2ng
uint8_t relocPages; // only version >= 2ng
uint8_t sidChipBase2; // only version >= 3
uint8_t sidChipBase3; // only version >= 4
};
enum
{
PSID_MUS = 1 << 0,
PSID_SPECIFIC = 1 << 1, // These two are mutally exclusive
PSID_BASIC = 1 << 1,
PSID_CLOCK = 3 << 2,
PSID_SIDMODEL = 3 << 4
};
enum
{
PSID_CLOCK_UNKNOWN = 0,
PSID_CLOCK_PAL = 1 << 2,
PSID_CLOCK_NTSC = 1 << 3,
PSID_CLOCK_ANY = PSID_CLOCK_PAL | PSID_CLOCK_NTSC
};
enum
{
PSID_SIDMODEL_UNKNOWN = 0,
PSID_SIDMODEL_6581 = 1,
PSID_SIDMODEL_8580 = 2,
PSID_SIDMODEL_ANY = PSID_SIDMODEL_6581 | PSID_SIDMODEL_8580
};
// Format strings
const char TXT_FORMAT_PSID[] = "PlaySID one-file format (PSID)";
const char TXT_FORMAT_RSID[] = "Real C64 one-file format (RSID)";
const char TXT_UNKNOWN_PSID[] = "Unsupported PSID version";
const char TXT_UNKNOWN_RSID[] = "Unsupported RSID version";
const int psid_headerSize = 118;
const int psidv2_headerSize = psid_headerSize + 6;
// Magic fields
const uint32_t PSID_ID = 0x50534944;
const uint32_t RSID_ID = 0x52534944;
/**
* Decode SID model flags.
*/
SidTuneInfo::model_t getSidModel(uint_least16_t modelFlag)
{
if ((modelFlag & PSID_SIDMODEL_ANY) == PSID_SIDMODEL_ANY)
return SidTuneInfo::SIDMODEL_ANY;
if (modelFlag & PSID_SIDMODEL_6581)
return SidTuneInfo::SIDMODEL_6581;
if (modelFlag & PSID_SIDMODEL_8580)
return SidTuneInfo::SIDMODEL_8580;
return SidTuneInfo::SIDMODEL_UNKNOWN;
}
/**
* Check if extra SID addres is valid for PSID specs.
*/
bool validateAddress(uint_least8_t address)
{
// Only even values are valid.
if (address & 1)
return false;
// Ranges $00-$41 ($D000-$D410) and $80-$DF ($D800-$DDF0) are invalid.
// Any invalid value means that no second SID is used, like $00.
if (address <= 0x41
|| (address >= 0x80 && address <= 0xdf))
return false;
return true;
}
SidTuneBase* PSID::load(buffer_t& dataBuf)
{
// File format check
if (dataBuf.size() < 4)
{
return nullptr;
}
const uint32_t magic = endian_big32(&dataBuf[0]);
if ((magic != PSID_ID)
&& (magic != RSID_ID))
{
return nullptr;
}
psidHeader pHeader;
readHeader(dataBuf, pHeader);
std::unique_ptr<PSID> tune(new PSID());
tune->tryLoad(pHeader);
return tune.release();
}
void PSID::readHeader(const buffer_t &dataBuf, psidHeader &hdr)
{
// Due to security concerns, input must be at least as long as version 1
// header plus 16-bit C64 load address. That is the area which will be
// accessed.
if (dataBuf.size() < (psid_headerSize + 2))
{
throw loadError(ERR_TRUNCATED);
}
// Read v1 fields
hdr.id = endian_big32(&dataBuf[0]);
hdr.version = endian_big16(&dataBuf[4]);
hdr.data = endian_big16(&dataBuf[6]);
hdr.load = endian_big16(&dataBuf[8]);
hdr.init = endian_big16(&dataBuf[10]);
hdr.play = endian_big16(&dataBuf[12]);
hdr.songs = endian_big16(&dataBuf[14]);
hdr.start = endian_big16(&dataBuf[16]);
hdr.speed = endian_big32(&dataBuf[18]);
memcpy(hdr.name, &dataBuf[22], PSID_MAXSTRLEN);
memcpy(hdr.author, &dataBuf[54], PSID_MAXSTRLEN);
memcpy(hdr.released, &dataBuf[86], PSID_MAXSTRLEN);
if (hdr.version >= 2)
{
if (dataBuf.size() < (psidv2_headerSize + 2))
{
throw loadError(ERR_TRUNCATED);
}
// Read v2/3/4 fields
hdr.flags = endian_big16(&dataBuf[118]);
hdr.relocStartPage = dataBuf[120];
hdr.relocPages = dataBuf[121];
hdr.sidChipBase2 = dataBuf[122];
hdr.sidChipBase3 = dataBuf[123];
}
}
void PSID::tryLoad(const psidHeader &pHeader)
{
SidTuneInfo::compatibility_t compatibility = SidTuneInfo::COMPATIBILITY_C64;
// Require a valid ID and version number.
if (pHeader.id == PSID_ID)
{
switch (pHeader.version)
{
case 1:
compatibility = SidTuneInfo::COMPATIBILITY_PSID;
break;
case 2:
case 3:
case 4:
break;
default:
throw loadError(TXT_UNKNOWN_PSID);
}
info->m_formatString = TXT_FORMAT_PSID;
}
else if (pHeader.id == RSID_ID)
{
switch (pHeader.version)
{
case 2:
case 3:
case 4:
break;
default:
throw loadError(TXT_UNKNOWN_RSID);
}
info->m_formatString = TXT_FORMAT_RSID;
compatibility = SidTuneInfo::COMPATIBILITY_R64;
}
fileOffset = pHeader.data;
info->m_loadAddr = pHeader.load;
info->m_initAddr = pHeader.init;
info->m_playAddr = pHeader.play;
info->m_songs = pHeader.songs;
info->m_startSong = pHeader.start;
info->m_compatibility = compatibility;
info->m_relocPages = 0;
info->m_relocStartPage = 0;
uint_least32_t speed = pHeader.speed;
SidTuneInfo::clock_t clock = SidTuneInfo::CLOCK_UNKNOWN;
bool musPlayer = false;
if (pHeader.version >= 2)
{
const uint_least16_t flags = pHeader.flags;
// Check clock
if (flags & PSID_MUS)
{ // MUS tunes run at any speed
clock = SidTuneInfo::CLOCK_ANY;
musPlayer = true;
}
else
{
switch (flags & PSID_CLOCK)
{
case PSID_CLOCK_ANY:
clock = SidTuneInfo::CLOCK_ANY;
break;
case PSID_CLOCK_PAL:
clock = SidTuneInfo::CLOCK_PAL;
break;
case PSID_CLOCK_NTSC:
clock = SidTuneInfo::CLOCK_NTSC;
break;
default:
break;
}
}
// These flags are only available for the appropriate
// file formats
switch (compatibility)
{
case SidTuneInfo::COMPATIBILITY_C64:
if (flags & PSID_SPECIFIC)
info->m_compatibility = SidTuneInfo::COMPATIBILITY_PSID;
break;
case SidTuneInfo::COMPATIBILITY_R64:
if (flags & PSID_BASIC)
info->m_compatibility = SidTuneInfo::COMPATIBILITY_BASIC;
break;
default:
break;
}
info->m_clockSpeed = clock;
info->m_sidModels[0] = getSidModel(flags >> 4);
info->m_relocStartPage = pHeader.relocStartPage;
info->m_relocPages = pHeader.relocPages;
if (pHeader.version >= 3)
{
if (validateAddress(pHeader.sidChipBase2))
{
info->m_sidChipAddresses.push_back(0xd000 | (pHeader.sidChipBase2 << 4));
info->m_sidModels.push_back(getSidModel(flags >> 6));
}
if (pHeader.version >= 4)
{
if (pHeader.sidChipBase3 != pHeader.sidChipBase2
&& validateAddress(pHeader.sidChipBase3))
{
info->m_sidChipAddresses.push_back(0xd000 | (pHeader.sidChipBase3 << 4));
info->m_sidModels.push_back(getSidModel(flags >> 8));
}
}
}
}
// Check reserved fields to force real c64 compliance
// as required by the RSID specification
if (compatibility == SidTuneInfo::COMPATIBILITY_R64)
{
if ((info->m_loadAddr != 0)
|| (info->m_playAddr != 0)
|| (speed != 0))
{
throw loadError(ERR_INVALID);
}
// Real C64 tunes appear as CIA
speed = ~0;
}
// Create the speed/clock setting table.
convertOldStyleSpeedToTables(speed, clock);
// Copy info strings.
info->m_infoString.push_back(std::string(pHeader.name, PSID_MAXSTRLEN));
info->m_infoString.push_back(std::string(pHeader.author, PSID_MAXSTRLEN));
info->m_infoString.push_back(std::string(pHeader.released, PSID_MAXSTRLEN));
if (musPlayer)
throw loadError("Compute!'s Sidplayer MUS data is not supported yet"); // TODO
}
const char *PSID::createMD5(char *md5)
{
if (md5 == nullptr)
md5 = m_md5;
*md5 = '\0';
try
{
// Include C64 data.
sidmd5 myMD5;
myMD5.append(&cache[fileOffset], info->m_c64dataLen);
uint8_t tmp[2];
// Include INIT and PLAY address.
endian_little16(tmp, info->m_initAddr);
myMD5.append(tmp, sizeof(tmp));
endian_little16(tmp, info->m_playAddr);
myMD5.append(tmp, sizeof(tmp));
// Include number of songs.
endian_little16(tmp, info->m_songs);
myMD5.append(tmp, sizeof(tmp));
{
// Include song speed for each song.
const unsigned int currentSong = info->m_currentSong;
for (unsigned int s = 1; s <= info->m_songs; s++)
{
selectSong(s);
const uint8_t songSpeed = static_cast<uint8_t>(info->m_songSpeed);
myMD5.append(&songSpeed, sizeof(songSpeed));
}
// Restore old song
selectSong(currentSong);
}
// Deal with PSID v2NG clock speed flags: Let only NTSC
// clock speed change the MD5 fingerprint. That way the
// fingerprint of a PAL-speed sidtune in PSID v1, v2, and
// PSID v2NG format is the same.
if (info->m_clockSpeed == SidTuneInfo::CLOCK_NTSC)
{
const uint8_t ntsc_val = 2;
myMD5.append(&ntsc_val, sizeof(ntsc_val));
}
// NB! If the fingerprint is used as an index into a
// song-lengths database or cache, modify above code to
// allow for PSID v2NG files which have clock speed set to
// SIDTUNE_CLOCK_ANY. If the SID player program fully
// supports the SIDTUNE_CLOCK_ANY setting, a sidtune could
// either create two different fingerprints depending on
// the clock speed chosen by the player, or there could be
// two different values stored in the database/cache.
myMD5.finish();
// Get fingerprint.
myMD5.getDigest().copy(md5, SidTune::MD5_LENGTH);
md5[SidTune::MD5_LENGTH] = '\0';
}
catch (md5Error const &)
{
return nullptr;
}
return md5;
}
}