From d7e9cc33490d79409b9dfb3d323cdf17d3390549 Mon Sep 17 00:00:00 2001 From: Federico Berti Date: Fri, 9 Aug 2024 17:07:20 +0100 Subject: [PATCH] helios: refactor region handling --- src/main/java/mcd/MegaCd.java | 14 +- .../java/mcd/cart/MegaCdCartInfoProvider.java | 129 ++++++++ src/main/java/mcd/cdd/ExtendedCueSheet.java | 65 +---- .../omegadrive/cart/MdCartInfoProvider.java | 16 +- .../omegadrive/sound/msumd/CueFileParser.java | 18 -- .../java/omegadrive/system/BaseSystem.java | 40 +-- src/main/java/omegadrive/system/Genesis.java | 275 ------------------ .../java/omegadrive/system/Megadrive.java | 17 +- .../omegadrive/system/SystemProvider.java | 4 +- src/main/java/omegadrive/system/gb/Gb.java | 8 +- .../java/omegadrive/util/RegionDetector.java | 54 +++- .../omegadrive/automated/MdRomHeaderTest.java | 2 +- .../omegadrive/cart/mapper/md/EepromTest.java | 5 +- 13 files changed, 224 insertions(+), 423 deletions(-) create mode 100644 src/main/java/mcd/cart/MegaCdCartInfoProvider.java delete mode 100644 src/main/java/omegadrive/system/Genesis.java diff --git a/src/main/java/mcd/MegaCd.java b/src/main/java/mcd/MegaCd.java index 652f84a5..d28ba270 100644 --- a/src/main/java/mcd/MegaCd.java +++ b/src/main/java/mcd/MegaCd.java @@ -20,6 +20,7 @@ package mcd; import mcd.bus.McdSubInterruptHandler; +import mcd.cart.MegaCdCartInfoProvider; import mcd.cdd.ExtendedCueSheet; import mcd.util.McdMemView; import omegadrive.SystemLoader; @@ -36,7 +37,6 @@ import omegadrive.memory.MemoryProvider; import omegadrive.savestate.BaseStateHandler; import omegadrive.system.BaseSystem; -import omegadrive.system.Megadrive; import omegadrive.system.SysUtil; import omegadrive.system.SystemProvider; import omegadrive.ui.DisplayWindow; @@ -239,16 +239,14 @@ private double getMicrosPerTick() { return 1_000_000.0 / (mclkhz / (FM_DIVIDER * MCLK_DIVIDER)); } - @Override - protected RegionDetector.Region getRomRegion() { - return RegionDetector.detectRegion((MdCartInfoProvider) romContext.cartridgeInfoProvider); - } - @Override protected RomContext createRomContext(SysUtil.RomSpec rom) { - romContext = Megadrive.createRomContext(rom, memory, emuFrame.getRegionOverride()); assert !ZipUtil.isCompressedByteStream(rom.file); - return romContext; + RomContext rc = new RomContext(rom); + MdCartInfoProvider mcip = MegaCdCartInfoProvider.createMcdInstance(memory, rc); + rc.cartridgeInfoProvider = mcip; + rc.region = RegionDetector.selectRegion(display, mcip); + return rc; } @Override diff --git a/src/main/java/mcd/cart/MegaCdCartInfoProvider.java b/src/main/java/mcd/cart/MegaCdCartInfoProvider.java new file mode 100644 index 00000000..f9a9b653 --- /dev/null +++ b/src/main/java/mcd/cart/MegaCdCartInfoProvider.java @@ -0,0 +1,129 @@ +package mcd.cart; + +import mcd.cdd.CdModel; +import omegadrive.cart.MdCartInfoProvider; +import omegadrive.memory.IMemoryProvider; +import omegadrive.system.SysUtil; +import omegadrive.system.SystemProvider; +import omegadrive.util.LogHelper; +import omegadrive.util.RegionDetector; +import omegadrive.util.Util; +import omegadrive.vdp.util.MemView; +import org.slf4j.Logger; + +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * Federico Berti + *

+ * Copyright 2024 + */ +public class MegaCdCartInfoProvider extends MdCartInfoProvider { + + private final static Logger LOG = LogHelper.getLogger(MegaCdCartInfoProvider.class.getSimpleName()); + + private final static int headerLen = 0x10; + + private final static int TRACK01_SECURITY_CODE_START = 0x200; + private final static byte[] SCD_SYS_BYTES = "SEGADISCSYSTEM".getBytes(); + private final static byte ff = (byte) 0xff; + private final static byte[] CD_SYNC_BYTES = {0x00, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, 0x00}; + + enum SecurityCodeInfo { + USA(RegionDetector.Region.USA, "d7b7e8e00cee68e15828197ee5090f5a14acd436", 0x584), + EU(RegionDetector.Region.EUROPE, "4a1946f9aaf7261d9d4ccb0556b7342eaf1cf2f8", 0x56E), + JP(RegionDetector.Region.JAPAN, "ffe78f1ffdb8b76b358eb8a29b1d5f9dbeabacca", 0x156); + public final RegionDetector.Region region; + public final String sha1; + public final int length; + + SecurityCodeInfo(RegionDetector.Region region, String sha1, int length) { + this.region = region; + this.sha1 = sha1; + this.length = length; + } + } + + + public SysUtil.RomFileType detectedRomFileType; + public RegionDetector.Region securityCodeRegion; + + + public static MegaCdCartInfoProvider createMcdInstance(IMemoryProvider memoryProvider, SystemProvider.RomContext rom) { + MegaCdCartInfoProvider m = new MegaCdCartInfoProvider(memoryProvider, rom); + m.init(); + return m; + } + + private MegaCdCartInfoProvider(IMemoryProvider memoryProvider, SystemProvider.RomContext rom) { + super(memoryProvider, rom); + } + + @Override + protected void init() { + super.init(); + CdModel.ExtendedTrackData t1 = romContext.sheet.extTracks.get(0); + checkTrack01Header(t1); + securityCodeRegion = verifySecurityCodeRegion(t1); + } + + private void checkTrack01Header(CdModel.ExtendedTrackData track01) { + //check that *.iso is really an iso file internally + SysUtil.RomFileType romFileType = romContext.sheet.romFileType; + try { + byte[] header = new byte[headerLen]; + RandomAccessFile raf = track01.file; + raf.seek(0); + CdModel.TrackDataType trackDataType = track01.trackDataType; + raf.read(header, 0, header.length); + detectedRomFileType = SysUtil.RomFileType.UNKNOWN; + if (Arrays.equals(SCD_SYS_BYTES, 0, SCD_SYS_BYTES.length, header, 0, SCD_SYS_BYTES.length)) { + System.out.println("valid Sega CD image"); + detectedRomFileType = SysUtil.RomFileType.ISO; + } else if (Arrays.equals(CD_SYNC_BYTES, 0, CD_SYNC_BYTES.length, header, 0, CD_SYNC_BYTES.length)) { + System.out.println("CD-ROM synchro pattern"); + detectedRomFileType = SysUtil.RomFileType.BIN_CUE; + } else if (trackDataType == CdModel.TrackDataType.AUDIO) { + System.out.println("CD-AUDIO"); + detectedRomFileType = SysUtil.RomFileType.BIN_CUE; + } + assert detectedRomFileType == romFileType : detectedRomFileType + " vs " + romFileType; + } catch (Exception e) { + e.printStackTrace(); + LOG.error(e.getMessage()); + } + } + + private static RegionDetector.Region verifySecurityCodeRegion(CdModel.ExtendedTrackData track01) { + try { + byte[] secCode = new byte[0x700]; + //sector 2352 starts with 0x10 sync/header bytes, ignore them + int secCodeStart = track01.trackDataType.size == CdModel.SectorSize.S_2048 ? + TRACK01_SECURITY_CODE_START : TRACK01_SECURITY_CODE_START + 0x10; + RandomAccessFile raf = track01.file; + raf.seek(secCodeStart); + raf.read(secCode); + ByteBuffer bb = ByteBuffer.wrap(secCode); + for (SecurityCodeInfo sci : SecurityCodeInfo.values()) { + byte[] b = new byte[sci.length]; + bb.position(0); + bb.get(b); + String sha1 = Util.computeSha1Sum(b); + if (sci.sha1.equalsIgnoreCase(sha1)) { + System.out.println(sci.region); + return sci.region; + } + } + LOG.error("Unknown security code!"); + StringBuilder sb = new StringBuilder(track01 + "\n"); + MemView.fillFormattedString(sb, secCode, 0, secCode.length); + System.out.println(sb); + } catch (Exception e) { + e.printStackTrace(); + LOG.error(e.getMessage()); + } + return null; + } +} diff --git a/src/main/java/mcd/cdd/ExtendedCueSheet.java b/src/main/java/mcd/cdd/ExtendedCueSheet.java index e3bad907..16c5d24e 100644 --- a/src/main/java/mcd/cdd/ExtendedCueSheet.java +++ b/src/main/java/mcd/cdd/ExtendedCueSheet.java @@ -10,7 +10,10 @@ import org.digitalmediaserver.cuelib.TrackData; import org.slf4j.Logger; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.RandomAccessFile; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; @@ -28,13 +31,6 @@ public class ExtendedCueSheet implements Closeable { private final static Logger LOG = LogHelper.getLogger(ExtendedCueSheet.class.getSimpleName()); - private final static byte[] SCD_SYS_BYTES = "SEGADISCSYSTEM".getBytes(); - private final static byte ff = (byte) 0xff; - private final static byte[] CD_SYNC_BYTES = {0x00, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, 0x00}; - - //TODO remove at some point - private static final boolean CUE_TEST_MODE = false; - private static final Path NO_BIN_FILE_PATH = Paths.get(".", "test.bin"); //placeholder name private static final String TEMPLATE_ISO_NAME_PH = ""; @@ -52,13 +48,6 @@ public class ExtendedCueSheet implements Closeable { public final List extTracks = new ArrayList<>(); public int numTracks, sectorEnd; protected final Map fileCache = new HashMap<>(); - private static RandomAccessFile NO_BIN_FILE; - - static { - if (CUE_TEST_MODE) { - LOG.warn("Cue test mode: {}", CUE_TEST_MODE); - } - } public ExtendedCueSheet(Path discImage, RomFileType rft) { assert rft.isDiscImage(); @@ -107,35 +96,11 @@ private void parseCueSheet() { List tracks = extCueSheet.cueSheet.getAllTrackData(); assert !tracks.isEmpty(); extCueSheet.numTracks = tracks.size(); - checkTrack01Header(tracks.get(0)); for (TrackData track : tracks) { parseTrack(extCueSheet, track.getNumber(), cuePath); } } - private void checkTrack01Header(TrackData track01) { - try { - byte[] header = new byte[16]; - RandomAccessFile raf = getDataFile(this, track01.getParent().getFile(), cuePath); - TrackDataType trackDataType = TrackDataType.parse(track01.getDataType()); - raf.read(header, 0, header.length); - RomFileType detected = RomFileType.UNKNOWN; - if (Arrays.equals(SCD_SYS_BYTES, 0, SCD_SYS_BYTES.length, header, 0, SCD_SYS_BYTES.length)) { - System.out.println("valid Sega CD image"); - detected = RomFileType.ISO; - } else if (Arrays.equals(CD_SYNC_BYTES, 0, CD_SYNC_BYTES.length, header, 0, CD_SYNC_BYTES.length)) { - System.out.println("CD-ROM synchro pattern"); - detected = RomFileType.BIN_CUE; - } else if (trackDataType == TrackDataType.AUDIO) { - System.out.println("CD-AUDIO"); - detected = RomFileType.BIN_CUE; - } - assert detected == romFileType : detected + " vs " + romFileType; - } catch (Exception e) { - e.printStackTrace(); - } - } - private void parseTrack(ExtendedCueSheet extCueSheet, int trackNumber, Path cuePath) { TrackData trackData = getTrack(extCueSheet.cueSheet, trackNumber); RandomAccessFile raf = getDataFile(extCueSheet, trackData.getParent().getFile(), cuePath); @@ -171,15 +136,12 @@ private void parseTrack(ExtendedCueSheet extCueSheet, int trackNumber, Path cueP } private static RandomAccessFile getDataFile(ExtendedCueSheet extCueSheet, String key, Path file, Path cuePath) { + //TODO close RandomAccessFile if (!extCueSheet.fileCache.containsKey(key)) { try { extCueSheet.fileCache.put(key, new RandomAccessFile(file.toFile(), "r")); } catch (Exception e) { - if (CUE_TEST_MODE) { - handleTestCueMode(extCueSheet, cuePath, key); - } else { - e.printStackTrace(); - } + e.printStackTrace(); } } return extCueSheet.fileCache.get(key); @@ -191,21 +153,6 @@ private static RandomAccessFile getDataFile(ExtendedCueSheet extCueSheet, String return getDataFile(extCueSheet, key, fp, cuePath); } - private static void handleTestCueMode(ExtendedCueSheet extCueSheet, Path cuePath, String key) { - try { - if (NO_BIN_FILE == null) { - Path p = cuePath.resolveSibling(NO_BIN_FILE_PATH); - NO_BIN_FILE = new RandomAccessFile(p.toFile(), "r"); - LOG.warn("Missing bin file: {}, using instead: {}, cue_test_mode: {}", key, p.toAbsolutePath(), CUE_TEST_MODE); - } - extCueSheet.fileCache.put(key, NO_BIN_FILE); - extCueSheet.romFileType = RomFileType.BIN_CUE; - } catch (FileNotFoundException ex) { - LOG.error("Missing bin file: {}", key); - throw new RuntimeException(ex); - } - } - private static TrackData getTrack(CueSheet cueSheet, int number) { assert number > 0; TrackData td = cueSheet.getAllTrackData().get(number - 1); diff --git a/src/main/java/omegadrive/cart/MdCartInfoProvider.java b/src/main/java/omegadrive/cart/MdCartInfoProvider.java index 01cb12dc..9099c960 100644 --- a/src/main/java/omegadrive/cart/MdCartInfoProvider.java +++ b/src/main/java/omegadrive/cart/MdCartInfoProvider.java @@ -23,6 +23,7 @@ import omegadrive.cart.loader.MdRomDbModel; import omegadrive.cart.mapper.md.MdMapperType; import omegadrive.memory.IMemoryProvider; +import omegadrive.memory.MemoryProvider; import omegadrive.system.SysUtil; import omegadrive.util.LogHelper; import omegadrive.util.Size; @@ -190,12 +191,17 @@ public String toString() { return sb.append(headerInfo).toString(); } + public static final MdCartInfoProvider NO_PROVIDER = new MdCartInfoProvider(MemoryProvider.NO_MEMORY, RomContext.NO_ROM); + public static MdCartInfoProvider createMdInstance(IMemoryProvider memoryProvider, RomContext rom) { - MdCartInfoProvider provider = new MdCartInfoProvider(); - provider.memoryProvider = memoryProvider; - provider.romContext = rom; - provider.init(); - return provider; + MdCartInfoProvider m = new MdCartInfoProvider(memoryProvider, rom); + m.init(); + return m; + } + + protected MdCartInfoProvider(IMemoryProvider memoryProvider, RomContext rom) { + this.memoryProvider = memoryProvider; + this.romContext = rom; } public boolean isSramUsedWithBrokenHeader(long address) { diff --git a/src/main/java/omegadrive/sound/msumd/CueFileParser.java b/src/main/java/omegadrive/sound/msumd/CueFileParser.java index 8b8d2375..b25f4821 100644 --- a/src/main/java/omegadrive/sound/msumd/CueFileParser.java +++ b/src/main/java/omegadrive/sound/msumd/CueFileParser.java @@ -1,21 +1,3 @@ -/* - * Copyright (C) 2003, 2014 Graham Sanderson - * - * This file is part of JPSX. - * - * JPSX 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 3 of the License, or - * (at your option) any later version. - * - * JPSX 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 JPSX. If not, see . - */ package omegadrive.sound.msumd; import omegadrive.util.LogHelper; diff --git a/src/main/java/omegadrive/system/BaseSystem.java b/src/main/java/omegadrive/system/BaseSystem.java index 5504fec1..cce84a19 100644 --- a/src/main/java/omegadrive/system/BaseSystem.java +++ b/src/main/java/omegadrive/system/BaseSystem.java @@ -24,6 +24,7 @@ import omegadrive.SystemLoader.SystemType; import omegadrive.UserConfigHolder; import omegadrive.bus.model.BaseBusProvider; +import omegadrive.cart.CartridgeInfoProvider; import omegadrive.input.InputProvider; import omegadrive.input.KeyboardInput; import omegadrive.joypad.JoypadProvider; @@ -36,7 +37,6 @@ import omegadrive.ui.DisplayWindow; import omegadrive.ui.PrefStore; import omegadrive.util.*; -import omegadrive.util.RegionDetector.Region; import omegadrive.vdp.model.BaseVdpAdapterEventSupport.VdpEventListener; import omegadrive.vdp.model.BaseVdpProvider; import org.slf4j.Logger; @@ -64,11 +64,9 @@ public abstract class BaseSystem implements protected InputProvider inputProvider; protected BUS bus; protected SystemType systemType; - - protected VideoMode videoMode = VideoMode.PAL_H40_V30; protected RomContext romContext = NO_ROM; protected Future runningRomFuture; - protected DisplayWindow emuFrame; + protected DisplayWindow display; protected DisplayWindow.DisplayContext displayContext; @@ -116,21 +114,8 @@ protected void initAfterRomLoad() { protected abstract void updateVideoMode(boolean force); - protected Region getRomRegion() { - return Region.JAPAN; - } - - public static Region getRegionInternal(String regionOvr, Region romRegion) { - Region ovrRegion = RegionDetector.getRegion(regionOvr); - if (ovrRegion != null && ovrRegion != romRegion) { - LOG.info("Setting region override from: {} to {}", romRegion, ovrRegion); - romRegion = ovrRegion; - } - return romRegion; - } - protected BaseSystem(DisplayWindow emuFrame) { - this.emuFrame = emuFrame; + this.display = emuFrame; } @Override @@ -152,7 +137,7 @@ public void handleSystemEvent(SystemEvent event, Object parameter) { handleSaveState((Path) parameter); break; case TOGGLE_FULL_SCREEN: - emuFrame.setFullScreen((Boolean) parameter); + display.setFullScreen((Boolean) parameter); break; case TOGGLE_PAUSE: handlePause(); @@ -192,8 +177,8 @@ protected void handleSoftReset() { } protected void reloadWindowState() { - emuFrame.addKeyListener(KeyboardInput.createKeyAdapter(systemType, joypad)); - emuFrame.reloadControllers(inputProvider.getAvailableControllers()); + display.addKeyListener(KeyboardInput.createKeyAdapter(systemType, joypad)); + display.reloadControllers(inputProvider.getAvailableControllers()); } public void handleNewRom(RomSpec romSpec) { @@ -206,7 +191,7 @@ public void handleNewRom(RomSpec romSpec) { private void handleCloseApp() { try { handleCloseRom(); - emuFrame.close(); + display.close(); sound.close(); Util.executorService.shutdown(); Util.executorService.awaitTermination(1, TimeUnit.SECONDS); @@ -309,12 +294,13 @@ private void handleRomInternal() { Util.sleep(100); } LOG.info("Rom thread cancel"); - emuFrame.resetScreen(); + display.resetScreen(); sound.reset(); bus.closeRom(); telemetry.reset(); Optional.ofNullable(vdp).ifPresent(Device::reset); cycleCounter = 1; + romContext = NO_ROM; } } @@ -374,9 +360,10 @@ public void run() { return; } memory.setRomData(data); + assert romContext == NO_ROM; romContext = createRomContext(romSpec); String romName = FileUtil.getFileName(romSpec.file); - emuFrame.setRomData(romContext); + display.setRomData(romContext); Thread.currentThread().setName(threadNamePrefix + romName); Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 1); LOG.info("Running rom: {},\n{}", romName, romContext); @@ -395,7 +382,8 @@ public void run() { protected RomContext createRomContext(RomSpec rom) { RomContext rc = new RomContext(rom); - rc.region = getRegionInternal(emuFrame.getRegionOverride(), getRomRegion()); + rc.cartridgeInfoProvider = CartridgeInfoProvider.createInstance(memory, rom.file); + rc.region = RegionDetector.selectRegion(display, rc.cartridgeInfoProvider); return rc; } @@ -408,7 +396,7 @@ protected void handleVdpDumpScreenData() { protected void doRendering(int[] data) { displayContext.data = data; - emuFrame.renderScreenLinear(displayContext); + display.renderScreenLinear(displayContext); } private void handlePause() { diff --git a/src/main/java/omegadrive/system/Genesis.java b/src/main/java/omegadrive/system/Genesis.java deleted file mode 100644 index 5e091501..00000000 --- a/src/main/java/omegadrive/system/Genesis.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Genesis - * Copyright (c) 2018-2019 Federico Berti - * Last modified: 26/10/19 15:29 - * - * This program 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 3 of the License, or - * (at your option) any later version. - * - * This program 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 this program. If not, see . - */ - -package omegadrive.system; - -import omegadrive.SystemLoader; -import omegadrive.bus.md.GenesisBus; -import omegadrive.bus.md.SvpMapper; -import omegadrive.bus.model.GenesisBusProvider; -import omegadrive.cart.MdCartInfoProvider; -import omegadrive.cpu.m68k.M68kProvider; -import omegadrive.cpu.m68k.MC68000Wrapper; -import omegadrive.cpu.ssp16.Ssp16; -import omegadrive.cpu.z80.Z80CoreWrapper; -import omegadrive.cpu.z80.Z80Provider; -import omegadrive.input.InputProvider; -import omegadrive.joypad.GenesisJoypad; -import omegadrive.memory.MemoryProvider; -import omegadrive.savestate.BaseStateHandler; -import omegadrive.sound.SoundProvider; -import omegadrive.ui.DisplayWindow; -import omegadrive.util.LogHelper; -import omegadrive.util.RegionDetector; -import omegadrive.util.RegionDetector.Region; -import omegadrive.util.Util; -import omegadrive.vdp.model.BaseVdpProvider; -import omegadrive.vdp.model.GenesisVdpProvider; -import omegadrive.vdp.util.MemView; -import omegadrive.vdp.util.UpdatableViewer; -import org.slf4j.Logger; - -/** - * Megadrive main class - * - * @author Federico Berti - * - * @Deprecated unused, to be removed - */ -@Deprecated -public class Genesis extends BaseSystem { - - public final static boolean verbose = false; - //the emulation runs at MCLOCK_MHZ/MCLK_DIVIDER - public final static int MCLK_DIVIDER = 7; - protected final static double VDP_RATIO = 4.0 / MCLK_DIVIDER; //16 -> MCLK/4, 20 -> MCLK/5 - protected final static int M68K_DIVIDER = 7 / MCLK_DIVIDER; - public final static double[] vdpVals = {VDP_RATIO * BaseVdpProvider.MCLK_DIVIDER_FAST_VDP, VDP_RATIO * BaseVdpProvider.MCLK_DIVIDER_SLOW_VDP}; - protected final static int Z80_DIVIDER = 14 / MCLK_DIVIDER; - protected final static int FM_DIVIDER = 42 / MCLK_DIVIDER; - protected static final int SVP_CYCLES = 100; - protected static final int SVP_RUN_CYCLES = (int) (SVP_CYCLES * 1.5); - - private final static Logger LOG = LogHelper.getLogger(Genesis.class.getSimpleName()); - - static { -// System.setProperty("68k.debug", "true"); -// System.setProperty("helios.68k.debug.mode", "2"); -// System.setProperty("z80.debug", "true"); - } - - protected Z80Provider z80; - protected M68kProvider cpu; - protected Ssp16 ssp16 = Ssp16.NO_SVP; - protected UpdatableViewer memView = UpdatableViewer.NO_OP_VIEWER; - protected boolean hasSvp = ssp16 != Ssp16.NO_SVP; - protected double nextVdpCycle = vdpVals[0]; - protected int next68kCycle = M68K_DIVIDER; - protected int nextZ80Cycle = Z80_DIVIDER; - protected int nextFMCycle = FM_DIVIDER; - protected int nextSvpCycle = SVP_CYCLES; - - protected Genesis(DisplayWindow emuFrame) { - super(emuFrame); - systemType = SystemLoader.SystemType.GENESIS; - } - - public static SystemProvider createNewInstance(DisplayWindow emuFrame) { - return new Genesis(emuFrame); - } - - @Override - public void init() { - stateHandler = BaseStateHandler.EMPTY_STATE; - joypad = GenesisJoypad.create(this); - inputProvider = InputProvider.createInstance(joypad); - - memory = MemoryProvider.createGenesisInstance(); - bus = createBus(); - vdp = GenesisVdpProvider.createVdp(bus); - cpu = MC68000Wrapper.createInstance(bus); - z80 = Z80CoreWrapper.createInstance(getSystemType(), bus); - //sound attached later - sound = SoundProvider.NO_SOUND; - - bus.attachDevices(this, memory, joypad, vdp, cpu, z80); - reloadWindowState(); - createAndAddVdpEventListener(); - } - - protected void loop() { - updateVideoMode(true); - int cnt; - do { - cnt = cycleCounter; - cnt = runZ80(cnt); - cnt = run68k(cnt); - cnt = runFM(cnt); - if (hasSvp) { - runSvp(cnt); - } - //this should be last as it could change the counter - cnt = runVdp(cnt); - cycleCounter = cnt; - } while (!futureDoneFlag); - } - - private int runSvp(int untilClock) { - while (nextSvpCycle <= untilClock) { - ssp16.ssp1601_run(SVP_RUN_CYCLES); - nextSvpCycle += SVP_CYCLES; - } - return untilClock; - } - - private int runVdp(int untilClock) { - while (nextVdpCycle <= untilClock) { - int vdpMclk = vdp.runSlot(); - nextVdpCycle += vdpVals[vdpMclk - 4]; - if (cycleCounter == 0) { //counter could be reset to 0 when calling vdp::runSlot - untilClock = 0; - } - } - return (int) Math.max(untilClock, nextVdpCycle); - } - - protected final int run68k(int untilClock) { - while (next68kCycle <= untilClock) { - boolean isRunning = bus.is68kRunning(); - boolean canRun = !cpu.isStopped() && isRunning; - int cycleDelay = 1; - if (canRun) { - cycleDelay = cpu.runInstruction(); - } - //interrupts are processed after the current instruction - if (isRunning) { - bus.handleVdpInterrupts68k(); - } - cycleDelay = Math.max(1, cycleDelay); - next68kCycle += M68K_DIVIDER * cycleDelay; - } - return untilClock; - } - - private int runZ80(int untilClock) { - while (nextZ80Cycle <= untilClock) { - int cycleDelay = 0; - boolean running = bus.isZ80Running(); - if (running) { - cycleDelay = z80.executeInstruction(); - bus.handleVdpInterruptsZ80(); - } - cycleDelay = Math.max(1, cycleDelay); - nextZ80Cycle += Z80_DIVIDER * cycleDelay; - } - return untilClock; - } - - private int runFM(int untilClock) { - while (nextFMCycle <= untilClock) { - bus.getFm().tick(); - nextFMCycle += FM_DIVIDER; - } - return nextFMCycle; - } - - protected GenesisBusProvider createBus() { - return new GenesisBus(); - } - - @Override - protected void updateVideoMode(boolean force) { - if (force || displayContext.videoMode != vdp.getVideoMode()) { - displayContext.videoMode = vdp.getVideoMode(); - double microsPerTick = getMicrosPerTick(); - sound.getFm().setMicrosPerTick(microsPerTick); - targetNs = (long) (getRegion().getFrameIntervalMs() * Util.MILLI_IN_NS); - LOG.info("Video mode changed: {}, microsPerTick: {}", displayContext.videoMode, microsPerTick); - } - } - - private double getMicrosPerTick() { - double mclkhz = displayContext.videoMode.isPal() ? Util.GEN_PAL_MCLOCK_MHZ : Util.GEN_NTSC_MCLOCK_MHZ; - return 1_000_000.0 / (mclkhz / (FM_DIVIDER * MCLK_DIVIDER)); - } - - @Override - protected Region getRomRegion() { - return RegionDetector.detectRegion((MdCartInfoProvider) romContext.cartridgeInfoProvider); - } - - @Override - protected RomContext createRomContext(SysUtil.RomSpec rom) { - return Megadrive.createRomContext(rom, memory, emuFrame.getRegionOverride()); - } - - @Override - public void newFrame() { - checkSvp(); - memView.update(); - super.newFrame(); - } - - private void checkSvp() { - ssp16 = SvpMapper.ssp16; - hasSvp = ssp16 != Ssp16.NO_SVP; - } - - protected UpdatableViewer createMemView() { - return MemView.createInstance(bus, vdp.getVdpMemory()); - } - - /** - * Counters can go negative when the video mode changes - */ - @Override - protected void resetCycleCounters(int counter) { - nextZ80Cycle = Math.max(1, counter - nextZ80Cycle); - next68kCycle = Math.max(1, counter - next68kCycle); - nextVdpCycle = Math.max(1, counter - nextVdpCycle); - nextFMCycle = Math.max(1, counter - nextFMCycle); - nextSvpCycle = Math.max(1, counter - nextSvpCycle); - } - - @Override - protected void initAfterRomLoad() { - super.initAfterRomLoad(); - bus.attachDevice(sound); - vdp.addVdpEventListener(sound); - SvpMapper.ssp16 = Ssp16.NO_SVP; - resetAfterRomLoad(); - memView.reset(); - memView = createMemView(); - } - - @Override - protected void resetAfterRomLoad() { - super.resetAfterRomLoad(); - cpu.reset(); - z80.reset(); //TODO confirm this is needed - } - - @Override - protected void handleSoftReset() { - if (softReset) { - cpu.softReset(); - } - super.handleSoftReset(); - } -} diff --git a/src/main/java/omegadrive/system/Megadrive.java b/src/main/java/omegadrive/system/Megadrive.java index 2557f1cb..acc02266 100644 --- a/src/main/java/omegadrive/system/Megadrive.java +++ b/src/main/java/omegadrive/system/Megadrive.java @@ -31,7 +31,6 @@ import omegadrive.cpu.z80.Z80Provider; import omegadrive.input.InputProvider; import omegadrive.joypad.GenesisJoypad; -import omegadrive.memory.IMemoryProvider; import omegadrive.memory.MemoryProvider; import omegadrive.savestate.BaseStateHandler; import omegadrive.ui.DisplayWindow; @@ -45,8 +44,6 @@ import org.slf4j.Logger; import s32x.util.Md32xRuntimeData; -import java.util.Optional; - import static omegadrive.util.BufferUtil.CpuDeviceAccess.M68K; import static omegadrive.util.BufferUtil.CpuDeviceAccess.Z80; @@ -200,24 +197,12 @@ private double getMicrosPerTick() { return 1_000_000.0 / (mclkhz / (FM_DIVIDER * MCLK_DIVIDER)); } - @Override - protected RegionDetector.Region getRomRegion() { - return RegionDetector.detectRegion((MdCartInfoProvider) romContext.cartridgeInfoProvider); - } - @Override protected RomContext createRomContext(SysUtil.RomSpec rom) { - return createRomContext(rom, memory, emuFrame.getRegionOverride()); - } - - public static RomContext createRomContext(SysUtil.RomSpec rom, IMemoryProvider memory, String regionOverride) { RomContext rc = new RomContext(rom); MdCartInfoProvider mcip = MdCartInfoProvider.createMdInstance(memory, rc); rc.cartridgeInfoProvider = mcip; - String regionOvr = Optional.ofNullable(mcip.getEntry().forceRegion). - orElse(regionOverride); - RegionDetector.Region romRegion = RegionDetector.detectRegion((MdCartInfoProvider) rc.cartridgeInfoProvider); - rc.region = getRegionInternal(regionOvr, romRegion); + rc.region = RegionDetector.selectRegion(display, mcip); return rc; } diff --git a/src/main/java/omegadrive/system/SystemProvider.java b/src/main/java/omegadrive/system/SystemProvider.java index 1bbc5f51..30ea4d21 100644 --- a/src/main/java/omegadrive/system/SystemProvider.java +++ b/src/main/java/omegadrive/system/SystemProvider.java @@ -23,7 +23,7 @@ import omegadrive.Device; import omegadrive.SystemLoader; import omegadrive.cart.CartridgeInfoProvider; -import omegadrive.cart.MdCartInfoProvider; +import omegadrive.memory.MemoryProvider; import omegadrive.system.SysUtil.RomSpec; import omegadrive.util.RegionDetector; @@ -42,7 +42,7 @@ class RomContext { NO_ROM = new RomContext(); NO_ROM.region = RegionDetector.Region.USA; NO_ROM.romSpec = RomSpec.of(Path.of("NO_PATH")); - NO_ROM.cartridgeInfoProvider = new MdCartInfoProvider(); + NO_ROM.cartridgeInfoProvider = CartridgeInfoProvider.createInstance(MemoryProvider.NO_MEMORY, NO_ROM.romSpec.file); } public RegionDetector.Region region; diff --git a/src/main/java/omegadrive/system/gb/Gb.java b/src/main/java/omegadrive/system/gb/Gb.java index 21205f43..6677ce05 100644 --- a/src/main/java/omegadrive/system/gb/Gb.java +++ b/src/main/java/omegadrive/system/gb/Gb.java @@ -15,7 +15,6 @@ import omegadrive.system.SystemProvider; import omegadrive.ui.DisplayWindow; import omegadrive.util.LogHelper; -import omegadrive.util.RegionDetector; import omegadrive.util.Util; import omegadrive.util.VideoMode; import omegadrive.vdp.model.BaseVdpAdapter; @@ -77,7 +76,7 @@ protected void loop() { private Emulator createEmulator(String[] args) { try { - HeliosDisplay display = new HeliosDisplay(this, emuFrame); + HeliosDisplay display = new HeliosDisplay(this, this.display); emulator = new Emulator(args, display, (SoundOutput) sound.getFm(), controller); vdp = BaseVdpAdapter.getVdpProviderWrapper(VideoMode.NTSCJ_H20_V18, display); } catch (Exception e) { @@ -118,9 +117,4 @@ protected void resetCycleCounters(int counter) { protected void updateVideoMode(boolean force) { displayContext.videoMode = vdp.getVideoMode(); } - - @Override - protected RegionDetector.Region getRomRegion() { - return RegionDetector.Region.USA; - } } diff --git a/src/main/java/omegadrive/util/RegionDetector.java b/src/main/java/omegadrive/util/RegionDetector.java index 8033fb66..30a04a6d 100644 --- a/src/main/java/omegadrive/util/RegionDetector.java +++ b/src/main/java/omegadrive/util/RegionDetector.java @@ -20,7 +20,10 @@ package omegadrive.util; import com.google.common.base.Strings; +import mcd.cart.MegaCdCartInfoProvider; +import omegadrive.cart.CartridgeInfoProvider; import omegadrive.cart.MdCartInfoProvider; +import omegadrive.ui.DisplayWindow; import org.slf4j.Logger; import java.util.*; @@ -34,7 +37,7 @@ public class RegionDetector { public final static int PAL_FPS = 50; public final static int NTSC_FPS = 60; - public static Region detectRegion(MdCartInfoProvider cartInfoProvider, boolean verbose) { + public static Region detectHeaderRegion(MdCartInfoProvider cartInfoProvider, boolean verbose) { String s = cartInfoProvider.getRegion(); s = Strings.padEnd(s, 3, ' '); assert s.length() == 3; @@ -58,8 +61,53 @@ public static Region detectRegion(MdCartInfoProvider cartInfoProvider, boolean v return res; } - public static Region detectRegion(MdCartInfoProvider cartInfo) { - return detectRegion(cartInfo, false); + public static Region detectHeaderRegion(MdCartInfoProvider cartInfo) { + return detectHeaderRegion(cartInfo, false); + } + + public static Region selectRegion(DisplayWindow w, CartridgeInfoProvider cip) { + assert cip != MdCartInfoProvider.NO_PROVIDER; + Region regionFileName = getRegionFileName(cip.getRomName()); + Region regionOvrUi = RegionDetector.getRegion(w.getRegionOverride()); + Region regionOvrConfig = null; + Region securityCodeRegion = null; + Region romHeaderRegion = RegionDetector.getRegion(cip.getRegion()); + if (cip instanceof MdCartInfoProvider mcip) { + regionOvrConfig = RegionDetector.getRegion(Optional.ofNullable((mcip).getEntry().forceRegion). + orElse(null)); + romHeaderRegion = detectHeaderRegion(mcip, false); + } + if (cip instanceof MegaCdCartInfoProvider mcdip) { + securityCodeRegion = mcdip.securityCodeRegion; + } + if (regionOvrUi != null && regionOvrUi != romHeaderRegion) { + LOG.info("Setting region override from UI: {} to {}", romHeaderRegion, regionOvrUi); + return regionOvrUi; + } + if (regionOvrConfig != null && regionOvrConfig != romHeaderRegion) { + LOG.info("Setting region override from Config: {} to {}", romHeaderRegion, regionOvrConfig); + return regionOvrConfig; + } + if (securityCodeRegion != null && securityCodeRegion != romHeaderRegion) { + LOG.info("Setting region from securityCode: {} to {}", romHeaderRegion, securityCodeRegion); + return securityCodeRegion; + } + if (regionFileName != null && regionFileName != romHeaderRegion) { + LOG.info("Setting region from fileName: {} to {}", romHeaderRegion, regionFileName); + return regionFileName; + } + LOG.info("Region: {}", romHeaderRegion); + return romHeaderRegion; + } + + public static Region getRegionFileName(String fileName) { + boolean eu = fileName.toUpperCase().contains("(EUROPE)"); + boolean us = fileName.toUpperCase().contains("(US)"); + boolean jp = fileName.toUpperCase().contains("(JAPAN)"); + Region r = eu ? Region.EUROPE : null; + r = jp ? Region.JAPAN : r; + r = us ? Region.USA : r; + return r; } /** diff --git a/src/test/java/omegadrive/automated/MdRomHeaderTest.java b/src/test/java/omegadrive/automated/MdRomHeaderTest.java index 2ad14289..724cc50b 100644 --- a/src/test/java/omegadrive/automated/MdRomHeaderTest.java +++ b/src/test/java/omegadrive/automated/MdRomHeaderTest.java @@ -46,7 +46,7 @@ private void testCartridgeInfo() throws Exception { CartridgeInfoProvider cartridgeInfoProvider = MdCartInfoProvider.createInstance(memoryProvider, rom); System.out.println(cartridgeInfoProvider); - System.out.println(RegionDetector.detectRegion((MdCartInfoProvider) cartridgeInfoProvider)); + System.out.println(RegionDetector.detectHeaderRegion((MdCartInfoProvider) cartridgeInfoProvider)); } catch (Exception e) { System.err.println("Exception: " + rom.getFileName()); e.printStackTrace(); diff --git a/src/test/java/omegadrive/cart/mapper/md/EepromTest.java b/src/test/java/omegadrive/cart/mapper/md/EepromTest.java index 46a82fbc..4e075ca3 100644 --- a/src/test/java/omegadrive/cart/mapper/md/EepromTest.java +++ b/src/test/java/omegadrive/cart/mapper/md/EepromTest.java @@ -1,6 +1,5 @@ package omegadrive.cart.mapper.md; -import omegadrive.cart.MdCartInfoProvider; import omegadrive.cart.loader.MdLoader; import omegadrive.cart.loader.MdRomDbModel; import omegadrive.cart.mapper.RomMapper; @@ -9,6 +8,7 @@ import org.junit.jupiter.api.Test; import static omegadrive.cart.MdCartInfoProvider.DEFAULT_SRAM_START_ADDRESS; +import static omegadrive.cart.MdCartInfoProvider.NO_PROVIDER; import static omegadrive.cart.loader.MdRomDbModel.NO_ENTRY; @@ -33,8 +33,7 @@ public void writeData(int address, int data, Size size) { private void setup() { MdRomDbModel.RomDbEntry romDbEntry = MdLoader.getEntry(nflQc32x_serial); Assertions.assertNotEquals(NO_ENTRY, romDbEntry); - MdCartInfoProvider provider = new MdCartInfoProvider(); - mapper = MdBackupMemoryMapper.createInstance(rom, provider, RomMapper.SramMode.READ_WRITE, romDbEntry); + mapper = MdBackupMemoryMapper.createInstance(rom, NO_PROVIDER, RomMapper.SramMode.READ_WRITE, romDbEntry); } /**