diff options
Diffstat (limited to 'gearboy/src/Cartridge.cpp')
| -rw-r--r-- | gearboy/src/Cartridge.cpp | 689 |
1 files changed, 689 insertions, 0 deletions
diff --git a/gearboy/src/Cartridge.cpp b/gearboy/src/Cartridge.cpp new file mode 100644 index 00000000..d58c7f67 --- /dev/null +++ b/gearboy/src/Cartridge.cpp @@ -0,0 +1,689 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 3 of the License, or + * 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, see http://www.gnu.org/licenses/ + * + */ + +#include <string> +#include <algorithm> +#include <ctype.h> +#include <time.h> +#include "Cartridge.h" +#include "miniz/miniz.c" + +Cartridge::Cartridge() +{ + InitPointer(m_pTheROM); + m_iTotalSize = 0; + m_szName[0] = 0; + m_iROMSize = 0; + m_iRAMSize = 0; + m_Type = CartridgeNotSupported; + m_bValidROM = false; + m_bCGB = false; + m_bSGB = false; + m_iVersion = 0; + m_bLoaded = false; + m_RTCCurrentTime = 0; + m_bBattery = false; + m_szFilePath[0] = 0; + m_szFileName[0] = 0; + m_bRTCPresent = false; + m_bRumblePresent = false; + m_iRAMBankCount = 0; + m_iROMBankCount = 0; +} + +Cartridge::~Cartridge() +{ + SafeDeleteArray(m_pTheROM); +} + +void Cartridge::Init() +{ + Reset(); +} + +void Cartridge::Reset() +{ + SafeDeleteArray(m_pTheROM); + m_iTotalSize = 0; + m_szName[0] = 0; + m_iROMSize = 0; + m_iRAMSize = 0; + m_Type = CartridgeNotSupported; + m_bValidROM = false; + m_bCGB = false; + m_bSGB = false; + m_iVersion = 0; + m_bLoaded = false; + m_RTCCurrentTime = 0; + m_bBattery = false; + m_szFilePath[0] = 0; + m_szFileName[0] = 0; + m_bRTCPresent = false; + m_bRumblePresent = false; + m_iRAMBankCount = 0; + m_iROMBankCount = 0; + m_GameGenieList.clear(); +} + +bool Cartridge::IsValidROM() const +{ + return m_bValidROM; +} + +bool Cartridge::IsLoadedROM() const +{ + return m_bLoaded; +} + +Cartridge::CartridgeTypes Cartridge::GetType() const +{ + return m_Type; +} + +int Cartridge::GetRAMSize() const +{ + return m_iRAMSize; +} + +int Cartridge::GetROMSize() const +{ + return m_iROMSize; +} + +int Cartridge::GetRAMBankCount() const +{ + return m_iRAMBankCount; +} + +int Cartridge::GetROMBankCount() const +{ + return m_iROMBankCount; +} + +const char* Cartridge::GetName() const +{ + return m_szName; +} + +const char* Cartridge::GetFilePath() const +{ + return m_szFilePath; +} + +const char* Cartridge::GetFileName() const +{ + return m_szFileName; +} + +int Cartridge::GetTotalSize() const +{ + return m_iTotalSize; +} + +bool Cartridge::HasBattery() const +{ + return m_bBattery; +} + +u8* Cartridge::GetTheROM() const +{ + return m_pTheROM; +} + +bool Cartridge::LoadFromZipFile(const u8* buffer, int size) +{ + using namespace std; + + mz_zip_archive zip_archive; + mz_bool status; + memset(&zip_archive, 0, sizeof (zip_archive)); + + status = mz_zip_reader_init_mem(&zip_archive, (void*) buffer, size, 0); + if (!status) + { + Log("mz_zip_reader_init_mem() failed!"); + return false; + } + + for (unsigned int i = 0; i < mz_zip_reader_get_num_files(&zip_archive); i++) + { + mz_zip_archive_file_stat file_stat; + if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) + { + Log("mz_zip_reader_file_stat() failed!"); + mz_zip_reader_end(&zip_archive); + return false; + } + + Log("ZIP Content - Filename: \"%s\", Comment: \"%s\", Uncompressed size: %u, Compressed size: %u", file_stat.m_filename, file_stat.m_comment, (unsigned int) file_stat.m_uncomp_size, (unsigned int) file_stat.m_comp_size); + + string fn((const char*) file_stat.m_filename); + transform(fn.begin(), fn.end(), fn.begin(), (int(*)(int)) tolower); + string extension = fn.substr(fn.find_last_of(".") + 1); + + if ((extension == "gb") || (extension == "dmg") || (extension == "gbc") || (extension == "cgb") || (extension == "sgb")) + { + void *p; + size_t uncomp_size; + + p = mz_zip_reader_extract_file_to_heap(&zip_archive, file_stat.m_filename, &uncomp_size, 0); + if (!p) + { + Log("mz_zip_reader_extract_file_to_heap() failed!"); + mz_zip_reader_end(&zip_archive); + return false; + } + + bool ok = LoadFromBuffer((const u8*) p, static_cast<int>(uncomp_size)); + + free(p); + mz_zip_reader_end(&zip_archive); + + return ok; + } + } + return false; +} + +bool Cartridge::LoadFromFile(const char* path) +{ + using namespace std; + + Log("Loading %s...", path); + + Reset(); + + strcpy(m_szFilePath, path); + + std::string pathstr(path); + std::string filename; + + size_t pos = pathstr.find_last_of("\\"); + if (pos != std::string::npos) + { + filename.assign(pathstr.begin() + pos + 1, pathstr.end()); + } + else + { + pos = pathstr.find_last_of("/"); + if (pos != std::string::npos) + { + filename.assign(pathstr.begin() + pos + 1, pathstr.end()); + } + else + { + filename = pathstr; + } + } + + strcpy(m_szFileName, filename.c_str()); + + ifstream file(path, ios::in | ios::binary | ios::ate); + + if (file.is_open()) + { + int size = static_cast<int> (file.tellg()); + char* memblock = new char[size]; + file.seekg(0, ios::beg); + file.read(memblock, size); + file.close(); + + string fn(path); + transform(fn.begin(), fn.end(), fn.begin(), (int(*)(int)) tolower); + string extension = fn.substr(fn.find_last_of(".") + 1); + + if (extension == "zip") + { + Log("Loading from ZIP..."); + m_bLoaded = LoadFromZipFile(reinterpret_cast<u8*> (memblock), size); + } + else + { + m_bLoaded = LoadFromBuffer(reinterpret_cast<u8*> (memblock), size); + } + + if (m_bLoaded) + { + Log("ROM loaded", path); + } + else + { + Log("There was a problem loading the memory for file %s...", path); + } + + SafeDeleteArray(memblock); + } + else + { + Log("There was a problem loading the file %s...", path); + m_bLoaded = false; + } + + if (!m_bLoaded) + { + Reset(); + } + + return m_bLoaded; +} + +bool Cartridge::LoadFromBuffer(const u8* buffer, int size) +{ + if (IsValidPointer(buffer)) + { + Log("Loading from buffer... Size: %d", size); + m_iTotalSize = size; + m_pTheROM = new u8[m_iTotalSize]; + memcpy(m_pTheROM, buffer, m_iTotalSize); + m_bLoaded = true; + return GatherMetadata(); + } + else + return false; +} + +void Cartridge::CheckCartridgeType(int type) +{ + if ((type != 0xEA) && (GetROMSize() == 0)) + type = 0; + + switch (type) + { + case 0x00: + // NO MBC + case 0x08: + // ROM + // SRAM + case 0x09: + // ROM + // SRAM + // BATT + m_Type = CartridgeNoMBC; + break; + case 0x01: + // MBC1 + case 0x02: + // MBC1 + // SRAM + case 0x03: + // MBC1 + // SRAM + // BATT + case 0xEA: + // Hack to accept 0xEA as a MBC1 (Sonic 3D Blast 5) + case 0xFF: + // Hack to accept HuC1 as a MBC1 + m_Type = CartridgeMBC1; + break; + case 0x05: + // MBC2 + // SRAM + case 0x06: + // MBC2 + // SRAM + // BATT + m_Type = CartridgeMBC2; + break; + case 0x0F: + // MBC3 + // TIMER + // BATT + case 0x10: + // MBC3 + // TIMER + // BATT + // SRAM + case 0x11: + // MBC3 + case 0x12: + // MBC3 + // SRAM + case 0x13: + // MBC3 + // BATT + // SRAM + case 0xFC: + // Game Boy Camera + m_Type = CartridgeMBC3; + break; + case 0x19: + // MBC5 + case 0x1A: + // MBC5 + // SRAM + case 0x1B: + // MBC5 + // BATT + // SRAM + case 0x1C: + // RUMBLE + case 0x1D: + // RUMBLE + // SRAM + case 0x1E: + // RUMBLE + // BATT + // SRAM + m_Type = CartridgeMBC5; + break; + case 0x0B: + // MMMO1 + case 0x0C: + // MMM01 + // SRAM + case 0x0D: + // MMM01 + // SRAM + // BATT + case 0x15: + // MBC4 + case 0x16: + // MBC4 + // SRAM + case 0x17: + // MBC4 + // SRAM + // BATT + case 0x22: + // MBC7 + // BATT + // SRAM + case 0x55: + // GG + case 0x56: + // GS3 + case 0xFD: + // TAMA 5 + case 0xFE: + // HuC3 + m_Type = CartridgeNotSupported; + Log("--> ** This cartridge is not supported. Type: %d", type); + break; + default: + m_Type = CartridgeNotSupported; + Log("--> ** Unknown cartridge type: %d", type); + } + + switch (type) + { + case 0x03: + case 0x06: + case 0x09: + case 0x0D: + case 0x0F: + case 0x10: + case 0x13: + case 0x17: + case 0x1B: + case 0x1E: + case 0x22: + case 0xFD: + case 0xFF: + m_bBattery = true; + break; + default: + m_bBattery = false; + } + + switch (type) + { + case 0x0F: + case 0x10: + m_bRTCPresent = true; + break; + default: + m_bRTCPresent = false; + } + + switch (type) + { + case 0x1C: + case 0x1D: + case 0x1E: + m_bRumblePresent = true; + break; + default: + m_bRumblePresent = false; + } +} + +int Cartridge::GetVersion() const +{ + return m_iVersion; +} + +bool Cartridge::IsSGB() const +{ + return m_bSGB; +} + +bool Cartridge::IsCGB() const +{ + return m_bCGB; +} + +void Cartridge::UpdateCurrentRTC() +{ + time(&m_RTCCurrentTime); +} + +time_t Cartridge::GetCurrentRTC() +{ + return m_RTCCurrentTime; +} + +bool Cartridge::IsRTCPresent() const +{ + return m_bRTCPresent; +} + +bool Cartridge::IsRumblePresent() const +{ + return m_bRumblePresent; +} + +void Cartridge::SetGameGenieCheat(const char* szCheat) +{ + std::string code(szCheat); + for (std::string::iterator p = code.begin(); code.end() != p; ++p) + *p = toupper(*p); + + if (m_bLoaded && (code.length() > 6) && ((code[3] < '0') || ((code[3] > '9') && (code[3] < 'A')))) + { + u8 new_value = (AsHex(code[0]) << 4 | AsHex(code[1])) & 0xFF; + u16 cheat_address = (AsHex(code[2]) << 8 | AsHex(code[4]) << 4 | AsHex(code[5]) | (AsHex(code[6]) ^ 0xF) << 12) & 0x7FFF; + bool avoid_compare = true; + u8 compare_value = 0; + + if ((code.length() == 11) && ((code[7] < '0') || ((code[7] > '9') && (code[7] < 'A')))) + { + compare_value = (AsHex(code[8]) << 4 | AsHex(code[10])) ^ 0xFF; + compare_value = ((compare_value >> 2 | compare_value << 6) ^ 0x45) & 0xFF; + avoid_compare = false; + } + + for (int bank = 0; bank < GetROMBankCount(); bank++) + { + int bank_address = (bank * 0x4000) + (cheat_address & 0x3FFF); + + if (avoid_compare || (m_pTheROM[bank_address] == compare_value)) + { + GameGenieCode undo_data; + undo_data.address = bank_address; + undo_data.old_value = m_pTheROM[bank_address]; + + m_pTheROM[bank_address] = new_value; + + m_GameGenieList.push_back(undo_data); + } + } + } +} + +void Cartridge::ClearGameGenieCheats() +{ + std::list<GameGenieCode>::iterator it; + + for (it = m_GameGenieList.begin(); it != m_GameGenieList.end(); it++) + { + m_pTheROM[it->address] = it->old_value; + } + + m_GameGenieList.clear(); +} + +unsigned int Cartridge::Pow2Ceil(unsigned int n) +{ + --n; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + ++n; + return n; +} + +bool Cartridge::GatherMetadata() +{ + char name[12] = {0}; + name[11] = 0; + + for (int i = 0; i < 11; i++) + { + name[i] = m_pTheROM[0x0134 + i]; + + if (name[i] == 0) + { + break; + } + } + + strcpy(m_szName, name); + + m_bCGB = (m_pTheROM[0x143] == 0x80) || (m_pTheROM[0x143] == 0xC0); + m_bSGB = (m_pTheROM[0x146] == 0x03); + int type = m_pTheROM[0x147]; + m_iROMSize = m_pTheROM[0x148]; + m_iRAMSize = m_pTheROM[0x149]; + m_iVersion = m_pTheROM[0x14C]; + + CheckCartridgeType(type); + + switch (m_iRAMSize) + { + case 0x00: + m_iRAMBankCount = (m_Type == Cartridge::CartridgeMBC2) ? 1 : 0; + break; + case 0x01: + case 0x02: + m_iRAMBankCount = 1; + break; + case 0x04: + m_iRAMBankCount = 16; + break; + default: + m_iRAMBankCount = 4; + break; + } + + m_iROMBankCount = std::max(Pow2Ceil(m_iTotalSize / 0x4000), 2u); + + bool presumeMultiMBC1 = ((type == 1) && (m_iRAMSize == 0) && (m_iROMBankCount == 64)); + + if ((m_Type == Cartridge::CartridgeMBC1) && presumeMultiMBC1) + { + m_Type = Cartridge::CartridgeMBC1Multi; + Log("Presumed Multi 64"); + } + + Log("Cartridge Size %d", m_iTotalSize); + Log("ROM Name %s", m_szName); + Log("ROM Version %d", m_iVersion); + Log("ROM Type %X", type); + Log("ROM Size %X", m_iROMSize); + Log("ROM Bank Count %d", m_iROMBankCount); + Log("RAM Size %X", m_iRAMSize); + Log("RAM Bank Count %d", m_iRAMBankCount); + + switch (m_Type) + { + case Cartridge::CartridgeNoMBC: + Log("No MBC found"); + break; + case Cartridge::CartridgeMBC1: + Log("MBC1 found"); + break; + case Cartridge::CartridgeMBC1Multi: + Log("MBC1 Multi 64 found"); + break; + case Cartridge::CartridgeMBC2: + Log("MBC2 found"); + break; + case Cartridge::CartridgeMBC3: + Log("MBC3 found"); + break; + case Cartridge::CartridgeMBC5: + Log("MBC5 found"); + break; + case Cartridge::CartridgeNotSupported: + Log("Cartridge not supported!!"); + break; + default: + break; + } + + if (m_bBattery) + { + Log("Battery powered RAM found"); + } + + if (m_pTheROM[0x143] == 0xC0) + { + Log("Game Boy Color only"); + } + else if (m_bCGB) + { + Log("Game Boy Color supported"); + } + + if (m_bSGB) + { + Log("Super Game Boy supported"); + } + + int checksum = 0; + + for (int j = 0x134; j < 0x14E; j++) + { + checksum += m_pTheROM[j]; + } + + m_bValidROM = ((checksum + 25) & 0xFF) == 0; + + if (m_bValidROM) + { + Log("Checksum OK!"); + } + else + { + Log("Checksum FAILED!!!"); + } + + return (m_Type != CartridgeNotSupported); +} |
