Adding Opera (3DO) as emulation core in Bizhawk (#4264)

* Adding base

* Progress with opera and adding 3do firmwares

* Adding missing callbacks

* Adding missing callbacks

* 3DO core starting

* Now passing inputs

* Passing gamepad inputs

* Fixing input names

* Now supporting mouse

* Adding mouse support

* Added flightstick

* Added flight stick

* Adding last inputs and fixing audio

* Adding controllers and fixing audio

* Adding orbatak

* Fixing mnemonics, added orbatak

* Adding font roms

* Adding font roms

* Adding region

* Adding region

* Fixing mouse issue

* Fixed initialization bug in fresh installs

* Setting mouse to relative inputs

* Using mouse as relative

* Bypassing bios checks to enable bizhawk to pass whatever bios it wants

* Adding default inputs for 3DO

* Adding detection of nvram changes

* Adding lag frame and nvram saving

* Adding cd use detection

* Adding cdrom light

* Using cd callbacks

* Using cd callbacks

* Bypassing image name check

* Adding multidisc support

* Removing 3do-iso special extension

* Fixing build

* Attempting to add disc swapping

* Trying to add multidisc support

* Uncommenting cd functions

* revert unrelated changes

* misc unmanaged integration

* Update waterbox-cores.yml

* Added reset button, removed eject/insert

* Added reset button, removed eject/insert

* Fix line endings in `Bk2MnemonicLookup.cs` changes

* Fix indentation

* Fix spaces in `Opera.cs`

* Alphabetise

* Make `Opera`'s `ISaveRam` implementations `override`

* Misc. code style changes

* Fix casting array index to `uint` for comparisons

* Adding default framerate and removing message duration parameter

* Refactoring input parsing and fixing some errors

* Fixing indentation

* Exposing non volatile ram

* Adding automatic sram management

* Now letting bk manage saveram automatically

* Fixing indentation and removing unncessary flag

* Removing dead code

* Removing dead code

* Removing unnecessary mnemonic fallbacks

* Keep fixing indentation errors

* Removing struct for memory areas

* Adding proper detection of input reading

* Changing namespace to not have an underscore

* fix whitespace in PlatformFrameRates

* make waterbox function non-virtual

* Update src/BizHawk.Emulation.Cores/Consoles/3DO/Opera.cs

Co-authored-by: feos <vadosnaprimer@users.noreply.github.com>

* Fixing controller mapping

* Fixing controller mapping, 3DO->Panasonic3DO, added submenu

* Fixing controller

* Fixing controllers

* Removing unnecessary function

* Fixing namespace and default ctrls

* Fixing 3DO detection

* Adding submodule branch

* Adding PAL framerate for opera

* controller pic

* Fixed firmware naming

* Fix line endings

* Recompress image

* Realphabetise after namespace/sysID change

* Move this `case` block up

* Minor code style fixes

* Renaming user-facing Panasonic3DO strings to 3DO

---------

Co-authored-by: Morilli <35152647+Morilli@users.noreply.github.com>
Co-authored-by: YoshiRulz <OSSYoshiRulz+git@gmail.com>
Co-authored-by: feos <vadosnaprimer@users.noreply.github.com>
Co-authored-by: feos <feykomylce@gmail.com>
This commit is contained in:
Sergio Martin 2025-05-09 09:04:14 +02:00 committed by GitHub
parent 42a96fb996
commit 654544b329
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1596 additions and 9 deletions

View File

@ -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

4
.gitmodules vendored
View File

@ -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

View File

@ -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",

BIN
Assets/dll/opera.wbx.zst Normal file

Binary file not shown.

View File

@ -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;

View File

@ -17,6 +17,8 @@ namespace BizHawk.Client.Common
private static readonly Dictionary<string, double> Rates = new Dictionary<string, double>
{
["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)

View File

@ -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<Opera>(CoreNames.Opera)));
// PCEHawk
items.Add(CreateCoreSubmenu(VSystemCategory.Consoles, CoreNames.PceHawk, CreateGenericCoreConfigItem<PCEngine>(CoreNames.PceHawk)));

View File

@ -36,6 +36,7 @@ namespace BizHawk.Client.EmuHawk.Properties
internal static readonly Lazy<Bitmap> SaturnController = new Lazy<Bitmap>(() => ReadEmbeddedBitmap("ControllerImages.SaturnController"));
internal static readonly Lazy<Bitmap> SmsController = new Lazy<Bitmap>(() => ReadEmbeddedBitmap("ControllerImages.SMSController"));
internal static readonly Lazy<Bitmap> SnesController = new Lazy<Bitmap>(() => ReadEmbeddedBitmap("ControllerImages.SNES_Controller"));
internal static readonly Lazy<Bitmap> ThreeDOController = new Lazy<Bitmap>(() => ReadEmbeddedBitmap("ControllerImages.3DOController"));
internal static readonly Lazy<Bitmap> TI83Controller = new Lazy<Bitmap>(() => ReadEmbeddedBitmap("ControllerImages.TI83_Controller"));
internal static readonly Lazy<Bitmap> VBoyController = new Lazy<Bitmap>(() => ReadEmbeddedBitmap("ControllerImages.VBoyController"));
internal static readonly Lazy<Bitmap> WonderSwanColor = new Lazy<Bitmap>(() => ReadEmbeddedBitmap("ControllerImages.WonderSwanColor"));

View File

@ -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);

View File

@ -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<string, string> SystemGroupNames = new Dictionary<string, string>
{
["3DO"] = "3DO / 3DO Arcade / 3DO M2",
["Amiga"] = "Amiga",
["NES"] = "NES",
["SNES"] = "SNES",

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -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,

View File

@ -182,6 +182,25 @@ namespace BizHawk.Emulation.Common
private static readonly Dictionary<string, Dictionary<string, char>> SystemOverrides = new Dictionary<string, Dictionary<string, char>>
{
[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<string, Dictionary<string, string>> AxisSystemOverrides = new Dictionary<string, Dictionary<string, string>>
{
[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",

View File

@ -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");

View File

@ -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",

View File

@ -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";

View File

@ -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;
}
}
}

View File

@ -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";
}
}
}

View File

@ -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";
}
}

View File

@ -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;
}
}

View File

@ -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<object, Opera.SyncSettings>
{
// 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);
}
}
}

View File

@ -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<IDiscAsset> _discAssets;
public override int VirtualWidth => BufferHeight * 4 / 3;
private LibOpera _libOpera;
// Image selection / swapping variables
[CoreConstructor(VSystemID.Raw.Panasonic3DO)]
public Opera(CoreLoadParameters<object, SyncSettings> 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<LibOpera>(
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<DiscSectorReader> _cdReaders = new List<DiscSectorReader>();
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();
}
}
}

View File

@ -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";

View File

@ -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;
}

View File

@ -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

103
waterbox/opera/Makefile Normal file
View File

@ -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

391
waterbox/opera/bizhawk.cpp Normal file
View File

@ -0,0 +1,391 @@
#include "bizhawk.hpp"
#include <stdlib.h>
#include <string>
#include <libretro.h>
#include <lr_input.h>
#include <opera_vdlp.h>
#include <opera_mem.h>
#include <opera_cdrom.h>
#include <opera_xbus.h>
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;
}

107
waterbox/opera/bizhawk.hpp Normal file
View File

@ -0,0 +1,107 @@
#pragma once
// system
#include <limits.h>
#include <stddef.h>
#include <stdarg.h>
#include <stdio.h>
// 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;

@ -0,0 +1 @@
Subproject commit 536b8e2d4fae8b2aea97cbfa4c4e2d62843076ea

View File

@ -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