mirror of
https://github.com/zoriya/ComSquare.git
synced 2026-06-07 19:50:48 +00:00
232 lines
6.9 KiB
C++
232 lines
6.9 KiB
C++
//
|
|
// Created by anonymus-raccoon on 1/27/20.
|
|
//
|
|
|
|
#include "Cartridge.hpp"
|
|
#include "Exceptions/InvalidAction.hpp"
|
|
#include "Exceptions/InvalidRom.hpp"
|
|
#include <cstring>
|
|
#include <sys/stat.h>
|
|
#include <fstream>
|
|
|
|
namespace ComSquare::Cartridge
|
|
{
|
|
constexpr unsigned HeaderSize = 0x40u;
|
|
|
|
Cartridge::Cartridge()
|
|
: Ram::Ram(0, Rom, "Cartridge")
|
|
{}
|
|
|
|
Cartridge::Cartridge(const std::string &romPath)
|
|
: Ram::Ram(0, Rom, "Cartridge"),
|
|
_romPath(romPath)
|
|
{
|
|
this->loadRom(romPath);
|
|
}
|
|
|
|
void Cartridge::loadRom(const std::string &path)
|
|
{
|
|
size_t size = Cartridge::getRomSize(path);
|
|
std::ifstream rom(path, std::ios::binary);
|
|
|
|
if (!rom)
|
|
throw InvalidRomException("Could not open the rom file at " + path + ". " + strerror(errno));
|
|
this->_data.resize(size);
|
|
rom.read(reinterpret_cast<char *>(this->_data.data()), size);
|
|
this->_loadHeader();
|
|
}
|
|
|
|
size_t Cartridge::getRomSize(const std::string &romPath)
|
|
{
|
|
struct stat info;
|
|
|
|
if (stat(romPath.c_str(), &info) < 0)
|
|
throw InvalidRomException("Could not stat the rom file at " + romPath + ". " + strerror(errno));
|
|
return info.st_size;
|
|
}
|
|
|
|
uint8_t Cartridge::read(uint24_t addr)
|
|
{
|
|
return Ram::read(addr + this->_romStart);
|
|
}
|
|
|
|
void Cartridge::write(uint24_t, uint8_t)
|
|
{
|
|
throw InvalidAction("Witting to the ROM is not allowed.");
|
|
}
|
|
|
|
std::filesystem::path Cartridge::getRomPath() const
|
|
{
|
|
return this->_romPath;
|
|
}
|
|
|
|
Header Cartridge::_mapHeader(uint32_t headerAddress)
|
|
{
|
|
Header head;
|
|
headerAddress -= 0xC0u;
|
|
|
|
head.mappingMode |= this->_data[headerAddress + 0xD5u] & 0x10u ? FastRom : SlowRom;
|
|
head.mappingMode |= this->_data[headerAddress + 0xD5u] & 0x1u ? HiRom : LoRom;
|
|
if (this->_data[headerAddress + 0xD5u] & 0x2u || this->_data[headerAddress + 0xD5u] & 0x4u)
|
|
head.mappingMode |= ExRom;
|
|
head.romType = this->_data[headerAddress + 0xD6u];
|
|
head.romSize = 0x400u << this->_data[headerAddress + 0xD7u];
|
|
head.sramSize = 0x400u << this->_data[headerAddress + 0xD8u];
|
|
head.creatorIDs[0] = this->_data[headerAddress + 0xD9u];
|
|
head.creatorIDs[1] = this->_data[headerAddress + 0xDAu];
|
|
head.version = this->_data[headerAddress + 0xDBu];
|
|
head.checksumComplements[0] = this->_data[headerAddress + 0xDCu];
|
|
head.checksumComplements[1] = this->_data[headerAddress + 0xDDu];
|
|
head.checksums[0] = this->_data[headerAddress + 0xDEu];
|
|
head.checksums[1] = this->_data[headerAddress + 0xDFu];
|
|
|
|
head.nativeInterrupts.cop8[0] = this->_data[headerAddress + 0xE4u];
|
|
head.nativeInterrupts.cop8[1] = this->_data[headerAddress + 0xE5u];
|
|
head.nativeInterrupts.brk8[0] = this->_data[headerAddress + 0xE6u];
|
|
head.nativeInterrupts.brk8[1] = this->_data[headerAddress + 0xE7u];
|
|
head.nativeInterrupts.abort8[0] = this->_data[headerAddress + 0xE8u];
|
|
head.nativeInterrupts.abort8[1] = this->_data[headerAddress + 0xE9u];
|
|
head.nativeInterrupts.nmi8[0] = this->_data[headerAddress + 0xEAu];
|
|
head.nativeInterrupts.nmi8[1] = this->_data[headerAddress + 0xEBu];
|
|
head.nativeInterrupts.reset8[0] = this->_data[headerAddress + 0xECu];
|
|
head.nativeInterrupts.reset8[1] = this->_data[headerAddress + 0xEDu];
|
|
head.nativeInterrupts.irq8[0] = this->_data[headerAddress + 0xEEu];
|
|
head.nativeInterrupts.irq8[1] = this->_data[headerAddress + 0xEFu];
|
|
|
|
head.emulationInterrupts.cop8[0] = this->_data[headerAddress + 0xF4u];
|
|
head.emulationInterrupts.cop8[1] = this->_data[headerAddress + 0xF5u];
|
|
head.emulationInterrupts.abort8[0] = this->_data[headerAddress + 0xF8u];
|
|
head.emulationInterrupts.abort8[1] = this->_data[headerAddress + 0xF9u];
|
|
head.emulationInterrupts.nmi8[0] = this->_data[headerAddress + 0xFAu];
|
|
head.emulationInterrupts.nmi8[1] = this->_data[headerAddress + 0xFBu];
|
|
head.emulationInterrupts.reset8[0] = this->_data[headerAddress + 0xFCu];
|
|
head.emulationInterrupts.reset8[1] = this->_data[headerAddress + 0xFDu];
|
|
head.emulationInterrupts.brk8[0] = this->_data[headerAddress + 0xFEu];
|
|
head.emulationInterrupts.brk8[1] = this->_data[headerAddress + 0xFFu];
|
|
head.emulationInterrupts.irq8[0] = this->_data[headerAddress + 0xFEu];
|
|
head.emulationInterrupts.irq8[1] = this->_data[headerAddress + 0xFFu];
|
|
return head;
|
|
}
|
|
|
|
uint32_t Cartridge::_getHeaderAddress()
|
|
{
|
|
const std::vector<uint32_t> address = {0x7FC0, 0xFFC0};
|
|
unsigned int smc = this->getSize() % 1024;
|
|
int bestScore = -1;
|
|
uint32_t bestAddress = 0;
|
|
|
|
for (uint32_t addr : address) {
|
|
int score = 0;
|
|
|
|
addr += smc;
|
|
if (addr + 0x32u >= this->getSize())
|
|
continue;
|
|
|
|
Header info = this->_mapHeader(addr);
|
|
if (info.romType <= 0x8u)
|
|
score++;
|
|
if (info.romSize < 0x400u << 0x10u)
|
|
score++;
|
|
if (info.sramSize < 0x400u << 0x08u)
|
|
score++;
|
|
if (info.checksum + info.checksumComplement == 0xFFFF && info.checksum != 0 && info.checksumComplement != 0)
|
|
score += 8;
|
|
|
|
// The reset vector is the first thing called by the SNES so It must execute the code inside the ROM (the rom starts at 0x8000).
|
|
if (info.emulationInterrupts.reset < 0x8000u)
|
|
continue;
|
|
uint8_t resetOpCode = this->_data[info.emulationInterrupts.reset - 0x8000u];
|
|
switch (resetOpCode) {
|
|
case 0x18://CLI
|
|
case 0x78://SEI
|
|
case 0x4C://JMP
|
|
case 0x5C://JMP
|
|
case 0x20://JSR
|
|
case 0x22://JSL
|
|
case 0x9C://STZ
|
|
score += 8;
|
|
break;
|
|
case 0xC2://REP
|
|
case 0xE2://SEP
|
|
case 0xA9://LDA
|
|
case 0xA2://LDX
|
|
case 0xA0://LDY
|
|
score += 4;
|
|
break;
|
|
case 0x00://BRK
|
|
case 0xFF://SBC
|
|
case 0xCC://CPY
|
|
score -= 8;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
bestAddress = addr;
|
|
}
|
|
}
|
|
return bestAddress;
|
|
}
|
|
|
|
bool Cartridge::_isSPCFile()
|
|
{
|
|
if (this->getSize() < 0x25)
|
|
return false;
|
|
std::string str = std::string(reinterpret_cast<char *>(this->getData().data()), 0x21);
|
|
|
|
if (str != Cartridge::_magicSPC)
|
|
return false;
|
|
if (this->_data[0x21] != 0x1A || this->_data[0x22] != 0x1A)
|
|
return false;
|
|
if (this->_data[0x23] != 0x1A && this->_data[0x23] != 0x1B)
|
|
return false;
|
|
if (this->_data[0x24] != 0x1E)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool Cartridge::_loadHeader()
|
|
{
|
|
if (this->_isSPCFile()) {
|
|
this->_type = Audio;
|
|
return false;
|
|
}
|
|
this->_type = Game;
|
|
|
|
uint32_t headerAddress = this->_getHeaderAddress();
|
|
if (headerAddress + HeaderSize > this->getSize())
|
|
return false;
|
|
|
|
this->header = this->_mapHeader(headerAddress);
|
|
this->header.gameName = std::string(reinterpret_cast<char *>(&this->_data[headerAddress]), 21);
|
|
if ((headerAddress + 0x40u) & 0x200u) {
|
|
this->_romStart = 0x200u;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CartridgeType Cartridge::getType()
|
|
{
|
|
return this->_type;
|
|
}
|
|
|
|
uint24_t Cartridge::getSize() const
|
|
{
|
|
return Ram::getSize() - this->_romStart;
|
|
}
|
|
|
|
MappingMode operator|(const MappingMode &self, const MappingMode &other)
|
|
{
|
|
return static_cast<MappingMode>(static_cast<int>(self) | static_cast<int>(other));
|
|
}
|
|
|
|
MappingMode &operator|=(MappingMode &self, const MappingMode &other)
|
|
{
|
|
int &selfInt = reinterpret_cast<int &>(self);
|
|
selfInt |= static_cast<int>(other);
|
|
return self;
|
|
}
|
|
}// namespace ComSquare::Cartridge
|