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
*/
import java.applet.Applet;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Calendar;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* PgbCart is responsible for the cartridge are of the
* memory map. It also contains functions to read
* header data and to load and save carts and batteries.
*/
public final class PgbCart implements FilenameFilter {
private static final int C_NONE = 0x00;
private static final int C_MBC1 = 0x01;
private static final int C_MBC2 = 0x06;
private static final int C_MMM01 = 0x0B;
private static final int C_MBC3 = 0x0F;
private static final int C_MBC5 = 0x19;
private static final int CLOCK_REL = 0x00;
private static final int CLOCK_LATCH = 0x01;
private static final int CLOCK_SEC = 0x08;
private static final int CLOCK_MIN = 0x09;
private static final int CLOCK_HRS = 0x0A;
private static final int CLOCK_DAYL = 0x0B;
private static final int CLOCK_DAYH = 0x0C;
public byte[] romdata;
public byte[] ramdata;
//public int currom;
//public int curram;
public int romoffset;
public int ramoffset;
public boolean mbc1mode;
public boolean mbc1ramenabled;
public boolean mbc3clockenabled;
public long mbc3time;
public int mbc3reg;
public void reset() {
setRomBank(1);
setRamBank(0);
mbc1mode = true;
mbc1ramenabled = false;
mbc3clockenabled = false;
}
public final byte read(int address) {
// ROM bank 0
if(address < 0x4000) {
return romdata[address];
}
// ROM bank 1 and beyond
if(address < 0x8000) {
return romdata[address + romoffset];
}
// cart RAM
if(address < 0xC000) {
if(!mbc3clockenabled) {
return ramdata[address + ramoffset];
} else {
Calendar cal;
cal = Calendar.getInstance();
//System.out.println("Read MBC3 clock");
switch(mbc3reg) {
case 0x08: // second
return (byte)cal.get(Calendar.SECOND);
case 0x09: // minute
return (byte)cal.get(Calendar.MINUTE);
case 0x0A: // hour
return (byte)cal.get(Calendar.HOUR);
case 0x0B: // day low
return (byte)cal.get(Calendar.DAY_OF_YEAR);
case 0x0C: // day high
return (byte)(cal.get(Calendar.DAY_OF_YEAR) >> 8);
default:
return 0;
}
}
}
System.out.println("Read from unmapped cart memory:" + Integer.toHexString(address));
return 0;
}
/**
* write a byte to the cart. This is how the various MBC
* registers are set and this is responsible for emulating
* them.
*/
public final void write(int address, int towrite) {
// ram enable
if(address < 0x2000) {
//System.out.println("ram enable:" + towrite);
mbc1ramenabled = (towrite & 0x0F) == 0x0A;
return;
}
// rom select (ls byte)
if(address < 0x3000) {
if(PgbSettings.DEBUG && (towrite & 0xFF) > getRomBanks()) {
System.out.println("rom select (ls byte):" + (towrite & 0xFF) + " (" + ((towrite & 0xFF) < getRomBanks() ? "valid" : "BAD!!") + ")");
}
if(getType() != C_MBC5 && towrite == 0) {
// can't set ROM0 here
setRomBank(1);
} else {
setRomBank(towrite & 0xFF);
}
return;
}
// rom select (ms byte on mbc5, ls byte otherwise)
if(address < 0x4000) {
if(getType() == C_MBC5) {
//System.out.println("rom select (ms byte):" + towrite);
} else {
setRomBank(towrite);
}
return;
}
// ram select
if(address < 0x6000) {
if(towrite < 0x04) {
if(PgbSettings.DEBUG && (towrite & 0xFF) > getRamBanks()) {
System.out.println("ram switch:" + towrite + " (" + ((towrite & 0xFF) < getRamBanks() ? "valid" : "BAD!!") + ")");
}
setRamBank(towrite & 0xFF);
mbc3clockenabled = false;
return;
} else {
// clock select
//System.out.println("MBC3 clock select:" + Integer.toHexString(towrite));
mbc3reg = towrite;
mbc3clockenabled = true;
return;
}
}
// RAM/ROM select (MBC1 only)
if(address < 0x8000 && getType() == C_MBC1) {
//System.out.println("RAM/ROM select (MBC1 only):" + Integer.toHexString(towrite & 0xFF) + " (" + (towrite & 1) + ")");
mbc1mode = (towrite & 1) == 0;
return;
}
// MBC3 clock latch/release
if(address < 0x8000) {
//System.out.println("MBC3 clock latch:" + towrite);
mbc3time = System.currentTimeMillis();
return;
}
// cart RAM
if(address < 0xC000 && address >= 0xA000) {
if(mbc1ramenabled) {
ramdata[address + ramoffset] = (byte)towrite;
} else {
//System.out.println("attempted to write to disabled RAM");
}
return;
}
System.out.println("Write to unmapped cart memory:" + Integer.toHexString(address) + ", " + Integer.toHexString(towrite));
}
public boolean loaded() {
return romdata != null && romdata.length > 0;
}
public void setRomBank(int bank) {
//currom = select;
romoffset = (bank - 1) * 0x4000;
}
public void setRamBank(int bank) {
//curram = select;
ramoffset = -0xA000 + bank * 0x2000;
}
public String getName() {
// this doesn't work right.
return new String(romdata, 134, 11);
}
/**
* read the number of ROM banks from the cart header
*/
public int getRomBanks() {
switch(romdata[0x0148]) {
case 0 : return 2;
case 1 : return 4;
case 2 : return 8;
case 3 : return 16;
case 4 : return 32;
case 5 : return 64;
case 6 : return 128;
case 7 : return 256;
default : return 2;
}
}
/**
* read the number of RAM banks from the cart header
*/
public int getRamBanks() {
switch(romdata[0x0149]) {
case 0 : return 0;
case 1 : return 1;
case 2 : return 1;
case 3 : return 4;
case 4 : return 16;
case 5 : return 32;
default : return 0;
}
}
/**
* is this cart Super Gameboy compatible?
*/
public boolean getSgb() {
return loaded() && romdata[0x0146] == 3;
}
/**
* is this cart Gameboy Color compatible?
*/
public boolean getGbc() {
return loaded() && ((romdata[0x0143] & 0xFF) == 0x80 || (romdata[0x0143] & 0xFF) == 0xC0);
}
public int getType() {
int type = romdata[0x0147] & 0xFF;
if(type >= 0x01 && type <= 0x03) {
return C_MBC1;
}
if(type == 0x06) {
return C_MBC2;
}
if(type >= 0x0B && type <= 0x0D) {
return C_MMM01;
}
if(type >= 0x0F && type <= 0x13) {
return C_MBC3;
}
if(type >= 0x19 && type <= 0x1E) {
return C_MBC5;
}
return C_NONE;
}
public String getTypeString() {
switch(getType()) {
case C_MBC1 :
return "MBC1";
case C_MBC2 :
return "MBC2";
case C_MMM01 :
return "MMM01";
case C_MBC3 :
return "MBC3";
case C_MBC5 :
return "MBC5";
default :
return "NONE";
}
}
/**
* loads a gb file, given a path and a filename
*/
public boolean load(String path, String filename) {
return load(filename);
}
/**
* loads a file in .gb or .zip format, then tries to
* load a battery file, if necessary
*/
public boolean load(String filename) {
if(filename.length() < 1) {
return false;
}
if(accept(null, filename)) {
loadGB(filename);
loadBattery(filename.substring(0, filename.lastIndexOf('.')) + ".sav");
System.out.println("");
return true;
}
if(filename.endsWith(".zip")) {
if(loadZip(filename)) {
loadBattery(filename.substring(0, filename.lastIndexOf('.')) + ".sav");
System.out.println("");
return true;
}
}
System.out.println("File format not recognized.");
return false;
}
/**
* simplified loadingcode for applet usage
*/
public void loadApplet(String filename, Applet a) {
InputStream is = null;
try {
is = new URL(a.getDocumentBase(), filename).openStream();
} catch (IOException e) {
System.out.println("Error opening rom");
}
romdata = new byte[255000];
loadCart(is,255000);
}
/**
* sends a GB formatted file (raw cart dump) to loadCart
*/
public void loadGB(String filename) {
InputStream is;
File romfile;
romfile = new File(filename);
romdata = new byte[(int)romfile.length()];
System.out.println("\nLoading cart '" + romfile.getName() + "', size:" + romfile.length() + "...");
try {
is = new FileInputStream(romfile);
} catch(FileNotFoundException e) {
System.out.println("File not found!");
System.out.println(e.getMessage());
romdata = new byte[0x8000];
return;
}
loadCart(is, (int)romfile.length());
}
/**
* searches for the first acceptable file in a .zip
* archive and sends it to loadCart
*/
public boolean loadZip(String filename) {
File romfile;
ZipEntry ze;
ZipInputStream zis;
romfile = new File(filename);
System.out.println("\nLoading zip file '" + romfile.getName() + "'...");
try {
zis = new ZipInputStream(new FileInputStream(romfile));
while((ze = zis.getNextEntry()) != null) { // or until out of files
if(accept(null, ze.getName())) {
System.out.println("Loading entry '" + ze.getName() + "', size : " + ze.getSize() + "...");
break;
}
}
loadCart(zis, (int) ze.getSize());
return true;
} catch(Exception ex) {
System.out.println("File not found!");
System.out.println(ex.getMessage());
return false;
}
}
/**
* loads a cartridge from the InputStream it is given
*/
public void loadCart(InputStream is, int length) {
try {
romdata = new byte[length];
int nRead, count = 0;
while((length > 0) && ((nRead = is.read(romdata, count, length)) != -1)) {
count += nRead;
length -= nRead;
}
is.close();
} catch(Exception e) {
System.out.println("File read error:");
System.out.println(e.getMessage());
}
System.out.println("rom byte: " + Integer.toHexString(romdata[0x0148]) + ", banks: " + getRomBanks());
System.out.println("ram byte: " + Integer.toHexString(romdata[0x0149]) + ", banks: " + getRamBanks());
System.out.println("type byte: " + Integer.toHexString(romdata[0x0147]) + " (" + getTypeString() + ")" + ", SGB features: " + Integer.toHexString(romdata[0x146] & 0xFF) + " (" + getSgb() + "), GBC features: " + Integer.toHexString(romdata[0x143] & 0xFF) + " (" + getGbc() + ")");
}
/**
* if there are any RAM banks, this allocates memory
* for them and tries to load saved RAM from disk
*/
public void loadBattery(String filename) {
InputStream is;
File ramfile;
String ramdesc;
// create & load ram
if(getRamBanks() > 0) {
ramdata = new byte[0x2000 * getRamBanks()];
ramfile = new File(filename);
ramdesc = "Looking for battery save... ";
try {
if(ramfile.exists()) {
ramdesc += "loading... ";
is = new FileInputStream(ramfile);
loadBattery(is);
ramdesc += "done.";
} else {
ramdesc += "not found.";
}
} catch(Exception e) {
System.out.println(e.getMessage());
}
// print some ram info
// System.out.println("ramdesc: "+ramdesc);
} else {
ramdata = new byte[0x2000];
}
}
/**
* reads battery-backed RAM from an InputStream
*/
public void loadBattery(InputStream is) {
try {
is.read(ramdata);
is.close();
} catch(Exception e) {
System.out.println("File read error:");
System.out.println(e.getMessage());
}
}
/**
* if there is any battery-backed save RAM allocated, this
* saves it to the disk, in the save directory specified
* in the settings
*/
public void saveBattery(String romfilename) {
if(!loaded() || getRamBanks() == 0) {
return;
}
OutputStream os;
File ramfile;
ramfile = new File(romfilename.substring(0, romfilename.lastIndexOf('.')) + ".sav");
try {
os = new FileOutputStream(ramfile);
os.write(ramdata);
os.close();
System.out.println("Saving ram to file... done.");
} catch(Exception e) {
System.out.println("Saving ram to file... error!");
System.out.println(e.getMessage());
}
}
/**
* tests if a file extension is in an acceptable format
*/
public boolean accept(File file, String filename) {
return (filename.toLowerCase().endsWith(".gb") || filename.toLowerCase().endsWith(".gbc") || filename.toLowerCase().endsWith(".cgb") || filename.toLowerCase().endsWith(".sgb"));
}
}