diff --git a/libgambatte/libgambatte.vcxproj b/libgambatte/libgambatte.vcxproj index e0e201771f..9934cb6173 100644 --- a/libgambatte/libgambatte.vcxproj +++ b/libgambatte/libgambatte.vcxproj @@ -167,6 +167,7 @@ + @@ -204,6 +205,7 @@ + diff --git a/libgambatte/libgambatte.vcxproj.filters b/libgambatte/libgambatte.vcxproj.filters index 504d75698f..318ef9d04d 100644 --- a/libgambatte/libgambatte.vcxproj.filters +++ b/libgambatte/libgambatte.vcxproj.filters @@ -135,6 +135,9 @@ Header Files + + Header Files + @@ -218,5 +221,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/libgambatte/src/initstate.cpp b/libgambatte/src/initstate.cpp index fb3588dc1b..64b6825ffe 100644 --- a/libgambatte/src/initstate.cpp +++ b/libgambatte/src/initstate.cpp @@ -1330,4 +1330,12 @@ void gambatte::setInitState(SaveState &state, const bool cgb) { state.rtc.dataM = 0; state.rtc.dataS = 0; state.rtc.lastLatchData = false; + + state.huc3.haltTime = state.time.seconds; + state.huc3.dataTime = 0; + state.huc3.writingTime = 0; + state.huc3.halted = false; + state.huc3.shift = 0; + state.huc3.ramValue = 1; + state.huc3.modeflag = 2; // huc3_none } diff --git a/libgambatte/src/mem/cartridge.cpp b/libgambatte/src/mem/cartridge.cpp index c0dacca575..3424ffe96a 100644 --- a/libgambatte/src/mem/cartridge.cpp +++ b/libgambatte/src/mem/cartridge.cpp @@ -437,6 +437,95 @@ public: } }; +class HuC3 : public DefaultMbc { +public: + HuC3(MemPtrs& memptrs, HuC3Chip* const huc3) + : memptrs_(memptrs) + , huc3_(huc3) + , rombank_(1) + , rambank_(0) + , ramflag_(0) + { + } + + virtual unsigned char curRomBank() const { + return rombank_; + } + + virtual bool disabledRam() const { + return false; + } + + virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) { + 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 SyncState(NewState *ns, bool isReader) + { + NSS(rombank_); + NSS(rambank_); + NSS(ramflag_); + } + + virtual void loadState(SaveState::Mem const& ss) { + rombank_ = ss.rombank; + rambank_ = ss.rambank; + ramflag_ = ss.HuC3RAMflag; + setRambank(); + setRombank(); + } + +private: + 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)); + } +}; + class Mbc5 : public DefaultMbc { public: explicit Mbc5(MemPtrs &memptrs) @@ -578,6 +667,7 @@ int asHex(char c) { Cartridge::Cartridge() : rtc_(time_) +, huc3_(time_) { } @@ -588,7 +678,9 @@ void Cartridge::setStatePtrs(SaveState &state) { } void Cartridge::loadState(SaveState const &state) { + huc3_.loadState(state); rtc_.loadState(state); + time_.loadState(state); mbc_->loadState(state.mem); } @@ -613,7 +705,8 @@ LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool type_mbc2, type_mbc3, type_mbc5, - type_huc1 }; + type_huc1, + type_huc3 }; Cartridgetype type = type_plain; unsigned rambanks = 1; unsigned rombanks = 2; @@ -656,7 +749,7 @@ LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool case 0x22: return LOADRES_UNSUPPORTED_MBC_MBC7; case 0xFC: return LOADRES_UNSUPPORTED_MBC_POCKET_CAMERA; case 0xFD: return LOADRES_UNSUPPORTED_MBC_TAMA5; - case 0xFE: return LOADRES_UNSUPPORTED_MBC_HUC3; + case 0xFE: type = type_huc3; break; case 0xFF: type = type_huc1; break; default: return LOADRES_BAD_FILE_OR_UNKNOWN_MBC; } @@ -686,6 +779,7 @@ LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool mbc_.reset(); memptrs_.reset(rombanks, rambanks, cgb ? 8 : 2); rtc_.set(false, 0); + huc3_.set(false); std::memcpy(memptrs_.romdata(), romfiledata, (filesize / rombank_size() * rombank_size())); std::memset(memptrs_.romdata() + filesize / rombank_size() * rombank_size(), @@ -708,6 +802,10 @@ LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool break; case type_mbc5: mbc_.reset(new Mbc5(memptrs_)); break; case type_huc1: mbc_.reset(new HuC1(memptrs_)); break; + case type_huc3: + huc3_.set(true); + mbc_.reset(new HuC3(memptrs_, &huc3_)); + break; } return LOADRES_OK; @@ -798,6 +896,7 @@ bool Cartridge::getMemoryArea(int which, unsigned char **data, int *length) cons SYNCFUNC(Cartridge) { + SSS(huc3_); SSS(memptrs_); SSS(time_); SSS(rtc_); diff --git a/libgambatte/src/mem/cartridge.h b/libgambatte/src/mem/cartridge.h index 2260c31349..9c575148fe 100644 --- a/libgambatte/src/mem/cartridge.h +++ b/libgambatte/src/mem/cartridge.h @@ -23,6 +23,7 @@ #include "memptrs.h" #include "time.h" #include "rtc.h" +#include "huc3.h" #include "savestate.h" #include #include @@ -82,11 +83,15 @@ public: bool getMemoryArea(int which, unsigned char **data, int *length) const; LoadRes loadROM(char const *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); char const * romTitle() const { return reinterpret_cast(memptrs_.romdata() + 0x134); } + 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, unsigned long const cc) { huc3_.write(p, data, cc); } private: MemPtrs memptrs_; Time time_; Rtc rtc_; + HuC3Chip huc3_; std::unique_ptr mbc_; public: diff --git a/libgambatte/src/mem/huc3.cpp b/libgambatte/src/mem/huc3.cpp new file mode 100644 index 0000000000..785555fd53 --- /dev/null +++ b/libgambatte/src/mem/huc3.cpp @@ -0,0 +1,201 @@ +// +// 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(Time &time) +: time_(time) +, haltTime_(0) +, dataTime_(0) +, writingTime_(0) +, ramValue_(0) +, shift_(0) +, ramflag_(0) +, modeflag_(HUC3_NONE) +, irBaseCycle_(0) +, enabled_(false) +, lastLatchData_(false) +, halted_(false) +, irReceivingPulse_(false) +{ +} + +void HuC3Chip::doLatch(unsigned long const cc) { + std::uint32_t tmp = time(cc); + + unsigned minute = (tmp / 60) % 1440; + unsigned day = (tmp / 86400) & 0xFFF; + dataTime_ = (day << 12) | minute; +} + +//void HuC3Chip::setStatePtrs(SaveState &state) { +// state.huc3.haltTime.set(haltTime_, sizeof haltTime_); +// state.huc3.dataTime.set(dataTime_, sizeof dataTime_); +// state.huc3.writingTime.set(writingTime_, sizeof writingTime_); +// state.huc3.irBaseCycle.set(irBaseCycle_, sizeof irBaseCycle_); +// state.huc3.halted.set(halted_, sizeof halted_); +// state.huc3.shift.set(shift_, sizeof shift_); +// state.huc3.ramValue.set(ramValue_, sizeof ramValue_); +// state.huc3.modeflag.set(modeflag_, sizeof modeflag_); +// state.huc3.irReceivingPulse.set(irReceivingPulse_, sizeof irReceivingPulse_); +//} + +void HuC3Chip::loadState(SaveState const &state) { + 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, unsigned long const cc) { + // as above + if(ramflag_ == 0x0B) { + // command + switch(data & 0xF0) { + case 0x10: + // read time + doLatch(cc); + 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(cc); + 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 long const cc) { + unsigned minute = (writingTime_ & 0xFFF) % 1440; + unsigned day = (writingTime_ & 0xFFF000) >> 12; + std::uint32_t seconds = minute*60 + day*86400; + time_.reset(seconds, cc); + haltTime_ = seconds; + +} +SYNCFUNC(HuC3Chip) +{ + NSS(haltTime_); + NSS(dataTime_); + NSS(writingTime_); + NSS(ramValue_); + NSS(shift_); + NSS(halted_); + NSS(modeflag_); + NSS(irBaseCycle_); + NSS(irReceivingPulse_); +} +} + diff --git a/libgambatte/src/mem/huc3.h b/libgambatte/src/mem/huc3.h new file mode 100644 index 0000000000..00b0db38a0 --- /dev/null +++ b/libgambatte/src/mem/huc3.h @@ -0,0 +1,76 @@ +// +// 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 "time.h" + +namespace gambatte { + + struct SaveState; + + class HuC3Chip { + public: + HuC3Chip(Time &time); + //void setStatePtrs(SaveState &); + 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, unsigned long cycleCounter); + + private: + Time &time_; + std::uint32_t haltTime_; + unsigned dataTime_; + unsigned writingTime_; + unsigned char ramValue_; + unsigned char shift_; + unsigned char ramflag_; + unsigned char modeflag_; + unsigned long irBaseCycle_; + bool enabled_; + bool lastLatchData_; + bool halted_; + bool irReceivingPulse_; + + void doLatch(unsigned long cycleCounter); + void updateTime(unsigned long cycleCounter); + + std::uint32_t time(unsigned long const cc) { + return halted_ ? haltTime_ : time_.get(cc); + } + public: + templatevoid SyncState(NewState* ns); + }; +} + +#endif \ No newline at end of file diff --git a/libgambatte/src/memory.cpp b/libgambatte/src/memory.cpp index 9873a0080d..9714cd1489 100644 --- a/libgambatte/src/memory.cpp +++ b/libgambatte/src/memory.cpp @@ -514,7 +514,9 @@ void Memory::updateOamDma(unsigned long const cc) { startOamDma(lastOamDmaUpdate_); if (oamDmaPos_ < oam_size) { - 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_ == oam_size) { endOamDma(lastOamDmaUpdate_); @@ -667,6 +669,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(); } @@ -1200,6 +1205,8 @@ void Memory::nontrivial_write(unsigned const p, unsigned const data, unsigned lo else if (p < mm_wram_begin) { if (cart_.wsrambankptr()) cart_.wsrambankptr()[p] = data; + else if (cart_.isHuC3()) + cart_.HuC3Write(p, data, cc); else cart_.rtcWrite(data, cc); } diff --git a/libgambatte/src/savestate.h b/libgambatte/src/savestate.h index e07908cacf..64388e247d 100644 --- a/libgambatte/src/savestate.h +++ b/libgambatte/src/savestate.h @@ -77,6 +77,7 @@ struct SaveState { unsigned char rambank; unsigned char oamDmaPos; unsigned char haltHdmaState; + unsigned char HuC3RAMflag; unsigned char /*bool*/ IME; unsigned char /*bool*/ halted; unsigned char /*bool*/ enableRam; @@ -205,6 +206,18 @@ struct SaveState { unsigned char dataS; unsigned char /*bool*/ lastLatchData; } rtc; + + struct HuC3 { + unsigned long haltTime; + unsigned long dataTime; + unsigned long writingTime; + unsigned long irBaseCycle; + unsigned char /*bool*/ halted; + unsigned char shift; + unsigned char ramValue; + unsigned char modeflag; + unsigned char /*bool*/ irReceivingPulse; + } huc3; }; } diff --git a/libgambatte/src/video/ppu.cpp b/libgambatte/src/video/ppu.cpp index 23dffe870e..9c09069276 100644 --- a/libgambatte/src/video/ppu.cpp +++ b/libgambatte/src/video/ppu.cpp @@ -563,8 +563,11 @@ namespace M3Loop { p.xpos = xpos; } - void doFullTilesUnrolledCgb(PPUPriv& p, int const xend, uint_least32_t* const dbufline, - unsigned char const* const tileMapLine, unsigned const tileline, unsigned tileMapXpos) { + void doFullTilesUnrolledCgb(PPUPriv &p, int const xend, uint_least32_t* const dbufline, + unsigned char const *const tileMapLine, unsigned const tileline, unsigned tileMapXpos) { + int const tileIndexSign = p.lcdc & lcdc_tdsel ? 0 : tile_pattern_table_size / tile_size / 2; + unsigned char const* const tileDataLine = p.vram + 2 * tile_size * tileIndexSign + + tileline * tile_line_size; int xpos = p.xpos; unsigned char const* const vram = p.vram; unsigned const tdoffset = tileline * tile_line_size @@ -620,7 +623,16 @@ namespace M3Loop { uint_least32_t* const dstend = dst + n; xpos += n; - do { + if (!lcdcBgEn(p) && p.cgbDmg) { + do { *dst++ = p.bgPalette[0]; } while (dst != dstend); + tileMapXpos += n / (1u * tile_len); + + unsigned const tno = tileMapLine[(tileMapXpos - 1) % tile_map_len]; + int const ts = tile_size; + ntileword = expand_lut[(tileDataLine + ts * tno - 2 * ts * (tno & tileIndexSign))[0]] + + expand_lut[(tileDataLine + ts * tno - 2 * ts * (tno & tileIndexSign))[1]] * 2; + } + else do { unsigned long const* const bgPalette = p.bgPalette + (nattrib & attr_cgbpalno) * num_palette_entries; dst[0] = bgPalette[ntileword & tile_bpp_mask]; @@ -659,7 +671,7 @@ namespace M3Loop { } uint_least32_t* const dst = dbufline + (xpos - tile_len); - unsigned const tileword = p.ntileword; + unsigned const tileword = ((p.lcdc & 1u * lcdc_bgen) | !p.cgbDmg) * p.ntileword;; unsigned const attrib = p.nattrib; unsigned long const* const bgPalette = p.bgPalette + (attrib & attr_cgbpalno) * num_palette_entries; @@ -870,7 +882,7 @@ namespace M3Loop { p.winDrawState |= win_draw_start; } - unsigned const twdata = tileword & ((p.lcdc & lcdc_bgen) | p.cgb) * tile_bpp_mask; + unsigned const twdata = tileword & ((p.lcdc & lcdc_bgen) | (p.cgb * !p.cgbDmg)) * tile_bpp_mask; unsigned long pixel = p.bgPalette[twdata + (p.attrib & attr_cgbpalno) * num_palette_entries]; int i = static_cast(p.nextSprite) - 1; diff --git a/output/dll/libgambatte.dll b/output/dll/libgambatte.dll index 6d541404eb..ae21e029c5 100644 Binary files a/output/dll/libgambatte.dll and b/output/dll/libgambatte.dll differ diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index ab64c6dc6e..562555a4cb 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -310,6 +310,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy case 0x1D: return "MBC5 ROM+RUMBLE+RAM"; // = MBC5; break; case 0x1E: return "MBC5 ROM+RUMBLE+RAM+BATTERY"; // = MBC5; break; case 0xFF: return "HuC1 ROM+RAM+BATTERY"; // = HUC1; break; + case 0xFE: return "HuC3 ROM+RAM+BATTERY"; default: return "UNKNOWN"; } } @@ -361,7 +362,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy case 0xfc: throw new UnsupportedGameException("\"Pocket Camera\" Mapper not supported!"); case 0xfd: throw new UnsupportedGameException("\"Bandai TAMA5\" Mapper not supported!"); - case 0xfe: throw new UnsupportedGameException("\"HuC3\" Mapper not supported!"); + case 0xfe: break; case 0xff: break; default: throw new UnsupportedGameException($"Unknown mapper: {romdata[0x147]:x2}"); }