diff --git a/Makefile.common b/Makefile.common index ad3b5064..4675a91b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -20,6 +20,7 @@ SOURCES_CXX := $(CORE_DIR)/bootloader.cpp \ $(CORE_DIR)/video_libretro.cpp \ $(CORE_DIR)/mem/cartridge.cpp \ $(CORE_DIR)/mem/cartridge_libretro.cpp \ + $(CORE_DIR)/mem/huc3.cpp \ $(CORE_DIR)/mem/memptrs.cpp \ $(CORE_DIR)/mem/rtc.cpp \ $(CORE_DIR)/sound/channel1.cpp \ diff --git a/libgambatte/src/gambatte-memory.cpp b/libgambatte/src/gambatte-memory.cpp index 9d7c9b7b..0060d229 100644 --- a/libgambatte/src/gambatte-memory.cpp +++ b/libgambatte/src/gambatte-memory.cpp @@ -471,7 +471,10 @@ void Memory::updateOamDma(unsigned long const cc) { if (oamDmaPos_ == 0) startOamDma(lastOamDmaUpdate_ - 1); - ioamhram_[oamDmaPos_] = oamDmaSrc ? oamDmaSrc[oamDmaPos_] : cart_.rtcRead(); + if (oamDmaSrc) ioamhram_[oamDmaPos_] = oamDmaSrc[oamDmaPos_]; + else if (cart_.isHuC3()) ioamhram_[oamDmaPos_] = cart_.HuC3Read(oamDmaPos_, cc); + else ioamhram_[oamDmaPos_] = cart_.rtcRead(); + } else if (oamDmaPos_ == 0xA0) { endOamDma(lastOamDmaUpdate_ - 1); lastOamDmaUpdate_ = disabled_time; @@ -639,6 +642,9 @@ unsigned Memory::nontrivial_read(unsigned const p, unsigned long const cc) { if (cart_.rsrambankptr()) return cart_.rsrambankptr()[p]; + if (cart_.isHuC3()) + return cart_.HuC3Read(p, cc); + return cart_.rtcRead(); } @@ -1134,6 +1140,8 @@ void Memory::nontrivial_write(unsigned const p, unsigned const data, unsigned lo } else if (p < 0xC000) { if (cart_.wsrambankptr()) cart_.wsrambankptr()[p] = data; + else if (cart_.isHuC3()) + cart_.HuC3Write(p, data); else cart_.rtcWrite(data); } else diff --git a/libgambatte/src/initstate.cpp b/libgambatte/src/initstate.cpp index d29c02a8..6184033f 100644 --- a/libgambatte/src/initstate.cpp +++ b/libgambatte/src/initstate.cpp @@ -1334,4 +1334,15 @@ void gambatte::setInitState(SaveState &state, bool const cgb, bool const gbaCgbM state.rtc.dataM = 0; state.rtc.dataS = 0; state.rtc.lastLatchData = false; + + state.huc3.baseTime = std::time(0); + state.huc3.haltTime = state.huc3.baseTime; + state.huc3.dataTime = 0; + state.huc3.writingTime = 0; + state.huc3.irBaseCycle = 0; + state.huc3.halted = false; + state.huc3.shift = 0; + state.huc3.ramValue = 1; + state.huc3.modeflag = 2; // huc3_none + state.huc3.irReceivingPulse = false; } diff --git a/libgambatte/src/mem/cartridge.cpp b/libgambatte/src/mem/cartridge.cpp index b5199329..6443e339 100644 --- a/libgambatte/src/mem/cartridge.cpp +++ b/libgambatte/src/mem/cartridge.cpp @@ -344,6 +344,74 @@ namespace gambatte setRombank(); } }; + class HuC3 : public DefaultMbc { + MemPtrs &memptrs_; + HuC3Chip *const huc3_; + unsigned char rombank_; + unsigned char rambank_; + unsigned char ramflag_; + void setRambank() const { + huc3_->setRamflag(ramflag_); + unsigned flags; + if(ramflag_ >= 0x0B && ramflag_ < 0x0F) { + // System registers mode + flags = MemPtrs::READ_EN | MemPtrs::WRITE_EN | MemPtrs::RTC_EN; + } + else if(ramflag_ == 0x0A || ramflag_ > 0x0D) { + // Read/write mode + flags = MemPtrs::READ_EN | MemPtrs::WRITE_EN; + } + else { + // Read-only mode ?? + flags = MemPtrs::READ_EN; + } + memptrs_.setRambank(flags, rambank_ & (rambanks(memptrs_) - 1)); + } + void setRombank() const { memptrs_.setRombank(std::max(rombank_ & (rombanks(memptrs_) - 1), 1u)); } + public: + explicit HuC3(MemPtrs &memptrs, HuC3Chip *const huc3) + : memptrs_(memptrs), + huc3_(huc3), + rombank_(1), + rambank_(0), + ramflag_(0) + { + } + virtual void romWrite(unsigned const p, unsigned const data) { + switch (p >> 13 & 3) { + case 0: + ramflag_ = data; + //printf("[HuC3] set ramflag to %02X\n", data); + setRambank(); + break; + case 1: + //printf("[HuC3] set rombank to %02X\n", data); + rombank_ = data; + setRombank(); + break; + case 2: + //printf("[HuC3] set rambank to %02X\n", data); + rambank_ = data; + setRambank(); + break; + case 3: + // GEST: "programs will write 1 here" + break; + } + } + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank_; + ss.rambank = rambank_; + ss.HuC3RAMflag = ramflag_; + } + virtual void loadState(SaveState::Mem const &ss) { + rombank_ = ss.rombank; + rambank_ = ss.rambank; + ramflag_ = ss.HuC3RAMflag; + setRambank(); + setRombank(); + } + }; class Mbc5 : public DefaultMbc { MemPtrs &memptrs; unsigned short rombank; @@ -400,6 +468,7 @@ namespace gambatte { switch (headerByte0x147) { + case 0xFE: // huc3 case 0x0F: case 0x10: return true; @@ -419,10 +488,12 @@ namespace gambatte { mbc->saveState(state.mem); rtc_.saveState(state); + huc3_.saveState(state); } void Cartridge::loadState(const SaveState &state) { + huc3_.loadState(state); rtc_.loadState(state); mbc->loadState(state.mem); } @@ -455,7 +526,7 @@ namespace gambatte unsigned rambanks = 1; unsigned rombanks = 2; bool cgb = false; - enum Cartridgetype { PLAIN, MBC1, MBC2, MBC3, MBC5, HUC1 } type = PLAIN; + enum Cartridgetype { PLAIN, MBC1, MBC2, MBC3, MBC5, HUC1, HUC3 } type = PLAIN; { unsigned i; @@ -494,7 +565,7 @@ namespace gambatte case 0x22: printf("MBC7 ROM not supported.\n"); return -1; case 0xFC: printf("Pocket Camera ROM not supported.\n"); return -1; case 0xFD: printf("Bandai TAMA5 ROM not supported.\n"); return -1; - case 0xFE: printf("HuC3 ROM+RAM+BATTERY loaded.\n"); return -1; + case 0xFE: printf("HuC3 ROM+RAM+BATTERY loaded.\n"); type = HUC3; break; case 0xFF: printf("HuC1 ROM+BATTERY loaded.\n"); type = HUC1; break; default: printf("Wrong data-format, corrupt or unsupported ROM.\n"); return -1; } @@ -541,6 +612,7 @@ namespace gambatte mbc.reset(); memptrs_.reset(rombanks, rambanks, cgb ? 8 : 2); rtc_.set(false, 0); + huc3_.set(false); memcpy(memptrs_.romdata(), romdata, ((romsize / 0x4000) * 0x4000ul) * sizeof(unsigned char)); std::memset(memptrs_.romdata() + (romsize / 0x4000) * 0x4000ul, 0xFF, (rombanks - romsize / 0x4000) * 0x4000ul); @@ -560,6 +632,10 @@ namespace gambatte case MBC3: mbc.reset(new Mbc3(memptrs_, hasRtc(memptrs_.romdata()[0x147]) ? &rtc_ : 0)); break; case MBC5: mbc.reset(new Mbc5(memptrs_)); break; case HUC1: mbc.reset(new HuC1(memptrs_)); break; + case HUC3: + huc3_.set(true); + mbc.reset(new HuC3(memptrs_, &huc3_)); + break; } return 0; diff --git a/libgambatte/src/mem/cartridge.h b/libgambatte/src/mem/cartridge.h index 64b46f0e..8890dff6 100644 --- a/libgambatte/src/mem/cartridge.h +++ b/libgambatte/src/mem/cartridge.h @@ -21,6 +21,7 @@ #include "memptrs.h" #include "rtc.h" +#include "huc3.h" #include "savestate.h" #include #include @@ -134,6 +135,10 @@ namespace gambatte void setGameGenie(const std::string &codes); void clearCheats(); + bool isHuC3() const { return huc3_.isHuC3(); } + unsigned char HuC3Read(unsigned p, unsigned long const cc) { return huc3_.read(p, cc); } + void HuC3Write(unsigned p, unsigned data) { huc3_.write(p, data); } + void *savedata_ptr(); unsigned savedata_size(); @@ -152,6 +157,7 @@ namespace gambatte }; MemPtrs memptrs_; Rtc rtc_; + HuC3Chip huc3_; std::auto_ptr mbc; diff --git a/libgambatte/src/mem/cartridge_libretro.cpp b/libgambatte/src/mem/cartridge_libretro.cpp index 6b273b26..8d7dbc65 100644 --- a/libgambatte/src/mem/cartridge_libretro.cpp +++ b/libgambatte/src/mem/cartridge_libretro.cpp @@ -21,6 +21,7 @@ namespace gambatte case 0x13: case 0x1B: case 0x1E: + case 0xFE: case 0xFF: return true; default: @@ -32,6 +33,7 @@ namespace gambatte { switch (headerByte0x147) { + case 0xFE: // huc3 case 0x0F: case 0x10: return true; @@ -57,15 +59,25 @@ namespace gambatte void *Cartridge::rtcdata_ptr() { - if (hasRtc(memptrs_.romdata()[0x147])) - return &rtc_.getBaseTime(); + if (hasRtc(memptrs_.romdata()[0x147])) { + if (isHuC3()) { + return &huc3_.getBaseTime(); + } else { + return &rtc_.getBaseTime(); + } + } return 0; } unsigned Cartridge::rtcdata_size() { - if (hasRtc(memptrs_.romdata()[0x147])) - return sizeof(rtc_.getBaseTime()); + if (hasRtc(memptrs_.romdata()[0x147])) { + if (isHuC3()) { + return sizeof(huc3_.getBaseTime()); + } else { + return sizeof(rtc_.getBaseTime()); + } + } return 0; } diff --git a/libgambatte/src/mem/huc3.cpp b/libgambatte/src/mem/huc3.cpp new file mode 100644 index 00000000..45a01239 --- /dev/null +++ b/libgambatte/src/mem/huc3.cpp @@ -0,0 +1,189 @@ +// +// Copyright (C) 2007 by sinamas +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License version 2 for more details. +// +// You should have received a copy of the GNU General Public License +// version 2 along with this program; if not, write to the +// Free Software Foundation, Inc., +// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "huc3.h" +#include "../savestate.h" +#include + +namespace gambatte { + +HuC3Chip::HuC3Chip() +: baseTime_(0) +, haltTime_(0) +, dataTime_(0) +, writingTime_(0) +, ramValue_(0) +, shift_(0) +, ramflag_(0) +, modeflag_(HUC3_NONE) +, irBaseCycle_(0) +, enabled_(false) +, halted_(false) +, irReceivingPulse_(false) +{ +} + +void HuC3Chip::doLatch() { + uint64_t tmp = (halted_ ? haltTime_ : std::time(0)) - baseTime_; + + unsigned minute = (tmp / 60) % 1440; + unsigned day = (tmp / 86400) & 0xFFF; + dataTime_ = (day << 12) | minute; +} + +void HuC3Chip::saveState(SaveState &state) const { + state.huc3.baseTime = baseTime_; + state.huc3.haltTime = haltTime_; + state.huc3.dataTime = dataTime_; + state.huc3.writingTime = writingTime_; + state.huc3.ramValue = ramValue_; + state.huc3.shift = shift_; + state.huc3.halted = halted_; + state.huc3.modeflag = modeflag_; + state.huc3.irBaseCycle = irBaseCycle_; + state.huc3.irReceivingPulse = irReceivingPulse_; +} + +void HuC3Chip::loadState(SaveState const &state) { + baseTime_ = state.huc3.baseTime; + haltTime_ = state.huc3.haltTime; + dataTime_ = state.huc3.dataTime; + ramValue_ = state.huc3.ramValue; + shift_ = state.huc3.shift; + halted_ = state.huc3.halted; + modeflag_ = state.huc3.modeflag; + writingTime_ = state.huc3.writingTime; + irBaseCycle_ = state.huc3.irBaseCycle; + irReceivingPulse_ = state.huc3.irReceivingPulse; +} + +unsigned char HuC3Chip::read(unsigned p, unsigned long const cc) { + // should only reach here with ramflag = 0B-0E + if(ramflag_ == 0x0E) { + // INFRARED + if(!irReceivingPulse_) { + irReceivingPulse_ = true; + irBaseCycle_ = cc; + } + unsigned long cyclesSinceStart = cc - irBaseCycle_; + unsigned char modulation = (cyclesSinceStart/105) & 1; // 4194304 Hz CPU, 40000 Hz remote signal + unsigned long timeUs = cyclesSinceStart*36/151; // actually *1000000/4194304 + // sony protocol + if(timeUs < 10000) { + // initialization allowance + return 0; + } + else if(timeUs < 10000 + 2400) { + // initial mark + return modulation; + } + else if(timeUs < 10000 + 2400 + 600) { + // initial space + return 0; + } + else { + // send data + timeUs -= 13000; + // write 20 bits (any 20 seem to do) + unsigned int data = 0xFFFFF; + for(unsigned long mask = 1UL << (20-1); mask; mask >>= 1) { + unsigned int markTime = (data & mask) ? 1200 : 600; + if(timeUs < markTime) { return modulation; } + timeUs -= markTime; + if(timeUs < 600) { return 0; } + timeUs -= 600; + } + + return 0; + } + } + if(ramflag_ < 0x0B || ramflag_ > 0x0D) { + printf("[HuC3] error, hit huc3 read with ramflag=%02X\n", ramflag_); + return 0xFF; + } + if(ramflag_ == 0x0D) return 1; + else return ramValue_; +} + +void HuC3Chip::write(unsigned p, unsigned data) { + // as above + if(ramflag_ == 0x0B) { + // command + switch(data & 0xF0) { + case 0x10: + // read time + doLatch(); + if(modeflag_ == HUC3_READ) { + ramValue_ = (dataTime_ >> shift_) & 0x0F; + shift_ += 4; + if(shift_ > 24) shift_ = 0; + } + break; + case 0x30: + // write time + if(modeflag_ == HUC3_WRITE) { + if(shift_ == 0) writingTime_ = 0; + if(shift_ < 24) { + writingTime_ |= (data & 0x0F) << shift_; + shift_ += 4; + if(shift_ == 24) { + updateTime(); + modeflag_ = HUC3_READ; + } + } + } + break; + case 0x40: + // some kind of mode shift + switch(data & 0x0F) { + case 0x0: + // shift reset? + shift_ = 0; + break; + case 0x3: + // write time? + modeflag_ = HUC3_WRITE; + shift_ = 0; + break; + case 0x7: + modeflag_ = HUC3_READ; + shift_ = 0; + break; + // others are unimplemented so far + } + break; + case 0x50: + // ??? + break; + case 0x60: + modeflag_ = HUC3_READ; // ??? + break; + } + } + // do nothing for 0C/0D yet +} + +void HuC3Chip::updateTime() { + unsigned minute = (writingTime_ & 0xFFF) % 1440; + unsigned day = (writingTime_ & 0xFFF000) >> 12; + baseTime_ = std::time(0) - minute*60 - day*86400; + haltTime_ = baseTime_; + +} + +} diff --git a/libgambatte/src/mem/huc3.h b/libgambatte/src/mem/huc3.h new file mode 100644 index 00000000..3fd1f39f --- /dev/null +++ b/libgambatte/src/mem/huc3.h @@ -0,0 +1,79 @@ +// +// Copyright (C) 2007 by sinamas +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License version 2 for more details. +// +// You should have received a copy of the GNU General Public License +// version 2 along with this program; if not, write to the +// Free Software Foundation, Inc., +// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifndef HuC3Chip_H +#define HuC3Chip_H + +enum +{ + HUC3_READ = 0, + HUC3_WRITE = 1, + HUC3_NONE = 2 +}; + +#include +#include + +namespace gambatte { + +struct SaveState; + +class HuC3Chip { +public: + HuC3Chip(); + uint64_t baseTime() const { return baseTime_; } + void setBaseTime(uint64_t baseTime) { baseTime_ = baseTime; } + + uint64_t& getBaseTime() + { + return baseTime_; + } + + void saveState(SaveState &state) const; + void loadState(SaveState const &state); + void setRamflag(unsigned char ramflag) { ramflag_ = ramflag; irReceivingPulse_ = false; } + bool isHuC3() const { return enabled_; } + + void set(bool enabled) { + enabled_ = enabled; + } + + unsigned char read(unsigned p, unsigned long const cc); + void write(unsigned p, unsigned data); + +private: + uint64_t baseTime_; + uint64_t haltTime_; + unsigned dataTime_; + unsigned writingTime_; + unsigned char ramValue_; + unsigned char shift_; + unsigned char ramflag_; + unsigned char modeflag_; + unsigned long irBaseCycle_; + bool enabled_; + bool halted_; + bool irReceivingPulse_; + + void doLatch(); + void updateTime(); +}; + +} + +#endif diff --git a/libgambatte/src/savestate.h b/libgambatte/src/savestate.h index cb3c5d31..eed18953 100644 --- a/libgambatte/src/savestate.h +++ b/libgambatte/src/savestate.h @@ -75,6 +75,7 @@ struct SaveState { unsigned short dmaDestination; unsigned char rambank; unsigned char oamDmaPos; + unsigned char HuC3RAMflag; #ifdef HAVE_NETWORK unsigned char serialize_value; bool serialize_is_fastcgb; @@ -198,6 +199,19 @@ struct SaveState { unsigned char dataS; bool lastLatchData; } rtc; + + struct HuC3 { + unsigned long baseTime; + unsigned long haltTime; + unsigned long dataTime; + unsigned long writingTime; + unsigned long irBaseCycle; + bool halted; + unsigned char shift; + unsigned char ramValue; + unsigned char modeflag; + bool irReceivingPulse; + } huc3; }; } diff --git a/libgambatte/src/statesaver.cpp b/libgambatte/src/statesaver.cpp index 90fc00a1..d4aad63e 100644 --- a/libgambatte/src/statesaver.cpp +++ b/libgambatte/src/statesaver.cpp @@ -344,6 +344,7 @@ SaverList::SaverList() { { static const char label[] = { s,r,a,m,o,n, NUL }; ADD(mem.enableRam); } { static const char label[] = { r,a,m,b,m,o,d, NUL }; ADD(mem.rambankMode); } { static const char label[] = { h,d,m,a, NUL }; ADD(mem.hdmaTransfer); } + { static const char label[] = { h,u,c,NO3,r,a,m, NUL }; ADD(mem.HuC3RAMflag); } { static const char label[] = { b,g,p, NUL }; ADDPTR(ppu.bgpData); } { static const char label[] = { o,b,j,p, NUL }; ADDPTR(ppu.objpData); } { static const char label[] = { s,p,o,s,b,u,f, NUL }; ADDPTR(ppu.oamReaderBuf); } @@ -426,6 +427,16 @@ SaverList::SaverList() { { static const char label[] = { r,t,c,m, NUL }; ADD(rtc.dataM); } { static const char label[] = { r,t,c,s, NUL }; ADD(rtc.dataS); } { static const char label[] = { r,t,c,l,l,d, NUL }; ADD(rtc.lastLatchData); } + { static char const label[] = { h,NO3,b,a,s,e,t, NUL }; ADD(huc3.baseTime); } + { static const char label[] = { h,NO3,h,a,l,t,t, NUL }; ADD(huc3.haltTime); } + { static const char label[] = { h,NO3,d,a,t,a,t, NUL }; ADD(huc3.dataTime); } + { static const char label[] = { h,NO3,w,r,i,t,t, NUL }; ADD(huc3.writingTime); } + { static const char label[] = { h,NO3,h,a,l,t, NUL }; ADD(huc3.halted); } + { static const char label[] = { h,NO3,s,h,f,t, NUL }; ADD(huc3.shift); } + { static const char label[] = { h,NO3,r,v, NUL }; ADD(huc3.ramValue); } + { static const char label[] = { h,NO3,m,f, NUL }; ADD(huc3.modeflag); } + { static const char label[] = { h,NO3,i,r,c,y, NUL }; ADD(huc3.irBaseCycle); } + { static const char label[] = { h,NO3,i,r,a,c, NUL }; ADD(huc3.irReceivingPulse); } #undef ADD #undef ADDPTR