TLDR: Please help me convert a C++ 65816 emulator to java.
Hi, I'm completely new to 65816 and even worse have very little experience in coding using native languages. I've decided to convert a SNES emulator from C++ to java (because I couldn't find one in java) and naturally from my lack of experience with C/pp I gravitated to the source which was the smallest. Obviously I realised it was a huge mistake. I'd like to continue going with this source, though, because it is easier for me to translate than any of the other full-featured emulators. I've decided to post some of my progress here, in hopes that someone may find it in their hearts to fix my code or give me recommendations on it. I know what you are thinking: "just read the docs, idiot!" - well to be honest, the ones I've found are extremely hard for me to understand; it is not worth it time wise for me to read a book about the 65816 then code the emulator - I'd rather just get it done in pieces. Since I am basically just reverse engineering another emulator, I'll post each class/set of classes as a module.
I'm also not sure as to whether there is a standard for specific offsets that are defined in this 65816 emu (I hope they are..)
NB. Alot of this code is based on converting c++ pointers and other syntactical deviations that it and java may have.
I've decided to go with the NIO ByteBuffer for mostly everything that I've coded up to this point. This is mainly due to the slice() method it offers which essentially gives you a pointer to a position within a parent ByteBuffer.
Memory
This is what I'm extremely sketchy about. First of all, the essentials..am I OK on the sizes?
Code:
package com.androidsnes.cpu.util;
public class Constants {
public static int RAM_SIZE = 0x20000;
public static int ROM_BANKS = 0xC0;
public static int ROM_SIZE = 0x8000;
}
Reading/parsing the smc.. does this look right?
Code:
package com.androidsnes.cpu.util;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
import java.io.FileInputStream;
import static System.out;
/**
* Load/map SMC into memory
* @author RLN
*/
public class Loader {
Memory mem;
Loader(Memory mem) {
this.mem = mem;
}
public void loadSMC(String fn) {
try {
/* open the channel for reading into partitioned byte buffers*/
FileChannel channel = new FileInputStream(fn).getChannel();
ByteBuffer[] ROM = mem.getROM();
int c = 0, len = len2 = channel.size();
len2 &= 512;
String title;
int type;
int country;
boolean palf;
int[] check = new int[2], checkinv = new int[2];
/* Read the first two blocks into memory */
channel.read(ROM[c++] = ByteBuffer.allocateDirect(32768));
channel.read(ROM[c++] = ByteBuffer.allocateDirect(32768));
/* verify checksums */
check[0] = (ROM[1].position(0x7FDE).get() | (ROM[1].position(0x7FDF).get() << 8));
checkinv[0] = (ROM[1].position(0x7FDC).get() | (ROM[1].position(0x7FDF).get() << 8));
check[1] = (ROM[0].position(0x7FDE).get() | (ROM[0].position(0x7FDF).get() << 8));
checkinv[1] = (ROM[0].position(0x7FDC).get() | (ROM[0].position(0x7FDF).get() << 8));
if(((check[0] + checkinv[0]) == 0xFFFF)
&& (check[1] + checkinv[1]) != 0xFFFF) {
// Let's load this puppy up
// Read the title
byte[] titl3 = new byte[20];
title = new String(ROM[1].position(0x7FC0).get(titl3));
// Read the type
type = ROM[1].position(0x7FD6).get() & 0xF;
// Read country & define whether game is PAL or NTSC
country = ROM[1].position(0x7FD9).get();
palf = ((country & 0xF) >= 2) && (country & 0xF < 14);
println("GAME TITLE -> " + title);
println("ROM TYPE -> " + type);
println("COUNTRY CODE -> " + country);
println("IMAGE TYPE -> " + palf ? "PAL" : "NTSC");
// Read the rest of the file into partitioned ROM
while(channel.read(ROM[c++] =
ByteBuffer.allocateDirect(32768)) != -1);
memory.setHiRom(true);
} else if((ROM[0].position(0x7FD5).get() & 1) == 1) {
// Read the title
byte[] titl3 = new byte[20];
title = new String(ROM[0].position(0x7FC0).get(titl3));
// Read the type
type = ROM[0].position(0x7FD6).get() & 0xF;
// Read country & define whether game is PAL or NTSC
country = ROM[0].position(0x7FD9).get();
palf = ((country & 0xF) >= 2);
println("GAME TITLE -> " + title);
println("ROM TYPE -> " + type);
println("COUNTRY CODE -> " + country);
println((len >> 15) + " banks!");
println("IMAGE TYPE -> " + palf ? "PAL" : "NTSC");
// WTF?
int t;
for(t = c; c < (len >> 16); c++)
channel.read(ROM[(c << 1) + 1] =
ByteBuffer.allocateDirect(32768));
for(c = t; c < (len >> 16); c++)
channel.read(ROM[c << 1] =
ByteBuffer.allocateDirect(32768));
memory.setHiRom(true);
} else {
// Read the title
byte[] titl3 = new byte[20];
title = new String(ROM[0].position(0x7FC0).get(titl3));
// Read the type
type = ROM[0].position(0x7FD6).get() & 0xF;
// Read country & define whether game is PAL or NTSC
country = ROM[0].position(0x7FD9).get();
palf = ((country & 0xF) >= 2);
println("GAME TITLE -> " + title);
println("ROM TYPE -> " + type);
println("COUNTRY CODE -> " + country);
println("IMAGE TYPE -> " + palf ? "PAL" : "NTSC");
// Read the rest of the file into partitioned ROM
while(channel.read(ROM[c++] =
ByteBuffer.allocateDirect(32768)) != -1);
memory.setHiRom(false);
}
memory.setProperties(title, type, country);
} catch (IOException e) { e.printStackTrace(); }
}
}
My memory construct...
Code:
package com.androidsnes.cpu;
import java.nio.ByteBuffer;
/**
* An object representing RAM and ROM
* @author RLN
*/
public class Memory {
/**
* Byte buffers!
*/
private ByteBuffer RAM;
private ByteBuffer[] ROM;
/**
* Properties of the current ROM
*/
private boolean hirom;
private String title;
private int type;
private int country;
/**
* Initialize RAM and ROM
*/
Memory(int ram_size, int rom_partitions, int rom_size) {
RAM = ByteBuffer.allocateDirect(ram_size);
for(int i = 0; i < ram_size; i += 2) {
RAM.put(0);
RAM.put(0);
}
RAM.rewind();
ROM = new ByteBuffer[rom_partitions];
}
/**
* Access!
*/
public void setProperties(String title, int type, int country) {
this.title = title;
this.type = type;
this.country = country;
}
public ByteBuffer getRAM() {
return RAM;
}
public ByteBuffer[] getROM() {
return ROM;
}
public ByteBuffer getROM(int bank) {
return ROM[bank];
}
public void setHiRom(boolean t) {
hirom = t;
}
public boolean isHiRom() {
return hirom;
}
}
PointersOkay.. according to the source I'm porting.. this part was sketchy to begin with. Now.. its beyond sketchy.. I am hoping for someone to please validate that this code is OK.. or atleast if you can't understand the code.. verify the offsets.
Code:
package com.androidsnes.cpu;
import java.nio.ByteBuffer;
import java.util.HashMap;
/**
* For mapping and requesting pointers for 65816 via NIO ByteBuffer
* @author RLN
*/
public class PointerMap {
/**
* Reference to RAM and ROM
*/
Memory memory;
/**
* Maps for pointers, format-> <Bank, Ptr>
*/
HashMap<Integer, ByteBuffer> pointers_bank1;
HashMap<Integer, ByteBuffer> pointers_bank2;
PointerMap(Memory memory) {
this.memory = memory;
pointers_bank1 = new HashMap<Integer, ByteBuffer>();
pointers_bank2 = new HashMap<Integer, ByteBuffer>();
}
/**
* These produce a direct pointer to a location in memory
* This seemed optimal for transition from C++ to java
*/
public ByteBuffer createPointer(ByteBuffer original, int pos, int lim) {
return original.position(pos).limit(limit).slice();
}
public ByteBuffer createPointer(ByteBuffer original, int pos) {
return createPointer(original, pos, original.capacity());
}
/**
* Gets a pointer for easy access to offsets in memory used by 65816
*/
public ByteBuffer getPointer(int bank, int idx) {
// This is the memory offset from 0th index of this bank
int ofs = idx & 0x7FFF;
// Get the first bit which tells us which map to use for this bank
HashMap<Integer, ByteBuffer> thismap =
((idx >> 15) == 0 ? pointers_bank1 : pointers_bank2);
// Get the pointer by the associated bank
return thismap.get(bank).position(ofs); // lets hope its not null
}
/**
* Generate pointers for 65816
*/
public void generatePointerMap() {
int c;
/* In the words of the original writer of these pointers, "I hope this works..." */
ByteBuffer buffer;
if(hiROM) {
for(c = 0; c < 0x40; c++) {
pointers_bank2.put(c, createPointer(memory.getROM(c), ((c << 1) + 1)));
pointers_bank1.put(c, createPointer(memory.getRAM(), 0));
}
for(c = 0; c < 0x40; c++) { // According to author, this is probably incorrect
buffer = memory.getROM(c+0x40);
pointers_bank1.put(c+0x40, createPointer(buffer, c << 1));
pointers_bank2.put(c+0x40, createPointer(buffer, ((c << 1) + 1)));
}
for(c = 0; c < 0x80; c++) {
pointers_bank1.put(c+0x80, pointers_bank1.get(c));
pointers_bank2.put(c+0x80, pointers_bank2.get(c));
}
buffer = memory.getRAM();
pointers_bank1.put(0x7E, createPointer(buffer, 0));
pointers_bank2.put(0x7E, createPointer(buffer, 0x8000));
pointers_bank1.put(0x7F, createPointer(buffer, 0x10000));
pointers_bank2.put(0x7F, createPointer(buffer, 0x18000));
pointers_bank1.put(0xFE, createPointer(memory.getROM(0x7C), 0));
pointers_bank2.put(0xFE, createPointer(memory.getROM(0x7D), 0));
pointers_bank1.put(0xFE, createPointer(memory.getROM(0x7E), 0));
pointers_bank2.put(0xFE, createPointer(memory.getROM(0x7F), 0));
} else {
for(c = 0; c < 0x40; c++) {
buffer = memory.getROM(c);
pointers_bank2.put(c, createPointer(buffer, c));
pointers_bank1.put(c, createPointer(buffer, 0));
}
for(c = 0; x < 0x40; c++) {
buffer = memory.getROM(c+0x40);
pointers_bank1.put(c+0x40, createPointer(buffer, c+0x40));
pointers_bank2.put(c+0x40, createPointer(buffer, c+0x40));
}
buffer = memory.getRAM();
pointers_bank1.put(0x7E, createPointer(buffer, 0));;
pointers_bank2.put(0x7E, createPointer(buffer, 0x8000));
pointers_bank1.put(0x7F, createPointer(buffer, 0x10000));
pointers_bank2.put(0x7F, createPointer(buffer, 0x18000));
for(c = 0; c < 0x80; c++) {
pointers_bank1.put(c+0x80, pointers_bank1.get(c));
pointers_bank2.put(c+0x80, pointers_bank2.get(c));
}
}
}
}
OpcodesThis wasn't too hard of a transition from C, as I've given each opcode its own class and I load them all dynamically. Firstly, here is the template for an opcode
Code:
package com.androidsnes.cpu;
/**
* An abstract operation
* @author RLN
*/
public class Operation {
int opcode = 0;
int mode = 0;
/*Processor parentProc;
Operation(Processor p) {
parentProc = p;
}
public Processor getProcessor() {
return parentProc;
}*/
public abstract void execute() { }
public int getOpcode() {
return opcode;
}
public void setOpcode(int o) {
opcode = o;
}
public int getMode() {
return mode;
}
public void setMode(int m) {
mode = m;
}
}
Secondly, here is the opcode loader/mapper
Code:
package com.androidsnes.cpu;
import java.util.HashMap;
import org.androidsnes.cpu.op.BadOperation;
/**
* Map used to easily map instruction to opcode #
* @author RLN
*/
public class OpcodeMap {
/**
* Map of opcodes HashMap<MODE, HashMap<OPCODE INDEX, OPCODE>>
*/
HashMap<Integer, HashMap<Integer, Operation>> opcodes =
new HashMap<Integer, HashMap<Integer, Operation>>();
/**
* Best Way I could think of to map the opcodes directly from C (reference used pointers)
* Parameters: addressing mode, opcode
*/
public Operation get(int mode, int idx) {
return opcodes.get(mode).get(idx);
}
/**
* Generate hashmaps of our operation objects
* Somewhat hacky methinks
*/
public void generateMap() {
try {
String s, thishex;
Operation o;
for(int mode1 = 0; mode1 < 4; i++) {
HashMap<Integer, Operation> mode_ops =
new HashMap<Integer, Operation>();
for(int i = 0; i < 256; i++) {
thishex = Integer.toHexString(i);
thishex = thishex.substring(
thishex.length() - 2,
thishex.length())
.toUpperCase();
try {
o = (Operation)(Class.forName(
"com.androidsnes.cpu.op.m"
+mode1+"op"+thishex)
.newInstance());
o.setOpcode(i);
o.setMode(mode1);
} catch (Exception e) { o = new BadOperation(); }
mode_ops.put(i, o);
}
opcodes.put(mode1, mode_ops);
}
} catch (Exception e) { e.printStackTrace(); }
}
}
thirdly, here is the implementation of an instruction
Code:
package com.androidsnes.cpu.op;
import com.androidsnes.cpu.Operation;
public class BadOperation extends Operation {
public void execute() {
System.out.println("BAD OPCODE: " + getOpcode());
}
}
The implementation classes aren't necessary yet, as they are quite bare bones. Any and all feedback is welcome.
Also: this code might not compile as I haven't tested it.. I don't expect it to compile out-of-the-box (sounds stupid.. i know) since I'm porting it from C++ to java - I really just hope it will all come together when I'm done the 65816 part of the SNES emulator.
I know I'm asking for a lot, not giving context etc., but I basically am praying for a pro to come a long and spoonfeed me. If I am rejected, so be it. There will be more from me to come.. (even faster if someone helps me
)