func (m *MBC5) Write(addr types.Word, value byte) { switch { case addr >= 0x0000 && addr <= 0x1FFF: if m.hasRAM { if r := value & 0x0F; r == 0x0A { m.ramEnabled = true } else { m.ramEnabled = false } } case addr >= 0x2000 && addr <= 0x2FFF: //lower 8 bits of rom bank are set here m.ROMBLower = types.Word(value) m.switchROMBank(int(m.ROMBLower | m.ROMBHigher<<8)) case addr >= 0x3000 && addr <= 0x3FFF: //lowest bit of this value allows you to select banks > 256 m.ROMBHigher = types.Word(value & 0x01) m.switchROMBank(int(m.ROMBLower | m.ROMBHigher<<8)) case addr >= 0x4000 && addr <= 0x5FFF: m.switchRAMBank(int(value & 0x03)) case addr >= 0xA000 && addr <= 0xBFFF: if m.hasRAM && m.ramEnabled { m.ramBanks[m.selectedRAMBank][addr-0xA000] = value } } }
func (g *GPU) RenderBackgroundScanline() { //find where in the tile map we are related to the current scan line + scroll Y (wraps around) screenYAdjusted := int(g.ly) + int(g.scrollY) var initialTilemapOffset types.Word = g.bgTilemap + types.Word(screenYAdjusted)%256/8*32 var initialLineOffset types.Word = types.Word(g.scrollX) / 8 % 32 //find where in the tile we are initialTileX := int(g.scrollX) % 8 initialTileY := screenYAdjusted % 8 //screen will always draw from X = 0 g.DrawScanline(initialTilemapOffset, initialLineOffset, 0, initialTileX, initialTileY) }
func (mmu *MMU) doInstantDMATransfer(startAddress, destinationAddr types.Word, blocks, blockSize int) { length := types.Word(blockSize * blocks) var i types.Word = 0x0000 for ; i < length; i++ { data := mmu.ReadByte(startAddress + i) mmu.WriteByte(destinationAddr+i, data) } }
//This area deals with registers (some only applicable to CGB hardware) func (mmu *MMU) WriteByteToRegister(addr types.Word, value byte) { switch addr { case DMG_STATUS_REG: mmu.dmgStatusRegister = value case CGB_DOUBLE_SPEED_PREP_REG: if mmu.RunningColorGBHardware == false { log.Printf("%s: WARNING -> Cannot write to %s in non-CGB mode! ROM may have unexpected behaviour (ROM is probably unsupported in non-CGB mode)", PREFIX, CGB_WRAM_BANK_SELECT) } else { mmu.cgbDoubleSpeedPreparationRegister = value } case CGB_INFRARED_PORT_REG: log.Printf("%s: Attempting to write 0x%X to infrared port register (%s), this is currently unsupported", PREFIX, value, addr) //Color GB Working RAM Bank Selection case CGB_WRAM_BANK_SELECT: if mmu.RunningColorGBHardware == false { log.Printf("%s: WARNING -> Cannot write to %s in non-CGB mode! ROM may have unexpected behaviour (ROM is probably unsupported in non-CGB mode)", PREFIX, CGB_WRAM_BANK_SELECT) } else { mmu.cgbWramBankSelectedRegister = value } case CGB_HDMA_SOURCE_HIGH_REG: mmu.hdmaTransferInfo.Source = (mmu.hdmaTransferInfo.Source & 0x00FF) | types.Word(value)<<8 case CGB_HDMA_SOURCE_LOW_REG: mmu.hdmaTransferInfo.Source = (mmu.hdmaTransferInfo.Source & 0xFF00) | types.Word(value) case CGB_HDMA_DEST_HIGH_REG: mmu.hdmaTransferInfo.Destination = (mmu.hdmaTransferInfo.Destination & 0x00FF) | types.Word(value)<<8 case CGB_HDMA_DEST_LOW_REG: mmu.hdmaTransferInfo.Destination = (mmu.hdmaTransferInfo.Destination & 0xFF00) | types.Word(value) case CGB_HDMA_REG: if mmu.RunningColorGBHardware == false { log.Printf("%s: WARNING -> Cannot write to %s in non-CGB mode! ROM may have unexpected behaviour (ROM is probably unsupported in non-CGB mode)", PREFIX, CGB_WRAM_BANK_SELECT) } else { if value&0x80 == 0x00 { mmu.hdmaTransferInfo.Length = int(value&0x7F) + 1 mmu.hdmaTransferInfo.Running = true mmu.doInstantDMATransfer(mmu.hdmaTransferInfo.Source, mmu.hdmaTransferInfo.Destination, mmu.hdmaTransferInfo.Length, 16) } else { log.Println("HDMA horizontal HBlank is unsupported at the moment ") } } default: //unknown register, who cares? mmu.emptySpace[addr-0xFF4D] = value } }
//method to calculate the tilenumber within the tilemap func (g *GPU) calculateTileNo(tilemapOffset types.Word, lineOffset types.Word) int { tileId := int(g.Read(types.Word(tilemapOffset + lineOffset))) //if tile data is 0 then it is signed if g.tileDataSelect == TILEDATA0 { if tileId < 128 { tileId += 256 } } return tileId }
func (g *GPU) DumpTilemap(tileMapAddr types.Word, tileDataSigned bool) [256][256]types.RGB { log.Print("Dumping Tilemap ", tileMapAddr) if tileDataSigned { log.Println(" (signed)") } else { log.Println(" (unsigned)") } var result [256][256]types.RGB var tileMapAddrOffset types.Word = tileMapAddr var rx int = 0 var ry int = 0 for lineX := 0; lineX < 32; lineX++ { for tileY := 0; tileY < 8; tileY++ { for lineY := 0; lineY < 32; lineY++ { tileId := int(g.Read(tileMapAddrOffset + types.Word(lineY))) if tileDataSigned { if tileId < 128 { tileId += 256 } } tile := g.tiledata[0][tileId] for tileX := 0; tileX < 8; tileX++ { cr := GBColours[tile[tileY][tileX]] result[rx][ry] = cr rx++ } } rx = 0 ry++ } tileMapAddrOffset += types.Word(32) } return result }
func (g *GPU) RenderWindowScanline() { screenYAdjusted := g.ly - int(g.windowY) if (g.windowX >= 0 && g.windowX < 167) && (g.windowY >= 0 && g.windowY < 144) && screenYAdjusted >= 0 { var initialTilemapOffset types.Word = g.windowTilemap + types.Word(screenYAdjusted)/8*32 var initialLineOffset types.Word = 0 var screenXAdjusted int = int((g.windowX - 7) % 255) //find where in the tile we are initialTileX := screenXAdjusted % 8 initialTileY := screenYAdjusted % 8 g.DrawScanline(initialTilemapOffset, initialLineOffset, screenXAdjusted, initialTileX, initialTileY) } }
func (mmu *MMU) WriteByte(addr types.Word, value byte) { //Check peripherals first if p, ok := mmu.peripheralIOMap[addr]; ok { p.Write(addr, value) return } switch { case addr >= 0x0000 && addr <= 0x9FFF: mmu.cartridge.MBC.Write(addr, value) //Cartridge External RAM case addr >= 0xA000 && addr <= 0xBFFF: mmu.cartridge.MBC.Write(addr, value) //GB Internal RAM case addr >= 0xC000 && addr <= 0xDFFF: mmu.WriteToWorkingRAM(addr, value) //copy value to shadow if within shadow range if addr >= 0xC000 && addr <= 0xDDFF { mmu.internalRAMShadow[addr&(0xDDFF-0xC000)] = mmu.ReadByte(addr) } case addr == 0xFF01 || addr == 0xFF02: //serial cable communication mmu.serialTmp = ZERO //INTERRUPT FLAG case addr == 0xFF0F: mmu.interruptsFlag = value //DMA transfer case addr == 0xFF46: var startAddr types.Word = types.Word(value) << 8 var oamAddr types.Word = 0xFE00 //transfer 10 blocks to OAM mmu.doInstantDMATransfer(startAddr, oamAddr, 10, 16) //Empty but "unusable for I/O" case addr > 0xFF4C && addr <= 0xFF7F: mmu.WriteByteToRegister(addr, value) //Zero page RAM case addr >= 0xFF80 && addr <= 0xFFFF: if addr == 0xFFFF { mmu.interruptsEnabled = value } else { mmu.zeroPageRAM[addr&(0xFFFF-0xFF80)] = value } default: log.Printf("%s: WARNING - Attempting to write 0x%X to address %s, this is invalid/unimplemented", PREFIX, value, addr) } }
//CGB has additional attributes in bank 1 for each background tile func (g *GPU) getCGBBackgroundTileAttrs(tilemapOffset types.Word, lineOffset types.Word) (int, *CGBBackgroundTileAttrs) { if g.RunningColorGBHardware { var currentSelectedBankTmp byte = g.cgbVramBankSelectionRegister //tile number data always comes from bank 0 g.cgbVramBankSelectionRegister = 0 var tileNo int = g.calculateTileNo(tilemapOffset, lineOffset) //tile attribute data always comes from bank 1 g.cgbVramBankSelectionRegister = 1 var attributeData byte = g.Read(types.Word(tilemapOffset + lineOffset)) //revert bank selection register to what it was set to previously g.cgbVramBankSelectionRegister = currentSelectedBankTmp return tileNo, NewCGBBackgroundTileAttrs(attributeData) } else { log.Panicln("Cannot call this function, not in color gb mode!") } return -1, nil }
func (mmu *MMU) ReadWord(addr types.Word) types.Word { var b1 byte = mmu.ReadByte(addr) var b2 byte = mmu.ReadByte(addr + 1) return types.Word(utils.JoinBytes(b1, b2)) }
func (m *MockMMU) ReadWord(address types.Word) types.Word { a, b := m.memory[address], m.memory[address+1] return (types.Word(a) << 8) ^ types.Word(b) }