├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc ├── screenshot1.png ├── screenshot10.png ├── screenshot2.png ├── screenshot3.png ├── screenshot4.png ├── screenshot5.png ├── screenshot6.png ├── screenshot7.png ├── screenshot8.png ├── screenshot9.png ├── test-rom-archives │ ├── cgb_sound.zip │ ├── cpu_instrs.zip │ ├── dmg_sound-2.zip │ ├── halt_bug.zip │ ├── instr_timing.zip │ ├── interrupt_time.zip │ ├── mem_timing-2.zip │ └── oam_bug-2.zip └── tetris.gif ├── pom.xml ├── run.sh └── src ├── main └── java │ └── eu │ └── rekawek │ └── coffeegb │ ├── AddressSpace.java │ ├── CartridgeOptions.java │ ├── Dumper.java │ ├── Gameboy.java │ ├── Main.java │ ├── controller │ ├── ButtonListener.java │ ├── Controller.java │ └── Joypad.java │ ├── cpu │ ├── AluFunctions.java │ ├── BitUtils.java │ ├── Cpu.java │ ├── Flags.java │ ├── InterruptManager.java │ ├── Opcodes.java │ ├── Registers.java │ ├── SpeedMode.java │ ├── op │ │ ├── Argument.java │ │ ├── DataType.java │ │ └── Op.java │ └── opcode │ │ ├── Opcode.java │ │ └── OpcodeBuilder.java │ ├── debug │ ├── Command.java │ ├── CommandArgument.java │ ├── CommandPattern.java │ ├── Console.java │ ├── ConsoleUtil.java │ └── command │ │ ├── Quit.java │ │ ├── ShowHelp.java │ │ ├── apu │ │ └── Channel.java │ │ ├── cpu │ │ ├── ShowOpcode.java │ │ └── ShowOpcodes.java │ │ └── ppu │ │ └── ShowBackground.java │ ├── gpu │ ├── ColorPalette.java │ ├── ColorPixelFifo.java │ ├── Display.java │ ├── DmgPixelFifo.java │ ├── Fetcher.java │ ├── Gpu.java │ ├── GpuRegister.java │ ├── GpuRegisterValues.java │ ├── IntQueue.java │ ├── Lcdc.java │ ├── PixelFifo.java │ ├── SpriteBug.java │ ├── TileAttributes.java │ └── phase │ │ ├── GpuPhase.java │ │ ├── HBlankPhase.java │ │ ├── OamSearch.java │ │ ├── PixelTransfer.java │ │ └── VBlankPhase.java │ ├── memory │ ├── BootRom.java │ ├── Dma.java │ ├── DmaAddressSpace.java │ ├── GbcRam.java │ ├── Hdma.java │ ├── Mmu.java │ ├── Ram.java │ ├── ShadowAddressSpace.java │ ├── UndocumentedGbcRegisters.java │ └── cart │ │ ├── Cartridge.java │ │ ├── CartridgeType.java │ │ ├── MemoryController.java │ │ ├── battery │ │ ├── Battery.java │ │ └── FileBattery.java │ │ ├── rtc │ │ ├── RealTimeClock.java │ │ ├── SystemTimeSource.java │ │ ├── TimeSource.java │ │ └── VirtualTimeSource.java │ │ └── type │ │ ├── Mbc1.java │ │ ├── Mbc2.java │ │ ├── Mbc3.java │ │ ├── Mbc5.java │ │ └── Rom.java │ ├── serial │ ├── ByteReceiver.java │ ├── ByteReceivingSerialEndpoint.java │ ├── ClockType.java │ ├── NaiveSerialPort.java │ ├── SerialEndpoint.java │ └── SerialPort.java │ ├── sound │ ├── AbstractSoundMode.java │ ├── FrequencySweep.java │ ├── LengthCounter.java │ ├── Lfsr.java │ ├── PolynomialCounter.java │ ├── Sound.java │ ├── SoundMode1.java │ ├── SoundMode2.java │ ├── SoundMode3.java │ ├── SoundMode4.java │ ├── SoundOutput.java │ └── VolumeEnvelope.java │ ├── swing │ ├── emulator │ │ ├── DisplayController.kt │ │ ├── EmulatorStateListener.kt │ │ ├── SerialController.kt │ │ ├── SoundController.kt │ │ ├── SwingEmulator.kt │ │ └── TimingTicker.kt │ ├── gui │ │ ├── SwingGui.kt │ │ ├── SwingMenu.kt │ │ └── properties │ │ │ ├── ControllerProperties.kt │ │ │ ├── EmulatorProperties.kt │ │ │ └── RecentRoms.kt │ └── io │ │ ├── AudioSystemSoundOutput.java │ │ ├── SwingController.java │ │ ├── SwingDisplay.java │ │ └── serial │ │ ├── ClientEventListener.kt │ │ ├── SerialEndpointWrapper.kt │ │ ├── SerialTcpClient.kt │ │ ├── SerialTcpServer.kt │ │ ├── ServerEventListener.kt │ │ └── StreamSerialEndpoint.kt │ └── timer │ └── Timer.java └── test ├── java └── eu │ └── rekawek │ └── coffeegb │ ├── cpu │ └── TimingTest.java │ ├── gpu │ ├── ColorPaletteTest.java │ └── PixelFifoTest.java │ ├── integration │ ├── blargg │ │ ├── BlarggRomTest.java │ │ └── individual │ │ │ ├── CgbSoundTest.java │ │ │ ├── CpuInstrsTest.java │ │ │ ├── DmgSound2Test.java │ │ │ ├── MemTiming2Test.java │ │ │ └── OamBug2Test.java │ ├── dmgacid2 │ │ └── DmgAcid2RomTest.java │ ├── mooneye │ │ ├── BitsTest.java │ │ ├── EmulatorOnlyTest.java │ │ ├── GeneralTest.java │ │ ├── InstrTest.java │ │ ├── InterruptsTest.java │ │ ├── MiscTest.java │ │ ├── OamDmaTest.java │ │ ├── PpuTest.java │ │ ├── SerialTest.java │ │ └── TimerTest.java │ └── support │ │ ├── ImageTestRunner.java │ │ ├── MemoryTestRunner.java │ │ ├── MooneyeTestRunner.java │ │ ├── ParametersProvider.java │ │ ├── RomTestUtils.java │ │ └── SerialTestRunner.java │ ├── memory │ └── cart │ │ └── rtc │ │ └── RealTimeTimeSourceTest.java │ └── sound │ ├── AbstractLengthCounterTest.java │ ├── LengthCounterTest.java │ ├── LengthTriggerTest.java │ ├── LfsrTest.java │ └── SweepTest.java └── resources ├── roms ├── blargg │ ├── cgb_sound.gb │ ├── cgb_sound │ │ ├── 01-registers.gb │ │ ├── 02-len ctr.gb │ │ ├── 03-trigger.gb │ │ ├── 04-sweep.gb │ │ ├── 05-sweep details.gb │ │ ├── 06-overflow on trigger.gb │ │ ├── 07-len sweep period sync.gb │ │ ├── 08-len ctr during power.gb │ │ ├── 09-wave read while on.gb │ │ ├── 10-wave trigger while on.gb │ │ ├── 11-regs after power.gb │ │ └── 12-wave.gb │ ├── cpu_instrs.gb │ ├── cpu_instrs │ │ ├── 01-special.gb │ │ ├── 02-interrupts.gb │ │ ├── 03-op sp,hl.gb │ │ ├── 04-op r,imm.gb │ │ ├── 05-op rp.gb │ │ ├── 06-ld r,r.gb │ │ ├── 07-jr,jp,call,ret,rst.gb │ │ ├── 08-misc instrs.gb │ │ ├── 09-op r,r.gb │ │ ├── 10-bit ops.gb │ │ └── 11-op a,(hl).gb │ ├── dmg_sound-2.gb │ ├── dmg_sound-2 │ │ ├── 01-registers.gb │ │ ├── 02-len ctr.gb │ │ ├── 03-trigger.gb │ │ ├── 04-sweep.gb │ │ ├── 05-sweep details.gb │ │ ├── 06-overflow on trigger.gb │ │ ├── 07-len sweep period sync.gb │ │ ├── 08-len ctr during power.gb │ │ ├── 09-wave read while on.gb │ │ ├── 10-wave trigger while on.gb │ │ ├── 11-regs after power.gb │ │ └── 12-wave write while on.gb │ ├── halt_bug.gb │ ├── instr_timing.gb │ ├── interrupt_time.gb │ ├── mem_timing-2.gb │ ├── mem_timing-2 │ │ ├── 01-read_timing.gb │ │ ├── 02-write_timing.gb │ │ └── 03-modify_timing.gb │ ├── oam_bug-2.gb │ └── oam_bug-2 │ │ ├── 1-lcd_sync.gb │ │ ├── 2-causes.gb │ │ ├── 3-non_causes.gb │ │ ├── 4-scanline_timing.gb │ │ ├── 5-timing_bug.gb │ │ ├── 6-timing_no_bug.gb │ │ ├── 7-timing_effect.gb │ │ └── 8-instr_effect.gb ├── dmg-acid2 │ ├── dmg-acid2.gb │ └── dmg-acid2.png └── mooneye │ ├── LICENSE │ ├── acceptance │ ├── add_sp_e_timing.gb │ ├── bits │ │ ├── mem_oam.gb │ │ ├── reg_f.gb │ │ └── unused_hwio-GS.gb │ ├── boot_div-S.gb │ ├── boot_div-dmg0.gb │ ├── boot_div-dmgABCmgb.gb │ ├── boot_div2-S.gb │ ├── boot_hwio-S.gb │ ├── boot_hwio-dmg0.gb │ ├── boot_hwio-dmgABCmgb.gb │ ├── boot_regs-dmg0.gb │ ├── boot_regs-dmgABC.gb │ ├── boot_regs-mgb.gb │ ├── boot_regs-sgb.gb │ ├── boot_regs-sgb2.gb │ ├── call_cc_timing.gb │ ├── call_cc_timing2.gb │ ├── call_timing.gb │ ├── call_timing2.gb │ ├── di_timing-GS.gb │ ├── div_timing.gb │ ├── ei_sequence.gb │ ├── ei_timing.gb │ ├── halt_ime0_ei.gb │ ├── halt_ime0_nointr_timing.gb │ ├── halt_ime1_timing.gb │ ├── halt_ime1_timing2-GS.gb │ ├── if_ie_registers.gb │ ├── instr │ │ └── daa.gb │ ├── interrupts │ │ └── ie_push.gb │ ├── intr_timing.gb │ ├── jp_cc_timing.gb │ ├── jp_timing.gb │ ├── ld_hl_sp_e_timing.gb │ ├── oam_dma │ │ ├── basic.gb │ │ ├── reg_read.gb │ │ └── sources-dmgABCmgbS.gb │ ├── oam_dma_restart.gb │ ├── oam_dma_start.gb │ ├── oam_dma_timing.gb │ ├── pop_timing.gb │ ├── ppu │ │ ├── hblank_ly_scx_timing-GS.gb │ │ ├── intr_1_2_timing-GS.gb │ │ ├── intr_2_0_timing.gb │ │ ├── intr_2_mode0_timing.gb │ │ ├── intr_2_mode0_timing_sprites.gb │ │ ├── intr_2_mode3_timing.gb │ │ ├── intr_2_oam_ok_timing.gb │ │ ├── lcdon_timing-dmgABCmgbS.gb │ │ ├── lcdon_write_timing-GS.gb │ │ ├── stat_irq_blocking.gb │ │ ├── stat_lyc_onoff.gb │ │ └── vblank_stat_intr-GS.gb │ ├── push_timing.gb │ ├── rapid_di_ei.gb │ ├── ret_cc_timing.gb │ ├── ret_timing.gb │ ├── reti_intr_timing.gb │ ├── reti_timing.gb │ ├── rst_timing.gb │ ├── serial │ │ └── boot_sclk_align-dmgABCmgb.gb │ └── timer │ │ ├── div_write.gb │ │ ├── rapid_toggle.gb │ │ ├── tim00.gb │ │ ├── tim00_div_trigger.gb │ │ ├── tim01.gb │ │ ├── tim01_div_trigger.gb │ │ ├── tim10.gb │ │ ├── tim10_div_trigger.gb │ │ ├── tim11.gb │ │ ├── tim11_div_trigger.gb │ │ ├── tima_reload.gb │ │ ├── tima_write_reloading.gb │ │ └── tma_write_reloading.gb │ ├── emulator-only │ └── mbc1 │ │ ├── bits_ram_en.gb │ │ ├── multicart_rom_8Mb.gb │ │ ├── ram_256Kb.gb │ │ ├── ram_64Kb.gb │ │ ├── rom_16Mb.gb │ │ ├── rom_1Mb.gb │ │ ├── rom_2Mb.gb │ │ ├── rom_4Mb.gb │ │ ├── rom_512Kb.gb │ │ └── rom_8Mb.gb │ ├── manual-only │ └── sprite_priority.gb │ ├── misc │ ├── bits │ │ └── unused_hwio-C.gb │ ├── boot_div-A.gb │ ├── boot_div-cgb0.gb │ ├── boot_div-cgbABCDE.gb │ ├── boot_hwio-C.gb │ ├── boot_regs-A.gb │ ├── boot_regs-cgb.gb │ └── ppu │ │ └── vblank_stat_intr-C.gb │ └── utils │ ├── bootrom_dumper.gb │ └── dump_boot_hwio.gb └── simplelogger.properties /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | *.iml 4 | games 5 | roms 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | script: mvn test -Ptest-blargg -B 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tomasz Rękawek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /doc/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot1.png -------------------------------------------------------------------------------- /doc/screenshot10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot10.png -------------------------------------------------------------------------------- /doc/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot2.png -------------------------------------------------------------------------------- /doc/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot3.png -------------------------------------------------------------------------------- /doc/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot4.png -------------------------------------------------------------------------------- /doc/screenshot5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot5.png -------------------------------------------------------------------------------- /doc/screenshot6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot6.png -------------------------------------------------------------------------------- /doc/screenshot7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot7.png -------------------------------------------------------------------------------- /doc/screenshot8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot8.png -------------------------------------------------------------------------------- /doc/screenshot9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/screenshot9.png -------------------------------------------------------------------------------- /doc/test-rom-archives/cgb_sound.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/test-rom-archives/cgb_sound.zip -------------------------------------------------------------------------------- /doc/test-rom-archives/cpu_instrs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/test-rom-archives/cpu_instrs.zip -------------------------------------------------------------------------------- /doc/test-rom-archives/dmg_sound-2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/test-rom-archives/dmg_sound-2.zip -------------------------------------------------------------------------------- /doc/test-rom-archives/halt_bug.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/test-rom-archives/halt_bug.zip -------------------------------------------------------------------------------- /doc/test-rom-archives/instr_timing.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/test-rom-archives/instr_timing.zip -------------------------------------------------------------------------------- /doc/test-rom-archives/interrupt_time.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/test-rom-archives/interrupt_time.zip -------------------------------------------------------------------------------- /doc/test-rom-archives/mem_timing-2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/test-rom-archives/mem_timing-2.zip -------------------------------------------------------------------------------- /doc/test-rom-archives/oam_bug-2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/test-rom-archives/oam_bug-2.zip -------------------------------------------------------------------------------- /doc/tetris.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/doc/tetris.gif -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | java -jar target/coffee-gb-*-SNAPSHOT-complete.jar "$@" 4 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/AddressSpace.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb; 2 | 3 | public interface AddressSpace { 4 | 5 | boolean accepts(int address); 6 | 7 | void setByte(int address, int value); 8 | 9 | int getByte(int address); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/CartridgeOptions.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb; 2 | 3 | import java.util.Collection; 4 | 5 | public class CartridgeOptions { 6 | 7 | private final boolean forceDmg; 8 | 9 | private final boolean forceCgb; 10 | 11 | private final boolean useBootstrap; 12 | 13 | private final boolean disableBatterySaves; 14 | 15 | public CartridgeOptions(Collection params, Collection shortParams) { 16 | this.forceDmg = params.contains("force-dmg") || shortParams.contains("d"); 17 | this.forceCgb = params.contains("force-cgb") || shortParams.contains("c"); 18 | if (forceDmg && forceCgb) { 19 | throw new IllegalArgumentException("force-dmg and force-cgb options are can't be used together"); 20 | } 21 | this.useBootstrap = params.contains("use-bootstrap") || shortParams.contains("b"); 22 | this.disableBatterySaves = params.contains("disable-battery-saves") || shortParams.contains("db"); 23 | } 24 | 25 | public boolean isForceDmg() { 26 | return forceDmg; 27 | } 28 | 29 | public boolean isForceCgb() { 30 | return forceCgb; 31 | } 32 | 33 | public boolean isUsingBootstrap() { 34 | return useBootstrap; 35 | } 36 | 37 | public boolean isSupportBatterySaves() { 38 | return !disableBatterySaves; 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/Dumper.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb; 2 | 3 | import static eu.rekawek.coffeegb.gpu.Fetcher.zip; 4 | 5 | public final class Dumper { 6 | 7 | private Dumper() { 8 | } 9 | 10 | public static void dump(AddressSpace addressSpace, int offset, int length) { 11 | for (int i = offset; i < (offset + length); i++) { 12 | System.out.printf("%02X ", addressSpace.getByte(i)); 13 | if ((i - offset + 1) % 16 == 0) { 14 | System.out.print(" "); 15 | dumpText(addressSpace, i - 16); 16 | System.out.println(); 17 | } 18 | } 19 | } 20 | 21 | private static void dumpText(AddressSpace addressSpace, int offset) { 22 | for (int i = 0; i < 16; i++) { 23 | System.out.print((char) addressSpace.getByte(offset + i)); 24 | } 25 | } 26 | 27 | public static void dumpTile(AddressSpace addressSpace, int offset) { 28 | for (int i = 0; i < 16; i += 2) { 29 | dumpTileLine(addressSpace.getByte(offset + i), addressSpace.getByte(offset + i + 1)); 30 | System.out.println(); 31 | } 32 | } 33 | 34 | public static void dumpTileLine(int data1, int data2) { 35 | for (int pixel : zip(data1, data2, false, new int[8])) { 36 | if (pixel == 0) { 37 | System.out.print('.'); 38 | } else { 39 | System.out.print(pixel); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/Main.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb; 2 | 3 | import eu.rekawek.coffeegb.swing.gui.SwingGui; 4 | 5 | import java.io.File; 6 | import java.io.PrintStream; 7 | import java.util.*; 8 | 9 | public class Main { 10 | 11 | public static void main(String[] args) { 12 | ParsedArgs parsedArgs = ParsedArgs.parse(args); 13 | if (parsedArgs.shortParams.contains("h") || parsedArgs.params.contains("help")) { 14 | printUsage(System.out); 15 | return; 16 | } 17 | if (parsedArgs.args.size() > 1) { 18 | printUsage(System.err); 19 | return; 20 | } 21 | 22 | boolean debug = parsedArgs.params.contains("debug"); 23 | 24 | File initialRom = null; 25 | if (parsedArgs.args.size() == 1) { 26 | initialRom = new File(parsedArgs.args.get(0)); 27 | } 28 | SwingGui.Companion.run(debug, initialRom); 29 | } 30 | 31 | private static class ParsedArgs { 32 | private final Set params; 33 | private final Set shortParams; 34 | private final List args; 35 | 36 | private ParsedArgs(Set params, Set shortParams, List args) { 37 | this.params = params; 38 | this.shortParams = shortParams; 39 | this.args = args; 40 | } 41 | 42 | private static ParsedArgs parse(String[] args) { 43 | Set params = new LinkedHashSet<>(); 44 | Set shortParams = new LinkedHashSet<>(); 45 | List restArgs = new ArrayList<>(); 46 | for (String a : args) { 47 | if (a.startsWith("--")) { 48 | params.add(a.substring(2)); 49 | } else if (a.startsWith("-")) { 50 | shortParams.add(a.substring(1)); 51 | } else { 52 | restArgs.add(a); 53 | } 54 | } 55 | return new ParsedArgs(params, shortParams, restArgs); 56 | } 57 | } 58 | 59 | public static void printUsage(PrintStream stream) { 60 | stream.println("Usage:"); 61 | stream.println("java -jar coffee-gb.jar [OPTIONS] [ROM_FILE]"); 62 | stream.println(); 63 | stream.println("Available options:"); 64 | stream.println(" -d --force-dmg Emulate classic GB (DMG) for universal ROMs"); 65 | stream.println(" -c --force-cgb Emulate color GB (CGB) for all ROMs"); 66 | stream.println(" -b --use-bootstrap Start with the GB bootstrap"); 67 | stream.println(" -db --disable-battery-saves Disable battery saves"); 68 | stream.println(" -h --help Displays this info"); 69 | stream.println(" --debug Enable debug console"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/controller/ButtonListener.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.controller; 2 | 3 | public interface ButtonListener { 4 | 5 | enum Button { 6 | RIGHT(0x01, 0x10), LEFT(0x02, 0x10), UP(0x04, 0x10), DOWN(0x08, 0x10), 7 | A(0x01, 0x20), B(0x02, 0x20), SELECT(0x04, 0x20), START(0x08, 0x20); 8 | 9 | private final int mask; 10 | 11 | private final int line; 12 | 13 | Button(int mask, int line) { 14 | this.mask = mask; 15 | this.line = line; 16 | } 17 | 18 | public int getMask() { 19 | return mask; 20 | } 21 | 22 | public int getLine() { 23 | return line; 24 | } 25 | } 26 | 27 | void onButtonPress(Button button); 28 | 29 | void onButtonRelease(Button button); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/controller/Controller.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.controller; 2 | 3 | public interface Controller { 4 | 5 | void setButtonListener(ButtonListener listener); 6 | 7 | Controller NULL_CONTROLLER = listener -> {}; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/controller/Joypad.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.controller; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.cpu.InterruptManager; 5 | 6 | import java.io.Serializable; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import java.util.concurrent.CopyOnWriteArraySet; 10 | 11 | public class Joypad implements AddressSpace, Serializable { 12 | 13 | private final Set buttons = new CopyOnWriteArraySet<>(); 14 | private final InterruptManager interruptManager; 15 | private int p1; 16 | 17 | public Joypad(InterruptManager interruptManager) { 18 | this.interruptManager = interruptManager; 19 | } 20 | 21 | public void init(Controller controller) { 22 | controller.setButtonListener(new ButtonListener() { 23 | @Override 24 | public void onButtonPress(Button button) { 25 | interruptManager.requestInterrupt(InterruptManager.InterruptType.P10_13); 26 | buttons.add(button); 27 | } 28 | 29 | @Override 30 | public void onButtonRelease(Button button) { 31 | buttons.remove(button); 32 | } 33 | }); 34 | } 35 | 36 | @Override 37 | public boolean accepts(int address) { 38 | return address == 0xff00; 39 | } 40 | 41 | @Override 42 | public void setByte(int address, int value) { 43 | p1 = value & 0b00110000; 44 | } 45 | 46 | @Override 47 | public int getByte(int address) { 48 | int result = p1 | 0b11001111; 49 | for (ButtonListener.Button b : buttons) { 50 | if ((b.getLine() & p1) == 0) { 51 | result &= 0xff & ~b.getMask(); 52 | } 53 | } 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/cpu/BitUtils.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.cpu; 2 | 3 | import static com.google.common.base.Preconditions.checkArgument; 4 | 5 | public final class BitUtils { 6 | 7 | private BitUtils() { 8 | } 9 | 10 | public static int getMSB(int word) { 11 | checkWordArgument("word", word); 12 | return word >> 8; 13 | } 14 | 15 | public static int getLSB(int word) { 16 | checkWordArgument("word", word); 17 | return word & 0xff; 18 | } 19 | 20 | public static int toWord(int[] bytes) { 21 | return toWord(bytes[1], bytes[0]); 22 | } 23 | 24 | public static int toWord(int msb, int lsb) { 25 | checkByteArgument("msb", msb); 26 | checkByteArgument("lsb", lsb); 27 | return (msb << 8) | lsb; 28 | } 29 | 30 | public static boolean getBit(int byteValue, int position) { 31 | return (byteValue & (1 << position)) != 0; 32 | } 33 | 34 | public static int setBit(int byteValue, int position, boolean value) { 35 | return value ? setBit(byteValue, position) : clearBit(byteValue, position); 36 | } 37 | 38 | public static int setBit(int byteValue, int position) { 39 | checkByteArgument("byteValue", byteValue); 40 | return (byteValue | (1 << position)) & 0xff; 41 | } 42 | 43 | public static int clearBit(int byteValue, int position) { 44 | checkByteArgument("byteValue", byteValue); 45 | return ~(1 << position) & byteValue & 0xff; 46 | } 47 | 48 | public static int toSigned(int byteValue) { 49 | if ((byteValue & (1 << 7)) == 0) { 50 | return byteValue; 51 | } else { 52 | return byteValue - 0x100; 53 | } 54 | } 55 | 56 | public static void checkByteArgument(String argumentName, int argument) { 57 | checkArgument(argument >= 0 && argument <= 0xff, "Argument {} should be a byte", argumentName); 58 | } 59 | 60 | public static void checkWordArgument(String argumentName, int argument) { 61 | checkArgument(argument >= 0 && argument <= 0xffff, "Argument {} should be a word", argumentName); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/cpu/Flags.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.cpu; 2 | 3 | import java.io.Serializable; 4 | 5 | import static eu.rekawek.coffeegb.cpu.BitUtils.*; 6 | 7 | public class Flags implements Serializable { 8 | 9 | private static final int Z_POS = 7; 10 | 11 | private static final int N_POS = 6; 12 | 13 | private static final int H_POS = 5; 14 | 15 | private static final int C_POS = 4; 16 | 17 | private int flags; 18 | 19 | public int getFlagsByte() { 20 | return flags; 21 | } 22 | 23 | public boolean isZ() { 24 | return getBit(flags, Z_POS); 25 | } 26 | 27 | public boolean isN() { 28 | return getBit(flags, N_POS); 29 | } 30 | 31 | public boolean isH() { 32 | return getBit(flags, H_POS); 33 | } 34 | 35 | public boolean isC() { 36 | return getBit(flags, C_POS); 37 | } 38 | 39 | public void setZ(boolean z) { 40 | flags = setBit(flags, Z_POS, z); 41 | } 42 | 43 | public void setN(boolean n) { 44 | flags = setBit(flags, N_POS, n); 45 | } 46 | 47 | public void setH(boolean h) { 48 | flags = setBit(flags, H_POS, h); 49 | } 50 | 51 | public void setC(boolean c) { 52 | flags = setBit(flags, C_POS, c); 53 | } 54 | 55 | public void setFlagsByte(int flags) { 56 | checkByteArgument("flags", flags); 57 | this.flags = flags & 0xf0; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return String.valueOf(isZ() ? 'Z' : '-') + 63 | (isN() ? 'N' : '-') + 64 | (isH() ? 'H' : '-') + 65 | (isC() ? 'C' : '-') + 66 | "----"; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/cpu/SpeedMode.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.cpu; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | public class SpeedMode implements AddressSpace, Serializable { 8 | 9 | private boolean currentSpeed; 10 | 11 | private boolean prepareSpeedSwitch; 12 | 13 | @Override 14 | public boolean accepts(int address) { 15 | return address == 0xff4d; 16 | } 17 | 18 | @Override 19 | public void setByte(int address, int value) { 20 | prepareSpeedSwitch = (value & 0x01) != 0; 21 | } 22 | 23 | @Override 24 | public int getByte(int address) { 25 | return (currentSpeed ? (1 << 7) : 0) | (prepareSpeedSwitch ? (1 << 0) : 0) | 0b01111110; 26 | } 27 | 28 | boolean onStop() { 29 | if (prepareSpeedSwitch) { 30 | currentSpeed = !currentSpeed; 31 | prepareSpeedSwitch = false; 32 | return true; 33 | } else { 34 | return false; 35 | } 36 | } 37 | 38 | public int getSpeedMode() { 39 | return currentSpeed ? 2 : 1; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/cpu/op/DataType.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.cpu.op; 2 | 3 | public enum DataType { 4 | 5 | D8, D16, R8 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/cpu/op/Op.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.cpu.op; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.cpu.InterruptManager; 5 | import eu.rekawek.coffeegb.cpu.Registers; 6 | import eu.rekawek.coffeegb.gpu.SpriteBug; 7 | 8 | import java.io.Serializable; 9 | 10 | public interface Op extends Serializable { 11 | 12 | default boolean readsMemory() { 13 | return false; 14 | } 15 | 16 | default boolean writesMemory() { 17 | return false; 18 | } 19 | 20 | default int operandLength() { 21 | return 0; 22 | } 23 | 24 | default int execute(Registers registers, AddressSpace addressSpace, int[] args, int context) { 25 | return context; 26 | } 27 | 28 | default void switchInterrupts(InterruptManager interruptManager) { 29 | } 30 | 31 | default boolean proceed(Registers registers) { 32 | return true; 33 | } 34 | 35 | default boolean forceFinishCycle() { 36 | return false; 37 | } 38 | 39 | default SpriteBug.CorruptionType causesOemBug(Registers registers, int context) { 40 | return null; 41 | } 42 | 43 | String toString(); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/cpu/opcode/Opcode.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.cpu.opcode; 2 | 3 | import eu.rekawek.coffeegb.cpu.op.Op; 4 | 5 | import java.io.Serializable; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class Opcode implements Serializable { 11 | 12 | private final int opcode; 13 | 14 | private final String label; 15 | 16 | private final List ops; 17 | 18 | private final int length; 19 | 20 | Opcode(OpcodeBuilder builder) { 21 | this.opcode = builder.getOpcode(); 22 | this.label = builder.getLabel(); 23 | this.ops = List.copyOf(builder.getOps()); 24 | this.length = ops.stream().mapToInt(Op::operandLength).max().orElse(0); 25 | } 26 | 27 | public int getOperandLength() { 28 | return length; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return String.format("%02x %s", opcode, label); 34 | } 35 | 36 | public List getOps() { 37 | return ops; 38 | } 39 | 40 | public String getLabel() { 41 | return label; 42 | } 43 | 44 | public int getOpcode() { 45 | return opcode; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/debug/Command.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.debug; 2 | 3 | import eu.rekawek.coffeegb.debug.CommandPattern.ParsedCommandLine; 4 | 5 | public interface Command { 6 | 7 | CommandPattern getPattern(); 8 | 9 | void run(ParsedCommandLine commandLine); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/debug/CommandArgument.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.debug; 2 | 3 | import java.util.Optional; 4 | import java.util.Set; 5 | 6 | public class CommandArgument { 7 | 8 | private final String name; 9 | 10 | private final boolean required; 11 | 12 | private final Optional> allowedValues; 13 | 14 | public CommandArgument(String name, boolean required) { 15 | this.name = name; 16 | this.required = required; 17 | this.allowedValues = Optional.empty(); 18 | } 19 | 20 | public CommandArgument(String name, boolean required, Set allowedValues) { 21 | this.name = name; 22 | this.required = required; 23 | this.allowedValues = Optional.of(allowedValues); 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public boolean isRequired() { 31 | return required; 32 | } 33 | 34 | public Optional> getAllowedValues() { 35 | return allowedValues; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | StringBuilder builder = new StringBuilder(); 41 | if (!required) { 42 | builder.append('['); 43 | } 44 | if (allowedValues.isPresent()) { 45 | builder.append('{'); 46 | builder.append(String.join(",", allowedValues.get())); 47 | builder.append('}'); 48 | } else { 49 | builder.append(name.toUpperCase()); 50 | } 51 | if (!required) { 52 | builder.append(']'); 53 | } 54 | return builder.toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/debug/ConsoleUtil.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.debug; 2 | 3 | public final class ConsoleUtil { 4 | 5 | private ConsoleUtil() { 6 | } 7 | 8 | public static void printSeparator(int width) { 9 | System.out.println(String.format("%" + width + "s", "").replace(' ', '-')); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/debug/command/Quit.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.debug.command; 2 | 3 | import eu.rekawek.coffeegb.debug.Command; 4 | import eu.rekawek.coffeegb.debug.CommandPattern; 5 | 6 | public class Quit implements Command { 7 | 8 | private static final CommandPattern PATTERN = CommandPattern.Builder 9 | .create("quit", "q") 10 | .withDescription("quits the emulator") 11 | .build(); 12 | 13 | @Override 14 | public CommandPattern getPattern() { 15 | return PATTERN; 16 | } 17 | 18 | @Override 19 | public void run(CommandPattern.ParsedCommandLine commandLine) { 20 | System.exit(0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/debug/command/ShowHelp.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.debug.command; 2 | 3 | import eu.rekawek.coffeegb.debug.Command; 4 | import eu.rekawek.coffeegb.debug.CommandArgument; 5 | import eu.rekawek.coffeegb.debug.CommandPattern; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | import static com.google.common.collect.Maps.newHashMap; 12 | 13 | public class ShowHelp implements Command { 14 | 15 | private static final CommandPattern PATTERN = CommandPattern.Builder 16 | .create("help", "?") 17 | .withDescription("displays supported commands") 18 | .build(); 19 | 20 | private final List commands; 21 | 22 | public ShowHelp(List commands) { 23 | this.commands = commands; 24 | } 25 | 26 | @Override 27 | public CommandPattern getPattern() { 28 | return PATTERN; 29 | } 30 | 31 | @Override 32 | public void run(CommandPattern.ParsedCommandLine commandLine) { 33 | int max = 0; 34 | Map commandMap = newHashMap(); 35 | for (Command command : commands) { 36 | CommandPattern pattern = command.getPattern(); 37 | String alias = pattern.getCommandNames().get(0); 38 | String commandWithArgs = getCommandWithArgs(alias, pattern.getArguments()); 39 | if (commandWithArgs.length() > max) { 40 | max = commandWithArgs.length(); 41 | } 42 | commandMap.put(command, commandWithArgs); 43 | } 44 | 45 | for (Command command : commands) { 46 | CommandPattern pattern = command.getPattern(); 47 | String longName = commandMap.get(command); 48 | System.out.printf("%-" + max + "s", longName); 49 | if (pattern.getCommandNames().size() > 1) { 50 | System.out.printf(" %-5s", pattern.getCommandNames().get(1)); 51 | } else { 52 | System.out.print(" "); 53 | } 54 | command.getPattern() 55 | .getDescription() 56 | .map(d -> " " + d) 57 | .ifPresent(System.out::print); 58 | System.out.println(); 59 | } 60 | } 61 | 62 | private String getCommandWithArgs(String alias, List args) { 63 | StringBuilder builder = new StringBuilder(alias); 64 | if (!args.isEmpty()) { 65 | builder.append(' ') 66 | .append(args 67 | .stream() 68 | .map(CommandArgument::toString) 69 | .collect(Collectors.joining(" "))); 70 | } 71 | return builder.toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/debug/command/apu/Channel.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.debug.command.apu; 2 | 3 | import eu.rekawek.coffeegb.debug.Command; 4 | import eu.rekawek.coffeegb.debug.CommandPattern; 5 | import eu.rekawek.coffeegb.sound.Sound; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | public class Channel implements Command { 11 | 12 | private static final CommandPattern PATTERN = CommandPattern.Builder 13 | .create("apu chan") 14 | .withDescription("enable given channels (1-4)") 15 | .build(); 16 | 17 | private final Sound sound; 18 | 19 | public Channel(Sound sound) { 20 | this.sound = sound; 21 | } 22 | 23 | @Override 24 | public CommandPattern getPattern() { 25 | return PATTERN; 26 | } 27 | 28 | @Override 29 | public void run(CommandPattern.ParsedCommandLine commandLine) { 30 | Set channels = new HashSet<>(commandLine.getRemainingArguments()); 31 | for (int i = 1; i <= 4; i++) { 32 | sound.enableChannel(i - 1, channels.contains(String.valueOf(i))); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/debug/command/cpu/ShowOpcodes.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.debug.command.cpu; 2 | 3 | import eu.rekawek.coffeegb.cpu.Opcodes; 4 | import eu.rekawek.coffeegb.cpu.opcode.Opcode; 5 | import eu.rekawek.coffeegb.debug.Command; 6 | import eu.rekawek.coffeegb.debug.CommandPattern; 7 | import eu.rekawek.coffeegb.debug.CommandPattern.ParsedCommandLine; 8 | 9 | import java.util.List; 10 | 11 | public class ShowOpcodes implements Command { 12 | 13 | private static final CommandPattern PATTERN = CommandPattern.Builder 14 | .create("cpu show opcodes") 15 | .withDescription("displays all opcodes") 16 | .build(); 17 | 18 | @Override 19 | public CommandPattern getPattern() { 20 | return PATTERN; 21 | } 22 | 23 | @Override 24 | public void run(ParsedCommandLine commandLine) { 25 | printTable(Opcodes.COMMANDS); 26 | System.out.println("\n0xCB"); 27 | printTable(Opcodes.EXT_COMMANDS); 28 | } 29 | 30 | private static void printTable(List opcodes) { 31 | System.out.print(" "); 32 | for (int i = 0; i < 0x10; i++) { 33 | System.out.printf("%02X ", i); 34 | } 35 | System.out.println(); 36 | 37 | for (int i = 0; i < 0x100; i += 0x10) { 38 | System.out.printf("%02X ", i); 39 | for (int j = 0; j < 0x10; j++) { 40 | Opcode opcode = opcodes.get(i + j); 41 | String label = opcode == null ? "-" : opcode.getLabel(); 42 | System.out.printf("%-12s", label); 43 | } 44 | System.out.println(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/ColorPalette.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | public class ColorPalette implements AddressSpace, Serializable { 8 | 9 | private final int indexAddr; 10 | 11 | private final int dataAddr; 12 | 13 | private final int[][] palettes = new int[8][4]; 14 | 15 | private int index; 16 | 17 | private boolean autoIncrement; 18 | 19 | public ColorPalette(int offset) { 20 | this.indexAddr = offset; 21 | this.dataAddr = offset + 1; 22 | } 23 | 24 | @Override 25 | public boolean accepts(int address) { 26 | return address == indexAddr || address == dataAddr; 27 | } 28 | 29 | @Override 30 | public void setByte(int address, int value) { 31 | if (address == indexAddr) { 32 | index = value & 0x3f; 33 | autoIncrement = (value & (1 << 7)) != 0; 34 | } else if (address == dataAddr) { 35 | int color = palettes[index / 8][(index % 8) / 2]; 36 | if (index % 2 == 0) { 37 | color = (color & 0xff00) | value; 38 | } else { 39 | color = (color & 0x00ff) | (value << 8); 40 | } 41 | palettes[index / 8][(index % 8) / 2] = color; 42 | if (autoIncrement) { 43 | index = (index + 1) & 0x3f; 44 | } 45 | } else { 46 | throw new IllegalArgumentException(); 47 | } 48 | } 49 | 50 | @Override 51 | public int getByte(int address) { 52 | if (address == indexAddr) { 53 | return index | (autoIncrement ? 0x80 : 0x00) | 0x40; 54 | } else if (address == dataAddr) { 55 | int color = palettes[index / 8][(index % 8) / 2]; 56 | if (index % 2 == 0) { 57 | return color & 0xff; 58 | } else { 59 | return (color >> 8) & 0xff; 60 | } 61 | } else { 62 | throw new IllegalArgumentException(); 63 | } 64 | } 65 | 66 | public int[] getPalette(int index) { 67 | return palettes[index]; 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | StringBuilder b = new StringBuilder(); 73 | for (int i = 0; i < 8; i++) { 74 | b.append(i).append(": "); 75 | int[] palette = getPalette(i); 76 | for (int c : palette) { 77 | b.append(String.format("%04X", c)).append(' '); 78 | } 79 | b.setCharAt(b.length() - 1, '\n'); 80 | } 81 | return b.toString(); 82 | } 83 | 84 | public void fillWithFF() { 85 | for (int i = 0; i < 8; i++) { 86 | for (int j = 0; j < 4; j++) { 87 | palettes[i][j] = 0x7fff; 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/Display.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | public interface Display { 4 | 5 | int DISPLAY_WIDTH = 160; 6 | 7 | int DISPLAY_HEIGHT = 144; 8 | 9 | void putDmgPixel(int color); 10 | 11 | void putColorPixel(int gbcRgb); 12 | 13 | void frameIsReady(); 14 | 15 | void enableLcd(); 16 | 17 | void disableLcd(); 18 | 19 | static int translateGbcRgb(int gbcRgb) { 20 | int r = (gbcRgb >> 0) & 0x1f; 21 | int g = (gbcRgb >> 5) & 0x1f; 22 | int b = (gbcRgb >> 10) & 0x1f; 23 | int result = (r * 8) << 16; 24 | result |= (g * 8) << 8; 25 | result |= (b * 8) << 0; 26 | return result; 27 | } 28 | 29 | Display NULL_DISPLAY = new Display() { 30 | 31 | @Override 32 | public void putDmgPixel(int color) { 33 | } 34 | 35 | @Override 36 | public void putColorPixel(int gbcRgb) { 37 | } 38 | 39 | @Override 40 | public void frameIsReady() { 41 | } 42 | 43 | @Override 44 | public void enableLcd() { 45 | } 46 | 47 | @Override 48 | public void disableLcd() { 49 | } 50 | }; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/DmgPixelFifo.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import java.io.Serializable; 4 | 5 | public class DmgPixelFifo implements PixelFifo, Serializable { 6 | 7 | private final IntQueue pixels = new IntQueue(16); 8 | 9 | private final IntQueue palettes = new IntQueue(16); 10 | 11 | private final IntQueue pixelType = new IntQueue(16); // 0 - bg, 1 - sprite 12 | 13 | private transient Display display; 14 | 15 | private final GpuRegisterValues registers; 16 | 17 | public DmgPixelFifo(GpuRegisterValues registers) { 18 | this.registers = registers; 19 | } 20 | 21 | @Override 22 | public void init(Display display) { 23 | this.display = display; 24 | } 25 | 26 | @Override 27 | public int getLength() { 28 | return pixels.size(); 29 | } 30 | 31 | @Override 32 | public void putPixelToScreen() { 33 | display.putDmgPixel(dequeuePixel()); 34 | } 35 | 36 | @Override 37 | public void dropPixel() { 38 | dequeuePixel(); 39 | } 40 | 41 | int dequeuePixel() { 42 | pixelType.dequeue(); 43 | return getColor(palettes.dequeue(), pixels.dequeue()); 44 | } 45 | 46 | @Override 47 | public void enqueue8Pixels(int[] pixelLine, TileAttributes tileAttributes) { 48 | for (int p : pixelLine) { 49 | pixels.enqueue(p); 50 | palettes.enqueue(registers.get(GpuRegister.BGP)); 51 | pixelType.enqueue(0); 52 | } 53 | } 54 | 55 | @Override 56 | public void setOverlay(int[] pixelLine, int offset, TileAttributes flags, int oamIndex) { 57 | boolean priority = flags.isPriority(); 58 | int overlayPalette = registers.get(flags.getDmgPalette()); 59 | 60 | for (int j = offset; j < pixelLine.length; j++) { 61 | int p = pixelLine[j]; 62 | int i = j - offset; 63 | if (pixelType.get(i) == 1) { 64 | continue; 65 | } 66 | if ((priority && pixels.get(i) == 0) || !priority && p != 0) { 67 | pixels.set(i, p); 68 | palettes.set(i, overlayPalette); 69 | pixelType.set(i, 1); 70 | } 71 | } 72 | } 73 | 74 | IntQueue getPixels() { 75 | return pixels; 76 | } 77 | 78 | private static int getColor(int palette, int colorIndex) { 79 | return 0b11 & (palette >> (colorIndex * 2)); 80 | } 81 | 82 | @Override 83 | public void clear() { 84 | pixels.clear(); 85 | palettes.clear(); 86 | pixelType.clear(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/GpuRegister.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import static eu.rekawek.coffeegb.gpu.GpuRegister.RegisterType.*; 4 | 5 | public enum GpuRegister { 6 | 7 | STAT(0xff41, RW), 8 | SCY(0xff42, RW), 9 | SCX(0xff43, RW), 10 | LY(0xff44, R), 11 | LYC(0xff45, RW), 12 | BGP(0xff47, RW), 13 | OBP0(0xff48, RW), 14 | OBP1(0xff49, RW), 15 | WY(0xff4a, RW), 16 | WX(0xff4b, RW), 17 | VBK(0xff4f, W); 18 | 19 | private final int address; 20 | 21 | private final RegisterType type; 22 | 23 | GpuRegister(int address, RegisterType type) { 24 | this.address = address; 25 | this.type = type; 26 | } 27 | 28 | public int getAddress() { 29 | return address; 30 | } 31 | 32 | public RegisterType getType() { 33 | return type; 34 | } 35 | 36 | public enum RegisterType { 37 | R(true, false), W(false, true), RW(true, true); 38 | 39 | private final boolean allowsRead; 40 | 41 | private final boolean allowsWrite; 42 | 43 | RegisterType(boolean allowsRead, boolean allowsWrite) { 44 | this.allowsRead = allowsRead; 45 | this.allowsWrite = allowsWrite; 46 | } 47 | 48 | public boolean isAllowsRead() { 49 | return allowsRead; 50 | } 51 | 52 | public boolean isAllowsWrite() { 53 | return allowsWrite; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/GpuRegisterValues.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | public class GpuRegisterValues implements AddressSpace, Serializable { 8 | 9 | private static final GpuRegister[] ADDRESS_TO_REG = new GpuRegister[0xf]; 10 | 11 | private static final int ADDRESS_TO_REG_BASE = GpuRegister.values()[0].getAddress(); 12 | 13 | static { 14 | for (GpuRegister r : GpuRegister.values()) { 15 | ADDRESS_TO_REG[r.getAddress() - ADDRESS_TO_REG_BASE] = r; 16 | } 17 | } 18 | 19 | private final int[] values; 20 | 21 | public GpuRegisterValues() { 22 | values = new int[GpuRegister.values().length]; 23 | } 24 | 25 | public int get(GpuRegister reg) { 26 | return values[reg.ordinal()]; 27 | } 28 | 29 | public void put(GpuRegister reg, int value) { 30 | values[reg.ordinal()] = value; 31 | } 32 | 33 | public int preIncrement(GpuRegister reg) { 34 | return ++values[reg.ordinal()]; 35 | } 36 | 37 | @Override 38 | public boolean accepts(int address) { 39 | return fromAddress(address) != null; 40 | } 41 | 42 | @Override 43 | public void setByte(int address, int value) { 44 | GpuRegister reg = fromAddress(address); 45 | if (reg != null && reg.getType().isAllowsWrite()) { 46 | values[reg.ordinal()] = value; 47 | } 48 | } 49 | 50 | @Override 51 | public int getByte(int address) { 52 | GpuRegister reg = fromAddress(address); 53 | if (reg != null && reg.getType().isAllowsRead()) { 54 | return values[reg.ordinal()]; 55 | } else { 56 | return 0xff; 57 | } 58 | } 59 | 60 | private static GpuRegister fromAddress(int address) { 61 | int index = address - ADDRESS_TO_REG_BASE; 62 | if (index >= 0 && index < ADDRESS_TO_REG.length) { 63 | return ADDRESS_TO_REG[index]; 64 | } else { 65 | return null; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/IntQueue.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import java.io.Serializable; 4 | import java.util.NoSuchElementException; 5 | 6 | public class IntQueue implements Serializable { 7 | 8 | private final int[] array; 9 | 10 | private int size; 11 | 12 | private int offset = 0; 13 | 14 | public IntQueue(int capacity) { 15 | this.array = new int[capacity]; 16 | this.size = 0; 17 | this.offset = 0; 18 | } 19 | 20 | public int size() { 21 | return size; 22 | } 23 | 24 | public void enqueue(int value) { 25 | if (size == array.length) { 26 | throw new IllegalStateException("Queue is full"); 27 | } 28 | array[(offset + size) % array.length] = value; 29 | size++; 30 | } 31 | 32 | public int dequeue() { 33 | if (size == 0) { 34 | throw new NoSuchElementException("Queue is empty"); 35 | } 36 | size--; 37 | int value = array[offset++]; 38 | if (offset == array.length) { 39 | offset = 0; 40 | } 41 | return value; 42 | } 43 | 44 | public int get(int index) { 45 | if (index >= size) { 46 | throw new IndexOutOfBoundsException(); 47 | } 48 | return array[(offset + index) % array.length]; 49 | } 50 | 51 | public void set(int index, int value) { 52 | if (index >= size) { 53 | throw new IndexOutOfBoundsException(); 54 | } 55 | array[(offset + index) % array.length] = value; 56 | } 57 | 58 | public void clear() { 59 | size = 0; 60 | offset = 0; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/Lcdc.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | import static com.google.common.base.Preconditions.checkArgument; 8 | 9 | public class Lcdc implements AddressSpace, Serializable { 10 | 11 | private int value = 0x91; 12 | 13 | public boolean isBgAndWindowDisplay() { 14 | return (value & 0x01) != 0; 15 | } 16 | 17 | public boolean isObjDisplay() { 18 | return (value & 0x02) != 0; 19 | } 20 | 21 | public int getSpriteHeight() { 22 | return (value & 0x04) == 0 ? 8 : 16; 23 | } 24 | 25 | public int getBgTileMapDisplay() { 26 | return (value & 0x08) == 0 ? 0x9800 : 0x9c00; 27 | } 28 | 29 | public int getBgWindowTileData() { 30 | return (value & 0x10) == 0 ? 0x9000 : 0x8000; 31 | } 32 | 33 | public boolean isBgWindowTileDataSigned() { 34 | return (value & 0x10) == 0; 35 | } 36 | 37 | public boolean isWindowDisplay() { 38 | return (value & 0x20) != 0; 39 | } 40 | 41 | public int getWindowTileMapDisplay() { 42 | return (value & 0x40) == 0 ? 0x9800 : 0x9c00; 43 | } 44 | 45 | public boolean isLcdEnabled() { 46 | return (value & 0x80) != 0; 47 | } 48 | 49 | @Override 50 | public boolean accepts(int address) { 51 | return address == 0xff40; 52 | } 53 | 54 | @Override 55 | public void setByte(int address, int value) { 56 | checkArgument(address == 0xff40); 57 | this.value = value; 58 | } 59 | 60 | @Override 61 | public int getByte(int address) { 62 | checkArgument(address == 0xff40); 63 | return value; 64 | } 65 | 66 | public void set(int value) { 67 | this.value = value; 68 | } 69 | 70 | public int get() { 71 | return value; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/PixelFifo.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | public interface PixelFifo { 4 | 5 | void init(Display display); 6 | 7 | int getLength(); 8 | 9 | void putPixelToScreen(); 10 | 11 | void dropPixel(); 12 | 13 | void enqueue8Pixels(int[] pixels, TileAttributes tileAttributes); 14 | 15 | void setOverlay(int[] pixelLine, int offset, TileAttributes flags, int oamIndex); 16 | 17 | void clear(); 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/SpriteBug.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | public final class SpriteBug { 6 | 7 | public enum CorruptionType { 8 | INC_DEC, POP_1, POP_2, PUSH_1, PUSH_2, LD_HL 9 | } 10 | 11 | private SpriteBug() { 12 | } 13 | 14 | public static void corruptOam(AddressSpace addressSpace, CorruptionType type, int ticksInLine) { 15 | int cpuCycle = (ticksInLine + 1) / 4 + 1; 16 | switch (type) { 17 | case INC_DEC: 18 | if (cpuCycle >= 2) { 19 | copyValues(addressSpace, (cpuCycle - 2) * 8 + 2, (cpuCycle - 1) * 8 + 2, 6); 20 | } 21 | break; 22 | 23 | case POP_1: 24 | if (cpuCycle >= 4) { 25 | copyValues(addressSpace, (cpuCycle - 3) * 8 + 2, (cpuCycle - 4) * 8 + 2, 8); 26 | copyValues(addressSpace, (cpuCycle - 3) * 8 + 8, (cpuCycle - 4) * 8, 2); 27 | copyValues(addressSpace, (cpuCycle - 4) * 8 + 2, (cpuCycle - 2) * 8 + 2, 6); 28 | } 29 | break; 30 | 31 | case POP_2: 32 | if (cpuCycle >= 5) { 33 | copyValues(addressSpace, (cpuCycle - 5) * 8, (cpuCycle - 2) * 8, 8); 34 | } 35 | break; 36 | 37 | case PUSH_1: 38 | if (cpuCycle >= 4) { 39 | copyValues(addressSpace, (cpuCycle - 4) * 8 + 2, (cpuCycle - 3) * 8 + 2, 8); 40 | copyValues(addressSpace, (cpuCycle - 3) * 8 + 2, (cpuCycle - 1) * 8 + 2, 6); 41 | } 42 | break; 43 | 44 | case PUSH_2: 45 | if (cpuCycle >= 5) { 46 | copyValues(addressSpace, (cpuCycle - 4) * 8 + 2, (cpuCycle - 3) * 8 + 2, 8); 47 | } 48 | break; 49 | 50 | case LD_HL: 51 | if (cpuCycle >= 4) { 52 | copyValues(addressSpace, (cpuCycle - 3) * 8 + 2, (cpuCycle - 4) * 8 + 2, 8); 53 | copyValues(addressSpace, (cpuCycle - 3) * 8 + 8, (cpuCycle - 4) * 8, 2); 54 | copyValues(addressSpace, (cpuCycle - 4) * 8 + 2, (cpuCycle - 2) * 8 + 2, 6); 55 | } 56 | break; 57 | } 58 | } 59 | 60 | private static void copyValues(AddressSpace addressSpace, int from, int to, int length) { 61 | for (int i = length - 1; i >= 0; i--) { 62 | int b = addressSpace.getByte(0xfe00 + from + i) % 0xff; 63 | addressSpace.setByte(0xfe00 + to + i, b); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/TileAttributes.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import java.io.Serializable; 4 | 5 | public class TileAttributes implements Serializable { 6 | 7 | public static final TileAttributes EMPTY; 8 | 9 | private static final TileAttributes[] ATTRIBUTES; 10 | 11 | static { 12 | ATTRIBUTES = new TileAttributes[256]; 13 | for (int i = 0; i < 256; i++) { 14 | ATTRIBUTES[i] = new TileAttributes(i); 15 | } 16 | EMPTY = ATTRIBUTES[0]; 17 | } 18 | 19 | private final int value; 20 | 21 | private TileAttributes(int value) { 22 | this.value = value; 23 | } 24 | 25 | public static TileAttributes valueOf(int value) { 26 | return ATTRIBUTES[value]; 27 | } 28 | 29 | public boolean isPriority() { 30 | return (value & (1 << 7)) != 0; 31 | } 32 | 33 | public boolean isYflip() { 34 | return (value & (1 << 6)) != 0; 35 | } 36 | 37 | public boolean isXflip() { 38 | return (value & (1 << 5)) != 0; 39 | } 40 | 41 | public GpuRegister getDmgPalette() { 42 | return (value & (1 << 4)) == 0 ? GpuRegister.OBP0 : GpuRegister.OBP1; 43 | } 44 | 45 | public int getBank() { 46 | return (value & (1 << 3)) == 0 ? 0 : 1; 47 | } 48 | 49 | public int getColorPaletteIndex() { 50 | return value & 0x07; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/phase/GpuPhase.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu.phase; 2 | 3 | public interface GpuPhase { 4 | 5 | boolean tick(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/phase/HBlankPhase.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu.phase; 2 | 3 | import java.io.Serializable; 4 | 5 | public class HBlankPhase implements GpuPhase, Serializable { 6 | 7 | private int ticks; 8 | 9 | public HBlankPhase start(int ticksInLine) { 10 | this.ticks = ticksInLine; 11 | return this; 12 | } 13 | 14 | @Override 15 | public boolean tick() { 16 | ticks++; 17 | return ticks < 456; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/gpu/phase/VBlankPhase.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu.phase; 2 | 3 | import java.io.Serializable; 4 | 5 | public class VBlankPhase implements GpuPhase, Serializable { 6 | 7 | private int ticks; 8 | 9 | public VBlankPhase start() { 10 | ticks = 0; 11 | return this; 12 | } 13 | 14 | @Override 15 | public boolean tick() { 16 | return ++ticks < 456; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/Dma.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.cpu.SpeedMode; 5 | 6 | import java.io.Serializable; 7 | 8 | public class Dma implements AddressSpace, Serializable { 9 | 10 | private final AddressSpace addressSpace; 11 | 12 | private final AddressSpace oam; 13 | 14 | private final SpeedMode speedMode; 15 | 16 | private boolean transferInProgress; 17 | 18 | private boolean restarted; 19 | 20 | private int from; 21 | 22 | private int ticks; 23 | 24 | private int regValue = 0xff; 25 | 26 | public Dma(AddressSpace addressSpace, AddressSpace oam, SpeedMode speedMode) { 27 | this.addressSpace = new DmaAddressSpace(addressSpace); 28 | this.speedMode = speedMode; 29 | this.oam = oam; 30 | } 31 | 32 | @Override 33 | public boolean accepts(int address) { 34 | return address == 0xff46; 35 | } 36 | 37 | public void tick() { 38 | if (transferInProgress) { 39 | if (++ticks >= 648 / speedMode.getSpeedMode()) { 40 | transferInProgress = false; 41 | restarted = false; 42 | ticks = 0; 43 | for (int i = 0; i < 0xa0; i++) { 44 | oam.setByte(0xfe00 + i, addressSpace.getByte(from + i)); 45 | } 46 | } 47 | } 48 | } 49 | 50 | @Override 51 | public void setByte(int address, int value) { 52 | from = value * 0x100; 53 | restarted = isOamBlocked(); 54 | ticks = 0; 55 | transferInProgress = true; 56 | regValue = value; 57 | } 58 | 59 | @Override 60 | public int getByte(int address) { 61 | return regValue; 62 | } 63 | 64 | public boolean isOamBlocked() { 65 | return restarted || (transferInProgress && ticks >= 5); 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/DmaAddressSpace.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | public class DmaAddressSpace implements AddressSpace, Serializable { 8 | 9 | private final AddressSpace addressSpace; 10 | 11 | public DmaAddressSpace(AddressSpace addressSpace) { 12 | this.addressSpace = addressSpace; 13 | } 14 | 15 | @Override 16 | public boolean accepts(int address) { 17 | return true; 18 | } 19 | 20 | @Override 21 | public void setByte(int address, int value) { 22 | throw new UnsupportedOperationException(); 23 | } 24 | 25 | @Override 26 | public int getByte(int address) { 27 | if (address < 0xe000) { 28 | return addressSpace.getByte(address); 29 | } else { 30 | return addressSpace.getByte(address - 0x2000); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/GbcRam.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | public class GbcRam implements AddressSpace, Serializable { 8 | 9 | private final int[] ram = new int[7 * 0x1000]; 10 | 11 | private int svbk; 12 | 13 | @Override 14 | public boolean accepts(int address) { 15 | return address == 0xff70 || (address >= 0xd000 && address < 0xe000); 16 | } 17 | 18 | @Override 19 | public void setByte(int address, int value) { 20 | if (address == 0xff70) { 21 | this.svbk = value; 22 | } else { 23 | ram[translate(address)] = value; 24 | } 25 | } 26 | 27 | @Override 28 | public int getByte(int address) { 29 | if (address == 0xff70) { 30 | return svbk; 31 | } else { 32 | return ram[translate(address)]; 33 | } 34 | } 35 | 36 | private int translate(int address) { 37 | int ramBank = svbk & 0x7; 38 | if (ramBank == 0) { 39 | ramBank = 1; 40 | } 41 | int result = address - 0xd000 + (ramBank - 1) * 0x1000; 42 | if (result < 0 || result >= ram.length) { 43 | throw new IllegalArgumentException(); 44 | } 45 | return result; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/Mmu.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.Serializable; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import static eu.rekawek.coffeegb.cpu.BitUtils.checkByteArgument; 12 | import static eu.rekawek.coffeegb.cpu.BitUtils.checkWordArgument; 13 | 14 | public class Mmu implements AddressSpace, Serializable { 15 | 16 | private static final Logger LOG = LoggerFactory.getLogger(Mmu.class); 17 | 18 | private static final AddressSpace VOID = new Void(); 19 | 20 | private final List spaces = new ArrayList<>(); 21 | 22 | private AddressSpace[] addressToSpace; 23 | 24 | public void addAddressSpace(AddressSpace space) { 25 | spaces.add(space); 26 | } 27 | 28 | public void indexSpaces() { 29 | addressToSpace = new AddressSpace[0x10000]; 30 | for (int i = 0; i < addressToSpace.length; i++) { 31 | addressToSpace[i] = VOID; 32 | for (AddressSpace s : spaces) { 33 | if (s.accepts(i)) { 34 | addressToSpace[i] = s; 35 | break; 36 | } 37 | } 38 | } 39 | } 40 | 41 | @Override 42 | public boolean accepts(int address) { 43 | return true; 44 | } 45 | 46 | @Override 47 | public void setByte(int address, int value) { 48 | checkByteArgument("value", value); 49 | checkWordArgument("address", address); 50 | getSpace(address).setByte(address, value); 51 | } 52 | 53 | @Override 54 | public int getByte(int address) { 55 | checkWordArgument("address", address); 56 | return getSpace(address).getByte(address); 57 | } 58 | 59 | private AddressSpace getSpace(int address) { 60 | if (addressToSpace == null) { 61 | throw new IllegalStateException("Address spaces hasn't been indexed yet"); 62 | } 63 | return addressToSpace[address]; 64 | } 65 | 66 | private static class Void implements AddressSpace, Serializable { 67 | @Override 68 | public boolean accepts(int address) { 69 | return true; 70 | } 71 | 72 | @Override 73 | public void setByte(int address, int value) { 74 | if (address < 0 || address > 0xffff) { 75 | throw new IllegalArgumentException("Invalid address: " + Integer.toHexString(address)); 76 | } 77 | if (LOG.isDebugEnabled()) { 78 | LOG.debug("Writing value {} to void address {}", Integer.toHexString(value), Integer.toHexString(address)); 79 | } 80 | } 81 | 82 | @Override 83 | public int getByte(int address) { 84 | if (address < 0 || address > 0xffff) { 85 | throw new IllegalArgumentException("Invalid address: " + Integer.toHexString(address)); 86 | } 87 | if (LOG.isDebugEnabled()) { 88 | LOG.debug("Reading value from void address {}", Integer.toHexString(address)); 89 | } 90 | return 0xff; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/Ram.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | public class Ram implements AddressSpace, Serializable { 8 | 9 | private final int[] space; 10 | 11 | private final int length; 12 | 13 | private final int offset; 14 | 15 | public Ram(int offset, int length) { 16 | this.space = new int[length]; 17 | this.length = length; 18 | this.offset = offset; 19 | } 20 | 21 | @Override 22 | public boolean accepts(int address) { 23 | return address >= offset && address < offset + length; 24 | } 25 | 26 | @Override 27 | public void setByte(int address, int value) { 28 | space[address - offset] = value; 29 | } 30 | 31 | @Override 32 | public int getByte(int address) { 33 | int index = address - offset; 34 | if (index < 0 || index >= space.length) { 35 | throw new IndexOutOfBoundsException("Address: " + address); 36 | } 37 | return space[index]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/ShadowAddressSpace.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | public class ShadowAddressSpace implements AddressSpace, Serializable { 8 | 9 | private final AddressSpace addressSpace; 10 | 11 | private final int echoStart; 12 | 13 | private final int targetStart; 14 | 15 | private final int length; 16 | 17 | public ShadowAddressSpace(AddressSpace addressSpace, int echoStart, int targetStart, int length) { 18 | this.addressSpace = addressSpace; 19 | this.echoStart = echoStart; 20 | this.targetStart = targetStart; 21 | this.length = length; 22 | } 23 | 24 | @Override 25 | public boolean accepts(int address) { 26 | return address >= echoStart && address < echoStart + length; 27 | } 28 | 29 | @Override 30 | public void setByte(int address, int value) { 31 | addressSpace.setByte(translate(address), value); 32 | } 33 | 34 | @Override 35 | public int getByte(int address) { 36 | return addressSpace.getByte(translate(address)); 37 | } 38 | 39 | private int translate(int address) { 40 | return getRelative(address) + targetStart; 41 | } 42 | 43 | private int getRelative(int address) { 44 | int i = address - echoStart; 45 | if (i < 0 || i >= length) { 46 | throw new IllegalArgumentException(); 47 | } 48 | return i; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/UndocumentedGbcRegisters.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | public class UndocumentedGbcRegisters implements AddressSpace, Serializable { 8 | 9 | private final Ram ram = new Ram(0xff72, 6); 10 | 11 | private int xff6c; 12 | 13 | public UndocumentedGbcRegisters() { 14 | xff6c = 0xfe; 15 | ram.setByte(0xff74, 0xff); 16 | ram.setByte(0xff75, 0x8f); 17 | } 18 | 19 | @Override 20 | public boolean accepts(int address) { 21 | return address == 0xff6c || ram.accepts(address); 22 | } 23 | 24 | @Override 25 | public void setByte(int address, int value) { 26 | switch (address) { 27 | case 0xff6c: 28 | xff6c = 0xfe | (value & 1); 29 | break; 30 | 31 | case 0xff72: 32 | case 0xff73: 33 | case 0xff74: 34 | ram.setByte(address, value); 35 | break; 36 | 37 | case 0xff75: 38 | ram.setByte(address, 0x8f | (value & 0b01110000)); 39 | } 40 | } 41 | 42 | @Override 43 | public int getByte(int address) { 44 | if (address == 0xff6c) { 45 | return xff6c; 46 | } else if (ram.accepts(address)) { 47 | return ram.getByte(address); 48 | } else { 49 | throw new IllegalArgumentException(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/cart/CartridgeType.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory.cart; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public enum CartridgeType { 6 | 7 | ROM(0x00), 8 | ROM_MBC1(0x01), 9 | ROM_MBC1_RAM(0x02), 10 | ROM_MBC1_RAM_BATTERY(0x03), 11 | ROM_MBC2(0x05), 12 | ROM_MBC2_BATTERY(0x06), 13 | ROM_RAM(0x08), 14 | ROM_RAM_BATTERY(0x09), 15 | ROM_MMM01(0x0b), 16 | ROM_MMM01_SRAM(0x0c), 17 | ROM_MMM01_SRAM_BATTERY(0x0d), 18 | ROM_MBC3_TIMER_BATTERY(0x0f), 19 | ROM_MBC3_TIMER_RAM_BATTERY(0x10), 20 | ROM_MBC3(0x11), 21 | ROM_MBC3_RAM(0x12), 22 | ROM_MBC3_RAM_BATTERY(0x13), 23 | ROM_MBC5(0x19), 24 | ROM_MBC5_RAM(0x1a), 25 | ROM_MBC5_RAM_BATTERY(0x01b), 26 | ROM_MBC5_RUMBLE(0x1c), 27 | ROM_MBC5_RUMBLE_SRAM(0x1d), 28 | ROM_MBC5_RUMBLE_SRAM_BATTERY(0x1e); 29 | 30 | private final int id; 31 | 32 | CartridgeType(int id) { 33 | this.id = id; 34 | } 35 | 36 | public boolean isMbc1() { 37 | return nameContainsSegment("MBC1"); 38 | } 39 | 40 | public boolean isMbc2() { 41 | return nameContainsSegment("MBC2"); 42 | } 43 | 44 | public boolean isMbc3() { 45 | return nameContainsSegment("MBC3"); 46 | } 47 | 48 | public boolean isMbc5() { 49 | return nameContainsSegment("MBC5"); 50 | } 51 | 52 | public boolean isMmm01() { 53 | return nameContainsSegment("MMM01"); 54 | } 55 | 56 | public boolean isRam() { 57 | return nameContainsSegment("RAM"); 58 | } 59 | 60 | public boolean isSram() { 61 | return nameContainsSegment("SRAM"); 62 | } 63 | 64 | public boolean isTimer() { 65 | return nameContainsSegment("TIMER"); 66 | } 67 | 68 | public boolean isBattery() { 69 | return nameContainsSegment("BATTERY"); 70 | } 71 | 72 | public boolean isRumble() { 73 | return nameContainsSegment("RUMBLE"); 74 | } 75 | 76 | private boolean nameContainsSegment(String segment) { 77 | Pattern p = Pattern.compile("(^|_)" + Pattern.quote(segment) + "($|_)"); 78 | return p.matcher(name()).find(); 79 | } 80 | 81 | public static CartridgeType getById(int id) { 82 | for (CartridgeType t : values()) { 83 | if (t.id == id) { 84 | return t; 85 | } 86 | } 87 | throw new IllegalArgumentException("Unsupported cartridge type: " + Integer.toHexString(id)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/cart/MemoryController.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory.cart; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | 5 | import java.io.Serializable; 6 | 7 | public interface MemoryController extends AddressSpace, Serializable { 8 | default void flushRam() {} 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/cart/battery/Battery.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory.cart.battery; 2 | 3 | import java.io.Serializable; 4 | 5 | public interface Battery extends Serializable { 6 | 7 | void loadRam(int[] ram); 8 | 9 | void saveRam(int[] ram); 10 | 11 | void loadRamWithClock(int[] ram, long[] clockData); 12 | 13 | void saveRamWithClock(int[] ram, long[] clockData); 14 | 15 | void flush(); 16 | 17 | Battery NULL_BATTERY = new Battery() { 18 | @Override 19 | public void loadRam(int[] ram) { 20 | } 21 | 22 | @Override 23 | public void saveRam(int[] ram) { 24 | } 25 | 26 | @Override 27 | public void loadRamWithClock(int[] ram, long[] clockData) { 28 | } 29 | 30 | @Override 31 | public void saveRamWithClock(int[] ram, long[] clockData) { 32 | } 33 | 34 | @Override 35 | public void flush() { 36 | } 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/cart/rtc/SystemTimeSource.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory.cart.rtc; 2 | 3 | import java.io.Serializable; 4 | 5 | public class SystemTimeSource implements TimeSource, Serializable { 6 | @Override 7 | public long currentTimeMillis() { 8 | return System.currentTimeMillis(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/cart/rtc/TimeSource.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory.cart.rtc; 2 | 3 | public interface TimeSource { 4 | long currentTimeMillis(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/cart/rtc/VirtualTimeSource.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory.cart.rtc; 2 | 3 | import java.io.Serializable; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | public class VirtualTimeSource implements TimeSource, Serializable { 7 | 8 | private long clock = System.currentTimeMillis(); 9 | 10 | @Override 11 | public long currentTimeMillis() { 12 | return clock; 13 | } 14 | 15 | public void forward(long i, TimeUnit unit) { 16 | clock += unit.toMillis(i); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/cart/type/Mbc2.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory.cart.type; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.memory.cart.MemoryController; 5 | import eu.rekawek.coffeegb.memory.cart.battery.Battery; 6 | 7 | import java.util.Arrays; 8 | 9 | public class Mbc2 implements MemoryController { 10 | 11 | private final int[] cartridge; 12 | 13 | private final int[] ram; 14 | 15 | private final Battery battery; 16 | 17 | private int selectedRomBank = 1; 18 | 19 | private boolean ramWriteEnabled; 20 | 21 | private boolean ramUpdated; 22 | 23 | public Mbc2(int[] cartridge, Battery battery) { 24 | this.cartridge = cartridge; 25 | this.ram = new int[0x0200]; 26 | Arrays.fill(ram, 0xff); 27 | this.battery = battery; 28 | battery.loadRam(ram); 29 | } 30 | 31 | @Override 32 | public boolean accepts(int address) { 33 | return (address >= 0x0000 && address < 0x8000) || 34 | (address >= 0xa000 && address < 0xc000); 35 | } 36 | 37 | @Override 38 | public void setByte(int address, int value) { 39 | if (address >= 0x0000 && address < 0x2000) { 40 | if ((address & 0x0100) == 0) { 41 | ramWriteEnabled = (value & 0b1010) != 0; 42 | } 43 | } else if (address >= 0x2000 && address < 0x4000) { 44 | if ((address & 0x0100) != 0) { 45 | selectedRomBank = value & 0b00001111; 46 | } 47 | } else if (address >= 0xa000 && address < 0xc000 && ramWriteEnabled) { 48 | int ramAddress = getRamAddress(address); 49 | if (ramAddress < ram.length) { 50 | ram[ramAddress] = value & 0x0f; 51 | ramUpdated = true; 52 | } 53 | } 54 | } 55 | 56 | @Override 57 | public int getByte(int address) { 58 | if (address >= 0x0000 && address < 0x4000) { 59 | return getRomByte(0, address); 60 | } else if (address >= 0x4000 && address < 0x8000) { 61 | return getRomByte(selectedRomBank, address - 0x4000); 62 | } else if (address >= 0xa000 && address < 0xb000) { 63 | int ramAddress = getRamAddress(address); 64 | if (ramAddress < ram.length) { 65 | return ram[ramAddress]; 66 | } else { 67 | return 0xff; 68 | } 69 | } else { 70 | return 0xff; 71 | } 72 | } 73 | 74 | @Override 75 | public void flushRam() { 76 | if (ramUpdated) { 77 | battery.saveRam(ram); 78 | battery.flush(); 79 | } 80 | } 81 | 82 | private int getRomByte(int bank, int address) { 83 | int cartOffset = bank * 0x4000 + address; 84 | if (cartOffset < cartridge.length) { 85 | return cartridge[cartOffset]; 86 | } else { 87 | return 0xff; 88 | } 89 | } 90 | 91 | private int getRamAddress(int address) { 92 | return address - 0xa000; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/memory/cart/type/Rom.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory.cart.type; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.memory.cart.CartridgeType; 5 | import eu.rekawek.coffeegb.memory.cart.MemoryController; 6 | 7 | public class Rom implements MemoryController { 8 | 9 | private final int[] rom; 10 | 11 | public Rom(int[] rom, CartridgeType type, int romBanks, int ramBanks) { 12 | this.rom = rom; 13 | } 14 | 15 | @Override 16 | public boolean accepts(int address) { 17 | return (address >= 0x0000 && address < 0x8000) || 18 | (address >= 0xa000 && address < 0xc000); 19 | } 20 | 21 | @Override 22 | public void setByte(int address, int value) { 23 | } 24 | 25 | @Override 26 | public int getByte(int address) { 27 | if (address >= 0x0000 && address < 0x8000) { 28 | return rom[address]; 29 | } else { 30 | return 0; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/serial/ByteReceiver.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.serial; 2 | 3 | public interface ByteReceiver { 4 | void onNewByte(int receivedByte); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/serial/ByteReceivingSerialEndpoint.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.serial; 2 | 3 | public class ByteReceivingSerialEndpoint implements SerialEndpoint { 4 | private final ByteReceiver byteReceiver; 5 | private int sb; 6 | private int bits; 7 | 8 | public ByteReceivingSerialEndpoint(ByteReceiver byteReceiver) { 9 | this.byteReceiver = byteReceiver; 10 | } 11 | 12 | @Override 13 | public void setSb(int sb) { 14 | this.sb = sb; 15 | } 16 | 17 | @Override 18 | public int recvBit() { 19 | return -1; 20 | } 21 | 22 | @Override 23 | public int recvByte() { 24 | return -1; 25 | } 26 | 27 | @Override 28 | public void startSending() { 29 | bits = 0; 30 | } 31 | 32 | @Override 33 | public int sendBit() { 34 | if (++bits == 8) { 35 | byteReceiver.onNewByte(sb); 36 | bits = 0; 37 | } 38 | return 1; 39 | } 40 | 41 | @Override 42 | public int sendByte() { 43 | byteReceiver.onNewByte(sb); 44 | return 0xFF; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/serial/ClockType.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.serial; 2 | 3 | enum ClockType { 4 | INTERNAL, EXTERNAL; 5 | 6 | public static ClockType getFromSc(int sc) { 7 | if ((sc & 1) == 1) { 8 | return INTERNAL; 9 | } else { 10 | return EXTERNAL; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/serial/NaiveSerialPort.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.serial; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.Gameboy; 5 | import eu.rekawek.coffeegb.cpu.InterruptManager; 6 | import eu.rekawek.coffeegb.cpu.SpeedMode; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * Simplified SerialPort implementation in which the bytes are immediately send to the other side, without any bit-by-bit 12 | * handling or the timing. 13 | */ 14 | public class NaiveSerialPort implements AddressSpace, Serializable { 15 | 16 | private transient SerialEndpoint serialEndpoint; 17 | 18 | private final InterruptManager interruptManager; 19 | 20 | private int sb; 21 | 22 | private int sc; 23 | 24 | private boolean transferInProgress; 25 | 26 | private ClockType clockType; 27 | 28 | private final boolean gbc; 29 | 30 | private final SpeedMode speedMode; 31 | 32 | private int speed; 33 | 34 | private int divider; 35 | 36 | public NaiveSerialPort(InterruptManager interruptManager, boolean gbc, SpeedMode speedMode) { 37 | this.interruptManager = interruptManager; 38 | this.speedMode = speedMode; 39 | this.gbc = gbc; 40 | } 41 | 42 | public void init(SerialEndpoint serialEndpoint) { 43 | this.serialEndpoint = serialEndpoint; 44 | } 45 | 46 | public void tick() { 47 | int incomingByte = -1; 48 | // We're receiving bits even without the transfer in progress. 49 | if (clockType == ClockType.EXTERNAL) { 50 | incomingByte = serialEndpoint.recvByte(); 51 | } else if (transferInProgress) { 52 | if (divider++ == 8 * Gameboy.TICKS_PER_SEC / speed) { 53 | incomingByte = serialEndpoint.sendByte(); 54 | transferInProgress = false; 55 | } 56 | } 57 | 58 | if (incomingByte != -1) { 59 | this.sb = incomingByte; 60 | interruptManager.requestInterrupt(InterruptManager.InterruptType.Serial); 61 | } 62 | } 63 | 64 | @Override 65 | public boolean accepts(int address) { 66 | return address == 0xff01 || address == 0xff02; 67 | } 68 | 69 | @Override 70 | public void setByte(int address, int value) { 71 | if (address == 0xff01) { 72 | sb = value; 73 | serialEndpoint.setSb(sb); 74 | } else if (address == 0xff02) { 75 | sc = value; 76 | if ((sc & (1 << 7)) != 0) { 77 | startTransfer(); 78 | } 79 | } 80 | } 81 | 82 | @Override 83 | public int getByte(int address) { 84 | if (address == 0xff01) { 85 | return sb; 86 | } else if (address == 0xff02) { 87 | return sc | 0b01111110; 88 | } else { 89 | throw new IllegalArgumentException(); 90 | } 91 | } 92 | 93 | private void startTransfer() { 94 | transferInProgress = true; 95 | divider = 0; 96 | clockType = ClockType.getFromSc(sc); 97 | if (gbc && (sc & (1 << 1)) != 0) { 98 | speed = 262144; 99 | } else { 100 | speed = 8192; 101 | } 102 | speed *= speedMode.getSpeedMode(); 103 | serialEndpoint.startSending(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/serial/SerialEndpoint.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.serial; 2 | 3 | public interface SerialEndpoint { 4 | /** 5 | * Listener waiting for any updates of the SB byte, so it can be shared with the other side. 6 | */ 7 | void setSb(int sb); 8 | 9 | /** 10 | * Returns the bit transferred from the active side or -1 if no bit has been received. 11 | */ 12 | int recvBit(); 13 | 14 | /** 15 | * Returns the received byte. 16 | */ 17 | default int recvByte() { 18 | throw new UnsupportedOperationException(); 19 | } 20 | 21 | /** 22 | * Starts byte transfer, should reset the index of bit to send. 23 | */ 24 | void startSending(); 25 | 26 | /** 27 | * Sends following SB bit. Returns the received bit. 28 | */ 29 | int sendBit(); 30 | 31 | /** 32 | * Sends the SB bit and returns the received byte. 33 | */ 34 | default int sendByte() { 35 | throw new UnsupportedOperationException(); 36 | } 37 | 38 | SerialEndpoint NULL_ENDPOINT = new SerialEndpoint() { 39 | @Override 40 | public void setSb(int sb) { 41 | } 42 | 43 | @Override 44 | public int recvBit() { 45 | return -1; 46 | } 47 | 48 | @Override 49 | public void startSending() { 50 | } 51 | 52 | @Override 53 | public int sendBit() { 54 | return 1; 55 | } 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/serial/SerialPort.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.serial; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.Gameboy; 5 | import eu.rekawek.coffeegb.cpu.InterruptManager; 6 | import eu.rekawek.coffeegb.cpu.SpeedMode; 7 | 8 | import java.io.Serializable; 9 | 10 | public class SerialPort implements AddressSpace, Serializable { 11 | 12 | private transient SerialEndpoint serialEndpoint; 13 | 14 | private final InterruptManager interruptManager; 15 | 16 | private final boolean gbc; 17 | 18 | private final SpeedMode speedMode; 19 | 20 | private int sb; 21 | 22 | private int sc; 23 | 24 | private boolean transferInProgress; 25 | 26 | private int divider; 27 | 28 | private ClockType clockType; 29 | 30 | private int speed; 31 | 32 | private int receivedBits; 33 | 34 | public SerialPort(InterruptManager interruptManager, boolean gbc, SpeedMode speedMode) { 35 | this.interruptManager = interruptManager; 36 | this.gbc = gbc; 37 | this.speedMode = speedMode; 38 | } 39 | 40 | public void init(SerialEndpoint serialEndpoint) { 41 | this.serialEndpoint = serialEndpoint; 42 | } 43 | 44 | public void tick() { 45 | int incomingBit = -1; 46 | // We're receiving bits even without the transfer in progress. 47 | if (clockType == ClockType.EXTERNAL) { 48 | incomingBit = serialEndpoint.recvBit(); 49 | } else if (transferInProgress) { 50 | if (divider++ == Gameboy.TICKS_PER_SEC / speed) { 51 | divider = 0; 52 | incomingBit = serialEndpoint.sendBit(); 53 | } 54 | } 55 | 56 | if (incomingBit != -1) { 57 | sb = (sb << 1) & 0xff | (incomingBit & 1); 58 | receivedBits++; 59 | if (receivedBits == 8) { 60 | interruptManager.requestInterrupt(InterruptManager.InterruptType.Serial); 61 | transferInProgress = false; 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | public boolean accepts(int address) { 68 | return address == 0xff01 || address == 0xff02; 69 | } 70 | 71 | @Override 72 | public void setByte(int address, int value) { 73 | if (address == 0xff01) { 74 | sb = value; 75 | serialEndpoint.setSb(sb); 76 | } else if (address == 0xff02) { 77 | sc = value; 78 | if ((sc & (1 << 7)) != 0) { 79 | startTransfer(); 80 | } 81 | } 82 | } 83 | 84 | @Override 85 | public int getByte(int address) { 86 | if (address == 0xff01) { 87 | return sb; 88 | } else if (address == 0xff02) { 89 | return sc | 0b01111110; 90 | } else { 91 | throw new IllegalArgumentException(); 92 | } 93 | } 94 | 95 | private void startTransfer() { 96 | transferInProgress = true; 97 | divider = 0; 98 | clockType = ClockType.getFromSc(sc); 99 | receivedBits = 0; 100 | if (gbc && (sc & (1 << 1)) != 0) { 101 | speed = 262144; 102 | } else { 103 | speed = 8192; 104 | } 105 | speed *= speedMode.getSpeedMode(); 106 | serialEndpoint.startSending(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/sound/FrequencySweep.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | import eu.rekawek.coffeegb.Gameboy; 4 | 5 | import java.io.Serializable; 6 | 7 | public class FrequencySweep implements Serializable { 8 | 9 | private static final int DIVIDER = Gameboy.TICKS_PER_SEC / 128; 10 | 11 | // sweep parameters 12 | private int period; 13 | 14 | private boolean negate; 15 | 16 | private int shift; 17 | 18 | // current process variables 19 | private int timer; 20 | 21 | private int shadowFreq; 22 | 23 | private int nr13, nr14; 24 | 25 | private int i; 26 | 27 | private boolean overflow; 28 | 29 | private boolean counterEnabled; 30 | 31 | private boolean negging; 32 | 33 | public void start() { 34 | counterEnabled = false; 35 | i = 8192; 36 | } 37 | 38 | public void trigger() { 39 | this.negging = false; 40 | this.overflow = false; 41 | 42 | this.shadowFreq = nr13 | ((nr14 & 0b111) << 8); 43 | this.timer = period == 0 ? 8 : period; 44 | this.counterEnabled = period != 0 || shift != 0; 45 | 46 | if (shift > 0) { 47 | calculate(); 48 | } 49 | } 50 | 51 | public void setNr10(int value) { 52 | this.period = (value >> 4) & 0b111; 53 | this.negate = (value & (1 << 3)) != 0; 54 | this.shift = value & 0b111; 55 | if (negging && !negate) { 56 | overflow = true; 57 | } 58 | } 59 | 60 | public void setNr13(int value) { 61 | this.nr13 = value; 62 | } 63 | 64 | public void setNr14(int value) { 65 | this.nr14 = value; 66 | if ((value & (1 << 7)) != 0) { 67 | trigger(); 68 | } 69 | } 70 | 71 | public int getNr13() { 72 | return nr13; 73 | } 74 | 75 | public int getNr14() { 76 | return nr14; 77 | } 78 | 79 | public void tick() { 80 | if (++i == DIVIDER) { 81 | i = 0; 82 | if (!counterEnabled) { 83 | return; 84 | } 85 | if (--timer == 0) { 86 | timer = period == 0 ? 8 : period; 87 | if (period != 0) { 88 | int newFreq = calculate(); 89 | if (!overflow && shift != 0) { 90 | shadowFreq = newFreq; 91 | nr13 = shadowFreq & 0xff; 92 | nr14 = (shadowFreq & 0x700) >> 8; 93 | calculate(); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | private int calculate() { 101 | int freq = shadowFreq >> shift; 102 | if (negate) { 103 | freq = shadowFreq - freq; 104 | negging = true; 105 | } else { 106 | freq = shadowFreq + freq; 107 | } 108 | if (freq > 2047) { 109 | overflow = true; 110 | } 111 | return freq; 112 | } 113 | 114 | public boolean isEnabled() { 115 | return !overflow; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/sound/LengthCounter.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | import java.io.Serializable; 4 | 5 | import static eu.rekawek.coffeegb.Gameboy.TICKS_PER_SEC; 6 | 7 | public class LengthCounter implements Serializable { 8 | 9 | private final int DIVIDER = TICKS_PER_SEC / 256; 10 | 11 | private final int fullLength; 12 | 13 | private int length; 14 | 15 | private long i; 16 | 17 | private boolean enabled; 18 | 19 | public LengthCounter(int fullLength) { 20 | this.fullLength = fullLength; 21 | } 22 | 23 | public void start() { 24 | i = 8192; 25 | } 26 | 27 | public void tick() { 28 | if (++i == DIVIDER) { 29 | i = 0; 30 | if (enabled && length > 0) { 31 | length--; 32 | } 33 | } 34 | } 35 | 36 | public void setLength(int length) { 37 | if (length == 0) { 38 | this.length = fullLength; 39 | } else { 40 | this.length = length; 41 | } 42 | } 43 | 44 | public void setNr4(int value) { 45 | boolean enable = (value & (1 << 6)) != 0; 46 | boolean trigger = (value & (1 << 7)) != 0; 47 | 48 | if (enabled) { 49 | if (length == 0 && trigger) { 50 | if (enable && i < DIVIDER / 2) { 51 | setLength(fullLength - 1); 52 | } else { 53 | setLength(fullLength); 54 | } 55 | } 56 | } else if (enable) { 57 | if (length > 0 && i < DIVIDER / 2) { 58 | length--; 59 | } 60 | if (length == 0 && trigger&& i < DIVIDER / 2) { 61 | setLength(fullLength - 1); 62 | } 63 | } else { 64 | if (length == 0 && trigger) { 65 | setLength(fullLength); 66 | } 67 | } 68 | this.enabled = enable; 69 | } 70 | 71 | public int getValue() { 72 | return length; 73 | } 74 | 75 | public boolean isEnabled() { 76 | return enabled; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return String.format("LengthCounter[l=%d,f=%d,c=%d,%s]", length, fullLength, i, enabled ? "enabled" : "disabled"); 82 | } 83 | 84 | void reset() { 85 | this.enabled = true; 86 | this.i = 0; 87 | this.length = 0; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/sound/Lfsr.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Lfsr implements Serializable { 6 | 7 | private int lfsr; 8 | 9 | public Lfsr() { 10 | reset(); 11 | } 12 | 13 | public void start() { 14 | reset(); 15 | } 16 | 17 | public void reset() { 18 | lfsr = 0x7fff; 19 | } 20 | 21 | public int nextBit(boolean widthMode7) { 22 | boolean x = ((lfsr & 1) ^ ((lfsr & 2) >> 1)) != 0; 23 | lfsr = lfsr >> 1; 24 | lfsr = lfsr | (x ? (1 << 14) : 0); 25 | if (widthMode7) { 26 | lfsr = lfsr | (x ? (1 << 6) : 0); 27 | } 28 | return 1 & ~lfsr; 29 | } 30 | 31 | int getValue() { 32 | return lfsr; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/sound/PolynomialCounter.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | import java.io.Serializable; 4 | 5 | public class PolynomialCounter implements Serializable { 6 | 7 | private int shiftedDivisor; 8 | 9 | private int i; 10 | 11 | public void setNr43(int value) { 12 | int clockShift = value >> 4; 13 | int divisor; 14 | switch (value & 0b111) { 15 | case 0: 16 | divisor = 8; 17 | break; 18 | 19 | case 1: 20 | divisor = 16; 21 | break; 22 | 23 | case 2: 24 | divisor = 32; 25 | break; 26 | 27 | case 3: 28 | divisor = 48; 29 | break; 30 | 31 | case 4: 32 | divisor = 64; 33 | break; 34 | 35 | case 5: 36 | divisor = 80; 37 | break; 38 | 39 | case 6: 40 | divisor = 96; 41 | break; 42 | 43 | case 7: 44 | divisor = 112; 45 | break; 46 | 47 | default: 48 | throw new IllegalStateException(); 49 | } 50 | shiftedDivisor = divisor << clockShift; 51 | i = 1; 52 | } 53 | 54 | public boolean tick() { 55 | if (--i == 0) { 56 | i = shiftedDivisor; 57 | return true; 58 | } else { 59 | return false; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/sound/SoundMode2.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | public class SoundMode2 extends AbstractSoundMode { 4 | 5 | private int freqDivider; 6 | 7 | private int lastOutput; 8 | 9 | private int i; 10 | 11 | private final VolumeEnvelope volumeEnvelope; 12 | 13 | public SoundMode2(boolean gbc) { 14 | super(0xff15, 64, gbc); 15 | this.volumeEnvelope = new VolumeEnvelope(); 16 | } 17 | 18 | @Override 19 | public void start() { 20 | i = 0; 21 | if (gbc) { 22 | length.reset(); 23 | } 24 | length.start(); 25 | volumeEnvelope.start(); 26 | } 27 | 28 | @Override 29 | public void trigger() { 30 | this.i = 0; 31 | freqDivider = 1; 32 | volumeEnvelope.trigger(); 33 | } 34 | 35 | @Override 36 | public int tick() { 37 | volumeEnvelope.tick(); 38 | 39 | boolean e; 40 | e = updateLength(); 41 | e = dacEnabled && e; 42 | if (!e) { 43 | return 0; 44 | } 45 | 46 | if (--freqDivider == 0) { 47 | resetFreqDivider(); 48 | lastOutput = ((getDuty() & (1 << i)) >> i); 49 | i = (i + 1) % 8; 50 | } 51 | return lastOutput * volumeEnvelope.getVolume(); 52 | } 53 | 54 | @Override 55 | protected void setNr0(int value) { 56 | super.setNr0(value); 57 | } 58 | 59 | @Override 60 | protected void setNr1(int value) { 61 | super.setNr1(value); 62 | length.setLength(64 - (value & 0b00111111)); 63 | } 64 | 65 | @Override 66 | protected void setNr2(int value) { 67 | super.setNr2(value); 68 | volumeEnvelope.setNr2(value); 69 | dacEnabled = (value & 0b11111000) != 0; 70 | channelEnabled &= dacEnabled; 71 | } 72 | 73 | private int getDuty() { 74 | switch (getNr1() >> 6) { 75 | case 0: 76 | return 0b00000001; 77 | case 1: 78 | return 0b10000001; 79 | case 2: 80 | return 0b10000111; 81 | case 3: 82 | return 0b01111110; 83 | default: 84 | throw new IllegalStateException(); 85 | } 86 | } 87 | 88 | private void resetFreqDivider() { 89 | freqDivider = getFrequency() * 4; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/sound/SoundMode4.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | public class SoundMode4 extends AbstractSoundMode { 4 | 5 | private final VolumeEnvelope volumeEnvelope; 6 | 7 | private final PolynomialCounter polynomialCounter; 8 | 9 | private int lastResult; 10 | 11 | private final Lfsr lfsr = new Lfsr(); 12 | 13 | public SoundMode4(boolean gbc) { 14 | super(0xff1f, 64, gbc); 15 | this.volumeEnvelope = new VolumeEnvelope(); 16 | this.polynomialCounter = new PolynomialCounter(); 17 | } 18 | 19 | @Override 20 | public void start() { 21 | if (gbc) { 22 | length.reset(); 23 | } 24 | length.start(); 25 | lfsr.start(); 26 | volumeEnvelope.start(); 27 | } 28 | 29 | @Override 30 | public void trigger() { 31 | lfsr.reset(); 32 | volumeEnvelope.trigger(); 33 | } 34 | 35 | @Override 36 | public int tick() { 37 | volumeEnvelope.tick(); 38 | 39 | if (!updateLength()) { 40 | return 0; 41 | } 42 | if (!dacEnabled) { 43 | return 0; 44 | } 45 | 46 | if (polynomialCounter.tick()) { 47 | lastResult = lfsr.nextBit((nr3 & (1 << 3)) != 0); 48 | } 49 | return lastResult * volumeEnvelope.getVolume(); 50 | } 51 | 52 | @Override 53 | protected void setNr1(int value) { 54 | super.setNr1(value); 55 | length.setLength(64 - (value & 0b00111111)); 56 | } 57 | 58 | @Override 59 | protected void setNr2(int value) { 60 | super.setNr2(value); 61 | volumeEnvelope.setNr2(value); 62 | dacEnabled = (value & 0b11111000) != 0; 63 | channelEnabled &= dacEnabled; 64 | } 65 | 66 | @Override 67 | protected void setNr3(int value) { 68 | super.setNr3(value); 69 | polynomialCounter.setNr43(value); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/sound/SoundOutput.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | public interface SoundOutput { 4 | 5 | void start(); 6 | 7 | void stop(); 8 | 9 | void play(int left, int right); 10 | 11 | SoundOutput NULL_OUTPUT = new SoundOutput() { 12 | @Override 13 | public void start() { 14 | } 15 | 16 | @Override 17 | public void stop() { 18 | } 19 | 20 | @Override 21 | public void play(int left, int right) { 22 | } 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/sound/VolumeEnvelope.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | import eu.rekawek.coffeegb.Gameboy; 4 | 5 | import java.io.Serializable; 6 | 7 | public class VolumeEnvelope implements Serializable { 8 | 9 | private int initialVolume; 10 | 11 | private int envelopeDirection; 12 | 13 | private int sweep; 14 | 15 | private int volume; 16 | 17 | private int i; 18 | 19 | private boolean finished; 20 | 21 | public void setNr2(int register) { 22 | this.initialVolume = register >> 4; 23 | this.envelopeDirection = (register & (1 << 3)) == 0 ? -1 : 1; 24 | this.sweep = register & 0b111; 25 | } 26 | 27 | public boolean isEnabled() { 28 | return sweep > 0; 29 | } 30 | 31 | public void start() { 32 | finished = true; 33 | i = 8192; 34 | } 35 | 36 | public void trigger() { 37 | volume = initialVolume; 38 | i = 0; 39 | finished = false; 40 | } 41 | 42 | public void tick() { 43 | if (finished) { 44 | return; 45 | } 46 | if ((volume == 0 && envelopeDirection == -1) || (volume == 15 && envelopeDirection == 1)) { 47 | finished = true; 48 | return; 49 | } 50 | if (++i == sweep * Gameboy.TICKS_PER_SEC / 64) { 51 | i = 0; 52 | volume += envelopeDirection; 53 | } 54 | } 55 | 56 | public int getVolume() { 57 | if (isEnabled()) { 58 | return volume; 59 | } else { 60 | return initialVolume; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/emulator/DisplayController.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.emulator 2 | 3 | import eu.rekawek.coffeegb.swing.io.SwingDisplay 4 | 5 | class DisplayController(private val display: SwingDisplay) { 6 | var scale: Int 7 | get() = display.scale 8 | set(value) { 9 | display.scale = value 10 | } 11 | var grayscale: Boolean 12 | get() = display.isGrayscale 13 | set(value) { 14 | display.isGrayscale = value 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/emulator/EmulatorStateListener.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.emulator 2 | 3 | interface EmulatorStateListener { 4 | fun onEmulationStart(cartTitle: String) {} 5 | fun onEmulationStop() {} 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/emulator/SerialController.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.emulator 2 | 3 | import eu.rekawek.coffeegb.swing.io.serial.* 4 | 5 | class SerialController(private val serialEndpointWrapper: SerialEndpointWrapper) { 6 | 7 | private var client: SerialTcpClient? = null 8 | private var server: SerialTcpServer? = null 9 | private val serverListeners = mutableListOf() 10 | private val clientListeners = mutableListOf() 11 | 12 | fun startServer() { 13 | stop() 14 | server = SerialTcpServer(serialEndpointWrapper) 15 | serverListeners.forEach { server!!.registerListener(it) } 16 | Thread(server).start() 17 | } 18 | 19 | fun startClient(host: String) { 20 | stop() 21 | client = SerialTcpClient(host, serialEndpointWrapper) 22 | clientListeners.forEach { client!!.registerListener(it) } 23 | Thread(client).start() 24 | } 25 | 26 | fun stop() { 27 | client?.stop() 28 | client = null 29 | 30 | server?.stop() 31 | server = null 32 | } 33 | 34 | fun registerServerListener(listener: ServerEventListener) { 35 | serverListeners.add(listener) 36 | server?.registerListener(listener) 37 | } 38 | 39 | fun registerClientListener(listener: ClientEventListener) { 40 | clientListeners.add(listener) 41 | client?.registerListener(listener) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/emulator/SoundController.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.emulator 2 | 3 | import eu.rekawek.coffeegb.swing.io.AudioSystemSoundOutput 4 | 5 | class SoundController(private val sound: AudioSystemSoundOutput) { 6 | var enabled: Boolean 7 | get() = sound.isEnabled 8 | set(value) { 9 | sound.isEnabled = value 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/emulator/TimingTicker.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.emulator 2 | 3 | import eu.rekawek.coffeegb.Gameboy 4 | import kotlin.concurrent.Volatile 5 | 6 | class TimingTicker : Runnable { 7 | private var lastSleep = System.nanoTime() 8 | private var ticks: Long = 0 9 | 10 | @Volatile 11 | private var delayEnabled = true 12 | 13 | override fun run() { 14 | if (++ticks < TICKS_PER_PERIOD) { 15 | return 16 | } 17 | ticks = 0 18 | if (delayEnabled) { 19 | while (System.nanoTime() - lastSleep < PERIOD_IN_NANOS) { 20 | } 21 | } 22 | lastSleep = System.nanoTime() 23 | } 24 | 25 | fun setDelayEnabled(delayEnabled: Boolean) { 26 | this.delayEnabled = delayEnabled 27 | } 28 | 29 | private companion object { 30 | const val PERIODS_PER_SECOND: Long = 65536 31 | const val TICKS_PER_PERIOD = Gameboy.TICKS_PER_SEC / PERIODS_PER_SECOND 32 | const val PERIOD_IN_NANOS = 1000000000 / PERIODS_PER_SECOND 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/gui/SwingGui.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.gui 2 | 3 | import eu.rekawek.coffeegb.debug.Console 4 | import eu.rekawek.coffeegb.memory.cart.Cartridge.GameboyType 5 | import eu.rekawek.coffeegb.swing.emulator.EmulatorStateListener 6 | import eu.rekawek.coffeegb.swing.emulator.SwingEmulator 7 | import eu.rekawek.coffeegb.swing.gui.properties.EmulatorProperties 8 | import eu.rekawek.coffeegb.swing.gui.properties.EmulatorProperties.Key 9 | import java.awt.event.WindowAdapter 10 | import java.awt.event.WindowEvent 11 | import java.io.File 12 | import javax.swing.JFrame 13 | import javax.swing.SwingUtilities 14 | 15 | 16 | class SwingGui private constructor(debug: Boolean, private val initialRom: File?) { 17 | 18 | private val emulator: SwingEmulator 19 | 20 | private val console: Console? = if (debug) Console() else null 21 | 22 | private val properties: EmulatorProperties = EmulatorProperties() 23 | 24 | private lateinit var mainWindow: JFrame 25 | 26 | init { 27 | emulator = SwingEmulator(console, properties.controllerMapping) 28 | 29 | emulator.displayController.scale = properties.getProperty(Key.DisplayScale, "2").toInt() 30 | emulator.displayController.grayscale = properties.getProperty(Key.DisplayGrayscale, "false").toBoolean() 31 | emulator.soundController.enabled = properties.getProperty(Key.SoundEnabled, "true").toBoolean() 32 | emulator.gameboyType = GameboyType.valueOf(properties.getProperty(Key.GameboyType, GameboyType.AUTOMATIC.name)) 33 | } 34 | 35 | private fun startGui() { 36 | mainWindow = JFrame("Coffee GB") 37 | 38 | SwingMenu(emulator, properties, mainWindow).addMenu() 39 | emulator.addEmulatorStateListener(object : EmulatorStateListener { 40 | override fun onEmulationStart(cartTitle: String) { 41 | mainWindow.title = "Coffee GB: $cartTitle" 42 | } 43 | 44 | override fun onEmulationStop() { 45 | mainWindow.title = "Coffee GB" 46 | } 47 | }) 48 | 49 | mainWindow.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE 50 | mainWindow.addWindowListener(object : WindowAdapter() { 51 | override fun windowClosed(windowEvent: WindowEvent) { 52 | stopGui() 53 | } 54 | }) 55 | 56 | emulator.bind(mainWindow) 57 | mainWindow.pack() 58 | mainWindow.repaint() 59 | mainWindow.setLocationRelativeTo(null) 60 | mainWindow.isResizable = false 61 | mainWindow.isVisible = true 62 | if (console != null) { 63 | Thread(console).start() 64 | } 65 | if (initialRom != null) { 66 | emulator.startEmulation(initialRom) 67 | } 68 | } 69 | 70 | private fun stopGui() { 71 | emulator.stopEmulation() 72 | emulator.serialController.stop() 73 | console?.stop() 74 | System.exit(0) 75 | } 76 | 77 | companion object { 78 | fun run(debug: Boolean, initialRom: File?) { 79 | System.setProperty("apple.awt.application.name", "Coffee GB") 80 | System.setProperty("sun.java2d.opengl", "true") 81 | SwingUtilities.invokeLater { SwingGui(debug, initialRom).startGui() } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/gui/properties/ControllerProperties.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.gui.properties 2 | 3 | import eu.rekawek.coffeegb.controller.ButtonListener 4 | import java.awt.event.KeyEvent 5 | import java.util.* 6 | 7 | object ControllerProperties { 8 | fun getControllerMapping(properties: Properties): Map { 9 | val buttonToKey = EnumMap(ButtonListener.Button::class.java) 10 | 11 | buttonToKey[ButtonListener.Button.LEFT] = KeyEvent.VK_LEFT 12 | buttonToKey[ButtonListener.Button.RIGHT] = KeyEvent.VK_RIGHT 13 | buttonToKey[ButtonListener.Button.UP] = KeyEvent.VK_UP 14 | buttonToKey[ButtonListener.Button.DOWN] = KeyEvent.VK_DOWN 15 | buttonToKey[ButtonListener.Button.A] = KeyEvent.VK_Z 16 | buttonToKey[ButtonListener.Button.B] = KeyEvent.VK_X 17 | buttonToKey[ButtonListener.Button.START] = KeyEvent.VK_ENTER 18 | buttonToKey[ButtonListener.Button.SELECT] = KeyEvent.VK_BACK_SPACE 19 | 20 | for (k in properties.stringPropertyNames()) { 21 | val v = properties.getProperty(k) 22 | if (k.startsWith("btn_") && v.startsWith("VK_")) { 23 | val button = ButtonListener.Button.valueOf(k.substring(4).uppercase(Locale.getDefault())) 24 | val field = KeyEvent::class.java.getField(properties.getProperty(k)) 25 | if (field.type != Int::class.javaPrimitiveType) { 26 | continue 27 | } 28 | val value = field.getInt(null) 29 | buttonToKey[button] = value 30 | } 31 | } 32 | 33 | return buttonToKey.entries.associate { (k, v) -> v to k } 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/gui/properties/EmulatorProperties.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.gui.properties 2 | 3 | import eu.rekawek.coffeegb.controller.ButtonListener 4 | import org.slf4j.Logger 5 | import org.slf4j.LoggerFactory 6 | import java.awt.event.KeyEvent 7 | import java.io.File 8 | import java.io.FileReader 9 | import java.io.FileWriter 10 | import java.io.IOException 11 | import java.util.* 12 | import java.util.stream.Collectors 13 | 14 | class EmulatorProperties() { 15 | 16 | internal val properties = loadProperties() 17 | 18 | val recentRoms = RecentRoms(this) 19 | 20 | val controllerMapping = ControllerProperties.getControllerMapping(properties) 21 | 22 | fun getProperty(key: Key, defaultValue: String? = null) = properties.getProperty(key.propertyName, defaultValue) 23 | 24 | fun setProperty(key: Key, value: String) { 25 | properties[key.propertyName] = value 26 | saveProperties() 27 | } 28 | 29 | internal fun saveProperties() { 30 | try { 31 | FileWriter(PROPERTIES_FILE).use { writer -> 32 | properties.store(writer, "") 33 | } 34 | } catch (e: IOException) { 35 | LOG.error("Can't store properties", e) 36 | } 37 | } 38 | 39 | enum class Key(val propertyName: String) { 40 | GameboyType("gameboy.type"), 41 | DisplayScale("display.scale"), 42 | DisplayGrayscale("display.grayscale"), 43 | SoundEnabled("sound.enabled"), 44 | RomDirectory("rom.directory"), 45 | } 46 | 47 | private companion object { 48 | val LOG: Logger = LoggerFactory.getLogger(EmulatorProperties::class.java) 49 | val PROPERTIES_FILE = File(File(System.getProperty("user.home")), ".coffeegb.properties") 50 | fun loadProperties(): Properties { 51 | val props = Properties() 52 | if (PROPERTIES_FILE.exists()) { 53 | FileReader(PROPERTIES_FILE).use { reader -> 54 | props.load(reader) 55 | } 56 | } 57 | return props 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/gui/properties/RecentRoms.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.gui.properties 2 | 3 | import java.util.* 4 | import java.util.stream.Collectors 5 | 6 | class RecentRoms(private val emulatorProperties: EmulatorProperties) { 7 | private val roms = LinkedList() 8 | 9 | private val properties = emulatorProperties.properties 10 | 11 | init { 12 | for (i in 0 until MAX_ROMS) { 13 | val key = ROM_KEY_PREFIX + i 14 | if (properties.containsKey(key)) { 15 | roms.add(properties.getProperty(key)) 16 | } 17 | } 18 | } 19 | 20 | fun getRoms(): List { 21 | return roms 22 | } 23 | 24 | fun addRom(rom: String) { 25 | roms.remove(rom) 26 | roms.addFirst(rom) 27 | while (roms.size > MAX_ROMS) { 28 | roms.removeLast() 29 | } 30 | cleanProperties() 31 | setProperties() 32 | emulatorProperties.saveProperties() 33 | } 34 | 35 | private fun cleanProperties() { 36 | val keys = properties.keys.stream().map { o: Any -> o as String }.filter { k: String -> k.startsWith(ROM_KEY_PREFIX) }.collect(Collectors.toList()) 37 | for (k in keys) { 38 | properties.remove(k) 39 | } 40 | } 41 | 42 | private fun setProperties() { 43 | for (i in roms.indices) { 44 | properties.setProperty(ROM_KEY_PREFIX + i, roms[i]) 45 | } 46 | } 47 | 48 | private companion object { 49 | const val ROM_KEY_PREFIX = "rom.recent." 50 | const val MAX_ROMS = 10 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/io/SwingController.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.io; 2 | 3 | import eu.rekawek.coffeegb.controller.ButtonListener; 4 | import eu.rekawek.coffeegb.controller.ButtonListener.Button; 5 | import eu.rekawek.coffeegb.controller.Controller; 6 | 7 | import java.awt.event.KeyEvent; 8 | import java.awt.event.KeyListener; 9 | import java.util.Map; 10 | 11 | public class SwingController implements Controller, KeyListener { 12 | 13 | private ButtonListener listener; 14 | 15 | private final Map mapping; 16 | 17 | public SwingController(Map mapping) { 18 | this.mapping = mapping; 19 | } 20 | 21 | @Override 22 | public void setButtonListener(ButtonListener listener) { 23 | this.listener = listener; 24 | } 25 | 26 | @Override 27 | public void keyTyped(KeyEvent e) { 28 | } 29 | 30 | @Override 31 | public void keyPressed(KeyEvent e) { 32 | if (listener == null) { 33 | return; 34 | } 35 | Button b = getButton(e); 36 | if (b != null) { 37 | listener.onButtonPress(b); 38 | } 39 | } 40 | 41 | @Override 42 | public void keyReleased(KeyEvent e) { 43 | if (listener == null) { 44 | return; 45 | } 46 | Button b = getButton(e); 47 | if (b != null) { 48 | listener.onButtonRelease(b); 49 | } 50 | } 51 | 52 | private Button getButton(KeyEvent e) { 53 | return mapping.get(e.getKeyCode()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/io/serial/ClientEventListener.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.io.serial 2 | 3 | interface ClientEventListener { 4 | fun onConnectedToServer() {} 5 | fun onDisconnectedFromServer() {} 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/io/serial/SerialEndpointWrapper.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.io.serial 2 | 3 | import eu.rekawek.coffeegb.serial.SerialEndpoint 4 | 5 | class SerialEndpointWrapper : SerialEndpoint { 6 | private var sb = 0 7 | 8 | private var delegate: SerialEndpoint? = null 9 | 10 | fun setDelegate(delegate: SerialEndpoint?) { 11 | this.delegate = delegate 12 | setSb(sb) 13 | } 14 | 15 | override fun setSb(sb: Int) { 16 | this.sb = sb 17 | if (delegate != null) { 18 | delegate!!.setSb(sb) 19 | } 20 | } 21 | 22 | override fun recvBit(): Int { 23 | if (delegate != null) { 24 | return delegate!!.recvBit() 25 | } 26 | return -1 27 | } 28 | 29 | override fun recvByte(): Int { 30 | if (delegate != null) { 31 | return delegate!!.recvByte() 32 | } 33 | return -1 34 | } 35 | 36 | override fun startSending() { 37 | if (delegate != null) { 38 | delegate!!.startSending() 39 | } 40 | } 41 | 42 | override fun sendBit(): Int { 43 | if (delegate != null) { 44 | return delegate!!.sendBit() 45 | } 46 | return 1 47 | } 48 | 49 | override fun sendByte(): Int { 50 | if (delegate != null) { 51 | return delegate!!.sendByte() 52 | } 53 | return 0xFF; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/io/serial/SerialTcpClient.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.io.serial 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | import java.io.IOException 6 | import java.net.Socket 7 | import java.util.concurrent.CopyOnWriteArrayList 8 | 9 | class SerialTcpClient(private val host: String, private val serialEndpointWrapper: SerialEndpointWrapper) : Runnable { 10 | private var clientSocket: Socket? = null 11 | private var endpoint: StreamSerialEndpoint? = null 12 | private val listeners = CopyOnWriteArrayList() 13 | 14 | override fun run() { 15 | try { 16 | clientSocket = Socket(host, SerialTcpServer.PORT) 17 | LOG.info("Connected to {}", clientSocket!!.inetAddress) 18 | listeners.forEach { it.onConnectedToServer() } 19 | 20 | endpoint = StreamSerialEndpoint( 21 | clientSocket!!.getInputStream(), 22 | clientSocket!!.getOutputStream() 23 | ) 24 | serialEndpointWrapper.setDelegate(endpoint) 25 | endpoint!!.run() 26 | } catch (e: IOException) { 27 | LOG.error("Error in making connection", e) 28 | } 29 | listeners.forEach { it.onDisconnectedFromServer() } 30 | } 31 | 32 | fun stop() { 33 | serialEndpointWrapper.setDelegate(null) 34 | if (endpoint != null) { 35 | endpoint!!.stop() 36 | } 37 | try { 38 | if (clientSocket != null) { 39 | clientSocket!!.close() 40 | } 41 | } catch (e: IOException) { 42 | LOG.error("Error in closing client socket", e) 43 | } 44 | } 45 | 46 | fun registerListener(listener: ClientEventListener) { 47 | listeners.add(listener) 48 | } 49 | 50 | companion object { 51 | private val LOG: Logger = LoggerFactory.getLogger(SerialTcpServer::class.java) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/io/serial/SerialTcpServer.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.io.serial 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | import java.io.IOException 6 | import java.net.ServerSocket 7 | import java.net.Socket 8 | import java.net.SocketTimeoutException 9 | import java.util.concurrent.CopyOnWriteArrayList 10 | import kotlin.concurrent.Volatile 11 | 12 | class SerialTcpServer(private val serialEndpointWrapper: SerialEndpointWrapper) : Runnable { 13 | @Volatile 14 | private var doStop = false 15 | private var endpoint: StreamSerialEndpoint? = null 16 | private val listeners = CopyOnWriteArrayList() 17 | 18 | override fun run() { 19 | doStop = false 20 | ServerSocket(PORT).use { serverSocket -> 21 | serverSocket.soTimeout = 100 22 | listeners.forEach { it.onServerStarted() } 23 | while (!doStop) { 24 | var socket: Socket 25 | try { 26 | socket = serverSocket.accept() 27 | LOG.info("Got new connection: {}", socket.inetAddress) 28 | endpoint = StreamSerialEndpoint( 29 | socket.getInputStream(), 30 | socket.getOutputStream() 31 | ) 32 | serialEndpointWrapper.setDelegate(endpoint) 33 | listeners.forEach { it.onNewConnection(socket.inetAddress.hostName) } 34 | endpoint!!.run() 35 | listeners.forEach { it.onConnectionClosed() } 36 | } catch (e: SocketTimeoutException) { 37 | // do nothing 38 | } catch (e: IOException) { 39 | LOG.error("Error in accepting connection", e) 40 | } 41 | } 42 | } 43 | listeners.forEach { it.onServerStopped() } 44 | } 45 | 46 | fun stop() { 47 | serialEndpointWrapper.setDelegate(null) 48 | doStop = true 49 | if (endpoint != null) { 50 | endpoint!!.stop() 51 | } 52 | } 53 | 54 | fun registerListener(listener: ServerEventListener) { 55 | listeners.add(listener) 56 | } 57 | 58 | companion object { 59 | private val LOG: Logger = LoggerFactory.getLogger(SerialTcpServer::class.java) 60 | const val PORT: Int = 6688 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/swing/io/serial/ServerEventListener.kt: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.swing.io.serial 2 | 3 | interface ServerEventListener { 4 | fun onServerStarted() {} 5 | fun onServerStopped() {} 6 | fun onNewConnection(host: String?) {} 7 | fun onConnectionClosed() {} 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/eu/rekawek/coffeegb/timer/Timer.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.timer; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.cpu.InterruptManager; 5 | import eu.rekawek.coffeegb.cpu.SpeedMode; 6 | 7 | import java.io.Serializable; 8 | 9 | public class Timer implements AddressSpace, Serializable { 10 | 11 | private final SpeedMode speedMode; 12 | 13 | private final InterruptManager interruptManager; 14 | 15 | private static final int[] FREQ_TO_BIT = {9, 3, 5, 7}; 16 | 17 | private int div, tac, tma, tima; 18 | 19 | private boolean previousBit; 20 | 21 | private boolean overflow; 22 | 23 | private int ticksSinceOverflow; 24 | 25 | public Timer(InterruptManager interruptManager, SpeedMode speedMode) { 26 | this.speedMode = speedMode; 27 | this.interruptManager = interruptManager; 28 | } 29 | 30 | public void tick() { 31 | updateDiv((div + 1) & 0xffff); 32 | if (overflow) { 33 | ticksSinceOverflow++; 34 | if (ticksSinceOverflow == 4) { 35 | interruptManager.requestInterrupt(InterruptManager.InterruptType.Timer); 36 | } 37 | if (ticksSinceOverflow == 5) { 38 | tima = tma; 39 | } 40 | if (ticksSinceOverflow == 6) { 41 | tima = tma; 42 | overflow = false; 43 | ticksSinceOverflow = 0; 44 | } 45 | } 46 | } 47 | 48 | private void incTima() { 49 | tima++; 50 | tima %= 0x100; 51 | if (tima == 0) { 52 | overflow = true; 53 | ticksSinceOverflow = 0; 54 | } 55 | } 56 | 57 | private void updateDiv(int newDiv) { 58 | this.div = newDiv; 59 | int bitPos = FREQ_TO_BIT[tac & 0b11]; 60 | bitPos <<= speedMode.getSpeedMode() - 1; 61 | boolean bit = (div & (1 << bitPos)) != 0; 62 | bit &= (tac & (1 << 2)) != 0; 63 | if (!bit && previousBit) { 64 | incTima(); 65 | } 66 | previousBit = bit; 67 | } 68 | 69 | @Override 70 | public boolean accepts(int address) { 71 | return address >= 0xff04 && address <= 0xff07; 72 | } 73 | 74 | @Override 75 | public void setByte(int address, int value) { 76 | switch (address) { 77 | case 0xff04: 78 | updateDiv(0); 79 | break; 80 | 81 | case 0xff05: 82 | if (ticksSinceOverflow < 5) { 83 | tima = value; 84 | overflow = false; 85 | ticksSinceOverflow = 0; 86 | } 87 | break; 88 | 89 | case 0xff06: 90 | tma = value; 91 | break; 92 | 93 | case 0xff07: 94 | tac = value; 95 | break; 96 | } 97 | } 98 | 99 | @Override 100 | public int getByte(int address) { 101 | switch (address) { 102 | case 0xff04: 103 | return div >> 8; 104 | 105 | case 0xff05: 106 | return tima; 107 | 108 | case 0xff06: 109 | return tma; 110 | 111 | case 0xff07: 112 | return tac | 0b11111000; 113 | } 114 | throw new IllegalArgumentException(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/gpu/ColorPaletteTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.fail; 7 | 8 | public class ColorPaletteTest { 9 | 10 | @Test 11 | public void testAutoIncrement() { 12 | ColorPalette p = new ColorPalette(0xff68); 13 | p.setByte(0xff68, 0x80); 14 | p.setByte(0xff69, 0x00); 15 | p.setByte(0xff69, 0xaa); 16 | p.setByte(0xff69, 0x11); 17 | p.setByte(0xff69, 0xbb); 18 | p.setByte(0xff69, 0x22); 19 | p.setByte(0xff69, 0xcc); 20 | p.setByte(0xff69, 0x33); 21 | p.setByte(0xff69, 0xdd); 22 | p.setByte(0xff69, 0x44); 23 | p.setByte(0xff69, 0xee); 24 | p.setByte(0xff69, 0x55); 25 | p.setByte(0xff69, 0xff); 26 | assertArrayEquals(new int[] {0xaa00, 0xbb11, 0xcc22, 0xdd33}, p.getPalette(0)); 27 | assertArrayEquals(new int[] {0xee44, 0xff55, 0x0000, 0x0000}, p.getPalette(1)); 28 | } 29 | 30 | private static void assertArrayEquals(int[] expected, int[] actual) { 31 | assertEquals(expected.length, actual.length); 32 | for (int i = 0; i < expected.length; i++) { 33 | if (expected[i] != actual[i]) { 34 | StringBuilder msg = new StringBuilder(); 35 | msg.append("arrays first differed at element [").append(i).append("]\n"); 36 | msg.append("Expected :").append(String.format("%04x", expected[i])).append("\n"); 37 | msg.append("Actual :").append(String.format("%04x", actual[i])); 38 | fail(msg.toString()); 39 | } 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/gpu/PixelFifoTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.gpu; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static java.util.Arrays.asList; 10 | import static org.junit.Assert.assertArrayEquals; 11 | import static org.junit.Assert.assertEquals; 12 | 13 | public class PixelFifoTest { 14 | 15 | private DmgPixelFifo fifo; 16 | 17 | @Before 18 | public void createFifo() { 19 | GpuRegisterValues r = new GpuRegisterValues(); 20 | r.put(GpuRegister.BGP, 0b11100100); 21 | fifo = new DmgPixelFifo(r); 22 | } 23 | 24 | @Test 25 | public void testEnqueue() { 26 | fifo.enqueue8Pixels(zip(0b11001001, 0b11110000, false), TileAttributes.EMPTY); 27 | assertEquals(asList(3, 3, 2, 2, 1, 0, 0, 1), arrayQueueAsList(fifo.getPixels())); 28 | } 29 | 30 | @Test 31 | public void testDequeue() { 32 | fifo.enqueue8Pixels(zip(0b11001001, 0b11110000, false), TileAttributes.EMPTY); 33 | fifo.enqueue8Pixels(zip(0b10101011, 0b11100111, false), TileAttributes.EMPTY); 34 | assertEquals(0b11, fifo.dequeuePixel()); 35 | assertEquals(0b11, fifo.dequeuePixel()); 36 | assertEquals(0b10, fifo.dequeuePixel()); 37 | assertEquals(0b10, fifo.dequeuePixel()); 38 | assertEquals(0b01, fifo.dequeuePixel()); 39 | } 40 | 41 | @Test 42 | public void testZip() { 43 | assertArrayEquals(new int[] {3, 3, 2, 2, 1, 0, 0, 1}, zip(0b11001001, 0b11110000, false)); 44 | assertArrayEquals(new int[] {1, 0, 0, 1, 2, 2, 3, 3}, zip(0b11001001, 0b11110000, true)); 45 | } 46 | 47 | private int[] zip(int data1, int data2, boolean reverse) { 48 | return Fetcher.zip(data1, data2, reverse, new int[8]); 49 | } 50 | 51 | private static List arrayQueueAsList(IntQueue queue) { 52 | List l = new ArrayList<>(queue.size()); 53 | for (int i = 0; i < queue.size(); i++) { 54 | l.add(queue.get(i)); 55 | } 56 | return l; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/blargg/BlarggRomTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.blargg; 2 | 3 | import org.junit.Ignore; 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | 10 | import static eu.rekawek.coffeegb.integration.support.RomTestUtils.testRomWithMemory; 11 | import static eu.rekawek.coffeegb.integration.support.RomTestUtils.testRomWithSerial; 12 | 13 | public class BlarggRomTest { 14 | 15 | @Test 16 | public void testCgbSound() throws IOException { 17 | testRomWithMemory(getPath("cgb_sound.gb")); 18 | } 19 | 20 | @Test 21 | public void testCpuInstrs() throws IOException { 22 | testRomWithSerial(getPath("cpu_instrs.gb")); 23 | } 24 | 25 | @Test 26 | public void testDmgSound2() throws IOException { 27 | testRomWithMemory(getPath("dmg_sound-2.gb")); 28 | } 29 | 30 | @Test 31 | public void testHaltBug() throws IOException { 32 | testRomWithMemory(getPath("halt_bug.gb")); 33 | } 34 | 35 | @Test 36 | public void testInstrTiming() throws IOException { 37 | testRomWithSerial(getPath("instr_timing.gb")); 38 | } 39 | 40 | @Test 41 | @Ignore 42 | public void testInterruptTime() throws IOException { 43 | testRomWithMemory(getPath("interrupt_time.gb")); 44 | } 45 | 46 | @Test 47 | public void testMemTiming2() throws IOException { 48 | testRomWithMemory(getPath("mem_timing-2.gb")); 49 | } 50 | 51 | @Test 52 | public void testOamBug2() throws IOException { 53 | testRomWithMemory(getPath("oam_bug-2.gb")); 54 | } 55 | 56 | private static Path getPath(String name) { 57 | return Paths.get("src/test/resources/roms/blargg", name); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/blargg/individual/CgbSoundTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.blargg.individual; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class CgbSoundTest { 15 | 16 | private final Path rom; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("blargg/cgb_sound"); 21 | } 22 | 23 | public CgbSoundTest(String name, Path rom) { 24 | this.rom = rom; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testRomWithMemory(rom); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/blargg/individual/CpuInstrsTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.blargg.individual; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class CpuInstrsTest { 15 | 16 | private final Path rom; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("blargg/cpu_instrs"); 21 | } 22 | 23 | public CpuInstrsTest(String name, Path rom) { 24 | this.rom = rom; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testRomWithSerial(rom); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/blargg/individual/DmgSound2Test.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.blargg.individual; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class DmgSound2Test { 15 | 16 | private final Path rom; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("blargg/dmg_sound-2"); 21 | } 22 | 23 | public DmgSound2Test(String name, Path rom) { 24 | this.rom = rom; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testRomWithMemory(rom); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/blargg/individual/MemTiming2Test.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.blargg.individual; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class MemTiming2Test { 15 | 16 | private final Path rom; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("blargg/mem_timing-2"); 21 | } 22 | 23 | public MemTiming2Test(String name, Path rom) { 24 | this.rom = rom; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testRomWithMemory(rom); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/blargg/individual/OamBug2Test.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.blargg.individual; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class OamBug2Test { 15 | 16 | private final Path rom; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("blargg/oam_bug-2"); 21 | } 22 | 23 | public OamBug2Test(String name, Path rom) { 24 | this.rom = rom; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testRomWithMemory(rom); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/dmgacid2/DmgAcid2RomTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.dmgacid2; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | 9 | import static eu.rekawek.coffeegb.integration.support.RomTestUtils.*; 10 | 11 | public class DmgAcid2RomTest { 12 | 13 | @Test 14 | public void testDmgAcid2() throws Exception { 15 | testRomWithImage(getPath("dmg-acid2.gb")); 16 | } 17 | 18 | private static Path getPath(String name) { 19 | return Paths.get("src/test/resources/roms/dmg-acid2", name); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/BitsTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class BitsTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/acceptance/bits"); 21 | } 22 | 23 | public BitsTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test(timeout = 5000) 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/EmulatorOnlyTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class EmulatorOnlyTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/emulator-only", Integer.MAX_VALUE); 21 | } 22 | 23 | public EmulatorOnlyTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/GeneralTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class GeneralTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/acceptance"); 21 | } 22 | 23 | public GeneralTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test(timeout = 5000) 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/InstrTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class InstrTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/acceptance/instr"); 21 | } 22 | 23 | public InstrTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/InterruptsTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class InterruptsTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/acceptance/interrupts"); 21 | } 22 | 23 | public InterruptsTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/MiscTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class MiscTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/misc"); 21 | } 22 | 23 | public MiscTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test(timeout = 5000) 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/OamDmaTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class OamDmaTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/acceptance/oam_dma"); 21 | } 22 | 23 | public OamDmaTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/PpuTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class PpuTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/acceptance/ppu"); 21 | } 22 | 23 | public PpuTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/SerialTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class SerialTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/acceptance/serial"); 21 | } 22 | 23 | public SerialTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/mooneye/TimerTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.mooneye; 2 | 3 | import eu.rekawek.coffeegb.integration.support.ParametersProvider; 4 | import eu.rekawek.coffeegb.integration.support.RomTestUtils; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | 13 | @RunWith(Parameterized.class) 14 | public class TimerTest { 15 | 16 | private final Path romPath; 17 | 18 | @Parameterized.Parameters(name = "{0}") 19 | public static Collection data() throws IOException { 20 | return ParametersProvider.getParameters("mooneye/acceptance/timer"); 21 | } 22 | 23 | public TimerTest(String name, Path romPath) { 24 | this.romPath = romPath; 25 | } 26 | 27 | @Test 28 | public void test() throws IOException { 29 | RomTestUtils.testMooneyeRom(romPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/support/MemoryTestRunner.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.support; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.Gameboy; 5 | import eu.rekawek.coffeegb.controller.Controller; 6 | import eu.rekawek.coffeegb.cpu.Cpu; 7 | import eu.rekawek.coffeegb.cpu.Registers; 8 | import eu.rekawek.coffeegb.gpu.Display; 9 | import eu.rekawek.coffeegb.memory.cart.Cartridge; 10 | import eu.rekawek.coffeegb.serial.SerialEndpoint; 11 | import eu.rekawek.coffeegb.sound.SoundOutput; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.io.OutputStream; 16 | 17 | public class MemoryTestRunner { 18 | 19 | private final Gameboy gb; 20 | 21 | private final StringBuilder text; 22 | 23 | private final OutputStream os; 24 | 25 | private boolean testStarted; 26 | 27 | public MemoryTestRunner(File romFile, OutputStream os) throws IOException { 28 | Cartridge cart = new Cartridge(romFile); 29 | gb = new Gameboy(cart); 30 | gb.init(Display.NULL_DISPLAY, SoundOutput.NULL_OUTPUT, Controller.NULL_CONTROLLER, SerialEndpoint.NULL_ENDPOINT, null); 31 | text = new StringBuilder(); 32 | this.os = os; 33 | } 34 | 35 | public TestResult runTest() throws IOException { 36 | int status = 0x80; 37 | int divider = 0; 38 | while (status == 0x80 && !SerialTestRunner.isInfiniteLoop(gb)) { 39 | gb.tick(); 40 | if (++divider >= (gb.getSpeedMode().getSpeedMode() == 2 ? 1 : 4)) { 41 | status = getTestResult(gb); 42 | divider = 0; 43 | } 44 | } 45 | return new TestResult(status, text.toString()); 46 | } 47 | 48 | private int getTestResult(Gameboy gb) throws IOException { 49 | AddressSpace mem = gb.getAddressSpace(); 50 | if (!testStarted) { 51 | int i = 0xa000; 52 | for (int v : new int[]{0x80, 0xde, 0xb0, 0x61}) { 53 | if (mem.getByte(i++) != v) { 54 | return 0x80; 55 | } 56 | } 57 | testStarted = true; 58 | } 59 | int status = mem.getByte(0xa000); 60 | 61 | if (gb.getCpu().getState() != Cpu.State.OPCODE) { 62 | return status; 63 | } 64 | 65 | Registers reg = gb.getCpu().getRegisters(); 66 | int i = reg.getPC(); 67 | for (int v : new int[]{0xe5, 0xf5, 0xfa, 0x83, 0xd8}) { 68 | if (mem.getByte(i++) != v) { 69 | return status; 70 | } 71 | } 72 | char c = (char) reg.getA(); 73 | text.append(c); 74 | if (os != null) { 75 | os.write(c); 76 | } 77 | reg.setPC(reg.getPC() + 0x19); 78 | return status; 79 | } 80 | 81 | public static class TestResult { 82 | 83 | private final int status; 84 | 85 | private final String text; 86 | 87 | public TestResult(int status, String text) { 88 | this.status = status; 89 | this.text = text; 90 | } 91 | 92 | public int getStatus() { 93 | return status; 94 | } 95 | 96 | public String getText() { 97 | return text; 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/support/MooneyeTestRunner.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.support; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.Gameboy; 5 | import eu.rekawek.coffeegb.controller.Controller; 6 | import eu.rekawek.coffeegb.cpu.Cpu; 7 | import eu.rekawek.coffeegb.cpu.Registers; 8 | import eu.rekawek.coffeegb.gpu.Display; 9 | import eu.rekawek.coffeegb.memory.cart.Cartridge; 10 | import eu.rekawek.coffeegb.serial.SerialEndpoint; 11 | import eu.rekawek.coffeegb.sound.SoundOutput; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.io.OutputStream; 16 | 17 | import static eu.rekawek.coffeegb.integration.support.RomTestUtils.isByteSequenceAtPc; 18 | 19 | public class MooneyeTestRunner { 20 | 21 | private final Gameboy gb; 22 | 23 | private final Cpu cpu; 24 | 25 | private final AddressSpace mem; 26 | 27 | private final Registers regs; 28 | 29 | private final OutputStream os; 30 | 31 | public MooneyeTestRunner(File romFile, OutputStream os) throws IOException { 32 | Cartridge.GameboyType type = Cartridge.GameboyType.AUTOMATIC; 33 | boolean useBootstrap = false; 34 | if (romFile.toString().endsWith("-C.gb") || romFile.toString().endsWith("-cgb.gb")) { 35 | type = Cartridge.GameboyType.FORCE_CGB; 36 | } 37 | if (romFile.getName().startsWith("boot_")) { 38 | useBootstrap = true; 39 | } 40 | Cartridge cart = new Cartridge(romFile, false, type, useBootstrap); 41 | gb = new Gameboy(cart); 42 | gb.init(Display.NULL_DISPLAY, SoundOutput.NULL_OUTPUT, Controller.NULL_CONTROLLER, SerialEndpoint.NULL_ENDPOINT, null); 43 | System.out.println("System type: " + (cart.isGbc() ? "CGB" : "DMG")); 44 | System.out.println("Bootstrap: " + (cart.isUseBootstrap() ? "enabled" : "disabled")); 45 | cpu = gb.getCpu(); 46 | regs = cpu.getRegisters(); 47 | mem = gb.getAddressSpace(); 48 | this.os = os; 49 | } 50 | 51 | public boolean runTest() throws IOException { 52 | int divider = 0; 53 | while (!isByteSequenceAtPc(gb, 0x00, 0x18, 0xfd)) { // infinite loop 54 | gb.tick(); 55 | if (++divider >= (gb.getSpeedMode().getSpeedMode() == 2 ? 1 : 4)) { 56 | displayProgress(); 57 | divider = 0; 58 | } 59 | } 60 | return regs.getA() == 0 && regs.getB() == 3 && regs.getC() == 5 && regs.getD() == 8 && regs.getE() == 13 && regs.getH() == 21 && regs.getL() == 34; 61 | } 62 | 63 | private void displayProgress() throws IOException { 64 | if (cpu.getState() == Cpu.State.OPCODE && mem.getByte(regs.getPC()) == 0x22 && regs.getHL() >= 0x9800 && regs.getHL() < 0x9c00) { 65 | if (regs.getA() != 0) { 66 | os.write(regs.getA()); 67 | } 68 | } else if (isByteSequenceAtPc(gb, 0x7d, 0xe6, 0x1f, 0xee, 0x1f)) { 69 | os.write('\n'); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/support/ParametersProvider.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.support; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | public final class ParametersProvider { 14 | 15 | static final List EXCLUDES = Arrays.asList( 16 | "-mgb.gb", 17 | "-sgb.gb", 18 | "-sgb2.gb", 19 | "-S.gb", 20 | "-A.gb" 21 | ); 22 | 23 | private ParametersProvider() { 24 | } 25 | 26 | public static Collection getParameters(String dirName) throws IOException { 27 | return getParameters(dirName, EXCLUDES, 1); 28 | } 29 | 30 | public static Collection getParameters(String dirName, int maxDepth) throws IOException { 31 | return getParameters(dirName, EXCLUDES, maxDepth); 32 | } 33 | 34 | public static Collection getParameters(String dirName, List excludes, int maxDepth) throws IOException { 35 | Path dir = Paths.get("src/test/resources/roms", dirName); 36 | return Files.walk(dir, maxDepth) 37 | .filter(Files::isRegularFile) 38 | .filter(f -> f.toString().endsWith(".gb")) 39 | .filter(f -> !excludes.stream().anyMatch(p -> f.toString().endsWith(p))) 40 | .map(p -> new Object[] { dir.relativize(p).toString(), p }) 41 | .collect(Collectors.toList()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/support/RomTestUtils.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.support; 2 | 3 | import eu.rekawek.coffeegb.Gameboy; 4 | import eu.rekawek.coffeegb.cpu.Cpu; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Path; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | public final class RomTestUtils { 13 | 14 | private RomTestUtils() { 15 | } 16 | 17 | public static void testRomWithMemory(Path romPath) throws IOException { 18 | System.out.println("\n### Running test rom " + romPath.getFileName() + " ###"); 19 | MemoryTestRunner runner = new MemoryTestRunner(romPath.toFile(), System.out); 20 | MemoryTestRunner.TestResult result = runner.runTest(); 21 | assertEquals("Non-zero return value", 0, result.getStatus()); 22 | } 23 | 24 | public static void testRomWithSerial(Path romPath) throws IOException { 25 | System.out.println("\n### Running test rom " + romPath.getFileName() + " ###"); 26 | SerialTestRunner runner = new SerialTestRunner(romPath.toFile(), System.out); 27 | String result = runner.runTest(); 28 | assertTrue(result.contains("Passed")); 29 | } 30 | 31 | public static void testRomWithImage(Path romPath) throws Exception { 32 | System.out.println("\n### Running test rom " + romPath.getFileName() + " ###"); 33 | ImageTestRunner runner = new ImageTestRunner(romPath.toFile()); 34 | ImageTestRunner.TestResult result = runner.runTest(); 35 | 36 | File resultFile = File.createTempFile(romPath.getFileName().toString(), "-result.png"); 37 | result.writeResultToFile(resultFile); 38 | assertArrayEquals("The result image is different from expected: " + resultFile, result.getExpectedRGB(), result.getResultRGB()); 39 | resultFile.delete(); 40 | } 41 | 42 | public static void testMooneyeRom(Path romPath) throws IOException { 43 | System.out.println("\n### Running test rom " + romPath.getFileName() + " ###"); 44 | MooneyeTestRunner runner = new MooneyeTestRunner(romPath.toFile(), System.out); 45 | assertTrue(runner.runTest()); 46 | } 47 | 48 | static boolean isByteSequenceAtPc(Gameboy gameboy, int... seq) { 49 | if (gameboy.getCpu().getState() != Cpu.State.OPCODE) { 50 | return false; 51 | } 52 | 53 | int i = gameboy.getCpu().getRegisters().getPC(); 54 | boolean found = true; 55 | for (int v : seq) { 56 | if (gameboy.getAddressSpace().getByte(i++) != v) { 57 | found = false; 58 | break; 59 | } 60 | } 61 | return found; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/integration/support/SerialTestRunner.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.integration.support; 2 | 3 | import eu.rekawek.coffeegb.AddressSpace; 4 | import eu.rekawek.coffeegb.Gameboy; 5 | import eu.rekawek.coffeegb.controller.Controller; 6 | import eu.rekawek.coffeegb.cpu.Cpu; 7 | import eu.rekawek.coffeegb.cpu.Registers; 8 | import eu.rekawek.coffeegb.gpu.Display; 9 | import eu.rekawek.coffeegb.memory.cart.Cartridge; 10 | import eu.rekawek.coffeegb.serial.ByteReceiver; 11 | import eu.rekawek.coffeegb.serial.ByteReceivingSerialEndpoint; 12 | import eu.rekawek.coffeegb.sound.SoundOutput; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.io.OutputStream; 17 | 18 | import static eu.rekawek.coffeegb.cpu.BitUtils.getLSB; 19 | import static eu.rekawek.coffeegb.cpu.BitUtils.getMSB; 20 | 21 | public class SerialTestRunner implements ByteReceiver { 22 | 23 | private final Gameboy gb; 24 | 25 | private final StringBuilder text; 26 | 27 | private final OutputStream os; 28 | 29 | public SerialTestRunner(File romFile, OutputStream os) throws IOException { 30 | Cartridge cart = new Cartridge(romFile); 31 | gb = new Gameboy(cart); 32 | gb.init(Display.NULL_DISPLAY, SoundOutput.NULL_OUTPUT, Controller.NULL_CONTROLLER, new ByteReceivingSerialEndpoint(this), null); 33 | text = new StringBuilder(); 34 | this.os = os; 35 | } 36 | 37 | public String runTest() { 38 | int divider = 0; 39 | while (true) { 40 | gb.tick(); 41 | if (++divider == 4) { 42 | if (isInfiniteLoop(gb)) { 43 | break; 44 | } 45 | divider = 0; 46 | } 47 | } 48 | return text.toString(); 49 | } 50 | 51 | @Override 52 | public void onNewByte(int b) { 53 | text.append((char) b); 54 | try { 55 | os.write(b); 56 | os.flush(); 57 | } catch (IOException e) { 58 | throw new RuntimeException(e); 59 | } 60 | } 61 | 62 | static boolean isInfiniteLoop(Gameboy gb) { 63 | Cpu cpu = gb.getCpu(); 64 | if (cpu.getState() != Cpu.State.OPCODE) { 65 | return false; 66 | } 67 | Registers regs = cpu.getRegisters(); 68 | AddressSpace mem = gb.getAddressSpace(); 69 | 70 | int i = regs.getPC(); 71 | boolean found = true; 72 | for (int v : new int[]{0x18, 0xfe}) { // jr fe 73 | if (mem.getByte(i++) != v) { 74 | found = false; 75 | break; 76 | } 77 | } 78 | if (found) { 79 | return true; 80 | } 81 | 82 | i = regs.getPC(); 83 | for (int v : new int[]{0xc3, getLSB(i), getMSB(i)}) { // jp pc 84 | if (mem.getByte(i++) != v) { 85 | return false; 86 | } 87 | } 88 | return true; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/memory/cart/rtc/RealTimeTimeSourceTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.memory.cart.rtc; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertFalse; 10 | import static org.junit.Assert.assertTrue; 11 | 12 | public class RealTimeTimeSourceTest { 13 | 14 | private RealTimeClock rtc; 15 | 16 | private VirtualTimeSource clock; 17 | 18 | @Before 19 | public void setup() { 20 | clock = new VirtualTimeSource(); 21 | rtc = new RealTimeClock(clock); 22 | } 23 | 24 | @Test 25 | public void testBasicGet() { 26 | forward(5, 8, 12, 2); 27 | assertClockEquals(5, 8, 12, 2); 28 | } 29 | 30 | @Test 31 | public void testLatch() { 32 | forward(5, 8, 12, 2); 33 | 34 | rtc.latch(); 35 | forward(10, 5, 19, 4); 36 | assertClockEquals(5, 8, 12, 2); 37 | rtc.unlatch(); 38 | 39 | assertClockEquals(5 + 10, 8 + 5, 12 + 19, 2 + 4); 40 | } 41 | 42 | @Test 43 | public void testCounterOverflow() { 44 | forward(511, 23, 59, 59); 45 | assertFalse(rtc.isCounterOverflow()); 46 | 47 | clock.forward(1, TimeUnit.SECONDS); 48 | assertClockEquals(0, 0, 0 ,0); 49 | assertTrue(rtc.isCounterOverflow()); 50 | 51 | forward(10, 5, 19, 4); 52 | assertClockEquals(10, 5, 19, 4); 53 | assertTrue(rtc.isCounterOverflow()); 54 | 55 | rtc.clearCounterOverflow(); 56 | assertClockEquals(10, 5, 19, 4); 57 | assertFalse(rtc.isCounterOverflow()); 58 | } 59 | 60 | @Test 61 | public void setClock() { 62 | forward(10, 5, 19, 4); 63 | assertClockEquals(10, 5, 19, 4); 64 | 65 | rtc.setHalt(true); 66 | assertTrue(rtc.isHalt()); 67 | 68 | rtc.setDayCounter(10); 69 | rtc.setHours(16); 70 | rtc.setMinutes(21); 71 | rtc.setSeconds(32); 72 | forward(1, 1, 1, 1); // should be ignored after unhalt 73 | rtc.setHalt(false); 74 | 75 | assertFalse(rtc.isHalt()); 76 | assertClockEquals(10, 16, 21, 32); 77 | forward(2, 2, 2, 2); 78 | assertClockEquals(12, 18, 23, 34); 79 | } 80 | 81 | private void forward(int days, int hours, int minutes, int seconds) { 82 | clock.forward(days, TimeUnit.DAYS); 83 | clock.forward(hours, TimeUnit.HOURS); 84 | clock.forward(minutes, TimeUnit.MINUTES); 85 | clock.forward(seconds, TimeUnit.SECONDS); 86 | } 87 | 88 | private void assertClockEquals(int days, int hours, int minutes, int seconds) { 89 | assertEquals("days", days, rtc.getDayCounter()); 90 | assertEquals("hours", hours, rtc.getHours()); 91 | assertEquals("minutes", minutes, rtc.getMinutes()); 92 | assertEquals("seconds", seconds, rtc.getSeconds()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/sound/AbstractLengthCounterTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | import eu.rekawek.coffeegb.Gameboy; 4 | 5 | public abstract class AbstractLengthCounterTest { 6 | 7 | protected final int maxlen; 8 | 9 | protected final LengthCounter lengthCounter; 10 | 11 | public AbstractLengthCounterTest() { 12 | this(256); 13 | } 14 | 15 | public AbstractLengthCounterTest(int maxlen) { 16 | this.maxlen = maxlen; 17 | this.lengthCounter = new LengthCounter(maxlen); 18 | } 19 | 20 | protected void wchn(int register, int value) { 21 | if (register == 1) { 22 | lengthCounter.setLength(0 - value); 23 | } else if (register == 4) { 24 | lengthCounter.setNr4(value); 25 | } else { 26 | throw new IllegalArgumentException(); 27 | } 28 | } 29 | 30 | protected void delayClocks(int clocks) { 31 | for (int i = 0; i < clocks; i++) { 32 | lengthCounter.tick(); 33 | } 34 | } 35 | 36 | protected void delayApu(int apuUnit) { 37 | delayClocks(apuUnit * (Gameboy.TICKS_PER_SEC / 256)); 38 | } 39 | 40 | protected void syncApu() { 41 | lengthCounter.reset(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/eu/rekawek/coffeegb/sound/LfsrTest.java: -------------------------------------------------------------------------------- 1 | package eu.rekawek.coffeegb.sound; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class LfsrTest { 7 | 8 | @Test 9 | public void testLfsr() { 10 | Lfsr lfsr = new Lfsr(); 11 | int previousValue = 0; 12 | for (int i = 0; i < 100; i++) { 13 | lfsr.nextBit(false); 14 | Assert.assertNotEquals(previousValue, lfsr.getValue()); 15 | previousValue = lfsr.getValue(); 16 | } 17 | } 18 | 19 | @Test 20 | public void testLfsrWidth7() { 21 | Lfsr lfsr = new Lfsr(); 22 | int previousValue = 0; 23 | for (int i = 0; i < 100; i++) { 24 | lfsr.nextBit(true); 25 | Assert.assertNotEquals(previousValue, lfsr.getValue()); 26 | previousValue = lfsr.getValue(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/01-registers.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/01-registers.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/02-len ctr.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/02-len ctr.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/03-trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/03-trigger.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/04-sweep.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/04-sweep.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/05-sweep details.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/05-sweep details.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/06-overflow on trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/06-overflow on trigger.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/07-len sweep period sync.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/07-len sweep period sync.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/08-len ctr during power.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/08-len ctr during power.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/09-wave read while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/09-wave read while on.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/10-wave trigger while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/10-wave trigger while on.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/11-regs after power.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/11-regs after power.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cgb_sound/12-wave.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cgb_sound/12-wave.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/01-special.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/01-special.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/02-interrupts.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/02-interrupts.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/03-op sp,hl.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/03-op sp,hl.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/04-op r,imm.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/04-op r,imm.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/05-op rp.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/05-op rp.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/06-ld r,r.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/06-ld r,r.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/07-jr,jp,call,ret,rst.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/07-jr,jp,call,ret,rst.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/08-misc instrs.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/08-misc instrs.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/09-op r,r.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/09-op r,r.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/10-bit ops.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/10-bit ops.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/cpu_instrs/11-op a,(hl).gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/cpu_instrs/11-op a,(hl).gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/01-registers.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/01-registers.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/02-len ctr.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/02-len ctr.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/03-trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/03-trigger.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/04-sweep.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/04-sweep.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/05-sweep details.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/05-sweep details.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/06-overflow on trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/06-overflow on trigger.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/07-len sweep period sync.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/07-len sweep period sync.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/08-len ctr during power.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/08-len ctr during power.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/09-wave read while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/09-wave read while on.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/10-wave trigger while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/10-wave trigger while on.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/11-regs after power.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/11-regs after power.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/dmg_sound-2/12-wave write while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/dmg_sound-2/12-wave write while on.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/halt_bug.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/halt_bug.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/instr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/instr_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/interrupt_time.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/interrupt_time.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/mem_timing-2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/mem_timing-2.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/mem_timing-2/01-read_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/mem_timing-2/01-read_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/mem_timing-2/02-write_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/mem_timing-2/02-write_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/mem_timing-2/03-modify_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/mem_timing-2/03-modify_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/oam_bug-2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/oam_bug-2.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/oam_bug-2/1-lcd_sync.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/oam_bug-2/1-lcd_sync.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/oam_bug-2/2-causes.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/oam_bug-2/2-causes.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/oam_bug-2/3-non_causes.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/oam_bug-2/3-non_causes.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/oam_bug-2/4-scanline_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/oam_bug-2/4-scanline_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/oam_bug-2/5-timing_bug.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/oam_bug-2/5-timing_bug.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/oam_bug-2/6-timing_no_bug.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/oam_bug-2/6-timing_no_bug.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/oam_bug-2/7-timing_effect.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/oam_bug-2/7-timing_effect.gb -------------------------------------------------------------------------------- /src/test/resources/roms/blargg/oam_bug-2/8-instr_effect.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/blargg/oam_bug-2/8-instr_effect.gb -------------------------------------------------------------------------------- /src/test/resources/roms/dmg-acid2/dmg-acid2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/dmg-acid2/dmg-acid2.gb -------------------------------------------------------------------------------- /src/test/resources/roms/dmg-acid2/dmg-acid2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/dmg-acid2/dmg-acid2.png -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2017 Joonas Javanainen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/add_sp_e_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/add_sp_e_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/bits/mem_oam.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/bits/mem_oam.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/bits/reg_f.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/bits/reg_f.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/bits/unused_hwio-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/bits/unused_hwio-GS.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_div-S.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_div-S.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_div-dmg0.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_div-dmg0.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_div-dmgABCmgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_div-dmgABCmgb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_div2-S.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_div2-S.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_hwio-S.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_hwio-S.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_hwio-dmg0.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_hwio-dmg0.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_hwio-dmgABCmgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_hwio-dmgABCmgb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_regs-dmg0.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_regs-dmg0.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_regs-dmgABC.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_regs-dmgABC.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_regs-mgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_regs-mgb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_regs-sgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_regs-sgb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/boot_regs-sgb2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/boot_regs-sgb2.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/call_cc_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/call_cc_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/call_cc_timing2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/call_cc_timing2.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/call_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/call_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/call_timing2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/call_timing2.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/di_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/di_timing-GS.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/div_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/div_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ei_sequence.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ei_sequence.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ei_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ei_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/halt_ime0_ei.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/halt_ime0_ei.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/halt_ime0_nointr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/halt_ime0_nointr_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/halt_ime1_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/halt_ime1_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/halt_ime1_timing2-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/halt_ime1_timing2-GS.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/if_ie_registers.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/if_ie_registers.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/instr/daa.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/instr/daa.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/interrupts/ie_push.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/interrupts/ie_push.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/intr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/intr_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/jp_cc_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/jp_cc_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/jp_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/jp_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ld_hl_sp_e_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ld_hl_sp_e_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/oam_dma/basic.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/oam_dma/basic.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/oam_dma/reg_read.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/oam_dma/reg_read.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/oam_dma/sources-dmgABCmgbS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/oam_dma/sources-dmgABCmgbS.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/oam_dma_restart.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/oam_dma_restart.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/oam_dma_start.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/oam_dma_start.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/oam_dma_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/oam_dma_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/pop_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/pop_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/hblank_ly_scx_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/hblank_ly_scx_timing-GS.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/intr_1_2_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/intr_1_2_timing-GS.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/intr_2_0_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/intr_2_0_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/intr_2_mode0_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/intr_2_mode0_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/intr_2_mode0_timing_sprites.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/intr_2_mode0_timing_sprites.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/intr_2_mode3_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/intr_2_mode3_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/intr_2_oam_ok_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/intr_2_oam_ok_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/lcdon_timing-dmgABCmgbS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/lcdon_timing-dmgABCmgbS.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/lcdon_write_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/lcdon_write_timing-GS.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/stat_irq_blocking.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/stat_irq_blocking.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/stat_lyc_onoff.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/stat_lyc_onoff.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ppu/vblank_stat_intr-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ppu/vblank_stat_intr-GS.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/push_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/push_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/rapid_di_ei.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/rapid_di_ei.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ret_cc_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ret_cc_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/ret_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/ret_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/reti_intr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/reti_intr_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/reti_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/reti_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/rst_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/rst_timing.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/serial/boot_sclk_align-dmgABCmgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/serial/boot_sclk_align-dmgABCmgb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/div_write.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/div_write.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/rapid_toggle.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/rapid_toggle.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tim00.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tim00.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tim00_div_trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tim00_div_trigger.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tim01.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tim01.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tim01_div_trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tim01_div_trigger.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tim10.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tim10.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tim10_div_trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tim10_div_trigger.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tim11.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tim11.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tim11_div_trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tim11_div_trigger.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tima_reload.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tima_reload.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tima_write_reloading.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tima_write_reloading.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/acceptance/timer/tma_write_reloading.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/acceptance/timer/tma_write_reloading.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/bits_ram_en.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/bits_ram_en.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/multicart_rom_8Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/multicart_rom_8Mb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/ram_256Kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/ram_256Kb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/ram_64Kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/ram_64Kb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/rom_16Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/rom_16Mb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/rom_1Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/rom_1Mb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/rom_2Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/rom_2Mb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/rom_4Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/rom_4Mb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/rom_512Kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/rom_512Kb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/emulator-only/mbc1/rom_8Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/emulator-only/mbc1/rom_8Mb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/manual-only/sprite_priority.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/manual-only/sprite_priority.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/misc/bits/unused_hwio-C.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/misc/bits/unused_hwio-C.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/misc/boot_div-A.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/misc/boot_div-A.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/misc/boot_div-cgb0.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/misc/boot_div-cgb0.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/misc/boot_div-cgbABCDE.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/misc/boot_div-cgbABCDE.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/misc/boot_hwio-C.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/misc/boot_hwio-C.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/misc/boot_regs-A.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/misc/boot_regs-A.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/misc/boot_regs-cgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/misc/boot_regs-cgb.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/misc/ppu/vblank_stat_intr-C.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/misc/ppu/vblank_stat_intr-C.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/utils/bootrom_dumper.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/utils/bootrom_dumper.gb -------------------------------------------------------------------------------- /src/test/resources/roms/mooneye/utils/dump_boot_hwio.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trekawek/coffee-gb/a35849d783545b113dc454929fe27687537f3b2f/src/test/resources/roms/mooneye/utils/dump_boot_hwio.gb -------------------------------------------------------------------------------- /src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | # SLF4J's SimpleLogger configuration file 2 | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. 3 | 4 | # Default logging detail level for all instances of SimpleLogger. 5 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 6 | # If not specified, defaults to "info". 7 | org.slf4j.simpleLogger.defaultLogLevel=info 8 | 9 | # Logging detail level for a SimpleLogger instance named "xxxxx". 10 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 11 | # If not specified, the default logging detail level is used. 12 | #org.slf4j.simpleLogger.log.xxxxx= 13 | 14 | # Set to true if you want the current date and time to be included in output messages. 15 | # Default is false, and will output the number of milliseconds elapsed since startup. 16 | org.slf4j.simpleLogger.showDateTime=true 17 | 18 | # The date and time format to be used in the output messages. 19 | # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. 20 | # If the format is not specified or is invalid, the default format is used. 21 | # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. 22 | org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z 23 | 24 | # Set to true if you want to output the current thread name. 25 | # Defaults to true. 26 | #org.slf4j.simpleLogger.showThreadName=true 27 | 28 | # Set to true if you want the Logger instance name to be included in output messages. 29 | # Defaults to true. 30 | #org.slf4j.simpleLogger.showLogName=true 31 | 32 | # Set to true if you want the last component of the name to be included in output messages. 33 | # Defaults to false. 34 | #org.slf4j.simpleLogger.showShortLogName=false --------------------------------------------------------------------------------