package com.pixelutilitys.arcade.emulators.AEPgb;
/**
* this source file released under the GNU Public Licence.
* see the accompanying copyright.txt for more information.
* Copyright (C) 2000-2001 Ben Mazur
* modified by retroK 2004 http://aep-emu.de/
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
/**
* PgbVideo is responsible for the GameBoy video hardware.
*
* Additionally, some SGB memory and functions are contained
* here.
*
* It is abstract because it only makes sure that the VRAM,
* etc., contain the proper values. PgbVideoOutput classes
* depend on the getScreenPixels, etc methods
*/
public abstract class PgbVideo {
static final int VBLANK_CYCLES = 109;
static final int HBLANK_CYCLES = 49;
static final int OAM_CYCLES = 20;
static final int TRANSFER_CYCLES = 40;
static final byte STAT_HBLANK = 0;
static final byte STAT_VBLANK = 1;
static final byte STAT_OAM = 2;
static final byte STAT_TRANSFER = 3;
static final byte INT_LYC = 0x40;
static final byte INT_OAM = 0x20;
static final byte INT_HBLANK = 0x10;
static final byte INT_VBLANK = 0x08;
static final byte INT_LYCMODE = 0x04;
// output
PgbVideoOutput vidout;
// registers
public int scx;
public int scy;
public int ly;
public int lyc;
public int wx;
public int wy;
// lcdc
public boolean lcd_on;
public boolean win_on;
public boolean win_mode;
public boolean chr_mode;
public boolean bg_mode;
public boolean obj_mode;
public boolean obj_on;
public boolean bg_on;
public int win_src;
public int bg_src;
public int obj_siz;
public int chr_src;
// stat
public boolean int_lyc;
public boolean int_oam;
public boolean int_hblank;
public boolean int_vblank;
public byte stat_mode;
// counters
int cycles;
int curframe;
public long framecount;
// memory
byte[] vram;
byte[] oam;
// regular gb palettes
public int bgpal;
public int objpal0;
public int objpal1;
// SGB memory
boolean sgbvramon;
byte sgbPicture[];
byte sgbCharset[];
byte sgbPalette[];
byte sgbSystemPalette[];
byte sgbAtfData[];
byte sgbPaletteOverlay[];
// color gb
byte[] gbcPalette;
byte gbcVram;
int vramOffset;
byte bgpi;
byte obpi;
public PgbVideo() {
// setup ram
vram = new byte[0x4000];
oam = new byte[0xA0];
// sgb
sgbPicture = new byte[0x1000];
sgbCharset = new byte[0x4000];
sgbPalette = new byte[0x20];
sgbSystemPalette = new byte[0x1000];
sgbAtfData = new byte[0x1800];
sgbPaletteOverlay = new byte[90];
// gbc
gbcPalette = new byte[0x80];
}
public void reset() {
int i;
// blank ram
for(i = 0; i < 0x4000; i++) {
vram[i] = 0;
}
for(i = 0; i < 0xA0; i++) {
oam[i] = 0;
}
// registers
setLcdc((byte)0x91);
setStat((byte)0x80);
scy = 0;
scx = 0;
wx = 0;
wy = 0;
ly = 0;
lyc = 0;
cycles = 0;
curframe = 0;
framecount = 0;
// bgpal
setBgPal(0xFC);
setObjPal0(0xFF);
setObjPal1(0xFF);
// super GB
sgbvramon = false;
// gbc
vramOffset = 0x8000;
gbcVram = (byte)0x00;
bgpi = (byte)0x00;
obpi = (byte)0x00;
}
public byte read(int address) {
// VRAM
if(address >= 0x8000 && address < 0xA000) {
return vram[address - vramOffset];
}
// OAM
if(address >= 0xFE00 && address < 0xFEA0) {
return oam[address - 0xFE00];
}
System.out.println("Read from unmapped video memory:" + Integer.toHexString(address));
return 0;
}
public void write(int address, byte towrite) {
/*
// SGB
if(sgbvramon) {
System.out.print(Integer.toHexString(address & 0xFFFF) + ":" + Integer.toHexString(towrite & 0xFF) + " ");
}
*/
// VRAM
if(address >= 0x8000 && address < 0xA000) {
vram[address - vramOffset] = towrite;
return;
}
// OAM
if(address >= 0xFE00 && address < 0xFEA0) {
oam[address - 0xFE00] = towrite;
return;
}
System.out.println("Write to unmapped video memory:" + Integer.toHexString(address) + ", " + Integer.toHexString(towrite));
}
/**
* how many cycles until the next possible interrupt?
*/
public int cyclesLeft() {
return cycles;
}
/**
* this is called to update the LCD cycles
*/
public int cycle(int cy, PgbMemory pgbmemory) {
byte interrupt = 0;
cycles -= cy;
if(cycles <= 0) {
if(stat_mode == STAT_HBLANK || stat_mode == STAT_VBLANK) {
if(++ly > 0x100) {
ly = 0;
}
if(ly < 0x90) {
hblank();
stat_mode = STAT_OAM;
cycles = (int)(OAM_CYCLES * PgbSettings.clockspeed);
interrupt |= int_hblank ? PgbMemory.INT_LCD : 0;
} else {
stat_mode = STAT_VBLANK;
cycles = (int)(VBLANK_CYCLES * PgbSettings.clockspeed);
if(ly == 0x90) {
vblank();
// by retroK:
pgbmemory.soundPlay();
interrupt |= int_vblank ? (PgbMemory.INT_LCD | PgbMemory.INT_VBLANK) : PgbMemory.INT_VBLANK;
}
}
// coincidence interrupt
if(ly == lyc) {
interrupt |= int_lyc ? PgbMemory.INT_LCD : 0;
}
return interrupt;
}
if(stat_mode == STAT_OAM) {
stat_mode = STAT_TRANSFER;
cycles = (int)(TRANSFER_CYCLES * PgbSettings.clockspeed);
interrupt |= int_oam ? PgbMemory.INT_LCD : 0;
return interrupt;
}
if(stat_mode == STAT_TRANSFER) {
stat_mode = STAT_HBLANK;
cycles = (int)(HBLANK_CYCLES * PgbSettings.clockspeed);
return interrupt;
}
}
return 0;
}
public void hblank() {
//if(lcd_on && curframe == 0 && (ly & 1) == (framecount & 1)) {
if(lcd_on && curframe == 0) {
vidout.hblank(ly);
}
}
public void vblank() {
framecount++;
curframe--;
if(curframe < 0) {
curframe = PgbSettings.frameskip;
vidout.vblank();
}
}
public void setVideoOutput(PgbVideoOutput vidout) {
this.vidout = vidout;
}
public abstract byte[] getScreenMemory();
public abstract byte getScreenMemory(int index);
public abstract byte getScreenRed(byte index);
public abstract byte getScreenGreen(byte index);
public abstract byte getScreenBlue(byte index);
public abstract int getScreenColor(byte index);
public void setBgPal(int pval) {
//System.out.println("Change background palette: " + Integer.toHexString(pval));
bgpal = pval;
}
public void setObjPal0(int pval) {
//System.out.println("Change obj 0 palette: " + Integer.toHexString(pval));
objpal0 = pval;
}
public void setObjPal1(int pval) {
//System.out.println("Change obj 1 palette: " + Integer.toHexString(pval));
objpal1 = pval;
}
public void setLcdc(byte lval) {
boolean old_lcd = lcd_on;
lcd_on = (lval & 0x80) == 0x80;
win_mode = (lval & 0x40) == 0x40;
win_on = (lval & 0x20) == 0x20;
chr_mode = (lval & 0x10) == 0x10;
bg_mode = (lval & 0x08) == 0x08;
obj_mode = (lval & 0x04) == 0x04;
obj_on = (lval & 0x02) == 0x02;
bg_on = (lval & 0x01) == 0x01;
win_src = win_mode ? 0x9C00 : 0x9800;
chr_src = chr_mode ? 0x8000 : 0x8800;
bg_src = bg_mode ? 0x9C00 : 0x9800;
obj_siz = obj_mode ? 16 : 8;
// reset ly when LCD goes off to on?
// docs are not very clear on this subject
if(!old_lcd && lcd_on) {
ly = 0;
stat_mode = STAT_OAM;
cycles = OAM_CYCLES;
}
if(!lcd_on) {
ly = 0;
stat_mode = STAT_HBLANK;
cycles = HBLANK_CYCLES;
}
}
public byte getLcdc() {
byte lcdc = 0;
lcdc |= lcd_on ? 0x80 : 0x00;
lcdc |= win_mode ? 0x40 : 0x00;
lcdc |= win_on ? 0x20 : 0x00;
lcdc |= chr_mode ? 0x10 : 0x00;
lcdc |= bg_mode ? 0x08 : 0x00;
lcdc |= obj_mode ? 0x04 : 0x00;
lcdc |= obj_on ? 0x02 : 0x00;
lcdc |= bg_on ? 0x01 : 0x00;
return lcdc;
}
public void setStat(byte sval) {
int_lyc = (sval & 0x40) == 0x40;
int_oam = (sval & 0x20) == 0x20;
int_vblank = (sval & 0x10) == 0x10;
int_hblank = (sval & 0x08) == 0x08;
}
public byte getStat() {
byte stat = 0;
stat |= int_lyc ? 0x40 : 0x00;
stat |= int_oam ? 0x20 : 0x00;
stat |= int_hblank ? 0x10 : 0x00;
stat |= int_vblank ? 0x08 : 0x00;
stat |= (ly == lyc) ? 0x04 : 0x00;
stat |= stat_mode;
return stat;
}
public void gbcSetVram(byte setting) {
gbcVram = setting;
vramOffset = ((gbcVram & 0x01) != 0) ? 0x6000 : 0x8000;
}
public byte gbcGetVram() {
return gbcVram;
}
public void gbcSetBgpi(byte index) {
//System.out.println("write to Color BG Palette Index:" + Integer.toHexString(index & 0xFF));
bgpi = index;
}
public void gbcSetBgpd(byte data) {
//System.out.println("write to Color BG Palette Data:" + Integer.toHexString(data & 0xFF));
gbcPalette[bgpi & 0x3F] = data;
if((bgpi & 0x80) == 0x80) {
bgpi++;
}
}
public void gbcSetObpi(byte index) {
//System.out.println("write to Color OBJ Palette Index:" + Integer.toHexString(index & 0xFF));
obpi = index;
}
public void gbcSetObpd(byte data) {
//System.out.println("write to Color OBJ Palette Data:" + Integer.toHexString(data & 0xFF));
gbcPalette[(obpi & 0x3F) + 0x40] = data;
if((obpi & 0x80) == 0x80) {
obpi++;
}
}
public byte gbcGetBgpi() {
//System.out.println("read Color BG Palette Index:" + Integer.toHexString(bgpi));
return bgpi;
}
public byte gbcGetBgpd() {
//System.out.println("read Color BG Palette Data:" + Integer.toHexString(gbcPalette[bgpi & 0x3F]));
return gbcPalette[bgpi & 0x3F];
}
public byte gbcGetObpi() {
//System.out.println("read Color OBJ Palette Index:" + Integer.toHexString(obpi));
return obpi;
}
public byte gbcGetObpd() {
//System.out.println("read Color OBJ Palette Data:" + Integer.toHexString(gbcPalette[(obpi & 0x3F) + 0x40]));
return gbcPalette[(obpi & 0x3F) + 0x40];
}
public void sgbSetPalette(int pal, int color, byte hi, byte low) {
sgbPalette[pal * 8 + color * 2 + 0] = low;
sgbPalette[pal * 8 + color * 2 + 1] = hi;
}
public void sgbSetPaletteIndirect(int pal0, int pal1, int pal2, int pal3, int atf) {
System.arraycopy(sgbSystemPalette, pal0 * 8, sgbPalette, 0, 8);
System.arraycopy(sgbSystemPalette, pal1 * 8, sgbPalette, 8, 8);
System.arraycopy(sgbSystemPalette, pal2 * 8, sgbPalette, 16, 8);
System.arraycopy(sgbSystemPalette, pal3 * 8, sgbPalette, 24, 8);
if(atf != 0) {
sgbSetOverlayFromAtf(atf);
}
}
public void sgbSetPaletteOverlay(int index, int palette) {
int i = index / 4, o = (3 - (index & 3)) * 2;
sgbPaletteOverlay[i] &= ~(byte)(0x03 << o);
sgbPaletteOverlay[i] |= (byte)((palette & 0x03) << o);
}
public void sgbSetPaletteOverlayByte(int index, byte palette) {
sgbPaletteOverlay[index] = palette;
}
public void sgbPictureTransfer() {
System.arraycopy(vram, 0x800, sgbPicture, 0, 0x1000);
}
public void sgbCharsetTransfer(boolean type, boolean range) {
System.arraycopy(vram, 0x0800, sgbCharset, range ? 0x1000 : 0, 0x1000);
}
public void sgbPaletteTransfer() {
System.arraycopy(vram, 0x800, sgbSystemPalette, 0, 0x1000);
}
public void sgbAtfTransfer() {
System.arraycopy(vram, 0x800, sgbAtfData, 0, 0x1800);
sgbSetOverlayFromAtf(1);
}
public void sgbSetOverlayFromAtf(int atf) {
System.arraycopy(sgbAtfData, (atf & 0x3F) * 90, sgbPaletteOverlay, 0, 90);
}
public void sgbBlockDesignate(byte control, byte palettes, byte startx, byte starty, byte endx, byte endy) {
int x, y;
byte in, on, out;
boolean useIn, useOn, useOut;
in = (byte)((palettes & 0x03) );
on = (byte)((palettes & 0x0C) >> 2);
out = (byte)((palettes & 0x30) >> 4);
useIn = (control & 0x01) == 0x01;
useOn = (control & 0x02) == 0x02;
useOut = (control & 0x04) == 0x04;
if(PgbSettings.DEBUG) {
System.out.println(" - sgb block[in:" + in + (useIn ? "+" : "-") + ", on:" + on + (useOn ? "+" : "-") + ", out:" + out + (useOut ? "+" : "-") + ", from(" + startx + ", " + starty + ") to(" + endx + ", " + endy + ")]");
}
// the ugliest code ever:
for(y = 0; y < 18; y++) {
for(x = 0; x < 20; x++) {
if(y < starty || y > endy) {
// y is outside the block
if(useOut) {
sgbSetPaletteOverlay(y * 20 + x, out);
}
}
if(y == starty || y == endy) {
// y is on the line
if(x < startx || x > endx) {
// but x is off the line
if(useOut) {
sgbSetPaletteOverlay(y * 20 + x, out);
}
} else {
// and x is on the line
if(useOn) {
sgbSetPaletteOverlay(y * 20 + x, on);
}
}
}
if(y > starty && y < endy) {
// y is inside the block
if(x < startx || x > endx) {
// but x is outside the block
if(useOut) {
sgbSetPaletteOverlay(y * 20 + x, out);
}
} else {
if(x == startx || x == endx) {
// and x is on the line
if(useOn) {
sgbSetPaletteOverlay(y * 20 + x, on);
}
} else {
// and x is inside the block
if(useIn) {
sgbSetPaletteOverlay(y * 20 + x, in);
}
}
}
}
}
}
}
public void sgbLineDesignate(byte control) {
int line, i;
byte pal;
boolean mode;
line = (control & 0x1F);
pal = (byte)((control & 0x60) >> 5);
mode = (control & 0x80) == 0x80;
if(mode) {
// vertical line
for(i = 0; i < 18; i++) {
sgbSetPaletteOverlay(i * 20 + line, pal);
}
} else {
// horizontal line
for(i = 0; i < 20; i++) {
sgbSetPaletteOverlay(line * 20 + i, pal);
}
}
}
public void sgbDivideDesignate(byte control, byte line) {
int x, y;
byte on, before, after;
boolean mode;
on = (byte)((control & 0x03) );
before = (byte)((control & 0x0C) >> 2);
after = (byte)((control & 0x30) >> 4);
mode = (control & 0x40) == 0x40;
if(mode) {
// divide vertical
for(y = 0; y < 18; y++) {
for(x = 0; x < 20; x++) {
if(x < line) {
sgbSetPaletteOverlay(y * 20 + x, before);
}
if(x == line) {
sgbSetPaletteOverlay(y * 20 + x, on);
}
if(x > line) {
sgbSetPaletteOverlay(y * 20 + x, after);
}
}
}
} else {
// divide horizontal
for(y = 0; y < 18; y++) {
for(x = 0; x < 20; x++) {
if(y < line) {
sgbSetPaletteOverlay(y * 20 + x, before);
}
if(y == line) {
sgbSetPaletteOverlay(y * 20 + x, on);
}
if(y > line) {
sgbSetPaletteOverlay(y * 20 + x, after);
}
}
}
}
}
public void dumpGbcPalette() {
OutputStream os;
File dumpfile;
dumpfile = new File("gbcPalette");
try {
os = new FileOutputStream(dumpfile);
os.write(gbcPalette);
os.close();
} catch(Exception e) {
System.out.println("error!");
System.out.println(e.getMessage());
}
}
public void dumpSgbPaletteOverlay() {
OutputStream os;
File dumpfile;
dumpfile = new File("sgbPaletteOverlay");
try {
os = new FileOutputStream(dumpfile);
os.write(sgbPaletteOverlay);
os.close();
} catch(Exception e) {
System.out.println("error!");
System.out.println(e.getMessage());
}
}
public void dumpVram() {
OutputStream os;
File dumpfile;
dumpfile = new File("vram");
try {
os = new FileOutputStream(dumpfile);
os.write(vram);
os.close();
} catch(Exception e) {
System.out.println("error!");
System.out.println(e.getMessage());
}
}
}