├── .idea ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── compiler.xml ├── vcs.xml ├── misc.xml └── jarRepositories.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ └── java │ │ └── JavaBoy │ │ ├── video │ │ ├── Palettes.java │ │ ├── Renderer.java │ │ ├── pixelpipeline │ │ │ ├── PixelFIFO.java │ │ │ ├── Pixel.java │ │ │ ├── ArrayQueue.java │ │ │ ├── DmgFifo.java │ │ │ └── FIFOFetcher.java │ │ ├── LCDC.java │ │ ├── GpuRegisters.java │ │ ├── LCDStat.java │ │ ├── Palette.java │ │ ├── Vram.java │ │ ├── Oam.java │ │ └── Gpu.java │ │ ├── cpu │ │ ├── REGISTERS.java │ │ ├── instructions │ │ │ ├── Instruction.java │ │ │ ├── jumpconditions │ │ │ │ ├── JumpCondition.java │ │ │ │ ├── CFlagNotSet.java │ │ │ │ ├── CFlagSet.java │ │ │ │ ├── ZFlagSet.java │ │ │ │ ├── ZFlagNotSet.java │ │ │ │ └── JumpConditions.java │ │ │ ├── DI.java │ │ │ ├── Nop.java │ │ │ ├── EI.java │ │ │ ├── SCF.java │ │ │ ├── CCF.java │ │ │ ├── CPL.java │ │ │ ├── CB.java │ │ │ ├── Pop.java │ │ │ ├── Push.java │ │ │ ├── Daa.java │ │ │ ├── Call.java │ │ │ ├── Swap.java │ │ │ ├── Cp.java │ │ │ ├── Or.java │ │ │ ├── Xor.java │ │ │ ├── And.java │ │ │ ├── Return.java │ │ │ ├── Dec.java │ │ │ ├── Jump.java │ │ │ ├── Inc.java │ │ │ ├── Rotate.java │ │ │ ├── Sub.java │ │ │ ├── Shift.java │ │ │ ├── Set.java │ │ │ ├── Reset.java │ │ │ ├── Bit.java │ │ │ ├── Add.java │ │ │ ├── RotateCB.java │ │ │ └── Load.java │ │ ├── registers │ │ │ ├── Register.java │ │ │ ├── Register8.java │ │ │ ├── Register16.java │ │ │ ├── RegisterPairs.java │ │ │ └── RegisterBank.java │ │ ├── flags │ │ │ ├── FLAGS.java │ │ │ └── FlagBank.java │ │ ├── interrupts │ │ │ ├── Interrupts.java │ │ │ └── InterruptManager.java │ │ └── CPU.java │ │ ├── memory │ │ ├── MemorySlot.java │ │ ├── MemoryMap.java │ │ └── Dma.java │ │ ├── cartridge │ │ ├── types │ │ │ ├── CartridgeTypes.java │ │ │ ├── ROM.java │ │ │ └── CartridgeType.java │ │ └── Cartridge.java │ │ ├── utils │ │ ├── ArithmeticUtils.java │ │ └── BitUtils.java │ │ ├── debug │ │ └── DebugMemory.java │ │ ├── input │ │ └── Joypad.java │ │ ├── timer │ │ └── Timer.java │ │ ├── JavaBoy.java │ │ └── gui │ │ └── GBGui.java └── test │ └── java │ └── JavaBoy │ └── JavaBoyTest.java ├── .gitmodules ├── .gitattributes ├── settings.gradle ├── .settings └── org.eclipse.buildship.core.prefs ├── .project ├── LICENSE ├── .classpath ├── gradlew.bat ├── .gitignore └── gradlew /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damilolarandolph/JavaBoy/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/Palettes.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video; 2 | 3 | public enum Palettes { 4 | BGB, OBP0, OBP1 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/REGISTERS.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu; 2 | 3 | public enum REGISTERS { 4 | A, B, C, D, E, F, H, L 5 | } 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/main/resources/gb-test-roms"] 2 | path = src/main/resources/gb-test-roms 3 | url = https://github.com/damilolarandolph/gb-test-roms.git 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Instruction.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | 5 | public interface Instruction { 6 | boolean execute(int opcode, CPU cpu); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/jumpconditions/JumpCondition.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions.jumpconditions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | 5 | interface JumpCondition { 6 | 7 | boolean test(CPU cpu); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/Renderer.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video; 2 | 3 | public interface Renderer { 4 | void renderPixel(Palette.GreyShades shade); 5 | void hBlank(); 6 | void vBlank(); 7 | void requestRefresh(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/registers/Register.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.registers; 2 | 3 | public interface Register { 4 | int read(); 5 | 6 | void write(int value); 7 | 8 | void increment(); 9 | 10 | void decrement(); 11 | } 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/memory/MemorySlot.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.memory; 2 | 3 | public interface MemorySlot { 4 | 5 | int getByte(int address); 6 | 7 | void setByte(int address, int value); 8 | 9 | boolean hasAddressInSlot(int address); 10 | } 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/flags/FLAGS.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.flags; 2 | 3 | public enum FLAGS { 4 | Z(7), C(4), N(6), H(5); 5 | 6 | private final int bitIndex; 7 | 8 | FLAGS(int index) { 9 | this.bitIndex = index; 10 | } 11 | 12 | public int getBitIndex() { 13 | return this.bitIndex; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/jumpconditions/CFlagNotSet.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions.jumpconditions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.flags.FLAGS; 5 | 6 | class CFlagNotSet implements JumpCondition { 7 | @Override 8 | public boolean test(CPU cpu) { 9 | return !cpu.isFlag(FLAGS.C); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/jumpconditions/CFlagSet.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions.jumpconditions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.flags.FLAGS; 5 | 6 | class CFlagSet implements JumpCondition { 7 | @Override 8 | public boolean test(CPU cpu) { 9 | return cpu.getFlag(FLAGS.C) == 1; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/jumpconditions/ZFlagSet.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions.jumpconditions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.flags.FLAGS; 5 | 6 | class ZFlagSet implements JumpCondition { 7 | 8 | @Override 9 | public boolean test(CPU cpu) { 10 | return cpu.isFlag(FLAGS.Z); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/jumpconditions/ZFlagNotSet.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions.jumpconditions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.flags.FLAGS; 5 | 6 | class ZFlagNotSet implements JumpCondition { 7 | @Override 8 | public boolean test(CPU cpu) { 9 | return !cpu.isFlag(FLAGS.Z); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.6.1/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = 'JavaBoy' 11 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/DI.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | 5 | public class DI implements Instruction { 6 | @Override 7 | public boolean execute(int opcode, CPU cpu) { 8 | if (opcode == 0xf3) { 9 | cpu.disableInterrupts(); 10 | cpu.addCycles(); 11 | return true; 12 | } 13 | return false; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Nop.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | 5 | public class Nop implements Instruction { 6 | @Override 7 | public boolean execute(int opcode, CPU cpu) { 8 | 9 | if (opcode == 0x0) { 10 | cpu.addCycles(); 11 | return true; 12 | } else { 13 | return false; 14 | } 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/EI.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | 5 | public class EI implements Instruction { 6 | 7 | @Override 8 | public boolean execute(int opcode, CPU cpu) { 9 | if (opcode == 0xfb) { 10 | cpu.enableInterrupts(); 11 | cpu.addCycles(); 12 | return true; 13 | } 14 | return false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/home/damilola/.sdkman/candidates/java/11.0.7.fx-zulu 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /src/test/java/JavaBoy/JavaBoyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Java source file was generated by the Gradle 'init' task. 3 | */ 4 | package JavaBoy; 5 | 6 | import org.junit.Test; 7 | import static org.junit.Assert.*; 8 | 9 | public class JavaBoyTest { 10 | @Test public void testAppHasAGreeting() { 11 | JavaBoy classUnderTest = new JavaBoy(); 12 | assertNotNull("app should have a greeting", classUnderTest.getGreeting()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/jumpconditions/JumpConditions.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions.jumpconditions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | 5 | public enum JumpConditions { 6 | NZ(new ZFlagNotSet()), 7 | Z(new ZFlagSet()), 8 | NC(new CFlagNotSet()), 9 | C(new CFlagSet()); 10 | 11 | private final JumpCondition condition; 12 | 13 | JumpConditions(JumpCondition condition) { 14 | this.condition = condition; 15 | } 16 | 17 | public boolean test(CPU cpu) { 18 | return condition.test(cpu); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/pixelpipeline/PixelFIFO.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video.pixelpipeline; 2 | 3 | import JavaBoy.video.Palette; 4 | import JavaBoy.video.Palettes; 5 | 6 | public interface PixelFIFO { 7 | 8 | void push(Pixel[] pixels); 9 | 10 | Palette.GreyShades getPixel(); 11 | void clear(); 12 | boolean canPop(); 13 | void disablePopping(); 14 | void enablePopping(); 15 | boolean canPush(); 16 | boolean peekIsAboveBG(); 17 | int peekColour(); 18 | Palettes peekPalette(); 19 | void pushOverlay(Pixel[] pixels); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/SCF.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.flags.FLAGS; 5 | 6 | public class SCF implements Instruction { 7 | 8 | @Override 9 | public boolean execute(int opcode, CPU cpu) { 10 | if (opcode == 0x37) { 11 | cpu.setFlag(FLAGS.C, true); 12 | cpu.setFlag(FLAGS.H, false); 13 | cpu.setFlag(FLAGS.N, false); 14 | cpu.addCycles(); 15 | return true; 16 | } else { 17 | return false; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cartridge/types/CartridgeTypes.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cartridge.types; 2 | 3 | public enum CartridgeTypes { 4 | ROM_ONLY(0x0, ROM.class); 5 | 6 | private final int code; 7 | private final Class cartClass; 8 | 9 | CartridgeTypes(int code, Class cartClass) { 10 | this.code = code; 11 | this.cartClass = cartClass; 12 | } 13 | 14 | public Class getCartClass() { 15 | return this.cartClass; 16 | } 17 | 18 | public int getCode() { 19 | return this.code; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/CCF.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.flags.FLAGS; 5 | 6 | public class CCF implements Instruction { 7 | 8 | @Override 9 | public boolean execute(int opcode, CPU cpu) { 10 | if (opcode == 0x3f) { 11 | cpu.setFlag(FLAGS.C, !cpu.isFlag(FLAGS.C)); 12 | cpu.setFlag(FLAGS.H, false); 13 | cpu.setFlag(FLAGS.N, false); 14 | cpu.addCycles(); 15 | return true; 16 | } else { 17 | return false; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/registers/Register8.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.registers; 2 | 3 | public class Register8 implements Register { 4 | 5 | private int value; 6 | 7 | @Override 8 | public int read() { 9 | return this.value; 10 | } 11 | 12 | @Override 13 | public void write(int value) { 14 | this.value = (value & 0xff); 15 | } 16 | 17 | @Override 18 | public void increment() { 19 | this.value = (value + 1) & 0xff; 20 | } 21 | 22 | @Override 23 | public void decrement() { 24 | this.value = (value - 1) & 0xff; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/registers/Register16.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.registers; 2 | 3 | public class Register16 implements Register { 4 | private int value; 5 | 6 | 7 | @Override 8 | public int read() { 9 | return this.value; 10 | } 11 | 12 | @Override 13 | public void write(int value) { 14 | this.value = (value & 0xffff); 15 | } 16 | 17 | @Override 18 | public void increment() { 19 | this.value = (value + 1) & 0xffff; 20 | } 21 | 22 | @Override 23 | public void decrement() { 24 | 25 | this.value = (value - 1) & 0xffff; 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/interrupts/Interrupts.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.interrupts; 2 | 3 | public enum Interrupts { 4 | V_BLANK(0, 0x40), 5 | LCD_STAT(1, 0x48), 6 | TIMER(2, 0x50), 7 | SERIAL(3, 0x58), 8 | JOYPAD(4, 0x60); 9 | private final int bitIndex; 10 | private final int interruptVector; 11 | 12 | Interrupts(int index, int interruptVector) { 13 | this.bitIndex = index; 14 | this.interruptVector = interruptVector; 15 | } 16 | 17 | public int getBitIndex() { 18 | return this.bitIndex; 19 | } 20 | 21 | public int getInterruptVector() { 22 | return this.interruptVector; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/CPL.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | 7 | public class CPL implements Instruction { 8 | @Override 9 | public boolean execute(int opcode, CPU cpu) { 10 | if (opcode == 0x2f) { 11 | int value = cpu.readRegister(REGISTERS.A); 12 | cpu.writeRegister(REGISTERS.A, ~(value)); 13 | cpu.setFlag(FLAGS.H, true); 14 | cpu.setFlag(FLAGS.N, true); 15 | cpu.addCycles(); 16 | return true; 17 | 18 | } else { 19 | return false; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/utils/ArithmeticUtils.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.utils; 2 | 3 | public class ArithmeticUtils { 4 | 5 | public static boolean isHalfCarry16(int val1, int val2) { 6 | return (((val1 & 0xfff) + (val2 & 0xfff)) & 0x1000) == 0x1000; 7 | } 8 | 9 | public static boolean isCarry16(int val1, int val2) { 10 | return ((val1 & 0xffff) + (val2 & 0xffff)) > 0xffff; 11 | } 12 | 13 | public static boolean isHalfCarry8(int val1, int val2) { 14 | return (((val1 & 0xf) + (val2 & 0xf)) & 0x10) == 0x10; 15 | } 16 | 17 | public static boolean isCarry8(int val1, int val2) { 18 | return ((val1 & 0xff) + (val2 & 0xff)) > 0xff; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/registers/RegisterPairs.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.registers; 2 | 3 | import JavaBoy.cpu.REGISTERS; 4 | 5 | public enum RegisterPairs { 6 | 7 | BC(REGISTERS.B, REGISTERS.C), 8 | DE(REGISTERS.D, REGISTERS.E), 9 | HL(REGISTERS.H, REGISTERS.L), 10 | AF(REGISTERS.A, REGISTERS.F); 11 | 12 | private final REGISTERS high; 13 | private final REGISTERS low; 14 | 15 | RegisterPairs(REGISTERS high, REGISTERS low) { 16 | this.high = high; 17 | this.low = low; 18 | } 19 | 20 | public REGISTERS getHigh() { 21 | return this.high; 22 | } 23 | 24 | public REGISTERS getLow() { 25 | return this.low; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cartridge/types/ROM.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cartridge.types; 2 | 3 | public class ROM extends CartridgeType { 4 | 5 | public ROM(byte[] data) { 6 | super(data); 7 | } 8 | 9 | 10 | @Override 11 | public int getByte(int address) { 12 | if (address < 0x100 && bootRomMapped) 13 | return bootRom[address]; 14 | return Byte.toUnsignedInt(data[address]); 15 | } 16 | 17 | @Override 18 | public void setByte(int address, int value) { 19 | } 20 | 21 | 22 | @Override 23 | public boolean hasAddressInSlot(int address) { 24 | return (address >= 0x0 && address < 0x8000) || (address >= 0xA000 && address < 0xC000); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/pixelpipeline/Pixel.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video.pixelpipeline; 2 | 3 | import JavaBoy.video.Palettes; 4 | 5 | public class Pixel { 6 | private int colour; 7 | private Palettes palette; 8 | private boolean isAboveBG; 9 | 10 | public void setAboveBG(boolean aboveBG) { 11 | isAboveBG = aboveBG; 12 | } 13 | 14 | public void setColour(int colour) { 15 | this.colour = colour; 16 | } 17 | 18 | public void setPalette(Palettes palette) { 19 | this.palette = palette; 20 | } 21 | 22 | public boolean getAboveBG() { 23 | return isAboveBG; 24 | } 25 | 26 | public Palettes getPalette() { 27 | return palette; 28 | } 29 | 30 | public int getColour() { 31 | return colour; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/CB.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | 5 | public class CB implements Instruction { 6 | final Instruction[] instructions; 7 | 8 | public CB(Instruction[] instructions) { 9 | this.instructions = instructions; 10 | } 11 | 12 | @Override 13 | public boolean execute(int opcode, CPU cpu) { 14 | if (opcode == 0xcb) { 15 | int cbOpcode = cpu.readPC(); 16 | cpu.addCycles(); 17 | boolean result; 18 | for (Instruction instruction : instructions) { 19 | result = instruction.execute(cbOpcode, cpu); 20 | if (result) 21 | return true; 22 | } 23 | } 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/utils/BitUtils.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.utils; 2 | 3 | public class BitUtils { 4 | 5 | public static int getNthBit(int position, int value) { 6 | return (value >>> position) & 0x01; 7 | } 8 | 9 | public static int setNthBit(int position, int setBit, int bits) { 10 | int bitMask = ~(0x01 << position); 11 | int result = (bits & bitMask) | (setBit << position); 12 | return result; 13 | } 14 | 15 | public static int getLsn(int value) { 16 | return (value & 0xf); 17 | } 18 | 19 | public static int getMsn(int value) { 20 | return (value >>> 4); 21 | } 22 | 23 | public static int getLsb(int value) { 24 | return (value & 0xff); 25 | } 26 | 27 | public static int getMsb(int value) { 28 | return (value >>> 8); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | JavaBoy 4 | Project JavaBoy created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | 25 | 1603851797730 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Pop.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.registers.RegisterPairs; 5 | 6 | public class Pop implements Instruction { 7 | 8 | @Override 9 | public boolean execute(int opcode, CPU cpu) { 10 | switch (opcode) { 11 | case 0xf1: 12 | return pop(RegisterPairs.AF, cpu); 13 | case 0xc1: 14 | return pop(RegisterPairs.BC, cpu); 15 | case 0xd1: 16 | return pop(RegisterPairs.DE, cpu); 17 | case 0xe1: 18 | return pop(RegisterPairs.HL, cpu); 19 | 20 | 21 | default: 22 | return false; 23 | 24 | } 25 | } 26 | 27 | 28 | private boolean pop(RegisterPairs pair, CPU cpu) { 29 | int lowByte = cpu.popSP(); 30 | int highByte = cpu.popSP(); 31 | cpu.writeWordRegister(pair, (highByte << 8) | lowByte); 32 | cpu.addCycles(3); 33 | return true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Push.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.registers.RegisterPairs; 5 | 6 | import static JavaBoy.utils.BitUtils.getLsb; 7 | import static JavaBoy.utils.BitUtils.getMsb; 8 | 9 | public class Push implements Instruction { 10 | 11 | @Override 12 | public boolean execute(int opcode, CPU cpu) { 13 | switch (opcode) { 14 | case 0xf5: 15 | return push(RegisterPairs.AF, cpu); 16 | case 0xc5: 17 | return push(RegisterPairs.BC, cpu); 18 | case 0xd5: 19 | return push(RegisterPairs.DE, cpu); 20 | case 0xe5: 21 | return push(RegisterPairs.HL, cpu); 22 | default: 23 | return false; 24 | } 25 | } 26 | 27 | private boolean push(RegisterPairs pair, CPU cpu) { 28 | int val = cpu.readWordRegister(pair); 29 | cpu.pushSP(getMsb(val)); 30 | cpu.pushSP(getLsb(val)); 31 | cpu.addCycles(4); 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Damilola Randolph 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 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/flags/FlagBank.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.flags; 2 | 3 | import JavaBoy.cpu.registers.Register; 4 | 5 | import static JavaBoy.utils.BitUtils.getNthBit; 6 | import static JavaBoy.utils.BitUtils.setNthBit; 7 | 8 | 9 | public class FlagBank implements Register { 10 | 11 | private int flags = 0; 12 | 13 | 14 | public void setFlag(FLAGS flag, boolean val) { 15 | flags = setNthBit(flag.getBitIndex(), val ? 1 : 0, flags); 16 | } 17 | 18 | public boolean isFlag(FLAGS flag) { 19 | int val = getNthBit(flag.getBitIndex(), flags); 20 | return val == 1; 21 | } 22 | 23 | public int getFlag(FLAGS flag) { 24 | return getNthBit(flag.getBitIndex(), flags); 25 | } 26 | 27 | 28 | @Override 29 | public int read() { 30 | return this.flags; 31 | } 32 | 33 | @Override 34 | public void write(int value) { 35 | 36 | this.flags = (value & 0xf0) ; 37 | } 38 | 39 | @Override 40 | public void increment() { 41 | this.flags = (flags + 1) & 0xff; 42 | } 43 | 44 | @Override 45 | public void decrement() { 46 | this.flags = (flags - 1) & 0xff; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/debug/DebugMemory.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.debug; 2 | 3 | import JavaBoy.memory.MemorySlot; 4 | 5 | public class DebugMemory implements MemorySlot { 6 | private final byte[] data = new byte[(0x9fff - 0x8000) + 1]; 7 | private final byte[] bank2 = new byte[(0xffff - 0xc000) + 1]; 8 | 9 | @Override 10 | public int getByte(int address) { 11 | if (address >= 0xc000) 12 | return Byte.toUnsignedInt(bank2[address - 0xc000]); 13 | else { 14 | return Byte.toUnsignedInt(data[address - 0x8000]); 15 | } 16 | } 17 | 18 | @Override 19 | public void setByte(int address, int value) { 20 | if (address >= 0xc000) { 21 | bank2[address - 0xc000] = (byte) value; 22 | if (address == 0xff02 && value == 0x81) { 23 | System.out.print((char) this.getByte(0xff01)); 24 | } 25 | } else { 26 | data[address - 0x8000] = (byte) value; 27 | } 28 | } 29 | 30 | @Override 31 | public boolean hasAddressInSlot(int address) { 32 | return (address >= 0x8000 && address <= 0x9fff) || (address >= 0xc000 && address <= 0xFFFF); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/pixelpipeline/ArrayQueue.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video.pixelpipeline; 2 | 3 | 4 | public class ArrayQueue { 5 | private final Object[] data; 6 | private final int capacity; 7 | private int size = 0; 8 | private int head =0; 9 | private int tail = head; 10 | 11 | public ArrayQueue(int capacity){ 12 | this.capacity = capacity; 13 | this.data = new Object[capacity]; 14 | } 15 | 16 | public T poll(){ 17 | --size; 18 | T item = (T) data[head]; 19 | head = (++head) % capacity; 20 | 21 | return item; 22 | } 23 | 24 | 25 | public T peek (){ 26 | return (T)data[head]; 27 | } 28 | public void clear(){ 29 | this.head = this.tail = this.size = 0; 30 | } 31 | public void add(T item){ 32 | ++size; 33 | data[tail] = item; 34 | tail = (++tail) % capacity; 35 | } 36 | 37 | public int size(){ 38 | return this.size; 39 | } 40 | 41 | public T getAt(int index){ 42 | return (T)data[(head + index) % capacity]; 43 | } 44 | 45 | public void setAt(int index, T value){ 46 | data[(head + index) % capacity] = value; 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Daa.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | 7 | import static JavaBoy.utils.BitUtils.getLsb; 8 | 9 | public class Daa implements Instruction { 10 | @Override 11 | public boolean execute(int opcode, CPU cpu) { 12 | if (opcode == 0x27) { 13 | 14 | applyDAA(cpu); 15 | cpu.addCycles(); 16 | return true; 17 | } else { 18 | return false; 19 | } 20 | } 21 | 22 | private void applyDAA(CPU cpu) { 23 | int adjustment = 0; 24 | int aReg = cpu.readRegister(REGISTERS.A); 25 | if (cpu.isFlag(FLAGS.C) || (aReg > 0x99 && !cpu.isFlag(FLAGS.N))){ 26 | adjustment = 0x60; 27 | cpu.setFlag(FLAGS.C, true); 28 | } 29 | 30 | if (cpu.isFlag(FLAGS.H) || (aReg & 0x0f) > 0x09 && !cpu.isFlag(FLAGS.N)){ 31 | adjustment += 0x06; 32 | } 33 | 34 | aReg += cpu.isFlag(FLAGS.N) ? -(adjustment) : adjustment; 35 | aReg &= 0xff; 36 | 37 | cpu.setFlag(FLAGS.Z, aReg == 0); 38 | cpu.setFlag(FLAGS.H, false); 39 | cpu.writeRegister(REGISTERS.A, aReg); 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/memory/MemoryMap.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.memory; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class MemoryMap { 6 | 7 | private MemorySlot[] slots; 8 | 9 | 10 | 11 | public void setSlots(MemorySlot[] slots) { 12 | this.slots = slots; 13 | } 14 | 15 | public int readByte(int address) { 16 | 17 | MemorySlot slot = getSlot(address); 18 | 19 | if (slot != null) { 20 | return slot.getByte(address); 21 | } else { 22 | System.err.println("Address: " + Integer.toHexString( 23 | address) + " is not mapped to any slot"); 24 | System.exit(1); 25 | return -99; 26 | 27 | } 28 | 29 | } 30 | 31 | public void setByte(int address, int value) { 32 | 33 | MemorySlot slot = getSlot(address); 34 | 35 | if (slot != null) { 36 | slot.setByte(address, value); 37 | } else { 38 | System.err.println("Address: " + Integer.toHexString( 39 | address) + " is not mapped to any slot"); 40 | System.exit(1); 41 | } 42 | 43 | } 44 | 45 | private MemorySlot getSlot(int address) { 46 | MemorySlot result = null; 47 | int size = slots.length; 48 | for (int idx = 0; idx < size; ++idx) { 49 | if (slots[idx].hasAddressInSlot(address)) 50 | { 51 | result = slots[idx]; 52 | break; 53 | } 54 | } 55 | return result; 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Call.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.instructions.jumpconditions.JumpConditions; 5 | 6 | import static JavaBoy.utils.BitUtils.getLsb; 7 | import static JavaBoy.utils.BitUtils.getMsb; 8 | 9 | public class Call implements Instruction { 10 | 11 | @Override 12 | public boolean execute(int opcode, CPU cpu) { 13 | switch (opcode) { 14 | case 0xcd: 15 | return call(cpu); 16 | case 0xc4: 17 | return call(JumpConditions.NZ, cpu); 18 | case 0xcc: 19 | return call(JumpConditions.Z, cpu); 20 | case 0xd4: 21 | return call(JumpConditions.NC, cpu); 22 | case 0xdc: 23 | return call(JumpConditions.C, cpu); 24 | default: 25 | return false; 26 | } 27 | } 28 | 29 | 30 | private boolean call(CPU cpu) { 31 | applyCall(cpu); 32 | cpu.addCycles(6); 33 | return true; 34 | } 35 | 36 | private boolean call(JumpConditions condition, CPU cpu) { 37 | if (condition.test(cpu)) { 38 | return call(cpu); 39 | } else { 40 | cpu.setPC(cpu.getPC() + 2); 41 | cpu.addCycles(3); 42 | return true; 43 | } 44 | } 45 | 46 | private void applyCall(CPU cpu) { 47 | 48 | int word = cpu.readWordPC(); 49 | cpu.pushSP(getMsb(cpu.getPC())); 50 | cpu.pushSP(getLsb(cpu.getPC())); 51 | cpu.setPC(word); 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/memory/Dma.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.memory; 2 | 3 | public class Dma implements MemorySlot { 4 | 5 | final MemoryMap mmu; 6 | int ticks = 0; 7 | int data = 0; 8 | int nextSource = 0; 9 | int nextDestination = 0xfe00; 10 | int cycles = 0; 11 | boolean isTransferring = false; 12 | 13 | public Dma(MemoryMap mmu) { 14 | this.mmu = mmu; 15 | } 16 | 17 | public void tick() { 18 | if (isTransferring) { 19 | if (cycles == 160) { 20 | isTransferring = false; 21 | } else { 22 | 23 | if ((nextSource & 0xff) <= 0x9f) { 24 | mmu.setByte(nextDestination, mmu.readByte(nextSource)); 25 | ++nextSource; 26 | ++nextDestination; 27 | } 28 | 29 | ++cycles; 30 | } 31 | } 32 | } 33 | 34 | public boolean canAccess(int address) { 35 | return !isTransferring || (address >= 0xff80 && address <= 0xfffe); 36 | } 37 | 38 | 39 | @Override 40 | public int getByte(int address) { 41 | return this.data; 42 | } 43 | 44 | @Override 45 | public void setByte(int address, int value) { 46 | value &= 0xff; 47 | isTransferring = true; 48 | ticks = 0; 49 | nextDestination = 0xfe00; 50 | this.data = value; 51 | cycles = 0; 52 | this.nextSource = (value * 0x100); 53 | } 54 | 55 | @Override 56 | public boolean hasAddressInSlot(int address) { 57 | return address == 0xff46; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/LCDC.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video; 2 | 3 | import JavaBoy.memory.MemorySlot; 4 | import JavaBoy.utils.BitUtils; 5 | 6 | public class LCDC implements MemorySlot { 7 | int data = 0; 8 | 9 | 10 | public boolean isLCD() { 11 | return BitUtils.getNthBit(7, data) == 1; 12 | } 13 | 14 | public Vram.BGMaps getWindowMap() { 15 | int select = BitUtils.getNthBit(6, data); 16 | 17 | return select == 0 ? Vram.BGMaps.MAP1 : Vram.BGMaps.MAP2; 18 | } 19 | 20 | public boolean isWindowEnabled() { 21 | return BitUtils.getNthBit(5, data) == 1; 22 | } 23 | 24 | public AddressingModes getBGWindowAddressing() { 25 | int select = BitUtils.getNthBit(4, data); 26 | 27 | return select == 0 ? AddressingModes.M8800 : AddressingModes.M8000; 28 | } 29 | 30 | public Vram.BGMaps getBGMap() { 31 | int select = BitUtils.getNthBit(3, data); 32 | return select == 0 ? Vram.BGMaps.MAP1 : Vram.BGMaps.MAP2; 33 | } 34 | 35 | public boolean isOBJSize() { 36 | return BitUtils.getNthBit(2, data) == 1; 37 | } 38 | 39 | public boolean isOBJEnable() { 40 | return BitUtils.getNthBit(1, data) == 1; 41 | } 42 | 43 | public boolean isPriority() { 44 | return BitUtils.getNthBit(0, data) == 1; 45 | } 46 | 47 | @Override 48 | public int getByte(int address) { 49 | return data; 50 | } 51 | 52 | @Override 53 | public void setByte(int address, int value) { 54 | data = value & 0xff; 55 | } 56 | 57 | @Override 58 | public boolean hasAddressInSlot(int address) { 59 | return address == 0xff40; 60 | } 61 | 62 | public enum AddressingModes { 63 | M8000, M8800, M9800, M9C00 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cartridge/Cartridge.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cartridge; 2 | 3 | import JavaBoy.cartridge.types.CartridgeType; 4 | import JavaBoy.cartridge.types.CartridgeTypes; 5 | import JavaBoy.memory.MemorySlot; 6 | 7 | import java.io.BufferedInputStream; 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | 11 | public class Cartridge implements MemorySlot { 12 | 13 | CartridgeType cartData; 14 | int cartRegister = 0; 15 | public Cartridge(File file) { 16 | 17 | byte[] rom = getFileArray(file); 18 | try { 19 | cartData = CartridgeTypes.ROM_ONLY.getCartClass().getConstructor( 20 | byte[].class).newInstance(rom); 21 | } catch (Exception err) { 22 | System.out.println("Error loading cart " + err.getMessage()); 23 | } 24 | 25 | } 26 | 27 | private byte[] getFileArray(File file) { 28 | try { 29 | 30 | BufferedInputStream stream = new BufferedInputStream( 31 | new FileInputStream(file)); 32 | return stream.readAllBytes(); 33 | } catch (Exception err) { 34 | return null; 35 | } 36 | } 37 | 38 | 39 | @Override 40 | public int getByte(int address) { 41 | if (address == 0xff50){ 42 | return cartRegister; 43 | } 44 | return cartData.getByte(address); 45 | } 46 | 47 | @Override 48 | public void setByte(int address, int value) { 49 | if (address == 0xff50){ 50 | cartRegister =value; 51 | cartData.setBootRomMapped(value == 0); 52 | }else { 53 | cartData.setByte(address, value); 54 | } 55 | } 56 | 57 | 58 | @Override 59 | public boolean hasAddressInSlot(int address) { 60 | return address == 0xff50 || cartData.hasAddressInSlot(address); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Swap.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | import static JavaBoy.utils.BitUtils.getLsn; 9 | import static JavaBoy.utils.BitUtils.getMsn; 10 | 11 | public class Swap implements Instruction { 12 | 13 | @Override 14 | public boolean execute(int opcode, CPU cpu) { 15 | switch (opcode) { 16 | case 0x37: 17 | return swap(REGISTERS.A, cpu); 18 | case 0x30: 19 | return swap(REGISTERS.B, cpu); 20 | case 0x31: 21 | return swap(REGISTERS.C, cpu); 22 | case 0x32: 23 | return swap(REGISTERS.D, cpu); 24 | case 0x33: 25 | return swap(REGISTERS.E, cpu); 26 | case 0x34: 27 | return swap(REGISTERS.H, cpu); 28 | case 0x35: 29 | return swap(REGISTERS.L, cpu); 30 | case 0x36: 31 | return swap(cpu); 32 | default: 33 | return false; 34 | } 35 | } 36 | 37 | 38 | private boolean swap(REGISTERS reg, CPU cpu) { 39 | int bits = cpu.readRegister(reg); 40 | int result = applySwap(bits, cpu); 41 | cpu.writeRegister(reg, result); 42 | cpu.addCycles(); 43 | return true; 44 | } 45 | 46 | private boolean swap(CPU cpu) { 47 | int addr = cpu.readWordRegister(RegisterPairs.HL); 48 | int bits = cpu.readAddress(addr); 49 | int result = applySwap(bits, cpu); 50 | cpu.writeAddress(addr, result); 51 | cpu.addCycles(4); 52 | return true; 53 | } 54 | 55 | private int applySwap(int val, CPU cpu) { 56 | int result = (getLsn(val) << 4) | getMsn(val); 57 | cpu.setFlag(FLAGS.C, false); 58 | cpu.setFlag(FLAGS.H, false); 59 | cpu.setFlag(FLAGS.N, false); 60 | cpu.setFlag(FLAGS.Z, result == 0); 61 | return result; 62 | } 63 | } 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/registers/RegisterBank.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.registers; 2 | 3 | import JavaBoy.cpu.REGISTERS; 4 | import JavaBoy.cpu.flags.FlagBank; 5 | 6 | import java.util.HashMap; 7 | 8 | import static JavaBoy.utils.BitUtils.getLsb; 9 | import static JavaBoy.utils.BitUtils.getMsb; 10 | 11 | public class RegisterBank { 12 | 13 | private final HashMap registers = new HashMap<>(); 14 | private final FlagBank flags; 15 | private final Register SP; 16 | private final Register PC; 17 | 18 | public RegisterBank(FlagBank flags, Register SP, Register PC) { 19 | this.flags = flags; 20 | this.SP = SP; 21 | this.PC = PC; 22 | this.registers.put(REGISTERS.F, flags); 23 | } 24 | 25 | public FlagBank getFlags() { 26 | return this.flags; 27 | } 28 | 29 | public Register getSP() { 30 | return this.SP; 31 | } 32 | 33 | public Register getPC() { 34 | return this.PC; 35 | } 36 | 37 | public void writeRegister(REGISTERS register, int value) { 38 | if (registers.containsKey(register)) { 39 | registers.get(register).write(value); 40 | } else { 41 | Register reg = new Register8(); 42 | reg.write(value); 43 | this.registers.put(register, reg); 44 | } 45 | } 46 | 47 | public void writeRegister(RegisterPairs registerPair, int value) { 48 | writeRegister(registerPair.getHigh(), getMsb(value)); 49 | writeRegister(registerPair.getLow(), getLsb(value)); 50 | } 51 | 52 | public int readRegister(REGISTERS register) { 53 | if (this.registers.containsKey(register)) { 54 | return registers.get(register).read(); 55 | } else { 56 | Register reg = new Register8(); 57 | reg.write(0); 58 | this.registers.put(register, reg); 59 | return reg.read(); 60 | } 61 | } 62 | 63 | public int readRegister(RegisterPairs registerPair) { 64 | int msb = readRegister(registerPair.getHigh()); 65 | int lsb = readRegister(registerPair.getLow()); 66 | return (msb << 8) | lsb; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Cp.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | public class Cp implements Instruction { 9 | 10 | @Override 11 | public boolean execute(int opcode, CPU cpu) { 12 | switch (opcode) { 13 | case 0xbf: 14 | return cp(REGISTERS.A, cpu); 15 | case 0xb8: 16 | return cp(REGISTERS.B, cpu); 17 | case 0xb9: 18 | return cp(REGISTERS.C, cpu); 19 | case 0xba: 20 | return cp(REGISTERS.D, cpu); 21 | case 0xbb: 22 | return cp(REGISTERS.E, cpu); 23 | case 0xbc: 24 | return cp(REGISTERS.H, cpu); 25 | case 0xbd: 26 | return cp(REGISTERS.L, cpu); 27 | case 0xbe: 28 | return cpHL(cpu); 29 | case 0xfe: 30 | return cp(cpu); 31 | default: 32 | return false; 33 | } 34 | } 35 | 36 | 37 | private boolean cp(REGISTERS reg, CPU cpu) { 38 | int val1 = cpu.readRegister(REGISTERS.A); 39 | int val2 = cpu.readRegister(reg); 40 | 41 | applyCp(val1, val2, cpu); 42 | cpu.addCycles(); 43 | return true; 44 | } 45 | 46 | 47 | private boolean cpHL(CPU cpu) { 48 | int val1 = cpu.readRegister(REGISTERS.A); 49 | int val2 = cpu.readAddress(cpu.readWordRegister(RegisterPairs.HL)); 50 | 51 | applyCp(val1, val2, cpu); 52 | cpu.addCycles(2); 53 | return true; 54 | } 55 | 56 | private boolean cp(CPU cpu) { 57 | int val1 = cpu.readRegister(REGISTERS.A); 58 | int val2 = cpu.readPC(); 59 | 60 | applyCp(val1, val2, cpu); 61 | cpu.addCycles(2); 62 | return true; 63 | } 64 | 65 | 66 | private void applyCp(int val1, int val2, CPU cpu) { 67 | boolean isEqual = (val1 & 0xff) == (val2 & 0xff); 68 | 69 | cpu.setFlag(FLAGS.Z, isEqual); 70 | cpu.setFlag(FLAGS.H, (val1 & 0xf) < (val2 & 0xf)); 71 | cpu.setFlag(FLAGS.C, val1 < val2); 72 | cpu.setFlag(FLAGS.N, true); 73 | 74 | } 75 | } 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Or.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | public class Or implements Instruction { 9 | 10 | @Override 11 | public boolean execute(int opcode, CPU cpu) { 12 | switch (opcode) { 13 | case 0xb7: 14 | return or(REGISTERS.A, cpu); 15 | case 0xb0: 16 | return or(REGISTERS.B, cpu); 17 | case 0xb1: 18 | return or(REGISTERS.C, cpu); 19 | case 0xb2: 20 | return or(REGISTERS.D, cpu); 21 | case 0xb3: 22 | return or(REGISTERS.E, cpu); 23 | case 0xb4: 24 | return or(REGISTERS.H, cpu); 25 | case 0xb5: 26 | return or(REGISTERS.L, cpu); 27 | case 0xb6: 28 | return orHL(cpu); 29 | case 0xf6: 30 | return or(cpu); 31 | 32 | default: 33 | return false; 34 | } 35 | } 36 | 37 | 38 | boolean or(REGISTERS reg, CPU cpu) { 39 | int val1 = cpu.readRegister(REGISTERS.A); 40 | int val2 = cpu.readRegister(reg); 41 | cpu.writeRegister(REGISTERS.A, applyOr(val1, val2, cpu)); 42 | cpu.addCycles(); 43 | return true; 44 | } 45 | 46 | 47 | boolean orHL(CPU cpu) { 48 | int val1 = cpu.readRegister(REGISTERS.A); 49 | int val2 = cpu.readAddress(cpu.readWordRegister(RegisterPairs.HL)); 50 | cpu.writeRegister(REGISTERS.A, applyOr(val1, val2, cpu)); 51 | cpu.addCycles(2); 52 | return true; 53 | } 54 | 55 | 56 | boolean or(CPU cpu) { 57 | int val1 = cpu.readRegister(REGISTERS.A); 58 | int val2 = cpu.readPC(); 59 | cpu.writeRegister(REGISTERS.A, applyOr(val1, val2, cpu)); 60 | cpu.addCycles(2); 61 | return true; 62 | } 63 | 64 | 65 | int applyOr(int val1, int val2, CPU cpu) { 66 | int result = (val1 & 0xff) | (val2 & 0xff); 67 | cpu.setFlag(FLAGS.Z, result == 0); 68 | cpu.setFlag(FLAGS.C, false); 69 | cpu.setFlag(FLAGS.H, false); 70 | cpu.setFlag(FLAGS.N, false); 71 | return result; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Xor.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | public class Xor implements Instruction { 9 | 10 | @Override 11 | public boolean execute(int opcode, CPU cpu) { 12 | switch (opcode) { 13 | case 0xaf: 14 | return xor(REGISTERS.A, cpu); 15 | case 0xa8: 16 | return xor(REGISTERS.B, cpu); 17 | case 0xa9: 18 | return xor(REGISTERS.C, cpu); 19 | case 0xaa: 20 | return xor(REGISTERS.D, cpu); 21 | case 0xab: 22 | return xor(REGISTERS.E, cpu); 23 | case 0xac: 24 | return xor(REGISTERS.H, cpu); 25 | case 0xad: 26 | return xor(REGISTERS.L, cpu); 27 | case 0xae: 28 | return xorHL(cpu); 29 | case 0xee: 30 | return xor(cpu); 31 | default: 32 | return false; 33 | } 34 | } 35 | 36 | private boolean xor(REGISTERS reg, CPU cpu) { 37 | int val1 = cpu.readRegister(REGISTERS.A); 38 | int val2 = cpu.readRegister(reg); 39 | cpu.writeRegister(REGISTERS.A, applyXOR(val1, val2, cpu)); 40 | cpu.addCycles(); 41 | return true; 42 | } 43 | 44 | private boolean xorHL(CPU cpu) { 45 | int val1 = cpu.readRegister(REGISTERS.A); 46 | int val2 = cpu.readAddress(cpu.readWordRegister(RegisterPairs.HL)); 47 | cpu.writeRegister(REGISTERS.A, applyXOR(val1, val2, cpu)); 48 | cpu.addCycles(2); 49 | return true; 50 | } 51 | 52 | 53 | private boolean xor(CPU cpu) { 54 | int val1 = cpu.readRegister(REGISTERS.A); 55 | int val2 = cpu.readPC(); 56 | cpu.writeRegister(REGISTERS.A, applyXOR(val1, val2, cpu)); 57 | cpu.addCycles(2); 58 | return true; 59 | } 60 | 61 | private int applyXOR(int val1, int val2, CPU cpu) { 62 | int result = (val1 & 0xff) ^ (val2 & 0xff); 63 | cpu.setFlag(FLAGS.Z, result == 0); 64 | cpu.setFlag(FLAGS.C, false); 65 | cpu.setFlag(FLAGS.H, false); 66 | cpu.setFlag(FLAGS.N, false); 67 | return result; 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/GpuRegisters.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video; 2 | 3 | import JavaBoy.memory.MemorySlot; 4 | 5 | public class GpuRegisters implements MemorySlot { 6 | 7 | private int scy = 0; 8 | private int scx = 0; 9 | private int ly = 0; 10 | private int lyc = 0; 11 | private int wy = 0; 12 | private int wx = 0; 13 | 14 | public void setLy(int ly) { 15 | this.ly = ly; 16 | } 17 | 18 | public int getLy() { 19 | return ly; 20 | } 21 | 22 | public int getLyc() { 23 | return lyc; 24 | } 25 | 26 | public int getScx() { 27 | return scx; 28 | } 29 | 30 | public int getScy() { 31 | return scy; 32 | } 33 | 34 | public int getWx() { 35 | return wx; 36 | } 37 | 38 | public int getWy() { 39 | return wy; 40 | } 41 | 42 | @Override 43 | public int getByte(int address) { 44 | switch (address) { 45 | case 0xff42: 46 | return scy; 47 | case 0xff43: 48 | return scx; 49 | case 0xff44: 50 | return ly; 51 | case 0xff45: 52 | return lyc; 53 | case 0xff4a: 54 | return wy; 55 | default: 56 | return wx; 57 | } 58 | } 59 | 60 | @Override 61 | public void setByte(int address, int value) { 62 | switch (address) { 63 | case 0xff42: 64 | scy = value; 65 | break; 66 | case 0xff43: 67 | scx = value; 68 | break; 69 | case 0xff44: 70 | ly = value; 71 | case 0xff45: 72 | lyc = value; 73 | break; 74 | case 0xff4a: 75 | wy = value; 76 | break; 77 | case 0xff4b: 78 | wx = value; 79 | break; 80 | } 81 | } 82 | 83 | @Override 84 | public boolean hasAddressInSlot(int address) { 85 | switch (address) { 86 | case 0xff42: 87 | case 0xff43: 88 | case 0xff44: 89 | case 0xff45: 90 | case 0xff4a: 91 | case 0xff4b: 92 | return true; 93 | default: 94 | return false; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/And.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | public class And implements Instruction { 9 | 10 | 11 | @Override 12 | public boolean execute(int opcode, CPU cpu) { 13 | switch (opcode) { 14 | case 0xa7: 15 | return and(REGISTERS.A, cpu); 16 | case 0xa0: 17 | return and(REGISTERS.B, cpu); 18 | case 0xa1: 19 | return and(REGISTERS.C, cpu); 20 | case 0xa2: 21 | return and(REGISTERS.D, cpu); 22 | case 0xa3: 23 | return and(REGISTERS.E, cpu); 24 | case 0xa4: 25 | return and(REGISTERS.H, cpu); 26 | case 0xa5: 27 | return and(REGISTERS.L, cpu); 28 | case 0xa6: 29 | return andHL(cpu); 30 | case 0xe6: 31 | return and(cpu); 32 | default: 33 | return false; 34 | } 35 | } 36 | 37 | 38 | private boolean and(REGISTERS reg, CPU cpu) { 39 | 40 | int reg1 = cpu.readRegister(REGISTERS.A); 41 | int reg2 = cpu.readRegister(reg); 42 | 43 | cpu.writeRegister(REGISTERS.A, applyAnd(reg1, reg2, cpu)); 44 | cpu.addCycles(); 45 | return true; 46 | } 47 | 48 | private boolean and(CPU cpu) { 49 | int val1 = cpu.readRegister(REGISTERS.A); 50 | int val2 = cpu.readPC(); 51 | 52 | cpu.writeRegister(REGISTERS.A, applyAnd(val1, val2, cpu)); 53 | cpu.addCycles(2); 54 | return true; 55 | } 56 | 57 | boolean andHL(CPU cpu) { 58 | int val1 = cpu.readRegister(REGISTERS.A); 59 | int val2 = cpu.readAddress(cpu.readWordRegister(RegisterPairs.HL)); 60 | 61 | cpu.writeRegister(REGISTERS.A, applyAnd(val1, val2, cpu)); 62 | cpu.addCycles(2); 63 | return true; 64 | } 65 | 66 | 67 | int applyAnd(int val1, int val2, CPU cpu) { 68 | int result = (val1 & 0xff) & (val2 & 0xff); 69 | cpu.setFlag(FLAGS.Z, result == 0); 70 | 71 | cpu.setFlag(FLAGS.H, true); 72 | cpu.setFlag(FLAGS.C, false); 73 | cpu.setFlag(FLAGS.N, false); 74 | return result; 75 | } 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Return.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.instructions.jumpconditions.JumpConditions; 5 | 6 | public class Return implements Instruction { 7 | @Override 8 | public boolean execute(int opcode, CPU cpu) { 9 | switch (opcode) { 10 | case 0xc9: 11 | return ret(cpu); 12 | case 0xd9: 13 | return reti(cpu); 14 | case 0xc0: 15 | return ret(JumpConditions.NZ, cpu); 16 | case 0xc8: 17 | return ret(JumpConditions.Z, cpu); 18 | case 0xd0: 19 | return ret(JumpConditions.NC, cpu); 20 | case 0xd8: 21 | return ret(JumpConditions.C, cpu); 22 | case 0xc7: 23 | return rst(0x0, cpu); 24 | case 0xcf: 25 | return rst(0x08, cpu); 26 | case 0xd7: 27 | return rst(0x10, cpu); 28 | case 0xdf: 29 | return rst(0x18, cpu); 30 | case 0xe7: 31 | return rst(0x20, cpu); 32 | case 0xef: 33 | return rst(0x28, cpu); 34 | case 0xf7: 35 | return rst(0x30, cpu); 36 | case 0xff: 37 | return rst(0x38, cpu); 38 | default: 39 | return false; 40 | } 41 | } 42 | 43 | private void applyRet(CPU cpu) { 44 | int lowByte = cpu.popSP(); 45 | int highByte = cpu.popSP(); 46 | 47 | cpu.setPC((highByte << 8) | lowByte); 48 | 49 | } 50 | 51 | private boolean ret(CPU cpu) { 52 | applyRet(cpu); 53 | cpu.addCycles(4); 54 | return true; 55 | } 56 | 57 | private boolean ret(JumpConditions condition, CPU cpu) { 58 | if (condition.test(cpu)) { 59 | applyRet(cpu); 60 | cpu.addCycles(5); 61 | } else { 62 | cpu.addCycles(2); 63 | } 64 | return true; 65 | } 66 | 67 | private boolean reti(CPU cpu) { 68 | applyRet(cpu); 69 | cpu.enableInterrupts(); 70 | cpu.addCycles(4); 71 | return true; 72 | } 73 | 74 | 75 | private boolean rst(int lowP, CPU cpu) { 76 | cpu.pushSP(cpu.getPC() >>> 8); 77 | cpu.pushSP(cpu.getPC() & 0xff); 78 | cpu.setPC(lowP); 79 | cpu.addCycles(4); 80 | return true; 81 | } 82 | 83 | } 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Dec.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | public class Dec implements Instruction { 9 | @Override 10 | public boolean execute(int opcode, CPU cpu) { 11 | switch (opcode) { 12 | case 0x3d: 13 | return dec(REGISTERS.A, cpu); 14 | case 0x05: 15 | return dec(REGISTERS.B, cpu); 16 | case 0x0d: 17 | return dec(REGISTERS.C, cpu); 18 | case 0x15: 19 | return dec(REGISTERS.D, cpu); 20 | case 0x1d: 21 | return dec(REGISTERS.E, cpu); 22 | case 0x25: 23 | return dec(REGISTERS.H, cpu); 24 | case 0x2d: 25 | return dec(REGISTERS.L, cpu); 26 | case 0x35: 27 | return decHL(cpu); 28 | case 0x0b: 29 | return dec16(RegisterPairs.BC, cpu); 30 | case 0x1b: 31 | return dec16(RegisterPairs.DE, cpu); 32 | case 0x2b: 33 | return dec16(RegisterPairs.HL, cpu); 34 | case 0x3b: 35 | return dec16SP(cpu); 36 | default: 37 | return false; 38 | } 39 | } 40 | 41 | 42 | private boolean dec(REGISTERS reg, CPU cpu) { 43 | int value = cpu.readRegister(reg); 44 | 45 | cpu.writeRegister(reg, applyDec(value, cpu)); 46 | cpu.addCycles(); 47 | return true; 48 | } 49 | 50 | private boolean decHL(CPU cpu) { 51 | int address = cpu.readWordRegister(RegisterPairs.HL); 52 | int value = cpu.readAddress(address); 53 | 54 | cpu.writeAddress(address, applyDec(value, cpu)); 55 | cpu.addCycles(3); 56 | return true; 57 | } 58 | 59 | private boolean dec16(RegisterPairs pair, CPU cpu) { 60 | 61 | cpu.writeWordRegister(pair, cpu.readWordRegister(pair) - 1); 62 | cpu.addCycles(2); 63 | return true; 64 | } 65 | 66 | private boolean dec16SP(CPU cpu) { 67 | cpu.setSP(cpu.getSP() - 1); 68 | cpu.addCycles(2); 69 | return true; 70 | } 71 | 72 | private int applyDec(int value, CPU cpu) { 73 | int result = value - 0x01; 74 | cpu.setFlag(FLAGS.Z, result == 0); 75 | cpu.setFlag(FLAGS.H, (value & 0xf) < 0x01); 76 | cpu.setFlag(FLAGS.N, true); 77 | 78 | return result; 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cartridge/types/CartridgeType.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cartridge.types; 2 | 3 | import JavaBoy.memory.MemorySlot; 4 | 5 | public abstract class CartridgeType implements MemorySlot { 6 | 7 | protected boolean bootRomMapped = true; 8 | protected int[] bootRom = new int[]{ 9 | 0x31, 0xfe, 0xff, 0xaf, 0x21, 0xff, 0x9f, 0x32, 0xcb, 0x7c, 0x20, 10 | 0xfb, 0x21, 0x26, 0xff, 0xe, 11 | 0x11, 0x3e, 0x80, 0x32, 0xe2, 0xc, 0x3e, 0xf3, 0xe2, 0x32, 0x3e, 12 | 0x77, 0x77, 0x3e, 0xfc, 0xe0, 13 | 0x47, 0x11, 0x4, 0x1, 0x21, 0x10, 0x80, 0x1a, 0xcd, 0x95, 0x0, 14 | 0xcd, 0x96, 0x0, 0x13, 0x7b, 15 | 0xfe, 0x34, 0x20, 0xf3, 0x11, 0xd8, 0x0, 0x6, 0x8, 0x1a, 0x13, 16 | 0x22, 0x23, 0x5, 0x20, 0xf9, 17 | 0x3e, 0x19, 0xea, 0x10, 0x99, 0x21, 0x2f, 0x99, 0xe, 0xc, 0x3d, 18 | 0x28, 0x8, 0x32, 0xd, 0x20, 19 | 0xf9, 0x2e, 0xf, 0x18, 0xf3, 0x67, 0x3e, 0x64, 0x57, 0xe0, 0x42, 20 | 0x3e, 0x91, 0xe0, 0x40, 0x4, 21 | 0x1e, 0x2, 0xe, 0xc, 0xf0, 0x44, 0xfe, 0x90, 0x20, 0xfa, 0xd, 22 | 0x20, 0xf7, 0x1d, 0x20, 0xf2, 23 | 0xe, 0x13, 0x24, 0x7c, 0x1e, 0x83, 0xfe, 0x62, 0x28, 0x6, 0x1e, 24 | 0xc1, 0xfe, 0x64, 0x20, 0x6, 25 | 0x7b, 0xe2, 0xc, 0x3e, 0x87, 0xe2, 0xf0, 0x42, 0x90, 0xe0, 0x42, 26 | 0x15, 0x20, 0xd2, 0x5, 0x20, 27 | 0x4f, 0x16, 0x20, 0x18, 0xcb, 0x4f, 0x6, 0x4, 0xc5, 0xcb, 0x11, 28 | 0x17, 0xc1, 0xcb, 0x11, 0x17, 29 | 0x5, 0x20, 0xf5, 0x22, 0x23, 0x22, 0x23, 0xc9, 0xce, 0xed, 0x66, 30 | 0x66, 0xcc, 0xd, 0x0, 0xb, 31 | 0x3, 0x73, 0x0, 0x83, 0x0, 0xc, 0x0, 0xd, 0x0, 0x8, 0x11, 0x1f, 32 | 0x88, 0x89, 0x0, 0xe, 33 | 0xdc, 0xcc, 0x6e, 0xe6, 0xdd, 0xdd, 0xd9, 0x99, 0xbb, 0xbb, 0x67, 34 | 0x63, 0x6e, 0xe, 0xec, 0xcc, 35 | 0xdd, 0xdc, 0x99, 0x9f, 0xbb, 0xb9, 0x33, 0x3e, 0x3c, 0x42, 0xb9, 36 | 0xa5, 0xb9, 0xa5, 0x42, 0x3c, 37 | 0x21, 0x4, 0x1, 0x11, 0xa8, 0x0, 0x1a, 0x13, 0xbe, 0x20, 0xfe, 38 | 0x23, 0x7d, 0xfe, 0x34, 0x20, 39 | 0xf5, 0x6, 0x19, 0x78, 0x86, 0x23, 0x5, 0x20, 0xfb, 0x86, 0x20, 40 | 0xfe, 0x3e, 0x1, 0xe0, 0x50, 41 | }; 42 | protected byte[] data; 43 | 44 | CartridgeType(byte[] data) { 45 | this.data = data; 46 | } 47 | 48 | public boolean isBootRomMapped() { 49 | return bootRomMapped; 50 | } 51 | 52 | public void setBootRomMapped(boolean bootRomMapped) { 53 | this.bootRomMapped = bootRomMapped; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Jump.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.instructions.jumpconditions.JumpConditions; 5 | import JavaBoy.cpu.registers.RegisterPairs; 6 | 7 | public class Jump implements Instruction { 8 | 9 | @Override 10 | public boolean execute(int opcode, CPU cpu) { 11 | switch (opcode) { 12 | case 0xc3: 13 | return jp(cpu); 14 | case 0xc2: 15 | return jp(JumpConditions.NZ, cpu); 16 | case 0xca: 17 | return jp(JumpConditions.Z, cpu); 18 | case 0xd2: 19 | return jp(JumpConditions.NC, cpu); 20 | case 0xda: 21 | return jp(JumpConditions.C, cpu); 22 | case 0x18: 23 | return jr(cpu); 24 | case 0x20: 25 | return jr(JumpConditions.NZ, cpu); 26 | case 0x28: 27 | return jr(JumpConditions.Z, cpu); 28 | case 0x30: 29 | return jr(JumpConditions.NC, cpu); 30 | case 0x38: 31 | return jr(JumpConditions.C, cpu); 32 | case 0xe9: 33 | return jpHL(cpu); 34 | default: 35 | return false; 36 | 37 | } 38 | } 39 | 40 | private boolean jp(CPU cpu) { 41 | int word = cpu.readWordPC(); 42 | cpu.setPC(word); 43 | cpu.addCycles(4); 44 | return true; 45 | } 46 | 47 | private boolean jp(JumpConditions condition, CPU cpu) { 48 | if (condition.test(cpu)) { 49 | return jp(cpu); 50 | } else { 51 | cpu.setPC(cpu.getPC() + 2); 52 | cpu.addCycles(3); 53 | return true; 54 | } 55 | } 56 | 57 | private boolean jpHL(CPU cpu) { 58 | int value = cpu.readWordRegister(RegisterPairs.HL); 59 | cpu.setPC(value); 60 | cpu.addCycles(); 61 | return true; 62 | } 63 | 64 | private boolean jr(CPU cpu) { 65 | int value = cpu.readPC(); 66 | applyJR(value, cpu); 67 | cpu.addCycles(3); 68 | return true; 69 | } 70 | 71 | private boolean jr(JumpConditions condition, CPU cpu) { 72 | if (condition.test(cpu)) { 73 | return jr(cpu); 74 | } else { 75 | cpu.setPC(cpu.getPC() + 1); 76 | cpu.addCycles(2); 77 | return true; 78 | } 79 | } 80 | 81 | private void applyJR(int value, CPU cpu) { 82 | byte signedByte = (byte) value; 83 | cpu.setPC(cpu.getPC() + signedByte); 84 | 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Inc.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | import static JavaBoy.utils.ArithmeticUtils.isHalfCarry8; 9 | 10 | public class Inc implements Instruction { 11 | 12 | @Override 13 | public boolean execute(int opcode, CPU cpu) { 14 | switch (opcode) { 15 | case 0x3c: 16 | return inc(REGISTERS.A, cpu); 17 | case 0x04: 18 | return inc(REGISTERS.B, cpu); 19 | case 0x0c: 20 | return inc(REGISTERS.C, cpu); 21 | case 0x14: 22 | return inc(REGISTERS.D, cpu); 23 | case 0x1c: 24 | return inc(REGISTERS.E, cpu); 25 | case 0x24: 26 | return inc(REGISTERS.H, cpu); 27 | case 0x2c: 28 | return inc(REGISTERS.L, cpu); 29 | case 0x34: 30 | return incHL(RegisterPairs.HL, cpu); 31 | //16 bit increment instructions 32 | case 0x03: 33 | return inc16(RegisterPairs.BC, cpu); 34 | case 0x13: 35 | return inc16(RegisterPairs.DE, cpu); 36 | case 0x23: 37 | return inc16(RegisterPairs.HL, cpu); 38 | case 0x33: 39 | return inc16SP(cpu); 40 | 41 | 42 | default: 43 | return false; 44 | } 45 | } 46 | 47 | private boolean inc(REGISTERS reg, CPU cpu) { 48 | int value = cpu.readRegister(reg); 49 | 50 | cpu.writeRegister(reg, applyInc(value, cpu)); 51 | cpu.addCycles(); 52 | return true; 53 | } 54 | 55 | private boolean incHL(RegisterPairs pair, CPU cpu) { 56 | int address = cpu.readWordRegister(pair); 57 | int value = cpu.readAddress(address); 58 | cpu.writeAddress(address, applyInc(value, cpu)); 59 | cpu.addCycles(3); 60 | return true; 61 | 62 | } 63 | 64 | private boolean inc16(RegisterPairs pair, CPU cpu) { 65 | 66 | cpu.writeWordRegister(pair, cpu.readWordRegister(pair) + 1); 67 | cpu.addCycles(4); 68 | return true; 69 | } 70 | 71 | private boolean inc16SP(CPU cpu) { 72 | cpu.setSP(cpu.getSP() + 1); 73 | cpu.addCycles(2); 74 | return true; 75 | } 76 | 77 | 78 | private int applyInc(int val, CPU cpu) { 79 | int result = (val + 1) & 0xff; 80 | cpu.setFlag(FLAGS.Z, result == 0); 81 | cpu.setFlag(FLAGS.H, isHalfCarry8(val, 1)); 82 | cpu.setFlag(FLAGS.N, false); 83 | 84 | return result; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/interrupts/InterruptManager.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.interrupts; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.memory.MemorySlot; 5 | 6 | import static JavaBoy.utils.BitUtils.*; 7 | 8 | public class InterruptManager implements MemorySlot { 9 | 10 | private int interruptEnable = 0; 11 | private int interruptRequests = 0xe0; 12 | private boolean ime = true; 13 | private boolean imeRequest = false; 14 | 15 | public boolean handleInterrupts(CPU cpu) { 16 | 17 | if (imeRequest) { 18 | this.ime = true; 19 | imeRequest = false; 20 | return false; 21 | } 22 | 23 | if (ime) { 24 | return tryHandle(cpu, Interrupts.V_BLANK) || 25 | tryHandle(cpu, Interrupts.LCD_STAT) || 26 | tryHandle(cpu, Interrupts.TIMER) || 27 | tryHandle(cpu, Interrupts.SERIAL) || 28 | tryHandle(cpu, Interrupts.JOYPAD); 29 | } 30 | 31 | return false; 32 | } 33 | 34 | public boolean tryHandle(CPU cpu, Interrupts interrupt) { 35 | if (getNthBit(interrupt.getBitIndex(), interruptRequests) != 1) 36 | return false; 37 | if (getNthBit(interrupt.getBitIndex(), interruptEnable) != 1) 38 | return false; 39 | ime = false; 40 | 41 | cpu.pushSP(getMsb(cpu.getPC())); 42 | cpu.pushSP(getLsb(cpu.getPC())); 43 | cpu.setPC(interrupt.getInterruptVector()); 44 | unrequestInterrupt(interrupt); 45 | cpu.addCycles(5); 46 | return true; 47 | } 48 | 49 | public boolean hasServiceableInterrupts() { 50 | return ((interruptRequests & 0x1f) & (interruptEnable & 0x1f)) != 0; 51 | } 52 | 53 | public boolean isMasterEnabled() { 54 | return ime; 55 | } 56 | 57 | public void enableInterrupts() { 58 | imeRequest = true; 59 | } 60 | 61 | public void disableInterrupts() { 62 | this.ime = false; 63 | } 64 | 65 | public void requestInterrupt(Interrupts interrupt) { 66 | interruptRequests = 0xe0 | setNthBit(interrupt.getBitIndex(), 1, 67 | interruptRequests); 68 | } 69 | 70 | private void unrequestInterrupt(Interrupts interrupts) { 71 | interruptRequests = 0xe0 | setNthBit(interrupts.getBitIndex(), 0, 72 | interruptRequests); 73 | } 74 | 75 | @Override 76 | public int getByte(int address) { 77 | if (address == 0xffff) { 78 | return this.interruptEnable & 0xff; 79 | } else { 80 | return this.interruptRequests & 0xff; 81 | } 82 | } 83 | 84 | @Override 85 | public void setByte(int address, int value) { 86 | if (address == 0xffff) { 87 | this.interruptEnable = value & 0xff; 88 | } else { 89 | this.interruptRequests = 0xe0 | (value & 0xff); 90 | } 91 | } 92 | 93 | @Override 94 | public boolean hasAddressInSlot(int address) { 95 | return address == 0xffff || address == 0xff0f; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/LCDStat.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video; 2 | 3 | import JavaBoy.memory.MemorySlot; 4 | 5 | import static JavaBoy.utils.BitUtils.getNthBit; 6 | 7 | public class LCDStat implements MemorySlot { 8 | 9 | int data = 0; 10 | private boolean coincidenceInterrupt = false; 11 | private boolean OAMInterrupt = false; 12 | private boolean vBlankInterrupt = false; 13 | private boolean hBlankInterrupt = false; 14 | private boolean coincidenceFlag = false; 15 | private Modes modeFlag = Modes.H_BLANK; 16 | 17 | @Override 18 | public int getByte(int address) { 19 | return (coincidenceInterrupt ? 1 << 6 : 0) 20 | | (OAMInterrupt ? 1 << 5 : 0) 21 | | (vBlankInterrupt ? 1 << 4 : 0) 22 | | (hBlankInterrupt ? 1 << 3 : 0) 23 | | (coincidenceFlag ? 1 << 2 : 0) 24 | | (modeFlag.getValue()); 25 | } 26 | 27 | @Override 28 | public void setByte(int address, int value) { 29 | value &= 0x7f; 30 | coincidenceInterrupt = getNthBit(6, value) == 1; 31 | OAMInterrupt = getNthBit(5, value) == 1; 32 | vBlankInterrupt = getNthBit(4, value) == 1; 33 | hBlankInterrupt = getNthBit(3, value) == 1; 34 | } 35 | 36 | @Override 37 | public boolean hasAddressInSlot(int address) { 38 | return address == 0xff41; 39 | } 40 | 41 | public boolean isCoincidenceInterrupt() { 42 | return coincidenceInterrupt; 43 | } 44 | 45 | public void setCoincidenceInterrupt(boolean coincidenceInterrupt) { 46 | this.coincidenceInterrupt = coincidenceInterrupt; 47 | } 48 | 49 | public Modes getMode() { 50 | return this.modeFlag; 51 | } 52 | 53 | public void setMode(Modes mode) { 54 | this.modeFlag = mode; 55 | } 56 | 57 | public boolean isOAMInterrupt() { 58 | return OAMInterrupt; 59 | } 60 | 61 | public void setOAMInterrupt(boolean OAMInterrupt) { 62 | this.OAMInterrupt = OAMInterrupt; 63 | } 64 | 65 | public boolean isvBlankInterrupt() { 66 | return vBlankInterrupt; 67 | } 68 | 69 | public void setvBlankInterrupt(boolean vBlankInterrupt) { 70 | this.vBlankInterrupt = vBlankInterrupt; 71 | } 72 | 73 | public boolean ishBlankInterrupt() { 74 | return hBlankInterrupt; 75 | } 76 | 77 | public void sethBlankInterrupt(boolean hBlankInterrupt) { 78 | this.hBlankInterrupt = hBlankInterrupt; 79 | } 80 | 81 | public boolean isCoincidenceFlag() { 82 | return coincidenceFlag; 83 | } 84 | 85 | public void setCoincidenceFlag(boolean coincidenceFlag) { 86 | this.coincidenceFlag = coincidenceFlag; 87 | } 88 | 89 | public enum Modes { 90 | H_BLANK(0), V_BLANK(1), OAM(2), Transfer(3); 91 | 92 | private final int value; 93 | 94 | Modes(int value) { 95 | this.value = value; 96 | } 97 | 98 | public int getValue() { 99 | return value; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/input/Joypad.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.input; 2 | 3 | import JavaBoy.cpu.interrupts.InterruptManager; 4 | import JavaBoy.cpu.interrupts.Interrupts; 5 | import JavaBoy.memory.MemorySlot; 6 | import JavaBoy.utils.BitUtils; 7 | 8 | public class Joypad implements MemorySlot { 9 | 10 | final InterruptManager manager; 11 | private int directionBits = 0xf; 12 | private int buttonBits = 0xf; 13 | private int directionSelect = 1; 14 | private int buttonSelect = 1; 15 | 16 | 17 | public Joypad(InterruptManager interruptManager) { 18 | this.manager = interruptManager; 19 | } 20 | 21 | @Override 22 | public int getByte(int address) { 23 | return (2 << 6) | (buttonSelect << 5) | 24 | (directionSelect << 4) | 25 | (directionSelect == 0 ? directionBits : buttonBits); 26 | } 27 | 28 | @Override 29 | public void setByte(int address, int value) { 30 | buttonSelect = BitUtils.getNthBit(5, value); 31 | directionSelect = BitUtils.getNthBit(4, value); 32 | } 33 | 34 | public void buttonPressed(Buttons button) { 35 | System.out.println(button + " was pressed"); 36 | int result = BitUtils.setNthBit(button.getIndex(), 0, 37 | button.isDirectional() ? 38 | directionBits : buttonBits); 39 | 40 | if (button.isDirectional()) { 41 | directionBits = result; 42 | } else { 43 | buttonBits = result; 44 | } 45 | 46 | if ((button.isDirectional && buttonSelect == 0) 47 | || (!button.isDirectional && directionBits == 0)) 48 | manager.requestInterrupt(Interrupts.JOYPAD); 49 | } 50 | 51 | public void buttonReleased(Buttons button) { 52 | System.out.println(button + " was released"); 53 | int result = BitUtils.setNthBit(button.getIndex(), 1, 54 | button.isDirectional() ? 55 | directionBits : buttonBits); 56 | if (button.isDirectional()) { 57 | directionBits = result; 58 | } else { 59 | buttonBits = result; 60 | } 61 | } 62 | 63 | @Override 64 | public boolean hasAddressInSlot(int address) { 65 | return address == 0xff00; 66 | } 67 | 68 | public enum Buttons { 69 | BUTTON_A(0), 70 | BUTTON_B(1), 71 | SELECT(2), 72 | START(3), 73 | RIGHT(0, true), 74 | LEFT(1, true), 75 | DOWN(3, true), 76 | UP(2, true); 77 | 78 | private final int index; 79 | private final boolean isDirectional; 80 | 81 | Buttons(int index, boolean isDirectional) { 82 | this.index = index; 83 | this.isDirectional = isDirectional; 84 | } 85 | 86 | Buttons(int index) { 87 | this(index, false); 88 | } 89 | 90 | public boolean isDirectional() { 91 | return isDirectional; 92 | } 93 | 94 | public int getIndex() { 95 | return this.index; 96 | } 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/Palette.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video; 2 | 3 | import JavaBoy.memory.MemorySlot; 4 | 5 | public class Palette implements MemorySlot { 6 | 7 | int bgb = 0; 8 | int objPal1 = 0; 9 | int objPal2 = 0; 10 | 11 | private GreyShades getShade(int colourIndex) { 12 | colourIndex &= 0x03; 13 | switch (colourIndex){ 14 | case 1: 15 | return GreyShades.LIGHT_GREY; 16 | case 2: 17 | return GreyShades.DARK_GREY; 18 | case 3: 19 | return GreyShades.BLACK; 20 | 21 | } 22 | return GreyShades.WHITE; 23 | } 24 | 25 | public GreyShades getPaletteShade(int colour, Palettes palette) { 26 | switch (palette) { 27 | case OBP0: 28 | return getObjPal1(colour); 29 | case OBP1: 30 | return getObjPal2(colour); 31 | default: 32 | return getBGP(colour); 33 | } 34 | } 35 | 36 | private GreyShades getBGP(int colourNum) { 37 | 38 | switch (colourNum) { 39 | case 3: 40 | return getShade(bgb >>> 6); 41 | case 2: 42 | return getShade(bgb >>> 4); 43 | case 1: 44 | return getShade(bgb >>> 2); 45 | case 0: 46 | return getShade(bgb); 47 | } 48 | 49 | return null; 50 | 51 | } 52 | 53 | private GreyShades getObjPal1(int colourNum) { 54 | 55 | switch (colourNum) { 56 | case 3: 57 | return getShade(objPal1 >>> 6); 58 | case 2: 59 | return getShade(objPal1 >>> 4); 60 | case 1: 61 | return getShade(objPal1 >>> 2); 62 | } 63 | return GreyShades.TRANSPARENT; 64 | } 65 | 66 | private GreyShades getObjPal2(int colourNum) { 67 | 68 | switch (colourNum) { 69 | case 3: 70 | return getShade(objPal2 >>> 6); 71 | case 2: 72 | return getShade(objPal2 >>> 4); 73 | case 1: 74 | return getShade(objPal2 >>> 2); 75 | } 76 | return GreyShades.TRANSPARENT; 77 | } 78 | 79 | @Override 80 | public int getByte(int address) { 81 | if (address == 0xff47) 82 | return bgb; 83 | if (address == 0xff48) 84 | return objPal1; 85 | 86 | return objPal2; 87 | } 88 | 89 | @Override 90 | public void setByte(int address, int value) { 91 | if (address == 0xff47) 92 | bgb = value; 93 | else if (address == 0xff48) 94 | objPal1 = value; 95 | else if (address == 0xff49) 96 | objPal2 = value; 97 | } 98 | 99 | @Override 100 | public boolean hasAddressInSlot(int address) { 101 | switch (address) { 102 | case 0xff47: 103 | case 0xff48: 104 | case 0xff49: 105 | return true; 106 | default: 107 | return false; 108 | } 109 | } 110 | 111 | public enum GreyShades { 112 | WHITE, LIGHT_GREY, DARK_GREY, BLACK, TRANSPARENT 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Rotate.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | 7 | public class Rotate implements Instruction { 8 | @Override 9 | public boolean execute(int opcode, CPU cpu) { 10 | switch (opcode) { 11 | case 0x07: 12 | return rlca(cpu); 13 | case 0x17: 14 | return rla(cpu); 15 | case 0x0f: 16 | return rrca(cpu); 17 | case 0x1f: 18 | return rra(cpu); 19 | 20 | default: 21 | return false; 22 | } 23 | } 24 | 25 | 26 | private boolean rlca(CPU cpu) { 27 | 28 | int bits = cpu.readRegister(REGISTERS.A); 29 | cpu.writeRegister(REGISTERS.A, applyRotateLC(bits, cpu)); 30 | 31 | cpu.setFlag(FLAGS.H, false); 32 | cpu.setFlag(FLAGS.N, false); 33 | cpu.setFlag(FLAGS.Z, false); 34 | cpu.addCycles(); 35 | return true; 36 | } 37 | 38 | private int applyRotateLC(int val, CPU cpu) { 39 | int bits = val; 40 | int msb = bits >>> 7; 41 | cpu.setFlag(FLAGS.C, msb == 1); 42 | bits = (bits << 1) & 0xff; 43 | bits = bits | msb; 44 | return bits; 45 | } 46 | 47 | private int applyRotateL(int val, CPU cpu) { 48 | int bits = val; 49 | int msb = bits >>> 7; 50 | bits = (bits << 1) & 0xff; 51 | bits = bits | cpu.getFlag(FLAGS.C); 52 | cpu.setFlag(FLAGS.C, msb == 1); 53 | return bits; 54 | } 55 | 56 | 57 | private boolean rla(CPU cpu) { 58 | int bits = cpu.readRegister(REGISTERS.A); 59 | cpu.writeRegister(REGISTERS.A, applyRotateL(bits, cpu)); 60 | cpu.setFlag(FLAGS.H, false); 61 | cpu.setFlag(FLAGS.N, false); 62 | cpu.setFlag(FLAGS.Z, false); 63 | cpu.addCycles(); 64 | return true; 65 | } 66 | 67 | private int applyRotateRC(int val, CPU cpu) { 68 | int bits = val; 69 | int lsb = bits & 0x1; 70 | bits = (bits >>> 1) & 0xff; 71 | bits = (lsb << 7) | bits; 72 | 73 | cpu.setFlag(FLAGS.C, lsb == 1); 74 | return bits; 75 | } 76 | 77 | private boolean rrca(CPU cpu) { 78 | int bits = cpu.readRegister(REGISTERS.A); 79 | 80 | cpu.writeRegister(REGISTERS.A, applyRotateRC(bits, cpu)); 81 | cpu.setFlag(FLAGS.H, false); 82 | cpu.setFlag(FLAGS.N, false); 83 | cpu.setFlag(FLAGS.Z, false); 84 | cpu.addCycles(2); 85 | return true; 86 | } 87 | 88 | private int applyRotateR(int val, CPU cpu) { 89 | int bits = val; 90 | int lsb = bits & 0x1; 91 | bits = (bits >>> 1) & 0xff; 92 | bits = (cpu.getFlag(FLAGS.C) << 7) | bits; 93 | cpu.setFlag(FLAGS.C, lsb == 1); 94 | return bits; 95 | } 96 | 97 | private boolean rra(CPU cpu) { 98 | int bits = cpu.readRegister(REGISTERS.A); 99 | cpu.writeRegister(REGISTERS.A, applyRotateR(bits, cpu)); 100 | cpu.setFlag(FLAGS.H, false); 101 | cpu.setFlag(FLAGS.N, false); 102 | cpu.setFlag(FLAGS.Z, false); 103 | cpu.addCycles(); 104 | return true; 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/pixelpipeline/DmgFifo.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video.pixelpipeline; 2 | 3 | import JavaBoy.video.Palette; 4 | import JavaBoy.video.Palettes; 5 | 6 | public class DmgFifo implements PixelFIFO { 7 | final private Palette palette; 8 | final private ArrayQueue pixels; 9 | boolean poppingEnabled = true; 10 | private int size = 0; 11 | 12 | public DmgFifo(Palette pal) { 13 | this.palette = pal; 14 | this.pixels = new ArrayQueue<>(16); 15 | for (int idx = 0; idx < 16; ++idx) { 16 | pixels.add(new Pixel()); 17 | } 18 | } 19 | 20 | @Override 21 | public void push(Pixel[] pixels) { 22 | for (int idx = 0; idx < 8; ++idx) { 23 | this.pixels.getAt(size + idx).setAboveBG(pixels[idx].getAboveBG()); 24 | this.pixels.getAt( size + idx).setPalette(pixels[idx].getPalette()); 25 | this.pixels.getAt(size + idx).setColour(pixels[idx].getColour()); 26 | } 27 | 28 | size += 8; 29 | } 30 | 31 | @Override 32 | public Palette.GreyShades getPixel() { 33 | var pixel = pixels.poll(); 34 | --size; 35 | return calculatePixel(pixel.getColour(), pixel.getPalette()); 36 | } 37 | 38 | @Override 39 | public void clear() { 40 | size = 0; 41 | pixels.clear(); 42 | } 43 | 44 | @Override 45 | public boolean canPop() { 46 | return size != 0 && this.poppingEnabled; 47 | } 48 | 49 | @Override 50 | public void disablePopping() { 51 | this.poppingEnabled = false; 52 | } 53 | 54 | @Override 55 | public void enablePopping() { 56 | this.poppingEnabled = true; 57 | } 58 | 59 | @Override 60 | public boolean canPush() { 61 | return size <= 8; 62 | } 63 | 64 | @Override 65 | public boolean peekIsAboveBG() { 66 | return this.pixels.peek().getAboveBG(); 67 | } 68 | 69 | @Override 70 | public int peekColour() { 71 | return this.pixels.peek().getColour(); 72 | } 73 | 74 | @Override 75 | public Palettes peekPalette() { 76 | return this.pixels.peek().getPalette(); 77 | } 78 | 79 | private Palette.GreyShades calculatePixel(int pixel, Palettes palette) { 80 | return this.palette.getPaletteShade(pixel, palette); 81 | } 82 | 83 | @Override 84 | public void pushOverlay(Pixel[] overlay) { 85 | for (int idx = this.size; idx < 8; ++idx) { 86 | ++size; 87 | this.pixels.getAt(idx).setAboveBG(false); 88 | this.pixels.getAt(idx).setPalette(Palettes.OBP0); 89 | this.pixels.getAt(idx).setColour(0); 90 | } 91 | 92 | for (int idx = 0; idx < 8; ++idx) { 93 | Palette.GreyShades existingShade = calculatePixel( 94 | pixels.getAt(idx).getColour(), 95 | pixels.getAt(idx).getPalette()); 96 | Palette.GreyShades incomingShade = palette.getPaletteShade( 97 | overlay[idx].getColour(), 98 | overlay[idx].getPalette()); 99 | 100 | if ((incomingShade != Palette.GreyShades.WHITE && 101 | existingShade == Palette.GreyShades.WHITE) || existingShade == Palette.GreyShades.TRANSPARENT) { 102 | this.pixels.getAt(idx).setColour(overlay[idx].getColour()); 103 | this.pixels.getAt(idx).setPalette(overlay[idx].getPalette()); 104 | this.pixels.getAt(idx).setAboveBG(overlay[idx].getAboveBG()); 105 | } 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/Vram.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video; 2 | 3 | import JavaBoy.memory.MemorySlot; 4 | 5 | import static JavaBoy.utils.BitUtils.getNthBit; 6 | 7 | public class Vram implements MemorySlot { 8 | 9 | private final int[] pixels = new int[8]; 10 | int[] data = new int[(0x97ff - 0x8000) + 1]; 11 | int[] bgMap1 = new int[(0x9bff - 0x9800) + 1]; 12 | int[] bgMap2 = new int[(0x9fff - 0x9c00) + 1]; 13 | 14 | /** 15 | * @param tileNum The tile number in the tile data table 16 | * @param line The tile line to retrieve 17 | * @param addressingMode The addressing being used to access the tile 18 | * data table 19 | * @return A volatile int [] 20 | */ 21 | public int[] getTile(int tileNum, int line, 22 | LCDC.AddressingModes addressingMode) { 23 | int block; 24 | if (addressingMode == LCDC.AddressingModes.M8000) { 25 | block = 0x8000; 26 | } else { 27 | if (tileNum > 127) { 28 | tileNum -= 128; 29 | block = 0x8800; 30 | } else { 31 | block = 0x9000; 32 | } 33 | } 34 | int tileStart = tileNum * 16; 35 | int lineStart = tileStart + (line * 2); 36 | int byte1 = data[(block + lineStart) - 0x8000]; 37 | int byte2 = data[(block + (lineStart + 1)) - 0x8000]; 38 | for (int a = 7; a >= 0; --a) { 39 | int palette = (getNthBit(a, byte2) << 1) | getNthBit(a, byte1); 40 | pixels[7 - a] = palette; 41 | } 42 | return pixels; 43 | } 44 | 45 | /** 46 | * @param x The BG pixel's column 47 | * @param y The BG pixel's row 48 | * @param map Dictates which background map to use 49 | * @param addressingMode The addressing mode being used to access the tile 50 | * data table 51 | * @return A volatile int[] 52 | */ 53 | public int[] getTileLineBG(int x, 54 | int y, 55 | BGMaps map, 56 | LCDC.AddressingModes addressingMode) { 57 | 58 | // If the x or y coordinates exceed the bounds of the map, 59 | // wrap to the opposite side on the respective coordinate. 60 | if (x > 255) x -= 256; 61 | if (y > 255) y -= 256; 62 | 63 | // Determining which slot in the map the pixel lies 64 | int mapX = (255 - (255 - x)) / 8; 65 | int mapY = (255 - (255 - y)) / 8; 66 | 67 | int tile = 0; 68 | switch (map) { 69 | case MAP1: 70 | tile = bgMap1[(mapY * 32) + mapX]; 71 | break; 72 | case MAP2: 73 | tile = bgMap2[(mapY * 32) + mapX]; 74 | break; 75 | } 76 | 77 | 78 | return getTile(tile, y % 8, addressingMode); 79 | } 80 | 81 | @Override 82 | public int getByte(int address) { 83 | if (address <= 0x97ff) 84 | return data[address - 0x8000]; 85 | else if (address <= 0x9bff) 86 | return bgMap1[address - 0x9800]; 87 | else 88 | return bgMap2[address - 0x9c00]; 89 | } 90 | 91 | @Override 92 | public void setByte(int address, int value) { 93 | value &= 0xff; 94 | if (address <= 0x97ff) 95 | data[address - 0x8000] = value; 96 | else if (address <= 0x9bff) 97 | bgMap1[address - 0x9800] = value; 98 | else 99 | bgMap2[address - 0x9c00] = value; 100 | } 101 | 102 | @Override 103 | public boolean hasAddressInSlot(int address) { 104 | return address >= 0x8000 && address <= 0x9fff; 105 | } 106 | 107 | public enum BGMaps { 108 | MAP1, MAP2 109 | } 110 | 111 | 112 | } 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Created by https://www.toptal.com/developers/gitignore/api/intellij,java,gradle 4 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,java,gradle 5 | 6 | ### Intellij ### 7 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 8 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 9 | 10 | # User-specific stuff 11 | .idea/**/workspace.xml 12 | .idea/**/tasks.xml 13 | .idea/**/usage.statistics.xml 14 | .idea/**/dictionaries 15 | .idea/**/shelf 16 | 17 | # Generated files 18 | .idea/**/contentModel.xml 19 | 20 | # Sensitive or high-churn files 21 | .idea/**/dataSources/ 22 | .idea/**/dataSources.ids 23 | .idea/**/dataSources.local.xml 24 | .idea/**/sqlDataSources.xml 25 | .idea/**/dynamic.xml 26 | .idea/**/uiDesigner.xml 27 | .idea/**/dbnavigator.xml 28 | 29 | # Gradle 30 | .idea/**/gradle.xml 31 | .idea/**/libraries 32 | 33 | # Gradle and Maven with auto-import 34 | # When using Gradle or Maven with auto-import, you should exclude module files, 35 | # since they will be recreated, and may cause churn. Uncomment if using 36 | # auto-import. 37 | # .idea/artifacts 38 | # .idea/compiler.xml 39 | # .idea/jarRepositories.xml 40 | # .idea/modules.xml 41 | # .idea/*.iml 42 | # .idea/modules 43 | # *.iml 44 | # *.ipr 45 | 46 | # CMake 47 | cmake-build-*/ 48 | 49 | # Mongo Explorer plugin 50 | .idea/**/mongoSettings.xml 51 | 52 | # File-based project format 53 | *.iws 54 | 55 | # IntelliJ 56 | out/ 57 | 58 | # mpeltonen/sbt-idea plugin 59 | .idea_modules/ 60 | 61 | # JIRA plugin 62 | atlassian-ide-plugin.xml 63 | 64 | # Cursive Clojure plugin 65 | .idea/replstate.xml 66 | 67 | # Crashlytics plugin (for Android Studio and IntelliJ) 68 | com_crashlytics_export_strings.xml 69 | crashlytics.properties 70 | crashlytics-build.properties 71 | fabric.properties 72 | 73 | # Editor-based Rest Client 74 | .idea/httpRequests 75 | 76 | # Android studio 3.1+ serialized cache file 77 | .idea/caches/build_file_checksums.ser 78 | 79 | ### Intellij Patch ### 80 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 81 | 82 | # *.iml 83 | # modules.xml 84 | # .idea/misc.xml 85 | # *.ipr 86 | 87 | # Sonarlint plugin 88 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 89 | .idea/**/sonarlint/ 90 | 91 | # SonarQube Plugin 92 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 93 | .idea/**/sonarIssues.xml 94 | 95 | # Markdown Navigator plugin 96 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 97 | .idea/**/markdown-navigator.xml 98 | .idea/**/markdown-navigator-enh.xml 99 | .idea/**/markdown-navigator/ 100 | 101 | # Cache file creation bug 102 | # See https://youtrack.jetbrains.com/issue/JBR-2257 103 | .idea/$CACHE_FILE$ 104 | 105 | # CodeStream plugin 106 | # https://plugins.jetbrains.com/plugin/12206-codestream 107 | .idea/codestream.xml 108 | 109 | ### Java ### 110 | # Compiled class file 111 | *.class 112 | 113 | # Log file 114 | *.log 115 | 116 | # BlueJ files 117 | *.ctxt 118 | 119 | # Mobile Tools for Java (J2ME) 120 | .mtj.tmp/ 121 | 122 | # Package Files # 123 | *.jar 124 | *.war 125 | *.nar 126 | *.ear 127 | *.zip 128 | *.tar.gz 129 | *.rar 130 | 131 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 132 | hs_err_pid* 133 | 134 | ### Gradle ### 135 | .gradle 136 | build/ 137 | bin/ 138 | 139 | # Ignore Gradle GUI config 140 | gradle-app.setting 141 | 142 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 143 | !gradle-wrapper.jar 144 | 145 | # Cache of project 146 | .gradletasknamecache 147 | 148 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 149 | # gradle/wrapper/gradle-wrapper.properties 150 | 151 | ### Gradle Patch ### 152 | **/build/ 153 | 154 | # End of https://www.toptal.com/developers/gitignore/api/intellij,java,gradle 155 | /leak.jfr 156 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Sub.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | public class Sub implements Instruction { 9 | 10 | @Override 11 | public boolean execute(int opcode, CPU cpu) { 12 | switch (opcode) { 13 | case 0x97: 14 | return sub(REGISTERS.A, cpu); 15 | case 0x90: 16 | return sub(REGISTERS.B, cpu); 17 | case 0x91: 18 | return sub(REGISTERS.C, cpu); 19 | case 0x92: 20 | return sub(REGISTERS.D, cpu); 21 | case 0x93: 22 | return sub(REGISTERS.E, cpu); 23 | case 0x94: 24 | return sub(REGISTERS.H, cpu); 25 | case 0x95: 26 | return sub(REGISTERS.L, cpu); 27 | case 0x96: 28 | return subHL(cpu); 29 | case 0xd6: 30 | return sub(cpu); 31 | case 0x9f: 32 | return sbc(REGISTERS.A, cpu); 33 | case 0x98: 34 | return sbc(REGISTERS.B, cpu); 35 | case 0x99: 36 | return sbc(REGISTERS.C, cpu); 37 | case 0x9a: 38 | return sbc(REGISTERS.D, cpu); 39 | case 0x9b: 40 | return sbc(REGISTERS.E, cpu); 41 | case 0x9c: 42 | return sbc(REGISTERS.H, cpu); 43 | case 0x9d: 44 | return sbc(REGISTERS.L, cpu); 45 | case 0x9e: 46 | return sbcHL(cpu); 47 | case 0xde: 48 | return sbc(cpu); 49 | 50 | default: 51 | return false; 52 | } 53 | } 54 | 55 | 56 | private boolean sub(REGISTERS reg2, CPU cpu) { 57 | int val1 = cpu.readRegister(REGISTERS.A); 58 | int val2 = cpu.readRegister(reg2); 59 | cpu.writeRegister(REGISTERS.A, subBytes(val1, val2, cpu)); 60 | cpu.addCycles(); 61 | return true; 62 | } 63 | 64 | private boolean sub(CPU cpu) { 65 | int val1 = cpu.readRegister(REGISTERS.A); 66 | int val2 = cpu.readPC(); 67 | cpu.writeRegister(REGISTERS.A, subBytes(val1, val2, cpu)); 68 | cpu.addCycles(2); 69 | return true; 70 | } 71 | 72 | private boolean subHL(CPU cpu) { 73 | int val1 = cpu.readRegister(REGISTERS.A); 74 | int val2 = cpu.readAddress(cpu.readWordRegister(RegisterPairs.HL)); 75 | cpu.writeRegister(REGISTERS.A, subBytes(val1, val2, cpu)); 76 | cpu.addCycles(2); 77 | return true; 78 | } 79 | 80 | 81 | private boolean sbc(REGISTERS reg2, CPU cpu) { 82 | int val1 = cpu.readRegister(REGISTERS.A); 83 | int val2 = cpu.readRegister(reg2) ; 84 | cpu.writeRegister(REGISTERS.A, subCBytes(val1, val2, cpu)); 85 | cpu.addCycles(); 86 | return true; 87 | } 88 | 89 | private boolean sbc(CPU cpu) { 90 | int val1 = cpu.readRegister(REGISTERS.A); 91 | int val2 = cpu.readPC(); 92 | cpu.writeRegister(REGISTERS.A, subCBytes(val1, val2, cpu)); 93 | cpu.addCycles(2); 94 | return true; 95 | } 96 | 97 | private int subCBytes(int val1, int val2, CPU cpu) { 98 | int carry = cpu.getFlag(FLAGS.C); 99 | int result = (val1 - val2 - carry); 100 | cpu.setFlag(FLAGS.Z, (result & 0xff) == 0); 101 | cpu.setFlag(FLAGS.N, true); 102 | cpu.setFlag(FLAGS.H, ((val1 & 0x0f) - carry) < (val2 & 0x0f)); 103 | cpu.setFlag(FLAGS.C, result < 0); 104 | 105 | return result; 106 | } 107 | 108 | private boolean sbcHL(CPU cpu) { 109 | int val1 = cpu.readRegister(REGISTERS.A); 110 | int val2 = cpu.readAddress(cpu.readWordRegister(RegisterPairs.HL)); 111 | cpu.writeRegister(REGISTERS.A, subCBytes(val1, val2, cpu)); 112 | cpu.addCycles(2); 113 | return true; 114 | } 115 | 116 | 117 | private int subBytes(int val1, int val2, CPU cpu) { 118 | int result = (val1 & 0xff) - (val2 & 0xff); 119 | cpu.setFlag(FLAGS.Z, result == 0); 120 | boolean borrowCheck = (val1 & 0xf) < (val2 & 0xf); 121 | cpu.setFlag(FLAGS.H, borrowCheck); 122 | cpu.setFlag(FLAGS.C, val1 < val2); 123 | cpu.setFlag(FLAGS.N, true); 124 | return result; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/timer/Timer.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.timer; 2 | 3 | import JavaBoy.cpu.interrupts.InterruptManager; 4 | import JavaBoy.cpu.interrupts.Interrupts; 5 | import JavaBoy.memory.MemorySlot; 6 | 7 | import static JavaBoy.utils.BitUtils.getNthBit; 8 | 9 | public class Timer implements MemorySlot { 10 | final InterruptManager interruptManager; 11 | int div = 0; 12 | int tima = 0; 13 | int tma = 0; 14 | int tac = 0; 15 | boolean didOverflow = false; 16 | int cyclesAfterOverflow = 0; 17 | int divCycles = 0; 18 | int timaCycles = 0; 19 | 20 | public Timer(InterruptManager interruptManager) { 21 | this.interruptManager = interruptManager; 22 | } 23 | 24 | public void tick() { 25 | incrementDIV(); 26 | incrementTIMA(); 27 | } 28 | 29 | public int getTIMA() { 30 | return this.tima; 31 | } 32 | 33 | private boolean isTimerEnabled() { 34 | return getNthBit(2, tac) == 1; 35 | } 36 | 37 | 38 | private void incrementDIV() { 39 | divCycles += 1; 40 | if (divCycles >= 256 / 4) { 41 | divCycles -= 256 / 4; 42 | if (div == 0xff) { 43 | div = 0x00; 44 | } else { 45 | div += 1; 46 | } 47 | } 48 | } 49 | 50 | private void incrementTIMA() { 51 | 52 | 53 | if (didOverflow && cyclesAfterOverflow < 1) { 54 | cyclesAfterOverflow += 1; 55 | tima = 0x0; 56 | return; 57 | } else if (didOverflow && cyclesAfterOverflow == 1) { 58 | tima = tma; 59 | interruptManager.requestInterrupt(Interrupts.TIMER); 60 | didOverflow = false; 61 | cyclesAfterOverflow = 0; 62 | } 63 | if (isTimerEnabled()) { 64 | timaCycles += 1; 65 | int addition = 0; 66 | if (timaCycles >= getClockSelect()) { 67 | addition = timaCycles / getClockSelect(); 68 | } 69 | for (; addition > 0; --addition) { 70 | if (tima == 0xff) { 71 | didOverflow = true; 72 | break; 73 | } 74 | tima += 1; 75 | timaCycles -= getClockSelect(); 76 | } 77 | } 78 | } 79 | 80 | private int getClockSelect() { 81 | int clockSelect = (getNthBit(1, tac) << 1) | getNthBit(0, tac); 82 | 83 | switch (clockSelect) { 84 | case 0x00: 85 | return 1024 / 4; 86 | case 0x01: 87 | return 16 / 4; 88 | case 0x02: 89 | return 64 / 4; 90 | default: 91 | return 256 / 4; 92 | } 93 | } 94 | 95 | private int translateAddr(int addr) { 96 | return addr - 0xff04; 97 | } 98 | 99 | @Override 100 | public int getByte(int address) { 101 | switch (address) { 102 | case 0xff04: 103 | return div; 104 | case 0xff05: 105 | return tima; 106 | case 0xff06: 107 | return tma; 108 | default: 109 | return tac; 110 | } 111 | } 112 | 113 | @Override 114 | public void setByte(int address, int value) { 115 | switch (address) { 116 | case 0xff04: 117 | if (isTimerEnabled() && div == 1) 118 | tima += 1; 119 | div = 0x0; 120 | divCycles = 0; 121 | timaCycles = 0; 122 | 123 | break; 124 | case 0xff05: 125 | tima = value; 126 | break; 127 | case 0xff06: 128 | tma = value; 129 | break; 130 | case 0xff07: 131 | if (isTimerEnabled()) { 132 | if (getClockSelect() == 16 / 4) { 133 | int newClock = (getNthBit(1, tac) << 1) | getNthBit(0, 134 | tac); 135 | if (newClock == 0) { 136 | tima += 1; 137 | tima = 0; 138 | } 139 | } 140 | } 141 | tac = value; 142 | break; 143 | } 144 | } 145 | 146 | @Override 147 | public boolean hasAddressInSlot(int address) { 148 | return address >= 0xff04 && address <= 0xff07; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/JavaBoy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Java source file was generated by the Gradle 'init' task. 3 | */ 4 | package JavaBoy; 5 | 6 | import JavaBoy.cartridge.Cartridge; 7 | import JavaBoy.cpu.CPU; 8 | import JavaBoy.cpu.flags.FlagBank; 9 | import JavaBoy.cpu.instructions.*; 10 | import JavaBoy.cpu.interrupts.InterruptManager; 11 | import JavaBoy.cpu.registers.Register16; 12 | import JavaBoy.cpu.registers.RegisterBank; 13 | import JavaBoy.debug.DebugMemory; 14 | import JavaBoy.gui.GBGui; 15 | import JavaBoy.input.Joypad; 16 | import JavaBoy.memory.Dma; 17 | import JavaBoy.memory.MemoryMap; 18 | import JavaBoy.memory.MemorySlot; 19 | import JavaBoy.timer.Timer; 20 | import JavaBoy.video.*; 21 | import JavaBoy.video.pixelpipeline.DmgFifo; 22 | import JavaBoy.video.pixelpipeline.FIFOFetcher; 23 | import JavaBoy.video.pixelpipeline.PixelFIFO; 24 | 25 | import java.io.File; 26 | 27 | public class JavaBoy { 28 | public static void main(String[] args) { 29 | //File file = new File(JavaBoy.class.getResource( 30 | // "/gb-test-roms/cpu_instrs/individual/02_interrupts.gb").getFile()); 31 | //File file = new File("/home/damilola/Downloads/Dr. Mario (JU) (V1.1).gb"); 32 | 33 | if (args.length == 0){ 34 | System.err.println("Expected at least one argument !"); 35 | System.exit(1); 36 | } 37 | 38 | File file = new File(args[0]); 39 | Cartridge cart = new Cartridge(file); 40 | 41 | 42 | var load = new Load(); 43 | var add = new Add(); 44 | var and = new And(); 45 | var call = new Call(); 46 | var ccf = new CCF(); 47 | var cp = new Cp(); 48 | var cpl = new CPL(); 49 | var daa = new Daa(); 50 | var ei = new EI(); 51 | var dec = new Dec(); 52 | var di = new DI(); 53 | var inc = new Inc(); 54 | var jump = new Jump(); 55 | var nop = new Nop(); 56 | var or = new Or(); 57 | var pop = new Pop(); 58 | var push = new Push(); 59 | var reset = new Reset(); 60 | var ret = new Return(); 61 | var rotate = new Rotate(); 62 | var rotateCB = new RotateCB(); 63 | var scf = new SCF(); 64 | var set = new Set(); 65 | var shift = new Shift(); 66 | var sub = new Sub(); 67 | var swap = new Swap(); 68 | var xor = new Xor(); 69 | 70 | var bit = new Bit(); 71 | 72 | var cb = new CB(new Instruction[]{ 73 | bit, rotateCB, reset, set, shift, swap, 74 | }); 75 | 76 | InterruptManager manager = new InterruptManager(); 77 | Timer timer = new Timer(manager); 78 | 79 | MemoryMap map = new MemoryMap(); 80 | LCDC lcdc = new LCDC(); 81 | LCDStat lcdStat = new LCDStat(); 82 | GpuRegisters gpuRegisters = new GpuRegisters(); 83 | Oam oam = new Oam(); 84 | Palette palette = new Palette(); 85 | Vram vram = new Vram(); 86 | PixelFIFO oamFifo = new DmgFifo(palette); 87 | PixelFIFO bgFifo = new DmgFifo(palette); 88 | FIFOFetcher fetcher = new FIFOFetcher(oamFifo, bgFifo, vram, lcdc, 89 | gpuRegisters, oam.getSpritesBuffer()); 90 | Joypad joypad = new Joypad(manager); 91 | GBGui gui = new GBGui(joypad); 92 | Gpu gpu = new Gpu(gui, lcdc, lcdStat, gpuRegisters, oam, palette, vram, 93 | fetcher, oamFifo, bgFifo, manager); 94 | Dma dma = new Dma(map); 95 | map.setSlots(new MemorySlot[]{ 96 | cart, 97 | oam, 98 | dma, 99 | lcdc, 100 | gpuRegisters, 101 | lcdStat, 102 | joypad, 103 | palette, 104 | vram, 105 | timer, 106 | manager, 107 | new DebugMemory(), 108 | }); 109 | 110 | 111 | RegisterBank registers = new RegisterBank(new FlagBank(), 112 | new Register16(), 113 | new Register16()); 114 | CPU cpu = new CPU(map, new Instruction[]{ 115 | jump, 116 | add, 117 | sub, 118 | and, 119 | call, 120 | cb, 121 | ccf, 122 | cp, 123 | cpl, 124 | daa, 125 | dec, 126 | di, 127 | ei, 128 | inc, 129 | nop, 130 | or, 131 | pop, 132 | push, 133 | ret, 134 | rotate, 135 | scf, 136 | xor, 137 | load, 138 | }, registers, manager, timer, 139 | dma, 140 | gpu); 141 | 142 | 143 | gui.show(); 144 | cpu.run(); 145 | } 146 | 147 | public String getGreeting() { 148 | return "Hello world."; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Shift.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | public class Shift implements Instruction { 9 | @Override 10 | public boolean execute(int opcode, CPU cpu) { 11 | switch (opcode) { 12 | case 0x27: 13 | return sla(REGISTERS.A, cpu); 14 | case 0x20: 15 | return sla(REGISTERS.B, cpu); 16 | case 0x21: 17 | return sla(REGISTERS.C, cpu); 18 | case 0x22: 19 | return sla(REGISTERS.D, cpu); 20 | case 0x23: 21 | return sla(REGISTERS.E, cpu); 22 | case 0x24: 23 | return sla(REGISTERS.H, cpu); 24 | case 0x25: 25 | return sla(REGISTERS.L, cpu); 26 | case 0x26: 27 | return sla(cpu); 28 | case 0x2f: 29 | return sra(REGISTERS.A, cpu); 30 | case 0x28: 31 | return sra(REGISTERS.B, cpu); 32 | case 0x29: 33 | return sra(REGISTERS.C, cpu); 34 | case 0x2a: 35 | return sra(REGISTERS.D, cpu); 36 | case 0x2b: 37 | return sra(REGISTERS.E, cpu); 38 | case 0x2c: 39 | return sra(REGISTERS.H, cpu); 40 | case 0x2d: 41 | return sra(REGISTERS.L, cpu); 42 | case 0x2e: 43 | return sra(cpu); 44 | case 0x3f: 45 | return srl(REGISTERS.A, cpu); 46 | case 0x38: 47 | return srl(REGISTERS.B, cpu); 48 | case 0x39: 49 | return srl(REGISTERS.C, cpu); 50 | case 0x3a: 51 | return srl(REGISTERS.D, cpu); 52 | case 0x3b: 53 | return srl(REGISTERS.E, cpu); 54 | case 0x3c: 55 | return srl(REGISTERS.H, cpu); 56 | case 0x3d: 57 | return srl(REGISTERS.L, cpu); 58 | case 0x3e: 59 | return srl(cpu); 60 | 61 | default: 62 | return false; 63 | 64 | } 65 | 66 | 67 | } 68 | 69 | 70 | private int applySRA(int value, CPU cpu) { 71 | int msb = value & 0x80; 72 | int lsb = value & 0x01; 73 | 74 | int result = (value >>> 1) | msb; 75 | 76 | cpu.setFlag(FLAGS.C, lsb == 1); 77 | cpu.setFlag(FLAGS.H, false); 78 | cpu.setFlag(FLAGS.N, false); 79 | cpu.setFlag(FLAGS.Z, result == 0); 80 | 81 | return result; 82 | } 83 | 84 | private int applySRL(int value, CPU cpu) { 85 | int lsb = value & 0x01; 86 | int result = value >>> 1; 87 | cpu.setFlag(FLAGS.C, lsb == 1); 88 | cpu.setFlag(FLAGS.H, false); 89 | cpu.setFlag(FLAGS.N, false); 90 | cpu.setFlag(FLAGS.Z, result == 0); 91 | return result; 92 | } 93 | 94 | private boolean srl(REGISTERS reg, CPU cpu) { 95 | int bits = cpu.readRegister(reg); 96 | int result = applySRL(bits, cpu); 97 | cpu.writeRegister(reg, result); 98 | cpu.addCycles(2); 99 | return true; 100 | } 101 | 102 | private boolean srl(CPU cpu) { 103 | int addr = cpu.readWordRegister(RegisterPairs.HL); 104 | int bits = cpu.readAddress(addr); 105 | int result = applySRL(bits, cpu); 106 | cpu.writeAddress(addr, result); 107 | cpu.addCycles(4); 108 | return true; 109 | } 110 | 111 | private boolean sra(REGISTERS reg, CPU cpu) { 112 | int bits = cpu.readRegister(reg); 113 | int result = applySRA(bits, cpu); 114 | cpu.writeRegister(reg, result); 115 | cpu.addCycles(2); 116 | return true; 117 | } 118 | 119 | private boolean sra(CPU cpu) { 120 | int addr = cpu.readWordRegister(RegisterPairs.HL); 121 | int bits = cpu.readAddress(addr); 122 | int result = applySRA(bits, cpu); 123 | cpu.writeAddress(addr, result); 124 | cpu.addCycles(4); 125 | return true; 126 | } 127 | 128 | private int applySLA(int value, CPU cpu) { 129 | cpu.setFlag(FLAGS.C, (value & 0x80) != 0); 130 | value <<= 1; 131 | value &= 0xff; 132 | cpu.setFlag(FLAGS.Z, value == 0); 133 | cpu.setFlag(FLAGS.N, false); 134 | cpu.setFlag(FLAGS.H, false); 135 | return value; 136 | } 137 | 138 | private boolean sla(REGISTERS reg, CPU cpu) { 139 | int bits = cpu.readRegister(reg); 140 | int result = applySLA(bits, cpu); 141 | cpu.writeRegister(reg, result); 142 | cpu.addCycles(2); 143 | return true; 144 | } 145 | 146 | private boolean sla(CPU cpu) { 147 | int addr = cpu.readWordRegister(RegisterPairs.HL); 148 | int bits = cpu.readAddress(addr); 149 | int result = applySLA(bits, cpu); 150 | cpu.writeAddress(addr, result); 151 | cpu.addCycles(4); 152 | return true; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Set.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.registers.RegisterPairs; 6 | 7 | public class Set implements Instruction { 8 | 9 | @Override 10 | public boolean execute(int opcode, CPU cpu) { 11 | switch (opcode) { 12 | case 0xc7: 13 | return set(0, REGISTERS.A, cpu); 14 | case 0xc0: 15 | return set(0, REGISTERS.B, cpu); 16 | case 0xc1: 17 | return set(0, REGISTERS.C, cpu); 18 | case 0xc2: 19 | return set(0, REGISTERS.D, cpu); 20 | case 0xc3: 21 | return set(0, REGISTERS.E, cpu); 22 | case 0xc4: 23 | return set(0, REGISTERS.H, cpu); 24 | case 0xc5: 25 | return set(0, REGISTERS.L, cpu); 26 | case 0xcf: 27 | return set(1, REGISTERS.A, cpu); 28 | case 0xc8: 29 | return set(1, REGISTERS.B, cpu); 30 | case 0xc9: 31 | return set(1, REGISTERS.C, cpu); 32 | case 0xca: 33 | return set(1, REGISTERS.D, cpu); 34 | case 0xcb: 35 | return set(1, REGISTERS.E, cpu); 36 | case 0xcc: 37 | return set(1, REGISTERS.H, cpu); 38 | case 0xcd: 39 | return set(1, REGISTERS.L, cpu); 40 | case 0xd7: 41 | return set(2, REGISTERS.A, cpu); 42 | case 0xd0: 43 | return set(2, REGISTERS.B, cpu); 44 | case 0xd1: 45 | return set(2, REGISTERS.C, cpu); 46 | case 0xd2: 47 | return set(2, REGISTERS.D, cpu); 48 | case 0xd3: 49 | return set(2, REGISTERS.E, cpu); 50 | case 0xd4: 51 | return set(2, REGISTERS.H, cpu); 52 | case 0xd5: 53 | return set(2, REGISTERS.L, cpu); 54 | case 0xdf: 55 | return set(3, REGISTERS.A, cpu); 56 | case 0xd8: 57 | return set(3, REGISTERS.B, cpu); 58 | case 0xd9: 59 | return set(3, REGISTERS.C, cpu); 60 | case 0xda: 61 | return set(3, REGISTERS.D, cpu); 62 | case 0xdb: 63 | return set(3, REGISTERS.E, cpu); 64 | case 0xdc: 65 | return set(3, REGISTERS.H, cpu); 66 | case 0xdd: 67 | return set(3, REGISTERS.L, cpu); 68 | case 0xe7: 69 | return set(4, REGISTERS.A, cpu); 70 | case 0xe0: 71 | return set(4, REGISTERS.B, cpu); 72 | case 0xe1: 73 | return set(4, REGISTERS.C, cpu); 74 | case 0xe2: 75 | return set(4, REGISTERS.D, cpu); 76 | case 0xe3: 77 | return set(4, REGISTERS.E, cpu); 78 | case 0xe4: 79 | return set(4, REGISTERS.H, cpu); 80 | case 0xe5: 81 | return set(4, REGISTERS.L, cpu); 82 | case 0xef: 83 | return set(5, REGISTERS.A, cpu); 84 | case 0xe8: 85 | return set(5, REGISTERS.B, cpu); 86 | case 0xe9: 87 | return set(5, REGISTERS.C, cpu); 88 | case 0xea: 89 | return set(5, REGISTERS.D, cpu); 90 | case 0xeb: 91 | return set(5, REGISTERS.E, cpu); 92 | case 0xec: 93 | return set(5, REGISTERS.H, cpu); 94 | case 0xed: 95 | return set(5, REGISTERS.L, cpu); 96 | case 0xf7: 97 | return set(6, REGISTERS.A, cpu); 98 | case 0xf0: 99 | return set(6, REGISTERS.B, cpu); 100 | case 0xf1: 101 | return set(6, REGISTERS.C, cpu); 102 | case 0xf2: 103 | return set(6, REGISTERS.D, cpu); 104 | case 0xf3: 105 | return set(6, REGISTERS.E, cpu); 106 | case 0xf4: 107 | return set(6, REGISTERS.H, cpu); 108 | case 0xf5: 109 | return set(6, REGISTERS.L, cpu); 110 | case 0xff: 111 | return set(7, REGISTERS.A, cpu); 112 | case 0xf8: 113 | return set(7, REGISTERS.B, cpu); 114 | case 0xf9: 115 | return set(7, REGISTERS.C, cpu); 116 | case 0xfa: 117 | return set(7, REGISTERS.D, cpu); 118 | case 0xfb: 119 | return set(7, REGISTERS.E, cpu); 120 | case 0xfc: 121 | return set(7, REGISTERS.H, cpu); 122 | case 0xfd: 123 | return set(7, REGISTERS.L, cpu); 124 | case 0xc6: 125 | return set(0, cpu); 126 | case 0xce: 127 | return set(1, cpu); 128 | case 0xd6: 129 | return set(2, cpu); 130 | case 0xde: 131 | return set(3, cpu); 132 | case 0xe6: 133 | return set(4, cpu); 134 | case 0xee: 135 | return set(5, cpu); 136 | case 0xf6: 137 | return set(6, cpu); 138 | case 0xfe: 139 | return set(7, cpu); 140 | 141 | default: 142 | return false; 143 | } 144 | } 145 | 146 | 147 | private int applySet(int setBit, int value) { 148 | int setBitFlip = 0x01 << setBit; 149 | return setBitFlip | value; 150 | } 151 | 152 | private boolean set(int setBit, REGISTERS reg, CPU cpu) { 153 | int bits = cpu.readRegister(reg); 154 | cpu.writeRegister(reg, applySet(setBit, bits)); 155 | cpu.addCycles(2); 156 | return true; 157 | } 158 | 159 | private boolean set(int setBit, CPU cpu) { 160 | int addr = cpu.readWordRegister(RegisterPairs.HL); 161 | int bits = cpu.readAddress(addr); 162 | cpu.writeAddress(addr, applySet(setBit, bits)); 163 | cpu.addCycles(4); 164 | return true; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Reset.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.registers.RegisterPairs; 6 | 7 | public class Reset implements Instruction { 8 | @Override 9 | public boolean execute(int opcode, CPU cpu) { 10 | switch (opcode) { 11 | case 0x87: 12 | return res(0, REGISTERS.A, cpu); 13 | case 0x80: 14 | return res(0, REGISTERS.B, cpu); 15 | case 0x81: 16 | return res(0, REGISTERS.C, cpu); 17 | case 0x82: 18 | return res(0, REGISTERS.D, cpu); 19 | case 0x83: 20 | return res(0, REGISTERS.E, cpu); 21 | case 0x84: 22 | return res(0, REGISTERS.H, cpu); 23 | case 0x85: 24 | return res(0, REGISTERS.L, cpu); 25 | case 0x8f: 26 | return res(1, REGISTERS.A, cpu); 27 | case 0x88: 28 | return res(1, REGISTERS.B, cpu); 29 | case 0x89: 30 | return res(1, REGISTERS.C, cpu); 31 | case 0x8a: 32 | return res(1, REGISTERS.D, cpu); 33 | case 0x8b: 34 | return res(1, REGISTERS.E, cpu); 35 | case 0x8c: 36 | return res(1, REGISTERS.H, cpu); 37 | case 0x8d: 38 | return res(1, REGISTERS.L, cpu); 39 | case 0x97: 40 | return res(2, REGISTERS.A, cpu); 41 | case 0x90: 42 | return res(2, REGISTERS.B, cpu); 43 | case 0x91: 44 | return res(2, REGISTERS.C, cpu); 45 | case 0x92: 46 | return res(2, REGISTERS.D, cpu); 47 | case 0x93: 48 | return res(2, REGISTERS.E, cpu); 49 | case 0x94: 50 | return res(2, REGISTERS.H, cpu); 51 | case 0x95: 52 | return res(2, REGISTERS.L, cpu); 53 | case 0x9f: 54 | return res(3, REGISTERS.A, cpu); 55 | case 0x98: 56 | return res(3, REGISTERS.B, cpu); 57 | case 0x99: 58 | return res(3, REGISTERS.C, cpu); 59 | case 0x9a: 60 | return res(3, REGISTERS.D, cpu); 61 | case 0x9b: 62 | return res(3, REGISTERS.E, cpu); 63 | case 0x9c: 64 | return res(3, REGISTERS.H, cpu); 65 | case 0x9d: 66 | return res(3, REGISTERS.L, cpu); 67 | case 0xa7: 68 | return res(4, REGISTERS.A, cpu); 69 | case 0xa0: 70 | return res(4, REGISTERS.B, cpu); 71 | case 0xa1: 72 | return res(4, REGISTERS.C, cpu); 73 | case 0xa2: 74 | return res(4, REGISTERS.D, cpu); 75 | case 0xa3: 76 | return res(4, REGISTERS.E, cpu); 77 | case 0xa4: 78 | return res(4, REGISTERS.H, cpu); 79 | case 0xa5: 80 | return res(4, REGISTERS.L, cpu); 81 | case 0xaf: 82 | return res(5, REGISTERS.A, cpu); 83 | case 0xa8: 84 | return res(5, REGISTERS.B, cpu); 85 | case 0xa9: 86 | return res(5, REGISTERS.C, cpu); 87 | case 0xaa: 88 | return res(5, REGISTERS.D, cpu); 89 | case 0xab: 90 | return res(5, REGISTERS.E, cpu); 91 | case 0xac: 92 | return res(5, REGISTERS.H, cpu); 93 | case 0xad: 94 | return res(5, REGISTERS.L, cpu); 95 | case 0xb7: 96 | return res(6, REGISTERS.A, cpu); 97 | case 0xb0: 98 | return res(6, REGISTERS.B, cpu); 99 | case 0xb1: 100 | return res(6, REGISTERS.C, cpu); 101 | case 0xb2: 102 | return res(6, REGISTERS.D, cpu); 103 | case 0xb3: 104 | return res(6, REGISTERS.E, cpu); 105 | case 0xb4: 106 | return res(6, REGISTERS.H, cpu); 107 | case 0xb5: 108 | return res(6, REGISTERS.L, cpu); 109 | case 0xbf: 110 | return res(7, REGISTERS.A, cpu); 111 | case 0xb8: 112 | return res(7, REGISTERS.B, cpu); 113 | case 0xb9: 114 | return res(7, REGISTERS.C, cpu); 115 | case 0xba: 116 | return res(7, REGISTERS.D, cpu); 117 | case 0xbb: 118 | return res(7, REGISTERS.E, cpu); 119 | case 0xbc: 120 | return res(7, REGISTERS.H, cpu); 121 | case 0xbd: 122 | return res(7, REGISTERS.L, cpu); 123 | case 0x86: 124 | return res(0, cpu); 125 | case 0x8e: 126 | return res(1, cpu); 127 | case 0x96: 128 | return res(2, cpu); 129 | case 0x9e: 130 | return res(3, cpu); 131 | case 0xa6: 132 | return res(4, cpu); 133 | case 0xae: 134 | return res(5, cpu); 135 | case 0xb6: 136 | return res(6, cpu); 137 | case 0xbe: 138 | return res(7, cpu); 139 | default: 140 | return false; 141 | } 142 | } 143 | 144 | private boolean res(int resetBit, REGISTERS reg, CPU cpu) { 145 | int bits = cpu.readRegister(reg); 146 | cpu.writeRegister(reg, applyRes(resetBit, bits)); 147 | cpu.addCycles(2); 148 | return true; 149 | } 150 | 151 | private boolean res(int resetBit, CPU cpu) { 152 | int addr = cpu.readWordRegister(RegisterPairs.HL); 153 | int bits = cpu.readAddress(addr); 154 | cpu.writeAddress(addr, applyRes(resetBit, bits)); 155 | cpu.addCycles(4); 156 | return true; 157 | } 158 | 159 | private int applyRes(int resetBit, int value) { 160 | int restBitMask = 0x01 << resetBit; 161 | restBitMask = ~(restBitMask); 162 | return value & restBitMask; 163 | } 164 | 165 | 166 | } 167 | 168 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Bit.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | import static JavaBoy.utils.BitUtils.getNthBit; 9 | 10 | public class Bit implements Instruction { 11 | @Override 12 | public boolean execute(int opcode, CPU cpu) { 13 | switch (opcode) { 14 | case 0x47: 15 | return bit(0, REGISTERS.A, cpu); 16 | case 0x40: 17 | return bit(0, REGISTERS.B, cpu); 18 | case 0x41: 19 | return bit(0, REGISTERS.C, cpu); 20 | case 0x42: 21 | return bit(0, REGISTERS.D, cpu); 22 | case 0x43: 23 | return bit(0, REGISTERS.E, cpu); 24 | case 0x44: 25 | return bit(0, REGISTERS.H, cpu); 26 | case 0x45: 27 | return bit(0, REGISTERS.L, cpu); 28 | case 0x4f: 29 | return bit(1, REGISTERS.A, cpu); 30 | case 0x48: 31 | return bit(1, REGISTERS.B, cpu); 32 | case 0x49: 33 | return bit(1, REGISTERS.C, cpu); 34 | case 0x4a: 35 | return bit(1, REGISTERS.D, cpu); 36 | case 0x4b: 37 | return bit(1, REGISTERS.E, cpu); 38 | case 0x4c: 39 | return bit(1, REGISTERS.H, cpu); 40 | case 0x4d: 41 | return bit(1, REGISTERS.L, cpu); 42 | case 0x57: 43 | return bit(2, REGISTERS.A, cpu); 44 | case 0x50: 45 | return bit(2, REGISTERS.B, cpu); 46 | case 0x51: 47 | return bit(2, REGISTERS.C, cpu); 48 | case 0x52: 49 | return bit(2, REGISTERS.D, cpu); 50 | case 0x53: 51 | return bit(2, REGISTERS.E, cpu); 52 | case 0x54: 53 | return bit(2, REGISTERS.H, cpu); 54 | case 0x55: 55 | return bit(2, REGISTERS.L, cpu); 56 | case 0x5f: 57 | return bit(3, REGISTERS.A, cpu); 58 | case 0x58: 59 | return bit(3, REGISTERS.B, cpu); 60 | case 0x59: 61 | return bit(3, REGISTERS.C, cpu); 62 | case 0x5a: 63 | return bit(3, REGISTERS.D, cpu); 64 | case 0x5b: 65 | return bit(3, REGISTERS.E, cpu); 66 | case 0x5c: 67 | return bit(3, REGISTERS.H, cpu); 68 | case 0x5d: 69 | return bit(3, REGISTERS.L, cpu); 70 | case 0x67: 71 | return bit(4, REGISTERS.A, cpu); 72 | case 0x60: 73 | return bit(4, REGISTERS.B, cpu); 74 | case 0x61: 75 | return bit(4, REGISTERS.C, cpu); 76 | case 0x62: 77 | return bit(4, REGISTERS.D, cpu); 78 | case 0x63: 79 | return bit(4, REGISTERS.E, cpu); 80 | case 0x64: 81 | return bit(4, REGISTERS.H, cpu); 82 | case 0x65: 83 | return bit(4, REGISTERS.L, cpu); 84 | case 0x6f: 85 | return bit(5, REGISTERS.A, cpu); 86 | case 0x68: 87 | return bit(5, REGISTERS.B, cpu); 88 | case 0x69: 89 | return bit(5, REGISTERS.C, cpu); 90 | case 0x6a: 91 | return bit(5, REGISTERS.D, cpu); 92 | case 0x6b: 93 | return bit(5, REGISTERS.E, cpu); 94 | case 0x6c: 95 | return bit(5, REGISTERS.H, cpu); 96 | case 0x6d: 97 | return bit(5, REGISTERS.L, cpu); 98 | case 0x77: 99 | return bit(6, REGISTERS.A, cpu); 100 | case 0x70: 101 | return bit(6, REGISTERS.B, cpu); 102 | case 0x71: 103 | return bit(6, REGISTERS.C, cpu); 104 | case 0x72: 105 | return bit(6, REGISTERS.D, cpu); 106 | case 0x73: 107 | return bit(6, REGISTERS.E, cpu); 108 | case 0x74: 109 | return bit(6, REGISTERS.H, cpu); 110 | case 0x75: 111 | return bit(6, REGISTERS.L, cpu); 112 | case 0x7f: 113 | return bit(7, REGISTERS.A, cpu); 114 | case 0x78: 115 | return bit(7, REGISTERS.B, cpu); 116 | case 0x79: 117 | return bit(7, REGISTERS.C, cpu); 118 | case 0x7a: 119 | return bit(7, REGISTERS.D, cpu); 120 | case 0x7b: 121 | return bit(7, REGISTERS.E, cpu); 122 | case 0x7c: 123 | return bit(7, REGISTERS.H, cpu); 124 | case 0x7d: 125 | return bit(7, REGISTERS.L, cpu); 126 | case 0x46: 127 | return bit(0, cpu); 128 | case 0x4e: 129 | return bit(1, cpu); 130 | case 0x56: 131 | return bit(2, cpu); 132 | case 0x5e: 133 | return bit(3, cpu); 134 | case 0x66: 135 | return bit(4, cpu); 136 | case 0x6e: 137 | return bit(5, cpu); 138 | case 0x76: 139 | return bit(6, cpu); 140 | case 0x7e: 141 | return bit(7, cpu); 142 | default: 143 | return false; 144 | } 145 | } 146 | 147 | 148 | private void applyBitTest(int testBit, int value, CPU cpu) { 149 | int bit = getNthBit(testBit, value) ; 150 | cpu.setFlag(FLAGS.Z, bit == 0); 151 | cpu.setFlag(FLAGS.H, true); 152 | cpu.setFlag(FLAGS.N, false); 153 | } 154 | 155 | private boolean bit(int testBit, REGISTERS REGISTERS, CPU cpu) { 156 | int bits = cpu.readRegister(REGISTERS); 157 | applyBitTest(testBit, bits, cpu); 158 | cpu.addCycles(2); 159 | return true; 160 | } 161 | 162 | private boolean bit(int testBit, CPU cpu) { 163 | int bits = cpu.readAddress(cpu.readWordRegister(RegisterPairs.HL)); 164 | applyBitTest(testBit, bits, cpu); 165 | cpu.addCycles(3); 166 | return true; 167 | } 168 | 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Add.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | import static JavaBoy.utils.ArithmeticUtils.*; 9 | 10 | public class Add implements Instruction { 11 | 12 | 13 | @Override 14 | public boolean execute(int opcode, CPU cpu) { 15 | switch (opcode) { 16 | case 0x87: 17 | return add(REGISTERS.A, cpu); 18 | case 0x80: 19 | return add(REGISTERS.B, cpu); 20 | case 0x81: 21 | return add(REGISTERS.C, cpu); 22 | case 0x82: 23 | return add(REGISTERS.D, cpu); 24 | case 0x83: 25 | return add(REGISTERS.E, cpu); 26 | case 0x84: 27 | return add(REGISTERS.H, cpu); 28 | case 0x85: 29 | return add(REGISTERS.L, cpu); 30 | case 0x86: 31 | return addHL(cpu); 32 | case 0xc6: 33 | return add(cpu); 34 | // Add + Carry Flag 35 | case 0x8f: 36 | return addC(REGISTERS.A, cpu); 37 | case 0x88: 38 | return addC(REGISTERS.B, cpu); 39 | case 0x89: 40 | return addC(REGISTERS.C, cpu); 41 | case 0x8a: 42 | return addC(REGISTERS.D, cpu); 43 | case 0x8b: 44 | return addC(REGISTERS.E, cpu); 45 | case 0x8c: 46 | return addC(REGISTERS.H, cpu); 47 | case 0x8d: 48 | return addC(REGISTERS.L, cpu); 49 | case 0x8e: 50 | return addCHL(cpu); 51 | case 0xce: 52 | return addC(cpu); 53 | 54 | //16-bit Add Instructions 55 | case 0x09: 56 | return add16(RegisterPairs.BC, cpu); 57 | case 0x19: 58 | return add16(RegisterPairs.DE, cpu); 59 | case 0x29: 60 | return add16(RegisterPairs.HL, cpu); 61 | case 0x39: 62 | return add16SP(cpu); 63 | case 0xe8: 64 | return addSP(cpu); 65 | default: 66 | return false; 67 | } 68 | 69 | } 70 | 71 | 72 | private boolean add(REGISTERS fromREGISTERS, CPU cpu) { 73 | int reg1Val = cpu.readRegister(REGISTERS.A); 74 | int reg2Val = cpu.readRegister(fromREGISTERS); 75 | cpu.writeRegister(REGISTERS.A, addBytes(reg1Val, reg2Val, cpu)); 76 | cpu.addCycles(); 77 | return true; 78 | } 79 | 80 | private boolean addHL(CPU cpu) { 81 | int val1 = cpu.readRegister(REGISTERS.A); 82 | int val2 = cpu.readAddress(cpu.readWordRegister(RegisterPairs.HL)); 83 | cpu.writeRegister(REGISTERS.A, addBytes(val1, val2, cpu)); 84 | cpu.addCycles(2); 85 | return true; 86 | } 87 | 88 | private boolean add(CPU cpu) { 89 | int val1 = cpu.readRegister(REGISTERS.A); 90 | int val2 = cpu.readPC(); 91 | cpu.writeRegister(REGISTERS.A, addBytes(val1, val2, cpu)); 92 | cpu.addCycles(2); 93 | return true; 94 | } 95 | 96 | private boolean addC(REGISTERS second, CPU cpu) { 97 | int val1 = cpu.readRegister(REGISTERS.A); 98 | int val2 = cpu.readRegister(second) ; 99 | cpu.writeRegister(REGISTERS.A, addCBytes(val1, val2, cpu)); 100 | cpu.addCycles(); 101 | return true; 102 | 103 | } 104 | 105 | private int addCBytes(int val1, int val2, CPU cpu){ 106 | int result = val1 + val2 + cpu.getFlag(FLAGS.C); 107 | int carryFlag = cpu.getFlag(FLAGS.C); 108 | cpu.setFlag(FLAGS.Z, (result & 0xff) == 0); 109 | cpu.setFlag(FLAGS.N, false); 110 | cpu.setFlag(FLAGS.H, ((val1 & 0x0f) + (val2 & 0x0f) + carryFlag) > 0x0f ); 111 | cpu.setFlag(FLAGS.C, result > 0xff); 112 | return result; 113 | } 114 | 115 | private boolean addC(CPU cpu) { 116 | int val1 = cpu.readRegister(REGISTERS.A); 117 | int val2 = cpu.readPC(); 118 | cpu.writeRegister(REGISTERS.A, addCBytes(val1, val2, cpu)); 119 | cpu.addCycles(2); 120 | return true; 121 | } 122 | 123 | private boolean addCHL(CPU cpu) { 124 | int val1 = cpu.readRegister(REGISTERS.A); 125 | int val2 = cpu.readAddress(cpu.readWordRegister(RegisterPairs.HL)); 126 | cpu.writeRegister(REGISTERS.A, addCBytes(val1, val2, cpu)); 127 | cpu.addCycles(2); 128 | return true; 129 | } 130 | 131 | 132 | private int addBytes(int value, int value2, CPU cpu) { 133 | 134 | int result = (value + value2) & 0xff; 135 | cpu.setFlag(FLAGS.Z, result == 0); 136 | cpu.setFlag(FLAGS.H, isHalfCarry8(value, value2)); 137 | cpu.setFlag(FLAGS.N, false); 138 | cpu.setFlag(FLAGS.C, isCarry8(value, value2)); 139 | return result; 140 | 141 | } 142 | 143 | 144 | private boolean add16(RegisterPairs pair, CPU cpu) { 145 | int val1 = cpu.readWordRegister(RegisterPairs.HL); 146 | int val2 = cpu.readWordRegister(pair); 147 | 148 | cpu.writeWordRegister(RegisterPairs.HL, applyAdd16(val1, val2, cpu)); 149 | cpu.addCycles(2); 150 | return true; 151 | } 152 | 153 | private boolean add16SP(CPU cpu) { 154 | int val1 = cpu.readWordRegister(RegisterPairs.HL); 155 | int val2 = cpu.getSP(); 156 | 157 | cpu.writeWordRegister(RegisterPairs.HL, applyAdd16(val1, val2, cpu)); 158 | cpu.addCycles(2); 159 | return true; 160 | } 161 | 162 | private boolean addSP(CPU cpu) { 163 | int val1 = cpu.getSP(); 164 | int val2 = (byte) cpu.readPC(); 165 | int result = (val1 + val2) ; 166 | cpu.setSP(result); 167 | cpu.setFlag(FLAGS.Z, false); 168 | cpu.setFlag(FLAGS.N, false); 169 | cpu.setFlag(FLAGS.H, ((result & 0x0f) < (val1 & 0x0f))); 170 | cpu.setFlag(FLAGS.C, (result & 0xff) < (val1 & 0xff)); 171 | cpu.addCycles(4); 172 | return true; 173 | } 174 | 175 | 176 | private int applyAdd16(int val1, int val2, CPU cpu) { 177 | int result = val1 + val2; 178 | cpu.setFlag(FLAGS.H, isHalfCarry16(val1, val2)); 179 | cpu.setFlag(FLAGS.C, isCarry16(val1, val2)); 180 | cpu.setFlag(FLAGS.N, false); 181 | return result; 182 | 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/Oam.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video; 2 | 3 | import JavaBoy.memory.MemorySlot; 4 | 5 | import java.util.ArrayList; 6 | 7 | import static JavaBoy.utils.BitUtils.getNthBit; 8 | 9 | public class Oam implements MemorySlot { 10 | private final SpriteAttribute[] lineSpritesBuffer = { 11 | new SpriteAttribute(), 12 | new SpriteAttribute(), 13 | new SpriteAttribute(), 14 | new SpriteAttribute(), 15 | new SpriteAttribute(), 16 | new SpriteAttribute(), 17 | new SpriteAttribute(), 18 | new SpriteAttribute(), 19 | new SpriteAttribute(), 20 | new SpriteAttribute(), 21 | }; 22 | int[] data = new int[(0xfe9f - 0xfe00) + 1]; 23 | ArrayList foundSprites = new ArrayList<>(10); 24 | 25 | @Override 26 | public int getByte(int address) { 27 | return data[address - 0xfe00]; 28 | } 29 | 30 | @Override 31 | public void setByte(int address, int value) { 32 | this.data[address - 0xfe00] = value; 33 | } 34 | 35 | @Override 36 | public boolean hasAddressInSlot(int address) { 37 | return address >= 0xfe00 && address <= 0xfe9f; 38 | } 39 | 40 | public SpriteAttribute[] getSprites(int line, boolean is16) { 41 | for (int idx = 0; idx < 10; ++idx) { 42 | lineSpritesBuffer[idx].setDirty(true); 43 | } 44 | int spriteFound = 0; 45 | int step = is16 ? 2 : 1; 46 | for (int a = 0; a < 40; a += step) { 47 | if (spriteFound == 10) 48 | break; 49 | 50 | int yPos = data[a * 4]; 51 | if (!is16 && yPos <= 8) 52 | continue; 53 | if ((yPos == 0) || (yPos >= 160)) 54 | continue; 55 | if ((line < (yPos - 16)) || line >= yPos) 56 | continue; 57 | if (!is16 && line >= (yPos - 8)) 58 | continue; 59 | initSprite(lineSpritesBuffer[spriteFound], a, is16); 60 | ++spriteFound; 61 | } 62 | return lineSpritesBuffer; 63 | } 64 | 65 | public SpriteAttribute[] getSpritesBuffer() { 66 | return this.lineSpritesBuffer; 67 | } 68 | 69 | private void initSprite(SpriteAttribute sprite, int spritePos, 70 | boolean is16) { 71 | int spriteBegin = spritePos * 4; 72 | sprite.setIs16(is16); 73 | sprite.init(spritePos, data[spriteBegin], 74 | data[spriteBegin + 1], 75 | is16 ? data[spriteBegin + 2] & 0xfe : data[spriteBegin + 2], 76 | data[spriteBegin + 3] 77 | ); 78 | sprite.setDirty(false); 79 | 80 | if (is16) { 81 | if (sprite.getLowerHalf() == null){ 82 | sprite.initLowerHalf(); 83 | } 84 | int lowerBegin = (spritePos + 1) * 4; 85 | sprite.getLowerHalf().init(spritePos + 1, data[lowerBegin], 86 | data[lowerBegin + 1], 87 | data[spriteBegin + 2] | 0x01, 88 | data[lowerBegin + 3]); 89 | } 90 | } 91 | 92 | 93 | public class SpriteAttribute implements Comparable { 94 | 95 | private SpriteAttribute lowerHalf; 96 | private boolean is16 = false; 97 | private int yPosition; 98 | private int spriteNumber; 99 | private int xPosition; 100 | private int tileNumber; 101 | private Palettes palette; 102 | private boolean isAboveBG; 103 | private boolean isXFlipped; 104 | private boolean isYFlipped; 105 | private boolean isDirty = true; 106 | 107 | public boolean isDirty() { 108 | return isDirty; 109 | } 110 | 111 | void initLowerHalf(){ 112 | this.lowerHalf = new SpriteAttribute(); 113 | } 114 | 115 | void setDirty(boolean dirty) { 116 | isDirty = dirty; 117 | } 118 | 119 | public boolean isIs16() { 120 | return is16; 121 | } 122 | 123 | public void setIs16(boolean is16) { 124 | this.is16 = is16; 125 | } 126 | 127 | void init(int spriteNumber, int yPosition, int xPosition, 128 | int tileNumber, 129 | int attributes) { 130 | this.spriteNumber = spriteNumber; 131 | this.yPosition = yPosition; 132 | this.xPosition = xPosition; 133 | this.tileNumber = tileNumber; 134 | this.palette = getNthBit(4, 135 | attributes) == 0 ? Palettes.OBP0 : 136 | Palettes.OBP1; 137 | this.isAboveBG = getNthBit(7, attributes) == 0; 138 | this.isXFlipped = getNthBit(5, attributes) == 1; 139 | this.isYFlipped = getNthBit(6, attributes) == 1; 140 | 141 | } 142 | 143 | SpriteAttribute getLowerHalf() { 144 | return this.lowerHalf; 145 | } 146 | 147 | @Override 148 | public int compareTo(SpriteAttribute spriteAttribute) { 149 | int result = 0; 150 | if (this.getXPosition() < spriteAttribute.getXPosition()) { 151 | ++result; 152 | } else if (this.getXPosition() > spriteAttribute.getXPosition()) { 153 | --result; 154 | } else { 155 | if (this.getSpriteNumber() < spriteAttribute.getSpriteNumber()) 156 | ++result; 157 | else 158 | --result; 159 | } 160 | return result; 161 | } 162 | 163 | public int getTileForLine(int y) { 164 | 165 | if (!is16) 166 | return this.tileNumber; 167 | 168 | if (y % 16 >= 8) { 169 | return lowerHalf.getTileNumber(); 170 | } 171 | 172 | return this.tileNumber; 173 | } 174 | 175 | public int getTileNumber() { 176 | return tileNumber; 177 | } 178 | 179 | public int getYPosition() { 180 | return yPosition; 181 | } 182 | 183 | public int getXPosition() { 184 | return xPosition; 185 | } 186 | 187 | public Palettes getPalette() { 188 | return palette; 189 | } 190 | 191 | public boolean isAboveBG() { 192 | return isAboveBG; 193 | } 194 | 195 | public boolean isXFlipped() { 196 | return isXFlipped; 197 | } 198 | 199 | public boolean isYFlipped() { 200 | return isYFlipped; 201 | } 202 | 203 | public int getSpriteNumber() { 204 | return spriteNumber; 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/RotateCB.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | public class RotateCB implements Instruction { 9 | 10 | @Override 11 | public boolean execute(int opcode, CPU cpu) { 12 | switch (opcode) { 13 | case 0x07: 14 | return rlc(REGISTERS.A, cpu); 15 | case 0x00: 16 | return rlc(REGISTERS.B, cpu); 17 | case 0x01: 18 | return rlc(REGISTERS.C, cpu); 19 | case 0x02: 20 | return rlc(REGISTERS.D, cpu); 21 | case 0x03: 22 | return rlc(REGISTERS.E, cpu); 23 | case 0x04: 24 | return rlc(REGISTERS.H, cpu); 25 | case 0x05: 26 | return rlc(REGISTERS.L, cpu); 27 | case 0x06: 28 | return rlc(cpu); 29 | case 0x17: 30 | return rl(REGISTERS.A, cpu); 31 | case 0x10: 32 | return rl(REGISTERS.B, cpu); 33 | case 0x11: 34 | return rl(REGISTERS.C, cpu); 35 | case 0x12: 36 | return rl(REGISTERS.D, cpu); 37 | case 0x13: 38 | return rl(REGISTERS.E, cpu); 39 | case 0x14: 40 | return rl(REGISTERS.H, cpu); 41 | case 0x15: 42 | return rl(REGISTERS.L, cpu); 43 | case 0x16: 44 | return rl(cpu); 45 | case 0x0f: 46 | return rrc(REGISTERS.A, cpu); 47 | case 0x08: 48 | return rrc(REGISTERS.B, cpu); 49 | case 0x09: 50 | return rrc(REGISTERS.C, cpu); 51 | case 0x0a: 52 | return rrc(REGISTERS.D, cpu); 53 | case 0x0b: 54 | return rrc(REGISTERS.E, cpu); 55 | case 0x0c: 56 | return rrc(REGISTERS.H, cpu); 57 | case 0x0d: 58 | return rrc(REGISTERS.L, cpu); 59 | case 0x0e: 60 | return rrc(cpu); 61 | case 0x1f: 62 | return rr(REGISTERS.A, cpu); 63 | case 0x18: 64 | return rr(REGISTERS.B, cpu); 65 | case 0x19: 66 | return rr(REGISTERS.C, cpu); 67 | case 0x1a: 68 | return rr(REGISTERS.D, cpu); 69 | case 0x1b: 70 | return rr(REGISTERS.E, cpu); 71 | case 0x1c: 72 | return rr(REGISTERS.H, cpu); 73 | case 0x1d: 74 | return rr(REGISTERS.L, cpu); 75 | case 0x1e: 76 | return rr(cpu); 77 | default: 78 | return false; 79 | 80 | } 81 | } 82 | 83 | private int applyRotateRC(int val, CPU cpu) { 84 | int bits = val; 85 | int lsb = bits & 0x1; 86 | bits = (bits >>> 1) & 0xff; 87 | bits = (lsb << 7) | bits; 88 | 89 | cpu.setFlag(FLAGS.C, lsb == 1); 90 | setFlags(bits, cpu); 91 | return bits; 92 | } 93 | 94 | private int applyRotateR(int val, CPU cpu) { 95 | int bits = val; 96 | int lsb = bits & 0x1; 97 | bits = (bits >>> 1) & 0xff; 98 | bits = (cpu.getFlag(FLAGS.C) << 7) | bits; 99 | cpu.setFlag(FLAGS.C, lsb == 1); 100 | 101 | setFlags(bits, cpu); 102 | return bits; 103 | } 104 | 105 | private int applyRotateLC(int val, CPU cpu) { 106 | int bits = val; 107 | int msb = bits >>> 7; 108 | cpu.setFlag(FLAGS.C, msb == 1); 109 | bits = (bits << 1) & 0xff; 110 | bits = bits | msb; 111 | 112 | setFlags(bits, cpu); 113 | return bits; 114 | } 115 | 116 | private int applyRotateL(int val, CPU cpu) { 117 | int bits = val; 118 | int msb = bits >>> 7; 119 | bits = (bits << 1) & 0xff; 120 | bits = bits | cpu.getFlag(FLAGS.C); 121 | cpu.setFlag(FLAGS.C, msb == 1); 122 | 123 | setFlags(bits, cpu); 124 | return bits; 125 | } 126 | 127 | 128 | private void setFlags(int result, CPU cpu) { 129 | cpu.setFlag(FLAGS.Z, result == 0); 130 | cpu.setFlag(FLAGS.H, false); 131 | cpu.setFlag(FLAGS.N, false); 132 | } 133 | 134 | private boolean rlc(CPU cpu) { 135 | int addr = cpu.readWordRegister(RegisterPairs.HL); 136 | int bits = cpu.readAddress(addr); 137 | int result = applyRotateLC(bits, cpu); 138 | cpu.writeAddress(addr, result); 139 | cpu.addCycles(4); 140 | return true; 141 | } 142 | 143 | 144 | private boolean rlc(REGISTERS reg, CPU cpu) { 145 | int bits = cpu.readRegister(reg); 146 | int result = applyRotateLC(bits, cpu); 147 | cpu.writeRegister(reg, result); 148 | cpu.addCycles(2); 149 | return true; 150 | } 151 | 152 | private boolean rl(REGISTERS reg, CPU cpu) { 153 | int bits = cpu.readRegister(reg); 154 | int result = applyRotateL(bits, cpu); 155 | cpu.writeRegister(reg, result); 156 | cpu.addCycles(2); 157 | return true; 158 | } 159 | 160 | private boolean rrc(REGISTERS reg, CPU cpu) { 161 | int bits = cpu.readRegister(reg); 162 | int result = applyRotateRC(bits, cpu); 163 | cpu.writeRegister(reg, result); 164 | cpu.addCycles(2); 165 | return true; 166 | } 167 | 168 | private boolean rrc(CPU cpu) { 169 | int addr = cpu.readWordRegister(RegisterPairs.HL); 170 | int bits = cpu.readAddress(addr); 171 | int result = applyRotateRC(bits, cpu); 172 | cpu.writeAddress(addr, result); 173 | cpu.addCycles(4); 174 | return true; 175 | } 176 | 177 | private boolean rr(REGISTERS reg, CPU cpu) { 178 | int bits = cpu.readRegister(reg); 179 | int result = applyRotateR(bits, cpu); 180 | cpu.writeRegister(reg, result); 181 | cpu.addCycles(2); 182 | return true; 183 | } 184 | 185 | private boolean rr(CPU cpu) { 186 | int addr = cpu.readWordRegister(RegisterPairs.HL); 187 | int bits = cpu.readAddress(addr); 188 | int result = applyRotateR(bits, cpu); 189 | cpu.writeAddress(addr, result); 190 | cpu.addCycles(4); 191 | return true; 192 | } 193 | 194 | private boolean rl(CPU cpu) { 195 | int addr = cpu.readWordRegister(RegisterPairs.HL); 196 | int bits = cpu.readAddress(addr); 197 | int result = applyRotateL(bits, cpu); 198 | cpu.writeAddress(addr, result); 199 | cpu.setFlag(FLAGS.Z, result == 0); 200 | cpu.setFlag(FLAGS.H, false); 201 | cpu.setFlag(FLAGS.N, false); 202 | cpu.addCycles(4); 203 | return true; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/Gpu.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video; 2 | 3 | import JavaBoy.cpu.interrupts.InterruptManager; 4 | import JavaBoy.cpu.interrupts.Interrupts; 5 | import JavaBoy.memory.MemorySlot; 6 | import JavaBoy.video.pixelpipeline.FIFOFetcher; 7 | import JavaBoy.video.pixelpipeline.PixelFIFO; 8 | 9 | public class Gpu implements MemorySlot { 10 | 11 | private final Renderer display; 12 | private final LCDC lcdc; 13 | private final LCDStat lcdStat; 14 | private final GpuRegisters gpuRegisters; 15 | private final Oam oam; 16 | private final InterruptManager interruptManager; 17 | private final Palette palette; 18 | private final Vram vram; 19 | private final FIFOFetcher fetcher; 20 | private final PixelFIFO oamFifo; 21 | private final PixelFIFO bgFifo; 22 | 23 | private int currentX = 0; 24 | private int currentY = 0; 25 | private Phases currentPhase = Phases.OAM_SCAN; 26 | private int cycles = 0; 27 | 28 | public Gpu(Renderer renderer, LCDC lcdc, LCDStat lcdStat, 29 | GpuRegisters gpuRegisters, Oam oam, Palette palette, Vram vram, 30 | FIFOFetcher fetcher, PixelFIFO oamFifo, PixelFIFO bgFifo, 31 | InterruptManager manager 32 | ) { 33 | this.display = renderer; 34 | this.lcdc = lcdc; 35 | this.lcdStat = lcdStat; 36 | this.interruptManager = manager; 37 | this.gpuRegisters = gpuRegisters; 38 | this.oam = oam; 39 | this.palette = palette; 40 | this.vram = vram; 41 | this.fetcher = fetcher; 42 | this.oamFifo = oamFifo; 43 | this.bgFifo = bgFifo; 44 | 45 | lcdStat.setMode(LCDStat.Modes.OAM); 46 | } 47 | 48 | public void refresh() { 49 | this.display.requestRefresh(); 50 | } 51 | 52 | public void tick() { 53 | 54 | if (!lcdc.isLCD()){ 55 | this.cycles = 0; 56 | this.currentPhase = Phases.OAM_SCAN; 57 | this.lcdStat.setMode(LCDStat.Modes.H_BLANK); 58 | fetcher.reset(0); 59 | this.currentY = 0; 60 | this.currentX = 0; 61 | return; 62 | } 63 | 64 | 65 | ++cycles; 66 | 67 | if (currentPhase == Phases.OAM_SCAN) { 68 | if (cycles == 20) { 69 | fetcher.reset(currentY); 70 | oam.getSprites(currentY, lcdc.isOBJSize()); 71 | this.currentPhase = Phases.PIXEL_TRANSFER; 72 | this.lcdStat.setMode(LCDStat.Modes.Transfer); 73 | } 74 | } else if (currentPhase == Phases.PIXEL_TRANSFER) { 75 | if (currentX < 160) { 76 | for (int a = 4; a > 0; --a) { 77 | fetcher.notifyFetcher(currentX, currentY); 78 | fetcher.tick(); 79 | if (bgFifo.canPop()) { 80 | display.renderPixel(mixFifo()); 81 | ++currentX; 82 | } 83 | if (currentX > 159) 84 | break; 85 | } 86 | } else { 87 | currentPhase = Phases.HBLANK; 88 | lcdStat.setMode(LCDStat.Modes.H_BLANK); 89 | } 90 | } else if (currentPhase == Phases.HBLANK) { 91 | if (114 - cycles == 0) { 92 | this.display.hBlank(); 93 | currentX = 0; 94 | cycles = 0; 95 | this.currentPhase = Phases.OAM_SCAN; 96 | lcdStat.setMode(LCDStat.Modes.OAM); 97 | ++currentY; 98 | } 99 | } else if (currentPhase == Phases.VBLANK) { 100 | if ((cycles / 114) > 10) { 101 | currentY = 0; 102 | cycles = 0; 103 | this.currentPhase = Phases.OAM_SCAN; 104 | lcdStat.setMode(LCDStat.Modes.OAM); 105 | } else if ((cycles % 114) == 0) { 106 | ++currentY; 107 | } 108 | } 109 | gpuRegisters.setLy(currentY); 110 | lcdStat.setCoincidenceFlag( 111 | gpuRegisters.getLy() == gpuRegisters.getLyc()); 112 | 113 | if (currentY == 144) { 114 | 115 | interruptManager.requestInterrupt(Interrupts.V_BLANK); 116 | this.currentPhase = Phases.VBLANK; 117 | lcdStat.setMode(LCDStat.Modes.V_BLANK); 118 | } 119 | 120 | checkStatInterrupts(); 121 | 122 | 123 | 124 | } 125 | 126 | 127 | private void checkStatInterrupts() { 128 | if ((currentPhase == Phases.OAM_SCAN && lcdStat.isOAMInterrupt()) 129 | || (currentPhase == Phases.HBLANK && lcdStat.ishBlankInterrupt()) 130 | || (lcdStat.isCoincidenceInterrupt() && lcdStat.isCoincidenceFlag()) 131 | || (lcdStat.isvBlankInterrupt() && currentPhase == Phases.VBLANK) 132 | ) 133 | interruptManager.requestInterrupt(Interrupts.LCD_STAT); 134 | } 135 | 136 | private Palette.GreyShades mixFifo() { 137 | Palette.GreyShades bgShade; 138 | 139 | if (lcdc.isPriority()) { 140 | bgShade = bgFifo.getPixel(); 141 | } else { 142 | bgShade = palette.getPaletteShade(0, bgFifo.peekPalette()); 143 | bgFifo.getPixel(); 144 | 145 | } 146 | boolean didOamPop = false; 147 | if (lcdc.isOBJEnable() && oamFifo.canPop()) { 148 | if (oamFifo.peekIsAboveBG() || bgShade == Palette.GreyShades.TRANSPARENT || bgShade == Palette.GreyShades.WHITE) { 149 | Palette.GreyShades oamShade = oamFifo.getPixel(); 150 | didOamPop = true; 151 | if (oamShade != Palette.GreyShades.TRANSPARENT) 152 | return oamShade; 153 | } 154 | } 155 | 156 | if (oamFifo.canPop() && !didOamPop) 157 | oamFifo.getPixel(); 158 | 159 | return bgShade; 160 | 161 | } 162 | 163 | 164 | @Override 165 | public int getByte(int address) { 166 | return getSlot(address).getByte(address); 167 | } 168 | 169 | @Override 170 | public void setByte(int address, int value) { 171 | getSlot(address).setByte(address, value); 172 | } 173 | 174 | private MemorySlot getSlot(int address) { 175 | if (lcdc.hasAddressInSlot(address)) 176 | return this.lcdc; 177 | if (oam.hasAddressInSlot(address)) 178 | return this.oam; 179 | if (lcdStat.hasAddressInSlot(address)) 180 | return lcdStat; 181 | if (gpuRegisters.hasAddressInSlot(address)) 182 | return gpuRegisters; 183 | if (vram.hasAddressInSlot(address)) 184 | return vram; 185 | if (palette.hasAddressInSlot(address)) 186 | return palette; 187 | 188 | return null; 189 | } 190 | 191 | @Override 192 | public boolean hasAddressInSlot(int address) { 193 | return getSlot(address) != null; 194 | } 195 | 196 | public enum Phases { 197 | OAM_SCAN, PIXEL_TRANSFER, HBLANK, VBLANK 198 | 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/gui/GBGui.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.gui; 2 | 3 | import JavaBoy.input.Joypad; 4 | import JavaBoy.video.Palette; 5 | import JavaBoy.video.Renderer; 6 | 7 | import java.awt.*; 8 | import java.awt.event.KeyAdapter; 9 | import java.awt.event.KeyEvent; 10 | import java.awt.event.WindowAdapter; 11 | import java.awt.event.WindowEvent; 12 | import java.awt.image.BufferedImage; 13 | import java.awt.image.DataBufferInt; 14 | 15 | 16 | public class GBGui implements Renderer { 17 | 18 | 19 | final int pixelSize = 2; 20 | private final int screenX = 160; 21 | private final int screenY = 144; 22 | private final int[] rgbWriteBuffer; 23 | private final Joypad joypad; 24 | BufferedImage readBuffer; 25 | private int x = 0; 26 | private int y = 0; 27 | private long frameTime; 28 | private int frames = 0; 29 | private boolean newFrame = true; 30 | private Canvas screen; 31 | private final Color WHITE = new Color(224, 248, 208); 32 | private final Color LIGHT_GREY = new Color(136, 192,112); 33 | private final Color DARK_GREY = new Color(52,104,86); 34 | private final Color BLACK = new Color(8, 24, 32); 35 | 36 | public GBGui(Joypad joypad) { 37 | GraphicsEnvironment env = 38 | GraphicsEnvironment.getLocalGraphicsEnvironment(); 39 | GraphicsDevice device = env.getDefaultScreenDevice(); 40 | GraphicsConfiguration config = device.getDefaultConfiguration(); 41 | 42 | readBuffer = config.createCompatibleImage(screenX, 43 | screenY, 44 | Transparency.OPAQUE); 45 | rgbWriteBuffer = new int[screenY * screenX]; 46 | this.joypad = joypad; 47 | } 48 | 49 | @Override 50 | public void requestRefresh() { 51 | 52 | if (newFrame) { 53 | frameTime = System.currentTimeMillis(); 54 | newFrame = false; 55 | } 56 | if ((System.currentTimeMillis() - frameTime) >= 1000) { 57 | System.out.println("Screen FPS: " + frames); 58 | frames = 0; 59 | newFrame = true; 60 | } 61 | 62 | screen.repaint(); 63 | } 64 | 65 | @Override 66 | public void renderPixel(Palette.GreyShades shade) { 67 | 68 | Color colour = SystemColor.MAGENTA; 69 | switch (shade) { 70 | case WHITE: 71 | colour = WHITE; 72 | break; 73 | case LIGHT_GREY: 74 | colour = LIGHT_GREY; 75 | break; 76 | case DARK_GREY: 77 | colour = DARK_GREY; 78 | break; 79 | case BLACK: 80 | colour = BLACK; 81 | break; 82 | case TRANSPARENT: 83 | colour = SystemColor.MAGENTA; 84 | break; 85 | } 86 | rgbWriteBuffer[(y * screenX) + x] = colour.getRGB(); 87 | ++x; 88 | } 89 | 90 | @Override 91 | public void hBlank() { 92 | x = 0; 93 | ++y; 94 | if (y == screenY) 95 | vBlank(); 96 | } 97 | 98 | @Override 99 | public void vBlank() { 100 | x = 0; 101 | y = 0; 102 | int[] data = 103 | ((DataBufferInt) readBuffer.getRaster().getDataBuffer()).getData(); 104 | System.arraycopy(rgbWriteBuffer, 0, data, 0, data.length); 105 | ++frames; 106 | } 107 | 108 | 109 | public void show() { 110 | 111 | Frame frame = new Frame(); 112 | frame.setTitle("JavaBoy !"); 113 | frame.setSize(700, 700); 114 | screen = new GBScreen(); 115 | screen.setSize(pixelSize * screenX, pixelSize * screenY); 116 | frame.add(screen); 117 | 118 | frame.pack(); 119 | frame.setVisible(true); 120 | 121 | screen.setVisible(true); 122 | frame.addWindowListener(new WindowAdapter() { 123 | @Override 124 | public void windowClosing(WindowEvent e) { 125 | System.exit(0); 126 | } 127 | }); 128 | 129 | 130 | screen.addKeyListener(new KeyAdapter() { 131 | private int currentKey; 132 | 133 | @Override 134 | public void keyReleased(KeyEvent e) { 135 | if (e.getKeyCode() == currentKey){ 136 | currentKey = -1; 137 | switch (e.getKeyCode()) { 138 | case KeyEvent.VK_UP: 139 | joypad.buttonReleased(Joypad.Buttons.UP); 140 | break; 141 | case KeyEvent.VK_DOWN: 142 | joypad.buttonReleased(Joypad.Buttons.DOWN); 143 | break; 144 | case KeyEvent.VK_LEFT: 145 | joypad.buttonReleased(Joypad.Buttons.LEFT); 146 | break; 147 | case KeyEvent.VK_RIGHT: 148 | joypad.buttonReleased( 149 | Joypad.Buttons.RIGHT); 150 | break; 151 | 152 | case KeyEvent.VK_ENTER: 153 | joypad.buttonReleased(Joypad.Buttons.START); 154 | break; 155 | case KeyEvent.VK_A: 156 | joypad.buttonReleased(Joypad.Buttons.BUTTON_A); 157 | break; 158 | case KeyEvent.VK_S: 159 | joypad.buttonReleased(Joypad.Buttons.BUTTON_B); 160 | break; 161 | case KeyEvent.VK_W: 162 | joypad.buttonReleased(Joypad.Buttons.SELECT); 163 | break; 164 | } 165 | } 166 | } 167 | 168 | @Override 169 | public void keyPressed(KeyEvent e) { 170 | if (e.getKeyCode() != currentKey) { 171 | currentKey = e.getKeyCode(); 172 | switch (e.getKeyCode()) { 173 | case KeyEvent.VK_UP: 174 | joypad.buttonPressed(Joypad.Buttons.UP); 175 | break; 176 | case KeyEvent.VK_DOWN: 177 | joypad.buttonPressed(Joypad.Buttons.DOWN); 178 | break; 179 | case KeyEvent.VK_LEFT: 180 | joypad.buttonPressed(Joypad.Buttons.LEFT); 181 | break; 182 | case KeyEvent.VK_RIGHT: 183 | joypad.buttonPressed( 184 | Joypad.Buttons.RIGHT); 185 | break; 186 | 187 | case KeyEvent.VK_ENTER: 188 | joypad.buttonPressed(Joypad.Buttons.START); 189 | break; 190 | case KeyEvent.VK_A: 191 | joypad.buttonPressed(Joypad.Buttons.BUTTON_A); 192 | break; 193 | case KeyEvent.VK_S: 194 | joypad.buttonPressed(Joypad.Buttons.BUTTON_B); 195 | break; 196 | case KeyEvent.VK_W: 197 | joypad.buttonPressed(Joypad.Buttons.SELECT); 198 | break; 199 | } 200 | } 201 | } 202 | }); 203 | 204 | } 205 | 206 | public class GBScreen extends Canvas { 207 | 208 | @Override 209 | public void paint(Graphics g) { 210 | g.drawImage(readBuffer, 0, 0, screenX * pixelSize, 211 | screenY * pixelSize, null); 212 | 213 | 214 | } 215 | 216 | @Override 217 | public void update(Graphics g) { 218 | paint(g); 219 | 220 | } 221 | } 222 | 223 | 224 | } 225 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/CPU.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu; 2 | 3 | import JavaBoy.cpu.flags.FLAGS; 4 | import JavaBoy.cpu.instructions.Instruction; 5 | import JavaBoy.cpu.interrupts.InterruptManager; 6 | import JavaBoy.cpu.registers.RegisterBank; 7 | import JavaBoy.cpu.registers.RegisterPairs; 8 | import JavaBoy.memory.Dma; 9 | import JavaBoy.memory.MemoryMap; 10 | import JavaBoy.timer.Timer; 11 | import JavaBoy.video.Gpu; 12 | 13 | import java.io.File; 14 | import java.io.FileWriter; 15 | 16 | public class CPU { 17 | private final MemoryMap mmu; 18 | private final Instruction[] instructions; 19 | private int cycles = 0; 20 | private long time; 21 | private final RegisterBank registers; 22 | private final InterruptManager interruptManager; 23 | private final Gpu gpu; 24 | private final Timer timer; 25 | private final Dma dma; 26 | File file = new File("/home/damilola/gameboy.txt"); 27 | FileWriter writer; 28 | private boolean halted = false; 29 | 30 | public CPU(MemoryMap mmu, Instruction[] instructions, 31 | RegisterBank registers, InterruptManager interruptManager, 32 | Timer timer, Dma dma, Gpu gpu) { 33 | try { 34 | writer = new FileWriter(file); 35 | } catch (Exception err) { 36 | System.err.println(err.getMessage()); 37 | } 38 | this.timer = timer; 39 | this.gpu = gpu; 40 | this.mmu = mmu; 41 | this.dma = dma; 42 | this.interruptManager = interruptManager; 43 | this.instructions = instructions; 44 | this.registers = registers; 45 | this.setPC(0x0); 46 | } 47 | 48 | 49 | public void run() { 50 | int opcode; 51 | time = System.currentTimeMillis(); 52 | boolean canRun = true; 53 | while (canRun) { 54 | boolean didInterrupt = interruptManager.handleInterrupts(this); 55 | if (halted) { 56 | if (didInterrupt) { 57 | halted = false; 58 | } else if ( 59 | interruptManager.hasServiceableInterrupts() && 60 | !interruptManager.isMasterEnabled()) { 61 | int currentPc = getPC(); 62 | halted = false; 63 | tryExecute(readPC()); 64 | setPC(currentPc); 65 | } 66 | addCycles(); 67 | } else { 68 | 69 | opcode = readPC(); 70 | canRun = tryExecute(opcode); 71 | } 72 | 73 | if (cycles >= 17476){ 74 | cycles = 0; 75 | 76 | this.gpu.refresh(); 77 | while (System.currentTimeMillis() - time < 16.67 ){ 78 | 79 | } 80 | time = System.currentTimeMillis(); 81 | } 82 | } 83 | //printState(opcode); 84 | 85 | 86 | } 87 | 88 | 89 | public void addCycles() { 90 | addCycles(1); 91 | } 92 | 93 | public void addCycles(int multiple) { 94 | for (int count = multiple; count > 0; --count) { 95 | timer.tick(); 96 | dma.tick(); 97 | gpu.tick(); 98 | } 99 | cycles+= multiple; 100 | 101 | } 102 | 103 | private boolean tryExecute(int opcode) { 104 | //printState(opcode); 105 | if (getPC() == 0xc002) { 106 | System.out.println("hey"); 107 | } 108 | boolean executed = false; 109 | if (opcode == 0x76) { 110 | halted = true; 111 | executed = true; 112 | addCycles(); 113 | } else { 114 | for (int idx = 0; idx < instructions.length; ++idx){ 115 | executed = instructions[idx].execute(opcode, this); 116 | if (executed) 117 | break; 118 | } 119 | } 120 | 121 | if (!executed) { 122 | System.err.println("Unknown Instruction: " + getHex(opcode)); 123 | } 124 | 125 | return true; 126 | 127 | //return result.isPresent(); 128 | 129 | } 130 | 131 | 132 | public int readPC() { 133 | int addr = this.registers.getPC().read(); 134 | this.registers.getPC().increment(); 135 | return getByte(addr); 136 | 137 | } 138 | 139 | private int getByte(int addr) { 140 | if(dma.canAccess(addr)) 141 | return this.mmu.readByte(addr); 142 | else 143 | return 0; 144 | } 145 | 146 | private void writeByte(int addr, int value) { 147 | if(dma.canAccess(addr)) 148 | this.mmu.setByte(addr, value); 149 | } 150 | 151 | 152 | public int readWordPC() { 153 | int lsb = this.readPC(); 154 | int msb = this.readPC(); 155 | return (msb << 8) | lsb; 156 | } 157 | 158 | public boolean isFlag(FLAGS flag) { 159 | return this.registers.getFlags().isFlag(flag); 160 | } 161 | 162 | public void writeAddress(int address, int value) { 163 | writeByte(address, value); 164 | } 165 | 166 | public int readAddress(int address) { 167 | return getByte(address); 168 | } 169 | 170 | private void printState(int opcode) { 171 | String string = ""; 172 | string += String.format("A: %s F: %s (AF: %s)\n", 173 | getHex(readRegister(REGISTERS.A)), 174 | getHex(readRegister(REGISTERS.F)), 175 | getHex(readWordRegister(RegisterPairs.AF))); 176 | 177 | string += String.format("B: %s C: %s (BC: %s)\n", 178 | getHex(readRegister(REGISTERS.B)), 179 | getHex(readRegister(REGISTERS.C)), 180 | getHex(readWordRegister(RegisterPairs.BC))); 181 | 182 | string += String.format("D: %s E: %s (DE: %s)\n", 183 | getHex(readRegister(REGISTERS.D)), 184 | getHex(readRegister(REGISTERS.E)), 185 | getHex(readWordRegister(RegisterPairs.DE))); 186 | 187 | string += String.format("H: %s L: %s (HL: %s)\n", 188 | getHex(readRegister(REGISTERS.H)), 189 | getHex(readRegister(REGISTERS.L)), 190 | getHex(readWordRegister(RegisterPairs.HL))); 191 | 192 | string += String.format("PC: %s SP: %s \n", getHex(getPC()), 193 | getHex(getSP())); 194 | 195 | string += String.format("Opcode: %s \n", getHex(opcode)); 196 | 197 | 198 | string += String.format("TIMA: %s \n", getHex(timer.getTIMA())); 199 | 200 | string += String.format("F: [%s%s%s%s]\n\n", 201 | isFlag(FLAGS.Z) ? "Z" : "-", 202 | isFlag(FLAGS.H) ? "H" : "-", 203 | isFlag(FLAGS.N) ? "N" : "-", 204 | isFlag(FLAGS.C) ? "C" : "-" 205 | ); 206 | try { 207 | writer.write(string); 208 | writer.flush(); 209 | } catch (Exception err) { 210 | System.err.println("Couldn't write " + err.getMessage()); 211 | } 212 | 213 | 214 | } 215 | 216 | private String getHex(int i) { 217 | return "0x" + Integer.toHexString(i).toUpperCase(); 218 | } 219 | 220 | public int getSP() { 221 | return this.registers.getSP().read(); 222 | } 223 | 224 | public void setSP(int val) { 225 | this.registers.getSP().write(val); 226 | } 227 | 228 | public void pushSP(int value) { 229 | this.registers.getSP().decrement(); 230 | int addr = this.getSP(); 231 | writeByte(addr, value); 232 | } 233 | 234 | public int popSP() { 235 | int addr = this.getSP(); 236 | int value = this.readAddress(addr); 237 | this.registers.getSP().increment(); 238 | return value; 239 | } 240 | 241 | public void writeRegister(REGISTERS reg, int value) { 242 | this.registers.writeRegister(reg, value); 243 | } 244 | 245 | public int readRegister(REGISTERS reg) { 246 | return this.registers.readRegister(reg); 247 | } 248 | 249 | public int readWordRegister(RegisterPairs pair) { 250 | return this.registers.readRegister(pair); 251 | } 252 | 253 | public void writeWordRegister(RegisterPairs pair, int value) { 254 | this.registers.writeRegister(pair, value); 255 | } 256 | 257 | 258 | public int getFlag(FLAGS flag) { 259 | return this.registers.getFlags().getFlag(flag); 260 | } 261 | 262 | public void setFlag(FLAGS flag, boolean value) { 263 | this.registers.getFlags().setFlag(flag, value); 264 | } 265 | 266 | public void enableInterrupts() { 267 | this.interruptManager.enableInterrupts(); 268 | } 269 | 270 | public void disableInterrupts() { 271 | this.interruptManager.disableInterrupts(); 272 | } 273 | 274 | public int getPC() { 275 | return this.registers.getPC().read(); 276 | } 277 | 278 | public void setPC(int word) { 279 | this.registers.getPC().write(word); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/video/pixelpipeline/FIFOFetcher.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.video.pixelpipeline; 2 | 3 | import JavaBoy.video.*; 4 | 5 | public class FIFOFetcher { 6 | 7 | final PixelFIFO oamFifo; 8 | final PixelFIFO bgFifo; 9 | final Vram vram; 10 | final GpuRegisters gpuRegisters; 11 | final LCDC lcdc; 12 | int currentX = -1; 13 | int currentY = -1; 14 | private final Pixel[] pixelLine = { 15 | new Pixel(), 16 | new Pixel(), 17 | new Pixel(), 18 | new Pixel(), 19 | new Pixel(), 20 | new Pixel(), 21 | new Pixel(), 22 | new Pixel() 23 | }; 24 | private boolean requestedPush = false; 25 | private final Oam.SpriteAttribute[] oamSpritesBuffer; 26 | private FetcherStages currentStage = FetcherStages.GET_TILE; 27 | private FetchTypes currentType; 28 | private int bgOffsetX = 0; 29 | private int bgOffsetY = 0; 30 | private FetchTypes wasFetching; 31 | private boolean requestedSpriteFetch = false; 32 | private Oam.SpriteAttribute nextSprite; 33 | private int cycles = 0; 34 | 35 | 36 | public FIFOFetcher(PixelFIFO oamFifo, 37 | PixelFIFO bgFifo, 38 | Vram vram, 39 | LCDC lcdc, 40 | GpuRegisters gpuRegisters, 41 | Oam.SpriteAttribute[] oamSpritesBuffer 42 | ) { 43 | this.oamFifo = oamFifo; 44 | this.bgFifo = bgFifo; 45 | this.oamSpritesBuffer = oamSpritesBuffer; 46 | this.vram = vram; 47 | this.lcdc = lcdc; 48 | this.gpuRegisters = gpuRegisters; 49 | } 50 | 51 | 52 | public void tick() { 53 | ++cycles; 54 | switch (currentStage) { 55 | case GET_TILE: 56 | case GET_TILE_LOW: 57 | case SLEEP: 58 | if (cycles == 2) { 59 | this.cycles = 0; 60 | moveToNextStage(); 61 | } 62 | break; 63 | case GET_TILE_HIGH: 64 | if (cycles == 2) { 65 | this.cycles = 0; 66 | if (currentType != FetchTypes.SPRITE) { 67 | int[] line = vram.getTileLineBG(bgOffsetX, bgOffsetY, 68 | currentType == FetchTypes.WINDOW 69 | ? 70 | lcdc.getWindowMap() 71 | : 72 | lcdc.getBGMap(), 73 | lcdc.getBGWindowAddressing()); 74 | 75 | for (int pixelIdx = 0; pixelIdx < 8; ++pixelIdx) { 76 | pixelLine[pixelIdx].setColour(line[pixelIdx]); 77 | pixelLine[pixelIdx].setPalette(Palettes.BGB); 78 | } 79 | }else{ 80 | int lineIndex =currentY - (nextSprite.getYPosition() - 16) ; 81 | if (nextSprite.isYFlipped()) 82 | lineIndex = 7 - lineIndex; 83 | int [] line = vram.getTile(nextSprite.getTileForLine(currentY), lineIndex, LCDC.AddressingModes.M8000); 84 | if (!nextSprite.isXFlipped()) { 85 | for (int pixelIdx = 0; pixelIdx < 8; ++pixelIdx) { 86 | pixelLine[pixelIdx].setColour(line[pixelIdx]); 87 | pixelLine[pixelIdx].setPalette( 88 | nextSprite.getPalette()); 89 | pixelLine[pixelIdx].setAboveBG( 90 | nextSprite.isAboveBG()); 91 | } 92 | }else { 93 | for (int pixelIdx = 0; pixelIdx < 8; ++pixelIdx) { 94 | pixelLine[7 - pixelIdx].setColour(line[pixelIdx]); 95 | pixelLine[7 - pixelIdx].setPalette( 96 | nextSprite.getPalette()); 97 | pixelLine[7 - pixelIdx].setAboveBG( 98 | nextSprite.isAboveBG()); 99 | } 100 | } 101 | } 102 | requestedPush = true; 103 | pushPixels(); 104 | moveToNextStage(); 105 | } 106 | break; 107 | case PUSH: 108 | if (!requestedPush) { 109 | moveToNextStage(); 110 | }else { 111 | pushPixels(); 112 | } 113 | this.cycles = 0; 114 | break; 115 | } 116 | } 117 | 118 | private void pushPixels(){ 119 | if (!requestedPush) 120 | return; 121 | if (currentType == FetchTypes.SPRITE && !oamFifo.canPush()) 122 | return; 123 | if (currentType != FetchTypes.SPRITE && !bgFifo.canPush()) 124 | return; 125 | 126 | if (currentType != FetchTypes.SPRITE){ 127 | bgOffsetX+= 8; 128 | bgFifo.push(pixelLine); 129 | }else { 130 | oamFifo.pushOverlay(pixelLine); 131 | requestedSpriteFetch = false; 132 | this.currentType = wasFetching; 133 | this.bgFifo.enablePopping(); 134 | } 135 | requestedPush = false; 136 | if (requestedSpriteFetch){ 137 | this.wasFetching = this.currentType; 138 | this.currentType = FetchTypes.SPRITE; 139 | } 140 | } 141 | 142 | public void notifyFetcher(int x, int y) { 143 | 144 | if (requestedSpriteFetch) 145 | return; 146 | 147 | if (lcdc.isWindowEnabled() && currentType != FetchTypes.WINDOW) { 148 | if (gpuRegisters.getWy() >= y && x >= gpuRegisters.getWx() - 7) { 149 | if (currentType == FetchTypes.BACKGROUND){ 150 | this.bgOffsetX = 0; 151 | if (gpuRegisters.getWx() < 7) 152 | this.bgOffsetX = gpuRegisters.getWx(); 153 | this.currentX = x; 154 | this.currentY = y; 155 | this.bgOffsetY = 0; 156 | this.cycles = 0; 157 | this.bgFifo.clear(); 158 | } 159 | 160 | this.currentStage = FetcherStages.GET_TILE; 161 | this.currentType = FetchTypes.WINDOW; 162 | } 163 | } 164 | Oam.SpriteAttribute foundSprite = checkForSpritesOnX(x); 165 | if (foundSprite != null && lcdc.isOBJEnable()) { 166 | this.nextSprite = foundSprite; 167 | this.requestedSpriteFetch = true; 168 | this.bgFifo.disablePopping(); 169 | if (!bgFifo.canPush()){ 170 | this.requestedPush = false; 171 | this.wasFetching = currentType; 172 | this.currentType = FetchTypes.SPRITE; 173 | this.currentStage = FetcherStages.GET_TILE; 174 | this.cycles = 0; 175 | } 176 | } 177 | } 178 | public void reset(int line){ 179 | bgFifo.clear(); 180 | oamFifo.clear(); 181 | this.bgOffsetX = gpuRegisters.getScx() ; 182 | this.bgOffsetY = gpuRegisters.getScy() + line; 183 | this.currentX = 0; 184 | this.currentY = line; 185 | this.currentStage = FetcherStages.GET_TILE; 186 | this.currentType = FetchTypes.BACKGROUND; 187 | this.requestedSpriteFetch = false; 188 | this.requestedPush = false; 189 | this.cycles = 0; 190 | } 191 | private Oam.SpriteAttribute checkForSpritesOnX(int x) { 192 | 193 | for (int idx = 0; idx < oamSpritesBuffer.length; ++idx){ 194 | if (oamSpritesBuffer[idx].isDirty()) 195 | continue; 196 | if (oamSpritesBuffer[idx].getXPosition() - 8 == x) { 197 | return oamSpritesBuffer[idx]; 198 | } 199 | } 200 | return null; 201 | } 202 | 203 | 204 | 205 | public void setCurrentX(int x) { 206 | this.currentX = x; 207 | } 208 | 209 | public void setCurrentY(int y) { 210 | this.currentY = y; 211 | } 212 | 213 | private void moveToNextStage() { 214 | switch (currentStage){ 215 | case GET_TILE: 216 | this.currentStage = FetcherStages.GET_TILE_LOW; 217 | break; 218 | case GET_TILE_LOW: 219 | this.currentStage = FetcherStages.GET_TILE_HIGH; 220 | break; 221 | case GET_TILE_HIGH: 222 | this.currentStage = FetcherStages.SLEEP; 223 | break; 224 | case SLEEP: 225 | this.currentStage = FetcherStages.PUSH; 226 | break; 227 | case PUSH: 228 | this.currentStage = FetcherStages.GET_TILE; 229 | break; 230 | } 231 | } 232 | 233 | private enum FetcherStages { 234 | GET_TILE, GET_TILE_LOW, GET_TILE_HIGH,PUSH, SLEEP 235 | } 236 | 237 | private enum FetchTypes { 238 | WINDOW, BACKGROUND, SPRITE 239 | } 240 | 241 | 242 | } 243 | -------------------------------------------------------------------------------- /src/main/java/JavaBoy/cpu/instructions/Load.java: -------------------------------------------------------------------------------- 1 | package JavaBoy.cpu.instructions; 2 | 3 | import JavaBoy.cpu.CPU; 4 | import JavaBoy.cpu.REGISTERS; 5 | import JavaBoy.cpu.flags.FLAGS; 6 | import JavaBoy.cpu.registers.RegisterPairs; 7 | 8 | import static JavaBoy.utils.BitUtils.getLsb; 9 | import static JavaBoy.utils.BitUtils.getMsb; 10 | 11 | public class Load implements Instruction { 12 | 13 | @Override 14 | public boolean execute(int opcode, CPU cpu) { 15 | switch (opcode) { 16 | //Register To Register Loads 17 | case 0x7f: 18 | return load(REGISTERS.A, REGISTERS.A, cpu); 19 | case 0x78: 20 | return load(REGISTERS.A, REGISTERS.B, cpu); 21 | case 0x79: 22 | return load(REGISTERS.A, REGISTERS.C, cpu); 23 | case 0x7a: 24 | return load(REGISTERS.A, REGISTERS.D, cpu); 25 | case 0x7b: 26 | return load(REGISTERS.A, REGISTERS.E, cpu); 27 | case 0x7c: 28 | return load(REGISTERS.A, REGISTERS.H, cpu); 29 | case 0x7d: 30 | return load(REGISTERS.A, REGISTERS.L, cpu); 31 | case 0x47: 32 | return load(REGISTERS.B, REGISTERS.A, cpu); 33 | case 0x40: 34 | return load(REGISTERS.B, REGISTERS.B, cpu); 35 | case 0x41: 36 | return load(REGISTERS.B, REGISTERS.C, cpu); 37 | case 0x42: 38 | return load(REGISTERS.B, REGISTERS.D, cpu); 39 | case 0x43: 40 | return load(REGISTERS.B, REGISTERS.E, cpu); 41 | case 0x44: 42 | return load(REGISTERS.B, REGISTERS.H, cpu); 43 | case 0x45: 44 | return load(REGISTERS.B, REGISTERS.L, cpu); 45 | case 0x4f: 46 | return load(REGISTERS.C, REGISTERS.A, cpu); 47 | case 0x48: 48 | return load(REGISTERS.C, REGISTERS.B, cpu); 49 | case 0x49: 50 | return load(REGISTERS.C, REGISTERS.C, cpu); 51 | case 0x4a: 52 | return load(REGISTERS.C, REGISTERS.D, cpu); 53 | case 0x4b: 54 | return load(REGISTERS.C, REGISTERS.E, cpu); 55 | case 0x4c: 56 | return load(REGISTERS.C, REGISTERS.H, cpu); 57 | case 0x4d: 58 | return load(REGISTERS.C, REGISTERS.L, cpu); 59 | case 0x57: 60 | return load(REGISTERS.D, REGISTERS.A, cpu); 61 | case 0x50: 62 | return load(REGISTERS.D, REGISTERS.B, cpu); 63 | case 0x51: 64 | return load(REGISTERS.D, REGISTERS.C, cpu); 65 | case 0x52: 66 | return load(REGISTERS.D, REGISTERS.D, cpu); 67 | case 0x53: 68 | return load(REGISTERS.D, REGISTERS.E, cpu); 69 | case 0x54: 70 | return load(REGISTERS.D, REGISTERS.H, cpu); 71 | case 0x55: 72 | return load(REGISTERS.D, REGISTERS.L, cpu); 73 | case 0x5f: 74 | return load(REGISTERS.E, REGISTERS.A, cpu); 75 | case 0x58: 76 | return load(REGISTERS.E, REGISTERS.B, cpu); 77 | case 0x59: 78 | return load(REGISTERS.E, REGISTERS.C, cpu); 79 | case 0x5a: 80 | return load(REGISTERS.E, REGISTERS.D, cpu); 81 | case 0x5b: 82 | return load(REGISTERS.E, REGISTERS.E, cpu); 83 | case 0x5c: 84 | return load(REGISTERS.E, REGISTERS.H, cpu); 85 | case 0x5d: 86 | return load(REGISTERS.E, REGISTERS.L, cpu); 87 | case 0x67: 88 | return load(REGISTERS.H, REGISTERS.A, cpu); 89 | case 0x60: 90 | return load(REGISTERS.H, REGISTERS.B, cpu); 91 | case 0x61: 92 | return load(REGISTERS.H, REGISTERS.C, cpu); 93 | case 0x62: 94 | return load(REGISTERS.H, REGISTERS.D, cpu); 95 | case 0x63: 96 | return load(REGISTERS.H, REGISTERS.E, cpu); 97 | case 0x64: 98 | return load(REGISTERS.H, REGISTERS.H, cpu); 99 | case 0x65: 100 | return load(REGISTERS.H, REGISTERS.L, cpu); 101 | case 0x6f: 102 | return load(REGISTERS.L, REGISTERS.A, cpu); 103 | case 0x68: 104 | return load(REGISTERS.L, REGISTERS.B, cpu); 105 | case 0x69: 106 | return load(REGISTERS.L, REGISTERS.C, cpu); 107 | case 0x6a: 108 | return load(REGISTERS.L, REGISTERS.D, cpu); 109 | case 0x6b: 110 | return load(REGISTERS.L, REGISTERS.E, cpu); 111 | case 0x6c: 112 | return load(REGISTERS.L, REGISTERS.H, cpu); 113 | case 0x6d: 114 | return load(REGISTERS.L, REGISTERS.L, cpu); 115 | //LD r, n immediate byte to register loads 116 | case 0x3e: 117 | return load(REGISTERS.A, cpu); 118 | case 0x06: 119 | return load(REGISTERS.B, cpu); 120 | case 0x0e: 121 | return load(REGISTERS.C, cpu); 122 | case 0x16: 123 | return load(REGISTERS.D, cpu); 124 | case 0x1e: 125 | return load(REGISTERS.E, cpu); 126 | case 0x26: 127 | return load(REGISTERS.H, cpu); 128 | case 0x2e: 129 | return load(REGISTERS.L, cpu); 130 | //LD (HL), r register into (HL) 131 | case 0x77: 132 | return loadHL(REGISTERS.A, cpu); 133 | case 0x70: 134 | return loadHL(REGISTERS.B, cpu); 135 | case 0x71: 136 | return loadHL(REGISTERS.C, cpu); 137 | case 0x72: 138 | return loadHL(REGISTERS.D, cpu); 139 | case 0x73: 140 | return loadHL(REGISTERS.E, cpu); 141 | case 0x74: 142 | return loadHL(REGISTERS.H, cpu); 143 | case 0x75: 144 | return loadHL(REGISTERS.L, cpu); 145 | //LD (HL), n imm into (HL) 146 | case 0x36: 147 | return loadHL(cpu); 148 | //LD r, (HL) (HL) into r 149 | case 0x7e: 150 | return loadRegHL(REGISTERS.A, cpu); 151 | case 0x46: 152 | return loadRegHL(REGISTERS.B, cpu); 153 | case 0x4e: 154 | return loadRegHL(REGISTERS.C, cpu); 155 | case 0x56: 156 | return loadRegHL(REGISTERS.D, cpu); 157 | case 0x5e: 158 | return loadRegHL(REGISTERS.E, cpu); 159 | case 0x66: 160 | return loadRegHL(REGISTERS.H, cpu); 161 | case 0x6e: 162 | return loadRegHL(REGISTERS.L, cpu); 163 | //LD A, (SS) (SS) into A 164 | case 0x0a: 165 | return loadA(RegisterPairs.BC, cpu); 166 | case 0x1a: 167 | return loadA(RegisterPairs.DE, cpu); 168 | case 0xfa: 169 | return loadAImmWord(cpu); 170 | //LD (SS), A A into (SS) 171 | case 0x02: 172 | return loadPair(RegisterPairs.BC, cpu); 173 | case 0x12: 174 | return loadPair(RegisterPairs.DE, cpu); 175 | case 0xea: 176 | return loadImmWordA(cpu); 177 | // LD A,(FF00+n) 178 | case 0xf0: 179 | return loadAImm(cpu); 180 | //(FF00+n),A 181 | case 0xe0: 182 | return loadImmA(cpu); 183 | case 0xf2: 184 | return loadOffsetC(cpu); 185 | case 0xe2: 186 | return loadOffsetA(cpu); 187 | case 0x22: 188 | return loadHLI(cpu); 189 | case 0x32: 190 | return loadHLD(cpu); 191 | case 0x2a: 192 | return loadAHLI(cpu); 193 | case 0x3a: 194 | return loadAHLD(cpu); 195 | //16 bit loads 196 | case 0x01: 197 | return load16(RegisterPairs.BC, cpu); 198 | case 0x11: 199 | return load16(RegisterPairs.DE, cpu); 200 | case 0x21: 201 | return load16(RegisterPairs.HL, cpu); 202 | case 0x31: 203 | return load16SP(cpu); 204 | case 0xf9: 205 | return load16SPHL(cpu); 206 | case 0x08: 207 | return load16SPAddr(cpu); 208 | case 0xf8: 209 | return ldhl(cpu); 210 | default: 211 | return false; 212 | 213 | } 214 | 215 | 216 | } 217 | 218 | 219 | private boolean load(REGISTERS reg1, REGISTERS reg2, CPU cpu) { 220 | int value = cpu.readRegister(reg2); 221 | cpu.writeRegister(reg1, value); 222 | cpu.addCycles(); 223 | return true; 224 | } 225 | 226 | private boolean load(REGISTERS reg, CPU cpu) { 227 | int value = cpu.readPC(); 228 | cpu.writeRegister(reg, value); 229 | cpu.addCycles(3); 230 | return true; 231 | } 232 | 233 | private boolean loadRegHL(REGISTERS reg, CPU cpu) { 234 | int addr = cpu.readWordRegister(RegisterPairs.HL); 235 | int value = cpu.readAddress(addr); 236 | 237 | cpu.writeRegister(reg, value); 238 | cpu.addCycles(2); 239 | return true; 240 | } 241 | 242 | 243 | private boolean loadHL(REGISTERS reg, CPU cpu) { 244 | int addr = cpu.readWordRegister(RegisterPairs.HL); 245 | int value = cpu.readRegister(reg); 246 | 247 | cpu.writeAddress(addr, value); 248 | cpu.addCycles(2); 249 | return true; 250 | } 251 | 252 | private boolean loadHL(CPU cpu) { 253 | int addr = cpu.readWordRegister(RegisterPairs.HL); 254 | int value = cpu.readPC(); 255 | 256 | cpu.writeAddress(addr, value); 257 | cpu.addCycles(3); 258 | return true; 259 | } 260 | 261 | private boolean loadA(RegisterPairs pair, CPU cpu) { 262 | int addr = cpu.readWordRegister(pair); 263 | int value = cpu.readAddress(addr); 264 | cpu.writeRegister(REGISTERS.A, value); 265 | cpu.addCycles(2); 266 | return true; 267 | } 268 | 269 | private boolean loadOffsetC(CPU cpu) { 270 | 271 | int address = 0xff00 + cpu.readRegister(REGISTERS.C); 272 | cpu.writeRegister(REGISTERS.A, cpu.readAddress(address)); 273 | cpu.addCycles(2); 274 | return true; 275 | } 276 | 277 | private boolean loadOffsetA(CPU cpu) { 278 | int address = 0xff00 + cpu.readRegister(REGISTERS.C); 279 | cpu.writeAddress(address, cpu.readRegister(REGISTERS.A)); 280 | cpu.addCycles(2); 281 | return true; 282 | } 283 | 284 | private boolean loadAImm(CPU cpu) { 285 | int offset = cpu.readPC(); 286 | int address = (0xff00 + offset) & 0xffff; 287 | int val = cpu.readAddress(address); 288 | 289 | cpu.writeRegister(REGISTERS.A, val); 290 | cpu.addCycles(3); 291 | return true; 292 | } 293 | 294 | private boolean loadImmA(CPU cpu) { 295 | int offset = cpu.readPC(); 296 | int address = (0xff00 + offset) & 0xffff; 297 | cpu.writeAddress(address, cpu.readRegister(REGISTERS.A)); 298 | cpu.addCycles(3); 299 | return true; 300 | } 301 | 302 | private boolean loadAImmWord(CPU cpu) { 303 | int address = cpu.readWordPC(); 304 | 305 | cpu.writeRegister(REGISTERS.A, cpu.readAddress(address)); 306 | cpu.addCycles(4); 307 | return true; 308 | } 309 | 310 | private boolean loadImmWordA(CPU cpu) { 311 | int address = cpu.readWordPC(); 312 | cpu.writeAddress(address, cpu.readRegister(REGISTERS.A)); 313 | cpu.addCycles(4); 314 | return true; 315 | } 316 | 317 | private boolean loadAHLI(CPU cpu) { 318 | int address = cpu.readWordRegister(RegisterPairs.HL); 319 | int value = cpu.readAddress(address); 320 | cpu.writeRegister(REGISTERS.A, value); 321 | cpu.writeWordRegister(RegisterPairs.HL, address + 1); 322 | cpu.addCycles(2); 323 | return true; 324 | } 325 | 326 | private boolean loadAHLD(CPU cpu) { 327 | int address = cpu.readWordRegister(RegisterPairs.HL); 328 | int value = cpu.readAddress(address); 329 | cpu.writeRegister(REGISTERS.A, value); 330 | cpu.writeWordRegister(RegisterPairs.HL, address - 1); 331 | cpu.addCycles(2); 332 | return true; 333 | } 334 | 335 | private boolean loadPair(RegisterPairs pair, CPU cpu) { 336 | int address = cpu.readWordRegister(pair); 337 | cpu.writeAddress(address, cpu.readRegister(REGISTERS.A)); 338 | cpu.addCycles(2); 339 | return true; 340 | } 341 | 342 | private boolean loadHLD(CPU cpu) { 343 | int address = cpu.readWordRegister(RegisterPairs.HL); 344 | cpu.writeAddress(address, cpu.readRegister(REGISTERS.A)); 345 | cpu.writeWordRegister(RegisterPairs.HL, address - 1); 346 | cpu.addCycles(2); 347 | return true; 348 | } 349 | 350 | private boolean loadHLI(CPU cpu) { 351 | int address = cpu.readWordRegister(RegisterPairs.HL); 352 | cpu.writeAddress(address, cpu.readRegister(REGISTERS.A)); 353 | cpu.writeWordRegister(RegisterPairs.HL, address + 1); 354 | cpu.addCycles(2); 355 | return true; 356 | } 357 | 358 | private boolean load16(RegisterPairs pair, CPU cpu) { 359 | int word = cpu.readWordPC(); 360 | cpu.writeWordRegister(pair, word); 361 | cpu.addCycles(3); 362 | return true; 363 | } 364 | 365 | private boolean load16SP(CPU cpu) { 366 | int word = cpu.readWordPC(); 367 | cpu.setSP(word); 368 | cpu.addCycles(3); 369 | return true; 370 | } 371 | 372 | private boolean load16SPHL(CPU cpu) { 373 | cpu.setSP(cpu.readWordRegister(RegisterPairs.HL)); 374 | cpu.addCycles(2); 375 | return true; 376 | } 377 | 378 | private boolean ldhl(CPU cpu) { 379 | int value = (byte) cpu.readPC(); 380 | int currentSP = cpu.getSP(); 381 | int result = value + currentSP; 382 | cpu.writeWordRegister(RegisterPairs.HL, result); 383 | cpu.setFlag(FLAGS.Z, false); 384 | cpu.setFlag(FLAGS.N, false); 385 | cpu.setFlag(FLAGS.H,(result & 0x0f) < (currentSP & 0x0f)); 386 | cpu.setFlag(FLAGS.C, (result & 0xff) < (currentSP & 0xff)); 387 | cpu.addCycles(3); 388 | return true; 389 | } 390 | 391 | private boolean load16SPAddr(CPU cpu) { 392 | int addr = cpu.readWordPC(); 393 | 394 | cpu.writeAddress(addr, getLsb(cpu.getSP())); 395 | cpu.writeAddress(addr + 1, getMsb(cpu.getSP())); 396 | cpu.addCycles(5); 397 | return true; 398 | } 399 | 400 | } 401 | --------------------------------------------------------------------------------