From 925cbf1f8813df170cd934b08a49addf45673d0e Mon Sep 17 00:00:00 2001 From: Michael McMaster Date: Mon, 12 Sep 2011 08:58:49 +1000 Subject: [PATCH] Interim checkin. Bugfixes Galore, plus MBC1,2,3 support. --- .gitmodules | 3 + ALU.hh | 24 +-- Core.cc | 79 +++++----- Core.hh | 3 +- GameboyCart.cc | 338 +++++++++++++++++++++++++++++------------- GameboyCart.hh | 25 ++++ GameboyGraphics.cc | 14 +- GameboyJoypad.cc | 6 +- GameboySound.cc | 1 - InstructionSet.opcode | 13 ++ Log.cc | 70 +++++++-- Log.hh | 78 ++++++++-- MBC1.cc | 114 ++++++++++++++ MBC1.hh | 55 +++++++ MBC2.cc | 79 ++++++++++ MBC2.hh | 49 ++++++ MBC3.cc | 100 +++++++++++++ MBC3.hh | 49 ++++++ Main.cc | 137 +++++------------ Makefile.am | 14 +- MemoryMap.cc | 13 +- NEWS | 4 + RAM.hh | 1 + RAM_4bit.cc | 49 ++++++ RAM_4bit.hh | 46 ++++++ ROM.cc | 5 +- Unzip.cc | 129 ---------------- autogen.sh | 1 + configure.ac | 2 + libzipper | 1 + makeOpcodes.pl | 9 +- 31 files changed, 1078 insertions(+), 433 deletions(-) create mode 100644 .gitmodules create mode 100644 MBC1.cc create mode 100644 MBC1.hh create mode 100644 MBC2.cc create mode 100644 MBC2.hh create mode 100644 MBC3.cc create mode 100644 MBC3.hh create mode 100644 RAM_4bit.cc create mode 100644 RAM_4bit.hh delete mode 100644 Unzip.cc create mode 160000 libzipper diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a77e7a7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libzipper"] + path = libzipper + url = git://www.codesrc.com/git/libzipper diff --git a/ALU.hh b/ALU.hh index aa51fda..d71a1e6 100644 --- a/ALU.hh +++ b/ALU.hh @@ -32,12 +32,8 @@ extern const uint8_t ParityFlagLookup[256]; inline uint8_t add(uint8_t a, uint8_t b, Flags& flags, bool carry = false) { - uint8_t result(a + b); - - if (carry && flags.C) - { - ++result; - } + uint8_t c = (carry && flags.C) ? 1 : 0; + uint8_t result(a + b + c); flags.byte = (result & 0x80) | // S MSB @@ -56,7 +52,7 @@ add(uint8_t a, uint8_t b, Flags& flags, bool carry = false) ((((~(a ^ b)) & (result ^ a)) & 0x80) >> 5) |// P // N - (((uint16_t(a) + uint16_t(b)) & 0x100) >> 8); // C + (((uint16_t(a) + uint16_t(b) + c) & 0x100) >> 8); // C return result; } @@ -70,15 +66,11 @@ adc(uint8_t a, uint8_t b, Flags& flags) inline DWORD add(DWORD a, DWORD b, Flags& flags, bool carry = false) { + uint8_t c = (carry && flags.C) ? 1 : 0; const uint16_t aHost(a.host()); const uint16_t bHost(b.host()); - uint16_t result(aHost + bHost); - - if (carry && flags.C) - { - ++result; - } + uint16_t result(aHost + bHost + c); if (carry) { @@ -91,7 +83,7 @@ add(DWORD a, DWORD b, Flags& flags, bool carry = false) flags.N = 0; flags.H = ((aHost ^ bHost ^ result) >> 10) & 0x1; - flags.C = (((uint32_t(aHost) + uint32_t(bHost)) & 0x10000) >> 16); + flags.C = (((uint32_t(aHost) + uint32_t(bHost) + c) & 0x10000) >> 16); return glBoy::htoz(result); } @@ -123,7 +115,7 @@ sub(uint8_t a, uint8_t b, Flags& flags, bool borrow = false) static_cast(result) ? 0 : 0x4) | // P 0x2 | // N - (((uint16_t(a) - uint16_t(b)) & 0x100) >> 8); // C + (((uint16_t(a) - uint16_t(b) - c) & 0x100) >> 8); // C return result; } @@ -158,7 +150,7 @@ sub(DWORD a, DWORD b, Flags& flags, bool borrow = false) static_cast(result) ? 0 : 0x4) | // P 0x2 | // N - (((uint32_t(aHost) - uint32_t(bHost)) & 0x10000) >> 16); // C + (((uint32_t(aHost) - uint32_t(bHost) - c) & 0x10000) >> 16); // C return htoz(result); } diff --git a/Core.cc b/Core.cc index 9e8224e..8daf450 100644 --- a/Core.cc +++ b/Core.cc @@ -23,14 +23,18 @@ #include "util.hh" #include +#include #include #include +#include // ffs + using namespace glBoy; Core::Core() : m_iff1(0), m_iff2(0), + m_pendingInterrupts(0), m_halted(false), m_clock(0), m_nextPeriodicCallback(std::numeric_limits::max()) @@ -42,26 +46,15 @@ Core::Core() : void Core::raiseInterrupt(int interrupt) { - if (m_iff1 != 0) // Are interrupts enabled ? - { - // TODO this interrupt handling is GB80 specific - bool enabled(false); + // TODO this interrupt handling is GB80 specific - uint8_t flags(m_mem.read8(htoz(0xFFFF))); - uint8_t outFlags(m_mem.read8(htoz(0xFF0F))); - - switch (interrupt) - { - case 0x40: enabled = flags & 0x1; outFlags |= 0x1; break; - case 0x48: enabled = flags & 0x2; outFlags |= 0x2; break; - case 0x50: enabled = flags & 0x4; outFlags |= 0x4; break; - case 0x58: enabled = flags & 0x8; outFlags |= 0x8; break; - case 0x60: enabled = flags & 0x10; outFlags += 0x10; break; - } - if (enabled) - { - m_interruptQueue.push_back(interrupt); - } + switch (interrupt) + { + case 0x40: m_pendingInterrupts |= 0x1; break; + case 0x48: m_pendingInterrupts |= 0x2; break; + case 0x50: m_pendingInterrupts |= 0x4; break; + case 0x58: m_pendingInterrupts |= 0x8; break; + case 0x60: m_pendingInterrupts |= 0x10; break; } } @@ -76,8 +69,6 @@ Core::run() // handler // TODO z80 only. Unused for gb80 uint8_t doublePrefixOperand(0); - Log& log(Log::Instance()); - while (true) { cycle_t clock = 0; @@ -85,21 +76,37 @@ Core::run() uint16_t prefix(0); // Used for debug logging only. // Check for interrupts. - if (!m_interruptQueue.empty()) + if (m_pendingInterrupts) { - int interrupt = m_interruptQueue.front(); - m_interruptQueue.pop_front(); + // Only call ONE interrupt, even if multiple triggered. + uint8_t priorityBit(1 << (ffs(m_pendingInterrupts) - 1)); + + // We clear the IF of the interrupt we end up calling. + // BUT if there are 2 simulataneous interrupts, it is left + // with bits set, but never called. + uint8_t iFlag(m_mem.read8(htoz(0xFF0F))); + iFlag |= m_pendingInterrupts; + m_pendingInterrupts = 0; - m_iff1 = 0; - m_iff2 = 0; + uint8_t ie(m_mem.read8(htoz(0xFFFF))); + + if (m_iff1 && (ie & priorityBit)) + { + // Clear the IF of ONLY the interrupt we end up calling. + iFlag ^= priorityBit; - // TODO interrupt modes! - m_reg.SP.dec(2); - m_mem.write16(m_reg.SP, m_reg.PC); + m_iff1 = 0; + m_iff2 = 0; - m_reg.PC = htoz(interrupt); + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.PC); - m_halted = false; + static const int addr[] = {0x40, 0x48, 0x50, 0x58, 0x60}; + m_reg.PC = htoz(addr[ffs(priorityBit) - 1]); + + m_halted = false; + } + m_mem.write8(htoz(0xFF0F), iFlag); } else if (!m_halted) { @@ -111,9 +118,10 @@ Core::run() #include "InstructionSet_Opcodes.cc" BAD_OPCODE: - log(Log::DBG_ERROR) << - "Unknown Opcode: " << glBoy::hex(opcode) << " (PC=" << opcodePC << ")" << - std::endl; + Err() << + "Unknown Opcode: " << glBoy::hex(opcode) << + " (PC=" << opcodePC << ")" << + Log::endl; assert(false); END_INSTRUCTIONS: ; @@ -125,7 +133,8 @@ END_INSTRUCTIONS: ; m_clock += clock; - if (m_halted && m_interruptQueue.empty()) + + if (m_halted && (m_pendingInterrupts == 0)) { m_clock = std::max(m_clock, m_nextPeriodicCallback); } diff --git a/Core.hh b/Core.hh index 167fbe5..a2b10b0 100644 --- a/Core.hh +++ b/Core.hh @@ -23,7 +23,6 @@ #include "MemoryMap.hh" #include "Registers.hh" -#include #include #include #include @@ -67,12 +66,12 @@ private: // Interrupt enablement bit m_iff1; bit m_iff2; + uint8_t m_pendingInterrupts; enum InterruptMode { IM_0, IM_1, IM_2 }; InterruptMode m_interruptMode; bool m_halted; - std::deque m_interruptQueue; MemoryMap m_mem; diff --git a/GameboyCart.cc b/GameboyCart.cc index 28badeb..a48313f 100644 --- a/GameboyCart.cc +++ b/GameboyCart.cc @@ -17,150 +17,251 @@ #include "glBoy.hh" #include "GameboyCart.hh" +#include "Log.hh" +#include "MBC1.hh" +#include "MBC2.hh" +#include "MBC3.hh" #include "RAM.hh" +#include "RAM_4bit.hh" #include "ROM.hh" +#include "libzipper/zipper.hh" + +#include +#include + #include using namespace glBoy; +using namespace zipper; using namespace std; namespace { - class MBC1 : public MemoryMap::Memory - { - public: - MBC1(GameboyCart* parent, const uint8_t* begin, const uint8_t* end) : - m_parent(parent), - m_rom(begin, end), - m_mode(ROM_SELECT), - m_romLow(1), - m_romHigh(0), - m_romOffset(0) - { - } + class CartWriter : public zipper::Writer + { + public: + std::vector data; - virtual uint8_t read8(uint16_t address) - { - if (address < 0x4000) + CartWriter() : + data(GameboyCart::MaxRomBytes) { - return m_rom[address]; } - else if ((m_romOffset + address) < m_rom.size()) - { - return m_rom[m_romOffset + address]; - } - else - { -std::cerr << address << " " << m_rom.size() << " " << m_romOffset << " " << (int) m_romLow << " " << (int) m_romHigh << std::endl; -assert(false); - return 0; - } - } - virtual void write8(uint16_t address, uint8_t value) - { - if (address < 0x1FFF) + bool isValid() const { - // TODO enable/disable ram - } - else if (address < 0x4000) - { - m_romLow = (value == 0) ? 1 : value; + bool isValid = true; + isValid &= data.size() >= GameboyCart::MinRomBytes; + isValid &= data.size() <= GameboyCart::MaxRomBytes; - m_romOffset = - ((((m_romHigh & 0x3) << 4) | (m_romLow & 0x1F)) - 1) * 0x4000; -assert(m_romOffset < m_rom.size()); - } - else if (address < 0x6000) - { - if (m_mode == RAM_SELECT) - { - m_parent->setRamBank(value & 0x3); - } - else - { - m_romHigh = value; + // Check some magic bytes. This is part of the "Nintendo" + // power-on logo that is verified on boot. + isValid &= data[0x104] == 0xCE; + isValid &= data[0x105] == 0xED; + isValid &= data[0x132] == 0x33; + isValid &= data[0x133] == 0x3E; - m_romOffset = - ((((m_romHigh & 0x3) << 4) | (m_romLow & 0x1F)) - 1) * 0x4000; -assert(m_romOffset < m_rom.size()); - } + return isValid; } - else + + virtual void writeData( + zsize_t offset, zsize_t bytes, const uint8_t* inData) { - if (value & 1) + if (offset + bytes <= GameboyCart::MaxRomBytes) { - m_mode = RAM_SELECT; + data.resize(std::max(offset + bytes, data.size())); + std::copy(inData, inData + bytes, &data[offset]); } else { - m_mode = ROM_SELECT; - m_parent->setRamBank(0); + data.clear(); } } - } - - private: - GameboyCart* m_parent; - - std::vector m_rom; - - enum Mode { ROM_SELECT, RAM_SELECT }; - Mode m_mode; - - uint8_t m_romLow; - uint8_t m_romHigh; - - uint32_t m_romOffset; + virtual zsize_t getSize() const { return data.size(); } }; } GameboyCart::GameboyCart( Core& core, const std::vector& rom ) : - m_core(core) + m_core(core), + m_isValid(false), + m_battery(false), + m_rtc(false), + m_rumble(false) +{ + init(rom); +} + +GameboyCart::GameboyCart( + Core& core, const std::string& filename + ) : + m_core(core), + m_isValid(false), + m_battery(false), + m_rtc(false), + m_rumble(false) { - if (rom.size() < 16384) + CartWriter writer; + zipper::FileReader reader(filename); + zipper::Decompressor decomp(reader); + + typedef std::vector cfptr; + cfptr entries(decomp.getEntries()); + + bool found(false); + for (cfptr::iterator it(entries.begin()); + !found && it != entries.end(); + ++it) + { + writer.data.clear(); + (*it)->decompress(writer); + found = writer.isValid();; + } + + if (!found) { - std::cerr << "Invalid ROM (too small)" << std::endl; + Err() << "Could not find valid ROM file." << Log::endl; abort(); } + init(writer.data); +} + +void +GameboyCart::init(const std::vector& rom) +{ + m_isValid = false; + + if (rom.size() < GameboyCart::MinRomBytes) + { + Err() << "Invalid ROM (too small)" << Log::endl; + return; + } - bool error(false); + bool supported(true); - string mbc; switch (rom[0x147]) { - case 0: + case 0x9: m_battery = true; + case 0x8: + case 0x0: + { + m_mbc = "ROM"; + + size_t romSize(std::min(0x8000ul, rom.size())); + shared_ptr mem( + new ROM(&rom[0], romSize) + ); + m_core.getMemoryMap().map(0, romSize, mem); + m_romBanks.push_back(mem); + }; break; + + case 0x3: m_battery = true; + case 0x1: + case 0x2: { - mbc = "None"; + m_mbc = "MBC1"; + size_t romSize(std::min(0x200000ul, rom.size())); shared_ptr mem( - new MBC1(this, &rom[0], &rom[0] + rom.size()) + new MBC1(this, &rom[0], &rom[0] + romSize) ); -/* shared_ptr mem( - new ROM(rom, size) - );*/ m_core.getMemoryMap().map(0, 0x8000, mem); m_romBanks.push_back(mem); }; break; - case 1: - case 2: - case 3: + + case 0x6: m_battery = true; + case 0x5: { - mbc = "MBC1"; + m_mbc = "MBC2"; + + size_t romSize(std::min(0x40000ul, rom.size())); shared_ptr mem( - new MBC1(this, &rom[0], &rom[0] + rom.size()) + new MBC2(this, &rom[0], &rom[0] + romSize) ); m_core.getMemoryMap().map(0, 0x8000, mem); m_romBanks.push_back(mem); - }; - break; + + // MBC2 contains 512x4bits built-in ram. + shared_ptr ram(new RAM_4bit(512)); + m_core.getMemoryMap().map(0xA000, 512, ram); + m_ramBanks.push_back(ram); + }; break; + + case 0x0F: + case 0x10: m_rtc = true; + case 0x13: m_battery = true; + case 0x11: + case 0x12: + { + m_mbc = "MBC3"; + + size_t romSize(std::min(0x200000ul, rom.size())); + shared_ptr mem( + new MBC3(this, &rom[0], &rom[0] + romSize) + ); + m_core.getMemoryMap().map(0, 0x8000, mem); + m_romBanks.push_back(mem); + }; break; + + case 0x0D: m_battery = true; + case 0x0B: + case 0x0C: + m_mbc = "MMM01 (unsupported)"; + supported = false; + break; + + case 0x17: m_battery = true; + case 0x15: + case 0x16: + m_mbc = "MBC4 (unsupported)"; + supported = false; + break; + + case 0x1B: + case 0x1E: m_battery = true; + case 0x1A: + case 0x1C: + case 0x1D: + m_mbc = "MBC5 (unsupported)"; + supported = false; + + if (rom[0x147] >= 0x1C || + rom[0x147] <= 0x1E) + { + m_rumble = true; + } + break; + + case 0xFC: + m_mbc = "Pocket Camera (unsupported)"; + supported = false; + break; + + case 0xFD: + m_mbc = "Bandai TAMA5 (unsupported)"; + supported = false; + break; + + case 0xFE: + m_mbc = "HuC3 (unsupported)"; + supported = false; + break; + + case 0xFF: + m_mbc = "HuC1 (unsupported)"; + m_battery = true; + supported = false; + break; + default: - mbc = "Unsupported"; - error = true; + { + std::stringstream ss; + ss << "Unsupported (" << glBoy::hex(rom[0x147]) << ")"; + m_mbc = ss.str(); + supported = false; + } }; int cartRam; @@ -168,11 +269,7 @@ GameboyCart::GameboyCart( { case 0: { - // Mis-reported on some carts. cartRam = 0; - shared_ptr mem(new RAM(8192)); - m_core.getMemoryMap().map(0xA000, 8192, mem); - m_ramBanks.push_back(mem); }; break; case 1: { @@ -204,23 +301,29 @@ GameboyCart::GameboyCart( }; break; default: - error = true; + supported = false; cartRam = 0; - cerr << "Invalid RAM size" << endl; + Err() << "Invalid RAM size" << Log::endl; } - string title(&rom[0x134], &rom[0x134 + 11]); - cout << "Loading Cart" << endl; - cout << "\tTitle:\t\t" << title << endl; - cout << "\tRegion:\t\t" << (rom[0x14A] ? "Worldwide" : "Japan") << endl; - cout << "\tVersion:\t" << int(rom[0x14D]) << endl; - cout << "\tMBC:\t\t" << mbc << endl; - cout << "\tROM size:\t" << (32 << rom[0x148]) << "KB" << endl; - cout << "\tExt RAM size:\t" << (cartRam / 1024) << "KB" << endl; + m_isValid = supported; - if (error) + m_title = string(&rom[0x134], &rom[0x134 + 11]); + m_region = rom[0x14A] ? "Worldwide" : "Japan"; { - abort(); + std::stringstream tmp; + tmp << int(rom[0x14D]); + tmp >> m_version; + } + { + std::stringstream tmp; + tmp << (32 << rom[0x148]) << "KB"; + tmp >> m_romSize; + } + { + std::stringstream tmp; + tmp << (cartRam / 1024) << "KB"; + tmp >> m_extRamSize; } } @@ -233,3 +336,24 @@ GameboyCart::setRamBank(size_t bank) m_core.getMemoryMap().map(0xA000, 8192, m_ramBanks[bank]); } } + +void +GameboyCart::dump(std::ostream& out) +{ + out << + "\tTitle:\t\t" << m_title << endl << + "\tRegion:\t\t" << m_region << endl << + "\tVersion:\t" << m_version << endl << + "\tMemory Type:\t" << m_mbc << endl << + "\tROM size:\t" << m_romSize << endl << + "\tExt RAM size:\t" << m_extRamSize << endl << + "\tBattery:\t" << (m_battery ? "true" : "false") << endl << + "\tClock:\t\t" << (m_rtc ? "true" : "false") << endl << + "\tRumble:\t\t" << (m_rumble ? "true" : "false") << endl; +} + +bool +GameboyCart::isValid() const +{ + return m_isValid; +} diff --git a/GameboyCart.hh b/GameboyCart.hh index 0588cd1..c5c1589 100644 --- a/GameboyCart.hh +++ b/GameboyCart.hh @@ -20,6 +20,7 @@ #include "glBoy.hh" #include "Core.hh" +#include #include namespace glBoy @@ -28,14 +29,38 @@ namespace glBoy class GameboyCart { public: + enum + { + MinRomBytes = 16384, + MaxRomBytes = 0x4000000 // 4MByte, 256 banks + }; + GameboyCart(Core& core, const std::vector& rom); + GameboyCart(Core& core, const std::string& filename); + + bool isValid() const; + + void dump(std::ostream& out); void setRamBank(size_t bank); private: + void init(const std::vector& rom); Core& m_core; + bool m_isValid; std::vector > m_romBanks; std::vector > m_ramBanks; + + + std::string m_title; + std::string m_region; + std::string m_version; + std::string m_romSize; + std::string m_extRamSize; + std::string m_mbc; + bool m_battery; + bool m_rtc; + bool m_rumble; }; } // namespace glBoy diff --git a/GameboyGraphics.cc b/GameboyGraphics.cc index 57cae8a..651666f 100644 --- a/GameboyGraphics.cc +++ b/GameboyGraphics.cc @@ -372,10 +372,10 @@ GameboyGraphics::startScanLine() m_frameSkip = false; displayStats(); - //if (m_reg->STAT & 0x10) Seems to need to trigger regardless ? - // Maybe we need to trigger 0x40 as well, depending on STAT ? + m_core.raiseInterrupt(0x40); // Start of vblank + if (m_reg->STAT & 0x10) { - m_core.raiseInterrupt(0x40); // Start of vblank + m_core.raiseInterrupt(0x48); } } else if ( @@ -668,9 +668,11 @@ GameboyGraphics::displayStats() (startTime.tv_sec + startTime.tv_usec / usec) ); - Log::Instance()(Log::DBG_STAT) << - "FPS = " << fps << " (error=" << (100 - (fps * 100 / 59.73)) << "%) " - "backround redraw=" << m_stat_redraw << std::endl; + Stat() << + "FPS = " << fps << + " (error=" << (100 - (fps * 100 / 59.73)) << "%) " << + "backround redraw=" << m_stat_redraw << + Log::endl; m_stat_frames = 0; m_stat_skipped = 0; m_stat_redraw = 0; diff --git a/GameboyJoypad.cc b/GameboyJoypad.cc index 73868dd..dd5244a 100644 --- a/GameboyJoypad.cc +++ b/GameboyJoypad.cc @@ -121,8 +121,12 @@ GameboyJoypad::read8(uint16_t) void GameboyJoypad::write8(uint16_t, uint8_t value) { - switch (value) + switch (value & 0x30) { + case 0x00: + // Should really be some sort of odd "both on" situation ? + m_state = Joypad_Off; + break; case 0x20: m_state = Joypad_Directional; break; diff --git a/GameboySound.cc b/GameboySound.cc index 606db98..09c0734 100644 --- a/GameboySound.cc +++ b/GameboySound.cc @@ -37,7 +37,6 @@ extern "C" GameboySound::GameboySound(Core& core) { - extern void mixaudio(void *unused, Uint8 *stream, int len); SDL_AudioSpec fmt; fmt.freq = SAMPLE_RATE; diff --git a/InstructionSet.opcode b/InstructionSet.opcode index e4acc1f..ca022d8 100644 --- a/InstructionSet.opcode +++ b/InstructionSet.opcode @@ -1613,6 +1613,19 @@ TODO NOT IMPLEMENTED m_reg.F.C = 0; + + + uint8_t tmp(m_mem.read8(m_reg.HL)); + uint8_t r = tmp << 4; + r |= (tmp >> 4); + m_mem.write8(m_reg.HL, r); + + m_reg.F.Z = r == 0; + m_reg.F.N = 0; + m_reg.F.H = 0; + m_reg.F.C = 0; + + diff --git a/Log.cc b/Log.cc index b77d0ca..62dc21d 100644 --- a/Log.cc +++ b/Log.cc @@ -17,12 +17,50 @@ #include "Log.hh" +#include #include #include #include using namespace glBoy; +Log::End::End() {} +Log::End Log::endl; + +Log& glBoy::GetLog(int level) +{ + Log& log(Log::Instance()); + assert(log.m_currentLevel == Log::DBG_NONE); + log.m_currentLevel = Log::DebugLevel(level); + return log; +} + +Log& glBoy::Err() +{ + return GetLog(Log::DBG_ERROR); +} + +Log& glBoy::Warn() +{ + return GetLog(Log::DBG_WARNING); +} + +Log& glBoy::Info() +{ + return GetLog(Log::DBG_INFO); +} + +Log& glBoy::Stat() +{ + return GetLog(Log::DBG_STAT); +} + +Log& glBoy::TraceMem() +{ + return GetLog(Log::DBG_TRACE_MEM); +} + + Log& Log::Instance() { @@ -31,8 +69,8 @@ Log::Instance() } Log::Log() : - m_levelMask(7), - m_nullStream(&m_nullBuf) + m_levelMask(15), + m_currentLevel(DBG_NONE) { char* env(getenv("GLBOY_DEBUG_LEVEL")); if (env) @@ -52,21 +90,31 @@ Log::setDebugLevel(uint32_t levelMask) } bool -Log::isDebug(DebugLevel level) const +Log::isDebugLevelOn(DebugLevel level) const { return (m_levelMask & level) != 0; } -std::ostream& -Log::operator()(DebugLevel level) +void +Log::operator<<(const End&) { - if (isDebug(level)) + if (isDebugLevelOn(m_currentLevel)) { - return std::cout; - } - else - { - return m_nullStream; + switch (m_currentLevel) + { + case DBG_ERROR: + std::cerr << "ERROR: " << m_collector.str() << std::endl; + break; + case DBG_WARNING: + std::cerr << "WARNING: " << m_collector.str() << std::endl; + break; + default: + std::cout << m_collector.str() << std::endl; + }; } + + m_collector.str(std::string()); + m_collector.clear(); // clear error flags + m_currentLevel = DBG_NONE; } diff --git a/Log.hh b/Log.hh index 1afdb65..8aaf419 100644 --- a/Log.hh +++ b/Log.hh @@ -20,28 +20,84 @@ #include "glBoy.hh" +#include #include -#include -#include +#include namespace glBoy { + class Log; + + Log& GetLog(int level); + Log& Err(); + Log& Warn(); + Log& Info(); + Log& Stat(); + Log& TraceMem(); + class Log { public: + class End + { + public: + End(); + }; + static End endl; - enum DebugLevel + enum DebugLevel { - DBG_STAT = 1, DBG_ERROR = 2, DBG_WARNING = 4, DBG_MEMORY = 8, DBG_TRACE = 16 + DBG_ERROR = 1, + DBG_WARNING = 2, + DBG_INFO = 4, + DBG_STAT = 8, + DBG_TRACE_MEM = 16, + + DBG_NONE = 0xFFFFFFFF }; static Log& Instance(); - void setDebugLevel(uint32_t levelMask); + void operator<<(const End&); + + template Log& + operator<<(const T& val) + { + assert(m_currentLevel != DBG_NONE); + m_collector << val; + return *this; + } + + // << endl support + Log& + operator<<(std::ostream& (*val)(std::ostream&)) + { + assert(m_currentLevel != DBG_NONE); + m_collector << val; + return *this; + } + + Log& + operator<<(std::ios& (*val)(std::ios&)) + { + assert(m_currentLevel != DBG_NONE); + m_collector << val; + return *this; + } + + Log& + operator<<(std::ios_base& (*val)(std::ios_base&)) + { + assert(m_currentLevel != DBG_NONE); + m_collector << val; + return *this; + } - bool isDebug(DebugLevel level) const; - std::ostream& operator()(DebugLevel level); + void setDebugLevel(uint32_t levelMask); + bool isDebugLevelOn(DebugLevel level) const; private: + friend Log& GetLog(int); + Log(); Log(const Log&); @@ -49,13 +105,9 @@ namespace glBoy uint32_t m_levelMask; - class NullBuf : public std::streambuf - { - public: - }; + DebugLevel m_currentLevel; - NullBuf m_nullBuf; - std::ostream m_nullStream; + std::stringstream m_collector; }; } // namespace glBoy diff --git a/MBC1.cc b/MBC1.cc new file mode 100644 index 0000000..31defe3 --- /dev/null +++ b/MBC1.cc @@ -0,0 +1,114 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy 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 +// (at your option) any later version. +// +// glBoy 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 glBoy. If not, see . + +#include "glBoy.hh" +#include "MBC1.hh" +#include "Log.hh" + +#include + +using namespace glBoy; +using namespace std; + +MBC1::MBC1( + GameboyCart* parent, const uint8_t* begin, const uint8_t* end + ) : + m_parent(parent), + m_rom(begin, end), + m_mode(ROM_SELECT), + m_romLow(1), + m_romHigh(0), + m_romBank(0) +{ + if (end - begin < 0x8000) + { + Err() << "MBC1 ROM too small (" << end - begin << " bytes)" << Log::endl; + abort(); + } +} + +uint8_t +MBC1::read8(uint16_t address) +{ + if (address < 0x4000) + { + return m_rom[address]; + } + else if (((m_romBank * 0x4000) + (address - 0x4000)) < m_rom.size()) + { + return m_rom[(m_romBank * 0x4000) + (address - 0x4000)]; + } + else + { + TraceMem() << "Attempt to read unmapped MBC1 rom data." << Log::endl; + abort(); + } +} + +void +MBC1::write8(uint16_t address, uint8_t value) +{ + if (address < 0x1FFF) + { + // TODO enable/disable ram + } + else if (address < 0x4000) + { + m_romLow = ((value & 0x1F) == 0) ? 1 : (value & 0x1F); + m_romBank = m_romLow; + + if (m_mode == ROM_SELECT) + { + m_romBank |= (m_romHigh << 5); + } + +std::cerr << "MBC1 ROM LOW " << (int)m_romBank << std::endl; + } + else if (address < 0x6000) + { + m_romHigh = (value & 0x3); + if (m_mode == RAM_SELECT) + { + m_parent->setRamBank(m_romHigh); + } + else + { + m_romBank = m_romLow | (m_romHigh << 5); +std::cerr << "MBC1 ROM HIGH " << (int)m_romBank << std::endl; + } + } + else + { + if (value & 1) + { + m_mode = RAM_SELECT; + // In this mode only lower 5 bits of rom bank can be used + + m_parent->setRamBank(m_romHigh); + m_romBank = m_romLow; +std::cerr << "MBC1 RAM SELECT " << (int)m_romBank << std::endl; + } + else + { + m_mode = ROM_SELECT; + m_parent->setRamBank(0); + m_romBank = m_romLow | (m_romHigh << 5); +std::cerr << "MBC1 ROM SELECT " << (int)m_romBank << std::endl; + } + } +} + diff --git a/MBC1.hh b/MBC1.hh new file mode 100644 index 0000000..a2b61af --- /dev/null +++ b/MBC1.hh @@ -0,0 +1,55 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy 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 +// (at your option) any later version. +// +// glBoy 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 glBoy. If not, see . + +#ifndef GLBOY_MBC1_HH +#define GLBOY_MBC1_HH + +#include "glBoy.hh" +#include "GameboyCart.hh" + +#include + +namespace glBoy +{ + class MBC1 : public MemoryMap::Memory + { + public: + MBC1( + GameboyCart* parent, + const uint8_t* begin, + const uint8_t* end); + + virtual uint8_t read8(uint16_t address); + virtual void write8(uint16_t address, uint8_t value); + + private: + GameboyCart* m_parent; + + std::vector m_rom; + + enum Mode { ROM_SELECT, RAM_SELECT }; + Mode m_mode; + + uint8_t m_romLow; + uint8_t m_romHigh; + + uint32_t m_romBank; + }; +} + +#endif + diff --git a/MBC2.cc b/MBC2.cc new file mode 100644 index 0000000..000a830 --- /dev/null +++ b/MBC2.cc @@ -0,0 +1,79 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy 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 +// (at your option) any later version. +// +// glBoy 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 glBoy. If not, see . + +#include "glBoy.hh" +#include "MBC2.hh" +#include "Log.hh" + +#include + +using namespace glBoy; +using namespace std; + +MBC2::MBC2( + GameboyCart* parent, const uint8_t* begin, const uint8_t* end + ) : + m_parent(parent), + m_rom(begin, end), + m_romBank(0) +{ + if (end - begin < 0x8000) + { + Err() << "MBC2 ROM too small (" << end - begin << " bytes)" << Log::endl; + abort(); + } +} + +uint8_t +MBC2::read8(uint16_t address) +{ + if (address < 0x4000) + { + return m_rom[address]; + } + else if (((m_romBank * 0x4000) + (address - 0x4000)) < m_rom.size()) + { + return m_rom[(m_romBank * 0x4000) + (address - 0x4000)]; + } + else + { + TraceMem() << "Attempt to read unmapped MBC2 rom data." << Log::endl; + abort(); + } +} + +void +MBC2::write8(uint16_t address, uint8_t value) +{ + if (address < 0x1FFF) + { + // TODO enable/disable ram + } + else if (address < 0x4000) + { + // LSB of upper address byte must be 1 to select a ROM bank + if (address & 0x100) + { + m_romBank= (value == 0) ? 1 : (value & 0xF); + } + } + else + { + // Do nothing. + } +} + diff --git a/MBC2.hh b/MBC2.hh new file mode 100644 index 0000000..1445f83 --- /dev/null +++ b/MBC2.hh @@ -0,0 +1,49 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy 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 +// (at your option) any later version. +// +// glBoy 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 glBoy. If not, see . + +#ifndef GLBOY_MBC2_HH +#define GLBOY_MBC2_HH + +#include "glBoy.hh" +#include "GameboyCart.hh" + +#include + +namespace glBoy +{ + class MBC2 : public MemoryMap::Memory + { + public: + MBC2( + GameboyCart* parent, + const uint8_t* begin, + const uint8_t* end); + + virtual uint8_t read8(uint16_t address); + virtual void write8(uint16_t address, uint8_t value); + + private: + GameboyCart* m_parent; + + std::vector m_rom; + + uint32_t m_romBank; + }; +} + +#endif + diff --git a/MBC3.cc b/MBC3.cc new file mode 100644 index 0000000..750a38b --- /dev/null +++ b/MBC3.cc @@ -0,0 +1,100 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy 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 +// (at your option) any later version. +// +// glBoy 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 glBoy. If not, see . + +#include "glBoy.hh" +#include "MBC3.hh" +#include "Log.hh" + +#include + +using namespace glBoy; +using namespace std; + +MBC3::MBC3( + GameboyCart* parent, const uint8_t* begin, const uint8_t* end + ) : + m_parent(parent), + m_rom(begin, end), + m_romBank(0) +{ + if (end - begin < 0x8000) + { + Err() << "MBC3 ROM too small (" << end - begin << " bytes)" << Log::endl; + abort(); + } +} + +uint8_t +MBC3::read8(uint16_t address) +{ + if (address < 0x4000) + { + return m_rom[address]; + } + else if (((m_romBank * 0x4000) + (address - 0x4000)) < m_rom.size()) + { + return m_rom[(m_romBank * 0x4000) + (address - 0x4000)]; + } + else + { + TraceMem() << "Attempt to read unmapped MBC3 rom data." << Log::endl; + abort(); + } +} + +void +MBC3::write8(uint16_t address, uint8_t value) +{ + static bool rtcWarning(false); + + if (address < 0x1FFF) + { + // TODO enable/disable ram + } + else if (address < 0x4000) + { + m_romBank = value; + } + else if (address < 0x6000) + { + if (value <= 0x03) + { + m_parent->setRamBank(value); + } + else if (value <= 0x0C) + { + // TODO RTC Register Select + if (!rtcWarning) + { + Warn() << + "MBC3 RTC functionality not implemented!" << Log::endl; + rtcWarning = true; + } + } + } + else + { + // TODO Latch Clock Data + if (!rtcWarning) + { + Warn() << + "MBC3 RTC functionality not implemented!" << Log::endl; + rtcWarning = true; + } + } +} + diff --git a/MBC3.hh b/MBC3.hh new file mode 100644 index 0000000..0b7081a --- /dev/null +++ b/MBC3.hh @@ -0,0 +1,49 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy 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 +// (at your option) any later version. +// +// glBoy 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 glBoy. If not, see . + +#ifndef GLBOY_MBC3_HH +#define GLBOY_MBC3_HH + +#include "glBoy.hh" +#include "GameboyCart.hh" + +#include + +namespace glBoy +{ + class MBC3 : public MemoryMap::Memory + { + public: + MBC3( + GameboyCart* parent, + const uint8_t* begin, + const uint8_t* end); + + virtual uint8_t read8(uint16_t address); + virtual void write8(uint16_t address, uint8_t value); + + private: + GameboyCart* m_parent; + + std::vector m_rom; + + uint32_t m_romBank; + }; +} + +#endif + diff --git a/Main.cc b/Main.cc index c8c2786..bd7ae88 100644 --- a/Main.cc +++ b/Main.cc @@ -33,7 +33,7 @@ #include #include -#include +#include #include #include @@ -61,7 +61,7 @@ namespace SDL_Surface* initOpenGL(double xScale, double yScale, bool fullscreen) { - glBoy::Log::Instance()(glBoy::Log::DBG_STAT) << "Enabling OpenGL" << std::endl; + glBoy::Info() << "Enabling OpenGL" << glBoy::Log::endl; SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); @@ -91,24 +91,28 @@ namespace return surface; } - void usage(const std::string& exe) + void usage() { - std::cout << - "Usage: " << exe << " " - "[--scale ratio] [--width w] [--height h] \n" + std::cout << std::endl << + "Usage: " << std::endl << + "\tglBoy {-I|--info} rom.gb" << std::endl << + "\tglBoy [--scale ratio] [--width w] [--height h] \\\n" //"\t\t [--resample-method={bilinear|nearest} \n" //"\t\t[--scaling-method={hq4x|scale2x|none}] " - "rom.gb" << std::endl << + "\t\trom.gb" << std::endl << std::endl << std::endl << - "\t--scale\t(Default=4.0)" << std::endl << + "--info" << std::endl << + "\t\tDisplay cartridge information and exit." << std::endl << + "--scale" << std::endl << "\t\tMultiplier applied to the display output dimensions." << std::endl << - "\t--width" << std::endl << + "\t\tDefault: 4.0" << std::endl << + "--width" << std::endl << "\t\tSet the display output dimensions directly" << std::endl << - "\t--height" << std::endl << + "--height" << std::endl << "\t\tSet the display output dimensions directly" << std::endl; - /*"\t--scaling-method\n\t\t\t(Default=\"hq4x\")" << std::endl << + /*"--scaling-method\n\t\t\t(Default=\"hq4x\")" << std::endl << "\t\tSets the upscaling interpolation method." << std::endl << - "\t--resample-method\n\t\t\t(Default=\"bilinear\")" << std::endl << + "--resample-method\n\t\t\t(Default=\"bilinear\")" << std::endl << "\t\tSets the resampling method." << std::endl;*/ } @@ -147,78 +151,6 @@ namespace } } - std::vector readCart(const std::string& filename) - { - std::vector result; - - // Map the cart memory. - int fd(open(filename.c_str(), O_RDONLY)); - if (fd <= 0) - { - char* err(strerror(errno)); - std::cerr << "Failed to load file " << filename << std::endl << - err << std::endl; - exit(1); - } - - struct stat buf; - if (fstat(fd, &buf)) - { - assert(!"Failed fstat"); - } - - if (buf.st_size > 1024*1024) - { - // Probably not a rom at all. - std::cerr << "Cart too large" << std::endl; - exit(1); - } - - char zipHeader[2] = {'\0', '\0'}; - read(fd, zipHeader, sizeof(zipHeader)); - lseek(fd, 0, SEEK_SET); - - if (zipHeader[0] == 'P' && zipHeader[1] == 'K') - { - // Unzip - using namespace zipios; - try - { - ZipFile zf(filename); - ConstEntries entries(zf.entries()); - if (!entries.empty()) - { - glBoy::Log::Instance()(glBoy::Log::DBG_STAT) << - "Uncompressing " << entries.front()->getName() << std::endl; - std::auto_ptr is( - zf.getInputStream(entries.front()) - ); -std::cerr << "*" << std::endl; - result.resize(entries.front()->getSize()); -std::cerr << "*" << std::endl; - is->rdbuf()->sgetn( - reinterpret_cast(&result[0]), - result.size() - ); -std::cerr << "*" << std::endl; - } - } catch (std::exception& e) - { - std::cerr << "Error while reading file " << filename << - std::endl << - e.what() << std::endl; - } - } - else - { - result.resize(buf.st_size); - read(fd, &result[0], result.size()); - } - close(fd); - - return result; - } - } int main(int argc, char** argv) @@ -230,6 +162,7 @@ int main(int argc, char** argv) static struct option longOptions[] = { + {"info", 0, NULL, 'I'}, {"video", 1, NULL, 'v'}, {"scale", 1, NULL, 's'}, {"width", 1, NULL, 'w'}, @@ -269,12 +202,16 @@ int main(int argc, char** argv) int optionIndex(0); int c; + bool loadCartAndExit(false); while ( - (c = getopt_long(argc, argv, "s:w:h:", &longOptions[0], &optionIndex)) + (c = getopt_long(argc, argv, "s:w:h:I", &longOptions[0], &optionIndex)) != -1) { switch (c) { + case 'I': + loadCartAndExit = true; + break; case 'h': { int height(160); @@ -338,7 +275,7 @@ int main(int argc, char** argv) }; break; case '?': - usage(argv[0]); + usage(); exit(1); break; @@ -352,16 +289,23 @@ int main(int argc, char** argv) filename = argv[optind++]; } - if (access(filename.c_str(), R_OK) != 0) + glBoy::Core core; + + glBoy::Info() << + "Loading cartridge from file " << filename << glBoy::Log::endl; + + glBoy::GameboyCart cart(core, filename); + std::stringstream cartInfo; + cart.dump(cartInfo); + glBoy::Info() << cartInfo.str() << glBoy::Log::endl; + if (loadCartAndExit) { - char* error(strerror(errno)); - std::cerr << "Could not read file " << filename << - " (" << error << ")" << std::endl; - exit(1); + exit(0); + } + else if (!cart.isValid()) + { + abort(); } - - glBoy::Core core; - glBoy::MemoryMap& map(core.getMemoryMap()); SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO); @@ -372,12 +316,9 @@ int main(int argc, char** argv) SDL_WM_SetCaption("glBoy", "glBoy"); // TODO SDL_WM_SetIcon - std::vector cart(readCart(filename)); - glBoy::GameboyCart tmp(core, cart); - - // Map the cart mem. // Map the standard internal RAM, plus shadow. + glBoy::MemoryMap& map(core.getMemoryMap()); { std::shared_ptr mem(new glBoy::RAM(8192)); map.map(0xC000, 8192, mem); diff --git a/Makefile.am b/Makefile.am index 2d21f24..0dad624 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with glBoy. If not, see . +SUBDIRS = libzipper + check_PROGRAMS = \ test_alu @@ -61,8 +63,16 @@ glBoy_SOURCES = \ Main.cc \ MemoryMap.hh \ MemoryMap.cc \ + MBC1.hh \ + MBC1.cc \ + MBC2.hh \ + MBC2.cc \ + MBC3.hh \ + MBC3.cc \ RAM.hh \ RAM.cc \ + RAM_4bit.hh \ + RAM_4bit.cc \ Registers.hh \ Registers.cc \ ROM.hh \ @@ -79,7 +89,9 @@ CLEANFILES = \ InstructionSet_Opcodes.cc \ InstructionSet_Tables.cc -glBoy_LDFLAGS = -lGL -lGLU -lzipios +glBoy_LDADD = libzipper/libzipper.la + +glBoy_LDFLAGS = -lGL -lGLU CXXFLAGS=-g -O3 -march=native -W -Wall -Werror -std=c++0x diff --git a/MemoryMap.cc b/MemoryMap.cc index caf563f..3d8df1b 100644 --- a/MemoryMap.cc +++ b/MemoryMap.cc @@ -58,9 +58,9 @@ MemoryMap::map( ); m_map.insert(it, mapping); - Log::Instance()(Log::DBG_MEMORY) << + TraceMem() << "Registering " << glBoy::hex(base) << - " for " << glBoy::hex(size) << " bytes."<< std::endl; + " for " << glBoy::hex(size) << " bytes."<< Log::endl; } void @@ -73,9 +73,9 @@ MemoryMap::remove( if (it != m_map.end()) { - Log::Instance()(Log::DBG_MEMORY) << + TraceMem() << "Removing " << glBoy::hex(it->base) << - " for " << glBoy::hex(it->size) << " bytes."<< std::endl; + " for " << glBoy::hex(it->size) << " bytes."<< Log::endl; m_map.erase(it); } @@ -128,8 +128,9 @@ MemoryMap::get(uint16_t address) const const MemoryMap::MappedArea& MemoryMap::getFailed(uint16_t address) const { - Log::Instance()(Log::DBG_MEMORY) << - "Returning NULL memory for address " << glBoy::hex(address) << std::endl; + TraceMem() << + "Returning NULL memory for address " << glBoy::hex(address) << + Log::endl; static NullMemory s_null; static MappedArea s_nullArea( diff --git a/NEWS b/NEWS index 455df39..2483f02 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ - Increased performance for some NVidia Quadro cards (tested with NVS 290) - Added configurable key mappings. Copy glBoy.config to ~/.glBoy to change settings. + - Improved MBC1 support. + - Added MBC2 and MBC3 support. + - Added compressed ROM support (zip and gzip). + - Numerous z80 emulation bugfixes. 2011-01-31 Version 1.0.0 - Initial release diff --git a/RAM.hh b/RAM.hh index b7ae42f..636b5dd 100644 --- a/RAM.hh +++ b/RAM.hh @@ -35,6 +35,7 @@ public: private: + bool m_fourBitMode; std::vector m_mem; }; diff --git a/RAM_4bit.cc b/RAM_4bit.cc new file mode 100644 index 0000000..ca4db00 --- /dev/null +++ b/RAM_4bit.cc @@ -0,0 +1,49 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy 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 +// (at your option) any later version. +// +// glBoy 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 glBoy. If not, see . + +#include "glBoy.hh" +#include "RAM_4bit.hh" + +#include + +using namespace glBoy; + +RAM_4bit::RAM_4bit(uint16_t size) +{ + m_mem.resize(size); +} + + +uint8_t +RAM_4bit::read8(uint16_t address) +{ + return m_mem[address] & 0xF; +} + +void +RAM_4bit::write8(uint16_t address, uint8_t value) +{ + m_mem[address] = value & 0xF; +} + + +void +RAM_4bit::copy(uint8_t* dest, uint16_t address, int bytes) +{ + memcpy(dest, &m_mem[0] + address, bytes); +} + diff --git a/RAM_4bit.hh b/RAM_4bit.hh new file mode 100644 index 0000000..f1d169a --- /dev/null +++ b/RAM_4bit.hh @@ -0,0 +1,46 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy 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 +// (at your option) any later version. +// +// glBoy 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 glBoy. If not, see . + +#ifndef GLBOY_RAM_4BIT_HH +#define GLBOY_RAM_4BIT_HH + +#include "glBoy.hh" +#include "MemoryMap.hh" + +#include +namespace glBoy +{ + +class RAM_4bit : public MemoryMap::Memory +{ +public: + // 4bit RAM is built-in to the Gameboy MBC2 chip + RAM_4bit(uint16_t size); + + virtual uint8_t read8(uint16_t address); + virtual void write8(uint16_t address, uint8_t value); + virtual void copy(uint8_t* dest, uint16_t address, int bytes); + + +private: + std::vector m_mem; +}; + +} // namespace glBoy + +#endif + diff --git a/ROM.cc b/ROM.cc index 23fd637..c1d6234 100644 --- a/ROM.cc +++ b/ROM.cc @@ -39,10 +39,9 @@ ROM::read8(uint16_t address) void ROM::write8(uint16_t address, uint8_t) { - //assert(false); - Log::Instance()(Log::DBG_MEMORY) << + TraceMem() << "Attempt to write to read-only address " << address << - std::endl; + Log::endl; } void diff --git a/Unzip.cc b/Unzip.cc deleted file mode 100644 index 2f102cf..0000000 --- a/Unzip.cc +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (C) 2011 Michael McMaster -// -// This file is part of glBoy. -// -// glBoy 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 -// (at your option) any later version. -// -// glBoy 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 glBoy. If not, see . - -#include "glBoy.hh" - -// Simple Unzip implementation -// Limitations: -// - Does not support spanned archives. -// - Does not support spanned encryption. -// - Does not support zip64 - - -uint32_t -read32(const std::vector& zipData, size_t pos) -{ - // Read 4 bytes in little-endian order. - // Return results in host-endian. - return uint32_t( - zipData[pos] | - (static_cast(zipData[pos+1]) << 8) | - (static_cast(zipData[pos+2]) << 16) | - (static_cast(zipData[pos+3]) << 24) - ); -} - -uint16_t -read16(const std::vector& zipData, size_t pos) -{ - // Read 2 bytes in little-endian order. - // Return results in host-endian. - return uint16_t( - zipData[pos] | - (static_cast(zipData[pos+1]) << 8) | - ); -} - -struct EndCentralDir -{ - size_t centralDirectoryBytes; - size_t centralDirectoryOffset; - size_t centralDirectoryEntries; - std::string zipFileComment; -}; - -TODO lets make this somewhat full-featured. -Accept a CLASS as input, with a readZipBytes virtual method. -And accept a different class with a writeUncompressedBytes method when -uncompressing. -EndCentralDir readEndRecord(const std::vector& zipData) -{ - // Read the end of central directory record. This - // record enables us to find the remainding - // records without searching for record signatures. - - enum - { - MinRecordBytes = 22, // Minimum size with no comment - MaxCommentBytes = 65535, // 2 bytes to store comment length - Signature = 0x06054b50 - }; - - if (zipData.size() < MinRecordBytes) - { - throw ZipFormatException("Too small"); - } - - // Need to search for this record, as it ends in a variable-length - // comment field. Search backwards, with the assumption that the - // comment doesn't exist, or is much smaller than the maximum - // length - size_t end(0); - if (zipData.size() > MinRecordBytes + MaxCommentBytes) - { - end = zipData.size() - MinRecordBytes + MaxCommentBytes; - } - size_t start(zipData.size() - MinRecordBytes); - - bool recordFound(false); - size_t pos(start); - for (; pos >= end; --pos) - { - recordFound = (read32(zipData, pos) == Signature); - break; - } - - if (!recordFound) - { - throw ZipFormatException("ZIP directory records not found"); - } - - if (read16(zipData, pos + 4) != 1) - { - throw ZipUnsupportedException("Spanned disks not supported"); - } - - EndCentralDir result; - result.entralDirectoryBytes = read32(zipData, pos + 12); - result.centralDirectoryOffset = read32(zipData, pos + 16); - result.centralDirectoryEntries = read16(zipData, pos + 10); - - size_t commentLength(read16(zipData, pos + 20)); - size_t commentStart(pos + MinRecordBytes); - if (commentStart + commentLength > zipData.size()) - { - throw ZipFormatException("ZIP comment is too long"); - } - result.zipFileComment = - std::string( - &zipData[commentStart], - &zipData[commentStart + commentLength] - ); - - return result; -} - diff --git a/autogen.sh b/autogen.sh index 3f026c4..c9b18c0 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,2 +1,3 @@ #!/bin/sh +libzipper/autogen.sh autoreconf --force --install diff --git a/configure.ac b/configure.ac index 0042776..89fbefb 100644 --- a/configure.ac +++ b/configure.ac @@ -2,6 +2,7 @@ AC_INIT([glBoy], [1.0.1], [michael@codesrc.com]) AC_CANONICAL_HOST AC_CANONICAL_TARGET AM_INIT_AUTOMAKE([foreign]) +AC_CONFIG_SUBDIRS([libzipper]) AC_CONFIG_HEADERS([autoconfig.h]) AC_CONFIG_FILES([Makefile]) @@ -11,6 +12,7 @@ AC_C_BIGENDIAN( ) AC_PROG_CXX +AC_PROG_LIBTOOL SDL_VERSION=1.2.0 AM_PATH_SDL( diff --git a/libzipper b/libzipper new file mode 160000 index 0000000..f275ba4 --- /dev/null +++ b/libzipper @@ -0,0 +1 @@ +Subproject commit f275ba42be259b90a32a5b7f9399bc175c6e4c4e diff --git a/makeOpcodes.pl b/makeOpcodes.pl index 05698d9..d6f8847 100644 --- a/makeOpcodes.pl +++ b/makeOpcodes.pl @@ -351,16 +351,17 @@ sub processInstruction my @dbgRegs = createRegReference($mask); - print CODE "#ifdef GLBOY_DEBUG\n"; - print CODE "std::ostream& trace(log(Log::DBG_TRACE));\n". + print CODE "//#ifdef GLBOY_DEBUG\n"; + print CODE "std::ostream& trace(std::cerr);//(log(Log::DBG_TRACE));\n". "trace << ". - "opcodePC << \": \";\n". + "hex(opcodePC) << \": \";\n". "if (prefix) trace << hex(prefix) << \" \";\n". "trace << hex(opcode) << \" $name\" << ". ($operand ? "\" ($operand=\" << $operand << \")\" << " : "") . ($dbgRegs[0] ? "\" ($dbgRegs[0]=\" << $dbgRegs[0] << \")\" << " : "") . + " \" \" << hex(m_reg.A) << ". "std::endl;\n"; - print CODE "#endif\n"; + print CODE "//#endif\n"; my $statements = $node->findvalue('./text()'); chomp($statements); -- 2.38.5