package chip8emu;

/*
 This file is part of JavaCHIP8.

 Copyright 2004 Kustaa Nyholm / SpareTimeLabs

 JavaCHIP8 is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 JavaCHIP8 is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with JavaCHIP8; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import java.io.*;
import java.util.*;

/**
 <p>Title: </p>
 <p>Description: </p>
 <p>Copyright: Copyright (c) 2004</p>
 <p>Company: </p>
 @author not attributable
 @version 1.0
 */
/*
 Code  Assembler  Description  Notes
 00Cx  scdown x  Scroll the screen down x lines  Super only, not implemented
 00E0  cls  Clear the screen
 00EE  rts  return from subroutine call
 00FE  low  disable extended screen mode  Super only
 00FF  high  enable extended screen mode (128 x 64)  Super only
 1xxx  jmp xxx  jump to address xxx
 2xxx  jsr xxx  jump to subroutine at address xxx  16 levels maximum
 3rxx  skeq vr,xx  skip if register r = constant
 4rxx  skne vr,xx  skip if register r <> constant
 5ry0  skeq vr,vy  skip f register r = register y
 6rxx  mov vr,xx  move constant to register r
 7rxx  add vr,vx  add constant to register r  No carry generated
 8ry0  mov vr,vy  move register vy into vr
 8ry1  or rx,ry  or register vy into register vx
 8ry2  and rx,ry  and register vy into register vx
 8ry3  xor rx,ry  exclusive or register ry into register rx
 8ry4  add vr,vy  add register vy to vr,carry in vf
 8ry5  sub vr,vy  subtract register vy from vr,borrow in vf  vf set to 1 if borroesws
 8r06  shr vr  shift register vy right, bit 0 goes into register vf
 8ry7  rsb vr,vy  subtract register vr from register vy, result in vr  vf set to 1 if borrows
 8r0e  shl vr  shift register vr left,bit 7 goes into register vf
 9ry0  skne rx,ry  skip if register rx <> register ry
 axxx  mvi xxx  Load index register with constant xxx
 bxxx  jmi xxx  Jump to address xxx+register v0
 crxx  rand vr,xxx     vr = random number less than or equal to xxx
 dxys  sprite rx,ry,s  Draw sprite at screen location register x,register y height s  Sprites stored in memory at location in index register, maximum 8 bits wide. Wraps around the screen. If when drawn, clears a pixel, vf is set to 1 otherwise it is zero. All drawing is xor drawing (e.g. it toggles the screen pixels
 ek9e  skpr k  skip if key (register rk) pressed  The key is a key number, see the chip-8 documentation
 eka1  skup k  skip if key (register rk) not pressed
 fr07  gdelay vr  get delay timer into vr
 fr0a  key vr  wait for for keypress,put key in register vr
 fr15  sdelay vr  set the delay timer to vr
 fr18  ssound vr  set the sound timer to vr
 fr1e  adi vr  add register vr to the index register
 fr29  font vr  point I to the sprite for hexadecimal character in vr  Sprite is 5 bytes high
 fr33  bcd vr  store the bcd representation of register vr at location I,I+1,I+2  Doesn't change I
 fr55  str v0-vr  store registers v0-vr at location I onwards  I is incremented to point to the next location on. e.g. I = I + r + 1
 fx65  ldr v0-vr  load registers v0-vr from location I onwards  as above.




 --------------------------------------------------------------------------------







 */
public class Chip8Emu
    implements Runnable {

  private boolean b;
  private Thread emulationThread;
  private int opcode;
  private int argument;
  private int result;

  private byte[] memory = new byte[4096];
  private int[] registers = new int[16];
  private int[] stack = new int[16];
  private int programCounter = 0x200;
  private int stackPointer = 0;
  private int indexRegister = 0;
  private int delayTimer = 0;
  private long timerSetTime = 0;
  private int foreColor = 0x00ffffff;
  private int backColor = 0x00000000;
  private boolean hiResMode = false;

  private int fontSprites[] {
      0xf0x90x90x90xf0x20x60x20x20x7,
      0xf0x10xf0x80xf0xf0x10xf0x10xf,
      0x90x90xf0x10x10xf0x80xf0x10xf,
      0xf0x80xf0x90xf0xf0x10x20x40x4,
      0xf0x90xf0x90xf0xf0x90xf0x10xf,
      0xf0x90xf0x90x90xe0x90xe0x90xe,
      0xf0x80x80x80xf0xe0x90x90x90xe,
      0xf0x80xf0x80xf0xf0x80xf0x80x8};

  private Chip8IO chip8IO;
  private Random random = new Random();
  private boolean running;
  private int[] pixels = new int[128 64];
  private int slowDown=0;
  static class Error
      extends RuntimeException {
    public Error(String x) {
      super(x);
    }
  }

  private void setHiRes(boolean set) {
    hiResMode = set;
    clearScreen();
  }

  public Chip8Emu(Chip8IO io) {
    chip8IO = io;
    clearScreen();
    emulationThread = new Thread(this);
    emulationThread.setPriority(Thread.NORM_PRIORITY+1);
    emulationThread.start();
  }

  private int fetchInstruction() {
    if (programCounter < 0x0200) {
      throw new Error("Program counter overflow");
    }
    if (programCounter > 0xFFF) {
      throw new Error("Program counter overflow");
    }
    int t = memory[programCounter++];
    if (t < 0) {
      t = 2560 + t;
    }
    return t;
  }

  private void illegalOpcode() {

    throw new Error("Illegal opcode " + hex(opcode, 2+ hex(argument, 2+
                    " at " +
                    hex(programCounter - 24));

  }

  private void clearScreen() {
    for (int i = 0; i < 128 64; ++i) {
      pixels[i= backColor;
    }
    chip8IO.drawPixels(pixels);
  }

  private void scrollDown(int n) {

    int N = (64 - n128;
    int d = 64 128;
    int s = d - n * 128;
    for (int i = N; i > 0; --i) {
      pixels[--d= pixels[--s];
    }
    for (int i = n * 128; i > 0; --i) {
      pixels[i= backColor;

    }
    chip8IO.drawPixels(pixels);
  }

  private void drawSprite(int x0, int y0, int h) {
    boolean f = false;
    if (hiResMode) {
      for (int iy = 0; iy < h; ++iy) {
        int m = memory[indexRegister + iy];
        int by = 128 ( (y0 + iy0x3f);
        for (int ix = 0; ix < 8; ix++) {
          if ( (m & 0x80!= 0) {
            int t = ( (x0 + ix0x7f+ by;
            if (pixels[t!= backColor) {
              pixels[t= backColor;
              f = true;
            }
            else {
              pixels[t= foreColor;
            }
          }
          m <<= 1;
        }
      }
    }
    else {
      for (int iy = 0; iy < h; ++iy) {
        int m = memory[indexRegister + iy];
        int by = 2*128 ( (y0 + iy0x1f);
        for (int ix = 0; ix < 8; ix++) {
          if ( (m & 0x80!= 0) {
            int t = ((x0 + ix0x3f)*+ by;
            if (pixels[t!= backColor) {
              pixels[t= backColor;
              pixels[t+1= backColor;
              pixels[t+128= backColor;
              pixels[t+128+1= backColor;
              f = true;
            }
            else {
              pixels[t= foreColor;
              pixels[t+1= foreColor;
              pixels[t+128= foreColor;
              pixels[t+128+1= foreColor;
            }
          }
          m <<= 1;
        }
      }

    }
    registers[0xf= f ? 0;
    chip8IO.drawPixels(pixels);
  }

  public void unimplementedOpcode() {
    System.out.println("Unimplemented opcode");
  }

  private void trace(String s) {
    System.out.println("PC 0x" + Integer.toHexString(programCounter": " + s);
  }

  private void executeOneInstruction() {
    opcode = memory[programCounter++0xff;
    argument = memory[programCounter++0xff;
    //System.out.println();
    //System.out.println(hex(programCounter - 2, 4) + "> " + hex(opcode, 2) +hex(argument, 2));
    switch (opcode & 0xf0) {
      case 0x00:
        if (opcode != 0x00) {
          illegalOpcode();
        }
        switch (argument) {
          case 0xc1:
            scrollDown(1);
            break;
          case 0xe0:

            //00E0  cls  Clear the screen
            clearScreen();
            break;
          case 0xee:

            //00EE  rts  return from subroutine call
            programCounter = stack[--stackPointer];

            break;
          case 0xfe:
            setHiRes(false);
            break;
          case 0xff:
            setHiRes(true);
            break;
          default:

            illegalOpcode();
            break;
        }
        break;
      case 0x10:

        //1xxx  jmp xxx  jump to address xxx
        programCounter = ( (opcode & 0xf<< 8+ argument;
        break;
      case 0x20:

        //2xxx  jsr xxx  jump to subroutine at address xxx  16 levels maximum
        stack[stackPointer++= programCounter;
        programCounter = ( (opcode & 0xf<< 8+ argument;

        break;
      case 0x30:

        //3rxx  skeq vr,xx  skip if register r = constant
        if (registers[opcode & 0xf== argument) {
          programCounter += 2;
        }
        break;
      case 0x40:

        //4rxx  skne vr,xx  skip if register r <> constant
        if (registers[opcode & 0xf!= argument) {
          programCounter += 2;
        }
        break;
      case 0x50:

        //5ry0  skeq vr,vy  skip f register r = register y
        if (registers[opcode & 0xf== registers[argument >> 4]) {
          programCounter += 2;
        }
        break;
      case 0x60:

        //6rxx  mov vr,xx  move constant to register r
        registers[opcode & 0xf= argument;
        break;
      case 0x70:

        //7rxx  add vr,vx  add constant to register r  No carry generated
        registers[opcode & 0xf0xff (registers[opcode & 0xf+ argument);
        break;
      case 0x80:
        switch (argument & 0xf) {

          case 0x0:

            //8ry0  mov vr,vy  move register vy into vr
            registers[opcode & 0xf= registers[argument >> 4];
            break;
          case 0x1:

            //8ry1  or rx,ry  or register vy into register vx
            registers[opcode & 0xf= registers[opcode & 0xf|
                registers[argument >> 4];
            break;
          case 0x2:

            //8ry2  and rx,ry  and register vy into register vx
            registers[opcode & 0xf= registers[opcode & 0xf&
                registers[argument >> 4];
            break;
          case 0x3:

            //8ry3  xor rx,ry  exclusive or register ry into register rx
            registers[opcode & 0xf= registers[opcode & 0xf^
                registers[argument >> 4];
            break;
          case 0x4:

            //8ry4  add vr,vy  add register vy to vr,carry in vf
            result = registers[opcode & 0xf+ registers[argument >> 4];
            registers[0xf(result & 0xFFFFFF00!= 0;
            registers[opcode & 0xf= result & 0xff;
            break;
          case 0x5:

            //8ry5  sub vr,vy  subtract register vy from vr,borrow in vf  vf set to 1 if borroesws
            result = registers[opcode & 0xf- registers[argument >> 4];
            registers[0xf(result & 0xFFFFFF00!= 0;
            registers[opcode & 0xf= result & 0xff;

            break;
          case 0x6:

            //8r06  shr vr  shift register vy right, bit 0 goes into register vf
            result = registers[opcode & 0xf];
            registers[0xf= result & 0x1;
            registers[opcode & 0xf= result >> 1;
            if ( (argument & 0xf0!= 0) {
              //illegalOpcode();
            }
            break;
          case 0x7:

            //8ry7  rsb vr,vy  subtract register vr from register vy, result in vr  vf set to 1 if borrows
            result = registers[argument >> 4- registers[opcode & 0xf];
            registers[0xf(result & 0xFFFFFF00!= 0;
            registers[opcode & 0xf= result & 0xff;
            break;
          case 0xe:

            //8r0e  shl vr  shift register vr left,bit 7 goes into register vf
            result = registers[opcode & 0xf];
            registers[0xf(result & 0x80!= 0;
            registers[opcode & 0xf(result << 10xff;
            if ( (argument & 0xf0!= 0) {
              illegalOpcode();
            }
            break;
          default:
            illegalOpcode();
            break;
        }
        break;
      case 0x90:

        //9ry0  skne rx,ry  skip if register rx <> register ry
        if (registers[opcode & 0xf!= registers[argument >> 4]) {
          programCounter += 2;
        }
        break;
      case 0xa0:

        //axxx  mvi xxx  Load index register with constant xxx
        indexRegister = 0xfff ( (opcode << 8+ argument);
        break;
      case 0xb0:

        //bxxx  jmi xxx  Jump to address xxx+register v0
        programCounter = 0xfff ( (opcode << 8+ argument + registers[0]);
        break;
      case 0xc0:

        //crxx  rand vr,xxx     vr = random number less than or equal to xxx
        registers[opcode & 0xf= random.nextInt() & argument;
        break;
      case 0xd0{

        //drys  sprite rx,ry,s  Draw sprite at screen location rx,ry height s  Sprites stored in memory at location in index register, maximum 8 bits wide. Wraps around the screen. If when drawn, clears a pixel, vf is set to 1 otherwise it is zero. All drawing is xor drawing (e.g. it toggles the screen pixels
        int x = registers[opcode & 0xf];
        int y = registers[argument >> 4];
        int h = argument & 0xf;
        drawSprite(x, y, h);

        break;
      }
      case 0xe0:
        switch (argument) {
          case 0x9e{
            //ek9e  skpr k  skip if key (register rk) pressed  The key is a key number, see the chip-8 documentation
            //System.out.println("KEY=" + registers[0xf & opcode]);
            int key = chip8IO.getKeys();
            if (registers[0xf & opcode== key) {
              programCounter += 2;
            }
            break;
          }
          case 0xa1{
            //eka1  skup k  skip if key (register rk) not pressed
            //System.out.println("KEY=" + registers[0xf & opcode]);
            int key = chip8IO.getKeys();
            if (registers[0xf & opcode!= key) {
              programCounter += 2;
            }
            break;
          }
          default:
            illegalOpcode();
        }

        break;
      case 0xf0:
        switch (argument) {
          case 0x07{

            //fr07  gdelay vr  get delay timer into vr
            int value = delayTimer -
                ( (int) (System.currentTimeMillis() - timerSetTime)) 60;
            if (value < 0) {
              value = 0;
            }
            registers[opcode & 0xf= value;
            break;
          }
          case 0x0a{
            int key;
            //fr0a  key vr  wait for for keypress,put key in register vr
            while ( (key = chip8IO.getKeys()) 0) {
              ;
            }
            registers[opcode & 0xf= key;
          }
          break;
          case 0x15:

            //fr15  sdelay vr  set the delay timer to vr
            delayTimer = registers[opcode & 0xf];
            timerSetTime = System.currentTimeMillis();
            break;
          case 0x18:

            chip8IO.beep();
            ;
            break;
          case 0x1e:

            //fr1e  adi vr  add register vr to the index register
            indexRegister = 0xfff (indexRegister + registers[opcode & 0xf]);
            //trace(Integer.toHexString(indexRegister)+" "+memory[indexRegister]);
            break;
          case 0x29:

            //fr29  font vr  point I to the sprite for hexadecimal character in vr  Sprite is 5 bytes high
            indexRegister = registers[opcode & 0xf5;
            break;
          case 0x33:

            //fr33  bcd vr  store the bcd representation of register vr at location I,I+1,I+2  Doesn't change I
            int val = registers[opcode & 0xf];
            memory[indexRegister + 0(byte) (val / 100);
            memory[indexRegister + 1(byte) (val % 100 10);
            memory[indexRegister + 2(byte) (val % 10);

            break;
          case 0x55{

            //fr55  str v0-vr  store registers v0-vr at location I onwards  I is incremented to point to the next location on. e.g. I = I + r + 1
            int r = opcode & 0xf;
            for (int i = 0; i <= r; ++i) {
              memory[indexRegister++(byteregisters[i];
            }
            break;

          }
          case 0x65{

            //fx65  ldr v0-vr  load registers v0-vr from location I onwards  as above.
            int r = opcode & 0xf;
            for (int i = 0; i <= r; ++i) {
              registers[i= memory[indexRegister++0xff;
            }
            break;
          }

        }
        break;
      default:
        illegalOpcode();
    }
  }

// sprite, timer, beep, hex char sprite

  public void run() {
    while (true) {
      if (running) {
        try {
          executeOneInstruction();
          if (slowDown>0)
            emulationThread.sleep(slowDown);
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
  }

  private int toInt(byte b1, byte b2) {
    int i1 = b1 >= ? b1 : 256 + b1;
    int i2 = b2 >= ? b2 : 256 + b2;
    return i1 * 256 + i2;
  }

  private String hex(int x, int n) {
    String t = Integer.toHexString(x);
    while (t.length() < n) {
      t = "0" + t;
    }
    return t;
  }

  public synchronized void loadGame(InputStream is) {
    try {
      int t, i = 0x200;
      while ( (t = is.read()) >= 0) {
        memory[i++(bytet;
      }

      int n = i;
      for (i = 0; i < fontSprites.length; ++i) {
        memory[i(byte) (fontSprites[i<< 4);
      }
      if (false) {
        for (int j = 0x200; j < n; j += 2) {
          //System.out.println(memory[j]);
          if ( (j & 0xf== 0) {
            System.out.println();
            System.out.print(hex(j, 4": ");
          }
          System.out.print(hex(toInt(memory[j], memory[j + 1])4" ");

        }
        System.out.println();
      }
    }
    catch (IOException e) {
      System.out.println(e);

    }
  }

  public void start(int slow) {
    slowDown=slow;
    setHiRes(false);
    running = false;

    for (int i = 0; i < registers.length; i++) {
      registers[i0;
    }
    programCounter = 0x200;
    indexRegister = 0;
    delayTimer = 0;
    clearScreen();
    running = true;
  }

  public void stop() {
    running = false;
  }

}
Java2html