├── .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 |
4 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
--------------------------------------------------------------------------------