diff --git a/.github/workflows/waterbox-cores.yml b/.github/workflows/waterbox-cores.yml index db32dc64f6..beb1ae59da 100644 --- a/.github/workflows/waterbox-cores.yml +++ b/.github/workflows/waterbox-cores.yml @@ -37,6 +37,7 @@ jobs: git submodule update --init uae/libretro-uae; git submodule update --init stella/core; git submodule update --init dsda/core; + git submodule update --init opera/opera-libretro; git submodule update --init --recursive dosbox/dosbox-x; - name: Install clang 18 run: wget https://apt.llvm.org/llvm.sh; @@ -70,6 +71,7 @@ jobs: Assets/dll/libsnes.wbx.zst Assets/dll/melonDS.wbx.zst Assets/dll/ngp.wbx.zst + Assets/dll/opera.wbx.zst Assets/dll/pcfx.wbx.zst Assets/dll/picodrive.wbx.zst Assets/dll/shock.wbx.zst diff --git a/.gitmodules b/.gitmodules index aa899a85f0..b37b17d919 100644 --- a/.gitmodules +++ b/.gitmodules @@ -93,3 +93,7 @@ path = waterbox/dsda/core url = https://github.com/TASEmulators/dsda-doom.git branch = wbx +[submodule "waterbox/opera/opera-libretro"] + path = waterbox/opera/opera-libretro + url = https://github.com/TASEmulators/opera-libretro.git + branch = wbx diff --git a/Assets/defctrl.json b/Assets/defctrl.json index f615633a9b..9048080e02 100644 --- a/Assets/defctrl.json +++ b/Assets/defctrl.json @@ -1109,6 +1109,48 @@ "Deadzone": 0.0 } }, + "3DO Controller": { + "P1 Mouse X": { + "Value": "RMouse X", + "Mult": 1.0, + "Deadzone": 0.0 + }, + "P1 Mouse Y": { + "Value": "RMouse Y", + "Mult": 1.0, + "Deadzone": 0.0 + }, + "P1 Flight Stick Horizontal Axis": { + "Value": "RMouse X", + "Mult": 1.0, + "Deadzone": 0.0 + }, + "P1 Flight Stick Vertical Axis": { + "Value": "RMouse Y", + "Mult": 1.0, + "Deadzone": 0.0 + }, + "P1 Light Gun Screen X": { + "Value": "RMouse X", + "Mult": 1.0, + "Deadzone": 0.0 + }, + "P1 Light Gun Screen Y": { + "Value": "RMouse Y", + "Mult": 1.0, + "Deadzone": 0.0 + }, + "P1 Trackball X": { + "Value": "RMouse X", + "Mult": 1.0, + "Deadzone": 0.0 + }, + "P1 Trackball Y": { + "Value": "RMouse Y", + "Mult": 1.0, + "Deadzone": 0.0 + } + }, "DOSBox Controller": { "Mouse Position X": { "Value": "WMouse X", diff --git a/Assets/dll/opera.wbx.zst b/Assets/dll/opera.wbx.zst new file mode 100644 index 0000000000..aabd3bba20 Binary files /dev/null and b/Assets/dll/opera.wbx.zst differ diff --git a/src/BizHawk.Client.Common/RomLoader.cs b/src/BizHawk.Client.Common/RomLoader.cs index 9fdf44ddf5..f0cb0f4201 100644 --- a/src/BizHawk.Client.Common/RomLoader.cs +++ b/src/BizHawk.Client.Common/RomLoader.cs @@ -261,6 +261,9 @@ namespace BizHawk.Client.Common case DiscType.MegaCD: game.System = VSystemID.Raw.GEN; break; + case DiscType.Panasonic3DO: + game.System = VSystemID.Raw.Panasonic3DO; + break; case DiscType.PCFX: game.System = VSystemID.Raw.PCFX; break; @@ -289,9 +292,6 @@ namespace BizHawk.Client.Common case DiscType.NeoGeoCD: NoCoreForSystem(VSystemID.Raw.NeoGeoCD); break; - case DiscType.Panasonic3DO: - NoCoreForSystem(VSystemID.Raw.Panasonic3DO); - break; case DiscType.Playdia: NoCoreForSystem(VSystemID.Raw.Playdia); break; diff --git a/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs b/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs index ef887a5961..2e4a499c51 100644 --- a/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs +++ b/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs @@ -17,6 +17,8 @@ namespace BizHawk.Client.Common private static readonly Dictionary Rates = new Dictionary { + ["Panasonic3DO"] = 60.0, // The emulator (Opera-Libretro) reports exact 60.0 for NTSC https://github.com/libretro/opera-libretro/blob/67a29e60a4d194b675c9272b21b61eaa022f3ba3/libopera/opera_region.c#L10 + ["Panasonic3DO_PAL"] = 50.0, // The emulator (Opera-Libretro) reports exact 50.0 for PAL https://github.com/libretro/opera-libretro/blob/67a29e60a4d194b675c9272b21b61eaa022f3ba3/libopera/opera_region.c#L17 ["NES"] = 60.098813897440515, // per https://forums.nesdev.org/viewtopic.php?p=3783#p3783 the nominal value is (19687500/11) / ((341*262 - 0.5) / 3) = 39375000/655171 ≈ 60.09881389744051553 (so our chosen number, which is approximately 60.09881389744051461, is very close) ["FDS"] = 60.098813897440515, // ditto ["NES_PAL"] = 50.00697796826829, // per https://forums.nesdev.org/viewtopic.php?p=3783#p3783 the nominal value is 1662607 / (341*312/3.2) = 3325214/66495 ≈ 50.0069779682682908 (so our chosen number, which is approximately 50.0069779682682877, is very close) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.VSystem.cs b/src/BizHawk.Client.EmuHawk/MainForm.VSystem.cs index ad29c600e6..2f3e4dbecf 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.VSystem.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.VSystem.cs @@ -36,6 +36,7 @@ using BizHawk.Emulation.Cores.Consoles.Nintendo.NDS; using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES; using BizHawk.Emulation.Cores.Consoles.Nintendo.VB; using BizHawk.Emulation.Cores.Consoles.O2Hawk; +using BizHawk.Emulation.Cores.Consoles.Panasonic3DO; using BizHawk.Emulation.Cores.Consoles.SNK; using BizHawk.Emulation.Cores.Consoles.Sega.PicoDrive; using BizHawk.Emulation.Cores.Consoles.Sega.Saturn; @@ -1370,6 +1371,9 @@ namespace BizHawk.Client.EmuHawk }; items.Add(octoshockSubmenu); + // Opera + items.Add(CreateCoreSubmenu(VSystemCategory.Consoles, CoreNames.Opera, CreateGenericCoreConfigItem(CoreNames.Opera))); + // PCEHawk items.Add(CreateCoreSubmenu(VSystemCategory.Consoles, CoreNames.PceHawk, CreateGenericCoreConfigItem(CoreNames.PceHawk))); diff --git a/src/BizHawk.Client.EmuHawk/Properties/Resources.cs b/src/BizHawk.Client.EmuHawk/Properties/Resources.cs index 8e7ec2a5b8..2fcaa18bee 100644 --- a/src/BizHawk.Client.EmuHawk/Properties/Resources.cs +++ b/src/BizHawk.Client.EmuHawk/Properties/Resources.cs @@ -36,6 +36,7 @@ namespace BizHawk.Client.EmuHawk.Properties internal static readonly Lazy SaturnController = new Lazy(() => ReadEmbeddedBitmap("ControllerImages.SaturnController")); internal static readonly Lazy SmsController = new Lazy(() => ReadEmbeddedBitmap("ControllerImages.SMSController")); internal static readonly Lazy SnesController = new Lazy(() => ReadEmbeddedBitmap("ControllerImages.SNES_Controller")); + internal static readonly Lazy ThreeDOController = new Lazy(() => ReadEmbeddedBitmap("ControllerImages.3DOController")); internal static readonly Lazy TI83Controller = new Lazy(() => ReadEmbeddedBitmap("ControllerImages.TI83_Controller")); internal static readonly Lazy VBoyController = new Lazy(() => ReadEmbeddedBitmap("ControllerImages.VBoyController")); internal static readonly Lazy WonderSwanColor = new Lazy(() => ReadEmbeddedBitmap("ControllerImages.WonderSwanColor")); diff --git a/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs b/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs index 959028b2d3..5ce917070f 100644 --- a/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/ControllerConfig.cs @@ -44,6 +44,7 @@ namespace BizHawk.Client.EmuHawk ControllerImages.Add("PC Engine Controller", Properties.Resources.PceController); ControllerImages.Add("Commodore 64 Controller", Properties.Resources.C64Joystick); ControllerImages.Add("TI83 Controller", Properties.Resources.TI83Controller); + ControllerImages.Add("3DO Controller", Properties.Resources.ThreeDOController); ControllerImages.Add("WonderSwan Controller", Properties.Resources.WonderSwanColor); ControllerImages.Add("Lynx Controller", Properties.Resources.Lynx); diff --git a/src/BizHawk.Client.EmuHawk/config/FirmwareConfig.cs b/src/BizHawk.Client.EmuHawk/config/FirmwareConfig.cs index faa27505c8..1e6954682b 100644 --- a/src/BizHawk.Client.EmuHawk/config/FirmwareConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/FirmwareConfig.cs @@ -61,6 +61,7 @@ namespace BizHawk.Client.EmuHawk // Redundant with SystemLookup? Not so fast. That data drives things. This is one step abstracted. Don't be such a smart guy. Keep this redundant list up to date. private static readonly Dictionary SystemGroupNames = new Dictionary { + ["3DO"] = "3DO / 3DO Arcade / 3DO M2", ["Amiga"] = "Amiga", ["NES"] = "NES", ["SNES"] = "SNES", diff --git a/src/BizHawk.Client.EmuHawk/images/ControllerImages/3DOController.png b/src/BizHawk.Client.EmuHawk/images/ControllerImages/3DOController.png new file mode 100644 index 0000000000..60d6609ff5 Binary files /dev/null and b/src/BizHawk.Client.EmuHawk/images/ControllerImages/3DOController.png differ diff --git a/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs b/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs index ebf90cd56d..d2a3a0ff19 100644 --- a/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs +++ b/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs @@ -47,6 +47,7 @@ namespace BizHawk.Client.EmuHawk VSystemID.Raw.N3DS, VSystemID.Raw.N64, VSystemID.Raw.NDS, + VSystemID.Raw.Panasonic3DO, VSystemID.Raw.PCFX, VSystemID.Raw.PSX, VSystemID.Raw.SAT, diff --git a/src/BizHawk.Emulation.Common/Base Implementations/Bk2MnemonicLookup.cs b/src/BizHawk.Emulation.Common/Base Implementations/Bk2MnemonicLookup.cs index 4a86cb9a42..7d75dd0b7d 100644 --- a/src/BizHawk.Emulation.Common/Base Implementations/Bk2MnemonicLookup.cs +++ b/src/BizHawk.Emulation.Common/Base Implementations/Bk2MnemonicLookup.cs @@ -182,6 +182,25 @@ namespace BizHawk.Emulation.Common private static readonly Dictionary> SystemOverrides = new Dictionary> { + [VSystemID.Raw.Panasonic3DO] = new() + { + ["LT"] = 'l', + ["RT"] = 'r', + ["P"] = 'P', + ["Trigger"] = 'T', + ["Reload"] = 'R', + ["Is Off-Screen"] = 'O', + ["Aux A"] = 'A', + ["Start P1"] = '1', + ["Start P2"] = '2', + ["Coin P1"] = 'C', + ["Coin P2"] = 'c', + ["Service"] = 'S', + ["Fourth Button"] = '4', + ["Next Disc"] = '>', + ["Prev Disc"] = '<', + }, + [VSystemID.Raw.NES] = new() { ["FDS Eject"] = 'E', @@ -964,6 +983,16 @@ namespace BizHawk.Emulation.Common private static readonly Dictionary> AxisSystemOverrides = new Dictionary> { + [VSystemID.Raw.Panasonic3DO] = new() + { + ["Flight Stick Horizontal Axis"] = "fsX", + ["Flight Stick Vertical Axis"] = "fsY", + ["Flight Stick Altitude Axis"] = "fsZ", + ["Light Gun Screen X"] = "lgX", + ["Light Gun Screen Y"] = "lgY", + ["Trackball X"] = "tX", + ["Trackball Y"] = "tY", + }, [VSystemID.Raw.A78] = new() { ["VPos"] = "X", diff --git a/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index 37f333bc28..4f65630804 100644 --- a/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -82,6 +82,60 @@ namespace BizHawk.Emulation.Common FirmwareAndOption("1E5B0B2441A4979B6966D942B20CC76C413B8C5E", 2048, "32X", "M", "32X_M_BIOS.BIN", "32x SH2 MASTER BIOS"); FirmwareAndOption("4103668C1BBD66C5E24558E73D4F3F92061A109A", 1024, "32X", "S", "32X_S_BIOS.BIN", "32x SH2 SLAVE BIOS"); + // 3DO + // Information obtained from https://3dodev.com/software/roms + + Firmware("3DO", "Panasonic_FZ1_U", "Panasonic FZ-1 (U)"); + Option("3DO", "Panasonic_FZ1_U", File("34BF189111295F74D7B7DFC1F304D98B8D36325A", 1048576, "panafz1.bin", "Panasonic FZ-1 (U)"), FirmwareOptionStatus.Ideal); + Option("3DO", "Panasonic_FZ1_U", File("DE3C55490733E6C69724D87E149B52ED955638ED", 1048576, "panafz1_dev_0.9.bin", "Panasonic FZ-1 (U) v0.9 dev build")); + Option("3DO", "Panasonic_FZ1_U", File("4CB4EE36E0F5BC0995D34992B4F241C420D49B2E", 1048576, "panafz1_dev.bin", "Panasonic FZ-1 (U) dev build")); + + Firmware("3DO", "Panasonic_FZ1_E", "Panasonic FZ-1 (E)"); + Option("3DO", "Panasonic_FZ1_E", File("1D0DB81E171EBC1D07CEFC8CE8AB082306186E56", 1048576, "panafz1e.bin", "Panasonic FZ-1 (E)"), FirmwareOptionStatus.Ideal); + Option("3DO", "Panasonic_FZ1_E", File("4696951E492E5526772A860EA2C0F35411A80927", 1048576, "panafz1e-unencrypted.bin", "Panasonic FZ-1 (E) (unencrypted)")); + + Firmware("3DO", "Panasonic_FZ1_J", "Panasonic FZ-1 (J)"); + Option("3DO", "Panasonic_FZ1_J", File("EC7EC62D60EC0459A14ED56EBC66761EF3C80EFC", 1048576, "panafz1j.bin", "Panasonic FZ-1 (J)"), FirmwareOptionStatus.Ideal); + Option("3DO", "Panasonic_FZ1_J", File("A417587AE3B0B8EF00C830920C21AF8BEE88E419", 1048576, "panafz1j - norsa.bin", "Panasonic FZ-1(J)(RSA check disabled)")); + + Firmware("3DO", "Panasonic_FZ10_U", "Panasonic FZ-10 (U)"); + Option("3DO", "Panasonic_FZ10_U", File("3C912300775D1AD730DC35757E279C274C0ACAAD", 1048576, "panafz10.bin", "Panasonic FZ-10 (U)"), FirmwareOptionStatus.Ideal); + Option("3DO", "Panasonic_FZ10_U", File("F05E642322C03694F06A809C0B90FC27AC73C002", 1048576, "panafz10-norsa.bin", "Panasonic FZ-10 (U) (RSA check disabled)")); + + Firmware("3DO", "Panasonic_FZ10_E", "Panasonic FZ-10 (E)"); + Option("3DO", "Panasonic_FZ10_E", File("A900371F0CDCDC03F79557F11D406FD71251A5FD", 1048576, "panafz10e-anvil.bin", "Panasonic FZ-10 (E) [ANVIL]"), FirmwareOptionStatus.Ideal); + Option("3DO", "Panasonic_FZ10_E", File("2765C7B4557CC838B32567D2428D088980295159", 1048576, "panafz10e-anvil-norsa.bin", "Panasonic FZ-10 (E) [ANVIL] (RSA check disabled)")); + + Firmware("3DO", "Panasonic_FZ10_J", "Panasonic FZ-10 (J)"); + Option("3DO", "Panasonic_FZ10_J", File("FE7F9C9C6A98910013BF13F2CF798DE9FEA52ACD", 1048576, "panafz10j.bin", "Panasonic FZ-10 (J)"), FirmwareOptionStatus.Ideal); + + Firmware("3DO", "Goldstar_GDO101P", "Goldstar GDO-101P"); + Option("3DO", "Goldstar_GDO101P", File("C4A2E5336F77FB5F743DE1EEA2CDA43675EE2DE7", 1048576, "goldstar.bin", "Goldstar GDO-101P"), FirmwareOptionStatus.Ideal); + + Firmware("3DO", "Goldstar_FC1", "Goldstar FC-1"); + Option("3DO", "Goldstar_FC1", File("8EF7503C948314D242DA47B7FDC272F68DAC2AEE", 1048576, "goldstar_fc1_enc.bin", "Goldstar FC-1 (encrypted)"), FirmwareOptionStatus.Ideal); + + Firmware("3DO", "Sanyo_IMP21J_Try", "Sanyo IMP-21J Try"); + Option("3DO", "Sanyo_IMP21J_Try", File("B01C53DA256DDE43FFEC4AD3FC3ADFA8D635E943", 1048576, "sanyotry.bin", "Sanyo IMP-21J Try"), FirmwareOptionStatus.Ideal); + + Firmware("3DO", "Sanyo_HC21", "Sanyo HC-21"); + Option("3DO", "Sanyo_HC21", File("C389AF32BCADF0D86826927DC3D20B7072F90069", 1048576, "sanyo_hc21_b3_unenc.bin", "Sanyo HC-21 B3 (unencrypted)"), FirmwareOptionStatus.Ideal); + Option("3DO", "Sanyo_HC21", File("29C40515DC1174FF13975BAA59EB532083E4A3D3", 1048576, "sanyo_hc21_alpha.bin", "Sanyo HC-21 (alpha 3/21/94)")); + + Firmware("3DO", "Shootout_At_Old_Tucson", "(3DO Arcade) Shootout At Old Tucson"); + Option("3DO", "Shootout_At_Old_Tucson", File("520D3D1B5897800AF47F92EFD2444A26B7A7DEAD", 524288, "3do_arcade_saot.bin", "Shootout At Old Tucson"), FirmwareOptionStatus.Ideal); + + Firmware("3DO", "3DO_NTSC_1fc2", "3DO-NTSC-1.0fc2"); + Option("3DO", "3DO_NTSC_1fc2", File("BD325C869E1DDE8A3872FC21565E0646A3D5B525", 1048576, "3do_devkit_1.0fc2.bin", "3DO-NTSC-1.0fc2 encrypted development kit"), FirmwareOptionStatus.Ideal); + + Firmware("3DO", "Kanji_ROM_Panasonic_FZ1", "Kanji ROM for Panasonic FZ-1"); + Option("3DO", "Kanji_ROM_Panasonic_FZ1", File("ACD39A8FEE1B9D2950D5AB447846C11FB31AF63E", 933636, "panafz1-kanji.bin", "Kanji ROM for Panasonic FZ-1 (J)"), FirmwareOptionStatus.Ideal); + Option("3DO", "Kanji_ROM_Panasonic_FZ1", File("884515605EE243577AB20767EF8C1A7368E4E407", 1048576, "panafz1j-kanji.bin", "Kanji ROM for Panasonic FZ-1 (J) / Panasonic FZ-10 (J)")); + + Firmware("3DO", "Kanji_ROM_Panasonic_FZ10", "Kanji ROM for Panasonic FZ-10"); + Option("3DO", "Kanji_ROM_Panasonic_FZ10", File("2E857B957803D0331FD229328DF01F3FFAB69EEE", 1048576, "panafz10ja-anvil-kanji.bin", "Kanji ROM for: Panasonic FZ-10 (J) [ANVIL]"), FirmwareOptionStatus.Ideal); + Option("3DO", "Kanji_ROM_Panasonic_FZ10", File("884515605EE243577AB20767EF8C1A7368E4E407", 1048576, "panafz1j-kanji.bin", "Kanji ROM for Panasonic FZ-1 (J) / Panasonic FZ-10 (J)")); + // 3DS // bleh, undefined hash AND size... FirmwareAndOption(SHA1Checksum.Dummy, 0, "3DS", "aes_keys", "aes_keys.txt", "AES Keys"); diff --git a/src/BizHawk.Emulation.Common/Extensions.cs b/src/BizHawk.Emulation.Common/Extensions.cs index c89f78afa7..06554349f2 100644 --- a/src/BizHawk.Emulation.Common/Extensions.cs +++ b/src/BizHawk.Emulation.Common/Extensions.cs @@ -48,6 +48,7 @@ namespace BizHawk.Emulation.Common [VSystemID.Raw.NGP] = "Neo-Geo Pocket", // NULL [VSystemID.Raw.O2] = "Odyssey2", + [VSystemID.Raw.Panasonic3DO] = "3DO", [VSystemID.Raw.PCE] = "TurboGrafx-16", [VSystemID.Raw.PCECD] = "TurboGrafx - 16(CD)", [VSystemID.Raw.PCFX] = "PCFX", diff --git a/src/BizHawk.Emulation.Common/VSystemID.cs b/src/BizHawk.Emulation.Common/VSystemID.cs index 218c626d88..88fda12860 100644 --- a/src/BizHawk.Emulation.Common/VSystemID.cs +++ b/src/BizHawk.Emulation.Common/VSystemID.cs @@ -48,7 +48,7 @@ namespace BizHawk.Emulation.Common public const string NGP = "NGP"; public const string NULL = "NULL"; public const string O2 = "O2"; - public const string Panasonic3DO = "Panasonic3DO"; + public const string Panasonic3DO = "3DO"; public const string PCE = "PCE"; public const string PCECD = "PCECD"; public const string PCFX = "PCFX"; diff --git a/src/BizHawk.Emulation.Cores/Consoles/3DO/LibOpera.cs b/src/BizHawk.Emulation.Cores/Consoles/3DO/LibOpera.cs new file mode 100644 index 0000000000..ac99a60b33 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/3DO/LibOpera.cs @@ -0,0 +1,153 @@ +using System.Runtime.InteropServices; +using BizHawk.BizInvoke; +using BizHawk.Emulation.Cores.Waterbox; + +namespace BizHawk.Emulation.Cores.Consoles.Panasonic3DO +{ + public abstract class LibOpera : LibWaterboxCore + { + // NTSC Specifications + public const int NTSC_WIDTH = 320; + public const int NTSC_HEIGHT = 240; + public const int NTSC_VIDEO_NUMERATOR = 60; + public const int NTSC_VIDEO_DENOMINATOR = 1; + + // PAL1 Specifications + public const int PAL1_WIDTH = 320; + public const int PAL1_HEIGHT = 288; + public const int PAL1_VIDEO_NUMERATOR = 50; + public const int PAL1_VIDEO_DENOMINATOR = 1; + + // PAL2 Specifications + public const int PAL2_WIDTH = 384; + public const int PAL2_HEIGHT = 288; + public const int PAL2_VIDEO_NUMERATOR = 50; + public const int PAL2_VIDEO_DENOMINATOR = 1; + + [UnmanagedFunctionPointer(CC)] + public delegate void CDReadCallback(int lba, IntPtr dst); + + [UnmanagedFunctionPointer(CC)] + public delegate int CDSectorCountCallback(); + + [BizImport(CC)] + public abstract void SetCdCallbacks(CDReadCallback cdrc, CDSectorCountCallback cdscc); + + [BizImport(CC, Compatibility = true)] + public abstract bool Init(string gameFile, string biosFile, string fontFile, int port1Type, int port2Type, int videoStandard); + + [BizImport(CC, Compatibility = true)] + public abstract bool sram_changed(); + + [BizImport(CC, Compatibility = true)] + public abstract int get_sram_size(); + + [BizImport(CC, Compatibility = true)] + public abstract void get_sram(IntPtr sramBuffer); + + [BizImport(CC, Compatibility = true)] + public abstract void set_sram(IntPtr sramBuffer); + + [StructLayout(LayoutKind.Sequential)] + public struct GamepadInputs + { + public int up; + public int down; + public int left; + public int right; + public int buttonX; + public int buttonP; + public int buttonA; + public int buttonB; + public int buttonC; + public int buttonL; + public int buttonR; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MouseInputs + { + public int dX; + public int dY; + public int leftButton; + public int middleButton; + public int rightButton; + public int fourthButton; + } + + [StructLayout(LayoutKind.Sequential)] + public struct FlightStickInputs + { + public int up; + public int down; + public int left; + public int right; + public int fire; + public int buttonA; + public int buttonB; + public int buttonC; + public int buttonX; + public int buttonP; + public int leftTrigger; + public int rightTrigger; + public int horizontalAxis; + public int verticalAxis; + public int altitudeAxis; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LightGunInputs + { + public int trigger; + public int select; + public int reload; + public int isOffScreen; + public int screenX; + public int screenY; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ArcadeLightGunInputs + { + public int trigger; + public int select; + public int start; + public int reload; + public int auxA; + public int isOffScreen; + public int screenX; + public int screenY; + } + + [StructLayout(LayoutKind.Sequential)] + public struct OrbatakTrackballInputs + { + public int dX; + public int dY; + public int startP1; + public int startP2; + public int coinP1; + public int coinP2; + public int service; + } + + [StructLayout(LayoutKind.Sequential)] + public struct GameInput + { + public GamepadInputs gamepad; + public MouseInputs mouse; + public FlightStickInputs flightStick; + public LightGunInputs lightGun; + public ArcadeLightGunInputs arcadeLightGun; + public OrbatakTrackballInputs orbatakTrackball; + } + + [StructLayout(LayoutKind.Sequential)] + public new class FrameInfo : LibWaterboxCore.FrameInfo + { + public GameInput port1; + public GameInput port2; + public int isReset = 0; + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.Controllers.cs b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.Controllers.cs new file mode 100644 index 0000000000..8408538345 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.Controllers.cs @@ -0,0 +1,202 @@ +using BizHawk.Common.CollectionExtensions; +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Panasonic3DO +{ + public partial class Opera + { + private static ControllerDefinition CreateControllerDefinition(SyncSettings settings, bool isMultiDisc) + { + var controller = new ControllerDefinition("3DO Controller"); + setPortControllers(1, settings.Controller1Type, controller); + setPortControllers(2, settings.Controller2Type, controller); + + // If this is multi-disc, add a cd swap option + if (isMultiDisc) + { + controller.BoolButtons.Add("Next Disc"); + controller.BoolButtons.Add("Prev Disc"); + } + + // Adding Reset button + controller.BoolButtons.Add("Reset"); + + return controller.MakeImmutable(); + } + + public const int MOUSE_MIN_POS_X = -64; + public const int MOUSE_MAX_POS_X = 64; + public const int MOUSE_MIN_POS_Y = -64; + public const int MOUSE_MAX_POS_Y = 64; + + public const int POINTER_MIN_POS = -32768; + public const int POINTER_MAX_POS = 32767; + private static void setPortControllers(int port, ControllerType type, ControllerDefinition controller) + { + switch (type) + { + case ControllerType.Gamepad: + foreach (var button in JoystickButtonCollection) controller.BoolButtons.Add($"P{port} {button}"); + break; + case ControllerType.Mouse: + controller.BoolButtons.AddRange([ + $"P{port} {Inputs.MouseLeftButton}", + $"P{port} {Inputs.MouseMiddleButton}", + $"P{port} {Inputs.MouseRightButton}", + $"P{port} {Inputs.MouseFourthButton}", + ]); + controller.AddAxis($"P{port} {Inputs.MouseX}", MOUSE_MIN_POS_X.RangeTo(MOUSE_MAX_POS_X), (MOUSE_MIN_POS_X + MOUSE_MAX_POS_X) / 2) + .AddAxis($"P{port} {Inputs.MouseY}", MOUSE_MIN_POS_Y.RangeTo(MOUSE_MAX_POS_Y), (MOUSE_MIN_POS_Y + MOUSE_MAX_POS_Y) / 2); + break; + case ControllerType.FlightStick: + foreach (var button in FlightStickButtonCollection) controller.BoolButtons.Add($"P{port} {button}"); + controller.AddAxis($"P{port} {Inputs.FlighStickHorizontalAxis}", MOUSE_MIN_POS_X.RangeTo(MOUSE_MAX_POS_X), (MOUSE_MIN_POS_X + MOUSE_MAX_POS_X) / 2) + .AddAxis($"P{port} {Inputs.FlighStickVerticalAxis}", MOUSE_MIN_POS_Y.RangeTo(MOUSE_MAX_POS_Y), (MOUSE_MIN_POS_Y + MOUSE_MAX_POS_Y) / 2) + .AddAxis($"P{port} {Inputs.FlighStickAltitudeAxis}", MOUSE_MIN_POS_Y.RangeTo(MOUSE_MAX_POS_Y), (MOUSE_MIN_POS_Y + MOUSE_MAX_POS_Y) / 2); + break; + case ControllerType.LightGun: + foreach (var button in LightGunButtonCollection) controller.BoolButtons.Add($"P{port} {button}"); + controller.AddAxis($"P{port} {Inputs.LightGunScreenX}", (POINTER_MIN_POS).RangeTo(POINTER_MAX_POS), 0) + .AddAxis($"P{port} {Inputs.LightGunScreenY}", (POINTER_MIN_POS).RangeTo(POINTER_MAX_POS), 0); + break; + case ControllerType.ArcadeLightGun: + foreach (var button in ArcadeLightGunButtonCollection) controller.BoolButtons.Add($"P{port} {button}"); + controller.AddAxis($"P{port} {Inputs.LightGunScreenX}", (POINTER_MIN_POS).RangeTo(POINTER_MAX_POS), 0) + .AddAxis($"P{port} {Inputs.LightGunScreenY}", (POINTER_MIN_POS).RangeTo(POINTER_MAX_POS), 0); + break; + case ControllerType.OrbatakTrackball: + foreach (var button in OrbatakTrackballCollection) controller.BoolButtons.Add($"P{port} {button}"); + controller.AddAxis($"P{port} {Inputs.TrackballPosX}", MOUSE_MIN_POS_X.RangeTo(MOUSE_MAX_POS_X), (MOUSE_MIN_POS_X + MOUSE_MAX_POS_X) / 2) + .AddAxis($"P{port} {Inputs.TrackballPosY}", MOUSE_MIN_POS_Y.RangeTo(MOUSE_MAX_POS_Y), (MOUSE_MIN_POS_Y + MOUSE_MAX_POS_Y) / 2); + break; + } + } + + private static string[] JoystickButtonCollection = [ + JoystickButtons.Up, + JoystickButtons.Down, + JoystickButtons.Left, + JoystickButtons.Right, + JoystickButtons.ButtonX, + JoystickButtons.ButtonP, + JoystickButtons.ButtonA, + JoystickButtons.ButtonB, + JoystickButtons.ButtonC, + JoystickButtons.ButtonL, + JoystickButtons.ButtonR, + ]; + + private static string[] FlightStickButtonCollection = [ + FlightStickButtons.Up, + FlightStickButtons.Down, + FlightStickButtons.Left, + FlightStickButtons.Right, + FlightStickButtons.Fire, + FlightStickButtons.ButtonA, + FlightStickButtons.ButtonB, + FlightStickButtons.ButtonX, + FlightStickButtons.ButtonP, + FlightStickButtons.LeftTrigger, + FlightStickButtons.RightTrigger, + ]; + + private static string[] LightGunButtonCollection = [ + LightGunButtons.Trigger, + LightGunButtons.Select, + LightGunButtons.Reload, + LightGunButtons.IsOffScreen, + ]; + + private static string[] ArcadeLightGunButtonCollection = [ + ArcadeLightGunButtons.Trigger, + ArcadeLightGunButtons.Select, + ArcadeLightGunButtons.Start, + ArcadeLightGunButtons.Reload, + ArcadeLightGunButtons.AuxA, + ArcadeLightGunButtons.IsOffScreen, + ]; + + private static string[] OrbatakTrackballCollection = [ + OrbatakTrackballButtons.StartP1, + OrbatakTrackballButtons.StartP2, + OrbatakTrackballButtons.CoinP1, + OrbatakTrackballButtons.CoinP2, + OrbatakTrackballButtons.Service, + ]; + + private static class JoystickButtons + { + public const string Up = "Up"; + public const string Down = "Down"; + public const string Left = "Left"; + public const string Right = "Right"; + public const string ButtonX = "X"; + public const string ButtonP = "P"; + public const string ButtonA = "A"; + public const string ButtonB = "B"; + public const string ButtonC = "C"; + public const string ButtonL = "L"; + public const string ButtonR = "R"; + } + private static class FlightStickButtons + { + public const string Up = "Up"; + public const string Down = "Down"; + public const string Left = "Left"; + public const string Right = "Right"; + public const string Fire = "Fire"; + public const string ButtonA = "A"; + public const string ButtonB = "B"; + public const string ButtonC = "C"; + public const string LeftTrigger = "LT"; + public const string RightTrigger = "RT"; + public const string ButtonP = "P"; + public const string ButtonX = "X"; + } + + private static class LightGunButtons + { + public const string Trigger = "Trigger"; + public const string Select = "Select"; + public const string Reload = "Reload"; + public const string IsOffScreen = "Is Off-Screen"; + } + private static class ArcadeLightGunButtons + { + public const string Trigger = "Trigger"; + public const string Select = "Select"; + public const string Start = "Start"; + public const string Reload = "Reload"; + public const string AuxA = "Aux A"; + public const string IsOffScreen = "Is Off-Screen"; + } + + private static class OrbatakTrackballButtons + { + public const string StartP1 = "Start P1"; + public const string StartP2 = "Start P2"; + public const string CoinP1 = "Coin P1"; + public const string CoinP2 = "Coin P2"; + public const string Service = "Service"; + } + + private static class Inputs + { + public const string Joystick = "Joystick"; + public const string MouseLeftButton = "Left Button"; + public const string MouseRightButton = "Right Button"; + public const string MouseMiddleButton = "Middle Button"; + public const string MouseFourthButton = "Fourth Button"; + public const string MouseX = "Mouse X"; + public const string MouseY = "Mouse Y"; + public const string FlighStickHorizontalAxis = "Flight Stick Horizontal Axis"; + public const string FlighStickVerticalAxis = "Flight Stick Vertical Axis"; + public const string FlighStickAltitudeAxis = "Flight Stick Altitude Axis"; + public const string LightGunScreenX = "Light Gun Screen X"; + public const string LightGunScreenY = "Light Gun Screen Y"; + public const string TrackballPosX = "Trackball X"; + public const string TrackballPosY = "Trackball Y"; + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.IDriveLight.cs b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.IDriveLight.cs new file mode 100644 index 0000000000..b7949e7b54 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.IDriveLight.cs @@ -0,0 +1,11 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Panasonic3DO +{ + public partial class Opera : IDriveLight + { + public bool DriveLightEnabled { get; private set; } + public bool DriveLightOn { get; private set; } + public string DriveLightIconDescription => "Drive Activity"; + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.IRegionable.cs b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.IRegionable.cs new file mode 100644 index 0000000000..ad02303923 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.IRegionable.cs @@ -0,0 +1,11 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Panasonic3DO +{ + public partial class Opera : IRegionable + { + public DisplayType Region => _syncSettings.VideoStandard is VideoStandard.NTSC + ? DisplayType.NTSC + : DisplayType.PAL; + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.ISettable.cs new file mode 100644 index 0000000000..d08e9bd6eb --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.ISettable.cs @@ -0,0 +1,135 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Panasonic3DO +{ + public partial class Opera : ISettable + { + // System type determines the version of the console to run + // The selection of the proper BIOS derives from this decision + public enum SystemType + { + [Display(Name = "Panasonic FZ-1 (U)")] + Panasonic_FZ1_U, + [Display(Name = "Panasonic FZ-1 (E)")] + Panasonic_FZ1_E, + [Display(Name = "Panasonic FZ-1 (J)")] + Panasonic_FZ1_J, + [Display(Name = "Panasonic FZ-10 (U)")] + Panasonic_FZ10_U, + [Display(Name = "Panasonic FZ-10 (E)")] + Panasonic_FZ10_E, + [Display(Name = "Panasonic FZ-10 (J)")] + Panasonic_FZ10_J, + [Display(Name = "Goldstar GDO-101P")] + Goldstar_GDO101P, + [Display(Name = "Goldstar FC-1")] + Goldstar_FC1, + [Display(Name = "Sanyo IMP-21J Try")] + Sanyo_IMP21J_Try, + [Display(Name = "Sanyo HC-21")] + Sanyo_HC21, + [Display(Name = "(3DO Arcade) Shootout At Old Tucson")] + Shootout_At_Old_Tucson, + [Display(Name = "3DO-NTSC-1.0fc2")] + _3DO_NTSC_1fc2, + } + + public enum FontROM + { + [Display(Name = "None")] + None, + [Display(Name = "Kanji ROM for Panasonic FZ-1")] + Kanji_ROM_Panasonic_FZ1, + [Display(Name = "Kanji ROM for Panasonic FZ-10")] + Kanji_ROM_Panasonic_FZ10, + } + + public enum VideoStandard + { + [Display(Name = "NTSC")] + NTSC = 0, + [Display(Name = "PAL1")] + PAL1 = 1, + [Display(Name = "PAL2")] + PAL2 = 2, + } + + public enum ControllerType + { + [Display(Name = "None")] + None = 0, + [Display(Name = "Joypad")] + Gamepad = 1, + [Display(Name = "Mouse")] + Mouse = 2, + [Display(Name = "Flight Stick")] + FlightStick = 257, + [Display(Name = "Light Gun")] + LightGun = 4, + [Display(Name = "Arcade Light Gun")] + ArcadeLightGun = 260, + [Display(Name = "Orbatak Trackball")] + OrbatakTrackball = 513, + } + + public object GetSettings() => null; + public PutSettingsDirtyBits PutSettings(object o) => PutSettingsDirtyBits.None; + + private SyncSettings _syncSettings; + public SyncSettings GetSyncSettings() + => _syncSettings.Clone(); + + public PutSettingsDirtyBits PutSyncSettings(SyncSettings o) + { + var ret = SyncSettings.NeedsReboot(_syncSettings, o); + _syncSettings = o; + return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None; + } + + [CoreSettings] + public class SyncSettings + { + [DisplayName("System Type")] + [Description("Sets the version of the console to emulate. This choice determines the corresponding BIOS ROM to use.")] + [DefaultValue(SystemType.Panasonic_FZ1_U)] + [TypeConverter(typeof(SystemType))] + public SystemType SystemType { get; set; } + + [DisplayName("Font ROM")] + [Description("Determines whether (if any) addition ROM to load for regional font support.")] + [DefaultValue(FontROM.None)] + [TypeConverter(typeof(FontROM))] + public FontROM FontROM { get; set; } + + [DisplayName("Video Standard")] + [Description("Determines the resolution and video timing. It should be selected according to the game and console's region.")] + [DefaultValue(VideoStandard.NTSC)] + [TypeConverter(typeof(VideoStandard))] + public VideoStandard VideoStandard { get; set; } + + [DisplayName("Controller 1 Type")] + [Description("Sets the type of controller connected to the console's port 1.")] + [DefaultValue(ControllerType.Gamepad)] + [TypeConverter(typeof(ControllerType))] + public ControllerType Controller1Type { get; set; } + + [DisplayName("Controller 2 Type")] + [Description("Sets the type of controller connected to the console's port 2.")] + [DefaultValue(ControllerType.None)] + [TypeConverter(typeof(ControllerType))] + public ControllerType Controller2Type { get; set; } + + public SyncSettings() + => SettingsUtil.SetDefaultValues(this); + + public SyncSettings Clone() + => (SyncSettings) MemberwiseClone(); + + public static bool NeedsReboot(SyncSettings x, SyncSettings y) + => !DeepEquality.DeepEquals(x, y); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.cs b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.cs new file mode 100644 index 0000000000..08af67d228 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.cs @@ -0,0 +1,320 @@ +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Waterbox; +using BizHawk.Emulation.DiscSystem; + +namespace BizHawk.Emulation.Cores.Consoles.Panasonic3DO +{ + [PortedCore( + name: CoreNames.Opera, + author: "Libretro Team", + portedVersion: "2025.03.08 (67a29e6)", + portedUrl: "https://github.com/libretro/opera-libretro", + isReleased: false)] + public partial class Opera : WaterboxCore + { + private static readonly Configuration ConfigNTSC = new Configuration + { + SystemId = VSystemID.Raw.Panasonic3DO, + MaxSamples = 8 * 1024, + DefaultWidth = LibOpera.NTSC_WIDTH, + DefaultHeight = LibOpera.NTSC_HEIGHT, + MaxWidth = LibOpera.PAL2_WIDTH, + MaxHeight = LibOpera.PAL2_HEIGHT, + DefaultFpsNumerator = LibOpera.NTSC_VIDEO_NUMERATOR, + DefaultFpsDenominator = LibOpera.NTSC_VIDEO_DENOMINATOR, + }; + + private static readonly Configuration ConfigPAL1 = new Configuration + { + SystemId = VSystemID.Raw.Panasonic3DO, + MaxSamples = 8 * 1024, + DefaultWidth = LibOpera.PAL1_WIDTH, + DefaultHeight = LibOpera.PAL1_HEIGHT, + MaxWidth = LibOpera.PAL1_WIDTH, + MaxHeight = LibOpera.PAL1_HEIGHT, + DefaultFpsNumerator = LibOpera.PAL1_VIDEO_NUMERATOR, + DefaultFpsDenominator = LibOpera.PAL1_VIDEO_DENOMINATOR, + }; + + private static readonly Configuration ConfigPAL2 = new Configuration + { + SystemId = VSystemID.Raw.Panasonic3DO, + MaxSamples = 8 * 1024, + DefaultWidth = LibOpera.PAL2_WIDTH, + DefaultHeight = LibOpera.PAL2_HEIGHT, + MaxWidth = LibOpera.PAL2_WIDTH, + MaxHeight = LibOpera.PAL2_HEIGHT, + DefaultFpsNumerator = LibOpera.PAL2_VIDEO_NUMERATOR, + DefaultFpsDenominator = LibOpera.PAL2_VIDEO_DENOMINATOR, + }; + + private readonly List _discAssets; + + public override int VirtualWidth => BufferHeight * 4 / 3; + private LibOpera _libOpera; + + // Image selection / swapping variables + + [CoreConstructor(VSystemID.Raw.Panasonic3DO)] + public Opera(CoreLoadParameters lp) + : base( + lp.Comm, + lp.SyncSettings?.VideoStandard switch + { + null or VideoStandard.NTSC => ConfigNTSC, + VideoStandard.PAL1 => ConfigPAL1, + VideoStandard.PAL2 => ConfigPAL2, + _ => throw new InvalidOperationException($"unexpected value for sync setting {nameof(SyncSettings.VideoStandard)}"), + }) + { + DriveLightEnabled = true; + _discAssets = lp.Discs; + + // If no discs loaded, then there's nothing to emulate + if (_discAssets.Count == 0) throw new InvalidOperationException("No CDs provided for emulation"); + _isMultidisc = _discAssets.Count > 1; + + _CDReadCallback = CDRead; + _CDSectorCountCallback = CDSectorCount; + _discIndex = 0; + foreach (var disc in _discAssets) _cdReaders.Add(new(disc.DiscData)); + + Console.WriteLine($"[CD] Sector count: {_discAssets[0].DiscData.Session1.LeadoutLBA}"); + _syncSettings = lp.SyncSettings ?? new(); + ControllerDefinition = CreateControllerDefinition(_syncSettings, _isMultidisc); + + _libOpera = PreInit( + new WaterboxOptions + { + Filename = "opera.wbx", + SbrkHeapSizeKB = 256 * 1024, + SealedHeapSizeKB = 1024, + InvisibleHeapSizeKB = 1024, + PlainHeapSizeKB = 1024, + MmapHeapSizeKB = 256 * 1024, + SkipCoreConsistencyCheck = lp.Comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck), + SkipMemoryConsistencyCheck = lp.Comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck), + }, + [ _CDReadCallback, _CDSectorCountCallback ]); + + // Setting CD callbacks + _libOpera.SetCdCallbacks(_CDReadCallback, _CDSectorCountCallback); + + // Adding BIOS file + string biosType = _syncSettings.SystemType switch + { + SystemType.Panasonic_FZ1_U => "Panasonic_FZ1_U", + SystemType.Panasonic_FZ1_E => "Panasonic_FZ1_E", + SystemType.Panasonic_FZ1_J => "Panasonic_FZ1_J", + SystemType.Panasonic_FZ10_U => "Panasonic_FZ10_U", + SystemType.Panasonic_FZ10_E => "Panasonic_FZ10_E", + SystemType.Panasonic_FZ10_J => "Panasonic_FZ10_J", + SystemType.Goldstar_GDO101P => "Goldstar_GDO101P", + SystemType.Goldstar_FC1 => "Goldstar_FC1", + SystemType.Sanyo_IMP21J_Try => "Sanyo_IMP21J_Try", + SystemType.Sanyo_HC21 => "Sanyo_HC21", + SystemType.Shootout_At_Old_Tucson => "Shootout_At_Old_Tucson", + SystemType._3DO_NTSC_1fc2 => "3DO_NTSC_1fc2", + _ => "None", + }; + + var (biosData, biosInfo) = CoreComm.CoreFileProvider.GetFirmwareWithGameInfoOrThrow(new(VSystemID.Raw.Panasonic3DO, biosType), "BIOS ROM files are required!"); + string biosFileName = biosInfo.Name + ".bin"; + _exe.AddReadonlyFile(biosData, biosFileName); + + // Adding Font ROM file, if required + string fontROMType = _syncSettings.FontROM switch + { + FontROM.Kanji_ROM_Panasonic_FZ1 => "Kanji_ROM_Panasonic_FZ1", + FontROM.Kanji_ROM_Panasonic_FZ10 => "Kanji_ROM_Panasonic_FZ10", + _ => "None", + }; + + string fontROMFileName = "None"; + if (fontROMType != "None") + { + var (fontROMData, fontROMInfo) = CoreComm.CoreFileProvider.GetFirmwareWithGameInfoOrThrow(new(VSystemID.Raw.Panasonic3DO, fontROMType), "Font ROM files are required!"); + _exe.AddReadonlyFile(fontROMData, fontROMInfo.Name); + fontROMFileName = fontROMInfo.Name; + } + + ////////////// Initializing Core + string cdName = _discAssets[0].DiscName; + Console.WriteLine($"Launching Core with Game: '{cdName}', BIOS ROM: '{biosFileName}', Font ROM: '{fontROMFileName}'"); + if (!_libOpera.Init( + gameFile: cdName, + biosFile: biosFileName, + fontFile: fontROMFileName, + port1Type: (int) _syncSettings.Controller1Type, + port2Type: (int) _syncSettings.Controller2Type, + videoStandard: (int) _syncSettings.VideoStandard)) + { + throw new InvalidOperationException("Core rejected the rom!"); + } + + PostInit(); + } + + // CD Handling logic + private bool _isMultidisc; + private bool _discInserted = true; + private readonly LibOpera.CDReadCallback _CDReadCallback; + private readonly LibOpera.CDSectorCountCallback _CDSectorCountCallback; + private int _discIndex; + private readonly List _cdReaders = new List(); + private static int CD_SECTOR_SIZE = 2048; + private readonly byte[] _sectorBuffer = new byte[CD_SECTOR_SIZE]; + + private void SelectNextDisc() + { + _discIndex++; + if (_discIndex == _discAssets.Count) _discIndex = 0; + CoreComm.Notify($"Selected CDROM {_discIndex}: {_discAssets[_discIndex].DiscName}", null); + } + + private void SelectPrevDisc() + { + _discIndex--; + if (_discIndex < 0) _discIndex = _discAssets.Count - 1; + CoreComm.Notify($"Selected CDROM {_discIndex}: {_discAssets[_discIndex].DiscName}", null); + } + + private void CDRead(int lba, IntPtr dest) + { + if (_discIndex < _discAssets.Count) + { + _cdReaders[_discIndex].ReadLBA_2048(lba, _sectorBuffer, 0); + Marshal.Copy(_sectorBuffer, 0, dest, CD_SECTOR_SIZE); + } + DriveLightOn = true; + } + + private int CDSectorCount() + { + if (_discIndex < _discAssets.Count) return _discAssets[_discIndex].DiscData.Session1.LeadoutLBA; + return -1; + } + + protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound) + { + var fi = new LibOpera.FrameInfo(); + + // Disc management + if (_isMultidisc) + { + if (controller.IsPressed("Next Disc")) SelectNextDisc(); + if (controller.IsPressed("Prev Disc")) SelectPrevDisc(); + } + + DriveLightOn = false; + fi.port1 = ProcessController(1, _syncSettings.Controller1Type, controller); + fi.port2 = ProcessController(2, _syncSettings.Controller2Type, controller); + + // Game reset + if (controller.IsPressed("Reset")) fi.isReset = 1; + + return fi; + } + + private static LibOpera.GameInput ProcessController(int port, ControllerType type, IController controller) + { + LibOpera.GameInput gameInput = new LibOpera.GameInput(); + + switch (type) + { + case ControllerType.Gamepad: + gameInput.gamepad.up = controller.IsPressed($"P{port} {JoystickButtons.Up}") ? 1 : 0; + gameInput.gamepad.down = controller.IsPressed($"P{port} {JoystickButtons.Down}") ? 1 : 0; + gameInput.gamepad.left = controller.IsPressed($"P{port} {JoystickButtons.Left}") ? 1 : 0; + gameInput.gamepad.right = controller.IsPressed($"P{port} {JoystickButtons.Right}") ? 1 : 0; + gameInput.gamepad.buttonX = controller.IsPressed($"P{port} {JoystickButtons.ButtonX}") ? 1 : 0; + gameInput.gamepad.buttonP = controller.IsPressed($"P{port} {JoystickButtons.ButtonP}") ? 1 : 0; + gameInput.gamepad.buttonA = controller.IsPressed($"P{port} {JoystickButtons.ButtonA}") ? 1 : 0; + gameInput.gamepad.buttonB = controller.IsPressed($"P{port} {JoystickButtons.ButtonB}") ? 1 : 0; + gameInput.gamepad.buttonC = controller.IsPressed($"P{port} {JoystickButtons.ButtonC}") ? 1 : 0; + gameInput.gamepad.buttonL = controller.IsPressed($"P{port} {JoystickButtons.ButtonL}") ? 1 : 0; + gameInput.gamepad.buttonR = controller.IsPressed($"P{port} {JoystickButtons.ButtonR}") ? 1 : 0; + break; + + case ControllerType.Mouse: + gameInput.mouse.dX = controller.AxisValue($"P{port} {Inputs.MouseX}"); + gameInput.mouse.dY = controller.AxisValue($"P{port} {Inputs.MouseY}"); + gameInput.mouse.leftButton = controller.IsPressed($"P{port} {Inputs.MouseLeftButton}") ? 1 : 0; + gameInput.mouse.middleButton = controller.IsPressed($"P{port} {Inputs.MouseMiddleButton}") ? 1 : 0; + gameInput.mouse.rightButton = controller.IsPressed($"P{port} {Inputs.MouseRightButton}") ? 1 : 0; + gameInput.mouse.fourthButton = controller.IsPressed($"P{port} {Inputs.MouseFourthButton}") ? 1 : 0; + break; + + case ControllerType.FlightStick: + gameInput.flightStick.up = controller.IsPressed($"P{port} {FlightStickButtons.Up}") ? 1 : 0; + gameInput.flightStick.down = controller.IsPressed($"P{port} {FlightStickButtons.Down}") ? 1 : 0; + gameInput.flightStick.left = controller.IsPressed($"P{port} {FlightStickButtons.Left}") ? 1 : 0; + gameInput.flightStick.right = controller.IsPressed($"P{port} {FlightStickButtons.Right}") ? 1 : 0; + gameInput.flightStick.fire = controller.IsPressed($"P{port} {FlightStickButtons.Fire}") ? 1 : 0; + gameInput.flightStick.buttonA = controller.IsPressed($"P{port} {FlightStickButtons.ButtonA}") ? 1 : 0; + gameInput.flightStick.buttonB = controller.IsPressed($"P{port} {FlightStickButtons.ButtonB}") ? 1 : 0; + gameInput.flightStick.buttonC = controller.IsPressed($"P{port} {FlightStickButtons.ButtonC}") ? 1 : 0; + gameInput.flightStick.buttonX = controller.IsPressed($"P{port} {FlightStickButtons.ButtonX}") ? 1 : 0; + gameInput.flightStick.buttonP = controller.IsPressed($"P{port} {FlightStickButtons.ButtonP}") ? 1 : 0; + gameInput.flightStick.leftTrigger = controller.IsPressed($"P{port} {FlightStickButtons.LeftTrigger}") ? 1 : 0; + gameInput.flightStick.rightTrigger = controller.IsPressed($"P{port} {FlightStickButtons.RightTrigger}") ? 1 : 0; + gameInput.flightStick.horizontalAxis = controller.AxisValue($"P{port} {Inputs.FlighStickHorizontalAxis}"); + gameInput.flightStick.verticalAxis = controller.AxisValue($"P{port} {Inputs.FlighStickVerticalAxis}"); + gameInput.flightStick.altitudeAxis = controller.AxisValue($"P{port} {Inputs.FlighStickAltitudeAxis}"); + break; + + case ControllerType.LightGun: + gameInput.lightGun.trigger = controller.IsPressed($"P{port} {LightGunButtons.Trigger}") ? 1 : 0; + gameInput.lightGun.select = controller.IsPressed($"P{port} {LightGunButtons.Select}") ? 1 : 0; + gameInput.lightGun.reload = controller.IsPressed($"P{port} {LightGunButtons.Reload}") ? 1 : 0; + gameInput.lightGun.isOffScreen = controller.IsPressed($"P{port} {LightGunButtons.IsOffScreen}") ? 1 : 0; + gameInput.lightGun.screenX = controller.AxisValue($"P{port} {Inputs.LightGunScreenX}"); + gameInput.lightGun.screenY = controller.AxisValue($"P{port} {Inputs.LightGunScreenY}"); + break; + + case ControllerType.ArcadeLightGun: + gameInput.arcadeLightGun.trigger = controller.IsPressed($"P{port} {ArcadeLightGunButtons.Trigger}") ? 1 : 0; + gameInput.arcadeLightGun.select = controller.IsPressed($"P{port} {ArcadeLightGunButtons.Select}") ? 1 : 0; + gameInput.arcadeLightGun.start = controller.IsPressed($"P{port} {ArcadeLightGunButtons.Start}") ? 1 : 0; + gameInput.arcadeLightGun.reload = controller.IsPressed($"P{port} {ArcadeLightGunButtons.Reload}") ? 1 : 0; + gameInput.arcadeLightGun.auxA = controller.IsPressed($"P{port} {ArcadeLightGunButtons.AuxA}") ? 1 : 0; + gameInput.arcadeLightGun.isOffScreen = controller.IsPressed($"P{port} {ArcadeLightGunButtons.IsOffScreen}") ? 1 : 0; + gameInput.arcadeLightGun.screenX = controller.AxisValue($"P{port} {Inputs.LightGunScreenX}"); + gameInput.arcadeLightGun.screenY = controller.AxisValue($"P{port} {Inputs.LightGunScreenY}"); + break; + + case ControllerType.OrbatakTrackball: + gameInput.orbatakTrackball.startP1 = controller.IsPressed($"P{port} {OrbatakTrackballButtons.StartP1}") ? 1 : 0; + gameInput.orbatakTrackball.startP2 = controller.IsPressed($"P{port} {OrbatakTrackballButtons.StartP2}") ? 1 : 0; + gameInput.orbatakTrackball.coinP1 = controller.IsPressed($"P{port} {OrbatakTrackballButtons.CoinP1}") ? 1 : 0; + gameInput.orbatakTrackball.coinP2 = controller.IsPressed($"P{port} {OrbatakTrackballButtons.CoinP2}") ? 1 : 0; + gameInput.orbatakTrackball.service = controller.IsPressed($"P{port} {OrbatakTrackballButtons.Service}") ? 1 : 0; + gameInput.orbatakTrackball.dX = controller.AxisValue($"P{port} {Inputs.TrackballPosX}"); + gameInput.orbatakTrackball.dY = controller.AxisValue($"P{port} {Inputs.TrackballPosY}"); + break; + } + + return gameInput; + } + + protected override void FrameAdvancePost() + { + } + + protected override void SaveStateBinaryInternal(BinaryWriter writer) + { + writer.Write(_discIndex); + writer.Write(_discInserted); + } + + protected override void LoadStateBinaryInternal(BinaryReader reader) + { + _discIndex = reader.ReadInt32(); + _discInserted = reader.ReadBoolean(); + } + + } +} diff --git a/src/BizHawk.Emulation.Cores/CoreNames.cs b/src/BizHawk.Emulation.Cores/CoreNames.cs index 5c7bd39bc6..097f1fdb76 100644 --- a/src/BizHawk.Emulation.Cores/CoreNames.cs +++ b/src/BizHawk.Emulation.Cores/CoreNames.cs @@ -47,6 +47,7 @@ namespace BizHawk.Emulation.Cores public const string Nymashock = "Nymashock"; public const string O2Hawk = "O2Hawk"; public const string Octoshock = "Octoshock"; + public const string Opera = "Opera"; public const string PceHawk = "PCEHawk"; public const string PicoDrive = "PicoDrive"; public const string QuickNes = "quickerNES"; diff --git a/src/BizHawk.Emulation.DiscSystem/DiscIdentifier.cs b/src/BizHawk.Emulation.DiscSystem/DiscIdentifier.cs index e10b1dace8..1ab67d5440 100644 --- a/src/BizHawk.Emulation.DiscSystem/DiscIdentifier.cs +++ b/src/BizHawk.Emulation.DiscSystem/DiscIdentifier.cs @@ -352,14 +352,21 @@ namespace BizHawk.Emulation.DiscSystem private bool Detect3DO() { var toc = _disc.TOC; - for (var t = toc.FirstRecordedTrackNumber; - t <= toc.LastRecordedTrackNumber; - t++) + for (var t = toc.FirstRecordedTrackNumber; t <= toc.LastRecordedTrackNumber; t++) { var track = _disc.TOC.TOCItems[t]; - if (track.IsData && SectorContains("iamaduckiamaduck", track.LBA)) - return true; + // https://groups.google.com/g/rec.games.video.3do/c/1U3qrmLSYMQ?pli=1 + // The iamaduck is not mandatory and not present in all games + if (track.IsData && SectorContains("iamaduckiamaduck", track.LBA)) return true; } + + for (var i = 0; i < 256; i++) + { + // The following sync pattern is present in 3DO games + // https://github.com/trapexit/3dt/blob/84f1aa5a5e778f14c2216fc0c891e78625266cad/src/tdo_disc_label.hpp#L65 + if (SectorContains(Convert.ToChar(01) + "ZZZZZ" + Convert.ToChar(01), i)) return true; + } + return false; } diff --git a/waterbox/make-all-cores.sh b/waterbox/make-all-cores.sh index 81f1593e74..312896974d 100755 --- a/waterbox/make-all-cores.sh +++ b/waterbox/make-all-cores.sh @@ -11,6 +11,7 @@ make -C dsda $1 -j make -C gpgx $1 -j make -C libsnes $1 -j make -C melon $1 -j +make -C opera $1 -j make -C picodrive $1 -j make -C stella $1 -j make -C snes9x $1 -j diff --git a/waterbox/opera/Makefile b/waterbox/opera/Makefile new file mode 100644 index 0000000000..ddf59477d2 --- /dev/null +++ b/waterbox/opera/Makefile @@ -0,0 +1,103 @@ +TARGET := opera.wbx +CCFLAGS := -I. \ + -Iopera-libretro \ + -Iopera-libretro/libopera \ + -Iopera-libretro/libretro-common/include \ + -Iopera-libretro/deps \ + -Iopera-libretro/deps/lzma-19.00/include \ + -Iopera-libretro/deps/libchdr/include \ + -Iopera-libretro/deps/zlib-1.2.11 \ + -DHAVE_CHD \ + -D_7ZIP_ST \ + -DDR_FLAC_NO_STDIO \ + -D__LIBRETRO__ \ + -DNDEBUG \ + -DHAVE_CDROM \ + -DHAVE_STDINT_H \ + -DHAVE_STDLIB_H \ + -DHAVE_SYS_PARAM_H \ + -DINLINE=inline \ + -DTHREADED_DSP +CXXFLAGS := $(CCFLAGS) +SRCS = bizhawk.cpp \ + opera-libretro/lr_input.c \ + opera-libretro/libretro_core_options.c \ + opera-libretro/lr_input_crosshair.c \ + opera-libretro/opera_lr_nvram.c \ + opera-libretro/libopera/opera_mem.c \ + opera-libretro/libopera/opera_diag_port.c \ + opera-libretro/libopera/prng32.c \ + opera-libretro/libopera/opera_sport.c \ + opera-libretro/libopera/opera_arm.c \ + opera-libretro/libopera/opera_region.c \ + opera-libretro/libopera/opera_bios.c \ + opera-libretro/libopera/prng16.c \ + opera-libretro/libopera/opera_cdrom.c \ + opera-libretro/libopera/opera_clock.c \ + opera-libretro/libopera/opera_log.c \ + opera-libretro/libopera/opera_fixedpoint_math.c \ + opera-libretro/libopera/opera_nvram.c \ + opera-libretro/libopera/opera_3do.c \ + opera-libretro/libopera/opera_xbus_cdrom_plugin.c \ + opera-libretro/libopera/opera_madam.c \ + opera-libretro/libopera/opera_dsp.c \ + opera-libretro/libopera/opera_state.c \ + opera-libretro/libopera/opera_xbus.c \ + opera-libretro/libopera/opera_bitop.c \ + opera-libretro/libopera/opera_vdlp.c \ + opera-libretro/libopera/opera_clio.c \ + opera-libretro/libopera/opera_pbus.c \ + opera-libretro/libretro.c \ + opera-libretro/retro_cdimage.c \ + opera-libretro/opera_lr_callbacks.c \ + opera-libretro/opera_lr_opts.c \ + opera-libretro/deps/zlib-1.2.11/zutil.c \ + opera-libretro/deps/zlib-1.2.11/adler32.c \ + opera-libretro/deps/zlib-1.2.11/inflate.c \ + opera-libretro/deps/zlib-1.2.11/inftrees.c \ + opera-libretro/deps/zlib-1.2.11/crc32.c \ + opera-libretro/deps/zlib-1.2.11/infback.c \ + opera-libretro/deps/zlib-1.2.11/inffast.c \ + opera-libretro/deps/lzma-19.00/src/BraIA64.c \ + opera-libretro/deps/lzma-19.00/src/CpuArch.c \ + opera-libretro/deps/lzma-19.00/src/Sort.c \ + opera-libretro/deps/lzma-19.00/src/Bra86.c \ + opera-libretro/deps/lzma-19.00/src/Alloc.c \ + opera-libretro/deps/lzma-19.00/src/LzmaEnc.c \ + opera-libretro/deps/lzma-19.00/src/Lzma86Dec.c \ + opera-libretro/deps/lzma-19.00/src/Delta.c \ + opera-libretro/deps/lzma-19.00/src/LzmaDec.c \ + opera-libretro/deps/lzma-19.00/src/Lzma86Enc.c \ + opera-libretro/deps/lzma-19.00/src/LzFind.c \ + opera-libretro/deps/libchdr/src/libchdr_cdrom.c \ + opera-libretro/deps/libchdr/src/libchdr_flac.c \ + opera-libretro/deps/libchdr/src/libchdr_bitstream.c \ + opera-libretro/deps/libchdr/src/libchdr_chd.c \ + opera-libretro/deps/libchdr/src/libchdr_huffman.c \ + opera-libretro/cuefile.c \ + opera-libretro/opera_lr_dsp.c \ + opera-libretro/libretro-common/string/stdstring.c \ + opera-libretro/libretro-common/vfs/vfs_implementation_cdrom.c \ + opera-libretro/libretro-common/vfs/vfs_implementation.c \ + opera-libretro/libretro-common/encodings/encoding_utf.c \ + opera-libretro/libretro-common/file/retro_dirent.c \ + opera-libretro/libretro-common/file/file_path.c \ + opera-libretro/libretro-common/compat/compat_strl.c \ + opera-libretro/libretro-common/compat/fopen_utf8.c \ + opera-libretro/libretro-common/compat/compat_snprintf.c \ + opera-libretro/libretro-common/compat/compat_posix_string.c \ + opera-libretro/libretro-common/compat/compat_strcasestr.c \ + opera-libretro/libretro-common/streams/file_stream.c \ + opera-libretro/libretro-common/streams/chd_stream.c \ + opera-libretro/libretro-common/streams/interface_stream.c \ + opera-libretro/libretro-common/streams/file_stream_transforms.c \ + opera-libretro/libretro-common/streams/memory_stream.c \ + opera-libretro/libretro-common/rthreads/rthreads.c \ + opera-libretro/libretro-common/lists/dir_list.c \ + opera-libretro/libretro-common/lists/string_list.c \ + opera-libretro/libretro-common/cdrom/cdrom.c \ + opera-libretro/libretro-common/memmap/memalign.c \ + opera-libretro/libretro-common/memmap/memmap.c \ + opera-libretro/lr_input_descs.c + +include ../common.mak diff --git a/waterbox/opera/bizhawk.cpp b/waterbox/opera/bizhawk.cpp new file mode 100644 index 0000000000..0883f6e551 --- /dev/null +++ b/waterbox/opera/bizhawk.cpp @@ -0,0 +1,391 @@ +#include "bizhawk.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +std::string _biosFilePath; +std::string _gameFilePath; +std::string _fontFilePath; +int _port1Type; +int _port2Type; +controllerData_t _port1Value; +controllerData_t _port2Value; +uint32_t* _videoBuffer; +size_t _videoHeight; +size_t _videoWidth; +size_t _videoPitch; +int _region; +int _nvramChanged; +int _inputPortsRead; + +#define _MAX_SAMPLES 4096 +#define _CHANNEL_COUNT 2 +int16_t _audioBuffer[_MAX_SAMPLES * _CHANNEL_COUNT]; +size_t _audioSamples; + +extern "C" +{ + void* xbus_cdrom_plugin(int proc_, void* data_); + void opera_cdrom_set_callbacks(opera_cdrom_get_size_cb_t get_size_, opera_cdrom_set_sector_cb_t set_sector_, opera_cdrom_read_sector_cb_t read_sector_); + void opera_nvram_init(void *buf, const int bufsize); + void opera_lr_callbacks_set_audio_sample(retro_audio_sample_t cb); + void opera_lr_callbacks_set_audio_sample_batch(retro_audio_sample_batch_t cb); + void opera_lr_callbacks_set_environment(retro_environment_t cb); + void opera_lr_callbacks_set_input_poll(retro_input_poll_t cb); + void opera_lr_callbacks_set_input_state(retro_input_state_t cb); + void opera_lr_callbacks_set_log_printf(retro_log_printf_t cb); + void opera_lr_callbacks_set_video_refresh(retro_video_refresh_t cb); + RETRO_API void *retro_get_memory_data(unsigned id); + RETRO_API size_t retro_get_memory_size(unsigned id); + void retro_set_controller_port_device(unsigned port_, unsigned device_); + void retro_get_system_av_info(struct retro_system_av_info *info_); +} + +void RETRO_CALLCONV retro_video_refresh_callback(const void *data, unsigned width, unsigned height, size_t pitch) +{ + // printf("Video %p, w: %u, h: %u, p: %lu\n", data, width, height, pitch); + _videoBuffer = (uint32_t*)data; + _videoWidth = width; + _videoHeight = height; + _videoPitch = pitch; +} + +void RETRO_CALLCONV retro_log_printf_callback(enum retro_log_level level, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + printf(format, ap); + va_end(ap); +} + +size_t RETRO_CALLCONV retro_audio_sample_batch_callback(const int16_t *data, size_t frames) +{ + memcpy(_audioBuffer, data, sizeof(int16_t) * frames * _CHANNEL_COUNT); + _audioSamples = frames; + return frames; +} + +void RETRO_CALLCONV retro_input_poll_callback() +{ + // printf("Libretro Input Poll Callback Called:\n"); +} + +int16_t processController(const int portType, controllerData_t& portValue, const unsigned device, const unsigned index, const unsigned id) +{ + switch (portType) + { + case RETRO_DEVICE_JOYPAD: + switch (id) + { + case RETRO_DEVICE_ID_JOYPAD_UP: return portValue.gamePad.up; + case RETRO_DEVICE_ID_JOYPAD_DOWN: return portValue.gamePad.down; + case RETRO_DEVICE_ID_JOYPAD_LEFT: return portValue.gamePad.left; + case RETRO_DEVICE_ID_JOYPAD_RIGHT: return portValue.gamePad.right; + case RETRO_DEVICE_ID_JOYPAD_L: return portValue.gamePad.buttonL; + case RETRO_DEVICE_ID_JOYPAD_R: return portValue.gamePad.buttonR; + case RETRO_DEVICE_ID_JOYPAD_SELECT: return portValue.gamePad.buttonX; + case RETRO_DEVICE_ID_JOYPAD_START: return portValue.gamePad.buttonP; + case RETRO_DEVICE_ID_JOYPAD_Y: return portValue.gamePad.buttonA; + case RETRO_DEVICE_ID_JOYPAD_B: return portValue.gamePad.buttonB; + case RETRO_DEVICE_ID_JOYPAD_A: return portValue.gamePad.buttonC; + default: return 0; + } + + case RETRO_DEVICE_MOUSE: + switch (id) + { + case RETRO_DEVICE_ID_MOUSE_X: return portValue.mouse.dX; + case RETRO_DEVICE_ID_MOUSE_Y: return portValue.mouse.dY; + case RETRO_DEVICE_ID_MOUSE_LEFT: return portValue.mouse.leftButton; + case RETRO_DEVICE_ID_MOUSE_MIDDLE: return portValue.mouse.middleButton; + case RETRO_DEVICE_ID_MOUSE_RIGHT: return portValue.mouse.rightButton; + case RETRO_DEVICE_ID_MOUSE_BUTTON_4: return portValue.mouse.fourthButton; + default: return 0; + } + + case RETRO_DEVICE_FLIGHTSTICK: + if (index == RETRO_DEVICE_INDEX_ANALOG_BUTTON) + { + switch (id) + { + case RETRO_DEVICE_ID_JOYPAD_R2: return portValue.flightStick.fire; + case RETRO_DEVICE_ID_JOYPAD_Y: return portValue.flightStick.buttonA; + case RETRO_DEVICE_ID_JOYPAD_B: return portValue.flightStick.buttonB; + case RETRO_DEVICE_ID_JOYPAD_A: return portValue.flightStick.buttonC; + case RETRO_DEVICE_ID_JOYPAD_UP: return portValue.flightStick.up; + case RETRO_DEVICE_ID_JOYPAD_DOWN: return portValue.flightStick.down; + case RETRO_DEVICE_ID_JOYPAD_LEFT: return portValue.flightStick.left; + case RETRO_DEVICE_ID_JOYPAD_RIGHT: return portValue.flightStick.right; + case RETRO_DEVICE_ID_JOYPAD_START: return portValue.flightStick.buttonP; + case RETRO_DEVICE_ID_JOYPAD_SELECT: return portValue.flightStick.buttonX; + case RETRO_DEVICE_ID_JOYPAD_L: return portValue.flightStick.leftTrigger; + case RETRO_DEVICE_ID_JOYPAD_R: return portValue.flightStick.rightTrigger; + default: return 0; + } + } + else + { + switch (id) + { + case RETRO_DEVICE_ID_ANALOG_X: + if (index == RETRO_DEVICE_INDEX_ANALOG_LEFT) return portValue.flightStick.horizontalAxis; + if (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT) return portValue.flightStick.altitudeAxis; + return 0; + + case RETRO_DEVICE_ID_ANALOG_Y: + if (index == RETRO_DEVICE_INDEX_ANALOG_LEFT) return portValue.flightStick.verticalAxis; + if (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT) return portValue.flightStick.altitudeAxis; + return 0; + default: return 0; + } + } + + case RETRO_DEVICE_LIGHTGUN: + switch (id) + { + case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X: return portValue.lightGun.screenX; + case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y: return portValue.lightGun.screenY; + case RETRO_DEVICE_ID_LIGHTGUN_TRIGGER: return portValue.lightGun.trigger; + case RETRO_DEVICE_ID_LIGHTGUN_SELECT: return portValue.lightGun.select; + case RETRO_DEVICE_ID_LIGHTGUN_RELOAD: return portValue.lightGun.reload; + case RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN: return portValue.lightGun.isOffScreen; + default: return 0; + } + + case RETRO_DEVICE_ARCADE_LIGHTGUN: + switch (id) + { + case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X: return portValue.arcadeLightGun.screenX; + case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y: return portValue.arcadeLightGun.screenY; + case RETRO_DEVICE_ID_LIGHTGUN_TRIGGER: return portValue.arcadeLightGun.trigger; + case RETRO_DEVICE_ID_LIGHTGUN_SELECT: return portValue.arcadeLightGun.select; + case RETRO_DEVICE_ID_LIGHTGUN_START: return portValue.arcadeLightGun.start; + case RETRO_DEVICE_ID_LIGHTGUN_RELOAD: return portValue.arcadeLightGun.reload; + case RETRO_DEVICE_ID_LIGHTGUN_AUX_A: return portValue.arcadeLightGun.auxA; + case RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN: return portValue.arcadeLightGun.isOffScreen; + default: return 0; + } + + case RETRO_DEVICE_ORBATAK_TRACKBALL: + switch (id) + { + case RETRO_DEVICE_ID_ANALOG_X: + if (index == RETRO_DEVICE_INDEX_ANALOG_LEFT) return portValue.orbatakTrackball.dX; + if (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT) return portValue.orbatakTrackball.dX; + return 0; + + case RETRO_DEVICE_ID_ANALOG_Y: + if (index == RETRO_DEVICE_INDEX_ANALOG_LEFT) return portValue.orbatakTrackball.dY; + if (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT) return portValue.orbatakTrackball.dY; + return 0; + + case RETRO_DEVICE_ID_JOYPAD_SELECT: return portValue.orbatakTrackball.startP1; + case RETRO_DEVICE_ID_JOYPAD_START: return portValue.orbatakTrackball.startP2; + case RETRO_DEVICE_ID_JOYPAD_L: return portValue.orbatakTrackball.coinP1; + case RETRO_DEVICE_ID_JOYPAD_R: return portValue.orbatakTrackball.coinP2; + case RETRO_DEVICE_ID_JOYPAD_R2: return portValue.orbatakTrackball.service; + default: return 0; + } + + default: return 0; + } + + return 0; +} + +int16_t RETRO_CALLCONV retro_input_state_callback(unsigned port, unsigned device, unsigned index, unsigned id) +{ + // printf("Libretro Input State Callback Called. Port: %u, Device: %u, Index: %u, Id: %u\n", port, device, index, id); + if (port == 0) return processController(_port1Type, _port1Value, device, index, id); + if (port == 1) return processController(_port2Type, _port2Value, device, index, id); + + return 0; +} + +char _deviceCountOption[256]; +void configHandler(struct retro_variable *var) +{ + printf("Variable Name: %s / Value: %s\n", var->key, var->value); + + std::string key(var->key); + + if (key == "opera_bios" && _biosFilePath != "None") var->value = _biosFilePath.c_str(); + if (key == "opera_font" && _fontFilePath != "None") var->value = _fontFilePath.c_str(); + if (key == "opera_region") + { + if (_region == 0) var->value = "ntsc"; + if (_region == 1) var->value = "pal1"; + if (_region == 2) var->value = "pal2"; + } + if (key == "opera_active_devices") + { + int deviceCount = 0; + if (_port1Type != RETRO_DEVICE_NONE) deviceCount++; + if (_port2Type != RETRO_DEVICE_NONE) deviceCount++; + sprintf(_deviceCountOption, "%d", deviceCount); + var->value = _deviceCountOption; + } +} + +const char* systemPath = "."; +bool RETRO_CALLCONV retro_environment_callback(unsigned cmd, void *data) +{ + // printf("Libretro Environment Callback Called: %u\n", cmd); + + if (cmd == RETRO_ENVIRONMENT_GET_LOG_INTERFACE) { *((retro_log_printf_t*)data) = retro_log_printf_callback; return true; } + if (cmd == RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL) { return true; } + if (cmd == RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS) { return true; } + if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE) { configHandler((struct retro_variable *)data); return true; } + if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE) { return true; } + if (cmd == RETRO_ENVIRONMENT_SET_PIXEL_FORMAT) { *((vdlp_pixel_format_e*) data) = VDLP_PIXEL_FORMAT_XRGB8888; return true; } + if (cmd == RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY) { *((const char**)data) = systemPath; return true; } + if (cmd == RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY) { *((const char**)data) = systemPath; return true; } + + fprintf(stderr, "Unrecognized environment callback command: %u\n", cmd); + + return false; +} + +/// CD Management Logic Start +#define CDIMAGE_SECTOR_SIZE 2048 +uint32_t _currentSector = 0; +void (*cd_read_callback)(int32_t lba, void * dest); +int (*cd_sector_count_callback)(); +ECL_EXPORT void SetCdCallbacks(void (*cdrc)(int32_t lba, void * dest), int (*cdscc)()) +{ + cd_read_callback = cdrc; + cd_sector_count_callback = cdscc; +} + +uint32_t cd_get_size(void) { return cd_sector_count_callback(); } +void cd_set_sector(const uint32_t sector_) { _currentSector = sector_; } +void cd_read_sector(void *buf_) { cd_read_callback(_currentSector, buf_); } +/// CD Management Logic End + +// SRAM management start +bool _sram_changed = false; +ECL_EXPORT bool sram_changed() { return _nvramChanged; } +ECL_EXPORT int get_sram_size() { return NVRAM_SIZE; } +ECL_EXPORT uint8_t* get_sram_buffer() { return (uint8_t*) NVRAM; } +ECL_EXPORT void get_sram(uint8_t* sramBuffer) +{ + if (NVRAM == NULL) return; + memcpy(sramBuffer, get_sram_buffer(), get_sram_size()); +} + +ECL_EXPORT void set_sram(uint8_t* sramBuffer) +{ + if (NVRAM == NULL) opera_nvram_init(NVRAM,NVRAM_SIZE); + memcpy(get_sram_buffer(), sramBuffer, get_sram_size()); +} +// SRAM Management end + +ECL_EXPORT bool Init(const char* gameFilePath, const char* biosFilePath, const char* fontFilePath, int port1Type, int port2Type, int region) +{ + _gameFilePath = gameFilePath; + _biosFilePath = biosFilePath; + _fontFilePath = fontFilePath; + _port1Type = port1Type; + _port2Type = port2Type; + _region = region; + + opera_lr_callbacks_set_environment(retro_environment_callback); + opera_lr_callbacks_set_input_state(retro_input_state_callback); + opera_lr_callbacks_set_input_poll(retro_input_poll_callback); + opera_lr_callbacks_set_audio_sample_batch(retro_audio_sample_batch_callback); + opera_lr_callbacks_set_video_refresh(retro_video_refresh_callback); + + retro_set_controller_port_device(0, port1Type); + retro_set_controller_port_device(1, port2Type); + retro_init(); + + // Setting cd callbacks + opera_cdrom_set_callbacks(cd_get_size, cd_set_sector, cd_read_sector); + + // Loading game file + struct retro_game_info game; + game.path = _gameFilePath.c_str(); + auto loadResult = retro_load_game(&game); + if (loadResult == false) { fprintf(stderr, "Could not load game: '%s'\n", _gameFilePath.c_str()); return false; } + + // Getting av info + struct retro_system_av_info info; + retro_get_system_av_info(&info); + printf("3DO Framerate: %f\n", info.timing.fps); + + return true; +} + +ECL_EXPORT void opera_get_video(int& w, int& h, int& pitch, uint8_t*& buffer) +{ + buffer = (uint8_t*)_videoBuffer; + w = _videoWidth; + h = _videoHeight; + pitch = _videoPitch; +} + +ECL_EXPORT void FrameAdvance(MyFrameInfo* f) +{ + // Setting inputs + _port1Value = f->port1; + _port2Value = f->port2; + + //printf("Mouse X%d(%d), Y%d(%d), L%d, M%d, B%d\n", _port1Value.mouse.posX, _port1Value.mouse.dX, _port1Value.mouse.posY, _port1Value.mouse.dY, _port1Value.mouse.leftButton, _port1Value.mouse.middleButton, _port1Value.mouse.rightButton); + //fflush(stdout); + + // Checking for changes in NVRAM + _nvramChanged = false; + + // Checking if ports have been read + _inputPortsRead = 0; + + // If resetting, do it now. Otherwise, running a single frame + if (f->isReset == 1) retro_reset(); + else retro_run(); + + // The frame is lagged if no inputs were read + f->base.Lagged = !_inputPortsRead; + + // Setting video buffer + f->base.Width = _videoWidth; + f->base.Height = _videoHeight; + memcpy(f->base.VideoBuffer, _videoBuffer, sizeof(uint32_t) * _videoWidth * _videoHeight); + + // Setting audio buffer + f->base.Samples = _audioSamples; + memcpy(f->base.SoundBuffer, _audioBuffer, _audioSamples * sizeof(int16_t) * _CHANNEL_COUNT); +} + +ECL_EXPORT void GetMemoryAreas(MemoryArea *m) +{ + int memAreaIdx = 0; + + m[memAreaIdx].Data = retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM); + m[memAreaIdx].Name = "System RAM"; + m[memAreaIdx].Size = retro_get_memory_size(RETRO_MEMORY_SYSTEM_RAM); + m[memAreaIdx].Flags = MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_PRIMARY; + memAreaIdx++; + + m[memAreaIdx].Data = retro_get_memory_data(RETRO_MEMORY_VIDEO_RAM); + m[memAreaIdx].Name = "Video RAM"; + m[memAreaIdx].Size = retro_get_memory_size(RETRO_MEMORY_VIDEO_RAM); + m[memAreaIdx].Flags = MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE; + memAreaIdx++; + + m[memAreaIdx].Data = get_sram_buffer(); + m[memAreaIdx].Name = "Non-volatile RAM"; + m[memAreaIdx].Size = get_sram_size(); + m[memAreaIdx].Flags = MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_SAVERAMMABLE; + memAreaIdx++; +} + +void (*InputCallback)(); +ECL_EXPORT void SetInputCallback(void (*callback)()) +{ + InputCallback = callback; +} diff --git a/waterbox/opera/bizhawk.hpp b/waterbox/opera/bizhawk.hpp new file mode 100644 index 0000000000..4ca0ec3e38 --- /dev/null +++ b/waterbox/opera/bizhawk.hpp @@ -0,0 +1,107 @@ +#pragma once + +// system +#include +#include +#include +#include + +// waterbox +#include "emulibc.h" +#include "waterboxcore.h" + +struct gamePad_t +{ + int up; + int down; + int left; + int right; + int buttonX; + int buttonP; + int buttonA; + int buttonB; + int buttonC; + int buttonL; + int buttonR; +}; + +struct mouse_t +{ + int dX; + int dY; + int leftButton; + int middleButton; + int rightButton; + int fourthButton; +}; + +struct flightStick_t +{ + int up; + int down; + int left; + int right; + int fire; + int buttonA; + int buttonB; + int buttonC; + int buttonX; + int buttonP; + int leftTrigger; + int rightTrigger; + int horizontalAxis; + int verticalAxis; + int altitudeAxis; +}; + +struct lightGun_t +{ + int trigger; + int select; + int reload; + int isOffScreen; + int screenX; + int screenY; +}; + +struct arcadeLightGun_t +{ + int trigger; + int select; + int start; + int reload; + int auxA; + int isOffScreen; + int screenX; + int screenY; +}; + +struct OrbatakTrackball_t +{ + int dX; + int dY; + int startP1; + int startP2; + int coinP1; + int coinP2; + int service; +}; + +struct controllerData_t +{ + gamePad_t gamePad; + mouse_t mouse; + flightStick_t flightStick; + lightGun_t lightGun; + arcadeLightGun_t arcadeLightGun; + OrbatakTrackball_t orbatakTrackball; +}; + +typedef struct +{ + FrameInfo base; + controllerData_t port1; + controllerData_t port2; + int isReset; +} MyFrameInfo; + diff --git a/waterbox/opera/opera-libretro b/waterbox/opera/opera-libretro new file mode 160000 index 0000000000..536b8e2d4f --- /dev/null +++ b/waterbox/opera/opera-libretro @@ -0,0 +1 @@ +Subproject commit 536b8e2d4fae8b2aea97cbfa4c4e2d62843076ea diff --git a/waterbox/readme.txt b/waterbox/readme.txt index 1592f6859f..ba96fd0213 100644 --- a/waterbox/readme.txt +++ b/waterbox/readme.txt @@ -39,6 +39,7 @@ It consists of a modified musl libc, and build scripts to tie it all together. * waterbox/uae/libretro-uae (required for uae) * waterbox/stella/core (required for stella) * waterbox/dsda/core (required for dsda) + * waterbox/opera/opera-libretro (required for opera) * waterbox/dosbox/dosbox-x (required for DOSBox-x) * none of these submodules need to be cloned recursively @@ -80,6 +81,7 @@ It consists of a modified musl libc, and build scripts to tie it all together. cd nyma && make -f ss.mak install cd nyma && make -f shock.mak install cd nyma && make -f vb.mak install + cd opera && make install cd picodrive && make install cd stella && make install cd snes9x && make install