From: Michael McMaster Date: Sun, 11 Sep 2011 22:58:49 +0000 (+1000) Subject: Interim checkin. Bugfixes Galore, plus MBC1,2,3 support. X-Git-Url: http://git.codesrc.com/gitweb.cgi?a=commitdiff_plain;h=925cbf1f8813df170cd934b08a49addf45673d0e;p=glBoy.git Interim checkin. Bugfixes Galore, plus MBC1,2,3 support. --- 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);