Ares64 (#3125)
* ares64
* build, currently cherry picking c9af34027b
to build this
* some work
* commit the frontend work. crashes on some NR_SCHED_GETAFFINITY syscall
* delete this
* fix oopsie, add debugging shit
* getting closer...
* what the fuck is this mame shit doing???
* fuck
* it's one of these isn't it
* fucking hell
* at least it's not crashing on init now
* bleh
* let's see if this works?
* audio i think?
* oh right, need to disable threading here since that's not wbx supported
* testing
* testing
* testing...
* testing
* wtf?
* lol
* it's doing something now i guess?
* let's try this
* maybe this will catch this bullshit?
* lol
* the fuck?
* what
* meh
* bleh
* let's have some fun
* bleh
* bleh
* bleh
* maybe this works better
* let's try this?
* meh
* this probably will break
* lol
* meh
* a
* a
* a
* a
* a
* meh
* a
* a
* b
* alloc invisible these video buffers
* a
* actually build
* bleh
* well uhhhhhhhh let's see if this blows up for fun i guess?
* bleh
* yeah that when as well as expected
* push this
* bleh
* bleh
* bleh
* a
* dirty hackery
* fuck space/tabs
* hook up input
* hook up input frontend side
* reset/power
* going to have to commit to dumb workaround here i guess?
* fuck this bullshit crash
* cic stuff
* oops
* testing
* hook up save detection
* memory domains and saveram shit
* oops
* someday i'll get macros right
* let's get this kinda reproducable
* build
* bleh
* frontend shit
* bleh
* testing
* a
* fucking
* a
* a
* a
* workaround C# bullshit marshalling maybe?
* revert that, let's hack it on c# side instead
* disgusting hack
* fuck c#
* fix oopsies for eeprom/flash and fuck c#
* apparently explicit layout just dont work, thanks c#!
* correct orientation
* actually fix orientation
* testing
* bleh
* b
* a
* fix crash here
* rumble getter
* hook up rumble with frontend, cache readonly controller settings on core init
* remove old experiment
* make clean make install
|
@ -26,6 +26,8 @@ namespace BizHawk.Client.Common
|
|||
new[] { CoreNames.QuickNes, CoreNames.NesHawk, CoreNames.SubNesHawk }),
|
||||
(new[] { VSystemID.Raw.SNES },
|
||||
new[] { CoreNames.Faust, CoreNames.Snes9X, CoreNames.Bsnes, CoreNames.Bsnes115 }),
|
||||
(new[] { VSystemID.Raw.N64 },
|
||||
new[] { CoreNames.Mupen64Plus, CoreNames.Ares64, }),
|
||||
(new[] { VSystemID.Raw.SGB },
|
||||
new[] { CoreNames.Gambatte, CoreNames.Bsnes, CoreNames.Bsnes115}),
|
||||
(new[] { VSystemID.Raw.GB, VSystemID.Raw.GBC },
|
||||
|
@ -316,6 +318,7 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
[VSystemID.Raw.NES] = CoreNames.QuickNes,
|
||||
[VSystemID.Raw.SNES] = CoreNames.Snes9X,
|
||||
[VSystemID.Raw.N64] = CoreNames.Mupen64Plus,
|
||||
[VSystemID.Raw.GB] = CoreNames.Gambatte,
|
||||
[VSystemID.Raw.GBC] = CoreNames.Gambatte,
|
||||
[VSystemID.Raw.GBL] = CoreNames.GambatteLink,
|
||||
|
|
|
@ -1980,7 +1980,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
case VSystemID.Raw.INTV:
|
||||
IntvSubMenu.Visible = true;
|
||||
break;
|
||||
case VSystemID.Raw.N64:
|
||||
case VSystemID.Raw.N64 when Emulator is N64:
|
||||
N64SubMenu.Visible = true;
|
||||
break;
|
||||
case VSystemID.Raw.NES:
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
<Compile Update="Consoles/Nintendo/GBHawkLink/GBHawkLink.*.cs" DependentUpon="GBHawkLink.cs" />
|
||||
<Compile Update="Consoles/Nintendo/GBHawkLink3x/GBHawkLink3x.*.cs" DependentUpon="GBHawkLink3x.cs" />
|
||||
<Compile Update="Consoles/Nintendo/GBHawkLink4x/GBHawkLink4x.*.cs" DependentUpon="GBHawkLink4x.cs" />
|
||||
<Compile Update="Consoles/Nintendo/Ares64/Ares64.*.cs" DependentUpon="Ares64.cs" />
|
||||
<Compile Update="Consoles/Nintendo/N64/N64.*.cs" DependentUpon="N64.cs" />
|
||||
<Compile Update="Consoles/Nintendo/NES/Boards/AxROM.cs" SubType="Code" />
|
||||
<Compile Update="Consoles/Nintendo/NES/Boards/CPROM.cs" SubType="Code" />
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64
|
||||
{
|
||||
public partial class Ares64 : ISettable<object, Ares64.Ares64SyncSettings>
|
||||
{
|
||||
private Ares64SyncSettings _syncSettings;
|
||||
|
||||
public object GetSettings() => null;
|
||||
|
||||
public Ares64SyncSettings GetSyncSettings() => _syncSettings.Clone();
|
||||
|
||||
public PutSettingsDirtyBits PutSettings(object o) => PutSettingsDirtyBits.None;
|
||||
|
||||
public PutSettingsDirtyBits PutSyncSettings(Ares64SyncSettings o)
|
||||
{
|
||||
var ret = Ares64SyncSettings.NeedsReboot(_syncSettings, o);
|
||||
_syncSettings = o;
|
||||
return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None;
|
||||
}
|
||||
|
||||
public class Ares64SyncSettings
|
||||
{
|
||||
[DisplayName("Player 1 Controller")]
|
||||
[Description("")]
|
||||
[DefaultValue(LibAres64.ControllerType.Mempak)]
|
||||
public LibAres64.ControllerType P1Controller { get; set; }
|
||||
|
||||
[DisplayName("Player 2 Controller")]
|
||||
[Description("")]
|
||||
[DefaultValue(LibAres64.ControllerType.Unplugged)]
|
||||
public LibAres64.ControllerType P2Controller { get; set; }
|
||||
|
||||
[DisplayName("Player 3 Controller")]
|
||||
[Description("")]
|
||||
[DefaultValue(LibAres64.ControllerType.Unplugged)]
|
||||
public LibAres64.ControllerType P3Controller { get; set; }
|
||||
|
||||
[DisplayName("Player 4 Controller")]
|
||||
[Description("")]
|
||||
[DefaultValue(LibAres64.ControllerType.Unplugged)]
|
||||
public LibAres64.ControllerType P4Controller { get; set; }
|
||||
|
||||
public Ares64SyncSettings() => SettingsUtil.SetDefaultValues(this);
|
||||
|
||||
public Ares64SyncSettings Clone() => MemberwiseClone() as Ares64SyncSettings;
|
||||
|
||||
public static bool NeedsReboot(Ares64SyncSettings x, Ares64SyncSettings y) => !DeepEquality.DeepEquals(x, y);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Cores.Properties;
|
||||
using BizHawk.Emulation.Cores.Waterbox;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64
|
||||
{
|
||||
[PortedCore(CoreNames.Ares64, "ares team, Near", "v126", "https://ares-emulator.github.io/", isReleased: false)]
|
||||
[ServiceNotApplicable(new[] { typeof(IDriveLight), })]
|
||||
public partial class Ares64 : WaterboxCore, IRegionable
|
||||
{
|
||||
private readonly LibAres64 _core;
|
||||
|
||||
[CoreConstructor(VSystemID.Raw.N64)]
|
||||
public Ares64(CoreLoadParameters<object, Ares64SyncSettings> lp)
|
||||
: base(lp.Comm, new Configuration
|
||||
{
|
||||
DefaultWidth = 640,
|
||||
DefaultHeight = 480,
|
||||
MaxWidth = 640,
|
||||
MaxHeight = 576,
|
||||
MaxSamples = 2048,
|
||||
DefaultFpsNumerator = 60000,
|
||||
DefaultFpsDenominator = 1001,
|
||||
SystemId = VSystemID.Raw.N64,
|
||||
})
|
||||
{
|
||||
_syncSettings = lp.SyncSettings ?? new();
|
||||
|
||||
ControllerSettings = new[]
|
||||
{
|
||||
_syncSettings.P1Controller,
|
||||
_syncSettings.P2Controller,
|
||||
_syncSettings.P3Controller,
|
||||
_syncSettings.P4Controller,
|
||||
};
|
||||
|
||||
N64Controller = CreateControllerDefinition(ControllerSettings);
|
||||
|
||||
_core = PreInit<LibAres64>(new WaterboxOptions
|
||||
{
|
||||
Filename = "ares64.wbx",
|
||||
SbrkHeapSizeKB = 2 * 1024,
|
||||
SealedHeapSizeKB = 4,
|
||||
InvisibleHeapSizeKB = 6 * 1024,
|
||||
PlainHeapSizeKB = 4,
|
||||
MmapHeapSizeKB = 512 * 1024,
|
||||
SkipCoreConsistencyCheck = CoreComm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck),
|
||||
SkipMemoryConsistencyCheck = CoreComm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck),
|
||||
});
|
||||
|
||||
var rom = lp.Roms[0].RomData;
|
||||
|
||||
Region = rom[0x3E] switch
|
||||
{
|
||||
0x44 or 0x46 or 0x49 or 0x50 or 0x53 or 0x55 or 0x58 or 0x59 => DisplayType.PAL,
|
||||
_ => DisplayType.NTSC,
|
||||
};
|
||||
|
||||
var pal = Region == DisplayType.PAL;
|
||||
|
||||
if (pal)
|
||||
{
|
||||
VsyncNumerator = 50;
|
||||
VsyncDenominator = 1;
|
||||
}
|
||||
|
||||
var pif = Util.DecompressGzipFile(new MemoryStream(pal ? Resources.PIF_PAL_ROM.Value : Resources.PIF_NTSC_ROM.Value));
|
||||
|
||||
_exe.AddReadonlyFile(pif, pal ? "pif.pal.rom" : "pif.ntsc.rom");
|
||||
_exe.AddReadonlyFile(rom, "program.rom");
|
||||
|
||||
if (!_core.Init(ControllerSettings, pal))
|
||||
{
|
||||
throw new InvalidOperationException("Init returned false!");
|
||||
}
|
||||
|
||||
_exe.RemoveReadonlyFile(pal ? "pif.pal.rom" : "pif.ntsc.rom");
|
||||
_exe.RemoveReadonlyFile("program.rom");
|
||||
|
||||
PostInit();
|
||||
DeterministicEmulation = true;
|
||||
}
|
||||
|
||||
public DisplayType Region { get; }
|
||||
|
||||
public override ControllerDefinition ControllerDefinition => N64Controller;
|
||||
|
||||
private ControllerDefinition N64Controller { get; }
|
||||
|
||||
public LibAres64.ControllerType[] ControllerSettings { get; }
|
||||
|
||||
private static ControllerDefinition CreateControllerDefinition(LibAres64.ControllerType[] controllerSettings)
|
||||
{
|
||||
var ret = new ControllerDefinition("Nintendo 64 Controller");
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (controllerSettings[i] != LibAres64.ControllerType.Unplugged)
|
||||
{
|
||||
ret.BoolButtons.Add($"P{i + 1} DPad U");
|
||||
ret.BoolButtons.Add($"P{i + 1} DPad D");
|
||||
ret.BoolButtons.Add($"P{i + 1} DPad L");
|
||||
ret.BoolButtons.Add($"P{i + 1} DPad R");
|
||||
ret.BoolButtons.Add($"P{i + 1} Start");
|
||||
ret.BoolButtons.Add($"P{i + 1} Z");
|
||||
ret.BoolButtons.Add($"P{i + 1} B");
|
||||
ret.BoolButtons.Add($"P{i + 1} A");
|
||||
ret.BoolButtons.Add($"P{i + 1} C Up");
|
||||
ret.BoolButtons.Add($"P{i + 1} C Down");
|
||||
ret.BoolButtons.Add($"P{i + 1} C Left");
|
||||
ret.BoolButtons.Add($"P{i + 1} C Right");
|
||||
ret.BoolButtons.Add($"P{i + 1} L");
|
||||
ret.BoolButtons.Add($"P{i + 1} R");
|
||||
ret.AddXYPair($"P{i + 1} {{0}} Axis", AxisPairOrientation.RightAndDown, (-32768).RangeTo(32767), 0);
|
||||
if (controllerSettings[i] == LibAres64.ControllerType.Rumblepak)
|
||||
{
|
||||
ret.HapticsChannels.Add($"P{i + 1} Rumble Pak");
|
||||
}
|
||||
}
|
||||
}
|
||||
ret.BoolButtons.Add("Reset");
|
||||
ret.BoolButtons.Add("Power");
|
||||
return ret.MakeImmutable();
|
||||
}
|
||||
|
||||
private static LibAres64.Buttons GetButtons(IController controller, int num)
|
||||
{
|
||||
LibAres64.Buttons ret = 0;
|
||||
|
||||
if (controller.IsPressed($"P{num} DPad U"))
|
||||
ret |= LibAres64.Buttons.UP;
|
||||
if (controller.IsPressed($"P{num} DPad D"))
|
||||
ret |= LibAres64.Buttons.DOWN;
|
||||
if (controller.IsPressed($"P{num} DPad L"))
|
||||
ret |= LibAres64.Buttons.LEFT;
|
||||
if (controller.IsPressed($"P{num} DPad R"))
|
||||
ret |= LibAres64.Buttons.RIGHT;
|
||||
if (controller.IsPressed($"P{num} B"))
|
||||
ret |= LibAres64.Buttons.B;
|
||||
if (controller.IsPressed($"P{num} A"))
|
||||
ret |= LibAres64.Buttons.A;
|
||||
if (controller.IsPressed($"P{num} C Up"))
|
||||
ret |= LibAres64.Buttons.C_UP;
|
||||
if (controller.IsPressed($"P{num} C Down"))
|
||||
ret |= LibAres64.Buttons.C_DOWN;
|
||||
if (controller.IsPressed($"P{num} C Left"))
|
||||
ret |= LibAres64.Buttons.C_LEFT;
|
||||
if (controller.IsPressed($"P{num} C Right"))
|
||||
ret |= LibAres64.Buttons.C_RIGHT;
|
||||
if (controller.IsPressed($"P{num} L"))
|
||||
ret |= LibAres64.Buttons.L;
|
||||
if (controller.IsPressed($"P{num} R"))
|
||||
ret |= LibAres64.Buttons.R;
|
||||
if (controller.IsPressed($"P{num} Z"))
|
||||
ret |= LibAres64.Buttons.Z;
|
||||
if (controller.IsPressed($"P{num} Start"))
|
||||
ret |= LibAres64.Buttons.START;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (ControllerSettings[i] == LibAres64.ControllerType.Rumblepak)
|
||||
{
|
||||
controller.SetHapticChannelStrength($"P{i + 1} Rumble Pak", _core.GetRumbleStatus(i) ? int.MaxValue : 0);
|
||||
}
|
||||
}
|
||||
|
||||
return new LibAres64.FrameInfo
|
||||
{
|
||||
P1Buttons = GetButtons(controller, 1),
|
||||
P1XAxis = (short)controller.AxisValue("P1 X Axis"),
|
||||
P1YAxis = (short)controller.AxisValue("P1 Y Axis"),
|
||||
|
||||
P2Buttons = GetButtons(controller, 2),
|
||||
P2XAxis = (short)controller.AxisValue("P2 X Axis"),
|
||||
P2YAxis = (short)controller.AxisValue("P2 Y Axis"),
|
||||
|
||||
P3Buttons = GetButtons(controller, 3),
|
||||
P3XAxis = (short)controller.AxisValue("P3 X Axis"),
|
||||
P3YAxis = (short)controller.AxisValue("P3 Y Axis"),
|
||||
|
||||
P4Buttons = GetButtons(controller, 4),
|
||||
P4XAxis = (short)controller.AxisValue("P4 X Axis"),
|
||||
P4YAxis = (short)controller.AxisValue("P4 Y Axis"),
|
||||
|
||||
Reset = controller.IsPressed("Reset"),
|
||||
Power = controller.IsPressed("Power"),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void FrameAdvancePost()
|
||||
{
|
||||
if (BufferWidth == 0)
|
||||
{
|
||||
BufferWidth = BufferHeight == 239 ? 320 : 640;
|
||||
}
|
||||
}
|
||||
|
||||
public override int VirtualWidth => 640;
|
||||
|
||||
public override int VirtualHeight => 480;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using BizHawk.BizInvoke;
|
||||
using BizHawk.Emulation.Cores.Waterbox;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64
|
||||
{
|
||||
public abstract class LibAres64 : LibWaterboxCore
|
||||
{
|
||||
[Flags]
|
||||
public enum Buttons : uint
|
||||
{
|
||||
UP = 1 << 0,
|
||||
DOWN = 1 << 1,
|
||||
LEFT = 1 << 2,
|
||||
RIGHT = 1 << 3,
|
||||
B = 1 << 4,
|
||||
A = 1 << 5,
|
||||
C_UP = 1 << 6,
|
||||
C_DOWN = 1 << 7,
|
||||
C_LEFT = 1 << 8,
|
||||
C_RIGHT = 1 << 9,
|
||||
L = 1 << 10,
|
||||
R = 1 << 11,
|
||||
Z = 1 << 12,
|
||||
START = 1 << 13,
|
||||
}
|
||||
|
||||
public enum ControllerType : uint
|
||||
{
|
||||
Unplugged,
|
||||
Standard,
|
||||
Mempak,
|
||||
Rumblepak,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public new class FrameInfo : LibWaterboxCore.FrameInfo
|
||||
{
|
||||
public Buttons P1Buttons;
|
||||
public short P1XAxis;
|
||||
public short P1YAxis;
|
||||
|
||||
public Buttons P2Buttons;
|
||||
public short P2XAxis;
|
||||
public short P2YAxis;
|
||||
|
||||
public Buttons P3Buttons;
|
||||
public short P3XAxis;
|
||||
public short P3YAxis;
|
||||
|
||||
public Buttons P4Buttons;
|
||||
public short P4XAxis;
|
||||
public short P4YAxis;
|
||||
|
||||
public bool Reset;
|
||||
public bool Power;
|
||||
}
|
||||
|
||||
[BizImport(CC)]
|
||||
public abstract bool Init(ControllerType[] controllerSettings, bool pal);
|
||||
|
||||
[BizImport(CC)]
|
||||
public abstract bool GetRumbleStatus(int num);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ namespace BizHawk.Emulation.Cores
|
|||
public static class CoreNames
|
||||
{
|
||||
public const string A7800Hawk = "A7800Hawk";
|
||||
public const string Ares64 = "Ares64";
|
||||
public const string Atari2600Hawk = "Atari2600Hawk";
|
||||
public const string Bsnes = "BSNES";
|
||||
public const string Bsnes115 = "BSNESv115+";
|
||||
|
|
|
@ -24,5 +24,7 @@ namespace BizHawk.Emulation.Cores.Properties {
|
|||
internal static readonly Lazy<byte[]> ZX_plus2_rom = new Lazy<byte[]>(() => ReadEmbeddedByteArray("plus2.rom.gz"));
|
||||
internal static readonly Lazy<byte[]> ZX_plus2a_rom = new Lazy<byte[]>(() => ReadEmbeddedByteArray("plus2a.rom.gz"));
|
||||
internal static readonly Lazy<byte[]> TMDS = new Lazy<byte[]>(() => ReadEmbeddedByteArray("tmds.zip.gz"));
|
||||
internal static readonly Lazy<byte[]> PIF_PAL_ROM = new Lazy<byte[]>(() => ReadEmbeddedByteArray("pif.pal.rom.gz"));
|
||||
internal static readonly Lazy<byte[]> PIF_NTSC_ROM = new Lazy<byte[]>(() => ReadEmbeddedByteArray("pif.ntsc.rom.gz"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Drawing;
|
|||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64;
|
||||
using BizHawk.Emulation.Cores.Nintendo.N64;
|
||||
|
||||
namespace BizHawk.Emulation.Cores
|
||||
|
@ -14,17 +15,33 @@ namespace BizHawk.Emulation.Cores
|
|||
{
|
||||
public IEnumerable<PadSchema> GetPadSchemas(IEmulator core, Action<string> showMessageBox)
|
||||
{
|
||||
var ss = ((N64)core).GetSyncSettings();
|
||||
for (var i = 0; i < 4; i++)
|
||||
if (core is N64 n64)
|
||||
{
|
||||
if (ss.Controllers[i].IsConnected)
|
||||
var ss = n64.GetSyncSettings();
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
yield return StandardController(i + 1);
|
||||
if (ss.Controllers[i].IsConnected)
|
||||
{
|
||||
yield return StandardController(i + 1, MupenRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (core is Ares64 ares64)
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
if (ares64.ControllerSettings[i] != LibAres64.ControllerType.Unplugged)
|
||||
{
|
||||
yield return StandardController(i + 1, AresRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PadSchema StandardController(int controller)
|
||||
private static readonly Func<bool, AxisSpec> MupenRange = (bool isY) => new((-128).RangeTo(127), 0, false);
|
||||
private static readonly Func<bool, AxisSpec> AresRange = (bool isY) => new((-32768).RangeTo(32767), 0, isY);
|
||||
|
||||
private static PadSchema StandardController(int controller, Func<bool, AxisSpec> makeRange)
|
||||
{
|
||||
return new PadSchema
|
||||
{
|
||||
|
@ -47,8 +64,8 @@ namespace BizHawk.Emulation.Cores
|
|||
new ButtonSchema(194, 221, controller, "C Right") { Icon = VGamepadButtonImage.YellowArrE },
|
||||
new AnalogSchema(6, 14, $"P{controller} X Axis")
|
||||
{
|
||||
Spec = new AxisSpec((-128).RangeTo(127), 0),
|
||||
SecondarySpec = new AxisSpec((-128).RangeTo(127), 0)
|
||||
Spec = makeRange(false),
|
||||
SecondarySpec = makeRange(true)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
From c9af34027bc9cb852a4e5e96154a7bd89531a6de Mon Sep 17 00:00:00 2001
|
||||
From: Craig Topper <craig.topper@intel.com>
|
||||
Date: Wed, 16 Sep 2020 21:56:01 -0700
|
||||
Subject: [PATCH] Add __divmodti4 to match libgcc.
|
||||
|
||||
gcc has used this on x86-64 since at least version 7.
|
||||
|
||||
Reviewed By: MaskRay
|
||||
|
||||
Differential Revision: https://reviews.llvm.org/D80506
|
||||
---
|
||||
compiler-rt/lib/builtins/CMakeLists.txt | 1 +
|
||||
compiler-rt/lib/builtins/README.txt | 2 +
|
||||
compiler-rt/lib/builtins/divmodti4.c | 32 +++++++
|
||||
.../test/builtins/Unit/divmodti4_test.c | 91 +++++++++++++++++++
|
||||
4 files changed, 126 insertions(+)
|
||||
create mode 100644 compiler-rt/lib/builtins/divmodti4.c
|
||||
create mode 100644 compiler-rt/test/builtins/Unit/divmodti4_test.c
|
||||
|
||||
diff --git a/compiler-rt/lib/builtins/CMakeLists.txt b/compiler-rt/lib/builtins/CMakeLists.txt
|
||||
index 8dbe15364ab8..3c50df179764 100644
|
||||
--- a/compiler-rt/lib/builtins/CMakeLists.txt
|
||||
+++ b/compiler-rt/lib/builtins/CMakeLists.txt
|
||||
@@ -71,6 +71,7 @@ set(GENERIC_SOURCES
|
||||
divdi3.c
|
||||
divmoddi4.c
|
||||
divmodsi4.c
|
||||
+ divmodti4.c
|
||||
divsc3.c
|
||||
divsf3.c
|
||||
divsi3.c
|
||||
diff --git a/compiler-rt/lib/builtins/README.txt b/compiler-rt/lib/builtins/README.txt
|
||||
index f9e1bc805092..d66d725e7ab5 100644
|
||||
--- a/compiler-rt/lib/builtins/README.txt
|
||||
+++ b/compiler-rt/lib/builtins/README.txt
|
||||
@@ -87,6 +87,8 @@ du_int __udivmoddi4(du_int a, du_int b, du_int* rem); // a / b, *rem = a % b u
|
||||
tu_int __udivmodti4(tu_int a, tu_int b, tu_int* rem); // a / b, *rem = a % b unsigned
|
||||
su_int __udivmodsi4(su_int a, su_int b, su_int* rem); // a / b, *rem = a % b unsigned
|
||||
si_int __divmodsi4(si_int a, si_int b, si_int* rem); // a / b, *rem = a % b signed
|
||||
+di_int __divmoddi4(di_int a, di_int b, di_int* rem); // a / b, *rem = a % b signed
|
||||
+ti_int __divmodti4(ti_int a, ti_int b, ti_int* rem); // a / b, *rem = a % b signed
|
||||
|
||||
|
||||
|
||||
diff --git a/compiler-rt/lib/builtins/divmodti4.c b/compiler-rt/lib/builtins/divmodti4.c
|
||||
new file mode 100644
|
||||
index 000000000000..b243ba4ef853
|
||||
--- /dev/null
|
||||
+++ b/compiler-rt/lib/builtins/divmodti4.c
|
||||
@@ -0,0 +1,32 @@
|
||||
+//===-- divmodti4.c - Implement __divmodti4 -------------------------------===//
|
||||
+//
|
||||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
+// See https://llvm.org/LICENSE.txt for license information.
|
||||
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
+//
|
||||
+//===----------------------------------------------------------------------===//
|
||||
+//
|
||||
+// This file implements __divmodti4 for the compiler_rt library.
|
||||
+//
|
||||
+//===----------------------------------------------------------------------===//
|
||||
+
|
||||
+#include "int_lib.h"
|
||||
+
|
||||
+#ifdef CRT_HAS_128BIT
|
||||
+
|
||||
+// Returns: a / b, *rem = a % b
|
||||
+
|
||||
+COMPILER_RT_ABI ti_int __divmodti4(ti_int a, ti_int b, ti_int *rem) {
|
||||
+ const int bits_in_tword_m1 = (int)(sizeof(ti_int) * CHAR_BIT) - 1;
|
||||
+ ti_int s_a = a >> bits_in_tword_m1; // s_a = a < 0 ? -1 : 0
|
||||
+ ti_int s_b = b >> bits_in_tword_m1; // s_b = b < 0 ? -1 : 0
|
||||
+ a = (a ^ s_a) - s_a; // negate if s_a == -1
|
||||
+ b = (b ^ s_b) - s_b; // negate if s_b == -1
|
||||
+ s_b ^= s_a; // sign of quotient
|
||||
+ tu_int r;
|
||||
+ ti_int q = (__udivmodti4(a, b, &r) ^ s_b) - s_b; // negate if s_b == -1
|
||||
+ *rem = (r ^ s_a) - s_a; // negate if s_a == -1
|
||||
+ return q;
|
||||
+}
|
||||
+
|
||||
+#endif // CRT_HAS_128BIT
|
||||
diff --git a/compiler-rt/test/builtins/Unit/divmodti4_test.c b/compiler-rt/test/builtins/Unit/divmodti4_test.c
|
||||
new file mode 100644
|
||||
index 000000000000..a9f70dcf1c1e
|
||||
--- /dev/null
|
||||
+++ b/compiler-rt/test/builtins/Unit/divmodti4_test.c
|
||||
@@ -0,0 +1,91 @@
|
||||
+// RUN: %clang_builtins %s %librt -o %t && %run %t
|
||||
+// REQUIRES: librt_has_divmodti4
|
||||
+// REQUIRES: int128
|
||||
+//===-- divmodti4_test.c - Test __divmodti4 -------------------------------===//
|
||||
+//
|
||||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
+// See https://llvm.org/LICENSE.txt for license information.
|
||||
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
+//
|
||||
+//===----------------------------------------------------------------------===//
|
||||
+//
|
||||
+// This file tests __divmodti4 for the compiler_rt library.
|
||||
+//
|
||||
+//===----------------------------------------------------------------------===//
|
||||
+
|
||||
+#include "int_lib.h"
|
||||
+#include <stdio.h>
|
||||
+
|
||||
+#ifdef CRT_HAS_128BIT
|
||||
+
|
||||
+// Effects: if rem != 0, *rem = a % b
|
||||
+// Returns: a / b
|
||||
+
|
||||
+COMPILER_RT_ABI ti_int __divmodti4(ti_int a, ti_int b, ti_int* rem);
|
||||
+
|
||||
+int test__divmodti4(ti_int a, ti_int b, ti_int expected_q, ti_int expected_r) {
|
||||
+ ti_int r;
|
||||
+ ti_int q = __divmodti4(a, b, &r);
|
||||
+ if (q != expected_q || r != expected_r)
|
||||
+ {
|
||||
+ utwords at;
|
||||
+ at.all = a;
|
||||
+ utwords bt;
|
||||
+ bt.all = b;
|
||||
+ utwords expected_qt;
|
||||
+ expected_qt.all = expected_q;
|
||||
+ utwords expected_rt;
|
||||
+ expected_rt.all = expected_r;
|
||||
+ utwords qt;
|
||||
+ qt.all = q;
|
||||
+ utwords rt;
|
||||
+ rt.all = r;
|
||||
+ printf("error in __divmodti4: 0x%.16llX%.16llX / 0x%.16llX%.16llX = "
|
||||
+ "0x%.16llX%.16llX, R = 0x%.16llX%.16llX, expected 0x%.16llX%.16llX, "
|
||||
+ "0x%.16llX%.16llX\n",
|
||||
+ at.s.high, at.s.low, bt.s.high, bt.s.low, qt.s.high, qt.s.low,
|
||||
+ rt.s.high, rt.s.low, expected_qt.s.high, expected_qt.s.low,
|
||||
+ expected_rt.s.high, expected_rt.s.low);
|
||||
+ }
|
||||
+ return !(q == expected_q && r == expected_r);
|
||||
+}
|
||||
+
|
||||
+char assumption_1[sizeof(ti_int) == 2*sizeof(di_int)] = {0};
|
||||
+
|
||||
+tu_int tests[][4] =
|
||||
+{
|
||||
+{ (ti_int) 0, (ti_int) 1, (ti_int) 0, (ti_int) 0 },
|
||||
+{ (ti_int) 0, (ti_int)-1, (ti_int) 0, (ti_int) 0 },
|
||||
+{ (ti_int) 2, (ti_int) 1, (ti_int) 2, (ti_int) 0 },
|
||||
+{ (ti_int) 2, (ti_int)-1, (ti_int)-2, (ti_int) 0 },
|
||||
+{ (ti_int)-2, (ti_int) 1, (ti_int)-2, (ti_int) 0 },
|
||||
+{ (ti_int)-2, (ti_int)-1, (ti_int) 2, (ti_int) 0 },
|
||||
+{ (ti_int) 5, (ti_int) 3, (ti_int) 1, (ti_int) 2 },
|
||||
+{ (ti_int) 5, (ti_int)-3, (ti_int)-1, (ti_int) 2 },
|
||||
+{ (ti_int)-5, (ti_int) 3, (ti_int)-1, (ti_int)-2 },
|
||||
+{ (ti_int)-5, (ti_int)-3, (ti_int) 1, (ti_int)-2 },
|
||||
+{ (ti_int)0x8000000000000000LL << 64 | 0, (ti_int) 1, (ti_int)0x8000000000000000LL << 64 | 0, (ti_int)0x0LL },
|
||||
+{ (ti_int)0x8000000000000000LL << 64 | 0, (ti_int)-1, (ti_int)0x8000000000000000LL << 64 | 0, (ti_int)0x0LL },
|
||||
+{ (ti_int)0x8000000000000000LL << 64 | 0, (ti_int)-2, (ti_int)0x4000000000000000LL << 64 | 0, (ti_int)0x0LL },
|
||||
+{ (ti_int)0x8000000000000000LL << 64 | 0, (ti_int) 2, (ti_int)0xC000000000000000LL << 64 | 0, (ti_int)0x0LL },
|
||||
+{ (ti_int)0x8000000000000000LL << 64 | 0, (ti_int)-3, (ti_int)0x2AAAAAAAAAAAAAAALL << 64 | 0xAAAAAAAAAAAAAAAALL, (ti_int)-2 },
|
||||
+{ (ti_int)0x8000000000000000LL << 64 | 0, (ti_int) 3, (ti_int)0xD555555555555555LL << 64 | 0x5555555555555556LL, (ti_int)-2 },
|
||||
+};
|
||||
+
|
||||
+#endif
|
||||
+
|
||||
+int main()
|
||||
+{
|
||||
+#ifdef CRT_HAS_128BIT
|
||||
+ const unsigned N = sizeof(tests) / sizeof(tests[0]);
|
||||
+ unsigned i;
|
||||
+ for (i = 0; i < N; ++i)
|
||||
+ if (test__divmodti4(tests[i][0], tests[i][1], tests[i][2], tests[i][3]))
|
||||
+ return 1;
|
||||
+
|
||||
+
|
||||
+#else
|
||||
+ printf("skipped\n");
|
||||
+#endif
|
||||
+ return 0;
|
||||
+}
|
||||
--
|
||||
2.25.1
|
||||
|
|
@ -0,0 +1,578 @@
|
|||
#include <n64/n64.hpp>
|
||||
|
||||
#include <emulibc.h>
|
||||
#include <waterboxcore.h>
|
||||
|
||||
#define EXPORT extern "C" ECL_EXPORT
|
||||
|
||||
typedef enum
|
||||
{
|
||||
Unplugged,
|
||||
Standard,
|
||||
Mempak,
|
||||
Rumblepak,
|
||||
} ControllerType;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
UP = 1 << 0,
|
||||
DOWN = 1 << 1,
|
||||
LEFT = 1 << 2,
|
||||
RIGHT = 1 << 3,
|
||||
B = 1 << 4,
|
||||
A = 1 << 5,
|
||||
C_UP = 1 << 6,
|
||||
C_DOWN = 1 << 7,
|
||||
C_LEFT = 1 << 8,
|
||||
C_RIGHT = 1 << 9,
|
||||
L = 1 << 10,
|
||||
R = 1 << 11,
|
||||
Z = 1 << 12,
|
||||
START = 1 << 13,
|
||||
} Buttons_t;
|
||||
|
||||
struct BizPlatform : ares::Platform
|
||||
{
|
||||
auto attach(ares::Node::Object) -> void override;
|
||||
auto pak(ares::Node::Object) -> ares::VFS::Pak override;
|
||||
auto video(ares::Node::Video::Screen, const u32*, u32, u32, u32) -> void override;
|
||||
auto input(ares::Node::Input::Input) -> void override;
|
||||
|
||||
ares::VFS::Pak bizpak = new vfs::directory;
|
||||
ares::Node::Audio::Stream stream = nullptr;
|
||||
u32* videobuf = nullptr;
|
||||
u32 pitch = 0;
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
bool newframe = false;
|
||||
void (*inputcb)() = nullptr;
|
||||
bool lagged = true;
|
||||
};
|
||||
|
||||
auto BizPlatform::attach(ares::Node::Object node) -> void
|
||||
{
|
||||
if (auto stream = node->cast<ares::Node::Audio::Stream>())
|
||||
{
|
||||
stream->setResamplerFrequency(44100);
|
||||
this->stream = stream;
|
||||
}
|
||||
}
|
||||
|
||||
auto BizPlatform::pak(ares::Node::Object) -> ares::VFS::Pak
|
||||
{
|
||||
return bizpak;
|
||||
}
|
||||
|
||||
auto BizPlatform::video(ares::Node::Video::Screen screen, const u32* data, u32 pitch, u32 width, u32 height) -> void
|
||||
{
|
||||
videobuf = (u32*)data;
|
||||
this->pitch = pitch >> 2;
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
newframe = true;
|
||||
}
|
||||
|
||||
auto BizPlatform::input(ares::Node::Input::Input node) -> void
|
||||
{
|
||||
if (auto input = node->cast<ares::Node::Input::Button>())
|
||||
{
|
||||
if (input->name() == "Start")
|
||||
{
|
||||
lagged = false;
|
||||
if (inputcb) inputcb();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static ares::Node::System root;
|
||||
static BizPlatform platform;
|
||||
|
||||
static inline void HackeryDoo()
|
||||
{
|
||||
root->run();
|
||||
root->run();
|
||||
platform.newframe = false;
|
||||
f64 buf[2];
|
||||
while (platform.stream->pending()) platform.stream->read(buf);
|
||||
}
|
||||
|
||||
typedef enum
|
||||
{
|
||||
NONE,
|
||||
EEPROM512,
|
||||
EEPROM2KB,
|
||||
SRAM32KB,
|
||||
SRAM96KB,
|
||||
FLASH128KB,
|
||||
} SaveType;
|
||||
|
||||
static inline SaveType DetectSaveType(u8* rom)
|
||||
{
|
||||
string id;
|
||||
id.append((char)rom[0x3B]);
|
||||
id.append((char)rom[0x3C]);
|
||||
id.append((char)rom[0x3D]);
|
||||
|
||||
char region_code = rom[0x3E];
|
||||
u8 revision = rom[0x3F];
|
||||
|
||||
SaveType ret = NONE;
|
||||
if (id == "NTW") ret = EEPROM512;
|
||||
if (id == "NHF") ret = EEPROM512;
|
||||
if (id == "NOS") ret = EEPROM512;
|
||||
if (id == "NTC") ret = EEPROM512;
|
||||
if (id == "NER") ret = EEPROM512;
|
||||
if (id == "NAG") ret = EEPROM512;
|
||||
if (id == "NAB") ret = EEPROM512;
|
||||
if (id == "NS3") ret = EEPROM512;
|
||||
if (id == "NTN") ret = EEPROM512;
|
||||
if (id == "NBN") ret = EEPROM512;
|
||||
if (id == "NBK") ret = EEPROM512;
|
||||
if (id == "NFH") ret = EEPROM512;
|
||||
if (id == "NMU") ret = EEPROM512;
|
||||
if (id == "NBC") ret = EEPROM512;
|
||||
if (id == "NBH") ret = EEPROM512;
|
||||
if (id == "NHA") ret = EEPROM512;
|
||||
if (id == "NBM") ret = EEPROM512;
|
||||
if (id == "NBV") ret = EEPROM512;
|
||||
if (id == "NBD") ret = EEPROM512;
|
||||
if (id == "NCT") ret = EEPROM512;
|
||||
if (id == "NCH") ret = EEPROM512;
|
||||
if (id == "NCG") ret = EEPROM512;
|
||||
if (id == "NP2") ret = EEPROM512;
|
||||
if (id == "NXO") ret = EEPROM512;
|
||||
if (id == "NCU") ret = EEPROM512;
|
||||
if (id == "NCX") ret = EEPROM512;
|
||||
if (id == "NDY") ret = EEPROM512;
|
||||
if (id == "NDQ") ret = EEPROM512;
|
||||
if (id == "NDR") ret = EEPROM512;
|
||||
if (id == "NN6") ret = EEPROM512;
|
||||
if (id == "NDU") ret = EEPROM512;
|
||||
if (id == "NJM") ret = EEPROM512;
|
||||
if (id == "NFW") ret = EEPROM512;
|
||||
if (id == "NF2") ret = EEPROM512;
|
||||
if (id == "NKA") ret = EEPROM512;
|
||||
if (id == "NFG") ret = EEPROM512;
|
||||
if (id == "NGL") ret = EEPROM512;
|
||||
if (id == "NGV") ret = EEPROM512;
|
||||
if (id == "NGE") ret = EEPROM512;
|
||||
if (id == "NHP") ret = EEPROM512;
|
||||
if (id == "NPG") ret = EEPROM512;
|
||||
if (id == "NIJ") ret = EEPROM512;
|
||||
if (id == "NIC") ret = EEPROM512;
|
||||
if (id == "NFY") ret = EEPROM512;
|
||||
if (id == "NKI") ret = EEPROM512;
|
||||
if (id == "NLL") ret = EEPROM512;
|
||||
if (id == "NLR") ret = EEPROM512;
|
||||
if (id == "NKT") ret = EEPROM512;
|
||||
if (id == "CLB") ret = EEPROM512;
|
||||
if (id == "NLB") ret = EEPROM512;
|
||||
if (id == "NMW") ret = EEPROM512;
|
||||
if (id == "NML") ret = EEPROM512;
|
||||
if (id == "NTM") ret = EEPROM512;
|
||||
if (id == "NMI") ret = EEPROM512;
|
||||
if (id == "NMG") ret = EEPROM512;
|
||||
if (id == "NMO") ret = EEPROM512;
|
||||
if (id == "NMS") ret = EEPROM512;
|
||||
if (id == "NMR") ret = EEPROM512;
|
||||
if (id == "NCR") ret = EEPROM512;
|
||||
if (id == "NEA") ret = EEPROM512;
|
||||
if (id == "NPW") ret = EEPROM512;
|
||||
if (id == "NPM") ret = EEPROM512;
|
||||
if (id == "NPY") ret = EEPROM512;
|
||||
if (id == "NPT") ret = EEPROM512;
|
||||
if (id == "NRA") ret = EEPROM512;
|
||||
if (id == "NWQ") ret = EEPROM512;
|
||||
if (id == "NSU") ret = EEPROM512;
|
||||
if (id == "NSN") ret = EEPROM512;
|
||||
if (id == "NK2") ret = EEPROM512;
|
||||
if (id == "NSV") ret = EEPROM512;
|
||||
if (id == "NFX") ret = EEPROM512;
|
||||
if (id == "NFP") ret = EEPROM512;
|
||||
if (id == "NS6") ret = EEPROM512;
|
||||
if (id == "NNA") ret = EEPROM512;
|
||||
if (id == "NRS") ret = EEPROM512;
|
||||
if (id == "NSW") ret = EEPROM512;
|
||||
if (id == "NSC") ret = EEPROM512;
|
||||
if (id == "NSA") ret = EEPROM512;
|
||||
if (id == "NB6") ret = EEPROM512;
|
||||
if (id == "NSM") ret = EEPROM512;
|
||||
if (id == "NSS") ret = EEPROM512;
|
||||
if (id == "NTX") ret = EEPROM512;
|
||||
if (id == "NT6") ret = EEPROM512;
|
||||
if (id == "NTP") ret = EEPROM512;
|
||||
if (id == "NTJ") ret = EEPROM512;
|
||||
if (id == "NRC") ret = EEPROM512;
|
||||
if (id == "NTR") ret = EEPROM512;
|
||||
if (id == "NTB") ret = EEPROM512;
|
||||
if (id == "NGU") ret = EEPROM512;
|
||||
if (id == "NIR") ret = EEPROM512;
|
||||
if (id == "NVL") ret = EEPROM512;
|
||||
if (id == "NVY") ret = EEPROM512;
|
||||
if (id == "NWR") ret = EEPROM512;
|
||||
if (id == "NWC") ret = EEPROM512;
|
||||
if (id == "NAD") ret = EEPROM512;
|
||||
if (id == "NWU") ret = EEPROM512;
|
||||
if (id == "NYK") ret = EEPROM512;
|
||||
if (id == "NMZ") ret = EEPROM512;
|
||||
if (id == "NDK" && region_code == 'J') ret = EEPROM512;
|
||||
if (id == "NWT" && region_code == 'J') ret = EEPROM512;
|
||||
|
||||
if (id == "NB7") ret = EEPROM2KB;
|
||||
if (id == "NGT") ret = EEPROM2KB;
|
||||
if (id == "NFU") ret = EEPROM2KB;
|
||||
if (id == "NCW") ret = EEPROM2KB;
|
||||
if (id == "NCZ") ret = EEPROM2KB;
|
||||
if (id == "ND6") ret = EEPROM2KB;
|
||||
if (id == "NDO") ret = EEPROM2KB;
|
||||
if (id == "ND2") ret = EEPROM2KB;
|
||||
if (id == "N3D") ret = EEPROM2KB;
|
||||
if (id == "NMX") ret = EEPROM2KB;
|
||||
if (id == "NGC") ret = EEPROM2KB;
|
||||
if (id == "NIM") ret = EEPROM2KB;
|
||||
if (id == "NK4") ret = EEPROM2KB;
|
||||
if (id == "NNB") ret = EEPROM2KB;
|
||||
if (id == "NMV") ret = EEPROM2KB;
|
||||
if (id == "NM8") ret = EEPROM2KB;
|
||||
if (id == "NEV") ret = EEPROM2KB;
|
||||
if (id == "NPP") ret = EEPROM2KB;
|
||||
if (id == "NUB") ret = EEPROM2KB;
|
||||
if (id == "NPD") ret = EEPROM2KB;
|
||||
if (id == "NRZ") ret = EEPROM2KB;
|
||||
if (id == "NR7") ret = EEPROM2KB;
|
||||
if (id == "NEP") ret = EEPROM2KB;
|
||||
if (id == "NYS") ret = EEPROM2KB;
|
||||
if (id == "ND3" && region_code == 'J') ret = EEPROM2KB;
|
||||
if (id == "ND4" && region_code == 'J') ret = EEPROM2KB;
|
||||
|
||||
if (id == "NTE") ret = SRAM32KB;
|
||||
if (id == "NVB") ret = SRAM32KB;
|
||||
if (id == "CFZ") ret = SRAM32KB;
|
||||
if (id == "NFZ") ret = SRAM32KB;
|
||||
if (id == "NSI") ret = SRAM32KB;
|
||||
if (id == "NG6") ret = SRAM32KB;
|
||||
if (id == "N3H") ret = SRAM32KB;
|
||||
if (id == "NGP") ret = SRAM32KB;
|
||||
if (id == "NYW") ret = SRAM32KB;
|
||||
if (id == "NHY") ret = SRAM32KB;
|
||||
if (id == "NIB") ret = SRAM32KB;
|
||||
if (id == "NPS") ret = SRAM32KB;
|
||||
if (id == "NPA") ret = SRAM32KB;
|
||||
if (id == "NP4") ret = SRAM32KB;
|
||||
if (id == "NJ5") ret = SRAM32KB;
|
||||
if (id == "NP6") ret = SRAM32KB;
|
||||
if (id == "NPE") ret = SRAM32KB;
|
||||
if (id == "NJG") ret = SRAM32KB;
|
||||
if (id == "CZL") ret = SRAM32KB;
|
||||
if (id == "NZL") ret = SRAM32KB;
|
||||
if (id == "NKG") ret = SRAM32KB;
|
||||
if (id == "NMF") ret = SRAM32KB;
|
||||
if (id == "NRI") ret = SRAM32KB;
|
||||
if (id == "NUT") ret = SRAM32KB;
|
||||
if (id == "NUM") ret = SRAM32KB;
|
||||
if (id == "NOB") ret = SRAM32KB;
|
||||
if (id == "CPS") ret = SRAM32KB;
|
||||
if (id == "NB5") ret = SRAM32KB;
|
||||
if (id == "NRE") ret = SRAM32KB;
|
||||
if (id == "NAL") ret = SRAM32KB;
|
||||
if (id == "NT3") ret = SRAM32KB;
|
||||
if (id == "NS4") ret = SRAM32KB;
|
||||
if (id == "NA2") ret = SRAM32KB;
|
||||
if (id == "NVP") ret = SRAM32KB;
|
||||
if (id == "NWL") ret = SRAM32KB;
|
||||
if (id == "NW2") ret = SRAM32KB;
|
||||
if (id == "NWX") ret = SRAM32KB;
|
||||
if (id == "NK4" && region_code == 'J' && revision < 2) ret = SRAM32KB;
|
||||
|
||||
if (id == "CDZ") ret = SRAM96KB;
|
||||
|
||||
if (id == "NCC") ret = FLASH128KB;
|
||||
if (id == "NDA") ret = FLASH128KB;
|
||||
if (id == "NAF") ret = FLASH128KB;
|
||||
if (id == "NJF") ret = FLASH128KB;
|
||||
if (id == "NKJ") ret = FLASH128KB;
|
||||
if (id == "NZS") ret = FLASH128KB;
|
||||
if (id == "NM6") ret = FLASH128KB;
|
||||
if (id == "NCK") ret = FLASH128KB;
|
||||
if (id == "NMQ") ret = FLASH128KB;
|
||||
if (id == "NPN") ret = FLASH128KB;
|
||||
if (id == "NPF") ret = FLASH128KB;
|
||||
if (id == "NPO") ret = FLASH128KB;
|
||||
if (id == "CP2") ret = FLASH128KB;
|
||||
if (id == "NP3") ret = FLASH128KB;
|
||||
if (id == "NRH") ret = FLASH128KB;
|
||||
if (id == "NSQ") ret = FLASH128KB;
|
||||
if (id == "NT9") ret = FLASH128KB;
|
||||
if (id == "NW4") ret = FLASH128KB;
|
||||
if (id == "NDP") ret = FLASH128KB;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT bool Init(ControllerType* controllers, bool pal)
|
||||
{
|
||||
FILE* f;
|
||||
array_view<u8>* data;
|
||||
u32 len;
|
||||
string name;
|
||||
|
||||
name = pal ? "pif.pal.rom" : "pif.ntsc.rom";
|
||||
f = fopen(name, "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
len = ftell(f);
|
||||
data = new array_view<u8>(new u8[len], len);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread((void*)data->data(), 1, len, f);
|
||||
fclose(f);
|
||||
platform.bizpak->append(name, *data);
|
||||
|
||||
name = "program.rom";
|
||||
f = fopen(name, "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
len = ftell(f);
|
||||
data = new array_view<u8>(new u8[len], len);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread((void*)data->data(), 1, len, f);
|
||||
fclose(f);
|
||||
platform.bizpak->append(name, *data);
|
||||
|
||||
string region = pal ? "PAL" : "NTSC";
|
||||
platform.bizpak->setAttribute("region", region);
|
||||
|
||||
string cic = pal ? "CIC-NUS-7101" : "CIC-NUS-6102";
|
||||
u32 crc32 = Hash::CRC32({&((u8*)data->data())[0x40], 0x9C0}).value();
|
||||
if (crc32 == 0x1DEB51A9) cic = pal ? "CIC-NUS-7102" : "CIC-NUS-6101";
|
||||
if (crc32 == 0xC08E5BD6) cic = pal ? "CIC-NUS-7101" : "CIC-NUS-6102";
|
||||
if (crc32 == 0x03B8376A) cic = pal ? "CIC-NUS-7103" : "CIC-NUS-6103";
|
||||
if (crc32 == 0xCF7F41DC) cic = pal ? "CIC-NUS-7105" : "CIC-NUS-6105";
|
||||
if (crc32 == 0xD1059C6A) cic = pal ? "CIC-NUS-7106" : "CIC-NUS-6106";
|
||||
platform.bizpak->setAttribute("cic", cic);
|
||||
|
||||
SaveType save = DetectSaveType((u8*)data->data());
|
||||
if (save != NONE)
|
||||
{
|
||||
switch (save)
|
||||
{
|
||||
case EEPROM512: len = 512; name = "save.eeprom"; break;
|
||||
case EEPROM2KB: len = 2 * 1024; name = "save.eeprom"; break;
|
||||
case SRAM32KB: len = 32 * 1024; name = "save.ram"; break;
|
||||
case SRAM96KB: len = 96 * 1024; name = "save.ram"; break;
|
||||
case FLASH128KB: len = 128 * 1024; name = "save.flash"; break;
|
||||
default: return false;
|
||||
}
|
||||
data = new array_view<u8>(new u8[len], len);
|
||||
memset((void*)data->data(), 0xFF, len);
|
||||
platform.bizpak->append(name, *data);
|
||||
}
|
||||
|
||||
ares::platform = &platform;
|
||||
|
||||
if (!ares::Nintendo64::load(root, {"[Nintendo] Nintendo 64 (", region, ")"}))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto port = root->find<ares::Node::Port>("Cartridge Slot"))
|
||||
{
|
||||
port->allocate();
|
||||
port->connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (auto port = root->find<ares::Node::Port>({"Controller Port ", 1 + i}))
|
||||
{
|
||||
if (controllers[i] == Unplugged) continue;
|
||||
|
||||
auto peripheral = port->allocate("Gamepad");
|
||||
port->connect();
|
||||
|
||||
string name;
|
||||
switch (controllers[i])
|
||||
{
|
||||
case Mempak: name = "Controller Pak"; break;
|
||||
case Rumblepak: name = "Rumble Pak"; break;
|
||||
default: continue;
|
||||
}
|
||||
|
||||
if (auto port = peripheral->find<ares::Node::Port>("Pak"))
|
||||
{
|
||||
port->allocate(name);
|
||||
port->connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
root->power(false);
|
||||
HackeryDoo();
|
||||
return true;
|
||||
}
|
||||
|
||||
EXPORT bool GetRumbleStatus(u32 num)
|
||||
{
|
||||
ares::Nintendo64::Gamepad* c = nullptr;
|
||||
switch (num)
|
||||
{
|
||||
case 0: c = (ares::Nintendo64::Gamepad*)ares::Nintendo64::controllerPort1.device.data(); break;
|
||||
case 1: c = (ares::Nintendo64::Gamepad*)ares::Nintendo64::controllerPort2.device.data(); break;
|
||||
case 2: c = (ares::Nintendo64::Gamepad*)ares::Nintendo64::controllerPort3.device.data(); break;
|
||||
case 3: c = (ares::Nintendo64::Gamepad*)ares::Nintendo64::controllerPort4.device.data(); break;
|
||||
}
|
||||
return c ? c->motor->enable() : false;
|
||||
}
|
||||
|
||||
#define MAYBE_ADD_MEMORY_DOMAIN(mem, name, flags) do { \
|
||||
if (ares::Nintendo64::mem.data) \
|
||||
{ \
|
||||
m[i].Data = ares::Nintendo64::mem.data; \
|
||||
m[i].Name = name; \
|
||||
m[i].Size = ares::Nintendo64::mem.size; \
|
||||
m[i].Flags = flags | MEMORYAREA_FLAGS_YUGEENDIAN | MEMORYAREA_FLAGS_WORDSIZE4 | MEMORYAREA_FLAGS_WRITABLE; \
|
||||
i++; \
|
||||
} \
|
||||
} while (0) \
|
||||
|
||||
#define MAYBE_ADD_MEMPAK_DOMAIN(num) do { \
|
||||
if (auto c = (ares::Nintendo64::Gamepad*)ares::Nintendo64::controllerPort##num.device.data()) \
|
||||
{ \
|
||||
if (c->ram.data) \
|
||||
{ \
|
||||
m[i].Data = c->ram.data; \
|
||||
m[i].Name = "MEMPAK " #num; \
|
||||
m[i].Size = c->ram.size; \
|
||||
m[i].Flags = MEMORYAREA_FLAGS_ONEFILLED | MEMORYAREA_FLAGS_SAVERAMMABLE | MEMORYAREA_FLAGS_YUGEENDIAN | MEMORYAREA_FLAGS_WORDSIZE4 | MEMORYAREA_FLAGS_WRITABLE; \
|
||||
i++; \
|
||||
} \
|
||||
} \
|
||||
} while (0) \
|
||||
|
||||
EXPORT void GetMemoryAreas(MemoryArea *m)
|
||||
{
|
||||
int i = 0;
|
||||
MAYBE_ADD_MEMORY_DOMAIN(rdram.ram, "RDRAM", MEMORYAREA_FLAGS_PRIMARY);
|
||||
MAYBE_ADD_MEMORY_DOMAIN(cartridge.rom, "ROM", 0);
|
||||
MAYBE_ADD_MEMORY_DOMAIN(pi.rom, "PI ROM", 0);
|
||||
MAYBE_ADD_MEMORY_DOMAIN(pi.ram, "PI RAM", 0);
|
||||
MAYBE_ADD_MEMORY_DOMAIN(rsp.dmem, "RSP DMEM", 0);
|
||||
MAYBE_ADD_MEMORY_DOMAIN(rsp.imem, "RSP IMEM", 0);
|
||||
MAYBE_ADD_MEMORY_DOMAIN(cartridge.ram, "SRAM", MEMORYAREA_FLAGS_ONEFILLED | MEMORYAREA_FLAGS_SAVERAMMABLE);
|
||||
MAYBE_ADD_MEMORY_DOMAIN(cartridge.eeprom, "EEPROM", MEMORYAREA_FLAGS_ONEFILLED | MEMORYAREA_FLAGS_SAVERAMMABLE);
|
||||
MAYBE_ADD_MEMORY_DOMAIN(cartridge.flash, "FLASH", MEMORYAREA_FLAGS_ONEFILLED | MEMORYAREA_FLAGS_SAVERAMMABLE);
|
||||
MAYBE_ADD_MEMPAK_DOMAIN(1);
|
||||
MAYBE_ADD_MEMPAK_DOMAIN(2);
|
||||
MAYBE_ADD_MEMPAK_DOMAIN(3);
|
||||
MAYBE_ADD_MEMPAK_DOMAIN(4);
|
||||
}
|
||||
|
||||
// fixme: this mismatches the c# side due to some re-ordering c# is doing for some reason
|
||||
struct MyFrameInfo : public FrameInfo
|
||||
{
|
||||
Buttons_t P1Buttons;
|
||||
Buttons_t P2Buttons;
|
||||
Buttons_t P3Buttons;
|
||||
Buttons_t P4Buttons;
|
||||
|
||||
s16 P1XAxis;
|
||||
s16 P1YAxis;
|
||||
|
||||
s16 P2XAxis;
|
||||
s16 P2YAxis;
|
||||
|
||||
s16 P3XAxis;
|
||||
s16 P3YAxis;
|
||||
|
||||
s16 P4XAxis;
|
||||
s16 P4YAxis;
|
||||
|
||||
bool Reset;
|
||||
bool Power;
|
||||
};
|
||||
|
||||
#define UPDATE_CONTROLLER(NUM) do { \
|
||||
if (auto c = (ares::Nintendo64::Gamepad*)ares::Nintendo64::controllerPort##NUM.device.data()) \
|
||||
{ \
|
||||
c->x->setValue(f->P##NUM##XAxis); \
|
||||
c->y->setValue(f->P##NUM##YAxis); \
|
||||
c->up->setValue(f->P##NUM##Buttons & UP); \
|
||||
c->down->setValue(f->P##NUM##Buttons & DOWN); \
|
||||
c->left->setValue(f->P##NUM##Buttons & LEFT); \
|
||||
c->right->setValue(f->P##NUM##Buttons & RIGHT); \
|
||||
c->b->setValue(f->P##NUM##Buttons & B); \
|
||||
c->a->setValue(f->P##NUM##Buttons & A); \
|
||||
c->cameraUp->setValue(f->P##NUM##Buttons & C_UP); \
|
||||
c->cameraDown->setValue(f->P##NUM##Buttons & C_DOWN); \
|
||||
c->cameraLeft->setValue(f->P##NUM##Buttons & C_LEFT); \
|
||||
c->cameraRight->setValue(f->P##NUM##Buttons & C_RIGHT); \
|
||||
c->l->setValue(f->P##NUM##Buttons & L); \
|
||||
c->r->setValue(f->P##NUM##Buttons & R); \
|
||||
c->z->setValue(f->P##NUM##Buttons & Z); \
|
||||
c->start->setValue(f->P##NUM##Buttons & START); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
EXPORT void FrameAdvance(MyFrameInfo* f)
|
||||
{
|
||||
if (f->Power)
|
||||
{
|
||||
root->power(false);
|
||||
HackeryDoo();
|
||||
}
|
||||
else if (f->Reset)
|
||||
{
|
||||
root->power(true);
|
||||
HackeryDoo();
|
||||
}
|
||||
|
||||
UPDATE_CONTROLLER(1);
|
||||
UPDATE_CONTROLLER(2);
|
||||
UPDATE_CONTROLLER(3);
|
||||
UPDATE_CONTROLLER(4);
|
||||
|
||||
platform.lagged = true;
|
||||
|
||||
root->run();
|
||||
|
||||
f->Width = platform.width;
|
||||
f->Height = platform.height;
|
||||
if (platform.newframe)
|
||||
{
|
||||
u32* src = platform.videobuf;
|
||||
u32* dst = f->VideoBuffer;
|
||||
for (int i = 0; i < f->Height; i++)
|
||||
{
|
||||
memcpy(dst, src, f->Width * 4);
|
||||
dst += f->Width;
|
||||
src += platform.pitch;
|
||||
}
|
||||
platform.newframe = false;
|
||||
}
|
||||
|
||||
s16* soundbuf = f->SoundBuffer;
|
||||
while (platform.stream->pending())
|
||||
{
|
||||
f64 buf[2];
|
||||
platform.stream->read(buf);
|
||||
*soundbuf++ = (s16)std::clamp(buf[0] * 32768, -32768.0, 32767.0);
|
||||
*soundbuf++ = (s16)std::clamp(buf[1] * 32768, -32768.0, 32767.0);
|
||||
f->Samples++;
|
||||
}
|
||||
|
||||
f->Lagged = platform.lagged;
|
||||
}
|
||||
|
||||
EXPORT void SetInputCallback(void (*callback)())
|
||||
{
|
||||
platform.inputcb = callback;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
NEED_LIBCO := 1
|
||||
|
||||
ARES_PATH = $(ROOT_DIR)/ares/ares
|
||||
MAME_PATH = $(ROOT_DIR)/ares/thirdparty/mame
|
||||
|
||||
CXXFLAGS := -std=c++17 -msse4.2 \
|
||||
-I../libco -I.$(ROOT_DIR)/ares/ -I.$(ROOT_DIR)/ares/thirdparty/ -I.$(ARES_PATH) \
|
||||
-Werror=int-to-pointer-cast -Wno-unused-but-set-variable \
|
||||
-Wno-parentheses -Wno-reorder -Wno-unused-variable \
|
||||
-Wno-sign-compare -Wno-switch -Wno-unused-local-typedefs \
|
||||
-fno-strict-aliasing -fwrapv -fno-operator-names \
|
||||
-I.$(MAME_PATH)/devices -I.$(MAME_PATH)/emu \
|
||||
-I.$(MAME_PATH)/lib/util -I.$(MAME_PATH)/mame \
|
||||
-I.$(MAME_PATH)/osd -DMAME_RDP -DLSB_FIRST -DPTR64 -DSDLMAME_EMSCRIPTEN
|
||||
|
||||
TARGET = ares64.wbx
|
||||
|
||||
SRCS_PROCESSORS = \
|
||||
$(ARES_PATH)/component/processor/sm5k/sm5k.cpp
|
||||
|
||||
SRCS_ARES = \
|
||||
$(ARES_PATH)/ares/ares.cpp \
|
||||
$(ARES_PATH)/ares/memory/fixed-allocator.cpp
|
||||
|
||||
SRCS_N64 = \
|
||||
$(ARES_PATH)/n64/memory/memory.cpp \
|
||||
$(ARES_PATH)/n64/system/system.cpp \
|
||||
$(ARES_PATH)/n64/cartridge/cartridge.cpp \
|
||||
$(ARES_PATH)/n64/controller/controller.cpp \
|
||||
$(ARES_PATH)/n64/dd/dd.cpp \
|
||||
$(ARES_PATH)/n64/sp/sp.cpp \
|
||||
$(ARES_PATH)/n64/dp/dp.cpp \
|
||||
$(ARES_PATH)/n64/mi/mi.cpp \
|
||||
$(ARES_PATH)/n64/vi/vi.cpp \
|
||||
$(ARES_PATH)/n64/ai/ai.cpp \
|
||||
$(ARES_PATH)/n64/pi/pi.cpp \
|
||||
$(ARES_PATH)/n64/ri/ri.cpp \
|
||||
$(ARES_PATH)/n64/si/si.cpp \
|
||||
$(ARES_PATH)/n64/rdram/rdram.cpp \
|
||||
$(ARES_PATH)/n64/cpu/cpu.cpp \
|
||||
$(ARES_PATH)/n64/rdp/rdp.cpp \
|
||||
$(ARES_PATH)/n64/rsp/rsp.cpp
|
||||
|
||||
SRCS_MAME = \
|
||||
$(MAME_PATH)/emu/emucore.cpp \
|
||||
$(MAME_PATH)/lib/util/delegate.cpp \
|
||||
$(MAME_PATH)/lib/util/strformat.cpp \
|
||||
$(MAME_PATH)/mame/video/n64.cpp \
|
||||
$(MAME_PATH)/mame/video/pin64.cpp \
|
||||
$(MAME_PATH)/mame/video/rdpblend.cpp \
|
||||
$(MAME_PATH)/mame/video/rdptpipe.cpp \
|
||||
$(MAME_PATH)/osd/osdcore.cpp \
|
||||
$(MAME_PATH)/osd/osdsync.cpp
|
||||
|
||||
SRCS = $(SRCS_PROCESSORS) $(SRCS_ARES) $(SRCS_N64) $(SRCS_MAME) BizInterface.cpp
|
||||
|
||||
include ../common.mak
|
|
@ -0,0 +1,78 @@
|
|||
----------------------------------------------------------------------
|
||||
ares
|
||||
|
||||
Copyright (c) 2004-2021 ares team, Near et al
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Stack-less Just-In-Time compiler
|
||||
|
||||
Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
MAME
|
||||
Copyright (c) 1997-2021 MAMEdev and contributors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
----------------------------------------------------------------------
|
|
@ -0,0 +1,11 @@
|
|||
#include <ares/ares.hpp>
|
||||
#include <ares/debug/debug.cpp>
|
||||
#include <ares/node/node.cpp>
|
||||
#include <ares/resource/resource.cpp>
|
||||
|
||||
namespace ares {
|
||||
|
||||
Platform* platform = nullptr;
|
||||
bool _runAhead = false;
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include <emulibc.h>
|
||||
|
||||
#include <libco.h>
|
||||
#include <sljit.h>
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/adaptive-array.hpp>
|
||||
#include <nall/any.hpp>
|
||||
#include <nall/array.hpp>
|
||||
#include <nall/bump-allocator.hpp>
|
||||
#include <nall/chrono.hpp>
|
||||
#include <nall/directory.hpp>
|
||||
#include <nall/dl.hpp>
|
||||
#include <nall/endian.hpp>
|
||||
#include <nall/image.hpp>
|
||||
#include <nall/literals.hpp>
|
||||
#include <nall/priority-queue.hpp>
|
||||
#include <nall/queue.hpp>
|
||||
#include <nall/random.hpp>
|
||||
#include <nall/serializer.hpp>
|
||||
#include <nall/set.hpp>
|
||||
#include <nall/shared-pointer.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/terminal.hpp>
|
||||
#include <nall/thread.hpp>
|
||||
#include <nall/traits.hpp>
|
||||
#include <nall/unique-pointer.hpp>
|
||||
#include <nall/variant.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
#include <nall/vfs.hpp>
|
||||
#include <nall/cd.hpp>
|
||||
#include <nall/dsp/iir/one-pole.hpp>
|
||||
#include <nall/dsp/iir/biquad.hpp>
|
||||
#include <nall/dsp/resampler/cubic.hpp>
|
||||
#include <nall/hash/crc32.hpp>
|
||||
#include <nall/hash/sha256.hpp>
|
||||
using namespace nall;
|
||||
|
||||
namespace ares {
|
||||
static const string Name = "ares";
|
||||
static const string Version = "126";
|
||||
static const string Copyright = "ares team, Near";
|
||||
static const string License = "ISC";
|
||||
static const string LicenseURI = "https://opensource.org/licenses/ISC";
|
||||
static const string Website = "ares-emulator.github.io";
|
||||
static const string WebsiteURI = "https://ares-emulator.github.io";
|
||||
|
||||
//incremented only when serialization format changes
|
||||
static const u32 SerializerSignature = 0x31545342; //"BST1" (little-endian)
|
||||
static const string SerializerVersion = "125";
|
||||
|
||||
namespace VFS {
|
||||
using Pak = shared_pointer<vfs::directory>;
|
||||
using File = shared_pointer<vfs::file>;
|
||||
}
|
||||
|
||||
namespace Video {
|
||||
static constexpr bool Threaded = false;
|
||||
}
|
||||
|
||||
namespace Constants {
|
||||
namespace Colorburst {
|
||||
static constexpr f64 NTSC = 315.0 / 88.0 * 1'000'000.0;
|
||||
static constexpr f64 PAL = 283.75 * 15'625.0 + 25.0;
|
||||
}
|
||||
}
|
||||
|
||||
extern bool _runAhead;
|
||||
inline auto runAhead() -> bool { return _runAhead; }
|
||||
inline auto setRunAhead(bool runAhead) -> void { _runAhead = runAhead; }
|
||||
}
|
||||
|
||||
#include <ares/types.hpp>
|
||||
#include <ares/random.hpp>
|
||||
#include <ares/debug/debug.hpp>
|
||||
#include <ares/node/node.hpp>
|
||||
#include <ares/platform.hpp>
|
||||
#include <ares/memory/fixed-allocator.hpp>
|
||||
#include <ares/memory/readable.hpp>
|
||||
#include <ares/memory/writable.hpp>
|
||||
#include <ares/resource/resource.hpp>
|
|
@ -0,0 +1,45 @@
|
|||
namespace ares {
|
||||
|
||||
Debug _debug;
|
||||
|
||||
auto Debug::reset() -> void {
|
||||
_totalNotices = 0;
|
||||
_unhandledNotices.reset();
|
||||
_unimplementedNotices.reset();
|
||||
_unusualNotices.reset();
|
||||
_unverifiedNotices.reset();
|
||||
}
|
||||
|
||||
auto Debug::_unhandled(const string& text) -> void {
|
||||
if(_unhandledNotices.find(text)) return;
|
||||
if(_totalNotices++ > 256) return;
|
||||
_unhandledNotices.append(text);
|
||||
|
||||
print(terminal::color::yellow("[unhandled] "), text, "\n");
|
||||
}
|
||||
|
||||
auto Debug::_unimplemented(const string& text) -> void {
|
||||
if(_unimplementedNotices.find(text)) return;
|
||||
if(_totalNotices++ > 256) return;
|
||||
_unimplementedNotices.append(text);
|
||||
|
||||
print(terminal::color::magenta("[unimplemented] "), text, "\n");
|
||||
}
|
||||
|
||||
auto Debug::_unusual(const string& text) -> void {
|
||||
if(_unusualNotices.find(text)) return;
|
||||
if(_totalNotices++ > 256) return;
|
||||
_unusualNotices.append(text);
|
||||
|
||||
print(terminal::color::cyan("[unusual] "), text, "\n");
|
||||
}
|
||||
|
||||
auto Debug::_unverified(const string& text) -> void {
|
||||
if(_unverifiedNotices.find(text)) return;
|
||||
if(_totalNotices++ > 256) return;
|
||||
_unverifiedNotices.append(text);
|
||||
|
||||
print(terminal::color::gray("[unverified] "), text, "\n");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
namespace ares {
|
||||
|
||||
struct Debug {
|
||||
auto reset() -> void;
|
||||
|
||||
template<typename... P> auto unhandled(P&&... p) -> void {
|
||||
return _unhandled({forward<P>(p)...});
|
||||
}
|
||||
|
||||
template<typename... P> auto unimplemented(P&&... p) -> void {
|
||||
return _unimplemented({forward<P>(p)...});
|
||||
}
|
||||
|
||||
template<typename... P> auto unusual(P&&... p) -> void {
|
||||
return _unusual({forward<P>(p)...});
|
||||
}
|
||||
|
||||
template<typename... P> auto unverified(P&&... p) -> void {
|
||||
return _unverified({forward<P>(p)...});
|
||||
}
|
||||
|
||||
private:
|
||||
auto _unhandled(const string&) -> void;
|
||||
auto _unimplemented(const string&) -> void;
|
||||
auto _unusual(const string&) -> void;
|
||||
auto _unverified(const string&) -> void;
|
||||
|
||||
u64 _totalNotices = 0;
|
||||
vector<string> _unhandledNotices;
|
||||
vector<string> _unimplementedNotices;
|
||||
vector<string> _unusualNotices;
|
||||
vector<string> _unverifiedNotices;
|
||||
};
|
||||
|
||||
extern Debug _debug;
|
||||
|
||||
}
|
||||
|
||||
#define debug(function, ...) if constexpr(1) ares::_debug.function(__VA_ARGS__)
|
|
@ -0,0 +1,4 @@
|
|||
#include <ares/scheduler/thread.hpp>
|
||||
#include <ares/scheduler/scheduler.hpp>
|
||||
#include <ares/scheduler/thread.cpp>
|
||||
#include <ares/scheduler/scheduler.cpp>
|
|
@ -0,0 +1,22 @@
|
|||
#include <ares/ares.hpp>
|
||||
|
||||
namespace ares::Memory {
|
||||
|
||||
#if defined(PLATFORM_MACOS) && defined(ARCHITECTURE_ARM64)
|
||||
//stub for unsupported platforms
|
||||
FixedAllocator::FixedAllocator() {
|
||||
}
|
||||
#else
|
||||
alignas(4096) u8 fixedBuffer[128_MiB];
|
||||
|
||||
FixedAllocator::FixedAllocator() {
|
||||
_allocator.resize(sizeof(fixedBuffer), 0, fixedBuffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto FixedAllocator::get() -> bump_allocator& {
|
||||
static FixedAllocator allocator;
|
||||
return allocator._allocator;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
namespace ares::Memory {
|
||||
|
||||
struct FixedAllocator {
|
||||
static auto get() -> bump_allocator&;
|
||||
|
||||
private:
|
||||
FixedAllocator();
|
||||
|
||||
bump_allocator _allocator;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
namespace ares::Memory {
|
||||
|
||||
inline auto mirror(u32 address, u32 size) -> u32 {
|
||||
if(size == 0) return 0;
|
||||
u32 base = 0;
|
||||
u32 mask = 1 << 31;
|
||||
while(address >= size) {
|
||||
while(!(address & mask)) mask >>= 1;
|
||||
address -= mask;
|
||||
if(size > mask) {
|
||||
size -= mask;
|
||||
base += mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
return base + address;
|
||||
}
|
||||
|
||||
inline auto reduce(u32 address, u32 mask) -> u32 {
|
||||
while(mask) {
|
||||
u32 bits = (mask & -mask) - 1;
|
||||
address = address >> 1 & ~bits | address & bits;
|
||||
mask = (mask & mask - 1) >> 1;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include <ares/memory/memory.hpp>
|
||||
|
||||
namespace ares::Memory {
|
||||
|
||||
template<typename T>
|
||||
struct Readable {
|
||||
~Readable() { reset(); }
|
||||
|
||||
auto reset() -> void {
|
||||
delete[] self.data;
|
||||
self.data = nullptr;
|
||||
self.size = 0;
|
||||
self.mask = 0;
|
||||
}
|
||||
|
||||
auto allocate(u32 size, T fill = (T)~0ull) -> void {
|
||||
if(!size) return reset();
|
||||
delete[] self.data;
|
||||
self.size = size;
|
||||
self.mask = bit::round(self.size) - 1;
|
||||
self.data = new T[self.mask + 1];
|
||||
memory::fill<T>(self.data, self.mask + 1, fill);
|
||||
}
|
||||
|
||||
auto fill(T fill = ~0ull) -> void {
|
||||
for(u32 address : range(self.size)) {
|
||||
self.data[address] = fill;
|
||||
}
|
||||
}
|
||||
|
||||
auto load(VFS::File fp) -> void {
|
||||
if(!self.size) allocate(fp->size());
|
||||
fp->read({self.data, min(fp->size(), self.size * sizeof(T))});
|
||||
for(u32 address = self.size; address <= self.mask; address++) {
|
||||
self.data[address] = self.data[mirror(address, self.size)];
|
||||
}
|
||||
}
|
||||
|
||||
auto save(VFS::File fp) -> void {
|
||||
fp->write({self.data, min(fp->size(), self.size * sizeof(T))});
|
||||
}
|
||||
|
||||
explicit operator bool() const { return (bool)self.data; }
|
||||
auto data() const -> const T* { return self.data; }
|
||||
auto size() const -> u32 { return self.size; }
|
||||
auto mask() const -> u32 { return self.mask; }
|
||||
|
||||
auto operator[](u32 address) const -> T { return self.data[address & self.mask]; }
|
||||
auto read(u32 address) const -> T { return self.data[address & self.mask]; }
|
||||
auto write(u32 address, T data) const -> void {}
|
||||
auto program(u32 address, T data) const -> void { self.data[address & self.mask] = data; }
|
||||
|
||||
auto begin() -> T* { return &self.data[0]; }
|
||||
auto end() -> T* { return &self.data[self.size]; }
|
||||
|
||||
auto begin() const -> const T* { return &self.data[0]; }
|
||||
auto end() const -> const T* { return &self.data[self.size]; }
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s(array_span<T>{self.data, self.size});
|
||||
}
|
||||
|
||||
//private:
|
||||
struct {
|
||||
T* data = nullptr;
|
||||
u32 size = 0;
|
||||
u32 mask = 0;
|
||||
} self;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
|
||||
#include <ares/memory/memory.hpp>
|
||||
|
||||
namespace ares::Memory {
|
||||
|
||||
template<typename T>
|
||||
struct Writable {
|
||||
~Writable() { reset(); }
|
||||
|
||||
auto reset() -> void {
|
||||
delete[] self.data;
|
||||
self.data = nullptr;
|
||||
self.size = 0;
|
||||
self.mask = 0;
|
||||
}
|
||||
|
||||
auto allocate(u32 size, T fill = (T)~0ull) -> void {
|
||||
if(!size) return reset();
|
||||
delete[] self.data;
|
||||
self.size = size;
|
||||
self.mask = bit::round(self.size) - 1;
|
||||
self.data = new T[self.mask + 1];
|
||||
memory::fill<T>(self.data, self.mask + 1, fill);
|
||||
}
|
||||
|
||||
auto fill(T fill = ~0ull) -> void {
|
||||
for(u32 address : range(self.size)) {
|
||||
self.data[address] = fill;
|
||||
}
|
||||
}
|
||||
|
||||
auto load(VFS::File fp) -> void {
|
||||
if(!self.size) allocate(fp->size());
|
||||
fp->read({self.data, min(fp->size(), self.size * sizeof(T))});
|
||||
for(u32 address = self.size; address <= self.mask; address++) {
|
||||
self.data[address] = self.data[mirror(address, self.size)];
|
||||
}
|
||||
}
|
||||
|
||||
auto save(VFS::File fp) -> void {
|
||||
fp->write({self.data, min(fp->size(), self.size * sizeof(T))});
|
||||
}
|
||||
|
||||
explicit operator bool() const { return (bool)self.data; }
|
||||
auto data() -> T* { return self.data; }
|
||||
auto data() const -> const T* { return self.data; }
|
||||
auto size() const -> u32 { return self.size; }
|
||||
auto mask() const -> u32 { return self.mask; }
|
||||
|
||||
auto operator[](u32 address) -> T& { return self.data[address & self.mask]; }
|
||||
auto operator[](u32 address) const -> T { return self.data[address & self.mask]; }
|
||||
auto read(u32 address) const -> T { return self.data[address & self.mask]; }
|
||||
auto write(u32 address, T data) -> void { self.data[address & self.mask] = data; }
|
||||
auto program(u32 address, T data) -> void { self.data[address & self.mask] = data; }
|
||||
|
||||
auto begin() -> T* { return &self.data[0]; }
|
||||
auto end() -> T* { return &self.data[self.size]; }
|
||||
|
||||
auto begin() const -> const T* { return &self.data[0]; }
|
||||
auto end() const -> const T* { return &self.data[self.size]; }
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s(array_span<T>{self.data, self.size});
|
||||
}
|
||||
|
||||
private:
|
||||
struct {
|
||||
T* data = nullptr;
|
||||
u32 size = 0;
|
||||
u32 mask = 0;
|
||||
} self;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
struct Attribute {
|
||||
Attribute(const string& name, const any& value = {}) : name(name), value(value) {}
|
||||
auto operator==(const Attribute& source) const -> bool { return name == source.name; }
|
||||
auto operator< (const Attribute& source) const -> bool { return name < source.name; }
|
||||
|
||||
string name;
|
||||
any value;
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
struct Audio : Object {
|
||||
DeclareClass(Audio, "audio")
|
||||
using Object::Object;
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
auto Stream::setChannels(u32 channels) -> void {
|
||||
_channels.reset();
|
||||
_channels.resize(channels);
|
||||
}
|
||||
|
||||
auto Stream::setFrequency(f64 frequency) -> void {
|
||||
_frequency = frequency;
|
||||
setResamplerFrequency(_resamplerFrequency);
|
||||
}
|
||||
|
||||
auto Stream::setResamplerFrequency(f64 resamplerFrequency) -> void {
|
||||
_resamplerFrequency = resamplerFrequency;
|
||||
|
||||
for(auto& channel : _channels) {
|
||||
channel.nyquist.reset();
|
||||
channel.resampler.reset(_frequency, _resamplerFrequency);
|
||||
}
|
||||
|
||||
if(_frequency >= _resamplerFrequency * 2) {
|
||||
//add a low-pass filter to prevent aliasing during resampling
|
||||
f64 cutoffFrequency = min(25000.0, _resamplerFrequency / 2.0 - 2000.0);
|
||||
for(auto& channel : _channels) {
|
||||
u32 passes = 3;
|
||||
for(u32 pass : range(passes)) {
|
||||
DSP::IIR::Biquad filter;
|
||||
f64 q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, _frequency, q);
|
||||
channel.nyquist.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::setMuted(bool muted) -> void {
|
||||
_muted = muted;
|
||||
}
|
||||
|
||||
auto Stream::resetFilters() -> void {
|
||||
for(auto& channel : _channels) {
|
||||
channel.filters.reset();
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addLowPassFilter(f64 cutoffFrequency, u32 order, u32 passes) -> void {
|
||||
for(auto& channel : _channels) {
|
||||
for(u32 pass : range(passes)) {
|
||||
if(order == 1) {
|
||||
Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First};
|
||||
filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, _frequency);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
if(order == 2) {
|
||||
Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second};
|
||||
f64 q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, _frequency, q);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addHighPassFilter(f64 cutoffFrequency, u32 order, u32 passes) -> void {
|
||||
for(auto& channel : _channels) {
|
||||
for(u32 pass : range(passes)) {
|
||||
if(order == 1) {
|
||||
Filter filter{Filter::Mode::OnePole, Filter::Type::HighPass, Filter::Order::First};
|
||||
filter.onePole.reset(DSP::IIR::OnePole::Type::HighPass, cutoffFrequency, _frequency);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
if(order == 2) {
|
||||
Filter filter{Filter::Mode::Biquad, Filter::Type::HighPass, Filter::Order::Second};
|
||||
f64 q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
||||
filter.biquad.reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, _frequency, q);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addLowShelfFilter(f64 cutoffFrequency, u32 order, f64 gain, f64 slope) -> void {
|
||||
for(auto& channel : _channels) {
|
||||
if(order == 2) {
|
||||
Filter filter{Filter::Mode::Biquad, Filter::Type::LowShelf, Filter::Order::Second};
|
||||
f64 q = DSP::IIR::Biquad::shelf(gain, slope);
|
||||
filter.biquad.reset(DSP::IIR::Biquad::Type::LowShelf, cutoffFrequency, _frequency, q);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::addHighShelfFilter(f64 cutoffFrequency, u32 order, f64 gain, f64 slope) -> void {
|
||||
for(auto& channel : _channels) {
|
||||
if(order == 2) {
|
||||
Filter filter{Filter::Mode::Biquad, Filter::Type::HighShelf, Filter::Order::Second};
|
||||
f64 q = DSP::IIR::Biquad::shelf(gain, slope);
|
||||
filter.biquad.reset(DSP::IIR::Biquad::Type::HighShelf, cutoffFrequency, _frequency, q);
|
||||
channel.filters.append(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::pending() const -> bool {
|
||||
return _channels && _channels[0].resampler.pending();
|
||||
}
|
||||
|
||||
auto Stream::read(f64 samples[]) -> u32 {
|
||||
for(u32 c : range(_channels.size())) {
|
||||
samples[c] = _channels[c].resampler.read() * !muted();
|
||||
}
|
||||
return _channels.size();
|
||||
}
|
||||
|
||||
auto Stream::write(const f64 samples[]) -> void {
|
||||
for(u32 c : range(_channels.size())) {
|
||||
f64 sample = samples[c] + 1e-25; //constant offset used to suppress denormals
|
||||
for(auto& filter : _channels[c].filters) {
|
||||
switch(filter.mode) {
|
||||
case Filter::Mode::OnePole: sample = filter.onePole.process(sample); break;
|
||||
case Filter::Mode::Biquad: sample = filter.biquad.process(sample); break;
|
||||
}
|
||||
}
|
||||
for(auto& filter : _channels[c].nyquist) {
|
||||
sample = filter.process(sample);
|
||||
}
|
||||
_channels[c].resampler.write(sample);
|
||||
}
|
||||
|
||||
//if there are samples pending, then alert the frontend to possibly process them.
|
||||
//this will generally happen when every audio stream has pending samples to be mixed.
|
||||
if(pending()) platform->audio(shared());
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
struct Stream : Audio {
|
||||
DeclareClass(Stream, "audio.stream")
|
||||
using Audio::Audio;
|
||||
|
||||
auto channels() const -> u32 { return _channels.size(); }
|
||||
auto frequency() const -> f64 { return _frequency; }
|
||||
auto resamplerFrequency() const -> f64 { return _resamplerFrequency; }
|
||||
auto muted() const -> bool { return _muted; }
|
||||
|
||||
auto setChannels(u32 channels) -> void;
|
||||
auto setFrequency(f64 frequency) -> void;
|
||||
auto setResamplerFrequency(f64 resamplerFrequency) -> void;
|
||||
auto setMuted(bool muted) -> void;
|
||||
|
||||
auto resetFilters() -> void;
|
||||
auto addLowPassFilter(f64 cutoffFrequency, u32 order, u32 passes = 1) -> void;
|
||||
auto addHighPassFilter(f64 cutoffFrequency, u32 order, u32 passes = 1) -> void;
|
||||
auto addLowShelfFilter(f64 cutoffFrequency, u32 order, f64 gain, f64 slope) -> void;
|
||||
auto addHighShelfFilter(f64 cutoffFrequency, u32 order, f64 gain, f64 slope) -> void;
|
||||
|
||||
auto pending() const -> bool;
|
||||
auto read(f64 samples[]) -> u32;
|
||||
auto write(const f64 samples[]) -> void;
|
||||
|
||||
template<typename... P>
|
||||
auto frame(P&&... p) -> void {
|
||||
if(runAhead()) return;
|
||||
f64 samples[sizeof...(p)] = {forward<P>(p)...};
|
||||
write(samples);
|
||||
}
|
||||
|
||||
protected:
|
||||
struct Filter {
|
||||
enum class Mode : u32 { OnePole, Biquad } mode;
|
||||
enum class Type : u32 { None, LowPass, HighPass, LowShelf, HighShelf } type;
|
||||
enum class Order : u32 { None, First, Second } order;
|
||||
DSP::IIR::OnePole onePole;
|
||||
DSP::IIR::Biquad biquad;
|
||||
};
|
||||
struct Channel {
|
||||
vector<Filter> filters;
|
||||
vector<DSP::IIR::Biquad> nyquist;
|
||||
DSP::Resampler::Cubic resampler;
|
||||
};
|
||||
vector<Channel> _channels;
|
||||
f64 _frequency = 48000.0;
|
||||
f64 _resamplerFrequency = 48000.0;
|
||||
bool _muted = false;
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
//horrible implementation of run-time introspection:
|
||||
//allow converting a unique class string to a derived Node type.
|
||||
|
||||
struct Class {
|
||||
struct Instance {
|
||||
const string identifier;
|
||||
const function<Node::Object ()> create;
|
||||
};
|
||||
|
||||
static auto classes() -> vector<Instance>& {
|
||||
static vector<Instance> classes;
|
||||
return classes;
|
||||
}
|
||||
|
||||
template<typename T> static auto register() -> void {
|
||||
if(!classes().find([&](auto instance) { return instance.identifier == T::identifier(); })) {
|
||||
classes().append({T::identifier(), &T::create});
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static auto create(string identifier) -> Node::Object {
|
||||
if(auto index = classes().find([&](auto instance) { return instance.identifier == identifier; })) {
|
||||
return classes()[*index].create();
|
||||
}
|
||||
if(identifier == "Object") throw; //should never occur: detects unregistered classes
|
||||
return create("Object");
|
||||
}
|
||||
|
||||
template<typename T> struct Register {
|
||||
Register() { Class::register<T>(); }
|
||||
};
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
struct Component : Object {
|
||||
DeclareClass(Component, "component");
|
||||
using Object::Object;
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
struct RealTimeClock : Component {
|
||||
DeclareClass(RealTimeClock, "component.real-time-clock")
|
||||
using Component::Component;
|
||||
|
||||
auto update() -> void { if(_update) return _update(); }
|
||||
auto timestamp() const -> u64 { return _timestamp; }
|
||||
|
||||
auto setUpdate(function<void ()> update) -> void { _update = update; }
|
||||
auto setTimestamp(u64 timestamp) -> void { _timestamp = timestamp; }
|
||||
|
||||
auto synchronize(u64 timestamp = 0) -> void {
|
||||
if(!timestamp) timestamp = chrono::timestamp();
|
||||
_timestamp = timestamp;
|
||||
update();
|
||||
}
|
||||
|
||||
auto serialize(string& output, string depth) -> void override {
|
||||
Component::serialize(output, depth);
|
||||
output.append(depth, " timestamp: ", _timestamp, "\n");
|
||||
}
|
||||
|
||||
auto unserialize(Markup::Node node) -> void override {
|
||||
Component::unserialize(node);
|
||||
_timestamp = node["timestamp"].natural();
|
||||
}
|
||||
|
||||
protected:
|
||||
function<void ()> _update;
|
||||
u64 _timestamp;
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
struct Debugger : Object {
|
||||
DeclareClass(Debugger, "debugger")
|
||||
using Object::Object;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
struct Graphics : Debugger {
|
||||
DeclareClass(Graphics, "debugger.graphics")
|
||||
|
||||
Graphics(string name = {}) : Debugger(name) {
|
||||
}
|
||||
|
||||
auto width() const -> u32 { return _width; }
|
||||
auto height() const -> u32 { return _height; }
|
||||
auto capture() const -> vector<u32> { if(_capture) return _capture(); return {}; }
|
||||
|
||||
auto setSize(u32 width, u32 height) -> void { _width = width, _height = height; }
|
||||
auto setCapture(function<vector<u32> ()> capture) -> void { _capture = capture; }
|
||||
|
||||
auto serialize(string& output, string depth) -> void override {
|
||||
Debugger::serialize(output, depth);
|
||||
}
|
||||
|
||||
auto unserialize(Markup::Node node) -> void override {
|
||||
Debugger::unserialize(node);
|
||||
}
|
||||
|
||||
protected:
|
||||
u32 _width = 0;
|
||||
u32 _height = 0;
|
||||
function<vector<u32> ()> _capture;
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
struct Memory : Debugger {
|
||||
DeclareClass(Memory, "debugger.memory")
|
||||
|
||||
Memory(string name = {}) : Debugger(name) {
|
||||
}
|
||||
|
||||
auto size() const -> u32 { return _size; }
|
||||
auto read(u32 address) const -> n8 { if(_read) return _read(address); return 0; }
|
||||
auto write(u32 address, u8 data) const -> void { if(_write) return _write(address, data); }
|
||||
|
||||
auto setSize(u32 size) -> void { _size = size; }
|
||||
auto setRead(function<u8 (u32)> read) -> void { _read = read; }
|
||||
auto setWrite(function<void (u32, u8)> write) -> void { _write = write; }
|
||||
|
||||
auto serialize(string& output, string depth) -> void override {
|
||||
Debugger::serialize(output, depth);
|
||||
}
|
||||
|
||||
auto unserialize(Markup::Node node) -> void override {
|
||||
Debugger::unserialize(node);
|
||||
}
|
||||
|
||||
protected:
|
||||
u32 _size = 0;
|
||||
function<u8 (u32)> _read;
|
||||
function<void (u32, u8)> _write;
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
struct Properties : Debugger {
|
||||
DeclareClass(Properties, "debugger.properties")
|
||||
|
||||
Properties(string name = {}) : Debugger(name) {
|
||||
}
|
||||
|
||||
auto query() const -> string { if(_query) return _query(); return {}; }
|
||||
|
||||
auto setQuery(function<string ()> query) -> void { _query = query; }
|
||||
|
||||
auto serialize(string& output, string depth) -> void override {
|
||||
Debugger::serialize(output, depth);
|
||||
}
|
||||
|
||||
auto unserialize(Markup::Node node) -> void override {
|
||||
Debugger::unserialize(node);
|
||||
}
|
||||
|
||||
protected:
|
||||
function<string ()> _query;
|
||||
};
|
|
@ -0,0 +1,125 @@
|
|||
struct Instruction : Tracer {
|
||||
DeclareClass(Instruction, "debugger.tracer.instruction")
|
||||
|
||||
Instruction(string name = {}, string component = {}) : Tracer(name, component) {
|
||||
setMask(_mask);
|
||||
setDepth(_depth);
|
||||
}
|
||||
|
||||
auto addressBits() const -> u32 { return _addressBits; }
|
||||
auto addressMask() const -> u32 { return _addressMask; }
|
||||
auto mask() const -> bool { return _mask; }
|
||||
auto depth() const -> u32 { return _depth; }
|
||||
|
||||
auto setAddressBits(u32 addressBits, u32 addressMask = 0) -> void {
|
||||
_addressBits = addressBits;
|
||||
_addressMask = addressMask;
|
||||
}
|
||||
|
||||
auto setMask(bool mask) -> void {
|
||||
_mask = mask;
|
||||
}
|
||||
|
||||
auto setDepth(u32 depth) -> void {
|
||||
_depth = depth;
|
||||
_history.reset();
|
||||
_history.resize(depth);
|
||||
for(auto& history : _history) history = ~0;
|
||||
}
|
||||
|
||||
auto address(u32 address) -> bool {
|
||||
address &= (1ull << _addressBits) - 1; //mask upper bits of address
|
||||
_address = address;
|
||||
address >>= _addressMask; //clip unneeded alignment bits (to reduce _masks size)
|
||||
|
||||
if(_mask && updateMasks()) {
|
||||
if(_masks[address >> 3] & 1 << (address & 7)) return false; //do not trace twice
|
||||
_masks[address >> 3] |= 1 << (address & 7);
|
||||
}
|
||||
|
||||
if(_depth) {
|
||||
for(auto history : _history) {
|
||||
if(_address == history) {
|
||||
_omitted++;
|
||||
return false; //do not trace again if recently traced
|
||||
}
|
||||
}
|
||||
for(auto index : range(_depth - 1)) {
|
||||
_history[index] = _history[index + 1];
|
||||
}
|
||||
_history.last() = _address;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//mark an already-executed address as not executed yet for trace masking.
|
||||
//call when writing to executable RAM to support self-modifying code.
|
||||
auto invalidate(u32 address) -> void {
|
||||
if(unlikely(_mask && updateMasks())) {
|
||||
address &= (1ull << _addressBits) - 1;
|
||||
address >>= _addressMask;
|
||||
_masks[address >> 3] &= ~(1 << (address & 7));
|
||||
}
|
||||
}
|
||||
|
||||
auto notify(const string& instruction, const string& context, const string& extra = {}) -> void {
|
||||
if(!enabled()) return;
|
||||
|
||||
if(_omitted) {
|
||||
PlatformLog({
|
||||
"[Omitted: ", _omitted, "]\n"}
|
||||
);
|
||||
_omitted = 0;
|
||||
}
|
||||
|
||||
string output{
|
||||
_component, " ",
|
||||
hex(_address, _addressBits + 3 >> 2), " ",
|
||||
instruction, " ",
|
||||
context, " ",
|
||||
extra
|
||||
};
|
||||
PlatformLog({output.strip(), "\n"});
|
||||
}
|
||||
|
||||
auto serialize(string& output, string depth) -> void override {
|
||||
Tracer::serialize(output, depth);
|
||||
output.append(depth, " addressBits: ", _addressBits, "\n");
|
||||
output.append(depth, " addressMask: ", _addressMask, "\n");
|
||||
output.append(depth, " mask: ", _mask, "\n");
|
||||
output.append(depth, " depth: ", _depth, "\n");
|
||||
}
|
||||
|
||||
auto unserialize(Markup::Node node) -> void override {
|
||||
Tracer::unserialize(node);
|
||||
_addressBits = node["addressBits"].natural();
|
||||
_addressMask = node["addressMask"].natural();
|
||||
_mask = node["mask"].boolean();
|
||||
_depth = node["depth"].natural();
|
||||
|
||||
setMask(_mask);
|
||||
setDepth(_depth);
|
||||
}
|
||||
|
||||
protected:
|
||||
auto updateMasks() -> bool {
|
||||
auto size = 1ull << _addressBits >> _addressMask >> 3;
|
||||
if(!_mask || !size) return _masks.reset(), false;
|
||||
if(_masks.size() == size) return true;
|
||||
_masks.reset();
|
||||
_masks.resize(size);
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 _addressBits = 32;
|
||||
u32 _addressMask = 0;
|
||||
bool _mask = false;
|
||||
u32 _depth = 4;
|
||||
|
||||
//unserialized:
|
||||
n64 _address = 0;
|
||||
n64 _omitted = 0;
|
||||
vector<u32> _history;
|
||||
vector<u08> _masks;
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
struct Notification : Tracer {
|
||||
DeclareClass(Notification, "debugger.tracer.notification")
|
||||
|
||||
Notification(string name = {}, string component = {}) : Tracer(name, component) {
|
||||
}
|
||||
|
||||
auto notify(const string& message = {}) -> void {
|
||||
if(!enabled()) return;
|
||||
|
||||
if(message) {
|
||||
PlatformLog({_component, " ", _name, ": ", message, "\n"});
|
||||
} else {
|
||||
PlatformLog({_component, " ", _name, "\n"});
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
struct Tracer : Debugger {
|
||||
DeclareClass(Tracer, "debugger.tracer")
|
||||
|
||||
Tracer(string name = {}, string component = {}) : Debugger(name) {
|
||||
_component = component;
|
||||
}
|
||||
|
||||
auto component() const -> string { return _component; }
|
||||
auto enabled() const -> bool { return _enabled; }
|
||||
|
||||
auto setComponent(string component) -> void { _component = component; }
|
||||
auto setEnabled(bool enabled) -> void { _enabled = enabled; }
|
||||
|
||||
auto serialize(string& output, string depth) -> void override {
|
||||
Debugger::serialize(output, depth);
|
||||
output.append(depth, " component: ", _component, "\n");
|
||||
output.append(depth, " enabled: ", _enabled, "\n");
|
||||
}
|
||||
|
||||
auto unserialize(Markup::Node node) -> void override {
|
||||
Debugger::unserialize(node);
|
||||
_component = node["component"].string();
|
||||
_enabled = node["enabled"].boolean();
|
||||
}
|
||||
|
||||
protected:
|
||||
string _component;
|
||||
bool _enabled = false;
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
struct Axis : Input {
|
||||
DeclareClass(Axis, "input.axis")
|
||||
using Input::Input;
|
||||
|
||||
auto value() const -> s64 { return _value; }
|
||||
auto setValue(s64 value) -> void { _value = value; }
|
||||
|
||||
protected:
|
||||
s64 _value = 0;
|
||||
s64 _minimum = -32768;
|
||||
s64 _maximum = +32767;
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
struct Button : Input {
|
||||
DeclareClass(Button, "input.button")
|
||||
using Input::Input;
|
||||
|
||||
auto value() const -> bool { return _value; }
|
||||
auto setValue(bool value) -> void { _value = value; }
|
||||
|
||||
protected:
|
||||
bool _value = 0;
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
struct Input : Object {
|
||||
DeclareClass(Input, "input")
|
||||
using Object::Object;
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
struct Rumble : Input {
|
||||
DeclareClass(Rumble, "input.rumble")
|
||||
using Input::Input;
|
||||
|
||||
auto enable() const -> bool { return _enable; }
|
||||
auto setEnable(bool enable) -> void { _enable = enable; }
|
||||
|
||||
protected:
|
||||
bool _enable = 0;
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
struct Trigger : Input {
|
||||
DeclareClass(Trigger, "input.trigger")
|
||||
using Input::Input;
|
||||
|
||||
auto value() const -> s64 { return _value; }
|
||||
auto setValue(s64 value) -> void { _value = value; }
|
||||
|
||||
protected:
|
||||
s64 _value = 0;
|
||||
s64 _minimum = 0;
|
||||
s64 _maximum = +32767;
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
namespace ares::Core {
|
||||
namespace Video {
|
||||
#include <ares/node/video/sprite.cpp>
|
||||
#include <ares/node/video/screen.cpp>
|
||||
}
|
||||
namespace Audio {
|
||||
#include <ares/node/audio/stream.cpp>
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
namespace ares::Core {
|
||||
struct Object;
|
||||
struct System;
|
||||
struct Peripheral;
|
||||
struct Port;
|
||||
namespace Component {
|
||||
struct Component;
|
||||
struct RealTimeClock;
|
||||
}
|
||||
namespace Video {
|
||||
struct Video;
|
||||
struct Sprite;
|
||||
struct Screen;
|
||||
}
|
||||
namespace Audio {
|
||||
struct Audio;
|
||||
struct Stream;
|
||||
}
|
||||
namespace Input {
|
||||
struct Input;
|
||||
struct Button;
|
||||
struct Axis;
|
||||
struct Trigger;
|
||||
struct Rumble;
|
||||
}
|
||||
namespace Setting {
|
||||
struct Setting;
|
||||
struct Boolean;
|
||||
struct Natural;
|
||||
struct Integer;
|
||||
struct Real;
|
||||
struct String;
|
||||
}
|
||||
namespace Debugger {
|
||||
struct Debugger;
|
||||
struct Memory;
|
||||
struct Graphics;
|
||||
struct Properties;
|
||||
namespace Tracer {
|
||||
struct Tracer;
|
||||
struct Notification;
|
||||
struct Instruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ares::Node {
|
||||
using Object = shared_pointer<Core::Object>;
|
||||
using System = shared_pointer<Core::System>;
|
||||
using Peripheral = shared_pointer<Core::Peripheral>;
|
||||
using Port = shared_pointer<Core::Port>;
|
||||
namespace Component {
|
||||
using Component = shared_pointer<Core::Component::Component>;
|
||||
using RealTimeClock = shared_pointer<Core::Component::RealTimeClock>;
|
||||
}
|
||||
namespace Video {
|
||||
using Video = shared_pointer<Core::Video::Video>;
|
||||
using Sprite = shared_pointer<Core::Video::Sprite>;
|
||||
using Screen = shared_pointer<Core::Video::Screen>;
|
||||
}
|
||||
namespace Audio {
|
||||
using Audio = shared_pointer<Core::Audio::Audio>;
|
||||
using Stream = shared_pointer<Core::Audio::Stream>;
|
||||
}
|
||||
namespace Input {
|
||||
using Input = shared_pointer<Core::Input::Input>;
|
||||
using Button = shared_pointer<Core::Input::Button>;
|
||||
using Axis = shared_pointer<Core::Input::Axis>;
|
||||
using Trigger = shared_pointer<Core::Input::Trigger>;
|
||||
using Rumble = shared_pointer<Core::Input::Rumble>;
|
||||
}
|
||||
namespace Setting {
|
||||
using Setting = shared_pointer<Core::Setting::Setting>;
|
||||
using Boolean = shared_pointer<Core::Setting::Boolean>;
|
||||
using Natural = shared_pointer<Core::Setting::Natural>;
|
||||
using Integer = shared_pointer<Core::Setting::Integer>;
|
||||
using Real = shared_pointer<Core::Setting::Real>;
|
||||
using String = shared_pointer<Core::Setting::String>;
|
||||
}
|
||||
namespace Debugger {
|
||||
using Debugger = shared_pointer<Core::Debugger::Debugger>;
|
||||
using Memory = shared_pointer<Core::Debugger::Memory>;
|
||||
using Graphics = shared_pointer<Core::Debugger::Graphics>;
|
||||
using Properties = shared_pointer<Core::Debugger::Properties>;
|
||||
namespace Tracer {
|
||||
using Tracer = shared_pointer<Core::Debugger::Tracer::Tracer>;
|
||||
using Notification = shared_pointer<Core::Debugger::Tracer::Notification>;
|
||||
using Instruction = shared_pointer<Core::Debugger::Tracer::Instruction>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ares::Core {
|
||||
// <ares/platform.hpp> forward declarations
|
||||
static auto PlatformAttach(Node::Object) -> void;
|
||||
static auto PlatformDetach(Node::Object) -> void;
|
||||
static auto PlatformLog(string_view) -> void;
|
||||
|
||||
#include <ares/node/attribute.hpp>
|
||||
#include <ares/node/class.hpp>
|
||||
#include <ares/node/object.hpp>
|
||||
#include <ares/node/system.hpp>
|
||||
#include <ares/node/peripheral.hpp>
|
||||
#include <ares/node/port.hpp>
|
||||
namespace Component {
|
||||
#include <ares/node/component/component.hpp>
|
||||
#include <ares/node/component/real-time-clock.hpp>
|
||||
}
|
||||
namespace Video {
|
||||
#include <ares/node/video/video.hpp>
|
||||
#include <ares/node/video/sprite.hpp>
|
||||
#include <ares/node/video/screen.hpp>
|
||||
}
|
||||
namespace Audio {
|
||||
#include <ares/node/audio/audio.hpp>
|
||||
#include <ares/node/audio/stream.hpp>
|
||||
}
|
||||
namespace Input {
|
||||
#include <ares/node/input/input.hpp>
|
||||
#include <ares/node/input/button.hpp>
|
||||
#include <ares/node/input/axis.hpp>
|
||||
#include <ares/node/input/trigger.hpp>
|
||||
#include <ares/node/input/rumble.hpp>
|
||||
}
|
||||
namespace Setting {
|
||||
#include <ares/node/setting/setting.hpp>
|
||||
#include <ares/node/setting/boolean.hpp>
|
||||
#include <ares/node/setting/natural.hpp>
|
||||
#include <ares/node/setting/integer.hpp>
|
||||
#include <ares/node/setting/real.hpp>
|
||||
#include <ares/node/setting/string.hpp>
|
||||
}
|
||||
namespace Debugger {
|
||||
#include <ares/node/debugger/debugger.hpp>
|
||||
#include <ares/node/debugger/memory.hpp>
|
||||
#include <ares/node/debugger/graphics.hpp>
|
||||
#include <ares/node/debugger/properties.hpp>
|
||||
namespace Tracer {
|
||||
#include <ares/node/debugger/tracer/tracer.hpp>
|
||||
#include <ares/node/debugger/tracer/notification.hpp>
|
||||
#include <ares/node/debugger/tracer/instruction.hpp>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ares::Node {
|
||||
static inline auto create(string identifier) -> Object {
|
||||
return Core::Class::create(identifier);
|
||||
}
|
||||
|
||||
static inline auto serialize(Object node) -> string {
|
||||
if(!node) return {};
|
||||
string result;
|
||||
node->serialize(result, {});
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline auto unserialize(string markup) -> Object {
|
||||
auto document = BML::unserialize(markup);
|
||||
if(!document) return {};
|
||||
auto node = Core::Class::create(document["node"].string());
|
||||
node->unserialize(document["node"]);
|
||||
return node;
|
||||
}
|
||||
|
||||
static inline auto parent(Object child) -> Object {
|
||||
if(!child || !child->parent()) return {};
|
||||
if(auto parent = child->parent().acquire()) return parent;
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline auto find(Object from, string name) -> Object {
|
||||
if(!from) return {};
|
||||
if(auto object = from->find<T>(name)) return object;
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline auto enumerate(Object from) -> vector<T> {
|
||||
vector<T> objects;
|
||||
if(from) from->enumerate<T>(objects);
|
||||
return objects;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
//identifier() is static, allowing template<typename T> to access via T::identifier()
|
||||
//identity() is virtual, allowing T* to access via T->identity()
|
||||
|
||||
#define DeclareClass(Type, Name) \
|
||||
static auto identifier() -> string { return Name; } \
|
||||
static auto create() -> Node::Object { return new Type; } \
|
||||
auto identity() const -> string override { return Name; } \
|
||||
private: static inline Class::Register<Type> register; public: \
|
||||
|
||||
struct Object : shared_pointer_this<Object> {
|
||||
static auto identifier() -> string { return "Object"; }
|
||||
static auto create() -> Node::Object { return new Object; }
|
||||
virtual auto identity() const -> string { return "Object"; }
|
||||
private: static inline Class::Register<Object> register; public:
|
||||
//DeclareClass(Object, "object")
|
||||
|
||||
Object(string name = {}) : _name(name) {}
|
||||
virtual ~Object() = default;
|
||||
|
||||
auto name() const -> string { return _name; }
|
||||
auto parent() const -> shared_pointer_weak<Object> { return _parent; }
|
||||
|
||||
auto setName(string_view name) -> void { _name = name; }
|
||||
|
||||
auto prepend(Node::Object node) -> Node::Object {
|
||||
if(auto found = find(node)) return found;
|
||||
_nodes.prepend(node);
|
||||
node->_parent = shared();
|
||||
PlatformAttach(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
template<typename T, typename... P>
|
||||
auto prepend(P&&... p) -> Node::Object {
|
||||
using Type = typename T::type;
|
||||
return prepend(shared_pointer<Type>::create(forward<P>(p)...));
|
||||
}
|
||||
|
||||
auto append(Node::Object node) -> Node::Object {
|
||||
if(auto found = find(node)) return found;
|
||||
_nodes.append(node);
|
||||
node->_parent = shared();
|
||||
PlatformAttach(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
template<typename T, typename... P>
|
||||
auto append(P&&... p) -> Node::Object {
|
||||
using Type = typename T::type;
|
||||
return append(shared_pointer<Type>::create(forward<P>(p)...));
|
||||
}
|
||||
|
||||
auto remove(Node::Object node) -> void {
|
||||
if(auto index = _nodes.find(node)) {
|
||||
PlatformDetach(node);
|
||||
node->reset();
|
||||
node->_parent.reset();
|
||||
_nodes.remove(*index);
|
||||
}
|
||||
}
|
||||
|
||||
auto reset() -> void {
|
||||
for(auto& node : _nodes) {
|
||||
PlatformDetach(node);
|
||||
node->reset();
|
||||
node->_parent.reset();
|
||||
}
|
||||
_nodes.reset();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
auto cast() -> shared_pointer<typename T::type> {
|
||||
if(dynamic_cast<typename T::type*>(this)) return shared();
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
auto is() -> bool {
|
||||
return (bool)cast<T>();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
auto find() -> vector<shared_pointer<typename T::type>> {
|
||||
vector<shared_pointer<typename T::type>> result;
|
||||
if(dynamic_cast<typename T::type*>(this)) {
|
||||
if(auto instance = shared()) result.append(instance);
|
||||
}
|
||||
for(auto& node : _nodes) result.append(node->find<T>());
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
auto find(u32 index) -> shared_pointer<typename T::type> {
|
||||
auto result = find<T>();
|
||||
if(index < result.size()) return result[index];
|
||||
return {};
|
||||
}
|
||||
|
||||
auto find(Node::Object source) -> Node::Object {
|
||||
if(!source) return {};
|
||||
for(auto& node : _nodes) {
|
||||
if(node->identity() == source->identity() && node->_name == source->_name) return node;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T = Node::Object>
|
||||
auto find(string name) -> T {
|
||||
using Type = typename T::type;
|
||||
auto path = name.split("/");
|
||||
name = path.takeFirst();
|
||||
for(auto& node : _nodes) {
|
||||
if(node->_name != name) continue;
|
||||
if(path) return node->find<T>(path.merge("/"));
|
||||
if(node->identity() == Type::identifier()) return node;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T = Node::Object>
|
||||
auto scan(string name) -> T {
|
||||
using Type = typename T::type;
|
||||
for(auto& node : _nodes) {
|
||||
if(node->identity() == Type::identifier() && node->_name == name) return node;
|
||||
if(auto result = node->scan<T>(name)) return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
auto enumerate(vector<T>& objects) -> void {
|
||||
using Type = typename T::type;
|
||||
if(auto instance = cast<T>()) objects.append(instance);
|
||||
for(auto& node : _nodes) node->enumerate<T>(objects);
|
||||
}
|
||||
|
||||
auto pak() -> VFS::Pak {
|
||||
return _pak;
|
||||
}
|
||||
|
||||
auto setPak(VFS::Pak pak) -> bool {
|
||||
_pak = pak;
|
||||
return (bool)_pak;
|
||||
}
|
||||
|
||||
template<typename T = string>
|
||||
auto attribute(const string& name) const -> T {
|
||||
if(auto attribute = _attributes.find(name)) {
|
||||
if(attribute->value.is<T>()) return attribute->value.get<T>();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T = string>
|
||||
auto hasAttribute(const string& name) const -> bool {
|
||||
if(auto attribute = _attributes.find(name)) {
|
||||
if(attribute->value.is<T>()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T = string, typename U = string>
|
||||
auto setAttribute(const string& name, const U& value = {}) -> void {
|
||||
if constexpr(is_same_v<T, string> && !is_same_v<U, string>) return setAttribute(name, string{value});
|
||||
if(auto attribute = _attributes.find(name)) {
|
||||
if((const T&)value) attribute->value = (const T&)value;
|
||||
else _attributes.remove(*attribute);
|
||||
} else {
|
||||
if((const T&)value) _attributes.insert({name, (const T&)value});
|
||||
}
|
||||
}
|
||||
|
||||
virtual auto load(Node::Object source) -> bool {
|
||||
if(!source || identity() != source->identity() || _name != source->_name) return false;
|
||||
_attributes = source->_attributes;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto save() -> string {
|
||||
string markup;
|
||||
serialize(markup, {});
|
||||
return markup;
|
||||
}
|
||||
|
||||
virtual auto serialize(string& output, string depth) -> void {
|
||||
output.append(depth, "node: ", identity(), "\n");
|
||||
output.append(depth, " name: ", _name, "\n");
|
||||
for(auto& attribute : _attributes) {
|
||||
if(!attribute.value.is<string>()) continue;
|
||||
output.append(depth, " attribute\n");
|
||||
output.append(depth, " name: ", attribute.name, "\n");
|
||||
output.append(depth, " value: ", attribute.value.get<string>(), "\n");
|
||||
}
|
||||
depth.append(" ");
|
||||
for(auto& node : _nodes) {
|
||||
node->serialize(output, depth);
|
||||
}
|
||||
}
|
||||
|
||||
virtual auto unserialize(Markup::Node markup) -> void {
|
||||
if(!markup) return;
|
||||
_name = markup["name"].string();
|
||||
_attributes.reset();
|
||||
for(auto& attribute : markup.find("attribute")) {
|
||||
_attributes.insert({attribute["name"].string(), attribute["value"].string()});
|
||||
}
|
||||
for(auto& leaf : markup.find("node")) {
|
||||
auto node = Class::create(leaf.string());
|
||||
append(node);
|
||||
node->unserialize(leaf);
|
||||
}
|
||||
}
|
||||
|
||||
virtual auto copy(Node::Object source) -> void {
|
||||
_attributes = source->_attributes;
|
||||
for(auto& from : source->_nodes) {
|
||||
for(auto& to : _nodes) {
|
||||
if(from->identity() != to->identity()) continue;
|
||||
if(from->name() != to->name()) continue;
|
||||
to->copy(from);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto begin() { return _nodes.begin(); }
|
||||
auto end() { return _nodes.end(); }
|
||||
|
||||
protected:
|
||||
string _name;
|
||||
VFS::Pak _pak;
|
||||
set<Attribute> _attributes;
|
||||
shared_pointer_weak<Object> _parent;
|
||||
vector<Node::Object> _nodes;
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
struct Peripheral : Object {
|
||||
DeclareClass(Peripheral, "peripheral")
|
||||
using Object::Object;
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
struct Port : Object {
|
||||
DeclareClass(Port, "port")
|
||||
using Object::Object;
|
||||
|
||||
auto type() const -> string { return _type; }
|
||||
auto family() const -> string { return _family; }
|
||||
auto hotSwappable() const -> bool { return _hotSwappable; }
|
||||
auto supported() const -> vector<string> { return _supported; }
|
||||
|
||||
auto setAllocate(function<Node::Peripheral (string)> allocate) -> void { _allocate = allocate; }
|
||||
auto setConnect(function<void ()> connect) -> void { _connect = connect; }
|
||||
auto setDisconnect(function<void ()> disconnect) -> void { _disconnect = disconnect; }
|
||||
auto setType(string type) -> void { _type = type; }
|
||||
auto setFamily(string family) -> void { _family = family; }
|
||||
auto setHotSwappable(bool hotSwappable) -> void { _hotSwappable = hotSwappable; }
|
||||
auto setSupported(vector<string> supported) -> void { _supported = supported; }
|
||||
|
||||
auto connected() -> Node::Peripheral {
|
||||
return find<Node::Peripheral>(0);
|
||||
}
|
||||
|
||||
auto allocate(string name = {}) -> Node::Peripheral {
|
||||
disconnect();
|
||||
if(_allocate) return _allocate(name);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto connect() -> void {
|
||||
if(_connect) _connect();
|
||||
}
|
||||
|
||||
auto disconnect() -> void {
|
||||
if(auto peripheral = connected()) {
|
||||
if(_disconnect) _disconnect();
|
||||
remove(peripheral);
|
||||
}
|
||||
}
|
||||
|
||||
auto serialize(string& output, string depth) -> void override {
|
||||
Object::serialize(output, depth);
|
||||
output.append(depth, " type: ", _type, "\n");
|
||||
output.append(depth, " family: ", _family, "\n");
|
||||
output.append(depth, " hotSwappable: ", _hotSwappable, "\n");
|
||||
}
|
||||
|
||||
auto unserialize(Markup::Node node) -> void override {
|
||||
Object::unserialize(node);
|
||||
_type = node["type"].string();
|
||||
_family = node["family"].string();
|
||||
_hotSwappable = node["hotSwappable"].boolean();
|
||||
}
|
||||
|
||||
auto copy(Node::Object object) -> void override {
|
||||
if(auto source = object->cast<Node::Port>()) {
|
||||
Object::copy(source);
|
||||
if(auto peripheral = source->find<Node::Peripheral>(0)) {
|
||||
if(auto node = allocate(peripheral->name())) {
|
||||
node->copy(peripheral);
|
||||
connect();
|
||||
node->copy(peripheral);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
function<Node::Peripheral (string)> _allocate;
|
||||
function<void ()> _connect;
|
||||
function<void ()> _disconnect;
|
||||
string _type;
|
||||
string _family;
|
||||
bool _hotSwappable = false;
|
||||
vector<string> _supported;
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
struct Boolean : Setting {
|
||||
DeclareClass(Boolean, "setting.boolean")
|
||||
|
||||
Boolean(string name = {}, bool value = {}, function<void (bool)> modify = {}) : Setting(name) {
|
||||
_currentValue = value;
|
||||
_latchedValue = value;
|
||||
_modify = modify;
|
||||
}
|
||||
|
||||
auto modify(bool value) const -> void { if(_modify) return _modify(value); }
|
||||
auto value() const -> bool { return _currentValue; }
|
||||
auto latch() const -> bool { return _latchedValue; }
|
||||
|
||||
auto setModify(function<void (bool)> modify) { _modify = modify; }
|
||||
|
||||
auto setValue(bool value) -> void {
|
||||
_currentValue = value;
|
||||
if(_dynamic) setLatch();
|
||||
}
|
||||
|
||||
auto setLatch() -> void override {
|
||||
if(_latchedValue == _currentValue) return;
|
||||
_latchedValue = _currentValue;
|
||||
modify(_latchedValue);
|
||||
}
|
||||
|
||||
auto readValue() const -> string override { return value(); }
|
||||
auto readLatch() const -> string override { return latch(); }
|
||||
auto readAllowedValues() const -> vector<string> override { return {"false", "true"}; }
|
||||
auto writeValue(string value) -> void override { setValue(value.boolean()); }
|
||||
|
||||
protected:
|
||||
function<void (bool)> _modify;
|
||||
bool _currentValue = {};
|
||||
bool _latchedValue = {};
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
struct Integer : Setting {
|
||||
DeclareClass(Integer, "setting.integer")
|
||||
|
||||
Integer(string name = {}, s64 value = {}, function<void (s64)> modify = {}) : Setting(name) {
|
||||
_currentValue = value;
|
||||
_latchedValue = value;
|
||||
_modify = modify;
|
||||
}
|
||||
|
||||
auto modify(s64 value) const -> void { if(_modify) return _modify(value); }
|
||||
auto value() const -> s64 { return _currentValue; }
|
||||
auto latch() const -> s64 { return _latchedValue; }
|
||||
|
||||
auto setModify(function<void (s64)> modify) { _modify = modify; }
|
||||
|
||||
auto setValue(s64 value) -> void {
|
||||
if(_allowedValues && !_allowedValues.find(value)) return;
|
||||
_currentValue = value;
|
||||
if(_dynamic) setLatch();
|
||||
}
|
||||
|
||||
auto setLatch() -> void override {
|
||||
if(_latchedValue == _currentValue) return;
|
||||
_latchedValue = _currentValue;
|
||||
modify(_latchedValue);
|
||||
}
|
||||
|
||||
auto setAllowedValues(vector<s64> allowedValues) -> void {
|
||||
_allowedValues = allowedValues;
|
||||
if(_allowedValues && !_allowedValues.find(_currentValue)) setValue(_allowedValues.first());
|
||||
}
|
||||
|
||||
auto readValue() const -> string override { return value(); }
|
||||
auto readLatch() const -> string override { return latch(); }
|
||||
auto readAllowedValues() const -> vector<string> override {
|
||||
vector<string> values;
|
||||
for(auto value : _allowedValues) values.append(value);
|
||||
return values;
|
||||
}
|
||||
auto writeValue(string value) -> void override { setValue(value.integer()); }
|
||||
|
||||
protected:
|
||||
function<void (s64)> _modify;
|
||||
s64 _currentValue = {};
|
||||
s64 _latchedValue = {};
|
||||
vector<s64> _allowedValues;
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
struct Natural : Setting {
|
||||
DeclareClass(Natural, "setting.natural")
|
||||
|
||||
Natural(string name = {}, u64 value = {}, function<void (u64)> modify = {}) : Setting(name) {
|
||||
_currentValue = value;
|
||||
_latchedValue = value;
|
||||
_modify = modify;
|
||||
}
|
||||
|
||||
auto modify(u64 value) const -> void { if(_modify) return _modify(value); }
|
||||
auto value() const -> u64 { return _currentValue; }
|
||||
auto latch() const -> u64 { return _latchedValue; }
|
||||
|
||||
auto setModify(function<void (u64)> modify) { _modify = modify; }
|
||||
|
||||
auto setValue(u64 value) -> void {
|
||||
if(_allowedValues && !_allowedValues.find(value)) return;
|
||||
_currentValue = value;
|
||||
if(_dynamic) setLatch();
|
||||
}
|
||||
|
||||
auto setLatch() -> void override {
|
||||
if(_latchedValue == _currentValue) return;
|
||||
_latchedValue = _currentValue;
|
||||
modify(_latchedValue);
|
||||
}
|
||||
|
||||
auto setAllowedValues(vector<u64> allowedValues) -> void {
|
||||
_allowedValues = allowedValues;
|
||||
if(_allowedValues && !_allowedValues.find(_currentValue)) setValue(_allowedValues.first());
|
||||
}
|
||||
|
||||
auto readValue() const -> string override { return value(); }
|
||||
auto readLatch() const -> string override { return latch(); }
|
||||
auto readAllowedValues() const -> vector<string> override {
|
||||
vector<string> values;
|
||||
for(auto value : _allowedValues) values.append(value);
|
||||
return values;
|
||||
}
|
||||
auto writeValue(string value) -> void override { setValue(value.natural()); }
|
||||
|
||||
protected:
|
||||
function<void (u64)> _modify;
|
||||
u64 _currentValue = {};
|
||||
u64 _latchedValue = {};
|
||||
vector<u64> _allowedValues;
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
struct Real : Setting {
|
||||
DeclareClass(Real, "setting.real")
|
||||
|
||||
Real(string name = {}, f64 value = {}, function<void (f64)> modify = {}) : Setting(name) {
|
||||
_currentValue = value;
|
||||
_latchedValue = value;
|
||||
_modify = modify;
|
||||
}
|
||||
|
||||
auto modify(f64 value) const -> void { if(_modify) return _modify(value); }
|
||||
auto value() const -> f64 { return _currentValue; }
|
||||
auto latch() const -> f64 { return _latchedValue; }
|
||||
|
||||
auto setModify(function<void (f64)> modify) { _modify = modify; }
|
||||
|
||||
auto setValue(f64 value) -> void {
|
||||
if(_allowedValues && !_allowedValues.find(value)) return;
|
||||
_currentValue = value;
|
||||
if(_dynamic) setLatch();
|
||||
}
|
||||
|
||||
auto setLatch() -> void override {
|
||||
if(_latchedValue == _currentValue) return;
|
||||
_latchedValue = _currentValue;
|
||||
modify(_latchedValue);
|
||||
}
|
||||
|
||||
auto setAllowedValues(vector<f64> allowedValues) -> void {
|
||||
_allowedValues = allowedValues;
|
||||
if(_allowedValues && !_allowedValues.find(_currentValue)) setValue(_allowedValues.first());
|
||||
}
|
||||
|
||||
auto readValue() const -> string override { return value(); }
|
||||
auto readLatch() const -> string override { return latch(); }
|
||||
auto readAllowedValues() const -> vector<string> override {
|
||||
vector<string> values;
|
||||
for(auto value : _allowedValues) values.append(value);
|
||||
return values;
|
||||
}
|
||||
auto writeValue(string value) -> void override { setValue(value.real()); }
|
||||
|
||||
protected:
|
||||
function<void (f64)> _modify;
|
||||
f64 _currentValue = {};
|
||||
f64 _latchedValue = {};
|
||||
vector<f64> _allowedValues;
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
struct Setting : Object {
|
||||
DeclareClass(Setting, "setting")
|
||||
using Object::Object;
|
||||
|
||||
auto dynamic() const -> bool { return _dynamic; }
|
||||
|
||||
auto setDynamic(bool dynamic) -> void {
|
||||
_dynamic = dynamic;
|
||||
}
|
||||
|
||||
virtual auto setLatch() -> void {}
|
||||
|
||||
virtual auto readValue() const -> string { return {}; }
|
||||
virtual auto readLatch() const -> string { return {}; }
|
||||
virtual auto readAllowedValues() const -> vector<string> { return {}; }
|
||||
virtual auto writeValue(string value) -> void {}
|
||||
|
||||
auto load(Node::Object source) -> bool override {
|
||||
if(!Object::load(source)) return false;
|
||||
if(auto setting = source->cast<shared_pointer<Setting>>()) writeValue(setting->readValue());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto copy(Node::Object object) -> void override {
|
||||
if(auto source = object->cast<Node::Setting::Setting>()) {
|
||||
Object::copy(source);
|
||||
writeValue(source->readValue());
|
||||
setLatch();
|
||||
}
|
||||
}
|
||||
|
||||
auto serialize(string& output, string depth) -> void override {
|
||||
Object::serialize(output, depth);
|
||||
output.append(depth, " dynamic: ", _dynamic, "\n");
|
||||
output.append(depth, " value: ", readValue(), "\n");
|
||||
}
|
||||
|
||||
auto unserialize(Markup::Node node) -> void override {
|
||||
Object::unserialize(node);
|
||||
_dynamic = node["dynamic"].boolean();
|
||||
writeValue(node["value"].string());
|
||||
}
|
||||
|
||||
protected:
|
||||
bool _dynamic = false;
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
struct String : Setting {
|
||||
DeclareClass(String, "setting.string")
|
||||
|
||||
String(string name = {}, string value = {}, function<void (string)> modify = {}) : Setting(name) {
|
||||
_currentValue = value;
|
||||
_latchedValue = value;
|
||||
_modify = modify;
|
||||
}
|
||||
|
||||
auto modify(string value) const -> void { if(_modify) return _modify(value); }
|
||||
auto value() const -> string { return _currentValue; }
|
||||
auto latch() const -> string { return _latchedValue; }
|
||||
|
||||
auto setModify(function<void (string)> modify) { _modify = modify; }
|
||||
|
||||
auto setValue(string value) -> void {
|
||||
if(_allowedValues && !_allowedValues.find(value)) return;
|
||||
_currentValue = value;
|
||||
if(_dynamic) setLatch();
|
||||
}
|
||||
|
||||
auto setLatch() -> void override {
|
||||
if(_latchedValue == _currentValue) return;
|
||||
_latchedValue = _currentValue;
|
||||
modify(_latchedValue);
|
||||
}
|
||||
|
||||
auto setAllowedValues(vector<string> allowedValues) -> void {
|
||||
_allowedValues = allowedValues;
|
||||
if(_allowedValues && !_allowedValues.find(_currentValue)) setValue(_allowedValues.first());
|
||||
}
|
||||
|
||||
auto readValue() const -> string override { return value(); }
|
||||
auto readLatch() const -> string override { return latch(); }
|
||||
auto readAllowedValues() const -> vector<string> override { return _allowedValues; }
|
||||
auto writeValue(string value) -> void override { setValue(value); }
|
||||
|
||||
protected:
|
||||
function<void (string)> _modify;
|
||||
string _currentValue = {};
|
||||
string _latchedValue = {};
|
||||
vector<string> _allowedValues;
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
struct System : Object {
|
||||
DeclareClass(System, "system")
|
||||
using Object::Object;
|
||||
|
||||
auto game() -> string { if(_game) return _game(); return {}; }
|
||||
auto run() -> void { if(_run) return _run(); }
|
||||
auto power(bool reset = false) -> void { if(_power) return _power(reset); }
|
||||
auto save() -> void { if(_save) return _save(); }
|
||||
auto unload() -> void { if(_unload) return _unload(); }
|
||||
auto serialize(bool synchronize = true) -> serializer { if(_serialize) return _serialize(synchronize); return {}; }
|
||||
auto unserialize(serializer& s) -> bool { if(_unserialize) return _unserialize(s); return false; }
|
||||
|
||||
auto setGame(function<string ()> game) -> void { _game = game; }
|
||||
auto setRun(function<void ()> run) -> void { _run = run; }
|
||||
auto setPower(function<void (bool)> power) -> void { _power = power; }
|
||||
auto setSave(function<void ()> save) -> void { _save = save; }
|
||||
auto setUnload(function<void ()> unload) -> void { _unload = unload; }
|
||||
auto setSerialize(function<serializer (bool)> serialize) -> void { _serialize = serialize; }
|
||||
auto setUnserialize(function<bool (serializer&)> unserialize) -> void { _unserialize = unserialize; }
|
||||
|
||||
protected:
|
||||
function<string ()> _game;
|
||||
function<void ()> _run;
|
||||
function<void (bool)> _power;
|
||||
function<void ()> _save;
|
||||
function<void ()> _unload;
|
||||
function<serializer (bool)> _serialize;
|
||||
function<bool (serializer&)> _unserialize;
|
||||
};
|
|
@ -0,0 +1,388 @@
|
|||
Screen::Screen(string name, u32 width, u32 height) : Video(name) {
|
||||
_canvasWidth = width;
|
||||
_canvasHeight = height;
|
||||
|
||||
if(width && height) {
|
||||
_inputA = alloc_invisible<u32>(width * height);
|
||||
_inputB = alloc_invisible<u32>(width * height);
|
||||
_output = alloc_invisible<u32>(width * height);
|
||||
_rotate = alloc_invisible<u32>(width * height);
|
||||
|
||||
if constexpr(ares::Video::Threaded) {
|
||||
_thread = nall::thread::create({&Screen::main, this});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Screen::~Screen() {
|
||||
if constexpr(ares::Video::Threaded) {
|
||||
if(_canvasWidth && _canvasHeight) {
|
||||
_kill = true;
|
||||
_thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Screen::main(uintptr_t) -> void {
|
||||
while(!_kill) {
|
||||
usleep(1);
|
||||
if(_frame) {
|
||||
refresh();
|
||||
_frame = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Screen::quit() -> void {
|
||||
_kill = true;
|
||||
_thread.join();
|
||||
_sprites.reset();
|
||||
}
|
||||
|
||||
auto Screen::power() -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
memory::fill<u32>(_inputA.data(), _canvasWidth * _canvasHeight, _fillColor);
|
||||
memory::fill<u32>(_inputB.data(), _canvasWidth * _canvasHeight, _fillColor);
|
||||
memory::fill<u32>(_output.data(), _canvasWidth * _canvasHeight, _fillColor);
|
||||
memory::fill<u32>(_rotate.data(), _canvasWidth * _canvasHeight, _fillColor);
|
||||
}
|
||||
|
||||
auto Screen::pixels(bool frame) -> array_span<u32> {
|
||||
if(frame == 0) return {_inputA.data(), _canvasWidth * _canvasHeight};
|
||||
if(frame == 1) return {_inputB.data(), _canvasWidth * _canvasHeight};
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Screen::resetPalette() -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_palette.reset();
|
||||
}
|
||||
|
||||
auto Screen::resetSprites() -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_sprites.reset();
|
||||
}
|
||||
|
||||
auto Screen::setRefresh(function<void ()> refresh) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_refresh = refresh;
|
||||
}
|
||||
|
||||
auto Screen::setViewport(u32 x, u32 y, u32 width, u32 height) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_viewportX = x;
|
||||
_viewportY = y;
|
||||
_viewportWidth = width;
|
||||
_viewportHeight = height;
|
||||
}
|
||||
|
||||
auto Screen::setSize(u32 width, u32 height) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_width = width;
|
||||
_height = height;
|
||||
}
|
||||
|
||||
auto Screen::setScale(f64 scaleX, f64 scaleY) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_scaleX = scaleX;
|
||||
_scaleY = scaleY;
|
||||
}
|
||||
|
||||
auto Screen::setAspect(f64 aspectX, f64 aspectY) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_aspectX = aspectX;
|
||||
_aspectY = aspectY;
|
||||
}
|
||||
|
||||
auto Screen::setSaturation(f64 saturation) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_saturation = saturation;
|
||||
_palette.reset();
|
||||
}
|
||||
|
||||
auto Screen::setGamma(f64 gamma) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_gamma = gamma;
|
||||
_palette.reset();
|
||||
}
|
||||
|
||||
auto Screen::setLuminance(f64 luminance) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_luminance = luminance;
|
||||
_palette.reset();
|
||||
}
|
||||
|
||||
auto Screen::setFillColor(u32 fillColor) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_fillColor = fillColor;
|
||||
}
|
||||
|
||||
auto Screen::setColorBleed(bool colorBleed) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_colorBleed = colorBleed;
|
||||
}
|
||||
|
||||
auto Screen::setInterframeBlending(bool interframeBlending) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_interframeBlending = interframeBlending;
|
||||
}
|
||||
|
||||
auto Screen::setRotation(u32 rotation) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_rotation = rotation;
|
||||
}
|
||||
|
||||
auto Screen::setProgressive(bool progressiveDouble) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_interlace = false;
|
||||
_progressive = true;
|
||||
_progressiveDouble = progressiveDouble;
|
||||
}
|
||||
|
||||
auto Screen::setInterlace(bool interlaceField) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_progressive = false;
|
||||
_interlace = true;
|
||||
_interlaceField = interlaceField;
|
||||
}
|
||||
|
||||
auto Screen::attach(Node::Video::Sprite sprite) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
if(_sprites.find(sprite)) return;
|
||||
_sprites.append(sprite);
|
||||
}
|
||||
|
||||
auto Screen::detach(Node::Video::Sprite sprite) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
if(!_sprites.find(sprite)) return;
|
||||
_sprites.removeByValue(sprite);
|
||||
}
|
||||
|
||||
auto Screen::colors(u32 colors, function<n64 (n32)> color) -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_colors = colors;
|
||||
_color = color;
|
||||
_palette.reset();
|
||||
}
|
||||
|
||||
auto Screen::frame() -> void {
|
||||
if(runAhead()) return;
|
||||
while(_frame) spinloop();
|
||||
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
_inputA.swap(_inputB);
|
||||
_frame = true;
|
||||
if constexpr(!ares::Video::Threaded) {
|
||||
refresh();
|
||||
_frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto Screen::refresh() -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
if(runAhead()) return;
|
||||
|
||||
refreshPalette();
|
||||
if(_refresh) _refresh();
|
||||
|
||||
auto viewX = _viewportX;
|
||||
auto viewY = _viewportY;
|
||||
auto viewWidth = _viewportWidth;
|
||||
auto viewHeight = _viewportHeight;
|
||||
|
||||
auto pitch = _canvasWidth;
|
||||
auto width = _canvasWidth;
|
||||
auto height = _canvasHeight;
|
||||
auto input = _inputB.data();
|
||||
auto output = _output.data();
|
||||
|
||||
for(u32 y : range(height)) {
|
||||
auto source = input + y * pitch;
|
||||
auto target = output + y * width;
|
||||
|
||||
if(_interlace) {
|
||||
if((_interlaceField & 1) == (y & 1)) {
|
||||
for(u32 x : range(width)) {
|
||||
auto color = _palette[*source++];
|
||||
*target++ = color;
|
||||
}
|
||||
}
|
||||
} else if(_progressive && _progressiveDouble) {
|
||||
source = input + (y & ~1) * pitch;
|
||||
for(u32 x : range(width)) {
|
||||
auto color = _palette[*source++];
|
||||
*target++ = color;
|
||||
}
|
||||
} else if(_interframeBlending) {
|
||||
n32 mask = 1 << 24 | 1 << 16 | 1 << 8 | 1 << 0;
|
||||
for(u32 x : range(width)) {
|
||||
auto a = *target;
|
||||
auto b = _palette[*source++];
|
||||
*target++ = (a + b - ((a ^ b) & mask)) >> 1;
|
||||
}
|
||||
} else {
|
||||
for(u32 x : range(width)) {
|
||||
auto color = _palette[*source++];
|
||||
*target++ = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(_colorBleed) {
|
||||
n32 mask = 1 << 24 | 1 << 16 | 1 << 8 | 1 << 0;
|
||||
for(u32 y : range(height)) {
|
||||
auto target = output + y * width;
|
||||
for(u32 x : range(width)) {
|
||||
auto a = target[x];
|
||||
auto b = target[x + (x != width - 1)];
|
||||
target[x] = (a + b - ((a ^ b) & mask)) >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& sprite : _sprites) {
|
||||
if(!sprite->visible()) continue;
|
||||
|
||||
n32 alpha = 255u << 24;
|
||||
for(int y : range(sprite->height())) {
|
||||
s32 pixelY = sprite->y() + y;
|
||||
if(pixelY < 0 || pixelY >= height) continue;
|
||||
|
||||
auto source = sprite->image().data() + y * sprite->width();
|
||||
auto target = &output[pixelY * width];
|
||||
for(s32 x : range(sprite->width())) {
|
||||
s32 pixelX = sprite->x() + x;
|
||||
if(pixelX < 0 || pixelX >= width) continue;
|
||||
|
||||
auto pixel = source[x];
|
||||
if(pixel >> 24) target[pixelX] = alpha | pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(_rotation == 90) {
|
||||
//rotate left
|
||||
for(u32 y : range(height)) {
|
||||
auto source = output + y * width;
|
||||
for(u32 x : range(width)) {
|
||||
auto target = _rotate.data() + (width - 1 - x) * height + y;
|
||||
*target = *source++;
|
||||
}
|
||||
}
|
||||
output = _rotate.data();
|
||||
swap(width, height);
|
||||
swap(viewWidth, viewHeight);
|
||||
}
|
||||
|
||||
if(_rotation == 180) {
|
||||
//rotate upside down
|
||||
for(u32 y : range(height)) {
|
||||
auto source = output + y * width;
|
||||
for(u32 x : range(width)) {
|
||||
auto target = _rotate.data() + (height - 1 - y) * width + (width - 1 - x);
|
||||
*target = *source++;
|
||||
}
|
||||
}
|
||||
output = _rotate.data();
|
||||
}
|
||||
|
||||
if(_rotation == 270) {
|
||||
//rotate right
|
||||
for(u32 y : range(height)) {
|
||||
auto source = output + y * width;
|
||||
for(u32 x : range(width)) {
|
||||
auto target = _rotate.data() + x * height + (height - 1 - y);
|
||||
*target = *source++;
|
||||
}
|
||||
}
|
||||
output = _rotate.data();
|
||||
swap(width, height);
|
||||
swap(viewWidth, viewHeight);
|
||||
}
|
||||
|
||||
platform->video(shared(), output + viewX + viewY * width, width * sizeof(u32), viewWidth, viewHeight);
|
||||
memory::fill<u32>(_inputB.data(), width * height, _fillColor);
|
||||
}
|
||||
|
||||
auto Screen::refreshPalette() -> void {
|
||||
lock_guard<recursive_mutex> lock(_mutex);
|
||||
if(_palette) return;
|
||||
|
||||
//generate the color lookup palettes to convert native colors to ARGB8888
|
||||
_palette = new u32[_colors];
|
||||
for(u32 index : range(_colors)) {
|
||||
n64 color = _color(index);
|
||||
n16 b = color.bit( 0,15);
|
||||
n16 g = color.bit(16,31);
|
||||
n16 r = color.bit(32,47);
|
||||
n16 a = 65535;
|
||||
|
||||
if(_saturation != 1.0) {
|
||||
n16 grayscale = uclamp<16>((r + g + b) / 3);
|
||||
f64 inverse = max(0.0, 1.0 - _saturation);
|
||||
r = uclamp<16>(r * _saturation + grayscale * inverse);
|
||||
g = uclamp<16>(g * _saturation + grayscale * inverse);
|
||||
b = uclamp<16>(b * _saturation + grayscale * inverse);
|
||||
}
|
||||
|
||||
if(_gamma != 1.0) {
|
||||
f64 reciprocal = 1.0 / 32767.0;
|
||||
r = r > 32767 ? r : n16(32767 * pow(r * reciprocal, _gamma));
|
||||
g = g > 32767 ? g : n16(32767 * pow(g * reciprocal, _gamma));
|
||||
b = b > 32767 ? b : n16(32767 * pow(b * reciprocal, _gamma));
|
||||
}
|
||||
|
||||
if(_luminance != 1.0) {
|
||||
r = uclamp<16>(r * _luminance);
|
||||
g = uclamp<16>(g * _luminance);
|
||||
b = uclamp<16>(b * _luminance);
|
||||
}
|
||||
|
||||
a >>= 8;
|
||||
r >>= 8;
|
||||
g >>= 8;
|
||||
b >>= 8;
|
||||
|
||||
_palette[index] = a << 24 | r << 16 | g << 8 | b << 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto Screen::serialize(string& output, string depth) -> void {
|
||||
Video::serialize(output, depth);
|
||||
output.append(depth, " width: ", _width, "\n");
|
||||
output.append(depth, " height: ", _height, "\n");
|
||||
output.append(depth, " scaleX: ", _scaleX, "\n");
|
||||
output.append(depth, " scaleY: ", _scaleY, "\n");
|
||||
output.append(depth, " aspectX: ", _aspectX, "\n");
|
||||
output.append(depth, " aspectY: ", _aspectY, "\n");
|
||||
output.append(depth, " colors: ", _colors, "\n");
|
||||
output.append(depth, " saturation: ", _saturation, "\n");
|
||||
output.append(depth, " gamma: ", _gamma, "\n");
|
||||
output.append(depth, " luminance: ", _luminance, "\n");
|
||||
output.append(depth, " fillColor: ", _fillColor, "\n");
|
||||
output.append(depth, " colorBleed: ", _colorBleed, "\n");
|
||||
output.append(depth, " interlace: ", _interlace, "\n");
|
||||
output.append(depth, " interframeBlending: ", _interframeBlending, "\n");
|
||||
output.append(depth, " rotation: ", _rotation, "\n");
|
||||
}
|
||||
|
||||
auto Screen::unserialize(Markup::Node node) -> void {
|
||||
Video::unserialize(node);
|
||||
_width = node["width"].natural();
|
||||
_height = node["height"].natural();
|
||||
_scaleX = node["scaleX"].real();
|
||||
_scaleY = node["scaleY"].real();
|
||||
_aspectX = node["aspectX"].real();
|
||||
_aspectY = node["aspectY"].real();
|
||||
_colors = node["colors"].natural();
|
||||
_saturation = node["saturation"].real();
|
||||
_gamma = node["gamma"].real();
|
||||
_luminance = node["luminance"].real();
|
||||
_fillColor = node["fillColor"].natural();
|
||||
_colorBleed = node["colorBleed"].boolean();
|
||||
_interlace = node["interlace"].natural();
|
||||
_interframeBlending = node["interframeBlending"].boolean();
|
||||
_rotation = node["rotation"].natural();
|
||||
resetPalette();
|
||||
resetSprites();
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
struct Screen : Video {
|
||||
DeclareClass(Screen, "video.screen")
|
||||
using Video::Video;
|
||||
|
||||
Screen(string name = {}, u32 width = 0, u32 height = 0);
|
||||
~Screen();
|
||||
auto main(uintptr_t) -> void;
|
||||
auto quit() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto canvasWidth() const -> u32 { return _canvasWidth; }
|
||||
auto canvasHeight() const -> u32 { return _canvasHeight; }
|
||||
auto width() const -> u32 { return _width; }
|
||||
auto height() const -> u32 { return _height; }
|
||||
auto scaleX() const -> f64 { return _scaleX; }
|
||||
auto scaleY() const -> f64 { return _scaleY; }
|
||||
auto aspectX() const -> f64 { return _aspectX; }
|
||||
auto aspectY() const -> f64 { return _aspectY; }
|
||||
auto colors() const -> u32 { return _colors; }
|
||||
auto pixels(bool frame = 0) -> array_span<u32>;
|
||||
|
||||
auto saturation() const -> double { return _saturation; }
|
||||
auto gamma() const -> double { return _gamma; }
|
||||
auto luminance() const -> double { return _luminance; }
|
||||
|
||||
auto fillColor() const -> u32 { return _fillColor; }
|
||||
auto colorBleed() const -> bool { return _colorBleed; }
|
||||
auto interframeBlending() const -> bool { return _interframeBlending; }
|
||||
auto rotation() const -> u32 { return _rotation; }
|
||||
|
||||
auto resetPalette() -> void;
|
||||
auto resetSprites() -> void;
|
||||
|
||||
auto setRefresh(function<void ()> refresh) -> void;
|
||||
auto setViewport(u32 x, u32 y, u32 width, u32 height) -> void;
|
||||
|
||||
auto setSize(u32 width, u32 height) -> void;
|
||||
auto setScale(f64 scaleX, f64 scaleY) -> void;
|
||||
auto setAspect(f64 aspectX, f64 aspectY) -> void;
|
||||
|
||||
auto setSaturation(f64 saturation) -> void;
|
||||
auto setGamma(f64 gamma) -> void;
|
||||
auto setLuminance(f64 luminance) -> void;
|
||||
|
||||
auto setFillColor(u32 fillColor) -> void;
|
||||
auto setColorBleed(bool colorBleed) -> void;
|
||||
auto setInterframeBlending(bool interframeBlending) -> void;
|
||||
auto setRotation(u32 rotation) -> void;
|
||||
|
||||
auto setProgressive(bool progressiveDouble = false) -> void;
|
||||
auto setInterlace(bool interlaceField) -> void;
|
||||
|
||||
auto attach(Node::Video::Sprite) -> void;
|
||||
auto detach(Node::Video::Sprite) -> void;
|
||||
|
||||
auto colors(u32 colors, function<n64 (n32)> color) -> void;
|
||||
auto frame() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
auto serialize(string& output, string depth) -> void override;
|
||||
auto unserialize(Markup::Node node) -> void override;
|
||||
|
||||
private:
|
||||
auto refreshPalette() -> void;
|
||||
|
||||
protected:
|
||||
u32 _canvasWidth = 0;
|
||||
u32 _canvasHeight = 0;
|
||||
u32 _width = 0;
|
||||
u32 _height = 0;
|
||||
f64 _scaleX = 1.0;
|
||||
f64 _scaleY = 1.0;
|
||||
f64 _aspectX = 1.0;
|
||||
f64 _aspectY = 1.0;
|
||||
u32 _colors = 0;
|
||||
f64 _saturation = 1.0;
|
||||
f64 _gamma = 1.0;
|
||||
f64 _luminance = 1.0;
|
||||
u32 _fillColor = 0;
|
||||
bool _colorBleed = false;
|
||||
bool _interframeBlending = false;
|
||||
u32 _rotation = 0; //counter-clockwise (90 = left, 270 = right)
|
||||
|
||||
function<n64 (n32)> _color;
|
||||
unique_pointer<u32> _inputA;
|
||||
unique_pointer<u32> _inputB;
|
||||
unique_pointer<u32> _output;
|
||||
unique_pointer<u32> _rotate;
|
||||
unique_pointer<u32[]> _palette;
|
||||
vector<Node::Video::Sprite> _sprites;
|
||||
|
||||
//unserialized:
|
||||
nall::thread _thread;
|
||||
recursive_mutex _mutex;
|
||||
atomic<bool> _kill = false;
|
||||
atomic<bool> _frame = false;
|
||||
function<void ()> _refresh;
|
||||
bool _progressive = false;
|
||||
bool _progressiveDouble = false;
|
||||
bool _interlace = false;
|
||||
bool _interlaceField = false;
|
||||
u32 _viewportX = 0;
|
||||
u32 _viewportY = 0;
|
||||
u32 _viewportWidth = 0;
|
||||
u32 _viewportHeight = 0;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
auto Sprite::setVisible(bool visible) -> void {
|
||||
_visible = visible;
|
||||
}
|
||||
|
||||
auto Sprite::setPosition(u32 x, u32 y) -> void {
|
||||
_x = x;
|
||||
_y = y;
|
||||
}
|
||||
|
||||
auto Sprite::setImage(nall::image image, bool invert) -> void {
|
||||
_width = image.width();
|
||||
_height = image.height();
|
||||
_pixels = new u32[_width * _height];
|
||||
image.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0);
|
||||
memory::copy(_pixels.data(), _width * _height * sizeof(u32), image.data(), image.size());
|
||||
if(!invert) return;
|
||||
|
||||
for(u32 y : range(_height)) {
|
||||
auto data = _pixels.data() + y * _width;
|
||||
for(u32 x : range(_width)) {
|
||||
auto pixel = data[x];
|
||||
pixel ^= 0xffffff;
|
||||
data[x] = pixel;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
struct Sprite : Video {
|
||||
DeclareClass(Sprite, "video.sprite")
|
||||
using Video::Video;
|
||||
|
||||
auto visible() const -> bool { return _visible; }
|
||||
auto x() const -> u32 { return _x; }
|
||||
auto y() const -> u32 { return _y; }
|
||||
auto width() const -> u32 { return _width; }
|
||||
auto height() const -> u32 { return _height; }
|
||||
auto image() const -> array_view<u32> { return {_pixels.data(), _width * _height}; }
|
||||
|
||||
auto setVisible(bool visible) -> void;
|
||||
auto setPosition(u32 x, u32 y) -> void;
|
||||
auto setImage(nall::image, bool invert = false) -> void;
|
||||
|
||||
protected:
|
||||
bool _visible = false;
|
||||
u32 _x = 0;
|
||||
u32 _y = 0;
|
||||
u32 _width = 0;
|
||||
u32 _height = 0;
|
||||
unique_pointer<u32[]> _pixels;
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
struct Video : Object {
|
||||
DeclareClass(Video, "video");
|
||||
using Object::Object;
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
namespace ares {
|
||||
|
||||
enum class Event : u32 {
|
||||
None,
|
||||
Step,
|
||||
Frame,
|
||||
Power,
|
||||
Synchronize,
|
||||
};
|
||||
|
||||
struct Platform {
|
||||
virtual auto attach(Node::Object) -> void {}
|
||||
virtual auto detach(Node::Object) -> void {}
|
||||
virtual auto pak(Node::Object) -> shared_pointer<vfs::directory> { return {}; }
|
||||
virtual auto event(Event) -> void {}
|
||||
virtual auto log(string_view message) -> void {}
|
||||
virtual auto video(Node::Video::Screen, const u32* data, u32 pitch, u32 width, u32 height) -> void {}
|
||||
virtual auto audio(Node::Audio::Stream) -> void {}
|
||||
virtual auto input(Node::Input::Input) -> void {}
|
||||
};
|
||||
|
||||
extern Platform* platform;
|
||||
|
||||
}
|
||||
|
||||
namespace ares::Core {
|
||||
// <ares/node/node.hpp> forward declarations
|
||||
auto PlatformAttach(Node::Object node) -> void { if(platform && node->name()) platform->attach(node); }
|
||||
auto PlatformDetach(Node::Object node) -> void { if(platform && node->name()) platform->detach(node); }
|
||||
auto PlatformLog(string_view text) -> void { if(platform) platform->log(text); }
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
#pragma once
|
||||
|
||||
namespace ares {
|
||||
|
||||
struct Random {
|
||||
enum class Entropy : u32 { None, Low, High };
|
||||
|
||||
auto operator()() -> n64 {
|
||||
return random();
|
||||
}
|
||||
|
||||
auto entropy(Entropy entropy) -> void {
|
||||
_entropy = entropy;
|
||||
seed();
|
||||
}
|
||||
|
||||
auto seed(maybe<n32> seed = nothing, maybe<n32> sequence = nothing) -> void {
|
||||
if(!seed) seed = (n32)clock();
|
||||
if(!sequence) sequence = 0;
|
||||
|
||||
_state = 0;
|
||||
_increment = sequence() << 1 | 1;
|
||||
step();
|
||||
_state += seed();
|
||||
step();
|
||||
}
|
||||
|
||||
auto random() -> n64 {
|
||||
if(_entropy == Entropy::None) return 0;
|
||||
return (n64)step() << 32 | (n64)step() << 0;
|
||||
}
|
||||
|
||||
auto bias(n64 bias) -> n64 {
|
||||
if(_entropy == Entropy::None) return bias;
|
||||
return random();
|
||||
}
|
||||
|
||||
auto bound(n64 bound) -> n64 {
|
||||
n64 threshold = -bound % bound;
|
||||
while(true) {
|
||||
n64 result = random();
|
||||
if(result >= threshold) return result % bound;
|
||||
}
|
||||
}
|
||||
|
||||
auto array(array_span<n8> buffer) -> void {
|
||||
if(_entropy == Entropy::None) {
|
||||
memory::fill(buffer.data(), buffer.size());
|
||||
return;
|
||||
}
|
||||
|
||||
if(_entropy == Entropy::High) {
|
||||
for(n32 address : range(buffer.size())) {
|
||||
buffer[address] = random();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Entropy::Low
|
||||
u32 lobit = random() & 3;
|
||||
u32 hibit = (lobit + 8 + (random() & 3)) & 15;
|
||||
u32 lovalue = random() & 255;
|
||||
u32 hivalue = random() & 255;
|
||||
if((random() & 3) == 0) lovalue = 0;
|
||||
if((random() & 1) == 0) hivalue = ~lovalue;
|
||||
|
||||
for(n32 address : range(buffer.size())) {
|
||||
n8 value = address.bit(lobit) ? lovalue : hivalue;
|
||||
if(address.bit(hibit)) value = ~value;
|
||||
if((random() & 511) == 0) value.bit(random() & 7) ^= 1;
|
||||
if((random() & 2047) == 0) value.bit(random() & 7) ^= 1;
|
||||
buffer[address] = value;
|
||||
}
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s((u32&)_entropy);
|
||||
s(_state);
|
||||
s(_increment);
|
||||
}
|
||||
|
||||
private:
|
||||
auto step() -> n32 {
|
||||
n64 state = _state;
|
||||
_state = state * 6364136223846793005ull + _increment;
|
||||
n32 xorshift = (state >> 18 ^ state) >> 27;
|
||||
n32 rotate = state >> 59;
|
||||
return xorshift >> rotate | xorshift << (-rotate & 31);
|
||||
}
|
||||
|
||||
Entropy _entropy = Entropy::High;
|
||||
n64 _state;
|
||||
n64 _increment;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
all:
|
||||
sourcery resource.bml resource.cpp resource.hpp
|
||||
|
||||
clean:
|
||||
rm resource.cpp
|
||||
rm resource.hpp
|
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,29 @@
|
|||
namespace name=Resource
|
||||
namespace name=Ares
|
||||
binary name=Icon1x file=icon.png
|
||||
binary name=Icon2x file=icon@2x.png
|
||||
binary name=Logo1x file=logo.png
|
||||
binary name=Logo2x file=logo@2x.png
|
||||
namespace name=Sprite
|
||||
namespace name=SuperFamicom
|
||||
binary name=CrosshairBlue file=sprite/sfc/crosshair-blue.png
|
||||
binary name=CrosshairGreen file=sprite/sfc/crosshair-green.png
|
||||
binary name=CrosshairRed file=sprite/sfc/crosshair-red.png
|
||||
namespace name=WonderSwan
|
||||
binary name=Auxiliary0 file=sprite/ws/auxiliary-0.png
|
||||
binary name=Auxiliary1 file=sprite/ws/auxiliary-1.png
|
||||
binary name=Auxiliary2 file=sprite/ws/auxiliary-2.png
|
||||
binary name=Headphones file=sprite/ws/headphones.png
|
||||
binary name=Initialized file=sprite/ws/initialized.png
|
||||
binary name=LowBattery file=sprite/ws/low-battery.png
|
||||
binary name=Orientation0 file=sprite/ws/orientation-0.png
|
||||
binary name=Orientation1 file=sprite/ws/orientation-1.png
|
||||
binary name=PoweredOn file=sprite/ws/powered-on.png
|
||||
binary name=Sleeping file=sprite/ws/sleeping.png
|
||||
binary name=VolumeA0 file=sprite/ws/volume-a0.png
|
||||
binary name=VolumeA1 file=sprite/ws/volume-a1.png
|
||||
binary name=VolumeA2 file=sprite/ws/volume-a2.png
|
||||
binary name=VolumeB0 file=sprite/ws/volume-b0.png
|
||||
binary name=VolumeB1 file=sprite/ws/volume-b1.png
|
||||
binary name=VolumeB2 file=sprite/ws/volume-b2.png
|
||||
binary name=VolumeB3 file=sprite/ws/volume-b3.png
|
|
@ -0,0 +1,34 @@
|
|||
namespace Resource {
|
||||
namespace Ares {
|
||||
extern const unsigned char Icon1x[6562];
|
||||
extern const unsigned char Icon2x[9662];
|
||||
extern const unsigned char Logo1x[11533];
|
||||
extern const unsigned char Logo2x[18991];
|
||||
}
|
||||
namespace Sprite {
|
||||
namespace SuperFamicom {
|
||||
extern const unsigned char CrosshairBlue[332];
|
||||
extern const unsigned char CrosshairGreen[329];
|
||||
extern const unsigned char CrosshairRed[342];
|
||||
}
|
||||
namespace WonderSwan {
|
||||
extern const unsigned char Auxiliary0[117];
|
||||
extern const unsigned char Auxiliary1[134];
|
||||
extern const unsigned char Auxiliary2[136];
|
||||
extern const unsigned char Headphones[167];
|
||||
extern const unsigned char Initialized[136];
|
||||
extern const unsigned char LowBattery[144];
|
||||
extern const unsigned char Orientation0[148];
|
||||
extern const unsigned char Orientation1[150];
|
||||
extern const unsigned char PoweredOn[155];
|
||||
extern const unsigned char Sleeping[154];
|
||||
extern const unsigned char VolumeA0[129];
|
||||
extern const unsigned char VolumeA1[142];
|
||||
extern const unsigned char VolumeA2[146];
|
||||
extern const unsigned char VolumeB0[125];
|
||||
extern const unsigned char VolumeB1[137];
|
||||
extern const unsigned char VolumeB2[148];
|
||||
extern const unsigned char VolumeB3[153];
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 332 B |
After Width: | Height: | Size: 329 B |
After Width: | Height: | Size: 342 B |
After Width: | Height: | Size: 117 B |
After Width: | Height: | Size: 134 B |
After Width: | Height: | Size: 136 B |
After Width: | Height: | Size: 167 B |
After Width: | Height: | Size: 136 B |
After Width: | Height: | Size: 144 B |
After Width: | Height: | Size: 148 B |
After Width: | Height: | Size: 150 B |
After Width: | Height: | Size: 155 B |
After Width: | Height: | Size: 154 B |
After Width: | Height: | Size: 129 B |
After Width: | Height: | Size: 142 B |
After Width: | Height: | Size: 146 B |
After Width: | Height: | Size: 125 B |
After Width: | Height: | Size: 137 B |
After Width: | Height: | Size: 148 B |
After Width: | Height: | Size: 153 B |
|
@ -0,0 +1,137 @@
|
|||
inline auto Scheduler::reset() -> void {
|
||||
_threads.reset();
|
||||
}
|
||||
|
||||
inline auto Scheduler::threads() const -> u32 {
|
||||
return _threads.size();
|
||||
}
|
||||
|
||||
inline auto Scheduler::thread(u32 uniqueID) const -> maybe<Thread&> {
|
||||
for(auto& thread : _threads) {
|
||||
if(thread->_uniqueID == uniqueID) return *thread;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
//if threads A and B both have a clock value of 0, it is ambiguous which should run first.
|
||||
//to resolve this, a uniqueID is assigned to each thread when appended to the scheduler.
|
||||
//the first unused ID is selected, to avoid the uniqueID growing in an unbounded fashion.
|
||||
inline auto Scheduler::uniqueID() const -> u32 {
|
||||
u32 uniqueID = 0;
|
||||
while(thread(uniqueID)) uniqueID++;
|
||||
return uniqueID;
|
||||
}
|
||||
|
||||
//find the clock time of the furthest behind thread.
|
||||
inline auto Scheduler::minimum() const -> u64 {
|
||||
u64 minimum = (u64)-1;
|
||||
for(auto& thread : _threads) {
|
||||
minimum = min(minimum, thread->_clock - thread->_uniqueID);
|
||||
}
|
||||
return minimum;
|
||||
}
|
||||
|
||||
//find the clock time of the furthest ahead thread.
|
||||
inline auto Scheduler::maximum() const -> u64 {
|
||||
u64 maximum = 0;
|
||||
for(auto& thread : _threads) {
|
||||
maximum = max(maximum, thread->_clock - thread->_uniqueID);
|
||||
}
|
||||
return maximum;
|
||||
}
|
||||
|
||||
inline auto Scheduler::append(Thread& thread) -> bool {
|
||||
if(_threads.find(&thread)) return false;
|
||||
thread._uniqueID = uniqueID();
|
||||
thread._clock = maximum() + thread._uniqueID;
|
||||
_threads.append(&thread);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline auto Scheduler::remove(Thread& thread) -> void {
|
||||
_threads.removeByValue(&thread);
|
||||
}
|
||||
|
||||
//power cycle and soft reset events: assigns the primary thread and resets all thread clocks.
|
||||
inline auto Scheduler::power(Thread& thread) -> void {
|
||||
_primary = _resume = thread.handle();
|
||||
for(auto& thread : _threads) {
|
||||
thread->_clock = thread->_uniqueID;
|
||||
}
|
||||
}
|
||||
|
||||
inline auto Scheduler::enter(Mode mode) -> Event {
|
||||
if(mode == Mode::Run) {
|
||||
_mode = mode;
|
||||
_host = co_active();
|
||||
co_switch(_resume);
|
||||
platform->event(_event);
|
||||
return _event;
|
||||
}
|
||||
|
||||
if(mode == Mode::Synchronize) {
|
||||
//run all threads to safe points, starting with the primary thread.
|
||||
for(auto& thread : _threads) {
|
||||
if(thread->handle() == _primary) {
|
||||
_mode = Mode::SynchronizePrimary;
|
||||
_host = co_active();
|
||||
do {
|
||||
co_switch(_resume);
|
||||
platform->event(_event);
|
||||
} while(_event != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
for(auto& thread : _threads) {
|
||||
if(thread->handle() != _primary) {
|
||||
_mode = Mode::SynchronizeAuxiliary;
|
||||
_host = co_active();
|
||||
_resume = thread->handle();
|
||||
do {
|
||||
co_switch(_resume);
|
||||
platform->event(_event);
|
||||
} while(_event != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
return Event::Synchronize;
|
||||
}
|
||||
|
||||
return Event::None;
|
||||
}
|
||||
|
||||
inline auto Scheduler::exit(Event event) -> void {
|
||||
//subtract the minimum time from all threads to prevent clock overflow.
|
||||
auto reduce = minimum();
|
||||
for(auto& thread : _threads) {
|
||||
thread->_clock -= reduce;
|
||||
}
|
||||
|
||||
//return to the thread that entered the scheduler originally.
|
||||
_event = event;
|
||||
_resume = co_active();
|
||||
co_switch(_host);
|
||||
}
|
||||
|
||||
//used to prevent auxiliary threads from blocking during synchronization.
|
||||
//for instance, a secondary CPU waiting on an interrupt from the primary CPU.
|
||||
//as other threads are not run during synchronization, this would otherwise cause a deadlock.
|
||||
inline auto Scheduler::synchronizing() const -> bool {
|
||||
return _mode == Mode::SynchronizeAuxiliary;
|
||||
}
|
||||
|
||||
//marks a safe point (typically the beginning of the entry point) of a thread.
|
||||
//the scheduler may exit at these points for the purpose of synchronization.
|
||||
inline auto Scheduler::synchronize() -> void {
|
||||
if(co_active() == _primary) {
|
||||
if(_mode == Mode::SynchronizePrimary) return exit(Event::Synchronize);
|
||||
} else {
|
||||
if(_mode == Mode::SynchronizeAuxiliary) return exit(Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
inline auto Scheduler::getSynchronize() -> bool {
|
||||
return _synchronize;
|
||||
}
|
||||
|
||||
inline auto Scheduler::setSynchronize(bool synchronize) -> void {
|
||||
_synchronize = synchronize;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
struct Thread;
|
||||
|
||||
struct Scheduler {
|
||||
enum class Mode : u32 {
|
||||
Run,
|
||||
Synchronize,
|
||||
SynchronizePrimary,
|
||||
SynchronizeAuxiliary,
|
||||
};
|
||||
|
||||
Scheduler() = default;
|
||||
Scheduler(const Scheduler&) = delete;
|
||||
auto operator=(const Scheduler&) = delete;
|
||||
|
||||
auto reset() -> void;
|
||||
auto threads() const -> u32;
|
||||
auto thread(u32 threadID) const -> maybe<Thread&>;
|
||||
auto uniqueID() const -> u32;
|
||||
auto minimum() const -> u64;
|
||||
auto maximum() const -> u64;
|
||||
|
||||
auto append(Thread& thread) -> bool;
|
||||
auto remove(Thread& thread) -> void;
|
||||
|
||||
auto power(Thread& thread) -> void;
|
||||
auto enter(Mode mode = Mode::Run) -> Event;
|
||||
auto exit(Event event) -> void;
|
||||
|
||||
auto synchronizing() const -> bool;
|
||||
auto synchronize() -> void;
|
||||
|
||||
auto getSynchronize() -> bool;
|
||||
auto setSynchronize(bool) -> void;
|
||||
|
||||
private:
|
||||
cothread_t _host = nullptr; //program thread (used to exit scheduler)
|
||||
cothread_t _resume = nullptr; //resume thread (used to enter scheduler)
|
||||
cothread_t _primary = nullptr; //primary thread (used to synchronize components)
|
||||
Mode _mode = Mode::Run;
|
||||
Event _event = Event::Step;
|
||||
vector<Thread*> _threads;
|
||||
bool _synchronize = false;
|
||||
|
||||
friend class Thread;
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
|
@ -0,0 +1,119 @@
|
|||
inline auto Thread::EntryPoints() -> vector<EntryPoint>& {
|
||||
static vector<EntryPoint> entryPoints;
|
||||
return entryPoints;
|
||||
}
|
||||
|
||||
inline auto Thread::Enter() -> void {
|
||||
for(u32 index : range(EntryPoints().size())) {
|
||||
if(co_active() == EntryPoints()[index].handle) {
|
||||
auto entryPoint = EntryPoints()[index].entryPoint;
|
||||
EntryPoints().remove(index);
|
||||
while(true) {
|
||||
scheduler.synchronize();
|
||||
entryPoint();
|
||||
}
|
||||
}
|
||||
}
|
||||
struct ThreadNotFound{};
|
||||
throw ThreadNotFound{};
|
||||
}
|
||||
|
||||
inline Thread::~Thread() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
inline auto Thread::active() const -> bool { return co_active() == _handle; }
|
||||
inline auto Thread::handle() const -> cothread_t { return _handle; }
|
||||
inline auto Thread::frequency() const -> u64 { return _frequency; }
|
||||
inline auto Thread::scalar() const -> u64 { return _scalar; }
|
||||
inline auto Thread::clock() const -> u64 { return _clock; }
|
||||
|
||||
inline auto Thread::setHandle(cothread_t handle) -> void {
|
||||
_handle = handle;
|
||||
}
|
||||
|
||||
inline auto Thread::setFrequency(double frequency) -> void {
|
||||
_frequency = frequency + 0.5;
|
||||
_scalar = Second / _frequency;
|
||||
}
|
||||
|
||||
inline auto Thread::setScalar(u64 scalar) -> void {
|
||||
_scalar = scalar;
|
||||
}
|
||||
|
||||
inline auto Thread::setClock(u64 clock) -> void {
|
||||
_clock = clock;
|
||||
}
|
||||
|
||||
inline auto Thread::create(double frequency, function<void ()> entryPoint) -> void {
|
||||
if(!_handle) {
|
||||
_handle = co_create(Thread::Size, &Thread::Enter);
|
||||
} else {
|
||||
co_derive(_handle, Thread::Size, &Thread::Enter);
|
||||
}
|
||||
EntryPoints().append({_handle, entryPoint});
|
||||
setFrequency(frequency);
|
||||
setClock(0);
|
||||
scheduler.append(*this);
|
||||
}
|
||||
|
||||
//returns a thread to its entry point (eg for a reset), without resetting the clock value
|
||||
inline auto Thread::restart(function<void()> entryPoint) -> void {
|
||||
co_derive(_handle, Thread::Size, &Thread::Enter);
|
||||
EntryPoints().append({_handle, entryPoint});
|
||||
}
|
||||
|
||||
inline auto Thread::destroy() -> void {
|
||||
scheduler.remove(*this);
|
||||
if(_handle) co_delete(_handle);
|
||||
_handle = nullptr;
|
||||
}
|
||||
|
||||
inline auto Thread::step(u32 clocks) -> void {
|
||||
_clock += _scalar * clocks;
|
||||
}
|
||||
|
||||
//ensure all threads are caught up to the current thread before proceeding.
|
||||
inline auto Thread::synchronize() -> void {
|
||||
//note: this will call Thread::synchronize(*this) at some point, but this is safe:
|
||||
//the comparison will always fail as the current thread can never be behind itself.
|
||||
for(auto thread : scheduler._threads) synchronize(*thread);
|
||||
}
|
||||
|
||||
//ensure the specified thread(s) are caught up the current thread before proceeding.
|
||||
template<typename... P>
|
||||
inline auto Thread::synchronize(Thread& thread, P&&... p) -> void {
|
||||
//switching to another thread does not guarantee it will catch up before switching back.
|
||||
while(thread.clock() < clock()) {
|
||||
//disable synchronization for auxiliary threads during scheduler synchronization.
|
||||
//synchronization can begin inside of this while loop.
|
||||
if(scheduler.synchronizing()) break;
|
||||
co_switch(thread.handle());
|
||||
}
|
||||
//convenience: allow synchronizing multiple threads with one function call.
|
||||
if constexpr(sizeof...(p) > 0) synchronize(forward<P>(p)...);
|
||||
}
|
||||
|
||||
inline auto Thread::serialize(serializer& s) -> void {
|
||||
s(_frequency);
|
||||
s(_scalar);
|
||||
s(_clock);
|
||||
|
||||
if(!scheduler._synchronize) {
|
||||
static u8 stack[Thread::Size];
|
||||
bool resume = co_active() == _handle;
|
||||
|
||||
if(s.reading()) {
|
||||
s(stack);
|
||||
s(resume);
|
||||
memory::copy(_handle, stack, Thread::Size);
|
||||
if(resume) scheduler._resume = _handle;
|
||||
}
|
||||
|
||||
if(s.writing()) {
|
||||
memory::copy(stack, _handle, Thread::Size);
|
||||
s(stack);
|
||||
s(resume);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
struct Scheduler;
|
||||
|
||||
struct Thread {
|
||||
enum : u64 { Second = (u64)-1 >> 1 };
|
||||
enum : u64 { Size = 16_KiB * sizeof(void*) };
|
||||
|
||||
struct EntryPoint {
|
||||
cothread_t handle = nullptr;
|
||||
function<void ()> entryPoint;
|
||||
};
|
||||
|
||||
static auto EntryPoints() -> vector<EntryPoint>&;
|
||||
static auto Enter() -> void;
|
||||
|
||||
Thread() = default;
|
||||
Thread(const Thread&) = delete;
|
||||
auto operator=(const Thread&) = delete;
|
||||
virtual ~Thread();
|
||||
|
||||
explicit operator bool() const { return _handle; }
|
||||
auto active() const -> bool;
|
||||
auto handle() const -> cothread_t;
|
||||
auto frequency() const -> u64;
|
||||
auto scalar() const -> u64;
|
||||
auto clock() const -> u64;
|
||||
|
||||
auto setHandle(cothread_t handle) -> void;
|
||||
auto setFrequency(double frequency) -> void;
|
||||
auto setScalar(u64 scalar) -> void;
|
||||
auto setClock(u64 clock) -> void;
|
||||
|
||||
auto create(double frequency, function<void ()> entryPoint) -> void;
|
||||
auto restart(function<void ()> entryPoint) -> void;
|
||||
auto destroy() -> void;
|
||||
|
||||
auto step(u32 clocks) -> void;
|
||||
auto synchronize() -> void;
|
||||
template<typename... P> auto synchronize(Thread&, P&&...) -> void;
|
||||
|
||||
auto serialize(serializer& s) -> void;
|
||||
|
||||
protected:
|
||||
cothread_t _handle = nullptr;
|
||||
u32 _uniqueID = 0;
|
||||
u64 _frequency = 0;
|
||||
u64 _scalar = 0;
|
||||
u64 _clock = 0;
|
||||
|
||||
friend class Scheduler;
|
||||
};
|
|
@ -0,0 +1,134 @@
|
|||
using b1 = nall::Boolean;
|
||||
|
||||
using i1 = nall::Integer< 1>; using s1 = nall::IntegerPrimitive< 1>;
|
||||
using i2 = nall::Integer< 2>; using s2 = nall::IntegerPrimitive< 2>;
|
||||
using i3 = nall::Integer< 3>; using s3 = nall::IntegerPrimitive< 3>;
|
||||
using i4 = nall::Integer< 4>; using s4 = nall::IntegerPrimitive< 4>;
|
||||
using i5 = nall::Integer< 5>; using s5 = nall::IntegerPrimitive< 5>;
|
||||
using i6 = nall::Integer< 6>; using s6 = nall::IntegerPrimitive< 6>;
|
||||
using i7 = nall::Integer< 7>; using s7 = nall::IntegerPrimitive< 7>;
|
||||
using i8 = nall::Integer< 8>;
|
||||
using i9 = nall::Integer< 9>; using s9 = nall::IntegerPrimitive< 9>;
|
||||
using i10 = nall::Integer<10>; using s10 = nall::IntegerPrimitive<10>;
|
||||
using i11 = nall::Integer<11>; using s11 = nall::IntegerPrimitive<11>;
|
||||
using i12 = nall::Integer<12>; using s12 = nall::IntegerPrimitive<12>;
|
||||
using i13 = nall::Integer<13>; using s13 = nall::IntegerPrimitive<13>;
|
||||
using i14 = nall::Integer<14>; using s14 = nall::IntegerPrimitive<14>;
|
||||
using i15 = nall::Integer<15>; using s15 = nall::IntegerPrimitive<15>;
|
||||
using i16 = nall::Integer<16>;
|
||||
using i17 = nall::Integer<17>; using s17 = nall::IntegerPrimitive<17>;
|
||||
using i18 = nall::Integer<18>; using s18 = nall::IntegerPrimitive<18>;
|
||||
using i19 = nall::Integer<19>; using s19 = nall::IntegerPrimitive<19>;
|
||||
using i20 = nall::Integer<20>; using s20 = nall::IntegerPrimitive<20>;
|
||||
using i21 = nall::Integer<21>; using s21 = nall::IntegerPrimitive<21>;
|
||||
using i22 = nall::Integer<22>; using s22 = nall::IntegerPrimitive<22>;
|
||||
using i23 = nall::Integer<23>; using s23 = nall::IntegerPrimitive<23>;
|
||||
using i24 = nall::Integer<24>; using s24 = nall::IntegerPrimitive<24>;
|
||||
using i25 = nall::Integer<25>; using s25 = nall::IntegerPrimitive<25>;
|
||||
using i26 = nall::Integer<26>; using s26 = nall::IntegerPrimitive<26>;
|
||||
using i27 = nall::Integer<27>; using s27 = nall::IntegerPrimitive<27>;
|
||||
using i28 = nall::Integer<28>; using s28 = nall::IntegerPrimitive<28>;
|
||||
using i29 = nall::Integer<29>; using s29 = nall::IntegerPrimitive<29>;
|
||||
using i30 = nall::Integer<30>; using s30 = nall::IntegerPrimitive<30>;
|
||||
using i31 = nall::Integer<31>; using s31 = nall::IntegerPrimitive<31>;
|
||||
using i32 = nall::Integer<32>;
|
||||
using i33 = nall::Integer<33>; using s33 = nall::IntegerPrimitive<33>;
|
||||
using i34 = nall::Integer<34>; using s34 = nall::IntegerPrimitive<34>;
|
||||
using i35 = nall::Integer<35>; using s35 = nall::IntegerPrimitive<35>;
|
||||
using i36 = nall::Integer<36>; using s36 = nall::IntegerPrimitive<36>;
|
||||
using i37 = nall::Integer<37>; using s37 = nall::IntegerPrimitive<37>;
|
||||
using i38 = nall::Integer<38>; using s38 = nall::IntegerPrimitive<38>;
|
||||
using i39 = nall::Integer<39>; using s39 = nall::IntegerPrimitive<39>;
|
||||
using i40 = nall::Integer<40>; using s40 = nall::IntegerPrimitive<40>;
|
||||
using i41 = nall::Integer<41>; using s41 = nall::IntegerPrimitive<41>;
|
||||
using i42 = nall::Integer<42>; using s42 = nall::IntegerPrimitive<42>;
|
||||
using i43 = nall::Integer<43>; using s43 = nall::IntegerPrimitive<43>;
|
||||
using i44 = nall::Integer<44>; using s44 = nall::IntegerPrimitive<44>;
|
||||
using i45 = nall::Integer<45>; using s45 = nall::IntegerPrimitive<45>;
|
||||
using i46 = nall::Integer<46>; using s46 = nall::IntegerPrimitive<46>;
|
||||
using i47 = nall::Integer<47>; using s47 = nall::IntegerPrimitive<47>;
|
||||
using i48 = nall::Integer<48>; using s48 = nall::IntegerPrimitive<48>;
|
||||
using i49 = nall::Integer<49>; using s49 = nall::IntegerPrimitive<49>;
|
||||
using i50 = nall::Integer<50>; using s50 = nall::IntegerPrimitive<50>;
|
||||
using i51 = nall::Integer<51>; using s51 = nall::IntegerPrimitive<51>;
|
||||
using i52 = nall::Integer<52>; using s52 = nall::IntegerPrimitive<52>;
|
||||
using i53 = nall::Integer<53>; using s53 = nall::IntegerPrimitive<53>;
|
||||
using i54 = nall::Integer<54>; using s54 = nall::IntegerPrimitive<54>;
|
||||
using i55 = nall::Integer<55>; using s55 = nall::IntegerPrimitive<55>;
|
||||
using i56 = nall::Integer<56>; using s56 = nall::IntegerPrimitive<56>;
|
||||
using i57 = nall::Integer<57>; using s57 = nall::IntegerPrimitive<57>;
|
||||
using i58 = nall::Integer<58>; using s58 = nall::IntegerPrimitive<58>;
|
||||
using i59 = nall::Integer<59>; using s59 = nall::IntegerPrimitive<59>;
|
||||
using i60 = nall::Integer<60>; using s60 = nall::IntegerPrimitive<60>;
|
||||
using i61 = nall::Integer<61>; using s61 = nall::IntegerPrimitive<61>;
|
||||
using i62 = nall::Integer<62>; using s62 = nall::IntegerPrimitive<62>;
|
||||
using i63 = nall::Integer<63>; using s63 = nall::IntegerPrimitive<63>;
|
||||
using i64 = nall::Integer<64>;
|
||||
|
||||
using n1 = nall::Natural< 1>; using u1 = nall::NaturalPrimitive< 1>;
|
||||
using n2 = nall::Natural< 2>; using u2 = nall::NaturalPrimitive< 2>;
|
||||
using n3 = nall::Natural< 3>; using u3 = nall::NaturalPrimitive< 3>;
|
||||
using n4 = nall::Natural< 4>; using u4 = nall::NaturalPrimitive< 4>;
|
||||
using n5 = nall::Natural< 5>; using u5 = nall::NaturalPrimitive< 5>;
|
||||
using n6 = nall::Natural< 6>; using u6 = nall::NaturalPrimitive< 6>;
|
||||
using n7 = nall::Natural< 7>; using u7 = nall::NaturalPrimitive< 7>;
|
||||
using n8 = nall::Natural< 8>;
|
||||
using n9 = nall::Natural< 9>; using u9 = nall::NaturalPrimitive< 9>;
|
||||
using n10 = nall::Natural<10>; using u10 = nall::NaturalPrimitive<10>;
|
||||
using n11 = nall::Natural<11>; using u11 = nall::NaturalPrimitive<11>;
|
||||
using n12 = nall::Natural<12>; using u12 = nall::NaturalPrimitive<12>;
|
||||
using n13 = nall::Natural<13>; using u13 = nall::NaturalPrimitive<13>;
|
||||
using n14 = nall::Natural<14>; using u14 = nall::NaturalPrimitive<14>;
|
||||
using n15 = nall::Natural<15>; using u15 = nall::NaturalPrimitive<15>;
|
||||
using n16 = nall::Natural<16>;
|
||||
using n17 = nall::Natural<17>; using u17 = nall::NaturalPrimitive<17>;
|
||||
using n18 = nall::Natural<18>; using u18 = nall::NaturalPrimitive<18>;
|
||||
using n19 = nall::Natural<19>; using u19 = nall::NaturalPrimitive<19>;
|
||||
using n20 = nall::Natural<20>; using u20 = nall::NaturalPrimitive<20>;
|
||||
using n21 = nall::Natural<21>; using u21 = nall::NaturalPrimitive<21>;
|
||||
using n22 = nall::Natural<22>; using u22 = nall::NaturalPrimitive<22>;
|
||||
using n23 = nall::Natural<23>; using u23 = nall::NaturalPrimitive<23>;
|
||||
using n24 = nall::Natural<24>; using u24 = nall::NaturalPrimitive<24>;
|
||||
using n25 = nall::Natural<25>; using u25 = nall::NaturalPrimitive<25>;
|
||||
using n26 = nall::Natural<26>; using u26 = nall::NaturalPrimitive<26>;
|
||||
using n27 = nall::Natural<27>; using u27 = nall::NaturalPrimitive<27>;
|
||||
using n28 = nall::Natural<28>; using u28 = nall::NaturalPrimitive<28>;
|
||||
using n29 = nall::Natural<29>; using u29 = nall::NaturalPrimitive<29>;
|
||||
using n30 = nall::Natural<30>; using u30 = nall::NaturalPrimitive<30>;
|
||||
using n31 = nall::Natural<31>; using u31 = nall::NaturalPrimitive<31>;
|
||||
using n32 = nall::Natural<32>;
|
||||
using n33 = nall::Natural<33>; using u33 = nall::NaturalPrimitive<33>;
|
||||
using n34 = nall::Natural<34>; using u34 = nall::NaturalPrimitive<34>;
|
||||
using n35 = nall::Natural<35>; using u35 = nall::NaturalPrimitive<35>;
|
||||
using n36 = nall::Natural<36>; using u36 = nall::NaturalPrimitive<36>;
|
||||
using n37 = nall::Natural<37>; using u37 = nall::NaturalPrimitive<37>;
|
||||
using n38 = nall::Natural<38>; using u38 = nall::NaturalPrimitive<38>;
|
||||
using n39 = nall::Natural<39>; using u39 = nall::NaturalPrimitive<39>;
|
||||
using n40 = nall::Natural<40>; using u40 = nall::NaturalPrimitive<40>;
|
||||
using n41 = nall::Natural<41>; using u41 = nall::NaturalPrimitive<41>;
|
||||
using n42 = nall::Natural<42>; using u42 = nall::NaturalPrimitive<42>;
|
||||
using n43 = nall::Natural<43>; using u43 = nall::NaturalPrimitive<43>;
|
||||
using n44 = nall::Natural<44>; using u44 = nall::NaturalPrimitive<44>;
|
||||
using n45 = nall::Natural<45>; using u45 = nall::NaturalPrimitive<45>;
|
||||
using n46 = nall::Natural<46>; using u46 = nall::NaturalPrimitive<46>;
|
||||
using n47 = nall::Natural<47>; using u47 = nall::NaturalPrimitive<47>;
|
||||
using n48 = nall::Natural<48>; using u48 = nall::NaturalPrimitive<48>;
|
||||
using n49 = nall::Natural<49>; using u49 = nall::NaturalPrimitive<49>;
|
||||
using n50 = nall::Natural<50>; using u50 = nall::NaturalPrimitive<50>;
|
||||
using n51 = nall::Natural<51>; using u51 = nall::NaturalPrimitive<51>;
|
||||
using n52 = nall::Natural<52>; using u52 = nall::NaturalPrimitive<52>;
|
||||
using n53 = nall::Natural<53>; using u53 = nall::NaturalPrimitive<53>;
|
||||
using n54 = nall::Natural<54>; using u54 = nall::NaturalPrimitive<54>;
|
||||
using n55 = nall::Natural<55>; using u55 = nall::NaturalPrimitive<55>;
|
||||
using n56 = nall::Natural<56>; using u56 = nall::NaturalPrimitive<56>;
|
||||
using n57 = nall::Natural<57>; using u57 = nall::NaturalPrimitive<57>;
|
||||
using n58 = nall::Natural<58>; using u58 = nall::NaturalPrimitive<58>;
|
||||
using n59 = nall::Natural<59>; using u59 = nall::NaturalPrimitive<59>;
|
||||
using n60 = nall::Natural<60>; using u60 = nall::NaturalPrimitive<60>;
|
||||
using n61 = nall::Natural<61>; using u61 = nall::NaturalPrimitive<61>;
|
||||
using n62 = nall::Natural<62>; using u62 = nall::NaturalPrimitive<62>;
|
||||
using n63 = nall::Natural<63>; using u63 = nall::NaturalPrimitive<63>;
|
||||
using n64 = nall::Natural<64>;
|
||||
|
||||
using r32 = nall::Real<32>;
|
||||
using r64 = nall::Real<64>;
|
|
@ -0,0 +1,89 @@
|
|||
auto SM5K::disassembleInstruction() -> string {
|
||||
string s;
|
||||
|
||||
n8 opcode = ROM[PC + 0 & sizeof(ROM) - 1];
|
||||
n8 operand = ROM[PC + 1 & sizeof(ROM) - 1];
|
||||
|
||||
string p2 = {"0x", hex(n2(opcode), 1L)};
|
||||
string p4 = {"0x", hex(n4(opcode), 1L)};
|
||||
string p5 = {"0x", hex(n5(opcode), 2L)};
|
||||
string p6 = {"0x", hex(n6(opcode), 2L)};
|
||||
string p8 = {"0x", hex(n8(operand), 2L)};
|
||||
string pc = {"0x", hex(n4(opcode) << 8 | operand, 3L)};
|
||||
|
||||
switch(opcode) {
|
||||
case 0x00 ... 0x0f: s = {"adx ", p4}; break;
|
||||
case 0x10 ... 0x1f: s = {"lax ", p4}; break;
|
||||
case 0x20 ... 0x2f: s = {"lblx ", p4}; break;
|
||||
case 0x30 ... 0x3f: s = {"lbmx ", p4}; break;
|
||||
case 0x40 ... 0x43: s = {"rm ", p2}; break;
|
||||
case 0x44 ... 0x47: s = {"sm ", p2}; break;
|
||||
case 0x48 ... 0x4b: s = {"tm ", p2}; break;
|
||||
case 0x4c ... 0x4f: s = {"tpb ", p2}; break;
|
||||
case 0x50 ... 0x53: s = {"lda ", p2}; break;
|
||||
case 0x54 ... 0x57: s = {"exc ", p2}; break;
|
||||
case 0x58 ... 0x5b: s = {"exci ", p2}; break;
|
||||
case 0x5c ... 0x5f: s = {"excd ", p2}; break;
|
||||
case 0x60: s = {"rc " }; break;
|
||||
case 0x61: s = {"sc " }; break;
|
||||
case 0x62: s = {"id " }; break;
|
||||
case 0x63: s = {"ie " }; break;
|
||||
case 0x64: s = {"exax " }; break;
|
||||
case 0x65: s = {"atx " }; break;
|
||||
case 0x66: s = {"exbm " }; break;
|
||||
case 0x67: s = {"exbl " }; break;
|
||||
case 0x68: s = {"ex " }; break;
|
||||
case 0x69: s = {"dta ", p8}; break;
|
||||
case 0x6a: s = {"pat ", p8}; break;
|
||||
case 0x6b: s = {"tabl " }; break;
|
||||
case 0x6c: s = {"ta " }; break;
|
||||
case 0x6d: s = {"tb " }; break;
|
||||
case 0x6e: s = {"tc " }; break;
|
||||
case 0x6f: s = {"tam " }; break;
|
||||
case 0x70: s = {"inl " }; break;
|
||||
case 0x71: s = {"outl " }; break;
|
||||
case 0x72: s = {"anp " }; break;
|
||||
case 0x73: s = {"orp " }; break;
|
||||
case 0x74: s = {"in " }; break;
|
||||
case 0x75: s = {"out " }; break;
|
||||
case 0x76: s = {"stop " }; break;
|
||||
case 0x77: s = {"halt " }; break;
|
||||
case 0x78: s = {"incb " }; break;
|
||||
case 0x79: s = {"coma " }; break;
|
||||
case 0x7a: s = {"add " }; break;
|
||||
case 0x7b: s = {"adc " }; break;
|
||||
case 0x7c: s = {"decb " }; break;
|
||||
case 0x7d: s = {"rtn " }; break;
|
||||
case 0x7e: s = {"rtns " }; break;
|
||||
case 0x7f: s = {"rtni " }; break;
|
||||
case 0x80 ... 0xbf: s = {"tr ", p6}; break;
|
||||
case 0xc0 ... 0xdf: s = {"trs ", p5}; break;
|
||||
case 0xe0 ... 0xef: s = {"tl ", pc}; break;
|
||||
case 0xf0 ... 0xff: s = {"call ", pc}; break;
|
||||
}
|
||||
|
||||
while(s.size() < 10) s.append(" ");
|
||||
return s;
|
||||
}
|
||||
|
||||
auto SM5K::disassembleContext() -> string {
|
||||
string s;
|
||||
s.append("A:", hex(A, 1L), " ");
|
||||
s.append("X:", hex(X, 1L), " ");
|
||||
s.append("B:", hex(B, 2L), " ");
|
||||
s.append("C:", hex(C, 1L), " ");
|
||||
s.append("P0:", hex(P0, 1L), " ");
|
||||
s.append("P1:", hex(P1, 1L), " ");
|
||||
s.append("P2:", hex(P2, 1L), " ");
|
||||
s.append("P3:", hex(P3, 1L), " ");
|
||||
s.append("P4:", hex(P4, 1L), " ");
|
||||
s.append("P5:", hex(P5, 1L), " ");
|
||||
s.append("SP:", hex(SP, 1L), " ");
|
||||
s.append("SB:", hex(SB, 2L), " ");
|
||||
s.append("IFA:", hex(IFA, 1L), " ");
|
||||
s.append("IFB:", hex(IFB, 1L), " ");
|
||||
s.append("IFT:", hex(IFT, 1L), " ");
|
||||
s.append("IME:", hex(IME, 1L), " ");
|
||||
s.append("SKIP:", hex(SKIP, 1L));
|
||||
return s;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
#define op(id, name, ...) \
|
||||
case id: \
|
||||
if(SKIP) { SKIP = 0; return; } \
|
||||
return instruction##name(__VA_ARGS__); \
|
||||
|
||||
auto SM5K::interrupt(n2 id) -> void {
|
||||
SR[SP++] = PC;
|
||||
PU = 2;
|
||||
PL = id << 1;
|
||||
HALT = 0;
|
||||
STOP = 0;
|
||||
}
|
||||
|
||||
auto SM5K::instruction() -> void {
|
||||
if(IFA & RE.bit(0) & IME) return interrupt(0);
|
||||
if(IFB & RE.bit(1) & IME) return interrupt(1);
|
||||
if(IFT & RE.bit(2) & IME) return interrupt(2);
|
||||
if(HALT) return timerStep();
|
||||
if(STOP) return timerStep();
|
||||
|
||||
n8 opcode = fetch();
|
||||
switch(opcode) {
|
||||
op(0x00 ... 0x0f, ADX, n4(opcode));
|
||||
op(0x10 ... 0x1f, LAX, n4(opcode));
|
||||
op(0x20 ... 0x2f, LBLX, n4(opcode));
|
||||
op(0x30 ... 0x3f, LBMX, n4(opcode));
|
||||
op(0x40 ... 0x43, RM, n2(opcode));
|
||||
op(0x44 ... 0x47, SM, n2(opcode));
|
||||
op(0x48 ... 0x4b, TM, n2(opcode));
|
||||
op(0x4c ... 0x4f, TPB, n2(opcode));
|
||||
op(0x50 ... 0x53, LDA, n2(opcode));
|
||||
op(0x54 ... 0x57, EXC, n2(opcode));
|
||||
op(0x58 ... 0x5b, EXCI, n2(opcode));
|
||||
op(0x5c ... 0x5f, EXCD, n2(opcode));
|
||||
op(0x60, RC );
|
||||
op(0x61, SC );
|
||||
op(0x62, ID );
|
||||
op(0x63, IE );
|
||||
op(0x64, EXAX );
|
||||
op(0x65, ATX );
|
||||
op(0x66, EXBM );
|
||||
op(0x67, EXBL );
|
||||
op(0x68, EX );
|
||||
op(0x69, DTA, fetch());
|
||||
op(0x6a, PAT, fetch());
|
||||
op(0x6b, TABL );
|
||||
op(0x6c, TA );
|
||||
op(0x6d, TB );
|
||||
op(0x6e, TC );
|
||||
op(0x6f, TAM );
|
||||
op(0x70, INL );
|
||||
op(0x71, OUTL );
|
||||
op(0x72, ANP );
|
||||
op(0x73, ORP );
|
||||
op(0x74, IN );
|
||||
op(0x75, OUT );
|
||||
op(0x76, STOP );
|
||||
op(0x77, HALT );
|
||||
op(0x78, INCB );
|
||||
op(0x79, COMA );
|
||||
op(0x7a, ADD );
|
||||
op(0x7b, ADC );
|
||||
op(0x7c, DECB );
|
||||
op(0x7d, RTN );
|
||||
op(0x7e, RTNS );
|
||||
op(0x7f, RTNI );
|
||||
op(0x80 ... 0xbf, TR, n6(opcode));
|
||||
op(0xc0 ... 0xdf, TRS, n5(opcode));
|
||||
op(0xe0 ... 0xef, TL, n4(opcode) << 8 | fetch());
|
||||
op(0xf0 ... 0xff, CALL, n4(opcode) << 8 | fetch());
|
||||
}
|
||||
}
|
||||
|
||||
#undef op
|
|
@ -0,0 +1,275 @@
|
|||
auto SM5K::instructionADC() -> void {
|
||||
auto c = C;
|
||||
C = A + RAM[B] + c >= 0x10;
|
||||
A = A + RAM[B] + c;
|
||||
if(C) SKIP = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionADD() -> void {
|
||||
A += RAM[B];
|
||||
}
|
||||
|
||||
auto SM5K::instructionADX(n4 data) -> void {
|
||||
if(A + data >= 0x10) SKIP = 1;
|
||||
A += data;
|
||||
}
|
||||
|
||||
auto SM5K::instructionANP() -> void {
|
||||
switch(BL) {
|
||||
case 0x0: P0 &= A; break;
|
||||
case 0x2: P2 &= A; break;
|
||||
case 0x4: P4 &= A; break;
|
||||
case 0x5: P5 &= A; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto SM5K::instructionATX() -> void {
|
||||
X = A;
|
||||
}
|
||||
|
||||
auto SM5K::instructionCALL(n12 address) -> void {
|
||||
SR[SP++] = PC;
|
||||
PC = address;
|
||||
}
|
||||
|
||||
auto SM5K::instructionCOMA() -> void {
|
||||
A = ~A;
|
||||
}
|
||||
|
||||
auto SM5K::instructionDECB() -> void {
|
||||
if(!--BL) SKIP = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionDR() -> void {
|
||||
DIV = 0;
|
||||
}
|
||||
|
||||
auto SM5K::instructionDTA(n8 operand) -> void {
|
||||
switch(operand) {
|
||||
case 0x02: instructionTT(); break;
|
||||
case 0x03: instructionDR(); break;
|
||||
}
|
||||
|
||||
static constexpr u8 rom[8] = {0xfc, 0xfc, 0xa5, 0x6c, 0x03, 0x8f, 0x1b, 0x9a};
|
||||
if(BM >= 4 && BM <= 7) {
|
||||
SKIP = rom[BM << 1 | BL >> 3] >> n3(BL) & 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto SM5K::instructionEX() -> void {
|
||||
swap(B, SB);
|
||||
}
|
||||
|
||||
auto SM5K::instructionEXAX() -> void {
|
||||
swap(A, X);
|
||||
}
|
||||
|
||||
auto SM5K::instructionEXBL() -> void {
|
||||
auto a = A;
|
||||
A = BL;
|
||||
BL = a;
|
||||
}
|
||||
|
||||
auto SM5K::instructionEXBM() -> void {
|
||||
auto a = A;
|
||||
A = BM;
|
||||
BM = a;
|
||||
}
|
||||
|
||||
auto SM5K::instructionEXC(n2 data) -> void {
|
||||
swap(A, RAM[B]);
|
||||
BM ^= data;
|
||||
}
|
||||
|
||||
auto SM5K::instructionEXCD(n2 data) -> void {
|
||||
swap(A, RAM[B]);
|
||||
if(!--BL) SKIP = 1;
|
||||
BM ^= data;
|
||||
}
|
||||
|
||||
auto SM5K::instructionEXCI(n2 data) -> void {
|
||||
swap(A, RAM[B]);
|
||||
if(!++BL) SKIP = 1;
|
||||
BM ^= data;
|
||||
}
|
||||
|
||||
auto SM5K::instructionID() -> void {
|
||||
IME = 0;
|
||||
}
|
||||
|
||||
auto SM5K::instructionIE() -> void {
|
||||
IME = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionHALT() -> void {
|
||||
HALT = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionIN() -> void {
|
||||
switch(BL) {
|
||||
case 0x1: A = P1; break;
|
||||
case 0x2: A = P2; break;
|
||||
case 0x3: A = P3; break;
|
||||
case 0x4: A = P4; break;
|
||||
case 0x5: A = P5; break;
|
||||
case 0x8: A = R8 >> 0; X = R8 >> 4; break;
|
||||
case 0x9: A = R9 >> 0; X = R9 >> 4; break;
|
||||
case 0xa: A = RA >> 0; X = RA >> 4; break;
|
||||
case 0xb: A = RB >> 0; X = RB >> 4; break;
|
||||
case 0xc: A = RC; break;
|
||||
case 0xe: A = RE; break;
|
||||
case 0xf: A = RF; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto SM5K::instructionINCB() -> void {
|
||||
if(!++BL) SKIP = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionINL() -> void {
|
||||
A = P1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionLAX(n4 data) -> void {
|
||||
A = data;
|
||||
}
|
||||
|
||||
auto SM5K::instructionLBLX(n4 data) -> void {
|
||||
BL = data;
|
||||
}
|
||||
|
||||
auto SM5K::instructionLBMX(n4 data) -> void {
|
||||
BM = data;
|
||||
}
|
||||
|
||||
auto SM5K::instructionLDA(n2 data) -> void {
|
||||
A = RAM[B];
|
||||
BM ^= data;
|
||||
}
|
||||
|
||||
auto SM5K::instructionORP() -> void {
|
||||
switch(BL) {
|
||||
case 0x0: P0 |= A; break;
|
||||
case 0x2: P2 |= A; break;
|
||||
case 0x4: P4 |= A; break;
|
||||
case 0x5: P5 |= A; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto SM5K::instructionOUT() -> void {
|
||||
switch(BL) {
|
||||
case 0x0: P0 = A; break;
|
||||
case 0x2: P2 = A; break;
|
||||
case 0x3: R3 = A; break;
|
||||
case 0x4: P4 = A; break;
|
||||
case 0x5: P5 = A; break;
|
||||
case 0x8: R8 = A << 0 | X << 4; break;
|
||||
case 0x9: R9 = A << 0 | X << 4; break;
|
||||
case 0xa: RA = RB; break;
|
||||
case 0xb: RB = A << 0 | X << 4; break;
|
||||
case 0xc: RC = A; break;
|
||||
case 0xe: RE = A; break;
|
||||
case 0xf: RF = A; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto SM5K::instructionOUTL() -> void {
|
||||
P0 = A;
|
||||
}
|
||||
|
||||
auto SM5K::instructionPAT(n8) -> void {
|
||||
//should this actually modify the stack frame?
|
||||
n6 pu = 4;
|
||||
n6 pl = X << 4 | A;
|
||||
n8 data = ROM[pu << 6 | pl];
|
||||
A = data >> 0;
|
||||
X = data >> 4;
|
||||
}
|
||||
|
||||
auto SM5K::instructionRC() -> void {
|
||||
C = 0;
|
||||
}
|
||||
|
||||
auto SM5K::instructionRM(n2 data) -> void {
|
||||
RAM[B] &= ~(1 << data);
|
||||
}
|
||||
|
||||
auto SM5K::instructionRTN() -> void {
|
||||
PC = SR[--SP];
|
||||
}
|
||||
|
||||
auto SM5K::instructionRTNI() -> void {
|
||||
PC = SR[--SP];
|
||||
IME = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionRTNS() -> void {
|
||||
PC = SR[--SP];
|
||||
SKIP = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionSC() -> void {
|
||||
C = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionSM(n2 data) -> void {
|
||||
RAM[B] |= 1 << data;
|
||||
}
|
||||
|
||||
auto SM5K::instructionSTOP() -> void {
|
||||
STOP = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTA() -> void {
|
||||
if(IFA) SKIP = 1;
|
||||
IFA = 0;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTABL() -> void {
|
||||
if(A == BL) SKIP = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTAM() -> void {
|
||||
if(A == RAM[B]) SKIP = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTB() -> void {
|
||||
if(IFB) SKIP = 1;
|
||||
IFB = 0;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTC() -> void {
|
||||
if(C == 1) SKIP = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTL(n12 address) -> void {
|
||||
PC = address;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTM(n2 data) -> void {
|
||||
if(RAM[B] & 1 << data) SKIP = 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTPB(n2 port) -> void {
|
||||
switch(port) {
|
||||
case 0: if(P0 == 1) SKIP = 1; break;
|
||||
case 1: if(P1 == 1) SKIP = 1; break;
|
||||
case 2: if(P2 == 1) SKIP = 1; break;
|
||||
case 3: if(P3 == 1) SKIP = 1; break;
|
||||
}
|
||||
}
|
||||
|
||||
auto SM5K::instructionTR(n6 address) -> void {
|
||||
PL = address;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTRS(n5 address) -> void {
|
||||
SR[SP++] = PC;
|
||||
PU = 1;
|
||||
PL = address << 1;
|
||||
}
|
||||
|
||||
auto SM5K::instructionTT() -> void {
|
||||
if(IFT) SKIP = 1;
|
||||
IFT = 0;
|
||||
}
|