/** * * @author Andrew Hoffman */ package com.grapeshot.halfnes; import com.grapeshot.halfnes.cheats.ActionReplay; import com.grapeshot.halfnes.mappers.BadMapperException; import com.grapeshot.halfnes.mappers.Mapper; import com.grapeshot.halfnes.ui.ControllerInterface; import com.grapeshot.halfnes.ui.FrameLimiterImpl; import com.grapeshot.halfnes.ui.FrameLimiterInterface; import com.grapeshot.halfnes.ui.GUIInterface; import javafx.application.Platform; public class NES { private Mapper mapper; private APU apu; private CPU cpu; private CPURAM cpuram; private PPU ppu; private GUIInterface gui; private ControllerInterface controller1, controller2; final public static String VERSION = "062-dev"; public boolean runEmulation = false; private boolean dontSleep = false; private boolean shutdown = false; public long frameStartTime, framecount, frameDoneTime; private boolean frameLimiterOn = true; private String curRomPath, curRomName; private final FrameLimiterInterface limiter = new FrameLimiterImpl(this, 16639267); // Pro Action Replay device private ActionReplay actionReplay; public NES(GUIInterface gui) { if (gui != null) { this.gui = gui; gui.setNES(this); gui.run(); } } public CPURAM getCPURAM() { return this.cpuram; } public CPU getCPU() { return this.cpu; } public void run(final String romtoload) { Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 1); //set thread priority higher than the interface thread curRomPath = romtoload; gui.loadROMs(romtoload); run(); } public void run() { while (!shutdown) { if (runEmulation) { frameStartTime = System.nanoTime(); actionReplay.applyPatches(); runframe(); if (frameLimiterOn && !dontSleep) { limiter.sleep(); } frameDoneTime = System.nanoTime() - frameStartTime; } else { limiter.sleepFixed(); if (ppu != null && framecount > 1) { gui.render(); } } } } private synchronized void runframe() { //run cpu, ppu for a whole frame ppu.runFrame(); //do end of frame stuff dontSleep = apu.bufferHasLessThan(1000); //if the audio buffer is completely drained, don't sleep for this frame //this is to prevent the emulator from getting stuck sleeping too much //on a slow system or when the audio buffer runs dry. apu.finishframe(); cpu.modcycles(); // if (framecount == 13 * 60) { // cpu.startLog(); // System.err.println("log on"); // } //render the frame ppu.renderFrame(gui); if ((framecount & 2047) == 0) { //save sram every 30 seconds or so saveSRAM(true); } ++framecount; //System.err.println(framecount); } public void setControllers(ControllerInterface controller1, ControllerInterface controller2) { this.controller1 = controller1; this.controller2 = controller2; } public void toggleFrameLimiter() { frameLimiterOn = !frameLimiterOn; } public synchronized void loadROM(final String filename) { loadROM(filename, null); } public synchronized void loadROM(final String filename, Integer initialPC) { runEmulation = false; if (FileUtils.exists(filename) && (FileUtils.getExtension(filename).equalsIgnoreCase(".nes") || FileUtils.getExtension(filename).equalsIgnoreCase(".nsf"))) { Mapper newmapper; try { final ROMLoader loader = new ROMLoader(filename); loader.parseHeader(); newmapper = Mapper.getCorrectMapper(loader); newmapper.setLoader(loader); newmapper.loadrom(); } catch (BadMapperException e) { gui.messageBox("Error Loading File: ROM is" + " corrupted or uses an unsupported mapper.\n" + e.getMessage()); return; } catch (Exception e) { gui.messageBox("Error Loading File: ROM is" + " corrupted or uses an unsupported mapper.\n" + e.toString() + e.getMessage()); e.printStackTrace(); return; } if (apu != null) { //if rom already running save its sram before closing apu.destroy(); saveSRAM(false); //also get rid of mapper etc. mapper.destroy(); cpu = null; cpuram = null; ppu = null; } mapper = newmapper; //now some annoying getting of all the references where they belong cpuram = mapper.getCPURAM(); actionReplay = new ActionReplay(cpuram); cpu = mapper.cpu; ppu = mapper.ppu; apu = new APU(this, cpu, cpuram); cpuram.setAPU(apu); cpuram.setPPU(ppu); curRomPath = filename; curRomName = FileUtils.getFilenamefromPath(filename); framecount = 0; //if savestate exists, load it if (mapper.hasSRAM()) { loadSRAM(); } //and start emulation cpu.init(initialPC); mapper.init(); setParameters(); runEmulation = true; } else { gui.messageBox("Could not load file:\nFile " + filename + "\n" + "does not exist or is not a valid NES game."); } } private void saveSRAM(final boolean async) { if (mapper != null && mapper.hasSRAM() && mapper.supportsSaves()) { if (async) { FileUtils.asyncwritetofile(mapper.getPRGRam(), FileUtils.stripExtension(curRomPath) + ".sav"); } else { FileUtils.writetofile(mapper.getPRGRam(), FileUtils.stripExtension(curRomPath) + ".sav"); } } } private void loadSRAM() { final String name = FileUtils.stripExtension(curRomPath) + ".sav"; if (FileUtils.exists(name) && mapper.supportsSaves()) { mapper.setPRGRAM(FileUtils.readfromfile(name)); } } public void quit() { //save SRAM and quit //should wait for any save sram workers to be done before here if (cpu != null && curRomPath != null) { runEmulation = false; saveSRAM(false); } //there might be some subtle threading bug with saving? //System.Exit is very dirty and does NOT let the delete on exit handler //fire so the natives stick around... shutdown = true; Platform.exit(); } public synchronized void reset() { if (cpu != null) { mapper.reset(); cpu.reset(); runEmulation = true; apu.pause(); apu.resume(); } //reset frame counter as well because PPU is reset //on Famicom, PPU is not reset when Reset is pressed //but some NES games expect it to be and you get garbage. framecount = 0; } public synchronized void reloadROM() { loadROM(curRomPath); } public synchronized void pause() { if (apu != null) { apu.pause(); } runEmulation = false; } public long getFrameTime() { return frameDoneTime; } public String getrominfo() { if (mapper != null) { return mapper.getrominfo(); } return null; } public synchronized void frameAdvance() { runEmulation = false; if (cpu != null) { runframe(); } } public synchronized void resume() { if (apu != null) { apu.resume(); } if (cpu != null) { runEmulation = true; } } public String getCurrentRomName() { return curRomName; } public boolean isFrameLimiterOn() { return frameLimiterOn; } public void messageBox(final String string) { if (gui != null) { gui.messageBox(string); } } public ControllerInterface getcontroller1() { return controller1; } public ControllerInterface getcontroller2() { return controller2; } public synchronized void setParameters() { if (apu != null) { apu.setParameters(); } if (ppu != null) { ppu.setParameters(); } if (limiter != null && mapper != null) { switch (mapper.getTVType()) { case NTSC: default: limiter.setInterval(16639267); break; case PAL: case DENDY: limiter.setInterval(19997200); } } } /** * Access to the Pro Action Replay device. */ public synchronized ActionReplay getActionReplay() { return actionReplay; } }