summaryrefslogtreecommitdiffstats
path: root/gearboy/src/Cartridge.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gearboy/src/Cartridge.cpp')
-rw-r--r--gearboy/src/Cartridge.cpp689
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);
+}