diff --git a/.gitignore b/.gitignore index 2de037631d..db2295c5f0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /snes9xgit **/.vs/** +**/.vscode/ **/bin/** **/obj/** /output/** diff --git a/src/BizHawk.Client.Common/Api/Classes/EmulationApi.cs b/src/BizHawk.Client.Common/Api/Classes/EmulationApi.cs index 058acfce63..ea155f8f9f 100644 --- a/src/BizHawk.Client.Common/Api/Classes/EmulationApi.cs +++ b/src/BizHawk.Client.Common/Api/Classes/EmulationApi.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES; using BizHawk.Emulation.Cores.Consoles.Sega.gpgx; +using BizHawk.Emulation.Cores.Nintendo.BSNES; using BizHawk.Emulation.Cores.Nintendo.NES; using BizHawk.Emulation.Cores.Nintendo.SNES; using BizHawk.Emulation.Cores.PCEngine; @@ -214,7 +215,7 @@ namespace BizHawk.Client.Common public void SetRenderPlanes(params bool[] args) { static bool GetSetting(bool[] settings, int index) => index >= settings.Length || settings[index]; - void SetBsnes(LibsnesCore core) + void SetLibsnes(LibsnesCore core) { var s = core.GetSettings(); s.ShowBG1_0 = s.ShowBG1_1 = GetSetting(args, 0); @@ -227,6 +228,20 @@ namespace BizHawk.Client.Common s.ShowOBJ_3 = GetSetting(args, 7); core.PutSettings(s); } + void SetBsnes(BsnesCore core) + { + var s = core.GetSettings(); + // TODO: This should probably support both prios inidividually but I have no idea whether changing this breaks anything + s.ShowBG1_0 = s.ShowBG1_1 = GetSetting(args, 0); + s.ShowBG2_0 = s.ShowBG2_1 = GetSetting(args, 1); + s.ShowBG3_0 = s.ShowBG3_1 = GetSetting(args, 2); + s.ShowBG4_0 = s.ShowBG4_1 = GetSetting(args, 3); + s.ShowOBJ_0 = GetSetting(args, 4); + s.ShowOBJ_1 = GetSetting(args, 5); + s.ShowOBJ_2 = GetSetting(args, 6); + s.ShowOBJ_3 = GetSetting(args, 7); + core.PutSettings(s); + } void SetCygne(WonderSwan ws) { var s = ws.GetSettings(); @@ -286,7 +301,10 @@ namespace BizHawk.Client.Common SetGPGX(gpgx); break; case LibsnesCore snes: - SetBsnes(snes); + SetLibsnes(snes); + break; + case BsnesCore bsnes: + SetBsnes(bsnes); break; case NES nes: SetNesHawk(nes); diff --git a/src/BizHawk.Client.Common/config/Config.cs b/src/BizHawk.Client.Common/config/Config.cs index 24358a414d..879802dafc 100644 --- a/src/BizHawk.Client.Common/config/Config.cs +++ b/src/BizHawk.Client.Common/config/Config.cs @@ -22,9 +22,9 @@ namespace BizHawk.Client.Common (new[] { "NES" }, new[] { CoreNames.QuickNes, CoreNames.NesHawk, CoreNames.SubNesHawk }), (new[] { "SNES" }, - new[] { CoreNames.Faust, CoreNames.Snes9X, CoreNames.Bsnes }), + new[] { CoreNames.Faust, CoreNames.Snes9X, CoreNames.Bsnes, CoreNames.Bsnes115 }), (new[] { "SGB" }, - new[] { CoreNames.SameBoy, CoreNames.Bsnes }), + new[] { CoreNames.SameBoy, CoreNames.Bsnes, CoreNames.Bsnes115}), (new[] { "GB", "GBC" }, new[] { CoreNames.Gambatte, CoreNames.GbHawk, CoreNames.SubGbHawk }), (new[] { "DGB" }, @@ -208,7 +208,7 @@ namespace BizHawk.Client.Common public int AlertMessageColor { get; set; } = DefaultMessagePositions.AlertMessageColor; public int LastInputColor { get; set; } = DefaultMessagePositions.LastInputColor; public int MovieInput { get; set; } = DefaultMessagePositions.MovieInput; - + public int DispPrescale { get; set; } = 1; private static bool DetectDirectX() @@ -347,4 +347,4 @@ namespace BizHawk.Client.Common public bool UseStaticWindowTitles { get; set; } } -} \ No newline at end of file +} diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs index c7b4980db5..e2f413313c 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -22,6 +22,7 @@ using BizHawk.Emulation.Cores.Computers.SinclairSpectrum; using BizHawk.Emulation.Cores.Consoles.Nintendo.NDS; using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES; using BizHawk.Emulation.Cores.Intellivision; +using BizHawk.Emulation.Cores.Nintendo.BSNES; using BizHawk.Emulation.Cores.Nintendo.Gameboy; using BizHawk.Emulation.Cores.Nintendo.N64; using BizHawk.Emulation.Cores.Nintendo.NES; @@ -1472,7 +1473,7 @@ namespace BizHawk.Client.EmuHawk using var dlg = new NESSyncSettingsForm(this, sub.GetSyncSettings().Clone(), sub.HasMapperProperties); dlg.ShowDialog(this); } - + } private void BarcodeReaderMenuItem_Click(object sender, EventArgs e) @@ -1656,6 +1657,11 @@ namespace BizHawk.Client.EmuHawk using var form = new SNESControllerSettings(this, bsnes.GetSyncSettings().Clone()); form.ShowDialog(); } + else if (Emulator is BsnesCore bsnesCore) + { + using var form = new BSNESControllerSettings(this, bsnesCore.GetSyncSettings().Clone()); + form.ShowDialog(); + } } private void SnesGfxDebuggerMenuItem_Click(object sender, EventArgs e) @@ -1665,9 +1671,13 @@ namespace BizHawk.Client.EmuHawk private void SnesOptionsMenuItem_Click(object sender, EventArgs e) { - if (Emulator is LibsnesCore bsnes) + if (Emulator is LibsnesCore libsnes) { - SNESOptions.DoSettingsDialog(this, bsnes); + SNESOptions.DoSettingsDialog(this, libsnes); + } + if (Emulator is BsnesCore bsnes) + { + BSNESOptions.DoSettingsDialog(this, bsnes); } } @@ -2144,7 +2154,7 @@ namespace BizHawk.Client.EmuHawk using var form = new AmstradCpcNonSyncSettings(this, cpc.GetSettings().Clone()); form.ShowDialog(); } - + } private void HelpSubMenu_DropDownOpened(object sender, EventArgs e) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 3f778e21fe..178978466a 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -22,6 +22,7 @@ using BizHawk.Bizware.BizwareGL; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores; using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES; +using BizHawk.Emulation.Cores.Nintendo.BSNES; using BizHawk.Emulation.Cores.Nintendo.GBA; using BizHawk.Emulation.Cores.Nintendo.NES; using BizHawk.Emulation.Cores.Nintendo.SNES; @@ -138,7 +139,7 @@ namespace BizHawk.Client.EmuHawk tuple.Item1 == requestedExtToolDll || Path.GetFileName(tuple.Item1) == requestedExtToolDll || Path.GetFileNameWithoutExtension(tuple.Item1) == requestedExtToolDll); - + if(foundIndex != -1) loaded = Tools.LoadExternalToolForm(enabled[foundIndex].Item1, enabled[foundIndex].Item2, skipExtToolWarning: true); } @@ -672,7 +673,7 @@ namespace BizHawk.Client.EmuHawk _needsFullscreenOnLoad = false; ToggleFullscreen(); } - + // Simply exit the program if the version is asked for if (_argParser.printVersion) { @@ -1425,53 +1426,80 @@ namespace BizHawk.Client.EmuHawk private void SNES_ToggleBg(int layer) { - if (!(Emulator is LibsnesCore || Emulator is Snes9x) || !1.RangeTo(4).Contains(layer)) + if (Emulator is not (BsnesCore or LibsnesCore or Snes9x) || !1.RangeTo(4).Contains(layer)) { return; } bool result = false; - if (Emulator is LibsnesCore bsnes) + switch (Emulator) { - var s = bsnes.GetSettings(); - switch (layer) + case BsnesCore bsnes: { - case 1: - result = s.ShowBG1_0 = s.ShowBG1_1 ^= true; - break; - case 2: - result = s.ShowBG2_0 = s.ShowBG2_1 ^= true; - break; - case 3: - result = s.ShowBG3_0 = s.ShowBG3_1 ^= true; - break; - case 4: - result = s.ShowBG4_0 = s.ShowBG4_1 ^= true; - break; - } + var s = bsnes.GetSettings(); + switch (layer) + { + case 1: + result = s.ShowBG1_0 = s.ShowBG1_1 ^= true; + break; + case 2: + result = s.ShowBG2_0 = s.ShowBG2_1 ^= true; + break; + case 3: + result = s.ShowBG3_0 = s.ShowBG3_1 ^= true; + break; + case 4: + result = s.ShowBG4_0 = s.ShowBG4_1 ^= true; + break; + } - bsnes.PutSettings(s); - } - else if (Emulator is Snes9x snes9X) - { - var s = snes9X.GetSettings(); - switch (layer) + bsnes.PutSettings(s); + break; + } + case LibsnesCore libsnes: { - case 1: - result = s.ShowBg0 ^= true; - break; - case 2: - result = s.ShowBg1 ^= true; - break; - case 3: - result = s.ShowBg2 ^= true; - break; - case 4: - result = s.ShowBg3 ^= true; - break; - } + var s = libsnes.GetSettings(); + switch (layer) + { + case 1: + result = s.ShowBG1_0 = s.ShowBG1_1 ^= true; + break; + case 2: + result = s.ShowBG2_0 = s.ShowBG2_1 ^= true; + break; + case 3: + result = s.ShowBG3_0 = s.ShowBG3_1 ^= true; + break; + case 4: + result = s.ShowBG4_0 = s.ShowBG4_1 ^= true; + break; + } - snes9X.PutSettings(s); + libsnes.PutSettings(s); + break; + } + case Snes9x snes9X: + { + var s = snes9X.GetSettings(); + switch (layer) + { + case 1: + result = s.ShowBg0 ^= true; + break; + case 2: + result = s.ShowBg1 ^= true; + break; + case 3: + result = s.ShowBg2 ^= true; + break; + case 4: + result = s.ShowBg3 ^= true; + break; + } + + snes9X.PutSettings(s); + break; + } } AddOnScreenMessage($"BG {layer} Layer {(result ? "On" : "Off")}"); @@ -1982,6 +2010,11 @@ namespace BizHawk.Client.EmuHawk SNESSubMenu.Text = "&SNES"; SNESSubMenu.Visible = true; break; + case "SNES" when Emulator is BsnesCore bsnesCore: + SNESSubMenu.Text = bsnesCore.IsSGB ? "&SGB" : "&SNES"; + SNESSubMenu.DropDownItems[2].Visible = false; + SNESSubMenu.Visible = true; + break; default: DisplayDefaultCoreMenu(); break; @@ -2637,7 +2670,7 @@ namespace BizHawk.Client.EmuHawk { if (Config.ClockThrottle) return true; - + AddOnScreenMessage("Unable to change speed, please switch to clock throttle"); return false; } diff --git a/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.Designer.cs b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.Designer.cs new file mode 100644 index 0000000000..6f3846f028 --- /dev/null +++ b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.Designer.cs @@ -0,0 +1,184 @@ +using System; +using BizHawk.Emulation.Cores.Nintendo.SNES; + +namespace BizHawk.Client.EmuHawk +{ + partial class BSNESControllerSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.OkBtn = new System.Windows.Forms.Button(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.Port2ComboBox = new System.Windows.Forms.ComboBox(); + this.Port1ComboBox = new System.Windows.Forms.ComboBox(); + this.LimitAnalogChangeCheckBox = new System.Windows.Forms.CheckBox(); + this.MouseNagLabel1 = new BizHawk.WinForms.Controls.LocLabelEx(); + this.MouseSpeedLabel1 = new BizHawk.WinForms.Controls.LocLabelEx(); + this.label5 = new BizHawk.WinForms.Controls.LocLabelEx(); + this.label4 = new BizHawk.WinForms.Controls.LocLabelEx(); + this.label1 = new BizHawk.WinForms.Controls.LocLabelEx(); + this.SuspendLayout(); + // + // OkBtn + // + this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.OkBtn.Location = new System.Drawing.Point(170, 264); + this.OkBtn.Name = "OkBtn"; + this.OkBtn.Size = new System.Drawing.Size(60, 23); + this.OkBtn.TabIndex = 4; + this.OkBtn.Text = "&OK"; + this.OkBtn.UseVisualStyleBackColor = true; + this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); + // + // CancelBtn + // + this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.CancelBtn.Location = new System.Drawing.Point(236, 264); + this.CancelBtn.Name = "CancelBtn"; + this.CancelBtn.Size = new System.Drawing.Size(60, 23); + this.CancelBtn.TabIndex = 5; + this.CancelBtn.Text = "&Cancel"; + this.CancelBtn.UseVisualStyleBackColor = true; + this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); + // + // Port2ComboBox + // + this.Port2ComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.Port2ComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.Port2ComboBox.FormattingEnabled = true; + this.Port2ComboBox.Items.AddRange(new object[] { + "None", + "Gamepad", + "Mouse"}); + this.Port2ComboBox.Location = new System.Drawing.Point(12, 104); + this.Port2ComboBox.Name = "Port2ComboBox"; + this.Port2ComboBox.Size = new System.Drawing.Size(284, 21); + this.Port2ComboBox.TabIndex = 20; + this.Port2ComboBox.SelectedIndexChanged += new System.EventHandler(this.PortComboBox_SelectedIndexChanged); + // + // Port1ComboBox + // + this.Port1ComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.Port1ComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.Port1ComboBox.FormattingEnabled = true; + this.Port1ComboBox.Items.AddRange(new object[] { + "None", + "Gamepad", + "Mouse"}); + this.Port1ComboBox.Location = new System.Drawing.Point(12, 54); + this.Port1ComboBox.Name = "Port1ComboBox"; + this.Port1ComboBox.Size = new System.Drawing.Size(284, 21); + this.Port1ComboBox.TabIndex = 19; + this.Port1ComboBox.SelectedIndexChanged += new System.EventHandler(this.PortComboBox_SelectedIndexChanged); + // + // LimitAnalogChangeCheckBox + // + this.LimitAnalogChangeCheckBox.AutoSize = true; + this.LimitAnalogChangeCheckBox.Location = new System.Drawing.Point(15, 175); + this.LimitAnalogChangeCheckBox.Name = "LimitAnalogChangeCheckBox"; + this.LimitAnalogChangeCheckBox.Size = new System.Drawing.Size(173, 17); + this.LimitAnalogChangeCheckBox.TabIndex = 24; + this.LimitAnalogChangeCheckBox.Text = "Limit Analog Change Sensitivity"; + this.LimitAnalogChangeCheckBox.UseVisualStyleBackColor = true; + // + // MouseNagLabel1 + // + this.MouseNagLabel1.Location = new System.Drawing.Point(12, 135); + this.MouseNagLabel1.MaximumSize = new System.Drawing.Size(300, 0); + this.MouseNagLabel1.Name = "MouseNagLabel1"; + this.MouseNagLabel1.Text = "*Note: mouse and scope controls should be bound to an analog stick, not the mouse" + + "."; + // + // MouseSpeedLabel1 + // + this.MouseSpeedLabel1.Location = new System.Drawing.Point(12, 195); + this.MouseSpeedLabel1.MaximumSize = new System.Drawing.Size(300, 0); + this.MouseSpeedLabel1.Name = "MouseSpeedLabel1"; + this.MouseSpeedLabel1.Text = "For casual play this should be checked.\nThe full range of values are rather unusa" + + "ble in normal situations, but good if you need total control"; + // + // label5 + // + this.label5.Location = new System.Drawing.Point(12, 88); + this.label5.Name = "label5"; + this.label5.Text = "Port 2:"; + // + // label4 + // + this.label4.Location = new System.Drawing.Point(12, 38); + this.label4.Name = "label4"; + this.label4.Text = "Port 1:"; + // + // label1 + // + this.label1.Location = new System.Drawing.Point(12, 9); + this.label1.Name = "label1"; + this.label1.Text = "SNES Controller Settings"; + // + // BSNESControllerSettings + // + this.AcceptButton = this.OkBtn; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.CancelBtn; + this.ClientSize = new System.Drawing.Size(308, 299); + this.Controls.Add(this.MouseNagLabel1); + this.Controls.Add(this.LimitAnalogChangeCheckBox); + this.Controls.Add(this.MouseSpeedLabel1); + this.Controls.Add(this.label5); + this.Controls.Add(this.label4); + this.Controls.Add(this.Port2ComboBox); + this.Controls.Add(this.Port1ComboBox); + this.Controls.Add(this.label1); + this.Controls.Add(this.CancelBtn); + this.Controls.Add(this.OkBtn); + this.Name = "BSNESControllerSettings"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Controller Settings"; + this.Load += new System.EventHandler(this.SNESControllerSettings_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button OkBtn; + private System.Windows.Forms.Button CancelBtn; + private BizHawk.WinForms.Controls.LocLabelEx label1; + private BizHawk.WinForms.Controls.LocLabelEx label5; + private BizHawk.WinForms.Controls.LocLabelEx label4; + private System.Windows.Forms.ComboBox Port2ComboBox; + private System.Windows.Forms.ComboBox Port1ComboBox; + private BizHawk.WinForms.Controls.LocLabelEx MouseSpeedLabel1; + private System.Windows.Forms.CheckBox LimitAnalogChangeCheckBox; + private BizHawk.WinForms.Controls.LocLabelEx MouseNagLabel1; + } +} diff --git a/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.cs b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.cs new file mode 100644 index 0000000000..22a400f54a --- /dev/null +++ b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.cs @@ -0,0 +1,73 @@ +using System; +using System.Windows.Forms; +using BizHawk.Emulation.Cores.Nintendo.BSNES; +using BizHawk.Emulation.Cores.Nintendo.SNES; + +namespace BizHawk.Client.EmuHawk +{ + public partial class BSNESControllerSettings : Form + { + private readonly IMainFormForConfig _mainForm; + private readonly BsnesCore.SnesSyncSettings _syncSettings; + + public BSNESControllerSettings( + IMainFormForConfig mainForm, + BsnesCore.SnesSyncSettings syncSettings) + { + _mainForm = mainForm; + _syncSettings = syncSettings; + InitializeComponent(); + Icon = Properties.Resources.GameControllerIcon; + } + + private void SNESControllerSettings_Load(object sender, EventArgs e) + { + LimitAnalogChangeCheckBox.Checked = _syncSettings.LimitAnalogChangeSensitivity; + + Port1ComboBox.SelectedIndex = (int) _syncSettings.LeftPort >= Port1ComboBox.Items.Count ? 0 : (int) _syncSettings.LeftPort; + Port2ComboBox.PopulateFromEnum(_syncSettings.RightPort); + } + + private void OkBtn_Click(object sender, EventArgs e) + { + bool changed = + _syncSettings.LeftPort != (BsnesApi.BSNES_INPUT_DEVICE) Port1ComboBox.SelectedIndex + || _syncSettings.RightPort != (BsnesApi.BSNES_INPUT_DEVICE) Port2ComboBox.SelectedIndex + || _syncSettings.LimitAnalogChangeSensitivity != LimitAnalogChangeCheckBox.Checked; + + if (changed) + { + _syncSettings.LeftPort = (BsnesApi.BSNES_INPUT_DEVICE) Port1ComboBox.SelectedIndex; + _syncSettings.RightPort = (BsnesApi.BSNES_INPUT_DEVICE) Port2ComboBox.SelectedIndex; + _syncSettings.LimitAnalogChangeSensitivity = LimitAnalogChangeCheckBox.Checked; + + _mainForm.PutCoreSyncSettings(_syncSettings); + } + + DialogResult = DialogResult.OK; + Close(); + } + + private void CancelBtn_Click(object sender, EventArgs e) + { + _mainForm.AddOnScreenMessage("Controller settings aborted"); + DialogResult = DialogResult.Cancel; + Close(); + } + + private void PortComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + var leftPort = (BsnesApi.BSNES_INPUT_DEVICE) Port1ComboBox.SelectedIndex; + var rightPort = (BsnesApi.BSNES_INPUT_DEVICE) Port2ComboBox.SelectedIndex; + ToggleMouseSection(leftPort == BsnesApi.BSNES_INPUT_DEVICE.Mouse || rightPort == BsnesApi.BSNES_INPUT_DEVICE.Mouse); + } + + private void ToggleMouseSection(bool show) + { + LimitAnalogChangeCheckBox.Visible = + MouseSpeedLabel1.Visible = + MouseNagLabel1.Visible = + show; + } + } +} diff --git a/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.resx b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.resx new file mode 100644 index 0000000000..1af7de150c --- /dev/null +++ b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESControllerConfig.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/config/SNES/BSNESOptions.Designer.cs b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESOptions.Designer.cs new file mode 100644 index 0000000000..98917ffaa9 --- /dev/null +++ b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESOptions.Designer.cs @@ -0,0 +1,367 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class BSNESOptions + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btnOk = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.cbDoubleSize = new System.Windows.Forms.CheckBox(); + this.lblDoubleSize = new BizHawk.WinForms.Controls.LocLabelEx(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.lblPriority1 = new BizHawk.WinForms.Controls.LocLabelEx(); + this.lblPriority0 = new BizHawk.WinForms.Controls.LocLabelEx(); + this.Bg4_0Checkbox = new System.Windows.Forms.CheckBox(); + this.Bg3_0Checkbox = new System.Windows.Forms.CheckBox(); + this.Bg2_0Checkbox = new System.Windows.Forms.CheckBox(); + this.Bg1_0Checkbox = new System.Windows.Forms.CheckBox(); + this.Bg4_1Checkbox = new System.Windows.Forms.CheckBox(); + this.Bg3_1Checkbox = new System.Windows.Forms.CheckBox(); + this.Bg2_1Checkbox = new System.Windows.Forms.CheckBox(); + this.Bg1_1Checkbox = new System.Windows.Forms.CheckBox(); + this.Obj4Checkbox = new System.Windows.Forms.CheckBox(); + this.Obj3Checkbox = new System.Windows.Forms.CheckBox(); + this.Obj2Checkbox = new System.Windows.Forms.CheckBox(); + this.Obj1Checkbox = new System.Windows.Forms.CheckBox(); + this.EntropyBox = new System.Windows.Forms.ComboBox(); + this.lblEntropy = new BizHawk.WinForms.Controls.LocLabelEx(); + this.cbGameHotfixes = new System.Windows.Forms.CheckBox(); + this.cbFastPPU = new System.Windows.Forms.CheckBox(); + this.cbCropSGBFrame = new System.Windows.Forms.CheckBox(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // btnOk + // + this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnOk.Location = new System.Drawing.Point(136, 303); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(75, 23); + this.btnOk.TabIndex = 0; + this.btnOk.Text = "OK"; + this.btnOk.UseVisualStyleBackColor = true; + this.btnOk.Click += new System.EventHandler(this.BtnOk_Click); + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(217, 303); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 1; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.BtnCancel_Click); + // + // cbDoubleSize + // + this.cbDoubleSize.AutoSize = true; + this.cbDoubleSize.Location = new System.Drawing.Point(18, 16); + this.cbDoubleSize.Name = "cbDoubleSize"; + this.cbDoubleSize.Size = new System.Drawing.Size(178, 17); + this.cbDoubleSize.TabIndex = 6; + this.cbDoubleSize.Text = "Always Double-Size Framebuffer"; + this.cbDoubleSize.UseVisualStyleBackColor = true; + // + // lblDoubleSize + // + this.lblDoubleSize.Location = new System.Drawing.Point(33, 34); + this.lblDoubleSize.MaximumSize = new System.Drawing.Size(260, 0); + this.lblDoubleSize.Name = "lblDoubleSize"; + this.lblDoubleSize.Text = "Some games are changing the resolution constantly (e.g. SD3) so this option can f" + + "orce the SNES output to stay double-size always."; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.lblPriority1); + this.groupBox1.Controls.Add(this.lblPriority0); + this.groupBox1.Controls.Add(this.Bg4_0Checkbox); + this.groupBox1.Controls.Add(this.Bg3_0Checkbox); + this.groupBox1.Controls.Add(this.Bg2_0Checkbox); + this.groupBox1.Controls.Add(this.Bg1_0Checkbox); + this.groupBox1.Controls.Add(this.Bg4_1Checkbox); + this.groupBox1.Controls.Add(this.Bg3_1Checkbox); + this.groupBox1.Controls.Add(this.Bg2_1Checkbox); + this.groupBox1.Controls.Add(this.Bg1_1Checkbox); + this.groupBox1.Controls.Add(this.Obj4Checkbox); + this.groupBox1.Controls.Add(this.Obj3Checkbox); + this.groupBox1.Controls.Add(this.Obj2Checkbox); + this.groupBox1.Controls.Add(this.Obj1Checkbox); + this.groupBox1.Location = new System.Drawing.Point(18, 165); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(274, 132); + this.groupBox1.TabIndex = 11; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Display"; + // + // lblPriority1 + // + this.lblPriority1.Location = new System.Drawing.Point(220, 14); + this.lblPriority1.MaximumSize = new System.Drawing.Size(100, 0); + this.lblPriority1.Name = "lblPriority1"; + this.lblPriority1.Text = "Priority 1"; + // + // lblPriority0 + // + this.lblPriority0.Location = new System.Drawing.Point(162, 14); + this.lblPriority0.MaximumSize = new System.Drawing.Size(100, 0); + this.lblPriority0.Name = "lblPriority0"; + this.lblPriority0.Text = "Priority 0"; + this.lblPriority0.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // Bg4_0Checkbox + // + this.Bg4_0Checkbox.AutoSize = true; + this.Bg4_0Checkbox.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + this.Bg4_0Checkbox.Location = new System.Drawing.Point(128, 99); + this.Bg4_0Checkbox.Name = "Bg4_0Checkbox"; + this.Bg4_0Checkbox.Size = new System.Drawing.Size(62, 17); + this.Bg4_0Checkbox.TabIndex = 11; + this.Bg4_0Checkbox.Text = "BG 4 "; + this.Bg4_0Checkbox.UseVisualStyleBackColor = true; + // + // Bg3_0Checkbox + // + this.Bg3_0Checkbox.AutoSize = true; + this.Bg3_0Checkbox.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + this.Bg3_0Checkbox.Location = new System.Drawing.Point(128, 76); + this.Bg3_0Checkbox.Name = "Bg3_0Checkbox"; + this.Bg3_0Checkbox.Size = new System.Drawing.Size(62, 17); + this.Bg3_0Checkbox.TabIndex = 10; + this.Bg3_0Checkbox.Text = "BG 3 "; + this.Bg3_0Checkbox.UseVisualStyleBackColor = true; + // + // Bg2_0Checkbox + // + this.Bg2_0Checkbox.AutoSize = true; + this.Bg2_0Checkbox.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + this.Bg2_0Checkbox.Location = new System.Drawing.Point(128, 53); + this.Bg2_0Checkbox.Name = "Bg2_0Checkbox"; + this.Bg2_0Checkbox.Size = new System.Drawing.Size(62, 17); + this.Bg2_0Checkbox.TabIndex = 9; + this.Bg2_0Checkbox.Text = "BG 2 "; + this.Bg2_0Checkbox.UseVisualStyleBackColor = true; + // + // Bg1_0Checkbox + // + this.Bg1_0Checkbox.AutoSize = true; + this.Bg1_0Checkbox.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + this.Bg1_0Checkbox.Location = new System.Drawing.Point(128, 30); + this.Bg1_0Checkbox.Name = "Bg1_0Checkbox"; + this.Bg1_0Checkbox.Size = new System.Drawing.Size(62, 17); + this.Bg1_0Checkbox.TabIndex = 8; + this.Bg1_0Checkbox.Text = "BG 1 "; + this.Bg1_0Checkbox.UseVisualStyleBackColor = true; + // + // Bg4_1Checkbox + // + this.Bg4_1Checkbox.AutoSize = true; + this.Bg4_1Checkbox.Location = new System.Drawing.Point(234, 100); + this.Bg4_1Checkbox.Name = "Bg4_1Checkbox"; + this.Bg4_1Checkbox.Size = new System.Drawing.Size(15, 14); + this.Bg4_1Checkbox.TabIndex = 7; + this.Bg4_1Checkbox.UseVisualStyleBackColor = true; + // + // Bg3_1Checkbox + // + this.Bg3_1Checkbox.AutoSize = true; + this.Bg3_1Checkbox.Location = new System.Drawing.Point(234, 77); + this.Bg3_1Checkbox.Name = "Bg3_1Checkbox"; + this.Bg3_1Checkbox.Size = new System.Drawing.Size(15, 14); + this.Bg3_1Checkbox.TabIndex = 6; + this.Bg3_1Checkbox.UseVisualStyleBackColor = true; + // + // Bg2_1Checkbox + // + this.Bg2_1Checkbox.AutoSize = true; + this.Bg2_1Checkbox.Location = new System.Drawing.Point(234, 54); + this.Bg2_1Checkbox.Name = "Bg2_1Checkbox"; + this.Bg2_1Checkbox.Size = new System.Drawing.Size(15, 14); + this.Bg2_1Checkbox.TabIndex = 5; + this.Bg2_1Checkbox.UseVisualStyleBackColor = true; + // + // Bg1_1Checkbox + // + this.Bg1_1Checkbox.AutoSize = true; + this.Bg1_1Checkbox.Location = new System.Drawing.Point(234, 31); + this.Bg1_1Checkbox.Name = "Bg1_1Checkbox"; + this.Bg1_1Checkbox.Size = new System.Drawing.Size(15, 14); + this.Bg1_1Checkbox.TabIndex = 4; + this.Bg1_1Checkbox.UseVisualStyleBackColor = true; + // + // Obj4Checkbox + // + this.Obj4Checkbox.AutoSize = true; + this.Obj4Checkbox.Location = new System.Drawing.Point(21, 99); + this.Obj4Checkbox.Name = "Obj4Checkbox"; + this.Obj4Checkbox.Size = new System.Drawing.Size(55, 17); + this.Obj4Checkbox.TabIndex = 3; + this.Obj4Checkbox.Text = "OBJ 4"; + this.Obj4Checkbox.UseVisualStyleBackColor = true; + // + // Obj3Checkbox + // + this.Obj3Checkbox.AutoSize = true; + this.Obj3Checkbox.Location = new System.Drawing.Point(21, 76); + this.Obj3Checkbox.Name = "Obj3Checkbox"; + this.Obj3Checkbox.Size = new System.Drawing.Size(55, 17); + this.Obj3Checkbox.TabIndex = 2; + this.Obj3Checkbox.Text = "OBJ 3"; + this.Obj3Checkbox.UseVisualStyleBackColor = true; + // + // Obj2Checkbox + // + this.Obj2Checkbox.AutoSize = true; + this.Obj2Checkbox.Location = new System.Drawing.Point(21, 53); + this.Obj2Checkbox.Name = "Obj2Checkbox"; + this.Obj2Checkbox.Size = new System.Drawing.Size(55, 17); + this.Obj2Checkbox.TabIndex = 1; + this.Obj2Checkbox.Text = "OBJ 2"; + this.Obj2Checkbox.UseVisualStyleBackColor = true; + // + // Obj1Checkbox + // + this.Obj1Checkbox.AutoSize = true; + this.Obj1Checkbox.Location = new System.Drawing.Point(21, 30); + this.Obj1Checkbox.Name = "Obj1Checkbox"; + this.Obj1Checkbox.Size = new System.Drawing.Size(55, 17); + this.Obj1Checkbox.TabIndex = 0; + this.Obj1Checkbox.Text = "OBJ 1"; + this.Obj1Checkbox.UseVisualStyleBackColor = true; + // + // EntropyBox + // + this.EntropyBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.EntropyBox.FormattingEnabled = true; + this.EntropyBox.Items.AddRange(new object[] { + "None", + "Low", + "High"}); + this.EntropyBox.Location = new System.Drawing.Point(164, 138); + this.EntropyBox.Name = "EntropyBox"; + this.EntropyBox.Size = new System.Drawing.Size(128, 21); + this.EntropyBox.TabIndex = 14; + // + // lblEntropy + // + this.lblEntropy.Location = new System.Drawing.Point(249, 117); + this.lblEntropy.Name = "lblEntropy"; + this.lblEntropy.Text = "Entropy"; + // + // cbGameHotfixes + // + this.cbGameHotfixes.AutoSize = true; + this.cbGameHotfixes.Location = new System.Drawing.Point(18, 111); + this.cbGameHotfixes.Name = "cbGameHotfixes"; + this.cbGameHotfixes.Size = new System.Drawing.Size(93, 17); + this.cbGameHotfixes.TabIndex = 22; + this.cbGameHotfixes.Text = "Game hotfixes"; + this.cbGameHotfixes.UseVisualStyleBackColor = true; + // + // cbFastPPU + // + this.cbFastPPU.AutoSize = true; + this.cbFastPPU.Location = new System.Drawing.Point(18, 138); + this.cbFastPPU.Name = "cbFastPPU"; + this.cbFastPPU.Size = new System.Drawing.Size(90, 17); + this.cbFastPPU.TabIndex = 23; + this.cbFastPPU.Text = "Use fast PPU"; + this.cbFastPPU.UseVisualStyleBackColor = true; + this.cbFastPPU.CheckedChanged += new System.EventHandler(this.FastPPU_CheckedChanged); + // + // cbCropSGBFrame + // + this.cbCropSGBFrame.AutoSize = true; + this.cbCropSGBFrame.Location = new System.Drawing.Point(18, 84); + this.cbCropSGBFrame.Name = "cbCropSGBFrame"; + this.cbCropSGBFrame.Size = new System.Drawing.Size(105, 17); + this.cbCropSGBFrame.TabIndex = 27; + this.cbCropSGBFrame.Text = "Crop SGB Frame"; + this.cbCropSGBFrame.UseVisualStyleBackColor = true; + // + // BSNESOptions + // + this.AcceptButton = this.btnOk; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(304, 338); + this.Controls.Add(this.cbCropSGBFrame); + this.Controls.Add(this.cbFastPPU); + this.Controls.Add(this.cbGameHotfixes); + this.Controls.Add(this.lblEntropy); + this.Controls.Add(this.EntropyBox); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.lblDoubleSize); + this.Controls.Add(this.cbDoubleSize); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnOk); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "BSNESOptions"; + this.ShowIcon = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "BSNES Options"; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.CheckBox cbDoubleSize; + private BizHawk.WinForms.Controls.LocLabelEx lblDoubleSize; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.CheckBox Bg4_1Checkbox; + private System.Windows.Forms.CheckBox Bg3_1Checkbox; + private System.Windows.Forms.CheckBox Bg2_1Checkbox; + private System.Windows.Forms.CheckBox Bg1_1Checkbox; + private System.Windows.Forms.CheckBox Obj4Checkbox; + private System.Windows.Forms.CheckBox Obj3Checkbox; + private System.Windows.Forms.CheckBox Obj2Checkbox; + private System.Windows.Forms.CheckBox Obj1Checkbox; + private System.Windows.Forms.ComboBox EntropyBox; + private WinForms.Controls.LocLabelEx lblEntropy; + private System.Windows.Forms.CheckBox cbGameHotfixes; + private System.Windows.Forms.CheckBox cbFastPPU; + private System.Windows.Forms.CheckBox Bg1_0Checkbox; + private System.Windows.Forms.CheckBox Bg4_0Checkbox; + private System.Windows.Forms.CheckBox Bg3_0Checkbox; + private System.Windows.Forms.CheckBox Bg2_0Checkbox; + private WinForms.Controls.LocLabelEx lblPriority1; + private WinForms.Controls.LocLabelEx lblPriority0; + private System.Windows.Forms.CheckBox cbCropSGBFrame; + } +} diff --git a/src/BizHawk.Client.EmuHawk/config/SNES/BSNESOptions.cs b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESOptions.cs new file mode 100644 index 0000000000..7b107bc1d4 --- /dev/null +++ b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESOptions.cs @@ -0,0 +1,126 @@ +using System; +using System.Windows.Forms; +using BizHawk.Emulation.Cores.Nintendo.BSNES; + +namespace BizHawk.Client.EmuHawk +{ + public partial class BSNESOptions : Form + { + private BSNESOptions() + { + InitializeComponent(); + } + + public static void DoSettingsDialog(IMainFormForConfig mainForm, BsnesCore bsnes) + { + var s = bsnes.GetSettings(); + var ss = bsnes.GetSyncSettings(); + using var dlg = new BSNESOptions + { + AlwaysDoubleSize = s.AlwaysDoubleSize, + CropSGBFrame = s.CropSGBFrame, + Entropy = ss.Entropy, + Hotfixes = ss.Hotfixes, + FastPPU = ss.FastPPU, + ShowObj1 = s.ShowOBJ_0, + ShowObj2 = s.ShowOBJ_1, + ShowObj3 = s.ShowOBJ_2, + ShowObj4 = s.ShowOBJ_3, + ShowBg1_0 = s.ShowBG1_0, + ShowBg1_1 = s.ShowBG1_1, + ShowBg2_0 = s.ShowBG2_0, + ShowBg2_1 = s.ShowBG2_1, + ShowBg3_0 = s.ShowBG3_0, + ShowBg3_1 = s.ShowBG3_1, + ShowBg4_0 = s.ShowBG4_0, + ShowBg4_1 = s.ShowBG4_1 + }; + + DialogResult result = mainForm.ShowDialogAsChild(dlg); + if (result == DialogResult.OK) + { + s.AlwaysDoubleSize = dlg.AlwaysDoubleSize; + s.CropSGBFrame = dlg.CropSGBFrame; + ss.Entropy = dlg.Entropy; + ss.Hotfixes = dlg.Hotfixes; + ss.FastPPU = dlg.FastPPU; + s.ShowOBJ_0 = dlg.ShowObj1; + s.ShowOBJ_1 = dlg.ShowObj2; + s.ShowOBJ_2 = dlg.ShowObj3; + s.ShowOBJ_3 = dlg.ShowObj4; + s.ShowBG1_0 = dlg.ShowBg1_0; + s.ShowBG1_1 = dlg.ShowBg1_1; + s.ShowBG2_0 = dlg.ShowBg2_0; + s.ShowBG2_1 = dlg.ShowBg2_1; + s.ShowBG3_0 = dlg.ShowBg3_0; + s.ShowBG3_1 = dlg.ShowBg3_1; + s.ShowBG4_0 = dlg.ShowBg4_0; + s.ShowBG4_1 = dlg.ShowBg4_1; + + mainForm.PutCoreSettings(s); + mainForm.PutCoreSyncSettings(ss); + } + } + + private bool AlwaysDoubleSize + { + get => cbDoubleSize.Checked; + init => cbDoubleSize.Checked = value; + } + + private bool CropSGBFrame + { + get => cbCropSGBFrame.Checked; + init => cbCropSGBFrame.Checked = value; + } + + private bool Hotfixes + { + get => cbGameHotfixes.Checked; + init => cbGameHotfixes.Checked = value; + } + + private bool FastPPU + { + get => cbFastPPU.Checked; + init => cbDoubleSize.Enabled = cbFastPPU.Checked = value; + } + + private BsnesApi.ENTROPY Entropy + { + get => (BsnesApi.ENTROPY) EntropyBox.SelectedIndex; + init => EntropyBox.SelectedIndex = (int) value; + } + + private bool ShowObj1 { get => Obj1Checkbox.Checked; init => Obj1Checkbox.Checked = value; } + private bool ShowObj2 { get => Obj2Checkbox.Checked; init => Obj2Checkbox.Checked = value; } + private bool ShowObj3 { get => Obj3Checkbox.Checked; init => Obj3Checkbox.Checked = value; } + private bool ShowObj4 { get => Obj4Checkbox.Checked; init => Obj4Checkbox.Checked = value; } + + private bool ShowBg1_0 { get => Bg1_0Checkbox.Checked; init => Bg1_0Checkbox.Checked = value; } + private bool ShowBg1_1 { get => Bg1_1Checkbox.Checked; init => Bg1_1Checkbox.Checked = value; } + private bool ShowBg2_0 { get => Bg2_0Checkbox.Checked; init => Bg2_0Checkbox.Checked = value; } + private bool ShowBg2_1 { get => Bg2_1Checkbox.Checked; init => Bg2_1Checkbox.Checked = value; } + private bool ShowBg3_0 { get => Bg3_0Checkbox.Checked; init => Bg3_0Checkbox.Checked = value; } + private bool ShowBg3_1 { get => Bg3_1Checkbox.Checked; init => Bg3_1Checkbox.Checked = value; } + private bool ShowBg4_0 { get => Bg4_0Checkbox.Checked; init => Bg4_0Checkbox.Checked = value; } + private bool ShowBg4_1 { get => Bg4_1Checkbox.Checked; init => Bg4_1Checkbox.Checked = value; } + + private void BtnOk_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.OK; + Close(); + } + + private void BtnCancel_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + + private void FastPPU_CheckedChanged(object sender, EventArgs e) + { + cbDoubleSize.Enabled = cbFastPPU.Checked; + } + } +} diff --git a/src/BizHawk.Client.EmuHawk/config/SNES/BSNESOptions.resx b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESOptions.resx new file mode 100644 index 0000000000..1af7de150c --- /dev/null +++ b/src/BizHawk.Client.EmuHawk/config/SNES/BSNESOptions.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/BizHawk.Emulation.Common/Interfaces/Services/ITraceable.cs b/src/BizHawk.Emulation.Common/Interfaces/Services/ITraceable.cs index db097ac931..aba72c21d0 100644 --- a/src/BizHawk.Emulation.Common/Interfaces/Services/ITraceable.cs +++ b/src/BizHawk.Emulation.Common/Interfaces/Services/ITraceable.cs @@ -24,7 +24,7 @@ ITraceSink Sink { set; } /// - /// Gets a value indicating whether racing is enabled + /// Gets a value indicating whether tracing is enabled /// This is defined as equivalent to Sink != null /// It's put here because it's such a common operation to check whether it's enabled, and it's not nice to write Sink != null all over /// diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs new file mode 100644 index 0000000000..bbff8afc29 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs @@ -0,0 +1,206 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using BizHawk.Common; +using BizHawk.Emulation.Cores.Waterbox; +using BizHawk.BizInvoke; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public abstract unsafe class BsnesCoreImpl + { + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_set_audio_enabled(bool enabled); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_set_video_enabled(bool enabled); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_set_layer_enables(BsnesApi.LayerEnables layerEnables); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_set_trace_enabled(bool enabled); + + [BizImport(CallingConvention.Cdecl)] + public abstract BsnesApi.SNES_REGION snes_get_region(); + [BizImport(CallingConvention.Cdecl)] + public abstract BsnesApi.SNES_MAPPER snes_get_mapper(); + [BizImport(CallingConvention.Cdecl)] + public abstract void* snes_get_memory_region(int id, out int size, out int wordSize); + [BizImport(CallingConvention.Cdecl)] + public abstract byte snes_bus_read(uint address); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_bus_write(uint address, byte value); + + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_set_callbacks(IntPtr[] snesCallbacks); + + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_init(BsnesApi.ENTROPY entropy, BsnesApi.BSNES_INPUT_DEVICE left, + BsnesApi.BSNES_INPUT_DEVICE right, ushort mergedBools);// bool hotfixes, bool fastPPU); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_power(); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_term(); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_reset(); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_run(); + + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_serialize(byte[] serializedData, int serializedSize); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_unserialize(byte[] serializedData, int serializedSize); + [BizImport(CallingConvention.Cdecl)] + public abstract int snes_serialized_size(); + + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_load_cartridge_normal(string baseRomPath, byte[] romData, int romSize); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_load_cartridge_super_gameboy(string baseRomPath, byte[] romData, byte[] sgbRomData, ulong mergedRomSizes); + } + + public unsafe partial class BsnesApi : IDisposable, IMonitor, IStatable + { + internal WaterboxHost exe; + internal BsnesCoreImpl core; + private readonly ICallingConventionAdapter _adapter; + private bool _disposed; + + public void Enter() + { + exe.Enter(); + } + + public void Exit() + { + exe.Exit(); + } + + private readonly List _readonlyFiles = new(); + + public void AddReadonlyFile(byte[] data, string name) + { + // current logic potentially requests the same name twice; once for program and once for data + // because this gets mapped to the same file, we only add it once + if (!_readonlyFiles.Contains(name)) + { + exe.AddReadonlyFile(data, name); + _readonlyFiles.Add(name); + } + } + + public void SetCallbacks(SnesCallbacks callbacks) + { + FieldInfo[] fieldInfos = typeof(SnesCallbacks).GetFields(); + IntPtr[] functionPointerArray = new IntPtr[fieldInfos.Length]; + for (int i = 0; i < fieldInfos.Length; i++) + { + functionPointerArray[i] = _adapter.GetFunctionPointerForDelegate((Delegate) fieldInfos[i].GetValue(callbacks)); + } + core.snes_set_callbacks(functionPointerArray); + } + + public BsnesApi(string dllPath, CoreComm comm, IEnumerable allCallbacks) + { + exe = new WaterboxHost(new WaterboxOptions + { + Filename = "bsnes.wbx", + Path = dllPath, + SbrkHeapSizeKB = 14 * 1024, + InvisibleHeapSizeKB = 4, + MmapHeapSizeKB = 105 * 1024, // TODO: check whether this needs to be larger; it depends on the rom size + PlainHeapSizeKB = 0, + SealedHeapSizeKB = 0, + SkipCoreConsistencyCheck = comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck), + SkipMemoryConsistencyCheck = comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck), + }); + using (exe.EnterExit()) + { + // Marshal checks that function pointers passed to GetDelegateForFunctionPointer are + // _currently_ valid when created, even though they don't need to be valid until + // the delegate is later invoked. so GetInvoker needs to be acquired within a lock. + _adapter = CallingConventionAdapters.MakeWaterbox(allCallbacks, exe); + this.core = BizInvoker.GetInvoker(exe, exe, _adapter); + } + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + exe.Dispose(); + exe = null; + core = null; + // serializedSize = 0; + } + } + + public delegate void snes_video_frame_t(ushort* data, int width, int height, int pitch); + public delegate void snes_input_poll_t(); + public delegate short snes_input_state_t(int port, int index, int id); + public delegate void snes_no_lag_t(); + public delegate void snes_audio_sample_t(short left, short right); + public delegate string snes_path_request_t(int slot, string hint, bool required); + public delegate void snes_trace_t(string disassembly, string register_info); + + + // I cannot use a struct here because marshalling is retarded for bool (4 bytes). I honestly cannot + [StructLayout(LayoutKind.Sequential)] + public class LayerEnables + { + public bool BG1_Prio0, BG1_Prio1; + public bool BG2_Prio0, BG2_Prio1; + public bool BG3_Prio0, BG3_Prio1; + public bool BG4_Prio0, BG4_Prio1; + public bool Obj_Prio0, Obj_Prio1, Obj_Prio2, Obj_Prio3; + } + + [StructLayout(LayoutKind.Sequential)] + public class SnesCallbacks + { + public snes_input_poll_t inputPollCb; + public snes_input_state_t inputStateCb; + public snes_no_lag_t noLagCb; + public snes_video_frame_t videoFrameCb; + public snes_audio_sample_t audioSampleCb; + public snes_path_request_t pathRequestCb; + public snes_trace_t snesTraceCb; + } + + public void Seal() + { + exe.Seal(); + foreach (var s in _readonlyFiles) + { + exe.RemoveReadonlyFile(s); + } + _readonlyFiles.Clear(); + } + + // TODO: confirm that the serializedSize is CONSTANT for any given game, + // else this might be problematic + // private int serializedSize;// = 284275; + + public void SaveStateBinary(BinaryWriter writer) + { + // if (serializedSize == 0) + // serializedSize = _core.snes_serialized_size(); + // TODO: do some profiling and testing to check whether this is actually better than _exe.SaveStateBinary(writer); + // re-adding bsnes's own serialization will need to be done once it's confirmed to be deterministic, aka after libco update + + // byte[] serializedData = new byte[serializedSize]; + // _core.snes_serialize(serializedData, serializedSize); + // writer.Write(serializedData); + exe.SaveStateBinary(writer); + } + + public void LoadStateBinary(BinaryReader reader) + { + // byte[] serializedData = reader.ReadBytes(serializedSize); + // _core.snes_unserialize(serializedData, serializedSize); + exe.LoadStateBinary(reader); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi_Enums.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi_Enums.cs new file mode 100644 index 0000000000..5b3411b5b3 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi_Enums.cs @@ -0,0 +1,61 @@ +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public partial class BsnesApi + { + public enum SNES_MEMORY + { + CARTRIDGE_RAM, + BSX_RAM, + BSX_PRAM, + SUFAMI_TURBO_A_RAM, + SUFAMI_TURBO_B_RAM, + + WRAM, + APURAM, + VRAM, + // OAM, // needs some work in the core probably? or we return an objects pointer + CGRAM, + + CARTRIDGE_ROM + } + + public enum BSNES_INPUT_DEVICE + { + None, + Gamepad, + Mouse, + SuperMultitap, + SuperScope, + Justifier, + Justifiers + } + + public enum ENTROPY + { + None, + Low, + High + } + + public enum SNES_MAPPER : byte + { + LOROM = 0, + HIROM = 1, + EXLOROM = 2, + EXHIROM = 3, + SUPERFXROM = 4, + SA1ROM = 5, + SPC7110ROM = 6, + BSCLOROM = 7, + BSCHIROM = 8, + BSXROM = 9, + STROM = 10 + } + + public enum SNES_REGION : uint + { + NTSC = 0, + PAL = 1 + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesControllers.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesControllers.cs new file mode 100644 index 0000000000..a2784bcd6b --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesControllers.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BizHawk.Common; +using BizHawk.Common.NumberExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Nintendo.SNES; +using static BizHawk.Emulation.Cores.Nintendo.BSNES.BsnesApi; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public class BsnesControllers + { + private static IBsnesController GetController(BSNES_INPUT_DEVICE t, BsnesCore.SnesSyncSettings ss) + { + return t switch + { + BSNES_INPUT_DEVICE.None => new BsnesUnpluggedController(), + BSNES_INPUT_DEVICE.Gamepad => new BsnesController(), + BSNES_INPUT_DEVICE.Mouse => new BsnesMouseController + { + LimitAnalogChangeSensitivity = ss.LimitAnalogChangeSensitivity + }, + BSNES_INPUT_DEVICE.SuperMultitap => new BsnesMultitapController(), + BSNES_INPUT_DEVICE.SuperScope => new BsnesSuperScopeController(), + BSNES_INPUT_DEVICE.Justifier => new BsnesJustifierController(false), + BSNES_INPUT_DEVICE.Justifiers => new BsnesJustifierController(true), + _ => throw new InvalidOperationException() + }; + } + + private readonly IBsnesController[] _ports; + private readonly ControlDefUnMerger[] _mergers; + + public ControllerDefinition Definition { get; } + + public BsnesControllers(BsnesCore.SnesSyncSettings ss) + { + _ports = new[] + { + GetController(ss.LeftPort, ss), + GetController(ss.RightPort, ss) + }; + + Definition = ControllerDefinitionMerger.GetMerged(_ports.Select(p => p.Definition), out var tmp); + _mergers = tmp.ToArray(); + + // add buttons that the core itself will handle + Definition.BoolButtons.Add("Reset"); + Definition.BoolButtons.Add("Power"); + Definition.Name = "SNES Controller"; + } + + public void CoreInputPoll(IController controller) + { + // i hope this is correct lol + for (int i = 0; i < 2; i++) + { + _ports[i].UpdateState(_mergers[i].UnMerge(controller)); + } + } + + public short CoreInputState(int port, int index, int id) + { + return _ports[port].GetState(index, id); + } + } + + public interface IBsnesController + { + // Updates the internal state; gets called once per frame from the core + void UpdateState(IController controller); + + /// + /// Returns the internal state; gets called potentially many times per frame + /// + /// bsnes specific value, sometimes multitap number + /// bsnes specific value, sometimes button number + short GetState(int index, int id); + + ControllerDefinition Definition { get; } + } + + internal class BsnesController : IBsnesController + { + private readonly bool[] _state = new bool[12]; + + private static readonly string[] Buttons = + { + "0Up", "0Down", "0Left", "0Right", "0B", "0A", "0Y", "0X", "0L", "0R", "0Select", "0Start" + }; + + private static readonly Dictionary ButtonsOrder = new() + { + ["0Up"] = 0, + ["0Down"] = 1, + ["0Left"] = 2, + ["0Right"] = 3, + ["0Select"] = 4, + ["0Start"] = 5, + ["0Y"] = 6, + ["0B"] = 7, + ["0X"] = 8, + ["0A"] = 9, + ["0L"] = 10, + ["0R"] = 11 + }; + + private static readonly ControllerDefinition _definition = new() + { + BoolButtons = Buttons.OrderBy(b => ButtonsOrder[b]).ToList() + }; + + public ControllerDefinition Definition => _definition; + + public void UpdateState(IController controller) + { + for (int i = 0; i < 12; i++) + { + _state[i] = controller.IsPressed(Buttons[i]); + } + } + + public short GetState(int index, int id) + { + if (id >= 12) + return 0; + + return (short) (_state[id] ? 1 : 0); + } + } + + public class BsnesMultitapController : IBsnesController + { + private readonly bool[,] _state = new bool[4, 12]; + + private static readonly string[] Buttons = + { + "Up", "Down", "Left", "Right", "B", "A", "Y", "X", "L", "R", "Select", "Start" + }; + + private static readonly Dictionary ButtonsOrder = new() + { + ["Up"] = 0, + ["Down"] = 1, + ["Left"] = 2, + ["Right"] = 3, + ["Select"] = 4, + ["Start"] = 5, + ["Y"] = 6, + ["B"] = 7, + ["X"] = 8, + ["A"] = 9, + ["R"] = 10, + ["L"] = 11 + }; + + private static readonly ControllerDefinition _definition = new() + { + BoolButtons = Enumerable.Range(0, 4) + .SelectMany(i => Buttons.OrderBy(b => ButtonsOrder[b]) + .Select(b => i + b)) + .ToList() + }; + + public ControllerDefinition Definition => _definition; + + public void UpdateState(IController controller) + { + for (int port = 0; port < 4; port++) + for (int i = 0; i < 12; i++) + { + _state[port, i] = controller.IsPressed(port + Buttons[i]); + } + } + + public short GetState(int index, int id) + { + if (id >= 12 || index >= 4) + return 0; + + return (short) (_state[index, id] ? 1 : 0); + } + } + + public class BsnesUnpluggedController : IBsnesController + { + private static readonly ControllerDefinition _definition = new(); + + public ControllerDefinition Definition => _definition; + + public void UpdateState(IController controller) { } + + public short GetState(int index, int id) => 0; + } + + public class BsnesMouseController : IBsnesController + { + private readonly short[] _state = new short[4]; + + private static readonly ControllerDefinition _definition = new ControllerDefinition + { BoolButtons = { "0Mouse Left", "0Mouse Right" } } + .AddXYPair("0Mouse {0}", AxisPairOrientation.RightAndDown, (-127).RangeTo(127), 0); //TODO verify direction against hardware, R+D inferred from behaviour in Mario Paint + + public ControllerDefinition Definition => _definition; + public bool LimitAnalogChangeSensitivity { get; init; } = true; + + public void UpdateState(IController controller) + { + int x = controller.AxisValue("0Mouse X"); + if (LimitAnalogChangeSensitivity) + { + x = x.Clamp(-10, 10); + } + _state[0] = (short) x; + + int y = controller.AxisValue("0Mouse Y"); + if (LimitAnalogChangeSensitivity) + { + y = y.Clamp(-10, 10); + } + _state[1] = (short) y; + + _state[2] = (short) (controller.IsPressed("0Mouse Left") ? 1 : 0); + _state[3] = (short) (controller.IsPressed("0Mouse Right") ? 1 : 0); + } + + public short GetState(int index, int id) + { + if (id >= 4) + return 0; + + return _state[id]; + } + } + + public class BsnesSuperScopeController : IBsnesController + { + private readonly short[] _state = new short[6]; + + private static readonly ControllerDefinition _definition = new ControllerDefinition + { BoolButtons = { "0Trigger", "0Cursor", "0Turbo", "0Pause" } } + .AddLightGun("0Scope {0}"); + + public ControllerDefinition Definition => _definition; + + public void UpdateState(IController controller) + { + _state[0] = (short) controller.AxisValue("0Scope X"); + _state[1] = (short) controller.AxisValue("0Scope Y"); + for (int i = 2; i < 6; i++) + { + _state[i] = (short) (controller.IsPressed(_definition.BoolButtons[i]) ? 1 : 0); + } + } + + public short GetState(int index, int id) + { + if (id >= 6) + return 0; + + return _state[id]; + } + } + + public class BsnesJustifierController : IBsnesController + { + public BsnesJustifierController(bool chained) + { + Definition = chained + ? new ControllerDefinition + { BoolButtons = { "0Trigger", "0Start", "1Trigger", "1Start" } } + .AddLightGun("0Justifier {0}") + .AddLightGun("1Justifier {0}") + : new ControllerDefinition + {BoolButtons = { "0Trigger", "0Start"} } + .AddLightGun("0Justifier {0}"); + _state = new short[chained ? 8 : 4]; + _chained = chained; + } + + private readonly bool _chained; + private readonly short[] _state; + + public ControllerDefinition Definition { get; } + + public void UpdateState(IController controller) + { + _state[0] = (short) controller.AxisValue("0Justifier X"); + _state[1] = (short) controller.AxisValue("0Justifier Y"); + _state[2] = (short) (controller.IsPressed(Definition.BoolButtons[0]) ? 1 : 0); + _state[3] = (short) (controller.IsPressed(Definition.BoolButtons[1]) ? 1 : 0); + if (_chained) + { + _state[4] = (short) controller.AxisValue("1Justifier X"); + _state[5] = (short) controller.AxisValue("1Justifier Y"); + _state[6] = (short) (controller.IsPressed(Definition.BoolButtons[2]) ? 1 : 0); + _state[7] = (short) (controller.IsPressed(Definition.BoolButtons[3]) ? 1 : 0); + } + } + public short GetState(int index, int id) + { + if (index >= 2 || id >= 4 || (index == 1 && !_chained)) + return 0; + + return _state[index * 4 + id]; + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs new file mode 100644 index 0000000000..f84c73916d --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs @@ -0,0 +1,92 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public partial class BsnesCore : IEmulator + { + public IEmulatorServiceProvider ServiceProvider { get; } + + public ControllerDefinition ControllerDefinition => _controllers.Definition; + + public bool FrameAdvance(IController controller, bool render, bool renderSound) + { + _controller = controller; + + /* if the input poll callback is called, it will set this to false + * this has to be done before we save the per-frame state in deterministic + * mode, because in there, the core actually advances, and might advance + * through the point in time where IsLagFrame gets set to false. makes sense? + */ + IsLagFrame = true; + + bool resetSignal = controller.IsPressed("Reset"); + if (resetSignal) + { + Api.core.snes_reset(); + } + + bool powerSignal = controller.IsPressed("Power"); + if (powerSignal) + { + Api.core.snes_power(); + } + + var enables = new BsnesApi.LayerEnables + { + BG1_Prio0 = _settings.ShowBG1_0, + BG1_Prio1 = _settings.ShowBG1_1, + BG2_Prio0 = _settings.ShowBG2_0, + BG2_Prio1 = _settings.ShowBG2_1, + BG3_Prio0 = _settings.ShowBG3_0, + BG3_Prio1 = _settings.ShowBG3_1, + BG4_Prio0 = _settings.ShowBG4_0, + BG4_Prio1 = _settings.ShowBG4_1, + Obj_Prio0 = _settings.ShowOBJ_0, + Obj_Prio1 = _settings.ShowOBJ_1, + Obj_Prio2 = _settings.ShowOBJ_2, + Obj_Prio3 = _settings.ShowOBJ_3 + }; + // TODO: I really don't think stuff like this should be set every single frame (only on change) + Api.core.snes_set_layer_enables(enables); + Api.core.snes_set_trace_enabled(_tracer.Enabled); + Api.core.snes_set_video_enabled(render); + Api.core.snes_set_audio_enabled(renderSound); + + // run the core for one frame + Frame++; + Api.core.snes_run(); + + if (IsLagFrame) + { + LagCount++; + } + + return true; + } + + public int Frame { get; private set; } + + public string SystemId { get; } + public bool DeterministicEmulation => true; + + public void ResetCounters() + { + Frame = 0; + LagCount = 0; + IsLagFrame = false; + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + Api.Dispose(); + _resampler.Dispose(); + + _disposed = true; + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IInputPollable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IInputPollable.cs new file mode 100644 index 0000000000..2c3cc307aa --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IInputPollable.cs @@ -0,0 +1,15 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public partial class BsnesCore : IInputPollable + { + public int LagCount { get; set; } + + public bool IsLagFrame { get; set; } + + // TODO: optimize managed to unmanaged using the ActiveChanged event + // ??? no idea what this is + public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem(); + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IMemoryDomains.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IMemoryDomains.cs new file mode 100644 index 0000000000..35d67e55db --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IMemoryDomains.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public partial class BsnesCore + { + private IMemoryDomains _memoryDomains; + + private unsafe void SetMemoryDomains() + { + List mm = new(); + foreach (int i in Enum.GetValues(typeof(BsnesApi.SNES_MEMORY))) + { + void* data = Api.core.snes_get_memory_region(i, out int size, out int wordSize); + if (data == null) continue; + if (Enum.GetName(typeof(BsnesApi.SNES_MEMORY), i) == BsnesApi.SNES_MEMORY.CARTRIDGE_RAM.ToString()) + { + _saveRam = (byte*) data; + _saveRamSize = size; + } + mm.Add(new MemoryDomainIntPtr(Enum.GetName(typeof(BsnesApi.SNES_MEMORY), i), MemoryDomain.Endian.Little, (IntPtr) data, size, true, wordSize)); + } + + mm.Add(new MemoryDomainDelegate( + "System Bus", + 0x1000000, + MemoryDomain.Endian.Little, + address => Api.core.snes_bus_read((uint) address), + (address, value) => Api.core.snes_bus_write((uint) address, value), wordSize: 4)); + mm.Add(Api.exe.GetPagesDomain()); + + _memoryDomains = new MemoryDomainList(mm); + ((BasicServiceProvider) ServiceProvider).Register(_memoryDomains); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IRegionable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IRegionable.cs new file mode 100644 index 0000000000..5669fdc479 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IRegionable.cs @@ -0,0 +1,11 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public partial class BsnesCore : IRegionable + { + public DisplayType Region => _region == BsnesApi.SNES_REGION.NTSC + ? DisplayType.NTSC + : DisplayType.PAL; + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISaveRam.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISaveRam.cs new file mode 100644 index 0000000000..3e56fe36ca --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISaveRam.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public unsafe partial class BsnesCore : ISaveRam + { + private byte* _saveRam; + private int _saveRamSize; + + // yeah this is not the best. this will basically always return true as long as the saveRam exists. + public bool SaveRamModified => _saveRamSize != 0; + + public byte[] CloneSaveRam() + { + if (_saveRamSize == 0) return null; + + byte[] saveRamCopy = new byte[_saveRamSize]; + using (Api.exe.EnterExit()) + { + Marshal.Copy((IntPtr) _saveRam, saveRamCopy, 0, _saveRamSize); + } + + return saveRamCopy; + } + + public void StoreSaveRam(byte[] data) + { + if (_saveRamSize == 0) return; + + using (Api.exe.EnterExit()) + { + Marshal.Copy(data, 0, (IntPtr) _saveRam, _saveRamSize); + } + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISettable.cs new file mode 100644 index 0000000000..3e817bfcdf --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISettable.cs @@ -0,0 +1,84 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public partial class BsnesCore : ISettable + { + public SnesSettings GetSettings() + { + return _settings.Clone(); + } + + public SnesSyncSettings GetSyncSettings() + { + return _syncSettings.Clone(); + } + + public PutSettingsDirtyBits PutSettings(SnesSettings o) + { + _settings = o; + + return PutSettingsDirtyBits.None; + } + + public PutSettingsDirtyBits PutSyncSettings(SnesSyncSettings o) + { + bool ret = o.LeftPort != _syncSettings.LeftPort + || o.RightPort != _syncSettings.RightPort + || o.LimitAnalogChangeSensitivity != _syncSettings.LimitAnalogChangeSensitivity + || o.Entropy != _syncSettings.Entropy + || o.Hotfixes != _syncSettings.Hotfixes + || o.FastPPU != _syncSettings.FastPPU; + + _syncSettings = o; + return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None; + } + + private SnesSettings _settings; + private SnesSyncSettings _syncSettings; + + public class SnesSettings + { + public bool ShowBG1_0 { get; set; } = true; + public bool ShowBG2_0 { get; set; } = true; + public bool ShowBG3_0 { get; set; } = true; + public bool ShowBG4_0 { get; set; } = true; + public bool ShowBG1_1 { get; set; } = true; + public bool ShowBG2_1 { get; set; } = true; + public bool ShowBG3_1 { get; set; } = true; + public bool ShowBG4_1 { get; set; } = true; + public bool ShowOBJ_0 { get; set; } = true; + public bool ShowOBJ_1 { get; set; } = true; + public bool ShowOBJ_2 { get; set; } = true; + public bool ShowOBJ_3 { get; set; } = true; + + public bool AlwaysDoubleSize { get; set; } + public bool CropSGBFrame { get; set; } + + public SnesSettings Clone() + { + return (SnesSettings) MemberwiseClone(); + } + } + + public class SnesSyncSettings + { + public BsnesApi.BSNES_INPUT_DEVICE LeftPort { get; set; } = BsnesApi.BSNES_INPUT_DEVICE.Gamepad; + + public BsnesApi.BSNES_INPUT_DEVICE RightPort { get; set; } = BsnesApi.BSNES_INPUT_DEVICE.None; + + public bool LimitAnalogChangeSensitivity { get; set; } = true; + + public BsnesApi.ENTROPY Entropy { get; set; } = BsnesApi.ENTROPY.Low; + + public bool Hotfixes { get; set; } = true; + + public bool FastPPU { get; set; } = true; + + public SnesSyncSettings Clone() + { + return (SnesSyncSettings) MemberwiseClone(); + } + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IStatable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IStatable.cs new file mode 100644 index 0000000000..2441ca0232 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IStatable.cs @@ -0,0 +1,24 @@ +using System.IO; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public partial class BsnesCore : IStatable + { + public void SaveStateBinary(BinaryWriter writer) + { + Api.SaveStateBinary(writer); + writer.Write(IsLagFrame); + writer.Write(LagCount); + writer.Write(Frame); + } + + public void LoadStateBinary(BinaryReader reader) + { + Api.LoadStateBinary(reader); + IsLagFrame = reader.ReadBoolean(); + LagCount = reader.ReadInt32(); + Frame = reader.ReadInt32(); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IVideoProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IVideoProvider.cs new file mode 100644 index 0000000000..dde3d4510b --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IVideoProvider.cs @@ -0,0 +1,26 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public partial class BsnesCore : IVideoProvider + { + // TODO: This should probably be different for PAL? + public int VirtualWidth => (int) Math.Ceiling((double) BufferHeight * 64 / 49); + + public int VirtualHeight => BufferHeight; + + public int BufferWidth { get; private set; } = 256; + + public int BufferHeight { get; private set; } = 224; + + public int BackgroundColor => 0; + + public int[] GetVideoBuffer() => _videoBuffer; + + public int VsyncNumerator { get; } + public int VsyncDenominator { get; } + + private int[] _videoBuffer = new int[256 * 224]; + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs new file mode 100644 index 0000000000..fcae5ce778 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs @@ -0,0 +1,391 @@ +using System; +using System.Linq; +using System.Xml; +using System.IO; + +using BizHawk.Common.BufferExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components.W65816; + +// http://wiki.superfamicom.org/snes/show/Backgrounds + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + [PortedCore(CoreNames.Bsnes115, "bsnes team", "v115+", "https://bsnes.dev", isReleased: false)] + [ServiceNotApplicable(new[] { typeof(IDriveLight) })] + public unsafe partial class BsnesCore : IEmulator, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ISettable + { + private BsnesApi.SNES_REGION _region; + + [CoreConstructor("SGB")] + [CoreConstructor("SNES")] + public BsnesCore(GameInfo game, byte[] rom, CoreComm comm, + SnesSettings settings, SnesSyncSettings syncSettings) + :this(game, rom, null, null, comm, settings, syncSettings) + {} + + public BsnesCore(GameInfo game, byte[] romData, byte[] xmlData, string baseRomPath, CoreComm comm, + SnesSettings settings, SnesSyncSettings syncSettings) + { + _baseRomPath = baseRomPath; + var ser = new BasicServiceProvider(this); + ServiceProvider = ser; + + CoreComm = comm; + byte[] sgbRomData = null; + + if (game.System == "SGB") + { + if ((romData[0x143] & 0xc0) == 0xc0) + { + throw new CGBNotSupportedException(); + } + + sgbRomData = CoreComm.CoreFileProvider.GetFirmware("SNES", "Rom_SGB", true, "SGB Rom is required for SGB emulation."); + game.FirmwareHash = sgbRomData.HashSHA1(); + } + + _settings = settings ?? new SnesSettings(); + _syncSettings = syncSettings ?? new SnesSyncSettings(); + + BsnesApi.SnesCallbacks callbacks = new() + { + inputPollCb = snes_input_poll, + inputStateCb = snes_input_state, + noLagCb = snes_no_lag, + videoFrameCb = snes_video_refresh, + audioSampleCb = snes_audio_sample, + pathRequestCb = snes_path_request, + snesTraceCb = snes_trace + }; + + Api = new BsnesApi(CoreComm.CoreFileProvider.DllPath(), CoreComm, new Delegate[] + { + callbacks.inputPollCb, + callbacks.inputStateCb, + callbacks.noLagCb, + callbacks.videoFrameCb, + callbacks.audioSampleCb, + callbacks.pathRequestCb, + callbacks.snesTraceCb + }); + + _controllers = new BsnesControllers(_syncSettings); + + generate_palette(); + // TODO: massive random hack till waterboxhost gets fixed to support 5+ args + ushort mergedBools = (ushort) ((_syncSettings.Hotfixes ? 1 << 8 : 0) | (_syncSettings.FastPPU ? 1 : 0)); + Api.core.snes_init(_syncSettings.Entropy, _syncSettings.LeftPort, _syncSettings.RightPort, mergedBools); + Api.SetCallbacks(callbacks); + + // start up audio resampler + InitAudio(); + ser.Register(_resampler); + + if (game.System == "SGB") + { + IsSGB = true; + SystemId = "SNES"; + ser.Register(new SGBBoardInfo()); + + _currLoadParams = new LoadParams + { + type = LoadParamType.SuperGameBoy, + baseRomPath = baseRomPath, + romData = sgbRomData, + sgbRomData = romData + }; + } + else + { + // we may need to get some information out of the cart, even during the following bootup/load process + if (xmlData != null) + { + _romxml = new XmlDocument(); + _romxml.Load(new MemoryStream(xmlData)); + + // bsnes wont inspect the xml to load the necessary sfc file. + // so, we have to do that here and pass it in as the romData :/ + + // TODO: uhh i have no idea what the xml is or whether this below code is needed + if (_romxml["cartridge"]?["rom"] != null) + { + romData = File.ReadAllBytes(PathSubfile(_romxml["cartridge"]["rom"].Attributes["name"].Value)); + } + else + { + throw new Exception("Could not find rom file specification in xml file. Please check the integrity of your xml file"); + } + } + + SystemId = "SNES"; + _currLoadParams = new LoadParams + { + type = LoadParamType.Normal, + baseRomPath = baseRomPath, + romData = romData + }; + } + + LoadCurrent(); + + if (_region == BsnesApi.SNES_REGION.NTSC) + { + // taken from bsnes source + VsyncNumerator = 21477272; + VsyncDenominator = 357366; + } + else + { + // http://forums.nesdev.com/viewtopic.php?t=5367&start=19 + VsyncNumerator = 21281370; + VsyncDenominator = 4 * 341 * 312; + } + + SetMemoryDomains(); + + _tracer = new TraceBuffer + { + Header = "65816: PC, mnemonic, operands, registers (A, X, Y, S, D, B, flags (NVMXDIZC), V, H)" + }; + ser.Register(new W65816_DisassemblerService()); + ser.Register(_tracer); + + Api.Seal(); + } + + private CoreComm CoreComm { get; } + + private readonly string _baseRomPath; + + private string PathSubfile(string fname) => Path.Combine(_baseRomPath, fname); + + private readonly BsnesControllers _controllers; + private readonly ITraceable _tracer; + private readonly XmlDocument _romxml; + + private IController _controller; + private readonly LoadParams _currLoadParams; + private SpeexResampler _resampler; + private bool _disposed; + + public bool IsSGB { get; } + + private class SGBBoardInfo : IBoardInfo + { + public string BoardName => "SGB"; + } + + private BsnesApi Api { get; } + + private string snes_path_request(int slot, string hint, bool required) + { + // TODO: this msu1 handling code is outdated and needs to be remade from someone with knowledge. + // every rom requests msu1.rom... why? who knows. + // also handle msu-1 pcm files here + bool isMsu1Rom = hint == "msu1/data.rom"; + bool isMsu1Pcm = Path.GetExtension(hint).ToLower() == ".pcm"; + if (isMsu1Rom || isMsu1Pcm) + { + // well, check if we have an msu-1 xml + if (_romxml?["cartridge"]?["msu1"] != null) + { + var msu1 = _romxml["cartridge"]["msu1"]; + if (isMsu1Rom && msu1["rom"]?.Attributes["name"] != null) + { + return PathSubfile(msu1["rom"].Attributes["name"].Value); + } + + if (isMsu1Pcm) + { + // return @"D:\roms\snes\SuperRoadBlaster\SuperRoadBlaster-1.pcm"; + // return ""; + int wantsTrackNumber = int.Parse(hint.Replace("track-", "").Replace(".pcm", "")); + wantsTrackNumber++; + string wantsTrackString = wantsTrackNumber.ToString(); + foreach (var child in msu1.ChildNodes.Cast()) + { + if (child.Name == "track" && child.Attributes["number"].Value == wantsTrackString) + { + return PathSubfile(child.Attributes["name"].Value); + } + } + } + } + + // not found.. what to do? (every rom will get here when msu1.rom is requested) + return ""; + } + + // not MSU-1. ok. + if (hint == "save.ram") + { + // core asked for saveram, but the interface isn't designed to be able to handle this. + // so, we'll just return nothing and the frontend will set the saveram itself later + return null; + } + + string firmwareId; + + switch (hint) + { + case "cx4": firmwareId = "CX4"; break; + case "dsp1": firmwareId = "DSP1"; break; + case "dsp1b": firmwareId = "DSP1b"; break; + case "dsp2": firmwareId = "DSP2"; break; + case "dsp3": firmwareId = "DSP3"; break; + case "dsp4": firmwareId = "DSP4"; break; + case "st010": firmwareId = "ST010"; break; + case "st011": firmwareId = "ST011"; break; + case "st018": firmwareId = "ST018"; break; + default: + CoreComm.ShowMessage($"Unrecognized SNES firmware request \"{hint}\"."); + return ""; + } + + string ret = ""; + var data = CoreComm.CoreFileProvider.GetFirmware("SNES", firmwareId, required, "Game may function incorrectly without the requested firmware."); + if (data != null) + { + ret = hint; + Api.AddReadonlyFile(data, hint); + } + + Console.WriteLine("Served bsnescore request for firmware \"{0}\"", hint); + + // return the path we built + return ret; + } + + private enum LoadParamType + { + Normal, SuperGameBoy + } + + private struct LoadParams + { + public LoadParamType type; + public string baseRomPath; + public byte[] romData; + public byte[] sgbRomData; + } + + private void LoadCurrent() + { + if (_currLoadParams.type == LoadParamType.Normal) + Api.core.snes_load_cartridge_normal(_currLoadParams.baseRomPath, _currLoadParams.romData, _currLoadParams.romData.Length); + else + Api.core.snes_load_cartridge_super_gameboy(_currLoadParams.baseRomPath, _currLoadParams.romData, + _currLoadParams.sgbRomData, (ulong) _currLoadParams.romData.Length << 32 | (uint)_currLoadParams.sgbRomData.Length); + + _region = Api.core.snes_get_region(); + } + + // poll which updates the controller state + private void snes_input_poll() + { + _controllers.CoreInputPoll(_controller); + } + + /// 0 or 1, corresponding to L and R physical ports on the snes + /// meaningless for most controllers. for multitap, 0-3 for which multitap controller + /// button ID enum; in the case of a regular controller, this corresponds to shift register position + /// for regular controllers, one bit D0 of button status. for other controls, varying ranges depending on id + private short snes_input_state(int port, int index, int id) + { + return _controllers.CoreInputState(port, index, id); + } + + private void snes_no_lag() + { + // gets called whenever there was input polled, aka no lag + IsLagFrame = false; + } + + private readonly int[] palette = new int[32768]; + + private void generate_palette() + { + for (int color = 0; color < 32768; color++) { + int r = (color >> 10) & 31; + int g = (color >> 5) & 31; + int b = (color >> 0) & 31; + + r = r << 3 | r >> 2; r = r << 8 | r << 0; + g = g << 3 | g >> 2; g = g << 8 | g << 0; + b = b << 3 | b >> 2; b = b << 8 | b << 0; + + palette[color] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; + } + } + + private void snes_video_refresh(ushort* data, int width, int height, int pitch) + { + int widthMultiplier = 1; + int heightMultiplier = 1; + if (_settings.CropSGBFrame && IsSGB) + { + BufferWidth = 160; + BufferHeight = 144; + } + else + { + if (_settings.AlwaysDoubleSize) + { + if (width == 256) widthMultiplier = 2; + if (height == 224) heightMultiplier = 2; + } + BufferWidth = width * widthMultiplier; + BufferHeight = height * heightMultiplier; + } + + int size = BufferWidth * BufferHeight; + if (_videoBuffer.Length != size) + { + _videoBuffer = new int[size]; + } + + int di = 0; + if (_settings.CropSGBFrame && IsSGB) + { + for (int y = 39; y < 39 + 144; y++) + { + ushort* sp = data + y * pitch + 48; + for (int x = 0; x < 160; x++) + { + _videoBuffer[di++] = palette[*sp++]; + } + } + return; + } + + for (int y = 0; y < height * heightMultiplier; y++) + { + int si = y / heightMultiplier * pitch; + for (int x = 0; x < width * widthMultiplier; x++) + { + _videoBuffer[di++] = palette[data[si + x / widthMultiplier]]; + } + } + } + + private void InitAudio() + { + _resampler = new SpeexResampler(SpeexResampler.Quality.QUALITY_DESKTOP, 64080, 88200, 32040, 44100); + } + + private void snes_audio_sample(short left, short right) + { + _resampler.EnqueueSample(left, right); + } + + private void snes_trace(string disassembly, string registerInfo) + { + _tracer.Put(new TraceInfo + { + Disassembly = disassembly, + RegisterInfo = registerInfo + }); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/CoreNames.cs b/src/BizHawk.Emulation.Cores/CoreNames.cs index 7af7175023..251c546ffd 100644 --- a/src/BizHawk.Emulation.Cores/CoreNames.cs +++ b/src/BizHawk.Emulation.Cores/CoreNames.cs @@ -12,6 +12,7 @@ namespace BizHawk.Emulation.Cores public const string A7800Hawk = "A7800Hawk"; public const string Atari2600Hawk = "Atari2600Hawk"; public const string Bsnes = "BSNES"; + public const string Bsnes115 = "BSNESv115+"; public const string C64Hawk = "C64Hawk"; public const string ChannelFHawk = "ChannelFHawk"; public const string ColecoHawk = "ColecoHawk"; diff --git a/waterbox/bsnescore/.editorconfig b/waterbox/bsnescore/.editorconfig new file mode 100644 index 0000000000..b4ed3d43de --- /dev/null +++ b/waterbox/bsnescore/.editorconfig @@ -0,0 +1,6 @@ +root = false + +[*] +# let it auto-detect indent_style for each file +# i'm not gonna let my workflow get tampered with +indent_style = diff --git a/waterbox/bsnescore/Makefile b/waterbox/bsnescore/Makefile new file mode 100644 index 0000000000..f2e567be78 --- /dev/null +++ b/waterbox/bsnescore/Makefile @@ -0,0 +1,60 @@ +NEED_LIBCO := 1 + +CXXFLAGS := -std=c++17 \ + -I../libco -I./bsnes \ + -Werror=int-to-pointer-cast \ + -Wno-parentheses -Wno-sign-compare -Wno-unused-variable -Wno-trigraphs -Wno-switch -Wno-reorder -Wno-misleading-indentation \ + -fno-threadsafe-statics -fno-strict-aliasing -fwrapv + +CCFLAGS := -std=c11 -DGB_INTERNAL -DGB_DISABLE_DEBUGGER -DGB_DISABLE_CHEATS -D_GNU_SOURCE \ + -Werror \ + -Wall -Wno-multichar -Wno-int-in-bool-context \ + -fno-strict-aliasing + +TARGET = bsnes.wbx + +SRCS_PROCESSORS = \ + $(ROOT_DIR)/bsnes/processor/spc700/spc700.cpp \ + $(ROOT_DIR)/bsnes/processor/wdc65816/wdc65816.cpp \ + $(ROOT_DIR)/bsnes/processor/arm7tdmi/arm7tdmi.cpp + +SRCS_EMULATOR = \ + $(ROOT_DIR)/bsnes/emulator/emulator.cpp + +SRCS_GB = \ + $(ROOT_DIR)/bsnes/gb/Core/apu.c \ + $(ROOT_DIR)/bsnes/gb/Core/camera.c \ + $(ROOT_DIR)/bsnes/gb/Core/rumble.c \ + $(ROOT_DIR)/bsnes/gb/Core/display.c \ + $(ROOT_DIR)/bsnes/gb/Core/gb.c \ + $(ROOT_DIR)/bsnes/gb/Core/joypad.c \ + $(ROOT_DIR)/bsnes/gb/Core/mbc.c \ + $(ROOT_DIR)/bsnes/gb/Core/memory.c \ + $(ROOT_DIR)/bsnes/gb/Core/printer.c \ + $(ROOT_DIR)/bsnes/gb/Core/random.c \ + $(ROOT_DIR)/bsnes/gb/Core/rewind.c \ + $(ROOT_DIR)/bsnes/gb/Core/save_state.c \ + $(ROOT_DIR)/bsnes/gb/Core/sgb.c \ + $(ROOT_DIR)/bsnes/gb/Core/sm83_cpu.c \ + $(ROOT_DIR)/bsnes/gb/Core/symbol_hash.c \ + $(ROOT_DIR)/bsnes/gb/Core/timing.c + +SRCS_SFC = \ + $(ROOT_DIR)/bsnes/sfc/interface/interface.cpp \ + $(ROOT_DIR)/bsnes/sfc/system/system.cpp \ + $(ROOT_DIR)/bsnes/sfc/controller/controller.cpp \ + $(ROOT_DIR)/bsnes/sfc/cartridge/cartridge.cpp \ + $(ROOT_DIR)/bsnes/sfc/memory/memory.cpp \ + $(ROOT_DIR)/bsnes/sfc/cpu/cpu.cpp \ + $(ROOT_DIR)/bsnes/sfc/smp/smp.cpp \ + $(ROOT_DIR)/bsnes/sfc/dsp/dsp.cpp \ + $(ROOT_DIR)/bsnes/sfc/ppu/ppu.cpp \ + $(ROOT_DIR)/bsnes/sfc/ppu-fast/ppu.cpp \ + $(ROOT_DIR)/bsnes/sfc/expansion/expansion.cpp \ + $(ROOT_DIR)/bsnes/sfc/coprocessor/coprocessor.cpp \ + $(ROOT_DIR)/bsnes/sfc/slot/slot.cpp \ + $(ROOT_DIR)/bsnes/target-bsnescore/bsnescore.cpp + +SRCS = $(SRCS_PROCESSORS) $(SRCS_EMULATOR) $(SRCS_GB) $(SRCS_SFC) + +include ../common.mak diff --git a/waterbox/bsnescore/bsnes/emulator/audio/audio.cpp b/waterbox/bsnescore/bsnes/emulator/audio/audio.cpp new file mode 100644 index 0000000000..8fc279cc1d --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/audio/audio.cpp @@ -0,0 +1,73 @@ +namespace Emulator { + +#include "stream.cpp" +Audio audio; + +Audio::~Audio() { + reset(nullptr); +} + +auto Audio::reset(Interface* interface) -> void { + _interface = interface; + _streams.reset(); + _channels = 0; +} + +auto Audio::setFrequency(double frequency) -> void { + _frequency = frequency; + for(auto& stream : _streams) { + stream->setFrequency(stream->inputFrequency, frequency); + } +} + +auto Audio::setVolume(double volume) -> void { + _volume = volume; +} + +auto Audio::setBalance(double balance) -> void { + _balance = balance; +} + +auto Audio::createStream(uint channels, double frequency) -> shared_pointer { + _channels = max(_channels, channels); + shared_pointer stream = new Stream; + stream->reset(channels, frequency, _frequency); + _streams.append(stream); + return stream; +} + +auto Audio::process() -> void { + while(_streams) { + for(auto& stream : _streams) { + if(!stream->pending()) return; + } + + double samples[_channels]; + for(auto& sample : samples) sample = 0.0; + + for(auto& stream : _streams) { + double buffer[_channels]; + uint length = stream->read(buffer), offset = 0; + + for(auto& sample : samples) { + sample += buffer[offset]; + if(++offset >= length) offset = 0; + } + } + + for(auto c : range(_channels)) { + samples[c] = max(-1.0, min(+1.0, samples[c] * _volume)); + } + + if(_channels == 2) { + if(_balance < 0.0) samples[1] *= 1.0 + _balance; + if(_balance > 0.0) samples[0] *= 1.0 - _balance; + } + + platform->audioFrame(samples, _channels); + } +} + +} + +#undef double diff --git a/waterbox/bsnescore/bsnes/emulator/audio/audio.hpp b/waterbox/bsnescore/bsnes/emulator/audio/audio.hpp new file mode 100644 index 0000000000..d170552317 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/audio/audio.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include + +namespace Emulator { + +struct Interface; +struct Audio; +struct Filter; +struct Stream; + +struct Audio { + ~Audio(); + auto reset(Interface* interface) -> void; + + inline auto channels() const -> uint { return _channels; } + inline auto frequency() const -> double { return _frequency; } + inline auto volume() const -> double { return _volume; } + inline auto balance() const -> double { return _balance; } + + auto setFrequency(double frequency) -> void; + auto setVolume(double volume) -> void; + auto setBalance(double balance) -> void; + + auto createStream(uint channels, double frequency) -> shared_pointer; + +private: + auto process() -> void; + + Interface* _interface = nullptr; + vector> _streams; + + uint _channels = 0; + double _frequency = 48000.0; + + double _volume = 1.0; + double _balance = 0.0; + + friend class Stream; +}; + +struct Filter { + enum class Mode : uint { DCRemoval, OnePole, Biquad } mode; + enum class Type : uint { None, LowPass, HighPass } type; + enum class Order : uint { None, First, Second } order; + + DSP::IIR::DCRemoval dcRemoval; + DSP::IIR::OnePole onePole; + DSP::IIR::Biquad biquad; +}; + +struct Stream { + auto reset(uint channels, double inputFrequency, double outputFrequency) -> void; + auto reset() -> void; + + auto frequency() const -> double; + auto setFrequency(double inputFrequency, maybe outputFrequency = nothing) -> void; + + auto addDCRemovalFilter() -> void; + auto addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void; + auto addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void; + + auto pending() const -> uint; + auto read(double samples[]) -> uint; + auto write(const double samples[]) -> void; + + template auto sample(P&&... p) -> void { + double samples[sizeof...(P)] = {forward

(p)...}; + write(samples); + } + + auto serialize(serializer&) -> void; + +private: + struct Channel { + vector filters; + vector nyquist; + DSP::Resampler::Cubic resampler; + }; + vector channels; + double inputFrequency; + double outputFrequency; + + friend class Audio; +}; + +extern Audio audio; + +} + +#undef double diff --git a/waterbox/bsnescore/bsnes/emulator/audio/stream.cpp b/waterbox/bsnescore/bsnes/emulator/audio/stream.cpp new file mode 100644 index 0000000000..3fe659f8d3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/audio/stream.cpp @@ -0,0 +1,125 @@ +auto Stream::reset(uint channelCount, double inputFrequency, double outputFrequency) -> void { + channels.reset(); + channels.resize(channelCount); + + for(auto& channel : channels) { + channel.filters.reset(); + } + + setFrequency(inputFrequency, outputFrequency); +} + +auto Stream::reset() -> void { + for(auto& channel : channels) { + channel.resampler.reset(this->inputFrequency, this->outputFrequency); + } +} + +auto Stream::frequency() const -> double { + return inputFrequency; +} + +auto Stream::setFrequency(double inputFrequency, maybe outputFrequency) -> void { + this->inputFrequency = inputFrequency; + if(outputFrequency) this->outputFrequency = outputFrequency(); + + for(auto& channel : channels) { + channel.nyquist.reset(); + channel.resampler.reset(this->inputFrequency, this->outputFrequency); + } + + if(this->inputFrequency >= this->outputFrequency * 2) { + //add a low-pass filter to prevent aliasing during resampling + double cutoffFrequency = min(25000.0, this->outputFrequency / 2.0 - 2000.0); + for(auto& channel : channels) { + uint passes = 3; + for(uint pass : range(passes)) { + DSP::IIR::Biquad filter; + double q = DSP::IIR::Biquad::butterworth(passes * 2, pass); + filter.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, this->inputFrequency, q); + channel.nyquist.append(filter); + } + } + } +} + +auto Stream::addDCRemovalFilter() -> void { + return; //todo: test to ensure this is desirable before enabling + for(auto& channel : channels) { + Filter filter{Filter::Mode::DCRemoval, Filter::Type::None, Filter::Order::None}; + channel.filters.append(filter); + } +} + +auto Stream::addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void { + for(auto& channel : channels) { + for(uint pass : range(passes)) { + if(order == Filter::Order::First) { + Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First}; + filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, inputFrequency); + channel.filters.append(filter); + } + if(order == Filter::Order::Second) { + Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second}; + double q = DSP::IIR::Biquad::butterworth(passes * 2, pass); + filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q); + channel.filters.append(filter); + } + } + } +} + +auto Stream::addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void { + for(auto& channel : channels) { + for(uint pass : range(passes)) { + if(order == Filter::Order::First) { + Filter filter{Filter::Mode::OnePole, Filter::Type::HighPass, Filter::Order::First}; + filter.onePole.reset(DSP::IIR::OnePole::Type::HighPass, cutoffFrequency, inputFrequency); + channel.filters.append(filter); + } + if(order == Filter::Order::Second) { + Filter filter{Filter::Mode::Biquad, Filter::Type::HighPass, Filter::Order::Second}; + double q = DSP::IIR::Biquad::butterworth(passes * 2, pass); + filter.biquad.reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q); + channel.filters.append(filter); + } + } + } +} + +auto Stream::pending() const -> uint { + if(!channels) return 0; + return channels[0].resampler.pending(); +} + +auto Stream::read(double samples[]) -> uint { + for(uint c : range(channels.size())) samples[c] = channels[c].resampler.read(); + return channels.size(); +} + +auto Stream::write(const double samples[]) -> void { + for(auto c : range(channels.size())) { + double sample = samples[c] + 1e-25; //constant offset used to suppress denormals + for(auto& filter : channels[c].filters) { + switch(filter.mode) { + case Filter::Mode::DCRemoval: sample = filter.dcRemoval.process(sample); break; + 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); + } + + audio.process(); +} + +auto Stream::serialize(serializer& s) -> void { + for(auto& channel : channels) { + channel.resampler.serialize(s); + } + s.real(inputFrequency); + s.real(outputFrequency); +} diff --git a/waterbox/bsnescore/bsnes/emulator/cheat.hpp b/waterbox/bsnescore/bsnes/emulator/cheat.hpp new file mode 100644 index 0000000000..20f194b703 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/cheat.hpp @@ -0,0 +1,57 @@ +#pragma once + +namespace Emulator { + +struct Cheat { + struct Code { + auto operator==(const Code& code) const -> bool { + if(address != code.address) return false; + if(data != code.data) return false; + if((bool)compare != (bool)code.compare) return false; + if(compare && code.compare && compare() != code.compare()) return false; + return true; + } + + uint address; + uint data; + maybe compare; + bool enable; + uint restore; + }; + + explicit operator bool() const { + return codes.size() > 0; + } + + auto reset() -> void { + codes.reset(); + } + + auto append(uint address, uint data, maybe compare = {}) -> void { + codes.append({address, data, compare}); + } + + auto assign(const vector& list) -> void { + reset(); + for(auto& entry : list) { + for(auto code : entry.split("+")) { + auto part = code.transform("=?", "//").split("/"); + if(part.size() == 2) append(part[0].hex(), part[1].hex()); + if(part.size() == 3) append(part[0].hex(), part[2].hex(), part[1].hex()); + } + } + } + + auto find(uint address, uint compare) -> maybe { + for(auto& code : codes) { + if(code.address == address && (!code.compare || code.compare() == compare)) { + return code.data; + } + } + return nothing; + } + + vector codes; +}; + +} diff --git a/waterbox/bsnescore/bsnes/emulator/emulator.cpp b/waterbox/bsnescore/bsnes/emulator/emulator.cpp new file mode 100644 index 0000000000..ce4b705577 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/emulator.cpp @@ -0,0 +1,8 @@ +#include +#include + +namespace Emulator { + +Platform* platform = nullptr; + +} diff --git a/waterbox/bsnescore/bsnes/emulator/emulator.hpp b/waterbox/bsnescore/bsnes/emulator/emulator.hpp new file mode 100644 index 0000000000..71cd809a5f --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/emulator.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace nall; + +#include +#include +#include +#include + +namespace Emulator { + static const string Name = "bsnes"; + static const string Version = "115"; + static const string Copyright = "bsnes team"; + static const string License = "GPLv3 or later"; + static const string Website = "https://bsnes.dev"; + + //incremented only when serialization format changes + static const string SerializerVersion = "115"; + + namespace Constants { + namespace Colorburst { + static constexpr double NTSC = 315.0 / 88.0 * 1'000'000.0; + static constexpr double PAL = 283.75 * 15'625.0 + 25.0; + } + } + + //nall/vfs shorthand constants for open(), load() + namespace File { + static const auto Read = vfs::file::mode::read; + static const auto Write = vfs::file::mode::write; + static const auto Optional = false; + static const auto Required = true; + }; +} + +#include "platform.hpp" +#include "interface.hpp" +#include "game.hpp" diff --git a/waterbox/bsnescore/bsnes/emulator/game.hpp b/waterbox/bsnescore/bsnes/emulator/game.hpp new file mode 100644 index 0000000000..cad2141d07 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/game.hpp @@ -0,0 +1,112 @@ +#pragma once + +namespace Emulator { + +struct Game { + struct Memory; + struct Oscillator; + + inline auto load(string_view) -> void; + inline auto memory(Markup::Node) -> maybe; + inline auto oscillator(natural = 0) -> maybe; + + struct Memory { + Memory() = default; + inline Memory(Markup::Node); + explicit operator bool() const { return (bool)type; } + inline auto name() const -> string; + + string type; + natural size; + string content; + string manufacturer; + string architecture; + string identifier; + boolean nonVolatile; + }; + + struct Oscillator { + Oscillator() = default; + inline Oscillator(Markup::Node); + explicit operator bool() const { return frequency; } + + natural frequency; + }; + + Markup::Node document; + string sha256; + string label; + string name; + string title; + string region; + string revision; + string board; + vector memoryList; + vector oscillatorList; +}; + +auto Game::load(string_view text) -> void { + document = BML::unserialize(text); + + sha256 = document["game/sha256"].text(); + label = document["game/label"].text(); + name = document["game/name"].text(); + title = document["game/title"].text(); + region = document["game/region"].text(); + revision = document["game/revision"].text(); + board = document["game/board"].text(); + + for(auto node : document.find("game/board/memory")) { + memoryList.append(Memory{node}); + } + + for(auto node : document.find("game/board/oscillator")) { + oscillatorList.append(Oscillator{node}); + } +} + +auto Game::memory(Markup::Node node) -> maybe { + if(!node) return nothing; + for(auto& memory : memoryList) { + auto type = node["type"].text(); + auto size = node["size"].natural(); + auto content = node["content"].text(); + auto manufacturer = node["manufacturer"].text(); + auto architecture = node["architecture"].text(); + auto identifier = node["identifier"].text(); + if(type && type != memory.type) continue; + if(size && size != memory.size) continue; + if(content && content != memory.content) continue; + if(manufacturer && manufacturer != memory.manufacturer) continue; + if(architecture && architecture != memory.architecture) continue; + if(identifier && identifier != memory.identifier) continue; + return memory; + } + return nothing; +} + +auto Game::oscillator(natural index) -> maybe { + if(index < oscillatorList.size()) return oscillatorList[index]; + return nothing; +} + +Game::Memory::Memory(Markup::Node node) { + type = node["type"].text(); + size = node["size"].natural(); + content = node["content"].text(); + manufacturer = node["manufacturer"].text(); + architecture = node["architecture"].text(); + identifier = node["identifier"].text(); + nonVolatile = !(bool)node["volatile"]; +} + +auto Game::Memory::name() const -> string { + if(architecture) return string{architecture, ".", content, ".", type}.downcase(); + return string{content, ".", type}.downcase(); +} + +Game::Oscillator::Oscillator(Markup::Node node) { + frequency = node["frequency"].natural(); +} + +} diff --git a/waterbox/bsnescore/bsnes/emulator/interface.hpp b/waterbox/bsnescore/bsnes/emulator/interface.hpp new file mode 100644 index 0000000000..f21399fe4f --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/interface.hpp @@ -0,0 +1,109 @@ +#pragma once + +namespace Emulator { + +struct Interface { + struct Information { + string manufacturer; + string name; + string extension; + bool resettable = false; + }; + + struct Display { + struct Type { enum : uint { + CRT, + LCD, + };}; + uint id = 0; + string name; + uint type = 0; + uint colors = 0; + uint width = 0; + uint height = 0; + uint internalWidth = 0; + uint internalHeight = 0; + double aspectCorrection = 0; + }; + + struct Port { + uint id; + string name; + }; + + struct Device { + uint id; + string name; + }; + + struct Input { + struct Type { enum : uint { + Hat, + Button, + Trigger, + Control, + Axis, + Rumble, + };}; + + uint type; + string name; + }; + + //information + virtual auto information() -> Information { return {}; } + + virtual auto display() -> Display { return {}; } + virtual auto color(uint32 color) -> uint64 { return 0; } + + //game interface + virtual auto loaded() -> bool { return false; } + virtual auto hashes() -> vector { return {}; } + virtual auto manifests() -> vector { return {}; } + virtual auto titles() -> vector { return {}; } + virtual auto title() -> string { return {}; } + virtual auto load() -> bool { return false; } + virtual auto save() -> void {} + virtual auto unload() -> void {} + + //system interface + virtual auto ports() -> vector { return {}; } + virtual auto devices(uint port) -> vector { return {}; } + virtual auto inputs(uint device) -> vector { return {}; } + virtual auto connected(uint port) -> uint { return 0; } + virtual auto connect(uint port, uint device) -> void {} + virtual auto power() -> void {} + virtual auto reset() -> void {} + virtual auto run() -> void {} + + //time functions + virtual auto rtc() -> bool { return false; } + virtual auto synchronize(uint64 timestamp = 0) -> void {} + + //state functions + virtual auto serialize(bool synchronize = true) -> serializer { return {}; } + virtual auto unserialize(serializer&) -> bool { return false; } + + //cheat functions + virtual auto read(uint24 address) -> uint8 { return 0; } + virtual auto cheats(const vector& = {}) -> void {} + + //configuration + virtual auto configuration() -> string { return {}; } + virtual auto configuration(string name) -> string { return {}; } + virtual auto configure(string configuration = "") -> bool { return false; } + virtual auto configure(string name, string value) -> bool { return false; } + + //settings + virtual auto cap(const string& name) -> bool { return false; } + virtual auto get(const string& name) -> any { return {}; } + virtual auto set(const string& name, const any& value) -> bool { return false; } + + virtual auto frameSkip() -> uint { return 0; } + virtual auto setFrameSkip(uint frameSkip) -> void {} + + virtual auto runAhead() -> bool { return false; } + virtual auto setRunAhead(bool runAhead) -> void {} +}; + +} diff --git a/waterbox/bsnescore/bsnes/emulator/memory/memory.hpp b/waterbox/bsnescore/bsnes/emulator/memory/memory.hpp new file mode 100644 index 0000000000..8e6ce4b233 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/memory/memory.hpp @@ -0,0 +1,30 @@ +#pragma once + +namespace Emulator::Memory { + +inline auto mirror(uint address, uint size) -> uint { + if(size == 0) return 0; + uint base = 0; + uint 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(uint address, uint mask) -> uint { + while(mask) { + uint bits = (mask & -mask) - 1; + address = address >> 1 & ~bits | address & bits; + mask = (mask & mask - 1) >> 1; + } + return address; +} + +} diff --git a/waterbox/bsnescore/bsnes/emulator/memory/readable.hpp b/waterbox/bsnescore/bsnes/emulator/memory/readable.hpp new file mode 100644 index 0000000000..688a2df694 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/memory/readable.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include + +namespace Emulator::Memory { + +template +struct Readable { + ~Readable() { reset(); } + + inline auto reset() -> void { + delete[] self.data; + self.data = nullptr; + self.size = 0; + self.mask = 0; + } + + inline auto allocate(uint size, T fill = ~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(self.data, self.mask + 1, fill); + } + + inline auto load(shared_pointer fp) -> void { + fp->read(self.data, min(fp->size(), self.size * sizeof(T))); + for(uint address = self.size; address <= self.mask; address++) { + self.data[address] = self.data[mirror(address, self.size)]; + } + } + + inline auto save(shared_pointer fp) -> void { + fp->write(self.data, self.size * sizeof(T)); + } + + explicit operator bool() const { return (bool)self.data; } + inline auto data() const -> const T* { return self.data; } + inline auto size() const -> uint { return self.size; } + inline auto mask() const -> uint { return self.mask; } + + inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; } + inline auto read(uint address) const -> T { return self.data[address & self.mask]; } + inline auto write(uint address, T data) const -> void {} + + auto serialize(serializer& s) -> void { + const uint size = self.size; + s.integer(self.size); + s.integer(self.mask); + if(self.size != size) allocate(self.size); + s.array(self.data, self.size); + } + +private: + struct { + T* data = nullptr; + uint size = 0; + uint mask = 0; + } self; +}; + +} diff --git a/waterbox/bsnescore/bsnes/emulator/memory/writable.hpp b/waterbox/bsnescore/bsnes/emulator/memory/writable.hpp new file mode 100644 index 0000000000..8ae5bd984f --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/memory/writable.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +namespace Emulator::Memory { + +template +struct Writable { + ~Writable() { reset(); } + + inline auto reset() -> void { + delete[] self.data; + self.data = nullptr; + self.size = 0; + self.mask = 0; + } + + inline auto allocate(uint size, T fill = ~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(self.data, self.mask + 1, fill); + } + + inline auto load(shared_pointer fp) -> void { + fp->read(self.data, min(fp->size(), self.size * sizeof(T))); + for(uint address = self.size; address <= self.mask; address++) { + self.data[address] = self.data[mirror(address, self.size)]; + } + } + + inline auto save(shared_pointer fp) -> void { + fp->write(self.data, self.size * sizeof(T)); + } + + explicit operator bool() const { return (bool)self.data; } + inline auto data() -> T* { return self.data; } + inline auto data() const -> const T* { return self.data; } + inline auto size() const -> uint { return self.size; } + inline auto mask() const -> uint { return self.mask; } + + inline auto operator[](uint address) -> T& { return self.data[address & self.mask]; } + inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; } + inline auto read(uint address) const -> T { return self.data[address & self.mask]; } + inline auto write(uint address, T data) -> void { self.data[address & self.mask] = data; } + + auto serialize(serializer& s) -> void { + const uint size = self.size; + s.integer(self.size); + s.integer(self.mask); + if(self.size != size) allocate(self.size); + s.array(self.data, self.size); + } + +private: + struct { + T* data = nullptr; + uint size = 0; + uint mask = 0; + } self; +}; + +} diff --git a/waterbox/bsnescore/bsnes/emulator/platform.hpp b/waterbox/bsnescore/bsnes/emulator/platform.hpp new file mode 100644 index 0000000000..0cde99a938 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/platform.hpp @@ -0,0 +1,34 @@ +#pragma once + +namespace Emulator { + +struct Platform { + struct Load { + Load() = default; + Load(uint pathID, string option = "") : valid(true), pathID(pathID), option(option) {} + explicit operator bool() const { return valid; } + + bool valid = false; + uint pathID = 0; + string option; + }; + + virtual auto path(uint id) -> string { return ""; } + virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> shared_pointer { return {}; } + virtual auto load(uint id, string name, string type, vector options = {}) -> Load { return {}; } + virtual auto videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void {} + virtual auto audioFrame(const double* samples, uint channels) -> void {} + virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; } + virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {} + virtual auto dipSettings(Markup::Node node) -> uint { return 0; } + virtual auto notify(string text) -> void {} + // 03-may-2021 manual addition. unused currently but let's hope for the best + virtual auto getBackdropColor() -> uint16 { return 0; } + + bool traceEnabled = false; + virtual auto cpuTrace(vector) -> void {} +}; + +extern Platform* platform; + +} diff --git a/waterbox/bsnescore/bsnes/emulator/random.hpp b/waterbox/bsnescore/bsnes/emulator/random.hpp new file mode 100644 index 0000000000..b6228877f0 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/random.hpp @@ -0,0 +1,96 @@ +#pragma once + +namespace Emulator { + +struct Random { + enum class Entropy : uint { None, Low, High }; + + auto operator()() -> uint64 { + return random(); + } + + auto entropy(Entropy entropy) -> void { + _entropy = entropy; + seed(); + } + + auto seed(maybe seed = nothing, maybe sequence = nothing) -> void { + if(!seed) seed = (uint32)clock(); + if(!sequence) sequence = 0; + + _state = 0; + _increment = sequence() << 1 | 1; + step(); + _state += seed(); + step(); + } + + auto random() -> uint64 { + if(_entropy == Entropy::None) return 0; + return (uint64)step() << 32 | (uint64)step() << 0; + } + + auto bias(uint64 bias) -> uint64 { + if(_entropy == Entropy::None) return bias; + return random(); + } + + auto bound(uint64 bound) -> uint64 { + uint64 threshold = -bound % bound; + while(true) { + uint64 result = random(); + if(result >= threshold) return result % bound; + } + } + + auto array(uint8* data, uint32 size) -> void { + if(_entropy == Entropy::None) { + memory::fill(data, size); + return; + } + + if(_entropy == Entropy::High) { + for(uint32 address : range(size)) { + data[address] = random(); + } + return; + } + + //Entropy::Low + uint lobit = random() & 3; + uint hibit = (lobit + 8 + (random() & 3)) & 15; + uint lovalue = random() & 255; + uint hivalue = random() & 255; + if((random() & 3) == 0) lovalue = 0; + if((random() & 1) == 0) hivalue = ~lovalue; + + for(uint32 address : range(size)) { + uint8 value = (address & 1ull << lobit) ? lovalue : hivalue; + if((address & 1ull << hibit)) value = ~value; + if((random() & 511) == 0) value ^= 1 << (random() & 7); + if((random() & 2047) == 0) value ^= 1 << (random() & 7); + data[address] = value; + } + } + + auto serialize(serializer& s) -> void { + s.integer((uint&)_entropy); + s.integer(_state); + s.integer(_increment); + } + +private: + auto step() -> uint32 { + uint64 state = _state; + _state = state * 6364136223846793005ull + _increment; + uint32 xorshift = (state >> 18 ^ state) >> 27; + uint32 rotate = state >> 59; + return xorshift >> rotate | xorshift << (-rotate & 31); + } + + Entropy _entropy = Entropy::High; + uint64 _state; + uint64 _increment; +}; + +} diff --git a/waterbox/bsnescore/bsnes/emulator/types.hpp b/waterbox/bsnescore/bsnes/emulator/types.hpp new file mode 100644 index 0000000000..6c1fcda851 --- /dev/null +++ b/waterbox/bsnescore/bsnes/emulator/types.hpp @@ -0,0 +1,72 @@ +#pragma once + +using int1 = nall::Integer< 1>; +using int2 = nall::Integer< 2>; +using int3 = nall::Integer< 3>; +using int4 = nall::Integer< 4>; +using int5 = nall::Integer< 5>; +using int6 = nall::Integer< 6>; +using int7 = nall::Integer< 7>; +using int8 = int8_t; +using int9 = nall::Integer< 9>; +using int10 = nall::Integer<10>; +using int11 = nall::Integer<11>; +using int12 = nall::Integer<12>; +using int13 = nall::Integer<13>; +using int14 = nall::Integer<14>; +using int15 = nall::Integer<15>; +using int16 = int16_t; +using int17 = nall::Integer<17>; +using int18 = nall::Integer<18>; +using int19 = nall::Integer<19>; +using int20 = nall::Integer<20>; +using int21 = nall::Integer<21>; +using int22 = nall::Integer<22>; +using int23 = nall::Integer<23>; +using int24 = nall::Integer<24>; +using int25 = nall::Integer<25>; +using int26 = nall::Integer<26>; +using int27 = nall::Integer<27>; +using int28 = nall::Integer<28>; +using int29 = nall::Integer<29>; +using int30 = nall::Integer<30>; +using int31 = nall::Integer<31>; +using int32 = int32_t; +using int48 = nall::Integer<48>; //Cx4 +using int64 = int64_t; + +using uint1 = nall::Natural< 1>; +using uint2 = nall::Natural< 2>; +using uint3 = nall::Natural< 3>; +using uint4 = nall::Natural< 4>; +using uint5 = nall::Natural< 5>; +using uint6 = nall::Natural< 6>; +using uint7 = nall::Natural< 7>; +using uint8 = uint8_t; +using uint9 = nall::Natural< 9>; +using uint10 = nall::Natural<10>; +using uint11 = nall::Natural<11>; +using uint12 = nall::Natural<12>; +using uint13 = nall::Natural<13>; +using uint14 = nall::Natural<14>; +using uint15 = nall::Natural<15>; +using uint16 = uint16_t; +using uint17 = nall::Natural<17>; +using uint18 = nall::Natural<18>; +using uint19 = nall::Natural<19>; +using uint20 = nall::Natural<20>; +using uint21 = nall::Natural<21>; +using uint22 = nall::Natural<22>; +using uint23 = nall::Natural<23>; +using uint24 = nall::Natural<24>; +using uint25 = nall::Natural<25>; +using uint26 = nall::Natural<26>; +using uint27 = nall::Natural<27>; +using uint28 = nall::Natural<28>; +using uint29 = nall::Natural<29>; +using uint30 = nall::Natural<30>; +using uint31 = nall::Natural<31>; +using uint32 = uint32_t; +using uint40 = nall::Natural<40>; //SA1 +using uint48 = nall::Natural<48>; //Cx4 +using uint64 = uint64_t; diff --git a/waterbox/bsnescore/bsnes/gb/Core/apu.c b/waterbox/bsnescore/bsnes/gb/Core/apu.c new file mode 100644 index 0000000000..7e7ab31f66 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/apu.c @@ -0,0 +1,1064 @@ +#include +#include +#include +#include +#include "gb.h" + +#define likely(x) __builtin_expect((x), 1) +#define unlikely(x) __builtin_expect((x), 0) + +static const uint8_t duties[] = { + 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 0, +}; + +static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_offset) +{ + unsigned multiplier = gb->apu_output.cycles_since_render + cycles_offset - gb->apu_output.last_update[index]; + gb->apu_output.summed_samples[index].left += gb->apu_output.current_sample[index].left * multiplier; + gb->apu_output.summed_samples[index].right += gb->apu_output.current_sample[index].right * multiplier; + gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset; +} + +bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index) +{ + if (gb->model >= GB_MODEL_AGB) { + /* On the AGB, mixing is done digitally, so there are no per-channel + DACs. Instead, all channels are summed digital regardless of + whatever the DAC state would be on a CGB or earlier model. */ + return true; + } + + switch (index) { + case GB_SQUARE_1: + return gb->io_registers[GB_IO_NR12] & 0xF8; + + case GB_SQUARE_2: + return gb->io_registers[GB_IO_NR22] & 0xF8; + + case GB_WAVE: + return gb->apu.wave_channel.enable; + + case GB_NOISE: + return gb->io_registers[GB_IO_NR42] & 0xF8; + } + + return false; +} + +static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index) +{ + if (!gb->apu.is_active[index]) return 0; + + switch (index) { + case GB_SQUARE_1: + return gb->apu.square_channels[GB_SQUARE_1].current_volume; + case GB_SQUARE_2: + return gb->apu.square_channels[GB_SQUARE_2].current_volume; + case GB_WAVE: + return 0; + case GB_NOISE: + return gb->apu.noise_channel.current_volume; + } + return 0; +} + +static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset) +{ + if (gb->model >= GB_MODEL_AGB) { + /* On the AGB, because no analog mixing is done, the behavior of NR51 is a bit different. + A channel that is not connected to a terminal is idenitcal to a connected channel + playing PCM sample 0. */ + gb->apu.samples[index] = value; + + if (gb->apu_output.sample_rate) { + unsigned right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; + unsigned left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; + + if (index == GB_WAVE) { + /* For some reason, channel 3 is inverted on the AGB */ + value ^= 0xF; + } + + GB_sample_t output; + uint8_t bias = agb_bias_for_channel(gb, index); + + if (gb->io_registers[GB_IO_NR51] & (1 << index)) { + output.right = (0xf - value * 2 + bias) * right_volume; + } + else { + output.right = 0xf * right_volume; + } + + if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { + output.left = (0xf - value * 2 + bias) * left_volume; + } + else { + output.left = 0xf * left_volume; + } + + if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { + refresh_channel(gb, index, cycles_offset); + gb->apu_output.current_sample[index] = output; + } + } + + return; + } + + if (!GB_apu_is_DAC_enabled(gb, index)) { + value = gb->apu.samples[index]; + } + else { + gb->apu.samples[index] = value; + } + + if (gb->apu_output.sample_rate) { + unsigned right_volume = 0; + if (gb->io_registers[GB_IO_NR51] & (1 << index)) { + right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1; + } + unsigned left_volume = 0; + if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) { + left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; + } + GB_sample_t output = {(0xf - value * 2) * left_volume, (0xf - value * 2) * right_volume}; + if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) { + refresh_channel(gb, index, cycles_offset); + gb->apu_output.current_sample[index] = output; + } + } +} + +static double smooth(double x) +{ + return 3*x*x - 2*x*x*x; +} + +static void render(GB_gameboy_t *gb) +{ + GB_sample_t output = {0, 0}; + + UNROLL + for (unsigned i = 0; i < GB_N_CHANNELS; i++) { + double multiplier = CH_STEP; + + if (gb->model < GB_MODEL_AGB) { + if (!GB_apu_is_DAC_enabled(gb, i)) { + gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate; + if (gb->apu_output.dac_discharge[i] < 0) { + multiplier = 0; + gb->apu_output.dac_discharge[i] = 0; + } + else { + multiplier *= smooth(gb->apu_output.dac_discharge[i]); + } + } + else { + gb->apu_output.dac_discharge[i] += ((double) DAC_ATTACK_SPEED) / gb->apu_output.sample_rate; + if (gb->apu_output.dac_discharge[i] > 1) { + gb->apu_output.dac_discharge[i] = 1; + } + else { + multiplier *= smooth(gb->apu_output.dac_discharge[i]); + } + } + } + + if (likely(gb->apu_output.last_update[i] == 0)) { + output.left += gb->apu_output.current_sample[i].left * multiplier; + output.right += gb->apu_output.current_sample[i].right * multiplier; + } + else { + refresh_channel(gb, i, 0); + output.left += (signed long) gb->apu_output.summed_samples[i].left * multiplier + / gb->apu_output.cycles_since_render; + output.right += (signed long) gb->apu_output.summed_samples[i].right * multiplier + / gb->apu_output.cycles_since_render; + gb->apu_output.summed_samples[i] = (GB_sample_t){0, 0}; + } + gb->apu_output.last_update[i] = 0; + } + gb->apu_output.cycles_since_render = 0; + + GB_sample_t filtered_output = gb->apu_output.highpass_mode? + (GB_sample_t) {output.left - gb->apu_output.highpass_diff.left, + output.right - gb->apu_output.highpass_diff.right} : + output; + + switch (gb->apu_output.highpass_mode) { + case GB_HIGHPASS_OFF: + gb->apu_output.highpass_diff = (GB_double_sample_t) {0, 0}; + break; + case GB_HIGHPASS_ACCURATE: + gb->apu_output.highpass_diff = (GB_double_sample_t) + {output.left - filtered_output.left * gb->apu_output.highpass_rate, + output.right - filtered_output.right * gb->apu_output.highpass_rate}; + break; + case GB_HIGHPASS_REMOVE_DC_OFFSET: { + unsigned mask = gb->io_registers[GB_IO_NR51]; + unsigned left_volume = 0; + unsigned right_volume = 0; + UNROLL + for (unsigned i = GB_N_CHANNELS; i--;) { + if (gb->apu.is_active[i]) { + if (mask & 1) { + left_volume += (gb->io_registers[GB_IO_NR50] & 7) * CH_STEP * 0xF; + } + if (mask & 0x10) { + right_volume += ((gb->io_registers[GB_IO_NR50] >> 4) & 7) * CH_STEP * 0xF; + } + } + else { + left_volume += gb->apu_output.current_sample[i].left * CH_STEP; + right_volume += gb->apu_output.current_sample[i].right * CH_STEP; + } + mask >>= 1; + } + gb->apu_output.highpass_diff = (GB_double_sample_t) + {left_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.left * gb->apu_output.highpass_rate, + right_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.right * gb->apu_output.highpass_rate}; + + case GB_HIGHPASS_MAX:; + } + + } + + assert(gb->apu_output.sample_callback); + gb->apu_output.sample_callback(gb, &filtered_output); +} + +static uint16_t new_sweep_sample_length(GB_gameboy_t *gb) +{ + uint16_t delta = gb->apu.shadow_sweep_sample_length >> (gb->io_registers[GB_IO_NR10] & 7); + if (gb->io_registers[GB_IO_NR10] & 8) { + return gb->apu.shadow_sweep_sample_length - delta; + } + return gb->apu.shadow_sweep_sample_length + delta; +} + +static void update_square_sample(GB_gameboy_t *gb, unsigned index) +{ + if (gb->apu.square_channels[index].current_sample_index & 0x80) return; + + uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; + update_sample(gb, index, + duties[gb->apu.square_channels[index].current_sample_index + duty * 8]? + gb->apu.square_channels[index].current_volume : 0, + 0); +} + + +/* the effects of NRX2 writes on current volume are not well documented and differ + between models and variants. The exact behavior can only be verified on CGB as it + requires the PCM12 register. The behavior implemented here was verified on *my* + CGB, which might behave differently from other CGB revisions, as well as from the + DMG, MGB or SGB/2 */ +static void nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value) +{ + if (value & 8) { + (*volume)++; + } + + if (((value ^ old_value) & 8)) { + *volume = 0x10 - *volume; + } + + if ((value & 7) && !(old_value & 7) && *volume && !(value & 8)) { + (*volume)--; + } + + if ((old_value & 7) && (value & 8)) { + (*volume)--; + } + + (*volume) &= 0xF; +} + +static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) +{ + uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; + + if (gb->apu.square_channels[index].volume_countdown || (nrx2 & 7)) { + if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) { + if (gb->cgb_double_speed) { + if (index == GB_SQUARE_1) { + gb->apu.pcm_mask[0] &= gb->apu.square_channels[GB_SQUARE_1].current_volume | 0xF1; + } + else { + gb->apu.pcm_mask[0] &= (gb->apu.square_channels[GB_SQUARE_2].current_volume << 2) | 0x1F; + } + } + + if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { + gb->apu.square_channels[index].current_volume++; + } + + else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) { + gb->apu.square_channels[index].current_volume--; + } + + gb->apu.square_channels[index].volume_countdown = nrx2 & 7; + + if (gb->apu.is_active[index]) { + update_square_sample(gb, index); + } + } + } +} + +static void tick_noise_envelope(GB_gameboy_t *gb) +{ + uint8_t nr42 = gb->io_registers[GB_IO_NR42]; + + if (gb->apu.noise_channel.volume_countdown || (nr42 & 7)) { + if (!gb->apu.noise_channel.volume_countdown || !--gb->apu.noise_channel.volume_countdown) { + if (gb->cgb_double_speed) { + gb->apu.pcm_mask[0] &= (gb->apu.noise_channel.current_volume << 2) | 0x1F; + } + if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { + gb->apu.noise_channel.current_volume++; + } + + else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { + gb->apu.noise_channel.current_volume--; + } + + gb->apu.noise_channel.volume_countdown = nr42 & 7; + + if (gb->apu.is_active[GB_NOISE]) { + update_sample(gb, GB_NOISE, + (gb->apu.noise_channel.lfsr & 1) ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + } + } +} + +void GB_apu_div_event(GB_gameboy_t *gb) +{ + if (!gb->apu.global_enable) return; + if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIP) { + gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIPPED; + return; + } + if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIPPED) { + gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_INACTIVE; + } + else { + gb->apu.div_divider++; + } + + if ((gb->apu.div_divider & 1) == 0) { + for (unsigned i = GB_SQUARE_2 + 1; i--;) { + uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; + if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0 && (nrx2 & 7)) { + tick_square_envelope(gb, i); + } + } + + if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0 && (gb->io_registers[GB_IO_NR42] & 7)) { + tick_noise_envelope(gb); + } + } + + if ((gb->apu.div_divider & 7) == 0) { + for (unsigned i = GB_SQUARE_2 + 1; i--;) { + tick_square_envelope(gb, i); + } + + tick_noise_envelope(gb); + } + + if ((gb->apu.div_divider & 1) == 1) { + for (unsigned i = GB_SQUARE_2 + 1; i--;) { + if (gb->apu.square_channels[i].length_enabled) { + if (gb->apu.square_channels[i].pulse_length) { + if (!--gb->apu.square_channels[i].pulse_length) { + gb->apu.is_active[i] = false; + update_sample(gb, i, 0, 0); + } + } + } + } + + if (gb->apu.wave_channel.length_enabled) { + if (gb->apu.wave_channel.pulse_length) { + if (!--gb->apu.wave_channel.pulse_length) { + gb->apu.is_active[GB_WAVE] = false; + update_sample(gb, GB_WAVE, 0, 0); + } + } + } + + if (gb->apu.noise_channel.length_enabled) { + if (gb->apu.noise_channel.pulse_length) { + if (!--gb->apu.noise_channel.pulse_length) { + gb->apu.is_active[GB_NOISE] = false; + update_sample(gb, GB_NOISE, 0, 0); + } + } + } + } + + if ((gb->apu.div_divider & 3) == 3) { + if (!gb->apu.sweep_enabled) { + return; + } + if (gb->apu.square_sweep_countdown) { + if (!--gb->apu.square_sweep_countdown) { + if ((gb->io_registers[GB_IO_NR10] & 0x70) && (gb->io_registers[GB_IO_NR10] & 0x07)) { + gb->apu.square_channels[GB_SQUARE_1].sample_length = + gb->apu.shadow_sweep_sample_length = + gb->apu.new_sweep_sample_length; + } + + if (gb->io_registers[GB_IO_NR10] & 0x70) { + /* Recalculation and overflow check only occurs after a delay */ + gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div; + } + + gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); + if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; + } + } + } +} + + +void GB_apu_run(GB_gameboy_t *gb) +{ + /* Convert 4MHZ to 2MHz. apu_cycles is always divisable by 4. */ + uint8_t cycles = gb->apu.apu_cycles >> 2; + gb->apu.apu_cycles = 0; + if (!cycles) return; + + if (likely(!gb->stopped || GB_is_cgb(gb))) { + /* To align the square signal to 1MHz */ + gb->apu.lf_div ^= cycles & 1; + gb->apu.noise_channel.alignment += cycles; + + if (gb->apu.square_sweep_calculate_countdown) { + if (gb->apu.square_sweep_calculate_countdown > cycles) { + gb->apu.square_sweep_calculate_countdown -= cycles; + } + else { + /* APU bug: sweep frequency is checked after adding the sweep delta twice */ + gb->apu.new_sweep_sample_length = new_sweep_sample_length(gb); + if (gb->apu.new_sweep_sample_length > 0x7ff) { + gb->apu.is_active[GB_SQUARE_1] = false; + update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles); + gb->apu.sweep_enabled = false; + } + gb->apu.sweep_decreasing |= gb->io_registers[GB_IO_NR10] & 8; + gb->apu.square_sweep_calculate_countdown = 0; + } + } + + UNROLL + for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { + if (gb->apu.is_active[i]) { + uint8_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) { + cycles_left -= gb->apu.square_channels[i].sample_countdown + 1; + gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; + gb->apu.square_channels[i].current_sample_index++; + gb->apu.square_channels[i].current_sample_index &= 0x7; + if (cycles_left == 0 && gb->apu.samples[i] == 0) { + gb->apu.pcm_mask[0] &= i == GB_SQUARE_1? 0xF0 : 0x0F; + } + + update_square_sample(gb, i); + } + if (cycles_left) { + gb->apu.square_channels[i].sample_countdown -= cycles_left; + } + } + } + + gb->apu.wave_channel.wave_form_just_read = false; + if (gb->apu.is_active[GB_WAVE]) { + uint8_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { + cycles_left -= gb->apu.wave_channel.sample_countdown + 1; + gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; + gb->apu.wave_channel.current_sample_index++; + gb->apu.wave_channel.current_sample_index &= 0x1F; + gb->apu.wave_channel.current_sample = + gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; + update_sample(gb, GB_WAVE, + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, + cycles - cycles_left); + gb->apu.wave_channel.wave_form_just_read = true; + } + if (cycles_left) { + gb->apu.wave_channel.sample_countdown -= cycles_left; + gb->apu.wave_channel.wave_form_just_read = false; + } + } + + if (gb->apu.is_active[GB_NOISE]) { + uint8_t cycles_left = cycles; + while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) { + cycles_left -= gb->apu.noise_channel.sample_countdown + 1; + gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3; + + /* Step LFSR */ + unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; + bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; + gb->apu.noise_channel.lfsr >>= 1; + + if (new_high_bit) { + gb->apu.noise_channel.lfsr |= high_bit_mask; + } + else { + /* This code is not redundent, it's relevant when switching LFSR widths */ + gb->apu.noise_channel.lfsr &= ~high_bit_mask; + } + + gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + + if (cycles_left == 0 && gb->apu.samples[GB_NOISE] == 0) { + gb->apu.pcm_mask[1] &= 0x0F; + } + + update_sample(gb, GB_NOISE, + gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + if (cycles_left) { + gb->apu.noise_channel.sample_countdown -= cycles_left; + } + } + } + + if (gb->apu_output.sample_rate) { + gb->apu_output.cycles_since_render += cycles; + + if (gb->apu_output.sample_cycles >= gb->apu_output.cycles_per_sample) { + gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample; + render(gb); + } + } +} +void GB_apu_init(GB_gameboy_t *gb) +{ + memset(&gb->apu, 0, sizeof(gb->apu)); + /* Restore the wave form */ + for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) { + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF; + } + gb->apu.lf_div = 1; + /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on, + the first DIV/APU event is skipped. */ + if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) { + gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIP; + gb->apu.div_divider = 1; + } +} + +uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) +{ + if (reg == GB_IO_NR52) { + uint8_t value = 0; + for (unsigned i = 0; i < GB_N_CHANNELS; i++) { + value >>= 1; + if (gb->apu.is_active[i]) { + value |= 0x8; + } + } + if (gb->apu.global_enable) { + value |= 0x80; + } + value |= 0x70; + return value; + } + + static const char read_mask[GB_IO_WAV_END - GB_IO_NR10 + 1] = { + /* NRX0 NRX1 NRX2 NRX3 NRX4 */ + 0x80, 0x3F, 0x00, 0xFF, 0xBF, // NR1X + 0xFF, 0x3F, 0x00, 0xFF, 0xBF, // NR2X + 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, // NR3X + 0xFF, 0xFF, 0x00, 0x00, 0xBF, // NR4X + 0x00, 0x00, 0x70, 0xFF, 0xFF, // NR5X + + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Unused + // Wave RAM + 0, /* ... */ + }; + + if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) { + if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) { + return 0xFF; + } + reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; + } + + return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10]; +} + +void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) +{ + if (!gb->apu.global_enable && reg != GB_IO_NR52 && reg < GB_IO_WAV_START && (GB_is_cgb(gb) || + ( + reg != GB_IO_NR11 && + reg != GB_IO_NR21 && + reg != GB_IO_NR31 && + reg != GB_IO_NR41 + ) + )) { + return; + } + + if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) { + if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) { + return; + } + reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; + } + + /* Todo: this can and should be rewritten with a function table. */ + switch (reg) { + /* Globals */ + case GB_IO_NR50: + case GB_IO_NR51: + gb->io_registers[reg] = value; + /* These registers affect the output of all 4 channels (but not the output of the PCM registers).*/ + /* We call update_samples with the current value so the APU output is updated with the new outputs */ + for (unsigned i = GB_N_CHANNELS; i--;) { + update_sample(gb, i, gb->apu.samples[i], 0); + } + break; + case GB_IO_NR52: { + + uint8_t old_nrx1[] = { + gb->io_registers[GB_IO_NR11], + gb->io_registers[GB_IO_NR21], + gb->io_registers[GB_IO_NR31], + gb->io_registers[GB_IO_NR41] + }; + if ((value & 0x80) && !gb->apu.global_enable) { + GB_apu_init(gb); + gb->apu.global_enable = true; + } + else if (!(value & 0x80) && gb->apu.global_enable) { + for (unsigned i = GB_N_CHANNELS; i--;) { + update_sample(gb, i, 0, 0); + } + memset(&gb->apu, 0, sizeof(gb->apu)); + memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10); + old_nrx1[0] &= 0x3F; + old_nrx1[1] &= 0x3F; + + gb->apu.global_enable = false; + } + + if (!GB_is_cgb(gb) && (value & 0x80)) { + GB_apu_write(gb, GB_IO_NR11, old_nrx1[0]); + GB_apu_write(gb, GB_IO_NR21, old_nrx1[1]); + GB_apu_write(gb, GB_IO_NR31, old_nrx1[2]); + GB_apu_write(gb, GB_IO_NR41, old_nrx1[3]); + } + } + break; + + /* Square channels */ + case GB_IO_NR10: + if (gb->apu.sweep_decreasing && !(value & 8)) { + gb->apu.is_active[GB_SQUARE_1] = false; + update_sample(gb, GB_SQUARE_1, 0, 0); + gb->apu.sweep_enabled = false; + gb->apu.square_sweep_calculate_countdown = 0; + } + if ((value & 0x70) == 0) { + /* Todo: what happens if we set period to 0 while a calculate event is scheduled, and then + re-set it to non-zero? */ + gb->apu.square_sweep_calculate_countdown = 0; + } + break; + + case GB_IO_NR11: + case GB_IO_NR21: { + unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1; + gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3f)); + if (!gb->apu.global_enable) { + value &= 0x3f; + } + break; + } + + case GB_IO_NR12: + case GB_IO_NR22: { + unsigned index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1; + if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) { + /* Envelope disabled */ + gb->apu.square_channels[index].volume_countdown = 0; + } + if ((value & 0xF8) == 0) { + /* This disables the DAC */ + gb->io_registers[reg] = value; + gb->apu.is_active[index] = false; + update_sample(gb, index, 0, 0); + } + else if (gb->apu.is_active[index]) { + nrx2_glitch(&gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]); + update_square_sample(gb, index); + } + + break; + } + + case GB_IO_NR13: + case GB_IO_NR23: { + unsigned index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1; + gb->apu.square_channels[index].sample_length &= ~0xFF; + gb->apu.square_channels[index].sample_length |= value & 0xFF; + break; + } + + case GB_IO_NR14: + case GB_IO_NR24: { + unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1; + + /* TODO: When the sample length changes right before being updated, the countdown should change to the + old length, but the current sample should not change. Because our write timing isn't accurate to + the T-cycle, we hack around it by stepping the sample index backwards. */ + if ((value & 0x80) == 0 && gb->apu.is_active[index]) { + /* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on + double speed. */ + if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */ || gb->apu.square_channels[index].sample_countdown & 1) { + if (gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) { + gb->apu.square_channels[index].current_sample_index--; + gb->apu.square_channels[index].current_sample_index &= 7; + } + } + } + + gb->apu.square_channels[index].sample_length &= 0xFF; + gb->apu.square_channels[index].sample_length |= (value & 7) << 8; + if (index == GB_SQUARE_1) { + gb->apu.shadow_sweep_sample_length = + gb->apu.new_sweep_sample_length = + gb->apu.square_channels[0].sample_length; + } + if (value & 0x80) { + /* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by + turning the APU off. */ + if (!gb->apu.is_active[index]) { + gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 6 - gb->apu.lf_div; + } + else { + /* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/ + gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div; + } + gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4; + + /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously + started sound). The playback itself is not instant which is why we don't update the sample for other + cases. */ + if (gb->apu.is_active[index]) { + update_square_sample(gb, index); + } + + gb->apu.square_channels[index].volume_countdown = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 7; + + if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) { + gb->apu.is_active[index] = true; + update_sample(gb, index, 0, 0); + /* We use the highest bit in current_sample_index to mark this sample is not actually playing yet, */ + gb->apu.square_channels[index].current_sample_index |= 0x80; + } + if (gb->apu.square_channels[index].pulse_length == 0) { + gb->apu.square_channels[index].pulse_length = 0x40; + gb->apu.square_channels[index].length_enabled = false; + } + + if (index == GB_SQUARE_1) { + gb->apu.sweep_decreasing = false; + if (gb->io_registers[GB_IO_NR10] & 7) { + /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ + gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div; + } + else { + gb->apu.square_sweep_calculate_countdown = 0; + } + gb->apu.sweep_enabled = gb->io_registers[GB_IO_NR10] & 0x77; + gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); + if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; + } + + } + + /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ + if ((value & 0x40) && + !gb->apu.square_channels[index].length_enabled && + (gb->apu.div_divider & 1) && + gb->apu.square_channels[index].pulse_length) { + gb->apu.square_channels[index].pulse_length--; + if (gb->apu.square_channels[index].pulse_length == 0) { + if (value & 0x80) { + gb->apu.square_channels[index].pulse_length = 0x3F; + } + else { + gb->apu.is_active[index] = false; + update_sample(gb, index, 0, 0); + } + } + } + gb->apu.square_channels[index].length_enabled = value & 0x40; + break; + } + + /* Wave channel */ + case GB_IO_NR30: + gb->apu.wave_channel.enable = value & 0x80; + if (!gb->apu.wave_channel.enable) { + gb->apu.is_active[GB_WAVE] = false; + update_sample(gb, GB_WAVE, 0, 0); + } + break; + case GB_IO_NR31: + gb->apu.wave_channel.pulse_length = (0x100 - value); + break; + case GB_IO_NR32: + gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; + if (gb->apu.is_active[GB_WAVE]) { + update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0); + } + break; + case GB_IO_NR33: + gb->apu.wave_channel.sample_length &= ~0xFF; + gb->apu.wave_channel.sample_length |= value & 0xFF; + break; + case GB_IO_NR34: + gb->apu.wave_channel.sample_length &= 0xFF; + gb->apu.wave_channel.sample_length |= (value & 7) << 8; + if ((value & 0x80)) { + /* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU + reads from it. */ + if (!GB_is_cgb(gb) && + gb->apu.is_active[GB_WAVE] && + gb->apu.wave_channel.sample_countdown == 0 && + gb->apu.wave_channel.enable) { + unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF; + + /* This glitch varies between models and even specific instances: + DMG-B: Most of them behave as emulated. A few behave differently. + SGB: As far as I know, all tested instances behave as emulated. + MGB, SGB2: Most instances behave non-deterministically, a few behave as emulated. + + Additionally, I believe DMGs, including those we behave differently than emulated, + are all deterministic. */ + if (offset < 4) { + gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset]; + gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2]; + gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1]; + } + else { + memcpy(gb->io_registers + GB_IO_WAV_START, + gb->io_registers + GB_IO_WAV_START + (offset & ~3), + 4); + memcpy(gb->apu.wave_channel.wave_form, + gb->apu.wave_channel.wave_form + (offset & ~3) * 2, + 8); + } + } + if (!gb->apu.is_active[GB_WAVE]) { + gb->apu.is_active[GB_WAVE] = true; + update_sample(gb, GB_WAVE, + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, + 0); + } + gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3; + gb->apu.wave_channel.current_sample_index = 0; + if (gb->apu.wave_channel.pulse_length == 0) { + gb->apu.wave_channel.pulse_length = 0x100; + gb->apu.wave_channel.length_enabled = false; + } + /* Note that we don't change the sample just yet! This was verified on hardware. */ + } + + /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ + if ((value & 0x40) && + !gb->apu.wave_channel.length_enabled && + (gb->apu.div_divider & 1) && + gb->apu.wave_channel.pulse_length) { + gb->apu.wave_channel.pulse_length--; + if (gb->apu.wave_channel.pulse_length == 0) { + if (value & 0x80) { + gb->apu.wave_channel.pulse_length = 0xFF; + } + else { + gb->apu.is_active[GB_WAVE] = false; + update_sample(gb, GB_WAVE, 0, 0); + } + } + } + gb->apu.wave_channel.length_enabled = value & 0x40; + if (gb->apu.is_active[GB_WAVE] && !gb->apu.wave_channel.enable) { + gb->apu.is_active[GB_WAVE] = false; + update_sample(gb, GB_WAVE, 0, 0); + } + + break; + + /* Noise Channel */ + + case GB_IO_NR41: { + gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3f)); + break; + } + + case GB_IO_NR42: { + if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) { + /* Envelope disabled */ + gb->apu.noise_channel.volume_countdown = 0; + } + if ((value & 0xF8) == 0) { + /* This disables the DAC */ + gb->io_registers[reg] = value; + gb->apu.is_active[GB_NOISE] = false; + update_sample(gb, GB_NOISE, 0, 0); + } + else if (gb->apu.is_active[GB_NOISE]) { + nrx2_glitch(&gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]); + update_sample(gb, GB_NOISE, + gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + break; + } + + case GB_IO_NR43: { + gb->apu.noise_channel.narrow = value & 8; + unsigned divisor = (value & 0x07) << 1; + if (!divisor) divisor = 1; + gb->apu.noise_channel.sample_length = (divisor << (value >> 4)) - 1; + + /* Todo: changing the frequency sometimes delays the next sample. This is probably + due to how the frequency is actually calculated in the noise channel, which is probably + not by calculating the effective sample length and counting simiarly to the other channels. + This is not emulated correctly. */ + break; + } + + case GB_IO_NR44: { + if (value & 0x80) { + gb->apu.noise_channel.sample_countdown = (gb->apu.noise_channel.sample_length) * 2 + 6 - gb->apu.lf_div; + + /* I'm COMPLETELY unsure about this logic, but it passes all relevant tests. + See comment in NR43. */ + if ((gb->io_registers[GB_IO_NR43] & 7) && (gb->apu.noise_channel.alignment & 2) == 0) { + if ((gb->io_registers[GB_IO_NR43] & 7) == 1) { + gb->apu.noise_channel.sample_countdown += 2; + } + else { + gb->apu.noise_channel.sample_countdown -= 2; + } + } + if (gb->apu.is_active[GB_NOISE]) { + gb->apu.noise_channel.sample_countdown += 2; + } + + gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4; + + /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously + started sound). The playback itself is not instant which is why we don't update the sample for other + cases. */ + if (gb->apu.is_active[GB_NOISE]) { + update_sample(gb, GB_NOISE, + gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + gb->apu.noise_channel.lfsr = 0; + gb->apu.current_lfsr_sample = false; + gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; + + if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { + gb->apu.is_active[GB_NOISE] = true; + update_sample(gb, GB_NOISE, 0, 0); + } + + if (gb->apu.noise_channel.pulse_length == 0) { + gb->apu.noise_channel.pulse_length = 0x40; + gb->apu.noise_channel.length_enabled = false; + } + } + + /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ + if ((value & 0x40) && + !gb->apu.noise_channel.length_enabled && + (gb->apu.div_divider & 1) && + gb->apu.noise_channel.pulse_length) { + gb->apu.noise_channel.pulse_length--; + if (gb->apu.noise_channel.pulse_length == 0) { + if (value & 0x80) { + gb->apu.noise_channel.pulse_length = 0x3F; + } + else { + gb->apu.is_active[GB_NOISE] = false; + update_sample(gb, GB_NOISE, 0, 0); + } + } + } + gb->apu.noise_channel.length_enabled = value & 0x40; + break; + } + + default: + if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) { + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF; + } + } + gb->io_registers[reg] = value; +} + +void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate) +{ + + gb->apu_output.sample_rate = sample_rate; + if (sample_rate) { + gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); + } + gb->apu_output.rate_set_in_clocks = false; + GB_apu_update_cycles_per_sample(gb); +} + +void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample) +{ + + if (cycles_per_sample == 0) { + GB_set_sample_rate(gb, 0); + return; + } + gb->apu_output.cycles_per_sample = cycles_per_sample; + gb->apu_output.sample_rate = GB_get_clock_rate(gb) / cycles_per_sample * 2; + gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample); + gb->apu_output.rate_set_in_clocks = true; +} + +void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback) +{ + gb->apu_output.sample_callback = callback; +} + +void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode) +{ + gb->apu_output.highpass_mode = mode; +} + +void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb) +{ + if (gb->apu_output.rate_set_in_clocks) return; + if (gb->apu_output.sample_rate) { + gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */ + } +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/apu.h b/waterbox/bsnescore/bsnes/gb/Core/apu.h new file mode 100644 index 0000000000..a3a36a63f6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/apu.h @@ -0,0 +1,169 @@ +#ifndef apu_h +#define apu_h +#include +#include +#include +#include "gb_struct_def.h" + + +#ifdef GB_INTERNAL +/* Speed = 1 / Length (in seconds) */ +#define DAC_DECAY_SPEED 20000 +#define DAC_ATTACK_SPEED 20000 + + +/* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */ +#ifdef WIIU +/* Todo: Remove this hack once https://github.com/libretro/RetroArch/issues/6252 is fixed*/ +#define MAX_CH_AMP (0xFF0 / 2) +#else +#define MAX_CH_AMP 0xFF0 +#endif +#define CH_STEP (MAX_CH_AMP/0xF/8) +#endif + + + +/* APU ticks are 2MHz, triggered by an internal APU clock. */ + +typedef struct +{ + int16_t left; + int16_t right; +} GB_sample_t; + +typedef struct +{ + double left; + double right; +} GB_double_sample_t; + +enum GB_CHANNELS { + GB_SQUARE_1, + GB_SQUARE_2, + GB_WAVE, + GB_NOISE, + GB_N_CHANNELS +}; + +typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample); + +typedef struct +{ + bool global_enable; + uint8_t apu_cycles; + + uint8_t samples[GB_N_CHANNELS]; + bool is_active[GB_N_CHANNELS]; + + uint8_t div_divider; // The DIV register ticks the APU at 512Hz, but is then divided + // once more to generate 128Hz and 64Hz clocks + + uint8_t lf_div; // The APU runs in 2MHz, but channels 1, 2 and 4 run in 1MHZ so we divide + // need to divide the signal. + + uint8_t square_sweep_countdown; // In 128Hz + uint8_t square_sweep_calculate_countdown; // In 2 MHz + uint16_t new_sweep_sample_length; + uint16_t shadow_sweep_sample_length; + bool sweep_enabled; + bool sweep_decreasing; + + struct { + uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks + uint8_t current_volume; // Reloaded from NRX2 + uint8_t volume_countdown; // Reloaded from NRX2 + uint8_t current_sample_index; /* For save state compatibility, + highest bit is reused (See NR14/NR24's + write code)*/ + + uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) + uint16_t sample_length; // From NRX3, NRX4, in APU ticks + bool length_enabled; // NRX4 + + } square_channels[2]; + + struct { + bool enable; // NR30 + uint16_t pulse_length; // Reloaded from NR31 (xorred), in 256Hz DIV ticks + uint8_t shift; // NR32 + uint16_t sample_length; // NR33, NR34, in APU ticks + bool length_enabled; // NR34 + + uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) + uint8_t current_sample_index; + uint8_t current_sample; // Current sample before shifting. + + int8_t wave_form[32]; + bool wave_form_just_read; + } wave_channel; + + struct { + uint16_t pulse_length; // Reloaded from NR41 (xorred), in 256Hz DIV ticks + uint8_t current_volume; // Reloaded from NR42 + uint8_t volume_countdown; // Reloaded from NR42 + uint16_t lfsr; + bool narrow; + + uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length) + uint16_t sample_length; // From NR43, in APU ticks + bool length_enabled; // NR44 + + uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of + // 1MHz. This variable keeps track of the alignment. + + } noise_channel; + +#define GB_SKIP_DIV_EVENT_INACTIVE 0 +#define GB_SKIP_DIV_EVENT_SKIPPED 1 +#define GB_SKIP_DIV_EVENT_SKIP 2 + uint8_t skip_div_event; + bool current_lfsr_sample; + uint8_t pcm_mask[2]; // For CGB-0 to CGB-C PCM read glitch +} GB_apu_t; + +typedef enum { + GB_HIGHPASS_OFF, // Do not apply any filter, keep DC offset + GB_HIGHPASS_ACCURATE, // Apply a highpass filter similar to the one used on hardware + GB_HIGHPASS_REMOVE_DC_OFFSET, // Remove DC Offset without affecting the waveform + GB_HIGHPASS_MAX +} GB_highpass_mode_t; + +typedef struct { + unsigned sample_rate; + + double sample_cycles; // In 8 MHz units + double cycles_per_sample; + + // Samples are NOT normalized to MAX_CH_AMP * 4 at this stage! + unsigned cycles_since_render; + unsigned last_update[GB_N_CHANNELS]; + GB_sample_t current_sample[GB_N_CHANNELS]; + GB_sample_t summed_samples[GB_N_CHANNELS]; + double dac_discharge[GB_N_CHANNELS]; + + GB_highpass_mode_t highpass_mode; + double highpass_rate; + GB_double_sample_t highpass_diff; + + GB_sample_callback_t sample_callback; + + bool rate_set_in_clocks; +} GB_apu_output_t; + +void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate); +void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */ +void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); +void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); +#ifdef GB_INTERNAL +bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); +void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); +uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); +void GB_apu_div_event(GB_gameboy_t *gb); +void GB_apu_init(GB_gameboy_t *gb); +void GB_apu_run(GB_gameboy_t *gb); +void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); +void GB_borrow_sgb_border(GB_gameboy_t *gb); +#endif + +#endif /* apu_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/camera.c b/waterbox/bsnescore/bsnes/gb/Core/camera.c new file mode 100644 index 0000000000..bef84890cd --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/camera.c @@ -0,0 +1,149 @@ +#include "gb.h" + +static signed noise_seed = 0; + +/* This is not a complete emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported. + We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */ + +static uint8_t generate_noise(uint8_t x, uint8_t y) +{ + signed value = (x + y * 128 + noise_seed); + uint8_t *data = (uint8_t *) &value; + unsigned hash = 0; + + while ((signed *) data != &value + 1) { + hash ^= (*data << 8); + if (hash & 0x8000) { + hash ^= 0x8a00; + hash ^= *data; + } + data++; + hash <<= 1; + } + return (hash >> 8); +} + +static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y) +{ + if (x >= 128) { + x = 0; + } + if (y >= 112) { + y = 0; + } + + long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (generate_noise(x, y)); + + static const double gain_values[] = + {0.8809390, 0.9149149, 0.9457498, 0.9739758, + 1.0000000, 1.0241412, 1.0466537, 1.0677433, + 1.0875793, 1.1240310, 1.1568911, 1.1868043, + 1.2142561, 1.2396208, 1.2743837, 1.3157323, + 1.3525190, 1.3856512, 1.4157897, 1.4434309, + 1.4689574, 1.4926697, 1.5148087, 1.5355703, + 1.5551159, 1.5735801, 1.5910762, 1.6077008, + 1.6235366, 1.6386550, 1.6531183, 1.6669808}; + /* Multiply color by gain value */ + color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x1F]; + + + /* Color is multiplied by the exposure register to simulate exposure. */ + color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000; + + return color; +} + +uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) { + /* Forbid reading the image while the camera is busy. */ + return 0xFF; + } + uint8_t tile_x = addr / 0x10 % 0x10; + uint8_t tile_y = addr / 0x10 / 0x10; + + uint8_t y = ((addr >> 1) & 0x7) + tile_y * 8; + uint8_t bit = addr & 1; + + uint8_t ret = 0; + + for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) { + + long color = get_processed_color(gb, x, y); + + static const double edge_enhancement_ratios[] = {0.5, 0.75, 1, 1.25, 2, 3, 4, 5}; + double edge_enhancement_ratio = edge_enhancement_ratios[(gb->camera_registers[GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE] >> 4) & 0x7]; + if ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0xE0) == 0xE0) { + color += (color * 4) * edge_enhancement_ratio; + color -= get_processed_color(gb, x - 1, y) * edge_enhancement_ratio; + color -= get_processed_color(gb, x + 1, y) * edge_enhancement_ratio; + color -= get_processed_color(gb, x, y - 1) * edge_enhancement_ratio; + color -= get_processed_color(gb, x, y + 1) * edge_enhancement_ratio; + } + + + /* The camera's registers are used as a threshold pattern, which defines the dithering */ + uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START; + + if (color < gb->camera_registers[pattern_base]) { + color = 3; + } + else if (color < gb->camera_registers[pattern_base + 1]) { + color = 2; + } + else if (color < gb->camera_registers[pattern_base + 2]) { + color = 1; + } + else { + color = 0; + } + + ret <<= 1; + ret |= (color >> bit) & 1; + } + + return ret; +} + +void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback) +{ + gb->camera_get_pixel_callback = callback; +} + +void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback) +{ + gb->camera_update_request_callback = callback; +} + +void GB_camera_updated(GB_gameboy_t *gb) +{ + gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] &= ~1; +} + +void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + addr &= 0x7F; + if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) { + value &= 0x7; + noise_seed = rand(); + if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && gb->camera_update_request_callback) { + /* If no callback is set, ignore the write as if the camera is instantly done */ + gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] |= 1; + gb->camera_update_request_callback(gb); + } + } + else { + if (addr >= 0x36) { + GB_log(gb, "Wrote invalid camera register %02x: %2x\n", addr, value); + return; + } + gb->camera_registers[addr] = value; + } +} +uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr) +{ + if ((addr & 0x7F) == 0) { + return gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS]; + } + return 0; +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/camera.h b/waterbox/bsnescore/bsnes/gb/Core/camera.h new file mode 100644 index 0000000000..21c69b68e6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/camera.h @@ -0,0 +1,29 @@ +#ifndef camera_h +#define camera_h +#include +#include "gb_struct_def.h" + +typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y); +typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb); + +enum { + GB_CAMERA_SHOOT_AND_1D_FLAGS = 0, + GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS = 1, + GB_CAMERA_EXPOSURE_HIGH = 2, + GB_CAMERA_EXPOSURE_LOW = 3, + GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE = 4, + GB_CAMERA_DITHERING_PATTERN_START = 6, + GB_CAMERA_DITHERING_PATTERN_END = 0x35, +}; + +uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr); + +void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback); +void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback); + +void GB_camera_updated(GB_gameboy_t *gb); + +void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr); + +#endif diff --git a/waterbox/bsnescore/bsnes/gb/Core/cheats.c b/waterbox/bsnescore/bsnes/gb/Core/cheats.c new file mode 100644 index 0000000000..14875e0101 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/cheats.c @@ -0,0 +1,313 @@ +#include "gb.h" +#include "cheats.h" +#include +#include +#include + +static inline uint8_t hash_addr(uint16_t addr) +{ + return addr; +} + +static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x4000) { + return gb->mbc_rom0_bank; + } + + if (addr < 0x8000) { + return gb->mbc_rom_bank; + } + + if (addr < 0xD000) { + return 0; + } + + if (addr < 0xE000) { + return gb->cgb_ram_bank; + } + + return 0; +} + +void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value) +{ + if (!gb->cheat_enabled) return; + if (!gb->boot_rom_finished) return; + const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)]; + if (hash) { + for (unsigned i = 0; i < hash->size; i++) { + GB_cheat_t *cheat = hash->cheats[i]; + if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) { + if (cheat->bank == GB_CHEAT_ANY_BANK || cheat->bank == bank_for_addr(gb, address)) { + *value = cheat->value; + break; + } + } + } + } +} + +bool GB_cheats_enabled(GB_gameboy_t *gb) +{ + return gb->cheat_enabled; +} + +void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled) +{ + gb->cheat_enabled = enabled; +} + +void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled) +{ + GB_cheat_t *cheat = malloc(sizeof(*cheat)); + cheat->address = address; + cheat->bank = bank; + cheat->value = value; + cheat->old_value = old_value; + cheat->use_old_value = use_old_value; + cheat->enabled = enabled; + strncpy(cheat->description, description, sizeof(cheat->description)); + cheat->description[sizeof(cheat->description) - 1] = 0; + gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(*cheat)); + gb->cheats[gb->cheat_count - 1] = cheat; + + GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)]; + if (!*hash) { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat)); + (*hash)->size = 1; + (*hash)->cheats[0] = cheat; + } + else { + (*hash)->size++; + *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + (*hash)->cheats[(*hash)->size - 1] = cheat; + } +} + +const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size) +{ + *size = gb->cheat_count; + return (void *)gb->cheats; +} +void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat) +{ + for (unsigned i = 0; i < gb->cheat_count; i++) { + if (gb->cheats[i] == cheat) { + gb->cheats[i] = gb->cheats[--gb->cheat_count]; + if (gb->cheat_count == 0) { + free(gb->cheats); + gb->cheats = NULL; + } + else { + gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(*cheat)); + } + break; + } + } + + GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)]; + for (unsigned i = 0; i < (*hash)->size; i++) { + if ((*hash)->cheats[i] == cheat) { + (*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--]; + if ((*hash)->size == 0) { + free(*hash); + *hash = NULL; + } + else { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + } + break; + } + } + + free((void *)cheat); +} + +bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled) +{ + uint8_t dummy; + /* GameShark */ + { + uint8_t bank; + uint8_t value; + uint16_t address; + if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) { + if (bank >= 0x80) { + bank &= 0xF; + } + GB_add_cheat(gb, description, address, bank, value, 0, false, enabled); + return true; + } + } + + /* GameGenie */ + { + char stripped_cheat[10] = {0,}; + for (unsigned i = 0; i < 9 && *cheat; i++) { + stripped_cheat[i] = *(cheat++); + while (*cheat == '-') { + cheat++; + } + } + + // Delete the 7th character; + stripped_cheat[7] = stripped_cheat[8]; + stripped_cheat[8] = 0; + + uint8_t old_value; + uint8_t value; + uint16_t address; + if (sscanf(stripped_cheat, "%02hhx%04hx%02hhx%c", &value, &address, &old_value, &dummy) == 3) { + address = (uint16_t)(address >> 4) | (uint16_t)(address << 12); + address ^= 0xF000; + if (address > 0x7FFF) { + return false; + } + old_value = (uint8_t)(old_value >> 2) | (uint8_t)(old_value << 6); + old_value ^= 0xBA; + GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, old_value, true, enabled); + return true; + } + + if (sscanf(stripped_cheat, "%02hhx%04hx%c", &value, &address, &dummy) == 2) { + address = (uint16_t)(address >> 4) | (uint16_t)(address << 12); + address ^= 0xF000; + if (address > 0x7FFF) { + return false; + } + GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, false, true, enabled); + return true; + } + } + return false; +} + +void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled) +{ + GB_cheat_t *cheat = NULL; + for (unsigned i = 0; i < gb->cheat_count; i++) { + if (gb->cheats[i] == _cheat) { + cheat = gb->cheats[i]; + break; + } + } + + assert(cheat); + + if (cheat->address != address) { + /* Remove from old bucket */ + GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)]; + for (unsigned i = 0; i < (*hash)->size; i++) { + if ((*hash)->cheats[i] == cheat) { + (*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--]; + if ((*hash)->size == 0) { + free(*hash); + *hash = NULL; + } + else { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + } + break; + } + } + cheat->address = address; + + /* Add to new bucket */ + hash = &gb->cheat_hash[hash_addr(address)]; + if (!*hash) { + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat)); + (*hash)->size = 1; + (*hash)->cheats[0] = cheat; + } + else { + (*hash)->size++; + *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + (*hash)->cheats[(*hash)->size - 1] = cheat; + } + } + cheat->bank = bank; + cheat->value = value; + cheat->old_value = old_value; + cheat->use_old_value = use_old_value; + cheat->enabled = enabled; + if (description != cheat->description) { + strncpy(cheat->description, description, sizeof(cheat->description)); + cheat->description[sizeof(cheat->description) - 1] = 0; + } +} + +#define CHEAT_MAGIC 'SBCh' + +void GB_load_cheats(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + return; + } + + uint32_t magic = 0; + uint32_t struct_size = 0; + fread(&magic, sizeof(magic), 1, f); + fread(&struct_size, sizeof(struct_size), 1, f); + if (magic != CHEAT_MAGIC && magic != __builtin_bswap32(CHEAT_MAGIC)) { + GB_log(gb, "The file is not a SameBoy cheat database"); + return; + } + + if (struct_size != sizeof(GB_cheat_t)) { + GB_log(gb, "This cheat database is not compatible with this version of SameBoy"); + return; + } + + // Remove all cheats first + while (gb->cheats) { + GB_remove_cheat(gb, gb->cheats[0]); + } + + GB_cheat_t cheat; + while (fread(&cheat, sizeof(cheat), 1, f)) { + if (magic == __builtin_bswap32(CHEAT_MAGIC)) { + cheat.address = __builtin_bswap16(cheat.address); + cheat.bank = __builtin_bswap16(cheat.bank); + } + cheat.description[sizeof(cheat.description) - 1] = 0; + GB_add_cheat(gb, cheat.description, cheat.address, cheat.bank, cheat.value, cheat.old_value, cheat.use_old_value, cheat.enabled); + } + + return; +} + +int GB_save_cheats(GB_gameboy_t *gb, const char *path) +{ + if (!gb->cheat_count) return 0; // Nothing to save. + FILE *f = fopen(path, "wb"); + if (!f) { + GB_log(gb, "Could not dump cheat database: %s.\n", strerror(errno)); + return errno; + } + + uint32_t magic = CHEAT_MAGIC; + uint32_t struct_size = sizeof(GB_cheat_t); + + if (fwrite(&magic, sizeof(magic), 1, f) != 1) { + fclose(f); + return EIO; + } + + if (fwrite(&struct_size, sizeof(struct_size), 1, f) != 1) { + fclose(f); + return EIO; + } + + for (size_t i = 0; i cheat_count; i++) { + if (fwrite(gb->cheats[i], sizeof(*gb->cheats[i]), 1, f) != 1) { + fclose(f); + return EIO; + } + } + + errno = 0; + fclose(f); + return errno; +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/cheats.h b/waterbox/bsnescore/bsnes/gb/Core/cheats.h new file mode 100644 index 0000000000..cf8aa20d92 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/cheats.h @@ -0,0 +1,42 @@ +#ifndef cheats_h +#define cheats_h +#include "gb_struct_def.h" + +#define GB_CHEAT_ANY_BANK 0xFFFF + +typedef struct GB_cheat_s GB_cheat_t; + +void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled); +void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled); +bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled); +const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size); +void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat); +bool GB_cheats_enabled(GB_gameboy_t *gb); +void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled); +void GB_load_cheats(GB_gameboy_t *gb, const char *path); +int GB_save_cheats(GB_gameboy_t *gb, const char *path); + +#ifdef GB_INTERNAL +#ifdef GB_DISABLE_CHEATS +#define GB_apply_cheat(...) +#else +void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); +#endif +#endif + +typedef struct { + size_t size; + GB_cheat_t *cheats[]; +} GB_cheat_hash_t; + +struct GB_cheat_s { + uint16_t address; + uint16_t bank; + uint8_t value; + uint8_t old_value; + bool use_old_value; + bool enabled; + char description[128]; +}; + +#endif diff --git a/waterbox/bsnescore/bsnes/gb/Core/debugger.c b/waterbox/bsnescore/bsnes/gb/Core/debugger.c new file mode 100644 index 0000000000..002d455454 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/debugger.c @@ -0,0 +1,2668 @@ +#include +#include +#include +#include "gb.h" + +typedef struct { + bool has_bank; + uint16_t bank:9; + uint16_t value; +} value_t; + +typedef struct { + enum { + LVALUE_MEMORY, + LVALUE_MEMORY16, + LVALUE_REG16, + LVALUE_REG_H, + LVALUE_REG_L, + } kind; + union { + uint16_t *register_address; + value_t memory_address; + }; +} lvalue_t; + +#define VALUE_16(x) ((value_t){false, 0, (x)}) + +struct GB_breakpoint_s { + union { + struct { + uint16_t addr; + uint16_t bank; /* -1 = any bank*/ + }; + uint32_t key; /* For sorting and comparing */ + }; + char *condition; + bool is_jump_to; +}; + +#define BP_KEY(x) (((struct GB_breakpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) + +#define GB_WATCHPOINT_R (1) +#define GB_WATCHPOINT_W (2) + +struct GB_watchpoint_s { + union { + struct { + uint16_t addr; + uint16_t bank; /* -1 = any bank*/ + }; + uint32_t key; /* For sorting and comparing */ + }; + char *condition; + uint8_t flags; +}; + +#define WP_KEY(x) (((struct GB_watchpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) + +static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x4000) { + return gb->mbc_rom0_bank; + } + + if (addr < 0x8000) { + return gb->mbc_rom_bank; + } + + if (addr < 0xD000) { + return 0; + } + + if (addr < 0xE000) { + return gb->cgb_ram_bank; + } + + return 0; +} + +typedef struct { + uint16_t rom0_bank; + uint16_t rom_bank; + uint8_t mbc_ram_bank; + bool mbc_ram_enable; + uint8_t ram_bank; + uint8_t vram_bank; +} banking_state_t; + +static inline void save_banking_state(GB_gameboy_t *gb, banking_state_t *state) +{ + state->rom0_bank = gb->mbc_rom0_bank; + state->rom_bank = gb->mbc_rom_bank; + state->mbc_ram_bank = gb->mbc_ram_bank; + state->mbc_ram_enable = gb->mbc_ram_enable; + state->ram_bank = gb->cgb_ram_bank; + state->vram_bank = gb->cgb_vram_bank; +} + +static inline void restore_banking_state(GB_gameboy_t *gb, banking_state_t *state) +{ + + gb->mbc_rom0_bank = state->rom0_bank; + gb->mbc_rom_bank = state->rom_bank; + gb->mbc_ram_bank = state->mbc_ram_bank; + gb->mbc_ram_enable = state->mbc_ram_enable; + gb->cgb_ram_bank = state->ram_bank; + gb->cgb_vram_bank = state->vram_bank; +} + +static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank) +{ + gb->mbc_rom0_bank = bank; + gb->mbc_rom_bank = bank; + gb->mbc_ram_bank = bank; + gb->mbc_ram_enable = true; + if (GB_is_cgb(gb)) { + gb->cgb_ram_bank = bank & 7; + gb->cgb_vram_bank = bank & 1; + if (gb->cgb_ram_bank == 0) { + gb->cgb_ram_bank = 1; + } + } +} + +static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name) +{ + static __thread char output[256]; + const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value); + + if (symbol && (value - symbol->addr > 0x1000 || symbol->addr == 0) ) { + symbol = NULL; + } + + /* Avoid overflow */ + if (symbol && strlen(symbol->name) >= 240) { + symbol = NULL; + } + + if (!symbol) { + sprintf(output, "$%04x", value); + } + + else if (symbol->addr == value) { + if (prefer_name) { + sprintf(output, "%s ($%04x)", symbol->name, value); + } + else { + sprintf(output, "$%04x (%s)", value, symbol->name); + } + } + + else { + if (prefer_name) { + sprintf(output, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); + } + else { + sprintf(output, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); + } + } + return output; +} + +static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name) +{ + if (!value.has_bank) return value_to_string(gb, value.value, prefer_name); + + static __thread char output[256]; + const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value); + + if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) { + symbol = NULL; + } + + /* Avoid overflow */ + if (symbol && strlen(symbol->name) >= 240) { + symbol = NULL; + } + + if (!symbol) { + sprintf(output, "$%02x:$%04x", value.bank, value.value); + } + + else if (symbol->addr == value.value) { + if (prefer_name) { + sprintf(output, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); + } + else { + sprintf(output, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); + } + } + + else { + if (prefer_name) { + sprintf(output, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); + } + else { + sprintf(output, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); + } + } + return output; +} + +static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) +{ + /* Not used until we add support for operators like += */ + switch (lvalue.kind) { + case LVALUE_MEMORY: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + restore_banking_state(gb, &state); + return r; + } + return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + + case LVALUE_MEMORY16: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) | + (GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100)); + restore_banking_state(gb, &state); + return r; + } + return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) | + (GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100)); + + case LVALUE_REG16: + return VALUE_16(*lvalue.register_address); + + case LVALUE_REG_L: + return VALUE_16(*lvalue.register_address & 0x00FF); + + case LVALUE_REG_H: + return VALUE_16(*lvalue.register_address >> 8); + } + + return VALUE_16(0); +} + +static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) +{ + switch (lvalue.kind) { + case LVALUE_MEMORY: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + GB_write_memory(gb, lvalue.memory_address.value, value); + restore_banking_state(gb, &state); + return; + } + GB_write_memory(gb, lvalue.memory_address.value, value); + return; + + case LVALUE_MEMORY16: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + GB_write_memory(gb, lvalue.memory_address.value, value); + GB_write_memory(gb, lvalue.memory_address.value + 1, value >> 8); + restore_banking_state(gb, &state); + return; + } + GB_write_memory(gb, lvalue.memory_address.value, value); + GB_write_memory(gb, lvalue.memory_address.value + 1, value >> 8); + return; + + case LVALUE_REG16: + *lvalue.register_address = value; + return; + + case LVALUE_REG_L: + *lvalue.register_address &= 0xFF00; + *lvalue.register_address |= value & 0xFF; + return; + + case LVALUE_REG_H: + *lvalue.register_address &= 0x00FF; + *lvalue.register_address |= value << 8; + return; + } +} + +/* 16 bit value 16 bit value = 16 bit value + 25 bit address 16 bit value = 25 bit address + 16 bit value 25 bit address = 25 bit address + 25 bit address 25 bit address = 16 bit value (since adding pointers, for examples, makes no sense) + + Boolean operators always return a 16-bit value + */ +#define FIX_BANK(x) ((value_t){a.has_bank ^ b.has_bank, a.has_bank? a.bank : b.bank, (x)}) + +static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);} +static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);} +static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);} +static value_t _div(value_t a, value_t b) +{ + if (b.value == 0) { + return FIX_BANK(0); + } + return FIX_BANK(a.value / b.value); +}; +static value_t mod(value_t a, value_t b) +{ + if (b.value == 0) { + return FIX_BANK(0); + } + return FIX_BANK(a.value % b.value); +}; +static value_t and(value_t a, value_t b) {return FIX_BANK(a.value & b.value);} +static value_t or(value_t a, value_t b) {return FIX_BANK(a.value | b.value);} +static value_t xor(value_t a, value_t b) {return FIX_BANK(a.value ^ b.value);} +static value_t shleft(value_t a, value_t b) {return FIX_BANK(a.value << b.value);} +static value_t shright(value_t a, value_t b) {return FIX_BANK(a.value >> b.value);} +static value_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b) +{ + write_lvalue(gb, a, b); + return read_lvalue(gb, a); +} + +static value_t bool_and(value_t a, value_t b) {return VALUE_16(a.value && b.value);} +static value_t bool_or(value_t a, value_t b) {return VALUE_16(a.value || b.value);} +static value_t equals(value_t a, value_t b) {return VALUE_16(a.value == b.value);} +static value_t different(value_t a, value_t b) {return VALUE_16(a.value != b.value);} +static value_t lower(value_t a, value_t b) {return VALUE_16(a.value < b.value);} +static value_t greater(value_t a, value_t b) {return VALUE_16(a.value > b.value);} +static value_t lower_equals(value_t a, value_t b) {return VALUE_16(a.value <= b.value);} +static value_t greater_equals(value_t a, value_t b) {return VALUE_16(a.value >= b.value);} +static value_t bank(value_t a, value_t b) {return (value_t) {true, a.value, b.value};} + + +static struct { + const char *string; + int8_t priority; + value_t (*operator)(value_t, value_t); + value_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t); +} operators[] = +{ + // Yes. This is not C-like. But it makes much more sense. + // Deal with it. + {"+", 0, add}, + {"-", 0, sub}, + {"||", 0, bool_or}, + {"|", 0, or}, + {"*", 1, mul}, + {"/", 1, _div}, + {"%", 1, mod}, + {"&&", 1, bool_and}, + {"&", 1, and}, + {"^", 1, xor}, + {"<<", 2, shleft}, + {"<=", 3, lower_equals}, + {"<", 3, lower}, + {">>", 2, shright}, + {">=", 3, greater_equals}, + {">", 3, greater}, + {"==", 3, equals}, + {"=", -1, NULL, assign}, + {"!=", 3, different}, + {":", 4, bank}, +}; + +value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, + size_t length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value); + +static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, + size_t length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) +{ + *error = false; + // Strip whitespace + while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { + string++; + length--; + } + while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { + length--; + } + if (length == 0) { + GB_log(gb, "Expected expression.\n"); + *error = true; + return (lvalue_t){0,}; + } + if (string[0] == '(' && string[length - 1] == ')') { + // Attempt to strip parentheses + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '(') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ')') depth--; + } + if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + } + else if (string[0] == '[' && string[length - 1] == ']') { + // Attempt to strip square parentheses (memory dereference) + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '[') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ']') depth--; + } + if (depth == 0) { + return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; + } + } + else if (string[0] == '{' && string[length - 1] == '}') { + // Attempt to strip curly parentheses (memory dereference) + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '{') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == '}') depth--; + } + if (depth == 0) { + return (lvalue_t){LVALUE_MEMORY16, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; + } + } + + // Registers + if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { + if (length == 1) { + switch (string[0]) { + case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]}; + case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]}; + } + } + else if (length == 2) { + switch (string[0]) { + case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]}; + case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]}; + case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; + } + } + GB_log(gb, "Unknown register: %.*s\n", (unsigned) length, string); + *error = true; + return (lvalue_t){0,}; + } + + GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned) length, string); + *error = true; + return (lvalue_t){0,}; +} + +#define ERROR ((value_t){0,}) +value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, + size_t length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) +{ + /* Disable watchpoints while evaulating expressions */ + uint16_t n_watchpoints = gb->n_watchpoints; + gb->n_watchpoints = 0; + + value_t ret = ERROR; + + *error = false; + // Strip whitespace + while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { + string++; + length--; + } + while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { + length--; + } + if (length == 0) { + GB_log(gb, "Expected expression.\n"); + *error = true; + goto exit; + } + if (string[0] == '(' && string[length - 1] == ')') { + // Attempt to strip parentheses + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '(') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ')') depth--; + } + if (depth == 0) { + ret = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + goto exit; + } + } + else if (string[0] == '[' && string[length - 1] == ']') { + // Attempt to strip square parentheses (memory dereference) + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '[') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ']') depth--; + } + + if (depth == 0) { + value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + banking_state_t state; + if (addr.bank) { + save_banking_state(gb, &state); + switch_banking_state(gb, addr.bank); + } + ret = VALUE_16(GB_read_memory(gb, addr.value)); + if (addr.bank) { + restore_banking_state(gb, &state); + } + goto exit; + } + } + else if (string[0] == '{' && string[length - 1] == '}') { + // Attempt to strip curly parentheses (memory dereference) + signed depth = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '{') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == '}') depth--; + } + + if (depth == 0) { + value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + banking_state_t state; + if (addr.bank) { + save_banking_state(gb, &state); + switch_banking_state(gb, addr.bank); + } + ret = VALUE_16(GB_read_memory(gb, addr.value) | (GB_read_memory(gb, addr.value + 1) * 0x100)); + if (addr.bank) { + restore_banking_state(gb, &state); + } + goto exit; + } + } + // Search for lowest priority operator + signed depth = 0; + unsigned operator_index = -1; + unsigned operator_pos = 0; + for (unsigned i = 0; i < length; i++) { + if (string[i] == '(') depth++; + else if (string[i] == ')') depth--; + else if (string[i] == '[') depth++; + else if (string[i] == ']') depth--; + else if (depth == 0) { + for (unsigned j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) { + unsigned operator_length = strlen(operators[j].string); + if (operator_length > length - i) continue; // Operator too long + + if (memcmp(string + i, operators[j].string, operator_length) == 0) { + if (operator_index != -1 && operators[operator_index].priority < operators[j].priority) { + /* for supporting = vs ==, etc*/ + i += operator_length - 1; + break; + } + // Found an operator! + operator_pos = i; + operator_index = j; + /* for supporting = vs ==, etc*/ + i += operator_length - 1; + break; + } + } + } + } + if (operator_index != -1) { + unsigned right_start = (unsigned)(operator_pos + strlen(operators[operator_index].string)); + value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); + if (*error) goto exit; + if (operators[operator_index].lvalue_operator) { + lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); + if (*error) goto exit; + ret = operators[operator_index].lvalue_operator(gb, left, right.value); + goto exit; + } + value_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); + if (*error) goto exit; + ret = operators[operator_index].operator(left, right); + goto exit; + } + + // Not an expression - must be a register or a literal + + // Registers + if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { + if (length == 1) { + switch (string[0]) { + case 'a': ret = VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); goto exit; + case 'f': ret = VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); goto exit; + case 'b': ret = VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); goto exit; + case 'c': ret = VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); goto exit; + case 'd': ret = VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); goto exit; + case 'e': ret = VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); goto exit; + case 'h': ret = VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); goto exit; + case 'l': ret = VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); goto exit; + } + } + else if (length == 2) { + switch (string[0]) { + case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->registers[GB_REGISTER_AF]); goto exit;} + case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->registers[GB_REGISTER_BC]); goto exit;} + case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->registers[GB_REGISTER_DE]); goto exit;} + case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->registers[GB_REGISTER_HL]); goto exit;} + case 's': if (string[1] == 'p') {ret = VALUE_16(gb->registers[GB_REGISTER_SP]); goto exit;} + case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;} + } + } + else if (length == 3) { + if (watchpoint_address && memcmp(string, "old", 3) == 0) { + ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); + goto exit; + } + + if (watchpoint_new_value && memcmp(string, "new", 3) == 0) { + ret = VALUE_16(*watchpoint_new_value); + goto exit; + } + + /* $new is identical to $old in read conditions */ + if (watchpoint_address && memcmp(string, "new", 3) == 0) { + ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); + goto exit; + } + } + + char symbol_name[length + 1]; + memcpy(symbol_name, string, length); + symbol_name[length] = 0; + const GB_symbol_t *symbol = GB_reversed_map_find_symbol(&gb->reversed_symbol_map, symbol_name); + if (symbol) { + ret = (value_t){true, symbol->bank, symbol->addr}; + goto exit; + } + + GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned) length, string); + *error = true; + goto exit; + } + + char *end; + unsigned base = 10; + if (string[0] == '$') { + string++; + base = 16; + length--; + } + uint16_t literal = (uint16_t) (strtol(string, &end, base)); + if (end != string + length) { + GB_log(gb, "Failed to parse: %.*s\n", (unsigned) length, string); + *error = true; + goto exit; + } + ret = VALUE_16(literal); +exit: + gb->n_watchpoints = n_watchpoints; + return ret; +} + +struct debugger_command_s; +typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command); +typedef char *debugger_completer_imp_t(GB_gameboy_t *gb, const char *string, uintptr_t *context); + +typedef struct debugger_command_s { + const char *command; + uint8_t min_length; + debugger_command_imp_t *implementation; + const char *help_string; // Null if should not appear in help + const char *arguments_format; // For usage message + const char *modifiers_format; // For usage message + debugger_completer_imp_t *argument_completer; + debugger_completer_imp_t *modifiers_completer; +} debugger_command_t; + +static const char *lstrip(const char *str) +{ + while (*str == ' ' || *str == '\t') { + str++; + } + return str; +} + +#define STOPPED_ONLY \ +if (!gb->debug_stopped) { \ +GB_log(gb, "Program is running. \n"); \ +return false; \ +} + +#define NO_MODIFIERS \ +if (modifiers) { \ +print_usage(gb, command); \ +return true; \ +} + +static void print_usage(GB_gameboy_t *gb, const debugger_command_t *command) +{ + GB_log(gb, "Usage: %s", command->command); + + if (command->modifiers_format) { + GB_log(gb, "[/%s]", command->modifiers_format); + } + + if (command->arguments_format) { + GB_log(gb, " %s", command->arguments_format); + } + + GB_log(gb, "\n"); +} + +static bool cont(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + return false; +} + +static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + gb->debug_next_command = true; + gb->debug_call_depth = 0; + return false; +} + +static bool step(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + return false; +} + +static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + gb->debug_fin_command = true; + gb->debug_call_depth = 0; + return false; +} + +static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + gb->stack_leak_detection = true; + gb->debug_call_depth = 0; + return false; +} + +static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + + GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ + (gb->f & GB_CARRY_FLAG)? 'C' : '-', + (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', + (gb->f & GB_SUBTRACT_FLAG)? 'N' : '-', + (gb->f & GB_ZERO_FLAG)? 'Z' : '-'); + GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); + GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); + GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); + GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); + GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); + GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled"); + return true; +} + +static char *on_off_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"on", "off"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +/* Enable or disable software breakpoints */ +static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strcmp(lstrip(arguments), "on") == 0 || !strlen(lstrip(arguments))) { + gb->has_software_breakpoints = true; + } + else if (strcmp(lstrip(arguments), "off") == 0) { + gb->has_software_breakpoints = false; + } + else { + print_usage(gb, command); + } + + return true; +} + +/* Find the index of the closest breakpoint equal or greater to addr */ +static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) +{ + if (!gb->breakpoints) { + return 0; + } + + uint32_t key = BP_KEY(addr); + + unsigned min = 0; + unsigned max = gb->n_breakpoints; + while (min < max) { + uint16_t pivot = (min + max) / 2; + if (gb->breakpoints[pivot].key == key) return pivot; + if (gb->breakpoints[pivot].key > key) { + max = pivot; + } + else { + min = pivot + 1; + } + } + return (uint16_t) min; +} + +static inline bool is_legal_symbol_char(char c) +{ + if (c >= '0' && c <= '9') return true; + if (c >= 'A' && c <= 'Z') return true; + if (c >= 'a' && c <= 'z') return true; + if (c == '_') return true; + if (c == '.') return true; + return false; +} + +static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_context) +{ + const char *symbol_prefix = string; + while (*string) { + if (!is_legal_symbol_char(*string)) { + symbol_prefix = string + 1; + } + string++; + } + + if (*symbol_prefix == '$') { + return NULL; + } + + struct { + uint16_t bank; + uint32_t symbol; + } *context = (void *)_context; + + + size_t length = strlen(symbol_prefix); + while (context->bank < 0x200) { + if (gb->bank_symbols[context->bank] == NULL || + context->symbol >= gb->bank_symbols[context->bank]->n_symbols) { + context->bank++; + context->symbol = 0; + continue; + } + const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name; + if (memcmp(symbol_prefix, candidate, length) == 0) { + return strdup(candidate + length); + } + } + return NULL; +} + +static char *j_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"j"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + bool is_jump_to = true; + if (!modifiers) { + is_jump_to = false; + } + else if (strcmp(modifiers, "j") != 0) { + print_usage(gb, command); + return true; + } + + if (strlen(lstrip(arguments)) == 0) { + print_usage(gb, command); + return true; + } + + if (gb->n_breakpoints == (typeof(gb->n_breakpoints)) -1) { + GB_log(gb, "Too many breakpoints set\n"); + return true; + } + + char *condition = NULL; + if ((condition = strstr(arguments, " if "))) { + *condition = 0; + condition += strlen(" if "); + /* Verify condition is sane (Todo: This might have side effects!) */ + bool error; + debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, NULL, NULL); + if (error) return true; + + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint32_t key = BP_KEY(result); + + if (error) return true; + + uint16_t index = find_breakpoint(gb, result); + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { + GB_log(gb, "Breakpoint already set at %s\n", debugger_value_to_string(gb, result, true)); + if (!gb->breakpoints[index].condition && condition) { + GB_log(gb, "Added condition to breakpoint\n"); + gb->breakpoints[index].condition = strdup(condition); + } + else if (gb->breakpoints[index].condition && condition) { + GB_log(gb, "Replaced breakpoint condition\n"); + free(gb->breakpoints[index].condition); + gb->breakpoints[index].condition = strdup(condition); + } + else if (gb->breakpoints[index].condition && !condition) { + GB_log(gb, "Removed breakpoint condition\n"); + free(gb->breakpoints[index].condition); + gb->breakpoints[index].condition = NULL; + } + return true; + } + + gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0])); + memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); + gb->breakpoints[index].key = key; + + if (condition) { + gb->breakpoints[index].condition = strdup(condition); + } + else { + gb->breakpoints[index].condition = NULL; + } + gb->n_breakpoints++; + + gb->breakpoints[index].is_jump_to = is_jump_to; + + if (is_jump_to) { + gb->has_jump_to_breakpoints = true; + } + + GB_log(gb, "Breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments)) == 0) { + for (unsigned i = gb->n_breakpoints; i--;) { + if (gb->breakpoints[i].condition) { + free(gb->breakpoints[i].condition); + } + } + free(gb->breakpoints); + gb->breakpoints = NULL; + gb->n_breakpoints = 0; + return true; + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint32_t key = BP_KEY(result); + + if (error) return true; + + uint16_t index = 0; + for (unsigned i = 0; i < gb->n_breakpoints; i++) { + if (gb->breakpoints[i].key == key) { + /* Full match */ + index = i; + break; + } + if (gb->breakpoints[i].addr == result.value && result.has_bank != (gb->breakpoints[i].bank != (uint16_t) -1)) { + /* Partial match */ + index = i; + } + } + + if (index >= gb->n_breakpoints) { + GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; + } + + result.bank = gb->breakpoints[index].bank; + result.has_bank = gb->breakpoints[index].bank != (uint16_t) -1; + + if (gb->breakpoints[index].condition) { + free(gb->breakpoints[index].condition); + } + + if (gb->breakpoints[index].is_jump_to) { + gb->has_jump_to_breakpoints = false; + for (unsigned i = 0; i < gb->n_breakpoints; i++) { + if (i == index) continue; + if (gb->breakpoints[i].is_jump_to) { + gb->has_jump_to_breakpoints = true; + break; + } + } + } + + memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); + gb->n_breakpoints--; + gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); + + GB_log(gb, "Breakpoint removed from %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +/* Find the index of the closest watchpoint equal or greater to addr */ +static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) +{ + if (!gb->watchpoints) { + return 0; + } + uint32_t key = WP_KEY(addr); + unsigned min = 0; + unsigned max = gb->n_watchpoints; + while (min < max) { + uint16_t pivot = (min + max) / 2; + if (gb->watchpoints[pivot].key == key) return pivot; + if (gb->watchpoints[pivot].key > key) { + max = pivot; + } + else { + min = pivot + 1; + } + } + return (uint16_t) min; +} + +static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"r", "rw", "w"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { +print_usage: + print_usage(gb, command); + return true; + } + + if (gb->n_watchpoints == (typeof(gb->n_watchpoints)) -1) { + GB_log(gb, "Too many watchpoints set\n"); + return true; + } + + if (!modifiers) { + modifiers = "w"; + } + + uint8_t flags = 0; + while (*modifiers) { + switch (*modifiers) { + case 'r': + flags |= GB_WATCHPOINT_R; + break; + case 'w': + flags |= GB_WATCHPOINT_W; + break; + default: + goto print_usage; + } + modifiers++; + } + + if (!flags) { + goto print_usage; + } + + char *condition = NULL; + if ((condition = strstr(arguments, " if "))) { + *condition = 0; + condition += strlen(" if "); + /* Verify condition is sane (Todo: This might have side effects!) */ + bool error; + /* To make $new and $old legal */ + uint16_t dummy = 0; + uint8_t dummy2 = 0; + debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, &dummy, &dummy2); + if (error) return true; + + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint32_t key = WP_KEY(result); + + if (error) return true; + + uint16_t index = find_watchpoint(gb, result); + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + GB_log(gb, "Watchpoint already set at %s\n", debugger_value_to_string(gb, result, true)); + if (gb->watchpoints[index].flags != flags) { + GB_log(gb, "Modified watchpoint type\n"); + gb->watchpoints[index].flags = flags; + } + if (!gb->watchpoints[index].condition && condition) { + GB_log(gb, "Added condition to watchpoint\n"); + gb->watchpoints[index].condition = strdup(condition); + } + else if (gb->watchpoints[index].condition && condition) { + GB_log(gb, "Replaced watchpoint condition\n"); + free(gb->watchpoints[index].condition); + gb->watchpoints[index].condition = strdup(condition); + } + else if (gb->watchpoints[index].condition && !condition) { + GB_log(gb, "Removed watchpoint condition\n"); + free(gb->watchpoints[index].condition); + gb->watchpoints[index].condition = NULL; + } + return true; + } + + gb->watchpoints = realloc(gb->watchpoints, (gb->n_watchpoints + 1) * sizeof(gb->watchpoints[0])); + memmove(&gb->watchpoints[index + 1], &gb->watchpoints[index], (gb->n_watchpoints - index) * sizeof(gb->watchpoints[0])); + gb->watchpoints[index].key = key; + gb->watchpoints[index].flags = flags; + if (condition) { + gb->watchpoints[index].condition = strdup(condition); + } + else { + gb->watchpoints[index].condition = NULL; + } + gb->n_watchpoints++; + + GB_log(gb, "Watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments)) == 0) { + for (unsigned i = gb->n_watchpoints; i--;) { + if (gb->watchpoints[i].condition) { + free(gb->watchpoints[i].condition); + } + } + free(gb->watchpoints); + gb->watchpoints = NULL; + gb->n_watchpoints = 0; + return true; + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint32_t key = WP_KEY(result); + + if (error) return true; + + uint16_t index = 0; + for (unsigned i = 0; i < gb->n_watchpoints; i++) { + if (gb->watchpoints[i].key == key) { + /* Full match */ + index = i; + break; + } + if (gb->watchpoints[i].addr == result.value && result.has_bank != (gb->watchpoints[i].bank != (uint16_t) -1)) { + /* Partial match */ + index = i; + } + } + + if (index >= gb->n_watchpoints) { + GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; + } + + result.bank = gb->watchpoints[index].bank; + result.has_bank = gb->watchpoints[index].bank != (uint16_t) -1; + + if (gb->watchpoints[index].condition) { + free(gb->watchpoints[index].condition); + } + + memmove(&gb->watchpoints[index], &gb->watchpoints[index + 1], (gb->n_watchpoints - index - 1) * sizeof(gb->watchpoints[0])); + gb->n_watchpoints--; + gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints *sizeof(gb->watchpoints[0])); + + GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (gb->n_breakpoints == 0) { + GB_log(gb, "No breakpoints set.\n"); + } + else { + GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); + for (uint16_t i = 0; i < gb->n_breakpoints; i++) { + value_t addr = (value_t){gb->breakpoints[i].bank != (uint16_t)-1, gb->breakpoints[i].bank, gb->breakpoints[i].addr}; + if (gb->breakpoints[i].condition) { + GB_log(gb, " %d. %s (%sCondition: %s)\n", i + 1, + debugger_value_to_string(gb, addr, addr.has_bank), + gb->breakpoints[i].is_jump_to? "Jump to, ": "", + gb->breakpoints[i].condition); + } + else { + GB_log(gb, " %d. %s%s\n", i + 1, + debugger_value_to_string(gb, addr, addr.has_bank), + gb->breakpoints[i].is_jump_to? " (Jump to)" : ""); + } + } + } + + if (gb->n_watchpoints == 0) { + GB_log(gb, "No watchpoints set.\n"); + } + else { + GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints); + for (uint16_t i = 0; i < gb->n_watchpoints; i++) { + value_t addr = (value_t){gb->watchpoints[i].bank != (uint16_t)-1, gb->watchpoints[i].bank, gb->watchpoints[i].addr}; + if (gb->watchpoints[i].condition) { + GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-', + gb->watchpoints[i].condition); + } + else { + GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); + } + } + } + + return true; +} + +static bool _should_break(GB_gameboy_t *gb, value_t addr, bool jump_to) +{ + uint16_t index = find_breakpoint(gb, addr); + uint32_t key = BP_KEY(addr); + + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key && gb->breakpoints[index].is_jump_to == jump_to) { + if (!gb->breakpoints[index].condition) { + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition, + (unsigned)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return true; + } + return condition; + } + return false; +} + +static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to) +{ + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_should_break(gb, full_addr, jump_to)) return true; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + return _should_break(gb, full_addr, jump_to); +} + +static char *format_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"a", "b", "d", "o", "x"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + print_usage(gb, command); + return true; + } + + if (!modifiers || !modifiers[0]) { + modifiers = "a"; + } + else if (modifiers[1]) { + print_usage(gb, command); + return true; + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + if (!error) { + switch (modifiers[0]) { + case 'a': + GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false)); + break; + case 'd': + GB_log(gb, "=%d\n", result.value); + break; + case 'x': + GB_log(gb, "=$%x\n", result.value); + break; + case 'o': + GB_log(gb, "=0%o\n", result.value); + break; + case 'b': + { + if (!result.value) { + GB_log(gb, "=%%0\n"); + break; + } + char binary[17]; + binary[16] = 0; + char *ptr = &binary[16]; + while (result.value) { + *(--ptr) = (result.value & 1)? '1' : '0'; + result.value >>= 1; + } + GB_log(gb, "=%%%s\n", ptr); + break; + } + default: + break; + } + } + return true; +} + +static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + print_usage(gb, command); + return true; + } + + bool error; + value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint16_t count = 32; + + if (modifiers) { + char *end; + count = (uint16_t) (strtol(modifiers, &end, 10)); + if (*end) { + print_usage(gb, command); + return true; + } + } + + if (!error) { + if (addr.has_bank) { + banking_state_t old_state; + save_banking_state(gb, &old_state); + switch_banking_state(gb, addr.bank); + + while (count) { + GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); + for (unsigned i = 0; i < 16 && count; i++) { + GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + count--; + } + addr.value += 16; + GB_log(gb, "\n"); + } + + restore_banking_state(gb, &old_state); + } + else { + while (count) { + GB_log(gb, "%04x: ", addr.value); + for (unsigned i = 0; i < 16 && count; i++) { + GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + count--; + } + addr.value += 16; + GB_log(gb, "\n"); + } + } + } + return true; +} + +static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + arguments = "pc"; + } + + bool error; + value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + uint16_t count = 5; + + if (modifiers) { + char *end; + count = (uint16_t) (strtol(modifiers, &end, 10)); + if (*end) { + print_usage(gb, command); + return true; + } + } + + if (!error) { + if (addr.has_bank) { + banking_state_t old_state; + save_banking_state(gb, &old_state); + switch_banking_state(gb, addr.bank); + + GB_cpu_disassemble(gb, addr.value, count); + + restore_banking_state(gb, &old_state); + } + else { + GB_cpu_disassemble(gb, addr.value, count); + } + } + return true; +} + +static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + const GB_cartridge_t *cartridge = gb->cartridge_type; + + if (cartridge->has_ram) { + GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); + } + else { + GB_log(gb, "No cartridge RAM\n"); + } + + if (cartridge->mbc_type) { + if (gb->is_mbc30) { + GB_log(gb, "MBC30\n"); + } + else { + static const char *const mapper_names[] = { + [GB_MBC1] = "MBC1", + [GB_MBC2] = "MBC2", + [GB_MBC3] = "MBC3", + [GB_MBC5] = "MBC5", + [GB_HUC1] = "HUC-1", + [GB_HUC3] = "HUC-3", + }; + GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); + } + GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); + if (cartridge->has_ram) { + GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); + if (gb->cartridge_type->mbc_type != GB_HUC1) { + GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); + } + } + if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) { + GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM"); + } + if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_MBC1M_WIRING) { + GB_log(gb, "MBC1 uses MBC1M wiring. \n"); + GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank); + GB_log(gb, "MBC1 multicart banking mode is %s\n", gb->mbc1.mode == 1 ? "enabled" : "disabled"); + } + + } + else { + GB_log(gb, "No MBC\n"); + } + + if (cartridge->has_rumble) { + GB_log(gb, "Cart contains a Rumble Pak\n"); + } + + if (cartridge->has_rtc) { + GB_log(gb, "Cart contains a real time clock\n"); + } + + return true; +} + +static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true)); + for (unsigned i = gb->backtrace_size; i--;) { + GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true)); + } + + return true; +} + +static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + GB_log(gb, "Ticks: %llu. (Resetting)\n", (unsigned long long)gb->debugger_ticks); + gb->debugger_ticks = 0; + + return true; +} + + +static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (!GB_is_cgb(gb)) { + GB_log(gb, "Not available on a DMG.\n"); + return true; + } + + GB_log(gb, "Background palettes: \n"); + for (unsigned i = 0; i < 32; i++) { + GB_log(gb, "%04x ", ((uint16_t *)&gb->background_palettes_data)[i]); + if (i % 4 == 3) { + GB_log(gb, "\n"); + } + } + + GB_log(gb, "Sprites palettes: \n"); + for (unsigned i = 0; i < 32; i++) { + GB_log(gb, "%04x ", ((uint16_t *)&gb->sprite_palettes_data)[i]); + if (i % 4 == 3) { + GB_log(gb, "\n"); + } + } + + return true; +} + +static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + GB_log(gb, "LCDC:\n"); + GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled"); + GB_log(gb, " %s: %s\n", (gb->cgb_mode? "Sprite priority flags" : "Background and Window"), + (gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled"); + GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled"); + GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8"); + GB_log(gb, " Background tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 8)? "$9C00" : "$9800"); + GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & 16)? "$8000" : "$8800"); + GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & 32)? "Enabled" : "Disabled"); + GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 64)? "$9C00" : "$9800"); + + GB_log(gb, "\nSTAT:\n"); + static const char *modes[] = {"Mode 0, H-Blank", "Mode 1, V-Blank", "Mode 2, OAM", "Mode 3, Rendering"}; + GB_log(gb, " Current mode: %s\n", modes[gb->io_registers[GB_IO_STAT] & 3]); + GB_log(gb, " LYC flag: %s\n", (gb->io_registers[GB_IO_STAT] & 4)? "On" : "Off"); + GB_log(gb, " H-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 8)? "Enabled" : "Disabled"); + GB_log(gb, " V-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 16)? "Enabled" : "Disabled"); + GB_log(gb, " OAM interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 32)? "Enabled" : "Disabled"); + GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled"); + + + + GB_log(gb, "\nCurrent line: %d\n", gb->current_line); + GB_log(gb, "Current state: "); + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + GB_log(gb, "Off\n"); + } + else if (gb->display_state == 7 || gb->display_state == 8) { + GB_log(gb, "Reading OAM data (%d/40)\n", gb->display_state == 8? gb->oam_search_index : 0); + } + else if (gb->display_state <= 3 || gb->display_state == 24 || gb->display_state == 31) { + GB_log(gb, "Glitched line 0 OAM mode (%d cycles to next event)\n", -gb->display_cycles / 2); + } + else if (gb->mode_for_interrupt == 3) { + signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line; + GB_log(gb, "Rendering pixel (%d/160)\n", pixel); + } + else { + GB_log(gb, "Sleeping (%d cycles to next event)\n", -gb->display_cycles / 2); + } + GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]); + GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); + GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7, gb->io_registers[GB_IO_WY]); + GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off"); + + return true; +} + +static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + + GB_log(gb, "Current state: "); + if (!gb->apu.global_enable) { + GB_log(gb, "Disabled\n"); + } + else { + GB_log(gb, "Enabled\n"); + for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) { + GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1, + gb->apu.is_active[channel] ? "active " : "inactive", + GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive", + gb->apu.samples[channel]); + } + } + + GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07); + if (gb->io_registers[GB_IO_NR51] & 0x0f) { + for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if (gb->io_registers[GB_IO_NR51] & mask) { + GB_log(gb, " CH%u", channel + 1); + } + } + } + else { + GB_log(gb, " no channels"); + } + GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); + + GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4); + if (gb->io_registers[GB_IO_NR51] & 0xf0) { + for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) { + if (gb->io_registers[GB_IO_NR51] & mask) { + GB_log(gb, " CH%u", channel + 1); + } + } + } + else { + GB_log(gb, " no channels"); + } + GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": ""); + + + for (uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) { + GB_log(gb, "\nCH%u:\n", channel + 1); + GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", + gb->apu.square_channels[channel].current_volume, + (gb->apu.square_channels[channel].sample_length ^ 0x7FF) * 2 + 1, + gb->apu.square_channels[channel].sample_countdown); + + uint8_t nrx2 = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; + GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", + gb->apu.square_channels[channel].volume_countdown, + nrx2 & 8 ? "in" : "de", + nrx2 & 7); + + uint8_t duty = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; + GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n", + duty > 3? "" : (const char *[]){"12.5", " 25", " 50", " 75"}[duty], + duty > 3? "" : (const char *[]){"_______-", "-______-", "-____---", "_------_"}[duty], + gb->apu.square_channels[channel].current_sample_index & 0x7f, + gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : ""); + + if (channel == GB_SQUARE_1) { + GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n", + gb->apu.sweep_enabled? "active" : "inactive", + gb->apu.sweep_decreasing? "decreasing" : "increasing", + gb->apu.square_sweep_calculate_countdown); + } + + if (gb->apu.square_channels[channel].length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.square_channels[channel].pulse_length); + } + } + + + GB_log(gb, "\nCH3:\n"); + GB_log(gb, " Wave:"); + for (uint8_t i = 0; i < 32; i++) { + GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]); + } + GB_log(gb, "\n"); + GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index); + + GB_log(gb, " Volume %s (right-shifted %u times)\n", + gb->apu.wave_channel.shift > 4? "" : (const char *[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift], + gb->apu.wave_channel.shift); + + GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n", + gb->apu.wave_channel.sample_length ^ 0x7ff, + gb->apu.wave_channel.sample_countdown); + + if (gb->apu.wave_channel.length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.wave_channel.pulse_length); + } + + + GB_log(gb, "\nCH4:\n"); + GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", + gb->apu.noise_channel.current_volume, + gb->apu.noise_channel.sample_length * 4 + 3, + gb->apu.noise_channel.sample_countdown); + + GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", + gb->apu.noise_channel.volume_countdown, + gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de", + gb->io_registers[GB_IO_NR42] & 7); + + GB_log(gb, " LFSR in %u-step mode, current value ", + gb->apu.noise_channel.narrow? 7 : 15); + for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) { + GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " "); + } + + if (gb->apu.noise_channel.length_enabled) { + GB_log(gb, " Channel will end in %u 256 Hz ticks\n", + gb->apu.noise_channel.pulse_length); + } + + + GB_log(gb, "\n\nReminder: APU ticks are @ 2 MiHz\n"); + + return true; +} + +static char *wave_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"c", "f", "l"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) { + print_usage(gb, command); + return true; + } + + uint8_t shift_amount = 1, mask; + if (modifiers) { + switch (modifiers[0]) { + case 'c': + shift_amount = 2; + break; + case 'l': + shift_amount = 8; + break; + } + } + mask = (0xf << (shift_amount - 1)) & 0xf; + + for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) { + for (uint8_t i = 0; i < 32; i++) { + if ((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) { + GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]); + } + else { + GB_log(gb, "%c", i%4 == 2 ? '-' : ' '); + } + } + GB_log(gb, "\n"); + } + + return true; +} + +static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command); + +#define HELP_NEWLINE "\n " + +/* Commands without implementations are aliases of the previous non-alias commands */ +static const debugger_command_t commands[] = { + {"continue", 1, cont, "Continue running until next stop"}, + {"next", 1, next, "Run the next instruction, skipping over function calls"}, + {"step", 1, step, "Run the next instruction, stepping into function calls"}, + {"finish", 1, finish, "Run until the current function returns"}, + {"backtrace", 2, backtrace, "Displays the current call stack"}, + {"bt", 2, }, /* Alias */ + {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"}, + {"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE + "used"}, + {"registers", 1, registers, "Print values of processor registers and other important registers"}, + {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, + {"mbc", 3, }, /* Alias */ + {"apu", 3, apu, "Displays information about the current state of the audio chip"}, + {"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE + "Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE + "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer}, + {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, + {"palettes", 3, palettes, "Displays the current CGB palettes"}, + {"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer}, + {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE + "Can also modify the condition of existing breakpoints." HELP_NEWLINE + "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE + "jumping to the target.", + "[ if ]", "j", + .argument_completer = symbol_completer, .modifiers_completer = j_completer}, + {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]", .argument_completer = symbol_completer}, + {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE + "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE + "Default watchpoint type is write-only.", + "[ if ]", "(r|w|rw)", + .argument_completer = symbol_completer, .modifiers_completer = rw_completer + }, + {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]", .argument_completer = symbol_completer}, + {"list", 1, list, "List all set breakpoints and watchpoints"}, + {"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE + "Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE + "decimal (d), hexadecimal (x), octal (o) or binary (b).", + "", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer}, + {"eval", 2, }, /* Alias */ + {"examine", 2, examine, "Examine values at address", "", "count", .argument_completer = symbol_completer}, + {"x", 1, }, /* Alias */ + {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count", .argument_completer = symbol_completer}, + + + {"help", 1, help, "List available commands or show help for the specified command", "[]"}, + {NULL,}, /* Null terminator */ +}; + +static const debugger_command_t *find_command(const char *string) +{ + size_t length = strlen(string); + for (const debugger_command_t *command = commands; command->command; command++) { + if (command->min_length > length) continue; + if (memcmp(command->command, string, length) == 0) { /* Is a substring? */ + /* Aliases */ + while (!command->implementation) { + command--; + } + return command; + } + } + + return NULL; +} + +static void print_command_shortcut(GB_gameboy_t *gb, const debugger_command_t *command) +{ + GB_attributed_log(gb, GB_LOG_BOLD | GB_LOG_UNDERLINE, "%.*s", command->min_length, command->command); + GB_attributed_log(gb, GB_LOG_BOLD, "%s", command->command + command->min_length); +} + +static void print_command_description(GB_gameboy_t *gb, const debugger_command_t *command) +{ + print_command_shortcut(gb, command); + GB_log(gb, ": "); + GB_log(gb, (const char *)&" %s\n" + strlen(command->command), command->help_string); +} + +static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *ignored) +{ + const debugger_command_t *command = find_command(arguments); + if (command) { + print_command_description(gb, command); + GB_log(gb, "\n"); + print_usage(gb, command); + + command++; + if (command->command && !command->implementation) { /* Command has aliases*/ + GB_log(gb, "\nAliases: "); + do { + print_command_shortcut(gb, command); + GB_log(gb, " "); + command++; + } while (command->command && !command->implementation); + GB_log(gb, "\n"); + } + return true; + } + for (command = commands; command->command; command++) { + if (command->help_string) { + print_command_description(gb, command); + } + } + return true; +} + +void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr) +{ + /* Called just after the CPU calls a function/enters an interrupt/etc... */ + + if (gb->stack_leak_detection) { + if (gb->debug_call_depth >= sizeof(gb->sp_for_call_depth) / sizeof(gb->sp_for_call_depth[0])) { + GB_log(gb, "Potential stack overflow detected (Functions nest too much). \n"); + gb->debug_stopped = true; + } + else { + gb->sp_for_call_depth[gb->debug_call_depth] = gb->registers[GB_REGISTER_SP]; + gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc; + } + } + + if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) { + + while (gb->backtrace_size) { + if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->registers[GB_REGISTER_SP]) { + gb->backtrace_size--; + } + else { + break; + } + } + + gb->backtrace_sps[gb->backtrace_size] = gb->registers[GB_REGISTER_SP]; + gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr); + gb->backtrace_returns[gb->backtrace_size].addr = call_addr; + gb->backtrace_size++; + } + + gb->debug_call_depth++; +} + +void GB_debugger_ret_hook(GB_gameboy_t *gb) +{ + /* Called just before the CPU runs ret/reti */ + + gb->debug_call_depth--; + + if (gb->stack_leak_detection) { + if (gb->debug_call_depth < 0) { + GB_log(gb, "Function finished without a stack leak.\n"); + gb->debug_stopped = true; + } + else { + if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { + GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true)); + GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->registers[GB_REGISTER_SP], + gb->sp_for_call_depth[gb->debug_call_depth]); + gb->debug_stopped = true; + } + } + } + + while (gb->backtrace_size) { + if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->registers[GB_REGISTER_SP]) { + gb->backtrace_size--; + } + else { + break; + } + } +} + +static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, uint8_t value) +{ + uint16_t index = find_watchpoint(gb, addr); + uint32_t key = WP_KEY(addr); + + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_W)) { + return false; + } + if (!gb->watchpoints[index].condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, + (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return false; + } + if (condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); + return true; + } + } + return false; +} + +void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (gb->debug_stopped) return; + + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_GB_debugger_test_write_watchpoint(gb, full_addr, value)) return; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + _GB_debugger_test_write_watchpoint(gb, full_addr, value); +} + +static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr) +{ + uint16_t index = find_watchpoint(gb, addr); + uint32_t key = WP_KEY(addr); + + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) { + return false; + } + if (!gb->watchpoints[index].condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, + (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return false; + } + if (condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); + return true; + } + } + return false; +} + +void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->debug_stopped) return; + + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_GB_debugger_test_read_watchpoint(gb, full_addr)) return; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + _GB_debugger_test_read_watchpoint(gb, full_addr); +} + +/* Returns true if debugger waits for more commands */ +bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) +{ + if (!input[0]) { + return true; + } + + char *command_string = input; + char *arguments = strchr(input, ' '); + if (arguments) { + /* Actually "split" the string. */ + arguments[0] = 0; + arguments++; + } + else { + arguments = ""; + } + + char *modifiers = strchr(command_string, '/'); + if (modifiers) { + /* Actually "split" the string. */ + modifiers[0] = 0; + modifiers++; + } + + const debugger_command_t *command = find_command(command_string); + if (command) { + return command->implementation(gb, arguments, modifiers, command); + } + else { + GB_log(gb, "%s: no such command.\n", command_string); + return true; + } +} + +/* Returns true if debugger waits for more commands */ +char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context) +{ + char *command_string = input; + char *arguments = strchr(input, ' '); + if (arguments) { + /* Actually "split" the string. */ + arguments[0] = 0; + arguments++; + } + + char *modifiers = strchr(command_string, '/'); + if (modifiers) { + /* Actually "split" the string. */ + modifiers[0] = 0; + modifiers++; + } + + const debugger_command_t *command = find_command(command_string); + if (command && command->implementation == help && arguments) { + command_string = arguments; + arguments = NULL; + } + + /* No commands and no modifiers, complete the command */ + if (!arguments && !modifiers) { + size_t length = strlen(command_string); + if (*context >= sizeof(commands) / sizeof(commands[0])) { + return NULL; + } + for (const debugger_command_t *command = &commands[*context]; command->command; command++) { + (*context)++; + if (memcmp(command->command, command_string, length) == 0) { /* Is a substring? */ + return strdup(command->command + length); + } + } + return NULL; + } + + if (command) { + if (arguments) { + if (command->argument_completer) { + return command->argument_completer(gb, arguments, context); + } + return NULL; + } + + if (modifiers) { + if (command->modifiers_completer) { + return command->modifiers_completer(gb, modifiers, context); + } + return NULL; + } + } + return NULL; +} + +typedef enum { + JUMP_TO_NONE, + JUMP_TO_BREAK, + JUMP_TO_NONTRIVIAL, +} jump_to_return_t; + +static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address); + +void GB_debugger_run(GB_gameboy_t *gb) +{ + if (gb->debug_disable) return; + + char *input = NULL; + if (gb->debug_next_command && gb->debug_call_depth <= 0 && !gb->halted) { + gb->debug_stopped = true; + } + if (gb->debug_fin_command && gb->debug_call_depth == -1) { + gb->debug_stopped = true; + } + if (gb->debug_stopped) { + GB_cpu_disassemble(gb, gb->pc, 5); + } +next_command: + if (input) { + free(input); + } + if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc, false)) { + gb->debug_stopped = true; + GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + } + + if (gb->breakpoints && !gb->debug_stopped) { + uint16_t address = 0; + jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address); + + bool should_delete_state = true; + if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) { + if (gb->non_trivial_jump_breakpoint_occured) { + gb->non_trivial_jump_breakpoint_occured = false; + } + else { + gb->non_trivial_jump_breakpoint_occured = true; + GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + GB_load_state_from_buffer(gb, gb->nontrivial_jump_state, -1); + gb->debug_stopped = true; + } + } + else if (jump_to_result == JUMP_TO_BREAK) { + gb->debug_stopped = true; + GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, address, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + gb->non_trivial_jump_breakpoint_occured = false; + } + else if (jump_to_result == JUMP_TO_NONTRIVIAL) { + if (!gb->nontrivial_jump_state) { + gb->nontrivial_jump_state = malloc(GB_get_save_state_size(gb)); + } + GB_save_state_to_buffer(gb, gb->nontrivial_jump_state); + gb->non_trivial_jump_breakpoint_occured = false; + should_delete_state = false; + } + else { + gb->non_trivial_jump_breakpoint_occured = false; + } + + if (should_delete_state) { + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + gb->nontrivial_jump_state = NULL; + } + } + } + + if (gb->debug_stopped && !gb->debug_disable) { + gb->debug_next_command = false; + gb->debug_fin_command = false; + gb->stack_leak_detection = false; + input = gb->input_callback(gb); + + if (input == NULL) { + /* Debugging is no currently available, continue running */ + gb->debug_stopped = false; + return; + } + + if (GB_debugger_execute_command(gb, input)) { + goto next_command; + } + + free(input); + } +} + +void GB_debugger_handle_async_commands(GB_gameboy_t *gb) +{ + char *input = NULL; + + while (gb->async_input_callback && (input = gb->async_input_callback(gb))) { + GB_debugger_execute_command(gb, input); + free(input); + } +} + +void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol) +{ + bank &= 0x1FF; + + if (!gb->bank_symbols[bank]) { + gb->bank_symbols[bank] = GB_map_alloc(); + } + GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol); + if (allocated_symbol) { + GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol); + } +} + +void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) return; + + char *line = NULL; + size_t size = 0; + size_t length = 0; + while ((length = getline(&line, &size, f)) != -1) { + for (unsigned i = 0; i < length; i++) { + if (line[i] == ';' || line[i] == '\n' || line[i] == '\r') { + line[i] = 0; + length = i; + break; + } + } + if (length == 0) continue; + + unsigned bank, address; + char symbol[length]; + + if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) { + GB_debugger_add_symbol(gb, bank, address, symbol); + } + } + free(line); + fclose(f); +} + +void GB_debugger_clear_symbols(GB_gameboy_t *gb) +{ + for (unsigned i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) { + if (gb->bank_symbols[i]) { + GB_map_free(gb->bank_symbols[i]); + gb->bank_symbols[i] = 0; + } + } + for (unsigned i = sizeof(gb->reversed_symbol_map.buckets) / sizeof(gb->reversed_symbol_map.buckets[0]); i--;) { + while (gb->reversed_symbol_map.buckets[i]) { + GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next; + free(gb->reversed_symbol_map.buckets[i]); + gb->reversed_symbol_map.buckets[i] = next; + } + } +} + +const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) +{ + uint16_t bank = bank_for_addr(gb, addr); + + const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr); + if (symbol) return symbol; + if (bank != 0) return GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */ + + return NULL; +} + +const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr) +{ + const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, addr); + if (symbol && symbol->addr == addr) return symbol->name; + return NULL; +} + +/* The public version of debugger_evaluate */ +bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank) +{ + bool error = false; + value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL, NULL); + if (result) { + *result = value.value; + } + if (result_bank) { + *result_bank = value.has_bank? value.value : -1; + } + return error; +} + +void GB_debugger_break(GB_gameboy_t *gb) +{ + gb->debug_stopped = true; +} + +bool GB_debugger_is_stopped(GB_gameboy_t *gb) +{ + return gb->debug_stopped; +} + +void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled) +{ + gb->debug_disable = disabled; +} + +/* Jump-to breakpoints */ + +static bool is_in_trivial_memory(uint16_t addr) +{ + /* ROM */ + if (addr < 0x8000) { + return true; + } + + /* HRAM */ + if (addr >= 0xFF80 && addr < 0xFFFF) { + return true; + } + + /* RAM */ + if (addr >= 0xC000 && addr < 0xE000) { + return true; + } + + return false; +} + +typedef uint16_t GB_opcode_address_getter_t(GB_gameboy_t *gb, uint8_t opcode); + +uint16_t trivial_1(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 1; +} + +uint16_t trivial_2(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 2; +} + +uint16_t trivial_3(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 3; +} + +static uint16_t jr_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); +} + +static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 1: + return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 2: + return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + case 3: + return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + } + + return false; +} + +static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + if (!condition_code(gb, opcode)) { + return gb->pc + 2; + } + + return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); +} + +static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode) +{ + return GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | + (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); +} + + +static uint16_t ret_cc(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + return ret(gb, opcode); + } + else { + return gb->pc + 1; + } +} + +static uint16_t jp_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + return GB_read_memory(gb, gb->pc + 1) | + (GB_read_memory(gb, gb->pc + 2) << 8); +} + +static uint16_t jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + return jp_a16(gb, opcode); + } + else { + return gb->pc + 3; + } +} + +static uint16_t rst(GB_gameboy_t *gb, uint8_t opcode) +{ + return opcode ^ 0xC7; +} + +static uint16_t jp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->hl; +} + +static GB_opcode_address_getter_t *opcodes[256] = { + /* X0 X1 X2 X3 X4 X5 X6 X7 */ + /* X8 X9 Xa Xb Xc Xd Xe Xf */ + trivial_1, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 0X */ + trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + trivial_2, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 1X */ + jr_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 2X */ + jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 3X */ + jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 4X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 5X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 6X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, NULL, trivial_1, /* 7X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 8X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 9X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* aX */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* bX */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + ret_cc, trivial_1, jp_cc_a16, jp_a16, jp_cc_a16, trivial_1, trivial_2, rst, /* cX */ + ret_cc, ret, jp_cc_a16, trivial_2, jp_cc_a16, jp_a16, trivial_2, rst, + ret_cc, trivial_1, jp_cc_a16, NULL, jp_cc_a16, trivial_1, trivial_2, rst, /* dX */ + ret_cc, ret, jp_cc_a16, NULL, jp_cc_a16, NULL, trivial_2, rst, + trivial_2, trivial_1, trivial_1, NULL, NULL, trivial_1, trivial_2, rst, /* eX */ + trivial_2, jp_hl, trivial_3, NULL, NULL, NULL, trivial_2, rst, + trivial_2, trivial_1, trivial_1, trivial_1, NULL, trivial_1, trivial_2, rst, /* fX */ + trivial_2, trivial_1, trivial_3, trivial_1, NULL, NULL, trivial_2, rst, +}; + +static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address) +{ + if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE; + + if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) || + !is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) { + return JUMP_TO_NONTRIVIAL; + } + + /* Interrupts */ + if (gb->ime) { + for (unsigned i = 0; i < 5; i++) { + if ((gb->interrupt_enable & (1 << i)) && (gb->io_registers[GB_IO_IF] & (1 << i))) { + if (should_break(gb, 0x40 + i * 8, true)) { + if (address) { + *address = 0x40 + i * 8; + } + return JUMP_TO_BREAK; + } + } + } + } + + uint16_t n_watchpoints = gb->n_watchpoints; + gb->n_watchpoints = 0; + + uint8_t opcode = GB_read_memory(gb, gb->pc); + + if (opcode == 0x76) { + gb->n_watchpoints = n_watchpoints; + if (gb->ime) { /* Already handled in above */ + return JUMP_TO_NONE; + } + + if (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) { + return JUMP_TO_NONTRIVIAL; /* HALT bug could occur */ + } + + return JUMP_TO_NONE; + } + + GB_opcode_address_getter_t *getter = opcodes[opcode]; + if (!getter) { + gb->n_watchpoints = n_watchpoints; + return JUMP_TO_NONE; + } + + uint16_t new_pc = getter(gb, opcode); + + gb->n_watchpoints = n_watchpoints; + + if (address) { + *address = new_pc; + } + + return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE; +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/debugger.h b/waterbox/bsnescore/bsnes/gb/Core/debugger.h new file mode 100644 index 0000000000..0678b30c95 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/debugger.h @@ -0,0 +1,46 @@ +#ifndef debugger_h +#define debugger_h +#include +#include +#include "gb_struct_def.h" +#include "symbol_hash.h" + + +#ifdef GB_INTERNAL +#ifdef GB_DISABLE_DEBUGGER +#define GB_debugger_run(gb) (void)0 +#define GB_debugger_handle_async_commands(gb) (void)0 +#define GB_debugger_ret_hook(gb) (void)0 +#define GB_debugger_call_hook(gb, addr) (void)addr +#define GB_debugger_test_write_watchpoint(gb, addr, value) ((void)addr, (void)value) +#define GB_debugger_test_read_watchpoint(gb, addr) (void)addr +#define GB_debugger_add_symbol(gb, bank, address, symbol) ((void)bank, (void)address, (void)symbol) + +#else +void GB_debugger_run(GB_gameboy_t *gb); +void GB_debugger_handle_async_commands(GB_gameboy_t *gb); +void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr); +void GB_debugger_ret_hook(GB_gameboy_t *gb); +void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); +const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); +void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol); +#endif /* GB_DISABLE_DEBUGGER */ +#endif + +#ifdef GB_INTERNAL +bool /* Returns true if debugger waits for more commands. Not relevant for non-GB_INTERNAL */ +#else +void +#endif +GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */ +char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context); /* Destroys input, result requires free */ + +void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path); +const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr); +bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank); /* result_bank is -1 if unused. */ +void GB_debugger_break(GB_gameboy_t *gb); +bool GB_debugger_is_stopped(GB_gameboy_t *gb); +void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled); +void GB_debugger_clear_symbols(GB_gameboy_t *gb); +#endif /* debugger_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/display.c b/waterbox/bsnescore/bsnes/gb/Core/display.c new file mode 100644 index 0000000000..2eb8c424b5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/display.c @@ -0,0 +1,1487 @@ +#include +#include +#include +#include +#include "gb.h" + +/* FIFO functions */ + +static inline unsigned fifo_size(GB_fifo_t *fifo) +{ + return (fifo->write_end - fifo->read_end) & (GB_FIFO_LENGTH - 1); +} + +static void fifo_clear(GB_fifo_t *fifo) +{ + fifo->read_end = fifo->write_end = 0; +} + +static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) +{ + GB_fifo_item_t *ret = &fifo->fifo[fifo->read_end]; + fifo->read_end++; + fifo->read_end &= (GB_FIFO_LENGTH - 1); + return ret; +} + +static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x) +{ + if (!flip_x) { + UNROLL + for (unsigned i = 8; i--;) { + fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { + (lower >> 7) | ((upper >> 7) << 1), + palette, + 0, + bg_priority, + }; + lower <<= 1; + upper <<= 1; + + fifo->write_end++; + fifo->write_end &= (GB_FIFO_LENGTH - 1); + } + } + else { + UNROLL + for (unsigned i = 8; i--;) { + fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { + (lower & 1) | ((upper & 1) << 1), + palette, + 0, + bg_priority, + }; + lower >>= 1; + upper >>= 1; + + fifo->write_end++; + fifo->write_end &= (GB_FIFO_LENGTH - 1); + } + } +} + +static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, uint8_t priority, bool flip_x) +{ + while (fifo_size(fifo) < 8) { + fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {0,}; + fifo->write_end++; + fifo->write_end &= (GB_FIFO_LENGTH - 1); + } + + uint8_t flip_xor = flip_x? 0: 0x7; + + UNROLL + for (unsigned i = 8; i--;) { + uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1); + GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & (GB_FIFO_LENGTH - 1)]; + if (pixel != 0 && (target->pixel == 0 || target->priority > priority)) { + target->pixel = pixel; + target->palette = palette; + target->bg_priority = bg_priority; + target->priority = priority; + } + lower <<= 1; + upper <<= 1; + } +} + + +/* + Each line is 456 cycles. Without scrolling, sprites or a window: + Mode 2 - 80 cycles / OAM Transfer + Mode 3 - 172 cycles / Rendering + Mode 0 - 204 cycles / HBlank + + Mode 1 is VBlank + */ + +#define MODE2_LENGTH (80) +#define LINE_LENGTH (456) +#define LINES (144) +#define WIDTH (160) +#define BORDERED_WIDTH 256 +#define BORDERED_HEIGHT 224 +#define FRAME_LENGTH (LCDC_PERIOD) +#define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154 + +typedef struct __attribute__((packed)) { + uint8_t y; + uint8_t x; + uint8_t tile; + uint8_t flags; +} GB_object_t; + +static void display_vblank(GB_gameboy_t *gb) +{ + gb->vblank_just_occured = true; + + /* TODO: Slow in turbo mode! */ + if (GB_is_hle_sgb(gb)) { + GB_sgb_render(gb); + } + + if (gb->turbo) { + if (GB_timing_sync_turbo(gb)) { + return; + } + } + + bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80; + + if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) { + /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ + if (!GB_is_sgb(gb)) { + uint32_t color = 0; + if (GB_is_cgb(gb)) { + color = GB_convert_rgb15(gb, 0x7FFF, false); + } + else { + color = is_ppu_stopped ? + gb->background_palettes_rgb[0] : + gb->background_palettes_rgb[4]; + } + if (gb->border_mode == GB_BORDER_ALWAYS) { + for (unsigned y = 0; y < LINES; y++) { + for (unsigned x = 0; x < WIDTH; x++) { + gb ->screen[x + y * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH] = color; + } + } + } + else { + for (unsigned i = 0; i < WIDTH * LINES; i++) { + gb ->screen[i] = color; + } + } + } + } + + if (gb->border_mode == GB_BORDER_ALWAYS && !GB_is_sgb(gb)) { + GB_borrow_sgb_border(gb); + uint32_t border_colors[16 * 4]; + + if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model != GB_MODEL_AGB) { + static uint16_t colors[] = { + 0x2095, 0x5129, 0x1EAF, 0x1EBA, 0x4648, + 0x30DA, 0x69AD, 0x2B57, 0x2B5D, 0x632C, + 0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964, + }; + unsigned index = gb->rom? gb->rom[0x14e] % 5 : 0; + gb->borrowed_border.palette[0] = colors[index]; + gb->borrowed_border.palette[10] = colors[5 + index]; + gb->borrowed_border.palette[14] = colors[10 + index]; + + } + + for (unsigned i = 0; i < 16 * 4; i++) { + border_colors[i] = GB_convert_rgb15(gb, gb->borrowed_border.palette[i], true); + } + + for (unsigned tile_y = 0; tile_y < 28; tile_y++) { + for (unsigned tile_x = 0; tile_x < 32; tile_x++) { + if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) { + continue; + } + uint16_t tile = gb->borrowed_border.map[tile_x + tile_y * 32]; + uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; + uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; + uint8_t palette = (tile >> 10) & 3; + for (unsigned y = 0; y < 8; y++) { + for (unsigned x = 0; x < 8; x++) { + uint8_t color = gb->borrowed_border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; + uint32_t *output = gb->screen + tile_x * 8 + x + (tile_y * 8 + y) * 256; + if (color == 0) { + *output = border_colors[0]; + } + else { + *output = border_colors[color + palette * 16]; + } + } + } + } + } + } + GB_handle_rumble(gb); + + if (gb->vblank_callback) { + gb->vblank_callback(gb); + } + GB_timing_sync(gb); +} + +static inline uint8_t scale_channel(uint8_t x) +{ + return (x << 3) | (x >> 2); +} + +static inline uint8_t scale_channel_with_curve(uint8_t x) +{ + return (uint8_t[]){0,5,8,11,16,22,28,36,43,51,59,67,77,87,97,107,119,130,141,153,166,177,188,200,209,221,230,238,245,249,252,255}[x]; +} + +static inline uint8_t scale_channel_with_curve_agb(uint8_t x) +{ + return (uint8_t[]){0,2,5,10,15,20,26,32,38,45,52,60,68,76,84,92,101,110,119,128,138,148,158,168,178,189,199,210,221,232,244,255}[x]; +} + +static inline uint8_t scale_channel_with_curve_sgb(uint8_t x) +{ + return (uint8_t[]){0,2,5,9,15,20,27,34,42,50,58,67,76,85,94,104,114,123,133,143,153,163,173,182,192,202,211,220,229,238,247,255}[x]; +} + + +uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) +{ + uint8_t r = (color) & 0x1F; + uint8_t g = (color >> 5) & 0x1F; + uint8_t b = (color >> 10) & 0x1F; + + if (gb->color_correction_mode == GB_COLOR_CORRECTION_DISABLED || (for_border && !gb->has_sgb_border)) { + r = scale_channel(r); + g = scale_channel(g); + b = scale_channel(b); + } + else { + if (GB_is_sgb(gb) || for_border) { + return gb->rgb_encode_callback(gb, + scale_channel_with_curve_sgb(r), + scale_channel_with_curve_sgb(g), + scale_channel_with_curve_sgb(b)); + } + bool agb = gb->model == GB_MODEL_AGB; + r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r); + g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g); + b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b); + + if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) { + uint8_t new_r, new_g, new_b; + if (agb) { + new_g = (g * 6 + b * 1) / 7; + } + else { + new_g = (g * 3 + b) / 4; + } + new_r = r; + new_b = b; + if (gb->color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) { + r = new_r; + g = new_r; + b = new_r; + + new_r = new_r * 7 / 8 + ( g + b) / 16; + new_g = new_g * 7 / 8 + (r + b) / 16; + new_b = new_b * 7 / 8 + (r + g ) / 16; + + + new_r = new_r * (224 - 32) / 255 + 32; + new_g = new_g * (220 - 36) / 255 + 36; + new_b = new_b * (216 - 40) / 255 + 40; + } + else if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { + uint8_t old_max = MAX(r, MAX(g, b)); + uint8_t new_max = MAX(new_r, MAX(new_g, new_b)); + + if (new_max != 0) { + new_r = new_r * old_max / new_max; + new_g = new_g * old_max / new_max; + new_b = new_b * old_max / new_max; + } + + uint8_t old_min = MIN(r, MIN(g, b)); + uint8_t new_min = MIN(new_r, MIN(new_g, new_b)); + + if (new_min != 0xff) { + new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min); + new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min); + new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min); + } + } + r = new_r; + g = new_g; + b = new_b; + } + } + + return gb->rgb_encode_callback(gb, r, g, b); +} + +void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index) +{ + if (!gb->rgb_encode_callback || !GB_is_cgb(gb)) return; + uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data; + uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8); + + (background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color, false); +} + +void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode) +{ + gb->color_correction_mode = mode; + if (GB_is_cgb(gb)) { + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, false, i * 2); + GB_palette_changed(gb, true, i * 2); + } + } +} + +/* + STAT interrupt is implemented based on this finding: + http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531 + + General timing is based on GiiBiiAdvance's documents: + https://github.com/AntonioND/giibiiadvance + + */ + +void GB_STAT_update(GB_gameboy_t *gb) +{ + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return; + + bool previous_interrupt_line = gb->stat_interrupt_line; + /* Set LY=LYC bit */ + /* TODO: This behavior might not be correct for CGB revisions other than C and E */ + if (gb->ly_for_comparison != (uint16_t)-1 || gb->model <= GB_MODEL_CGB_C) { + if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) { + gb->lyc_interrupt_line = true; + gb->io_registers[GB_IO_STAT] |= 4; + } + else { + if (gb->ly_for_comparison != (uint16_t)-1) { + gb->lyc_interrupt_line = false; + } + gb->io_registers[GB_IO_STAT] &= ~4; + } + } + + switch (gb->mode_for_interrupt) { + case 0: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 8; break; + case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break; + case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; + default: gb->stat_interrupt_line = false; + } + + /* User requested a LY=LYC interrupt and the LY=LYC bit is on */ + if ((gb->io_registers[GB_IO_STAT] & 0x40) && gb->lyc_interrupt_line) { + gb->stat_interrupt_line = true; + } + + if (gb->stat_interrupt_line && !previous_interrupt_line) { + gb->io_registers[GB_IO_IF] |= 2; + } +} + +void GB_lcd_off(GB_gameboy_t *gb) +{ + gb->display_state = 0; + gb->display_cycles = 0; + /* When the LCD is disabled, state is constant */ + + /* When the LCD is off, LY is 0 and STAT mode is 0. */ + gb->io_registers[GB_IO_LY] = 0; + gb->io_registers[GB_IO_STAT] &= ~3; + if (gb->hdma_on_hblank) { + gb->hdma_on_hblank = false; + gb->hdma_on = false; + + /* Todo: is this correct? */ + gb->hdma_steps_left = 0xff; + } + + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + gb->cgb_palettes_blocked = false; + + gb->current_line = 0; + gb->ly_for_comparison = 0; + + gb->accessed_oam_row = -1; + gb->wy_triggered = false; +} + +static void add_object_from_index(GB_gameboy_t *gb, unsigned index) +{ + if (gb->n_visible_objs == 10) return; + + /* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */ + if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) { + return; + } + + if (gb->oam_ppu_blocked) { + return; + } + + /* This reverse sorts the visible objects by location and priority */ + GB_object_t *objects = (GB_object_t *) &gb->oam; + bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; + signed y = objects[index].y - 16; + if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) { + unsigned j = 0; + for (; j < gb->n_visible_objs; j++) { + if (gb->obj_comparators[j] <= objects[index].x) break; + } + memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j); + memmove(gb->obj_comparators + j + 1, gb->obj_comparators + j, gb->n_visible_objs - j); + gb->visible_objs[j] = index; + gb->obj_comparators[j] = objects[index].x; + gb->n_visible_objs++; + } +} + +static void render_pixel_if_possible(GB_gameboy_t *gb) +{ + GB_fifo_item_t *fifo_item = NULL; + GB_fifo_item_t *oam_fifo_item = NULL; + bool draw_oam = false; + bool bg_enabled = true, bg_priority = false; + + if (fifo_size(&gb->bg_fifo)) { + fifo_item = fifo_pop(&gb->bg_fifo); + bg_priority = fifo_item->bg_priority; + + if (fifo_size(&gb->oam_fifo)) { + oam_fifo_item = fifo_pop(&gb->oam_fifo); + if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2)) { + draw_oam = true; + bg_priority |= oam_fifo_item->bg_priority; + } + } + } + + + if (!fifo_item) return; + + /* Drop pixels for scrollings */ + if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) { + gb->position_in_line++; + return; + } + + /* Mixing */ + + if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { + if (gb->cgb_mode) { + bg_priority = false; + } + else { + bg_enabled = false; + } + } + + uint8_t icd_pixel = 0; + uint32_t *dest = NULL; + if (!gb->sgb) { + if (gb->border_mode != GB_BORDER_ALWAYS) { + dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH; + } + else { + dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; + } + } + + { + uint8_t pixel = bg_enabled? fifo_item->pixel : 0; + if (pixel && bg_priority) { + draw_oam = false; + } + if (!gb->cgb_mode) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + if (gb->sgb) { + if (gb->current_lcd_line < LINES) { + gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; + } + } + else if (gb->model & GB_MODEL_NO_SFC_BIT) { + if (gb->icd_pixel_callback) { + icd_pixel = pixel; + } + } + else if (gb->cgb_palettes_ppu_blocked) { + *dest = gb->rgb_encode_callback(gb, 0, 0, 0); + } + else { + *dest = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; + } + } + + if (draw_oam) { + uint8_t pixel = oam_fifo_item->pixel; + if (!gb->cgb_mode) { + /* Todo: Verify access timings */ + pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); + } + if (gb->sgb) { + if (gb->current_lcd_line < LINES) { + gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; + } + } + else if (gb->model & GB_MODEL_NO_SFC_BIT) { + if (gb->icd_pixel_callback) { + icd_pixel = pixel; + //gb->icd_pixel_callback(gb, pixel); + } + } + else if (gb->cgb_palettes_ppu_blocked) { + *dest = gb->rgb_encode_callback(gb, 0, 0, 0); + } + else { + *dest = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; + } + } + + if (gb->model & GB_MODEL_NO_SFC_BIT) { + if (gb->icd_pixel_callback) { + gb->icd_pixel_callback(gb, icd_pixel); + } + } + + gb->position_in_line++; + gb->lcd_x++; + gb->window_is_being_fetched = false; +} + +/* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have + slightly different timings than CPUs <= C. + + Todo: Add support to CPU C and older */ + +static inline uint8_t fetcher_y(GB_gameboy_t *gb) +{ + return gb->wx_triggered? gb->window_y : gb->current_line + gb->io_registers[GB_IO_SCY]; +} + +static void advance_fetcher_state_machine(GB_gameboy_t *gb) +{ + typedef enum { + GB_FETCHER_GET_TILE, + GB_FETCHER_GET_TILE_DATA_LOWER, + GB_FETCHER_GET_TILE_DATA_HIGH, + GB_FETCHER_PUSH, + GB_FETCHER_SLEEP, + } fetcher_step_t; + + fetcher_step_t fetcher_state_machine [8] = { + GB_FETCHER_SLEEP, + GB_FETCHER_GET_TILE, + GB_FETCHER_SLEEP, + GB_FETCHER_GET_TILE_DATA_LOWER, + GB_FETCHER_SLEEP, + GB_FETCHER_GET_TILE_DATA_HIGH, + GB_FETCHER_PUSH, + GB_FETCHER_PUSH, + }; + switch (fetcher_state_machine[gb->fetcher_state & 7]) { + case GB_FETCHER_GET_TILE: { + uint16_t map = 0x1800; + + if (!(gb->io_registers[GB_IO_LCDC] & 0x20)) { + gb->wx_triggered = false; + gb->wx166_glitch = false; + } + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->wx_triggered) { + map = 0x1C00; + } + else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->wx_triggered) { + map = 0x1C00; + } + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + uint8_t y = fetcher_y(gb); + uint8_t x = 0; + if (gb->wx_triggered) { + x = gb->window_tile_x; + } + else { + x = ((gb->io_registers[GB_IO_SCX] / 8) + gb->fetcher_x) & 0x1F; + } + if (gb->model > GB_MODEL_CGB_C) { + /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ + gb->fetcher_y = y; + } + gb->last_tile_index_address = map + x + y / 8 * 32; + gb->current_tile = gb->vram[gb->last_tile_index_address]; + if (gb->vram_ppu_blocked) { + gb->current_tile = 0xFF; + } + if (GB_is_cgb(gb)) { + /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. + This probably means the CGB has a 16-bit data bus for the VRAM. */ + gb->current_tile_attributes = gb->vram[gb->last_tile_index_address + 0x2000]; + if (gb->vram_ppu_blocked) { + gb->current_tile_attributes = 0xFF; + } + } + } + gb->fetcher_state++; + break; + + case GB_FETCHER_GET_TILE_DATA_LOWER: { + uint8_t y_flip = 0; + uint16_t tile_address = 0; + uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = gb->current_tile * 0x10; + } + else { + tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + if (gb->current_tile_attributes & 8) { + tile_address += 0x2000; + } + if (gb->current_tile_attributes & 0x40) { + y_flip = 0x7; + } + gb->current_tile_data[0] = + gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; + if (gb->vram_ppu_blocked) { + gb->current_tile_data[0] = 0xFF; + } + } + gb->fetcher_state++; + break; + + case GB_FETCHER_GET_TILE_DATA_HIGH: { + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. + Additionally, on CGB-D and newer mixing two tiles by changing the tileset + bit mid-fetching causes a glitched mixing of the two, in comparison to the + more logical DMG version. */ + uint16_t tile_address = 0; + uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); + + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = gb->current_tile * 0x10; + } + else { + tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + if (gb->current_tile_attributes & 8) { + tile_address += 0x2000; + } + uint8_t y_flip = 0; + if (gb->current_tile_attributes & 0x40) { + y_flip = 0x7; + } + gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1; + gb->current_tile_data[1] = + gb->vram[gb->last_tile_data_address]; + if (gb->vram_ppu_blocked) { + gb->current_tile_data[1] = 0xFF; + } + } + if (gb->wx_triggered) { + gb->window_tile_x++; + gb->window_tile_x &= 0x1f; + } + + // fallthrough + case GB_FETCHER_PUSH: { + if (gb->fetcher_state == 6) { + /* The background map index increase at this specific point. If this state is not reached, + it will simply not increase. */ + gb->fetcher_x++; + gb->fetcher_x &= 0x1f; + } + if (gb->fetcher_state < 7) { + gb->fetcher_state++; + } + if (fifo_size(&gb->bg_fifo) > 0) break; + + fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1], + gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20); + gb->fetcher_state = 0; + } + break; + + case GB_FETCHER_SLEEP: + { + gb->fetcher_state++; + } + break; + } +} + +static uint16_t get_object_line_address(GB_gameboy_t *gb, const GB_object_t *object) +{ + /* TODO: what does the PPU read if DMA is active? */ + if (gb->oam_ppu_blocked) { + static const GB_object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF}; + object = &blocked; + } + + bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ + uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7); + + if (object->flags & 0x40) { /* Flip Y */ + tile_y ^= height_16? 0xF : 7; + } + + /* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */ + uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2; + + if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */ + line_address += 0x2000; + } + return line_address; +} + +/* + TODO: It seems that the STAT register's mode bits are always "late" by 4 T-cycles. + The PPU logic can be greatly simplified if that delay is simply emulated. + */ +void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) +{ + /* The PPU does not advance while in STOP mode on the DMG */ + if (gb->stopped && !GB_is_cgb(gb)) { + gb->cycles_in_stop_mode += cycles; + if (gb->cycles_in_stop_mode >= LCDC_PERIOD) { + gb->cycles_in_stop_mode -= LCDC_PERIOD; + display_vblank(gb); + } + return; + } + GB_object_t *objects = (GB_object_t *) &gb->oam; + + GB_STATE_MACHINE(gb, display, cycles, 2) { + GB_STATE(gb, display, 1); + GB_STATE(gb, display, 2); + // GB_STATE(gb, display, 3); + // GB_STATE(gb, display, 4); + // GB_STATE(gb, display, 5); + GB_STATE(gb, display, 6); + GB_STATE(gb, display, 7); + GB_STATE(gb, display, 8); + // GB_STATE(gb, display, 9); + GB_STATE(gb, display, 10); + GB_STATE(gb, display, 11); + GB_STATE(gb, display, 12); + GB_STATE(gb, display, 13); + GB_STATE(gb, display, 14); + GB_STATE(gb, display, 15); + GB_STATE(gb, display, 16); + GB_STATE(gb, display, 17); + // GB_STATE(gb, display, 19); + GB_STATE(gb, display, 20); + GB_STATE(gb, display, 21); + GB_STATE(gb, display, 22); + GB_STATE(gb, display, 23); + // GB_STATE(gb, display, 24); + GB_STATE(gb, display, 25); + GB_STATE(gb, display, 26); + GB_STATE(gb, display, 27); + GB_STATE(gb, display, 28); + GB_STATE(gb, display, 29); + GB_STATE(gb, display, 30); + // GB_STATE(gb, display, 31); + GB_STATE(gb, display, 32); + GB_STATE(gb, display, 33); + GB_STATE(gb, display, 34); + GB_STATE(gb, display, 35); + GB_STATE(gb, display, 36); + GB_STATE(gb, display, 37); + GB_STATE(gb, display, 38); + GB_STATE(gb, display, 39); + GB_STATE(gb, display, 40); + GB_STATE(gb, display, 41); + GB_STATE(gb, display, 42); + } + + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + while (true) { + GB_SLEEP(gb, display, 1, LCDC_PERIOD); + display_vblank(gb); + gb->cgb_repeated_a_frame = true; + } + return; + } + + gb->is_odd_frame = false; + + if (!GB_is_cgb(gb)) { + GB_SLEEP(gb, display, 23, 1); + } + + /* Handle mode 2 on the very first line 0 */ + gb->current_line = 0; + gb->window_y = -1; + /* Todo: verify timings */ + if (gb->io_registers[GB_IO_WY] == 0) { + gb->wy_triggered = true; + } + else { + gb->wy_triggered = false; + } + + gb->ly_for_comparison = 0; + gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = -1; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + gb->cgb_palettes_blocked = false; + gb->cycles_for_line = MODE2_LENGTH - 4; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 2, MODE2_LENGTH - 4); + + gb->oam_write_blocked = true; + gb->cycles_for_line += 2; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 34, 2); + + gb->n_visible_objs = 0; + gb->cycles_for_line += 8; // Mode 0 is shorter on the first line 0, so we augment cycles_for_line by 8 extra cycles. + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 3; + gb->mode_for_interrupt = 3; + + gb->oam_write_blocked = true; + gb->oam_read_blocked = true; + gb->vram_read_blocked = gb->cgb_double_speed; + gb->vram_write_blocked = gb->cgb_double_speed; + if (!GB_is_cgb(gb)) { + gb->vram_read_blocked = true; + gb->vram_write_blocked = true; + } + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 37, 2); + + gb->cgb_palettes_blocked = true; + gb->cycles_for_line += (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3; + GB_SLEEP(gb, display, 38, (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3); + + gb->vram_read_blocked = true; + gb->vram_write_blocked = true; + gb->wx_triggered = false; + gb->wx166_glitch = false; + goto mode_3_start; + + while (true) { + /* Lines 0 - 143 */ + gb->window_y = -1; + for (; gb->current_line < LINES; gb->current_line++) { + /* Todo: verify timings */ + if ((gb->io_registers[GB_IO_WY] == gb->current_line || + (gb->current_line != 0 && gb->io_registers[GB_IO_WY] == gb->current_line - 1))) { + gb->wy_triggered = true; + } + + gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed; + gb->accessed_oam_row = 0; + + GB_SLEEP(gb, display, 35, 2); + gb->oam_write_blocked = GB_is_cgb(gb); + + GB_SLEEP(gb, display, 6, 1); + gb->io_registers[GB_IO_LY] = gb->current_line; + gb->oam_read_blocked = true; + gb->ly_for_comparison = gb->current_line? -1 : 0; + + /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0. + PPU glitch? */ + if (gb->current_line != 0) { + gb->mode_for_interrupt = 2; + gb->io_registers[GB_IO_STAT] &= ~3; + } + else if (!GB_is_cgb(gb)) { + gb->io_registers[GB_IO_STAT] &= ~3; + } + GB_STAT_update(gb); + + GB_SLEEP(gb, display, 7, 1); + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 2; + gb->mode_for_interrupt = 2; + gb->oam_write_blocked = true; + gb->ly_for_comparison = gb->current_line; + GB_STAT_update(gb); + gb->mode_for_interrupt = -1; + GB_STAT_update(gb); + gb->n_visible_objs = 0; + + for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) { + if (GB_is_cgb(gb)) { + add_object_from_index(gb, gb->oam_search_index); + /* The CGB does not care about the accessed OAM row as there's no OAM bug */ + } + GB_SLEEP(gb, display, 8, 2); + if (!GB_is_cgb(gb)) { + add_object_from_index(gb, gb->oam_search_index); + gb->accessed_oam_row = (gb->oam_search_index & ~1) * 4 + 8; + } + if (gb->oam_search_index == 37) { + gb->vram_read_blocked = !GB_is_cgb(gb); + gb->vram_write_blocked = false; + gb->cgb_palettes_blocked = false; + gb->oam_write_blocked = GB_is_cgb(gb); + GB_STAT_update(gb); + } + } + gb->cycles_for_line = MODE2_LENGTH + 4; + + gb->accessed_oam_row = -1; + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 3; + gb->mode_for_interrupt = 3; + gb->vram_read_blocked = true; + gb->vram_write_blocked = true; + gb->cgb_palettes_blocked = false; + gb->oam_write_blocked = true; + gb->oam_read_blocked = true; + + GB_STAT_update(gb); + + + uint8_t idle_cycles = 3; + if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { + idle_cycles = 2; + } + gb->cycles_for_line += idle_cycles; + GB_SLEEP(gb, display, 10, idle_cycles); + + gb->cgb_palettes_blocked = true; + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 32, 2); + mode_3_start: + + fifo_clear(&gb->bg_fifo); + fifo_clear(&gb->oam_fifo); + /* Fill the FIFO with 8 pixels of "junk", it's going to be dropped anyway. */ + fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false); + /* Todo: find out actual access time of SCX */ + gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; + gb->lcd_x = 0; + + gb->fetcher_x = 0; + gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); + + + /* The actual rendering cycle */ + gb->fetcher_state = 0; + while (true) { + /* Handle window */ + /* TODO: It appears that WX checks if the window begins *next* pixel, not *this* pixel. For this reason, + WX=167 has no effect at all (It checks if the PPU X position is 161, which never happens) and WX=166 + has weird artifacts (It appears to activate the window during HBlank, as PPU X is temporarily 160 at + that point. The code should be updated to represent this, and this will fix the time travel hack in + WX's access conflict code. */ + + if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) { + bool should_activate_window = false; + if (gb->io_registers[GB_IO_WX] == 0) { + static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14}; + if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { + should_activate_window = true; + } + } + else if (gb->wx166_glitch) { + static const uint8_t scx_to_wx166_comparisons[] = {-8, -9, -10, -11, -12, -13, -14, -15}; + if (gb->position_in_line == scx_to_wx166_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { + should_activate_window = true; + } + } + else if (gb->io_registers[GB_IO_WX] < 166 + GB_is_cgb(gb)) { + if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) { + should_activate_window = true; + } + else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed) { + should_activate_window = true; + /* LCD-PPU horizontal desync! It only appears to happen on DMGs, but not all of them. + This doesn't seem to be CPU revision dependent, but most revisions */ + if ((gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_DMG_FAMILY && !GB_is_sgb(gb)) { + if (gb->lcd_x > 0) { + gb->lcd_x--; + } + } + } + } + + if (should_activate_window) { + gb->window_y++; + /* TODO: Verify fetcher access timings in this case */ + if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7)) { + gb->cycles_for_line++; + GB_SLEEP(gb, display, 42, 1); + } + gb->wx_triggered = true; + gb->window_tile_x = 0; + fifo_clear(&gb->bg_fifo); + gb->fetcher_state = 0; + gb->window_is_being_fetched = true; + } + else if (!GB_is_cgb(gb) && gb->io_registers[GB_IO_WX] == 166 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) { + gb->window_y++; + } + } + + /* TODO: What happens when WX=0? */ + if (!GB_is_cgb(gb) && gb->wx_triggered && !gb->window_is_being_fetched && + gb->fetcher_state == 0 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) ) { + // Insert a pixel right at the FIFO's end + gb->bg_fifo.read_end--; + gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1; + gb->bg_fifo.fifo[gb->bg_fifo.read_end] = (GB_fifo_item_t){0,}; + gb->window_is_being_fetched = false; + } + + /* Handle objects */ + /* When the sprite enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB. + On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */ + + while (gb->n_visible_objs != 0 && + (gb->position_in_line < 160 || gb->position_in_line >= (uint8_t)(-8)) && + gb->obj_comparators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) { + gb->n_visible_objs--; + } + + gb->during_object_fetch = true; + while (gb->n_visible_objs != 0 && + (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) && + gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) { + + while (gb->fetcher_state < 5 || fifo_size(&gb->bg_fifo) == 0) { + advance_fetcher_state_machine(gb); + gb->cycles_for_line++; + GB_SLEEP(gb, display, 27, 1); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + } + + /* Todo: Measure if penalty occurs before or after waiting for the fetcher. */ + if (gb->extra_penalty_for_sprite_at_0 != 0) { + if (gb->obj_comparators[gb->n_visible_objs - 1] == 0) { + gb->cycles_for_line += gb->extra_penalty_for_sprite_at_0; + GB_SLEEP(gb, display, 28, gb->extra_penalty_for_sprite_at_0); + gb->extra_penalty_for_sprite_at_0 = 0; + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + } + } + + /* TODO: Can this be deleted? { */ + advance_fetcher_state_machine(gb); + gb->cycles_for_line++; + GB_SLEEP(gb, display, 41, 1); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + /* } */ + + advance_fetcher_state_machine(gb); + + gb->cycles_for_line += 3; + GB_SLEEP(gb, display, 20, 3); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + + gb->object_low_line_address = get_object_line_address(gb, &objects[gb->visible_objs[gb->n_visible_objs - 1]]); + + gb->cycles_for_line++; + GB_SLEEP(gb, display, 39, 1); + if (gb->object_fetch_aborted) { + goto abort_fetching_object; + } + + gb->during_object_fetch = false; + gb->cycles_for_line++; + GB_SLEEP(gb, display, 40, 1); + + const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; + + uint16_t line_address = get_object_line_address(gb, object); + + uint8_t palette = (object->flags & 0x10) ? 1 : 0; + if (gb->cgb_mode) { + palette = object->flags & 0x7; + } + fifo_overlay_object_row(&gb->oam_fifo, + gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address], + gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1], + palette, + object->flags & 0x80, + gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0, + object->flags & 0x20); + + gb->n_visible_objs--; + } + +abort_fetching_object: + gb->object_fetch_aborted = false; + gb->during_object_fetch = false; + + render_pixel_if_possible(gb); + advance_fetcher_state_machine(gb); + + if (gb->position_in_line == 160) break; + gb->cycles_for_line++; + GB_SLEEP(gb, display, 21, 1); + } + + while (gb->lcd_x != 160 && !gb->disable_rendering && gb->screen && !gb->sgb) { + /* Oh no! The PPU and LCD desynced! Fill the rest of the line whith white. */ + uint32_t *dest = NULL; + if (gb->border_mode != GB_BORDER_ALWAYS) { + dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH; + } + else { + dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; + } + *dest = gb->background_palettes_rgb[0]; + gb->lcd_x++; + + } + + /* TODO: Verify timing */ + if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] == 166) { + gb->wx166_glitch = true; + } + else { + gb->wx166_glitch = false; + } + gb->wx_triggered = false; + + if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) { + gb->cycles_for_line++; + GB_SLEEP(gb, display, 30, 1); + } + + if (!gb->cgb_double_speed) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + } + + gb->cycles_for_line++; + GB_SLEEP(gb, display, 22, 1); + + gb->io_registers[GB_IO_STAT] &= ~3; + gb->mode_for_interrupt = 0; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + GB_STAT_update(gb); + + /* Todo: Measure this value */ + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 33, 2); + gb->cgb_palettes_blocked = !gb->cgb_double_speed; + + gb->cycles_for_line += 2; + GB_SLEEP(gb, display, 36, 2); + gb->cgb_palettes_blocked = false; + + gb->cycles_for_line += 8; + GB_SLEEP(gb, display, 25, 8); + + if (gb->hdma_on_hblank) { + gb->hdma_starting = true; + } + GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); + gb->mode_for_interrupt = 2; + + // Todo: unverified timing + gb->current_lcd_line++; + if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { + display_vblank(gb); + } + + if (gb->icd_hreset_callback) { + gb->icd_hreset_callback(gb); + } + } + gb->wx166_glitch = false; + /* Lines 144 - 152 */ + for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) { + gb->io_registers[GB_IO_LY] = gb->current_line; + gb->ly_for_comparison = -1; + GB_SLEEP(gb, display, 26, 2); + if (gb->current_line == LINES) { + gb->mode_for_interrupt = 2; + } + GB_STAT_update(gb); + GB_SLEEP(gb, display, 12, 2); + gb->ly_for_comparison = gb->current_line; + + if (gb->current_line == LINES) { + /* Entering VBlank state triggers the OAM interrupt */ + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 1; + gb->io_registers[GB_IO_IF] |= 1; + gb->mode_for_interrupt = 2; + GB_STAT_update(gb); + gb->mode_for_interrupt = 1; + GB_STAT_update(gb); + + if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { + if (GB_is_cgb(gb)) { + GB_timing_sync(gb); + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED; + } + else { + if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { + gb->is_odd_frame ^= true; + display_vblank(gb); + } + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + } + } + else { + if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { + gb->is_odd_frame ^= true; + display_vblank(gb); + } + if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) { + gb->cgb_repeated_a_frame = true; + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + } + else { + gb->cgb_repeated_a_frame = false; + } + } + } + + GB_STAT_update(gb); + GB_SLEEP(gb, display, 13, LINE_LENGTH - 4); + } + + /* TODO: Verified on SGB2 and CGB-E. Actual interrupt timings not tested. */ + /* Lines 153 */ + gb->io_registers[GB_IO_LY] = 153; + gb->ly_for_comparison = -1; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 14, (gb->model > GB_MODEL_CGB_C)? 4: 6); + + if (!GB_is_cgb(gb)) { + gb->io_registers[GB_IO_LY] = 0; + } + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 15, (gb->model > GB_MODEL_CGB_C)? 4: 2); + + gb->io_registers[GB_IO_LY] = 0; + gb->ly_for_comparison = (gb->model > GB_MODEL_CGB_C)? 153 : -1; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 16, 4); + + gb->ly_for_comparison = 0; + GB_STAT_update(gb); + GB_SLEEP(gb, display, 29, 12); /* Writing to LYC during this period on a CGB has side effects */ + GB_SLEEP(gb, display, 17, LINE_LENGTH - 24); + + + gb->current_line = 0; + /* Todo: verify timings */ + if ((gb->io_registers[GB_IO_LCDC] & 0x20) && + (gb->io_registers[GB_IO_WY] == 0)) { + gb->wy_triggered = true; + } + else { + gb->wy_triggered = false; + } + + // TODO: not the correct timing + gb->current_lcd_line = 0; + if (gb->icd_vreset_callback) { + gb->icd_vreset_callback(gb); + } + } +} + +void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + + switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) { + default: + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palettes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7)); + break; + } + + for (unsigned y = 0; y < 192; y++) { + for (unsigned x = 0; x < 256; x++) { + if (x >= 128 && !GB_is_cgb(gb)) { + *(dest++) = gb->background_palettes_rgb[0]; + continue; + } + uint16_t tile = (x % 128) / 8 + y / 8 * 16; + uint16_t tile_address = tile * 0x10 + (x >= 128? 0x2000 : 0); + uint8_t pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) | + ((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1); + + if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_BACKGROUND) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + else if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_OAM) { + pixel = ((gb->io_registers[palette_index == 0? GB_IO_OBP0 : GB_IO_OBP1] >> (pixel << 1)) & 3); + } + } + } + + + *(dest++) = palette[pixel]; + } + } +} + +void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + uint16_t map = 0x1800; + + switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) { + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palettes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_AUTO: + break; + } + + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) { + map = 0x1c00; + } + + if (tileset_type == GB_TILESET_AUTO) { + tileset_type = (gb->io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + } + + for (unsigned y = 0; y < 256; y++) { + for (unsigned x = 0; x < 256; x++) { + uint8_t tile = gb->vram[map + x/8 + y/8 * 32]; + uint16_t tile_address; + uint8_t attributes = 0; + + if (tileset_type == GB_TILESET_8800) { + tile_address = tile * 0x10; + } + else { + tile_address = (int8_t) tile * 0x10 + 0x1000; + } + + if (gb->cgb_mode) { + attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000]; + } + + if (attributes & 0x8) { + tile_address += 0x2000; + } + + uint8_t pixel = (((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 ] >> (((attributes & 0x20)? x : ~x)&7)) & 1 ) | + ((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 + 1] >> (((attributes & 0x20)? x : ~x)&7)) & 1) << 1); + + if (!gb->cgb_mode && (palette_type == GB_PALETTE_BACKGROUND || palette_type == GB_PALETTE_AUTO)) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + + if (palette) { + *(dest++) = palette[pixel]; + } + else { + *(dest++) = gb->background_palettes_rgb[(attributes & 7) * 4 + pixel]; + } + } + } +} + +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height) +{ + uint8_t count = 0; + *sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; + uint8_t oam_to_dest_index[40] = {0,}; + for (unsigned y = 0; y < LINES; y++) { + GB_object_t *sprite = (GB_object_t *) &gb->oam; + uint8_t sprites_in_line = 0; + for (uint8_t i = 0; i < 40; i++, sprite++) { + signed sprite_y = sprite->y - 16; + bool obscured = false; + // Is sprite not in this line? + if (sprite_y > y || sprite_y + *sprite_height <= y) continue; + if (++sprites_in_line == 11) obscured = true; + + GB_oam_info_t *info = NULL; + if (!oam_to_dest_index[i]) { + info = dest + count; + oam_to_dest_index[i] = ++count; + info->x = sprite->x; + info->y = sprite->y; + info->tile = *sprite_height == 16? sprite->tile & 0xFE : sprite->tile; + info->flags = sprite->flags; + info->obscured_by_line_limit = false; + info->oam_addr = 0xFE00 + i * sizeof(*sprite); + } + else { + info = dest + oam_to_dest_index[i] - 1; + } + info->obscured_by_line_limit |= obscured; + } + } + + for (unsigned i = 0; i < count; i++) { + uint16_t vram_address = dest[i].tile * 0x10; + uint8_t flags = dest[i].flags; + uint8_t palette = gb->cgb_mode? (flags & 7) : ((flags & 0x10)? 1 : 0); + if (GB_is_cgb(gb) && (flags & 0x8)) { + vram_address += 0x2000; + } + + for (unsigned y = 0; y < *sprite_height; y++) { + UNROLL + for (unsigned x = 0; x < 8; x++) { + uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) | + ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 ); + + if (!gb->cgb_mode) { + color = (gb->io_registers[palette? GB_IO_OBP1:GB_IO_OBP0] >> (color << 1)) & 3; + } + dest[i].image[((flags & 0x20)?7-x:x) + ((flags & 0x40)?*sprite_height - 1 -y:y) * 8] = gb->sprite_palettes_rgb[palette * 4 + color]; + } + vram_address += 2; + } + } + return count; +} + + +bool GB_is_odd_frame(GB_gameboy_t *gb) +{ + return gb->is_odd_frame; +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/display.h b/waterbox/bsnescore/bsnes/gb/Core/display.h new file mode 100644 index 0000000000..5bdeba8ddc --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/display.h @@ -0,0 +1,62 @@ +#ifndef display_h +#define display_h + +#include "gb.h" +#include +#include + +#ifdef GB_INTERNAL +void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); +void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); +void GB_STAT_update(GB_gameboy_t *gb); +void GB_lcd_off(GB_gameboy_t *gb); + +enum { + GB_OBJECT_PRIORITY_UNDEFINED, // For save state compatibility + GB_OBJECT_PRIORITY_X, + GB_OBJECT_PRIORITY_INDEX, +}; + +#endif + +typedef enum { + GB_PALETTE_NONE, + GB_PALETTE_BACKGROUND, + GB_PALETTE_OAM, + GB_PALETTE_AUTO, +} GB_palette_type_t; + +typedef enum { + GB_MAP_AUTO, + GB_MAP_9800, + GB_MAP_9C00, +} GB_map_type_t; + +typedef enum { + GB_TILESET_AUTO, + GB_TILESET_8800, + GB_TILESET_8000, +} GB_tileset_type_t; + +typedef struct { + uint32_t image[128]; + uint8_t x, y, tile, flags; + uint16_t oam_addr; + bool obscured_by_line_limit; +} GB_oam_info_t; + +typedef enum { + GB_COLOR_CORRECTION_DISABLED, + GB_COLOR_CORRECTION_CORRECT_CURVES, + GB_COLOR_CORRECTION_EMULATE_HARDWARE, + GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS, + GB_COLOR_CORRECTION_REDUCE_CONTRAST, +} GB_color_correction_mode_t; + +void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); +void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type); +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height); +uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border); +void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode); +bool GB_is_odd_frame(GB_gameboy_t *gb); +#endif /* display_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/gb.c b/waterbox/bsnescore/bsnes/gb/Core/gb.c new file mode 100644 index 0000000000..7325d79ae0 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/gb.c @@ -0,0 +1,1619 @@ +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#endif +#include "random.h" +#include "gb.h" + + +#ifdef GB_DISABLE_REWIND +#define GB_rewind_free(...) +#define GB_rewind_push(...) +#endif + + +static inline uint32_t state_magic(void) +{ + if (sizeof(bool) == 1) return 'SAME'; + return 'S4ME'; +} + +void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) +{ + char *string = NULL; + vasprintf(&string, fmt, args); + if (string) { + if (gb->log_callback) { + gb->log_callback(gb, string, attributes); + } + else { + /* Todo: Add ANSI escape sequences for attributed text */ + printf("%s", string); + } + } + free(string); +} + +void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + GB_attributed_logv(gb, attributes, fmt, args); + va_end(args); +} + +void GB_log(GB_gameboy_t *gb, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + GB_attributed_logv(gb, 0, fmt, args); + va_end(args); +} + +#ifndef GB_DISABLE_DEBUGGER +static char *default_input_callback(GB_gameboy_t *gb) +{ + char *expression = NULL; + size_t size = 0; + if (gb->debug_stopped) { + printf(">"); + } + + if (getline(&expression, &size, stdin) == -1) { + /* The user doesn't have STDIN or used ^D. We make sure the program keeps running. */ + GB_set_async_input_callback(gb, NULL); /* Disable async input */ + return strdup("c"); + } + + if (!expression) { + return strdup(""); + } + + size_t length = strlen(expression); + if (expression[length - 1] == '\n') { + expression[length - 1] = 0; + } + + if (expression[0] == '\x03') { + gb->debug_stopped = true; + free(expression); + return strdup(""); + } + return expression; +} + +static char *default_async_input_callback(GB_gameboy_t *gb) +{ +#ifndef _WIN32 + fd_set set; + FD_ZERO(&set); + FD_SET(STDIN_FILENO, &set); + struct timeval time = {0,}; + if (select(1, &set, NULL, NULL, &time) == 1) { + if (feof(stdin)) { + GB_set_async_input_callback(gb, NULL); /* Disable async input */ + return NULL; + } + return default_input_callback(gb); + } +#endif + return NULL; +} +#endif + +static void load_default_border(GB_gameboy_t *gb) +{ + if (gb->has_sgb_border) return; + + #define LOAD_BORDER() do { \ + memcpy(gb->borrowed_border.map, tilemap, sizeof(tilemap));\ + memcpy(gb->borrowed_border.palette, palette, sizeof(palette));\ + \ + /* Expand tileset */\ + for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) {\ + for (unsigned y = 0; y < 8; y++) {\ + for (unsigned x = 0; x < 8; x++) {\ + gb->borrowed_border.tiles[tile * 8 * 8 + y * 8 + x] =\ + (tiles[tile * 32 + y * 2 + 0] & (1 << (7 ^ x)) ? 1 : 0) |\ + (tiles[tile * 32 + y * 2 + 1] & (1 << (7 ^ x)) ? 2 : 0) |\ + (tiles[tile * 32 + y * 2 + 16] & (1 << (7 ^ x)) ? 4 : 0) |\ + (tiles[tile * 32 + y * 2 + 17] & (1 << (7 ^ x)) ? 8 : 0);\ + }\ + }\ + }\ + } while (false); + + if (gb->model == GB_MODEL_AGB) { + #include "graphics/agb_border.inc" + LOAD_BORDER(); + } + else if (GB_is_cgb(gb)) { + #include "graphics/cgb_border.inc" + LOAD_BORDER(); + } + else { + #include "graphics/dmg_border.inc" + LOAD_BORDER(); + } +} + +void GB_init(GB_gameboy_t *gb, GB_model_t model) +{ + memset(gb, 0, sizeof(*gb)); + gb->model = model; + if (GB_is_cgb(gb)) { + gb->ram = malloc(gb->ram_size = 0x1000 * 8); + gb->vram = malloc(gb->vram_size = 0x2000 * 2); + } + else { + gb->ram = malloc(gb->ram_size = 0x2000); + gb->vram = malloc(gb->vram_size = 0x2000); + } + +#ifndef GB_DISABLE_DEBUGGER + gb->input_callback = default_input_callback; + gb->async_input_callback = default_async_input_callback; +#endif + gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + gb->clock_multiplier = 1.0; + + if (model & GB_MODEL_NO_SFC_BIT) { + /* Disable time syncing. Timing should be done by the SFC emulator. */ + gb->turbo = true; + } + + GB_reset(gb); + load_default_border(gb); +} + +GB_model_t GB_get_model(GB_gameboy_t *gb) +{ + return gb->model; +} + +void GB_free(GB_gameboy_t *gb) +{ + gb->magic = 0; + if (gb->ram) { + free(gb->ram); + } + if (gb->vram) { + free(gb->vram); + } + if (gb->mbc_ram) { + free(gb->mbc_ram); + } + if (gb->rom) { + free(gb->rom); + } + if (gb->breakpoints) { + free(gb->breakpoints); + } + if (gb->sgb) { + free(gb->sgb); + } + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + } +#ifndef GB_DISABLE_DEBUGGER + GB_debugger_clear_symbols(gb); +#endif + GB_rewind_free(gb); +#ifndef GB_DISABLE_CHEATS + while (gb->cheats) { + GB_remove_cheat(gb, gb->cheats[0]); + } +#endif + memset(gb, 0, sizeof(*gb)); +} + +int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open boot ROM: %s.\n", strerror(errno)); + return errno; + } + fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f); + fclose(f); + return 0; +} + +void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size) +{ + if (size > sizeof(gb->boot_rom)) { + size = sizeof(gb->boot_rom); + } + memset(gb->boot_rom, 0xFF, sizeof(gb->boot_rom)); + memcpy(gb->boot_rom, buffer, size); +} + +void GB_borrow_sgb_border(GB_gameboy_t *gb) +{ + if (GB_is_sgb(gb)) return; + if (gb->border_mode != GB_BORDER_ALWAYS) return; + if (gb->tried_loading_sgb_border) return; + gb->tried_loading_sgb_border = true; + if (gb->rom && gb->rom[0x146] != 3) return; // Not an SGB game, nothing to borrow + if (!gb->boot_rom_load_callback) return; // Can't borrow a border without this callback + GB_gameboy_t sgb; + GB_init(&sgb, GB_MODEL_SGB); + sgb.cartridge_type = gb->cartridge_type; + sgb.rom = gb->rom; + sgb.rom_size = gb->rom_size; + sgb.turbo = true; + sgb.turbo_dont_skip = true; + // sgb.disable_rendering = true; + + /* Load the boot ROM using the existing gb object */ + typeof(gb->boot_rom) boot_rom_backup; + memcpy(boot_rom_backup, gb->boot_rom, sizeof(gb->boot_rom)); + gb->boot_rom_load_callback(gb, GB_BOOT_ROM_SGB); + memcpy(sgb.boot_rom, gb->boot_rom, sizeof(gb->boot_rom)); + memcpy(gb->boot_rom, boot_rom_backup, sizeof(gb->boot_rom)); + sgb.sgb->intro_animation = -1; + + for (unsigned i = 600; i--;) { + GB_run_frame(&sgb); + if (sgb.sgb->border_animation) { + gb->has_sgb_border = true; + memcpy(&gb->borrowed_border, &sgb.sgb->pending_border, sizeof(gb->borrowed_border)); + gb->borrowed_border.palette[0] = sgb.sgb->effective_palettes[0]; + break; + } + } + + sgb.rom = NULL; + sgb.rom_size = 0; + GB_free(&sgb); +} + +int GB_load_rom(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open ROM: %s.\n", strerror(errno)); + return errno; + } + fseek(f, 0, SEEK_END); + gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */ + /* And then round to a power of two */ + while (gb->rom_size & (gb->rom_size - 1)) { + /* I promise this works. */ + gb->rom_size |= gb->rom_size >> 1; + gb->rom_size++; + } + if (gb->rom_size == 0) { + gb->rom_size = 0x8000; + } + fseek(f, 0, SEEK_SET); + if (gb->rom) { + free(gb->rom); + } + gb->rom = malloc(gb->rom_size); + memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ + fread(gb->rom, 1, gb->rom_size, f); + fclose(f); + GB_configure_cart(gb); + return 0; +} + +int GB_load_isx(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open ISX file: %s.\n", strerror(errno)); + return errno; + } + char magic[4]; +#define READ(x) if (fread(&x, sizeof(x), 1, f) != 1) goto error + fread(magic, 1, sizeof(magic), f); + +#ifdef GB_BIG_ENDIAN + bool extended = *(uint32_t *)&magic == 'ISX '; +#else + bool extended = *(uint32_t *)&magic == __builtin_bswap32('ISX '); +#endif + + fseek(f, extended? 0x20 : 0, SEEK_SET); + + + uint8_t *old_rom = gb->rom; + uint32_t old_size = gb->rom_size; + gb->rom = NULL; + gb->rom_size = 0; + + while (true) { + uint8_t record_type = 0; + if (fread(&record_type, sizeof(record_type), 1, f) != 1) break; + switch (record_type) { + case 0x01: { // Binary + uint16_t bank; + uint16_t address; + uint16_t length; + uint8_t byte; + READ(byte); + bank = byte; + if (byte >= 0x80) { + READ(byte); + bank |= byte << 8; + } + + READ(address); +#ifdef GB_BIG_ENDIAN + address = __builtin_bswap16(address); +#endif + address &= 0x3FFF; + + READ(length); +#ifdef GB_BIG_ENDIAN + length = __builtin_bswap16(length); +#endif + + size_t needed_size = bank * 0x4000 + address + length; + if (needed_size > 1024 * 1024 * 32) goto error; + + if (gb->rom_size < needed_size) { + gb->rom = realloc(gb->rom, needed_size); + memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); + gb->rom_size = needed_size; + } + + if (fread(gb->rom + (bank * 0x4000 + address), length, 1, f) != 1) goto error; + + break; + } + + case 0x11: { // Extended Binary + uint32_t address; + uint32_t length; + + READ(address); +#ifdef GB_BIG_ENDIAN + address = __builtin_bswap32(address); +#endif + + READ(length); +#ifdef GB_BIG_ENDIAN + length = __builtin_bswap32(length); +#endif + size_t needed_size = address + length; + if (needed_size > 1024 * 1024 * 32) goto error; + + if (gb->rom_size < needed_size) { + gb->rom = realloc(gb->rom, needed_size); + memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); + gb->rom_size = needed_size; + } + + if (fread(gb->rom + address, length, 1, f) != 1) goto error; + + break; + } + + case 0x04: { // Symbol + uint16_t count; + uint8_t length; + char name[257]; + uint8_t flag; + uint16_t bank; + uint16_t address; + uint8_t byte; + READ(count); +#ifdef GB_BIG_ENDIAN + count = __builtin_bswap16(count); +#endif + while (count--) { + READ(length); + if (fread(name, length, 1, f) != 1) goto error; + name[length] = 0; + READ(flag); // unused + + READ(byte); + bank = byte; + if (byte >= 0x80) { + READ(byte); + bank |= byte << 8; + } + + READ(address); +#ifdef GB_BIG_ENDIAN + address = __builtin_bswap16(address); +#endif + GB_debugger_add_symbol(gb, bank, address, name); + } + break; + } + + case 0x14: { // Extended Binary + uint16_t count; + uint8_t length; + char name[257]; + uint8_t flag; + uint32_t address; + READ(count); +#ifdef GB_BIG_ENDIAN + count = __builtin_bswap16(count); +#endif + while (count--) { + READ(length); + if (fread(name, length + 1, 1, f) != 1) goto error; + name[length] = 0; + READ(flag); // unused + + READ(address); +#ifdef GB_BIG_ENDIAN + address = __builtin_bswap32(address); +#endif + // TODO: How to convert 32-bit addresses to Bank:Address? Needs to tell RAM and ROM apart + } + break; + } + + default: + goto done; + } + } +done:; +#undef READ + if (gb->rom_size == 0) goto error; + + size_t needed_size = (gb->rom_size + 0x3FFF) & ~0x3FFF; /* Round to bank */ + + /* And then round to a power of two */ + while (needed_size & (needed_size - 1)) { + /* I promise this works. */ + needed_size |= needed_size >> 1; + needed_size++; + } + + if (needed_size < 0x8000) { + needed_size = 0x8000; + } + + if (gb->rom_size < needed_size) { + gb->rom = realloc(gb->rom, needed_size); + memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size); + gb->rom_size = needed_size; + } + + GB_configure_cart(gb); + + // Fix a common wrong MBC error + if (gb->rom[0x147] == 3) { // MBC1 + RAM + Battery + bool needs_fix = false; + if (gb->rom_size >= 0x21 * 0x4000) { + for (unsigned i = 0x20 * 0x4000; i < 0x21 * 0x4000; i++) { + if (gb->rom[i]) { + needs_fix = true; + break; + } + } + } + if (!needs_fix && gb->rom_size >= 0x41 * 0x4000) { + for (unsigned i = 0x40 * 0x4000; i < 0x41 * 0x4000; i++) { + if (gb->rom[i]) { + needs_fix = true; + break; + } + } + } + if (!needs_fix && gb->rom_size >= 0x61 * 0x4000) { + for (unsigned i = 0x60 * 0x4000; i < 0x61 * 0x4000; i++) { + if (gb->rom[i]) { + needs_fix = true; + break; + } + } + } + if (needs_fix) { + gb->rom[0x147] = 0x10; // MBC3 + RTC + RAM + Battery + GB_configure_cart(gb); + gb->rom[0x147] = 0x3; + GB_log(gb, "ROM claims to use MBC1 but appears to require MBC3 or 5, assuming MBC3.\n"); + } + } + + if (old_rom) { + free(old_rom); + } + + return 0; +error: + GB_log(gb, "Invalid or unsupported ISX file.\n"); + if (gb->rom) { + free(gb->rom); + gb->rom = old_rom; + gb->rom_size = old_size; + } + fclose(f); + return -1; +} + +void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) +{ + gb->rom_size = (size + 0x3fff) & ~0x3fff; + while (gb->rom_size & (gb->rom_size - 1)) { + gb->rom_size |= gb->rom_size >> 1; + gb->rom_size++; + } + if (gb->rom_size == 0) { + gb->rom_size = 0x8000; + } + if (gb->rom) { + free(gb->rom); + } + gb->rom = malloc(gb->rom_size); + memset(gb->rom, 0xff, gb->rom_size); + memcpy(gb->rom, buffer, size); + GB_configure_cart(gb); +} + +typedef struct { + uint8_t seconds; + uint8_t padding1[3]; + uint8_t minutes; + uint8_t padding2[3]; + uint8_t hours; + uint8_t padding3[3]; + uint8_t days; + uint8_t padding4[3]; + uint8_t high; + uint8_t padding5[3]; +} GB_vba_rtc_time_t; + +typedef struct __attribute__((packed)) { + uint64_t last_rtc_second; + uint16_t minutes; + uint16_t days; + uint16_t alarm_minutes, alarm_days; + uint8_t alarm_enabled; +} GB_huc3_rtc_time_t; + +typedef union { + struct __attribute__((packed)) { + GB_rtc_time_t rtc_real; + time_t last_rtc_second; /* Platform specific endianess and size */ + } sameboy_legacy; + struct { + /* Used by VBA versions with 32-bit timestamp*/ + GB_vba_rtc_time_t rtc_real, rtc_latched; + uint32_t last_rtc_second; /* Always little endian */ + } vba32; + struct { + /* Used by BGB and VBA versions with 64-bit timestamp*/ + GB_vba_rtc_time_t rtc_real, rtc_latched; + uint64_t last_rtc_second; /* Always little endian */ + } vba64; +} GB_rtc_save_t; + +int GB_save_battery_size(GB_gameboy_t *gb) +{ + if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t); + } + GB_rtc_save_t rtc_save_size; + return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); +} + +int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) +{ + if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ + + if (size < GB_save_battery_size(gb)) return EIO; + + memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size); + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + buffer += gb->mbc_ram_size; + +#ifdef GB_BIG_ENDIAN + GB_huc3_rtc_time_t rtc_save = { + __builtin_bswap64(gb->last_rtc_second), + __builtin_bswap16(gb->huc3_minutes), + __builtin_bswap16(gb->huc3_days), + __builtin_bswap16(gb->huc3_alarm_minutes), + __builtin_bswap16(gb->huc3_alarm_days), + gb->huc3_alarm_enabled, + }; +#else + GB_huc3_rtc_time_t rtc_save = { + gb->last_rtc_second, + gb->huc3_minutes, + gb->huc3_days, + gb->huc3_alarm_minutes, + gb->huc3_alarm_days, + gb->huc3_alarm_enabled, + }; +#endif + memcpy(buffer, &rtc_save, sizeof(rtc_save)); + } + else if (gb->cartridge_type->has_rtc) { + GB_rtc_save_t rtc_save = {{{{0,}},},}; + rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; + rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; + rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours; + rtc_save.vba64.rtc_real.days = gb->rtc_real.days; + rtc_save.vba64.rtc_real.high = gb->rtc_real.high; + rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds; + rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes; + rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours; + rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; + rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; +#ifdef GB_BIG_ENDIAN + rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second); +#else + rtc_save.vba64.last_rtc_second = gb->last_rtc_second; +#endif + memcpy(buffer + gb->mbc_ram_size, &rtc_save.vba64, sizeof(rtc_save.vba64)); + } + + errno = 0; + return errno; +} + +int GB_save_battery(GB_gameboy_t *gb, const char *path) +{ + if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ + FILE *f = fopen(path, "wb"); + if (!f) { + GB_log(gb, "Could not open battery save: %s.\n", strerror(errno)); + return errno; + } + + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + fclose(f); + return EIO; + } + if (gb->cartridge_type->mbc_type == GB_HUC3) { +#ifdef GB_BIG_ENDIAN + GB_huc3_rtc_time_t rtc_save = { + __builtin_bswap64(gb->last_rtc_second), + __builtin_bswap16(gb->huc3_minutes), + __builtin_bswap16(gb->huc3_days), + __builtin_bswap16(gb->huc3_alarm_minutes), + __builtin_bswap16(gb->huc3_alarm_days), + gb->huc3_alarm_enabled, + }; +#else + GB_huc3_rtc_time_t rtc_save = { + gb->last_rtc_second, + gb->huc3_minutes, + gb->huc3_days, + gb->huc3_alarm_minutes, + gb->huc3_alarm_days, + gb->huc3_alarm_enabled, + }; +#endif + + if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + fclose(f); + return EIO; + } + } + else if (gb->cartridge_type->has_rtc) { + GB_rtc_save_t rtc_save = {{{{0,}},},}; + rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; + rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; + rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours; + rtc_save.vba64.rtc_real.days = gb->rtc_real.days; + rtc_save.vba64.rtc_real.high = gb->rtc_real.high; + rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds; + rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes; + rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours; + rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; + rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; +#ifdef GB_BIG_ENDIAN + rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second); +#else + rtc_save.vba64.last_rtc_second = gb->last_rtc_second; +#endif + if (fwrite(&rtc_save.vba64, 1, sizeof(rtc_save.vba64), f) != sizeof(rtc_save.vba64)) { + fclose(f); + return EIO; + } + + } + + errno = 0; + fclose(f); + return errno; +} + +void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) +{ + memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size)); + if (size <= gb->mbc_ram_size) { + goto reset_rtc; + } + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + GB_huc3_rtc_time_t rtc_save; + if (size - gb->mbc_ram_size < sizeof(rtc_save)) { + goto reset_rtc; + } + memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save)); +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); + gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); + gb->huc3_days = __builtin_bswap16(rtc_save.days); + gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); + gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; +#else + gb->last_rtc_second = rtc_save.last_rtc_second; + gb->huc3_minutes = rtc_save.minutes; + gb->huc3_days = rtc_save.days; + gb->huc3_alarm_minutes = rtc_save.alarm_minutes; + gb->huc3_alarm_days = rtc_save.alarm_days; + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; +#endif + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } + + GB_rtc_save_t rtc_save; + memcpy(&rtc_save, buffer + gb->mbc_ram_size, MIN(sizeof(rtc_save), size)); + switch (size - gb->mbc_ram_size) { + case sizeof(rtc_save.sameboy_legacy): + memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second; + break; + + case sizeof(rtc_save.vba32): + gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba32.rtc_real.days; + gb->rtc_real.high = rtc_save.vba32.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba32.last_rtc_second; +#endif + break; + + case sizeof(rtc_save.vba64): + gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba64.rtc_real.days; + gb->rtc_real.high = rtc_save.vba64.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba64.last_rtc_second; +#endif + break; + + default: + goto reset_rtc; + } + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + + if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time, + so if the value we read is lower it means it wasn't + really RTC data. */ + goto reset_rtc; + } + goto exit; +reset_rtc: + gb->last_rtc_second = time(NULL); + gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ + gb->huc3_days = 0xFFFF; + gb->huc3_minutes = 0xFFF; + gb->huc3_alarm_enabled = false; +exit: + return; +} + +/* Loading will silently stop if the format is incomplete */ +void GB_load_battery(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + return; + } + + if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + goto reset_rtc; + } + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + GB_huc3_rtc_time_t rtc_save; + if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + goto reset_rtc; + } +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); + gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); + gb->huc3_days = __builtin_bswap16(rtc_save.days); + gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); + gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; +#else + gb->last_rtc_second = rtc_save.last_rtc_second; + gb->huc3_minutes = rtc_save.minutes; + gb->huc3_days = rtc_save.days; + gb->huc3_alarm_minutes = rtc_save.alarm_minutes; + gb->huc3_alarm_days = rtc_save.alarm_days; + gb->huc3_alarm_enabled = rtc_save.alarm_enabled; +#endif + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } + + GB_rtc_save_t rtc_save; + switch (fread(&rtc_save, 1, sizeof(rtc_save), f)) { + case sizeof(rtc_save.sameboy_legacy): + memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second; + break; + + case sizeof(rtc_save.vba32): + gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba32.rtc_real.days; + gb->rtc_real.high = rtc_save.vba32.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba32.last_rtc_second; +#endif + break; + + case sizeof(rtc_save.vba64): + gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba64.rtc_real.days; + gb->rtc_real.high = rtc_save.vba64.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba64.last_rtc_second; +#endif + break; + + default: + goto reset_rtc; + } + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + + if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time, + so if the value we read is lower it means it wasn't + really RTC data. */ + goto reset_rtc; + } + goto exit; +reset_rtc: + gb->last_rtc_second = time(NULL); + gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ + gb->huc3_days = 0xFFFF; + gb->huc3_minutes = 0xFFF; + gb->huc3_alarm_enabled = false; +exit: + fclose(f); + return; +} + +uint8_t GB_run(GB_gameboy_t *gb) +{ + gb->vblank_just_occured = false; + + if (gb->sgb && gb->sgb->intro_animation < 140) { + /* On the SGB, the GB is halted after finishing the boot ROM. + Then, after the boot animation is almost done, it's reset. + Since the SGB HLE does not perform any header validity checks, + we just halt the CPU (with hacky code) until the correct time. + This ensures the Nintendo logo doesn't flash on screen, and + the game does "run in background" while the animation is playing. */ + GB_display_run(gb, 228); + gb->cycles_since_last_sync += 228; + return 228; + } + + GB_debugger_run(gb); + gb->cycles_since_run = 0; + GB_cpu_run(gb); + if (gb->vblank_just_occured) { + GB_rtc_run(gb); + GB_debugger_handle_async_commands(gb); + GB_rewind_push(gb); + } + return gb->cycles_since_run; +} + +uint64_t GB_run_frame(GB_gameboy_t *gb) +{ + /* Configure turbo temporarily, the user wants to handle FPS capping manually. */ + bool old_turbo = gb->turbo; + bool old_dont_skip = gb->turbo_dont_skip; + gb->turbo = true; + gb->turbo_dont_skip = true; + + gb->cycles_since_last_sync = 0; + while (true) { + GB_run(gb); + if (gb->vblank_just_occured) { + break; + } + } + gb->turbo = old_turbo; + gb->turbo_dont_skip = old_dont_skip; + return gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ +} + +void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) +{ + gb->screen = output; +} + +void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback) +{ + gb->vblank_callback = callback; +} + +void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback) +{ + gb->log_callback = callback; +} + +void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) +{ +#ifndef GB_DISABLE_DEBUGGER + if (gb->input_callback == default_input_callback) { + gb->async_input_callback = NULL; + } + gb->input_callback = callback; +#endif +} + +void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) +{ +#ifndef GB_DISABLE_DEBUGGER + gb->async_input_callback = callback; +#endif +} + +const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xaa, 0xaa, 0xaa}, {0xff, 0xff, 0xff}, {0xff, 0xff, 0xff}}}; +const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xa5, 0x63}, {0xc6, 0xde, 0x8c}, {0xd2, 0xe6, 0xa6}}}; +const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0e}, {0x3a, 0x4c, 0x3a}, {0x81, 0x8d, 0x66}, {0xc2, 0xce, 0x93}, {0xcf, 0xda, 0xac}}}; +const GB_palette_t GB_PALETTE_GBL = {{{0x0a, 0x1c, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xb4, 0x95}, {0x7f, 0xe2, 0xc3}, {0x91, 0xea, 0xd0}}}; + +static void update_dmg_palette(GB_gameboy_t *gb) +{ + const GB_palette_t *palette = gb->dmg_palette ?: &GB_PALETTE_GREY; + if (gb->rgb_encode_callback && !GB_is_cgb(gb)) { + gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = + gb->rgb_encode_callback(gb, palette->colors[3].r, palette->colors[3].g, palette->colors[3].b); + gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = + gb->rgb_encode_callback(gb, palette->colors[2].r, palette->colors[2].g, palette->colors[2].b); + gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] = + gb->rgb_encode_callback(gb, palette->colors[1].r, palette->colors[1].g, palette->colors[1].b); + gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = + gb->rgb_encode_callback(gb, palette->colors[0].r, palette->colors[0].g, palette->colors[0].b); + + // LCD off color + gb->background_palettes_rgb[4] = + gb->rgb_encode_callback(gb, palette->colors[4].r, palette->colors[4].g, palette->colors[4].b); + } +} + +void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette) +{ + gb->dmg_palette = palette; + update_dmg_palette(gb); +} + +void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback) +{ + + gb->rgb_encode_callback = callback; + update_dmg_palette(gb); + + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, true, i * 2); + GB_palette_changed(gb, false, i * 2); + } +} + +void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) +{ + gb->infrared_callback = callback; +} + +void GB_set_infrared_input(GB_gameboy_t *gb, bool state) +{ + gb->infrared_input = state; + gb->cycles_since_input_ir_change = 0; + gb->ir_queue_length = 0; +} + +void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, uint64_t cycles_after_previous_change) +{ + if (gb->ir_queue_length == GB_MAX_IR_QUEUE) { + GB_log(gb, "IR Queue is full\n"); + return; + } + gb->ir_queue[gb->ir_queue_length++] = (GB_ir_queue_item_t){state, cycles_after_previous_change}; +} + +void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback) +{ + gb->rumble_callback = callback; +} + +void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback) +{ + gb->serial_transfer_bit_start_callback = callback; +} + +void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback) +{ + gb->serial_transfer_bit_end_callback = callback; +} + +bool GB_serial_get_data_bit(GB_gameboy_t *gb) +{ + if (gb->io_registers[GB_IO_SC] & 1) { + /* Internal Clock */ + GB_log(gb, "Serial read request while using internal clock. \n"); + return 0xFF; + } + return gb->io_registers[GB_IO_SB] & 0x80; +} +void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data) +{ + if (gb->io_registers[GB_IO_SC] & 1) { + /* Internal Clock */ + GB_log(gb, "Serial write request while using internal clock. \n"); + return; + } + gb->io_registers[GB_IO_SB] <<= 1; + gb->io_registers[GB_IO_SB] |= data; + gb->serial_count++; + if (gb->serial_count == 8) { + gb->io_registers[GB_IO_IF] |= 8; + gb->serial_count = 0; + } +} + +void GB_disconnect_serial(GB_gameboy_t *gb) +{ + gb->serial_transfer_bit_start_callback = NULL; + gb->serial_transfer_bit_end_callback = NULL; + + /* Reset any internally-emulated device. */ + memset(&gb->printer, 0, sizeof(gb->printer)); + memset(&gb->workboy, 0, sizeof(gb->workboy)); +} + +bool GB_is_inited(GB_gameboy_t *gb) +{ + return gb->magic == state_magic(); +} + +bool GB_is_cgb(GB_gameboy_t *gb) +{ + return (gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY; +} + +bool GB_is_sgb(GB_gameboy_t *gb) +{ + return (gb->model & ~GB_MODEL_PAL_BIT & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB || (gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB2; +} + +bool GB_is_hle_sgb(GB_gameboy_t *gb) +{ + return (gb->model & ~GB_MODEL_PAL_BIT) == GB_MODEL_SGB || gb->model == GB_MODEL_SGB2; +} + +void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip) +{ + gb->turbo = on; + gb->turbo_dont_skip = no_frame_skip; +} + +void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled) +{ + gb->disable_rendering = disabled; +} + +void *GB_get_user_data(GB_gameboy_t *gb) +{ + return gb->user_data; +} + +void GB_set_user_data(GB_gameboy_t *gb, void *data) +{ + gb->user_data = data; +} + +static void reset_ram(GB_gameboy_t *gb) +{ + switch (gb->model) { + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: /* Unverified */ + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = GB_random(); + } + break; + + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = GB_random(); + if (i & 0x100) { + gb->ram[i] &= GB_random(); + } + else { + gb->ram[i] |= GB_random(); + } + } + break; + + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = 0x55; + gb->ram[i] ^= GB_random() & GB_random() & GB_random(); + } + break; + + case GB_MODEL_CGB_C: + for (unsigned i = 0; i < gb->ram_size; i++) { + if ((i & 0x808) == 0x800 || (i & 0x808) == 0x008) { + gb->ram[i] = 0; + } + else { + gb->ram[i] = GB_random() | GB_random() | GB_random() | GB_random(); + } + } + break; + } + + /* HRAM */ + switch (gb->model) { + case GB_MODEL_CGB_C: + // case GB_MODEL_CGB_D: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: + for (unsigned i = 0; i < sizeof(gb->hram); i++) { + gb->hram[i] = GB_random(); + } + break; + + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + for (unsigned i = 0; i < sizeof(gb->hram); i++) { + if (i & 1) { + gb->hram[i] = GB_random() | GB_random() | GB_random(); + } + else { + gb->hram[i] = GB_random() & GB_random() & GB_random(); + } + } + break; + } + + /* OAM */ + switch (gb->model) { + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: + /* Zero'd out by boot ROM anyway*/ + break; + + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified */ + case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + for (unsigned i = 0; i < 8; i++) { + if (i & 2) { + gb->oam[i] = GB_random() & GB_random() & GB_random(); + } + else { + gb->oam[i] = GB_random() | GB_random() | GB_random(); + } + } + for (unsigned i = 8; i < sizeof(gb->oam); i++) { + gb->oam[i] = gb->oam[i - 8]; + } + break; + } + + /* Wave RAM */ + switch (gb->model) { + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: + /* Initialized by CGB-A and newer, 0s in CGB-0*/ + break; + + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_PAL: /* Unverified */ + case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ + case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: { + uint8_t temp; + for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) { + if (i & 1) { + temp = GB_random() & GB_random() & GB_random(); + } + else { + temp = GB_random() | GB_random() | GB_random(); + } + gb->apu.wave_channel.wave_form[i * 2] = temp >> 4; + gb->apu.wave_channel.wave_form[i * 2 + 1] = temp & 0xF; + gb->io_registers[GB_IO_WAV_START + i] = temp; + + } + break; + } + } + + for (unsigned i = 0; i < sizeof(gb->extra_oam); i++) { + gb->extra_oam[i] = GB_random(); + } + + if (GB_is_cgb(gb)) { + for (unsigned i = 0; i < 64; i++) { + gb->background_palettes_data[i] = GB_random(); /* Doesn't really matter as the boot ROM overrides it anyway*/ + gb->sprite_palettes_data[i] = GB_random(); + } + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, true, i * 2); + GB_palette_changed(gb, false, i * 2); + } + } +} + +static void request_boot_rom(GB_gameboy_t *gb) +{ + if (gb->boot_rom_load_callback) { + GB_boot_rom_t type = 0; + switch (gb->model) { + case GB_MODEL_DMG_B: + type = GB_BOOT_ROM_DMG; + break; + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: + type = GB_BOOT_ROM_SGB; + break; + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + type = GB_BOOT_ROM_SGB2; + break; + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_E: + type = GB_BOOT_ROM_CGB; + break; + case GB_MODEL_AGB: + type = GB_BOOT_ROM_AGB; + break; + } + gb->boot_rom_load_callback(gb, type); + } +} + +void GB_reset(GB_gameboy_t *gb) +{ + uint32_t mbc_ram_size = gb->mbc_ram_size; + GB_model_t model = gb->model; + memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved)); + gb->model = model; + gb->version = GB_STRUCT_VERSION; + + gb->mbc_rom_bank = 1; + gb->last_rtc_second = time(NULL); + gb->cgb_ram_bank = 1; + gb->io_registers[GB_IO_JOYP] = 0xCF; + gb->mbc_ram_size = mbc_ram_size; + if (GB_is_cgb(gb)) { + gb->ram_size = 0x1000 * 8; + gb->vram_size = 0x2000 * 2; + memset(gb->vram, 0, gb->vram_size); + gb->cgb_mode = true; + gb->object_priority = GB_OBJECT_PRIORITY_INDEX; + } + else { + gb->ram_size = 0x2000; + gb->vram_size = 0x2000; + memset(gb->vram, 0, gb->vram_size); + gb->object_priority = GB_OBJECT_PRIORITY_X; + + update_dmg_palette(gb); + } + reset_ram(gb); + + /* The serial interrupt always occur on the 0xF7th cycle of every 0x100 cycle since boot. */ + gb->serial_cycles = 0x100-0xF7; + gb->io_registers[GB_IO_SC] = 0x7E; + + /* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */ + gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF; + + gb->accessed_oam_row = -1; + + + if (GB_is_hle_sgb(gb)) { + if (!gb->sgb) { + gb->sgb = malloc(sizeof(*gb->sgb)); + } + memset(gb->sgb, 0, sizeof(*gb->sgb)); + memset(gb->sgb_intro_jingle_phases, 0, sizeof(gb->sgb_intro_jingle_phases)); + gb->sgb_intro_sweep_phase = 0; + gb->sgb_intro_sweep_previous_sample = 0; + gb->sgb->intro_animation = -10; + + gb->sgb->player_count = 1; + GB_sgb_load_default_data(gb); + + } + else { + if (gb->sgb) { + free(gb->sgb); + gb->sgb = NULL; + } + } + + /* Todo: Ugly, fixme, see comment in the timer state machine */ + gb->div_state = 3; + + GB_apu_update_cycles_per_sample(gb); + + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + gb->nontrivial_jump_state = NULL; + } + + gb->magic = state_magic(); + request_boot_rom(gb); +} + +void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) +{ + gb->model = model; + if (GB_is_cgb(gb)) { + gb->ram = realloc(gb->ram, gb->ram_size = 0x1000 * 8); + gb->vram = realloc(gb->vram, gb->vram_size = 0x2000 * 2); + } + else { + gb->ram = realloc(gb->ram, gb->ram_size = 0x2000); + gb->vram = realloc(gb->vram, gb->vram_size = 0x2000); + } + GB_rewind_free(gb); + GB_reset(gb); + load_default_border(gb); +} + +void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank) +{ + /* Set size and bank to dummy pointers if not set */ + size_t dummy_size; + uint16_t dummy_bank; + if (!size) { + size = &dummy_size; + } + + if (!bank) { + bank = &dummy_bank; + } + + + switch (access) { + case GB_DIRECT_ACCESS_ROM: + *size = gb->rom_size; + *bank = gb->mbc_rom_bank; + return gb->rom; + case GB_DIRECT_ACCESS_RAM: + *size = gb->ram_size; + *bank = gb->cgb_ram_bank; + return gb->ram; + case GB_DIRECT_ACCESS_CART_RAM: + *size = gb->mbc_ram_size; + *bank = gb->mbc_ram_bank; + return gb->mbc_ram; + case GB_DIRECT_ACCESS_VRAM: + *size = gb->vram_size; + *bank = gb->cgb_vram_bank; + return gb->vram; + case GB_DIRECT_ACCESS_HRAM: + *size = sizeof(gb->hram); + *bank = 0; + return &gb->hram; + case GB_DIRECT_ACCESS_IO: + *size = sizeof(gb->io_registers); + *bank = 0; + return &gb->io_registers; + case GB_DIRECT_ACCESS_BOOTROM: + *size = GB_is_cgb(gb)? sizeof(gb->boot_rom) : 0x100; + *bank = 0; + return &gb->boot_rom; + case GB_DIRECT_ACCESS_OAM: + *size = sizeof(gb->oam); + *bank = 0; + return &gb->oam; + case GB_DIRECT_ACCESS_BGP: + *size = sizeof(gb->background_palettes_data); + *bank = 0; + return &gb->background_palettes_data; + case GB_DIRECT_ACCESS_OBP: + *size = sizeof(gb->sprite_palettes_data); + *bank = 0; + return &gb->sprite_palettes_data; + case GB_DIRECT_ACCESS_IE: + *size = sizeof(gb->interrupt_enable); + *bank = 0; + return &gb->interrupt_enable; + default: + *size = 0; + *bank = 0; + return NULL; + } +} + +void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) +{ + gb->clock_multiplier = multiplier; + GB_apu_update_cycles_per_sample(gb); +} + +uint32_t GB_get_clock_rate(GB_gameboy_t *gb) +{ + if (gb->model & GB_MODEL_PAL_BIT) { + return SGB_PAL_FREQUENCY * gb->clock_multiplier; + } + if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { + return SGB_NTSC_FREQUENCY * gb->clock_multiplier; + } + return CPU_FREQUENCY * gb->clock_multiplier; +} + +void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode) +{ + if (gb->border_mode > GB_BORDER_ALWAYS) return; + gb->border_mode = border_mode; +} + +unsigned GB_get_screen_width(GB_gameboy_t *gb) +{ + switch (gb->border_mode) { + default: + case GB_BORDER_SGB: + return GB_is_hle_sgb(gb)? 256 : 160; + case GB_BORDER_NEVER: + return 160; + case GB_BORDER_ALWAYS: + return 256; + } +} + +unsigned GB_get_screen_height(GB_gameboy_t *gb) +{ + switch (gb->border_mode) { + default: + case GB_BORDER_SGB: + return GB_is_hle_sgb(gb)? 224 : 144; + case GB_BORDER_NEVER: + return 144; + case GB_BORDER_ALWAYS: + return 224; + } +} + +unsigned GB_get_player_count(GB_gameboy_t *gb) +{ + return GB_is_hle_sgb(gb)? gb->sgb->player_count : 1; +} + +void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback) +{ + gb->update_input_hint_callback = callback; +} + +double GB_get_usual_frame_rate(GB_gameboy_t *gb) +{ + return GB_get_clock_rate(gb) / (double)LCDC_PERIOD; +} + +void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback) +{ + gb->joyp_write_callback = callback; +} + +void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback) +{ + gb->icd_pixel_callback = callback; +} + +void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback) +{ + gb->icd_hreset_callback = callback; +} + + +void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback) +{ + gb->icd_vreset_callback = callback; +} + +void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback) +{ + gb->boot_rom_load_callback = callback; + request_boot_rom(gb); +} + +unsigned GB_time_to_alarm(GB_gameboy_t *gb) +{ + if (gb->cartridge_type->mbc_type != GB_HUC3) return 0; + if (!gb->huc3_alarm_enabled) return 0; + if (!(gb->huc3_alarm_days & 0x2000)) return 0; + unsigned current_time = (gb->huc3_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_minutes * 60 + (time(NULL) % 60); + unsigned alarm_time = (gb->huc3_alarm_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_alarm_minutes * 60; + if (current_time > alarm_time) return 0; + return alarm_time - current_time; +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/gb.h b/waterbox/bsnescore/bsnes/gb/Core/gb.h new file mode 100644 index 0000000000..9043936851 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/gb.h @@ -0,0 +1,816 @@ +#ifndef GB_h +#define GB_h +#define typeof __typeof__ +#include +#include +#include + +#include "gb_struct_def.h" +#include "save_state.h" + +#include "apu.h" +#include "camera.h" +#include "debugger.h" +#include "display.h" +#include "joypad.h" +#include "mbc.h" +#include "memory.h" +#include "printer.h" +#include "timing.h" +#include "rewind.h" +#include "sm83_cpu.h" +#include "symbol_hash.h" +#include "sgb.h" +#include "cheats.h" +#include "rumble.h" +#include "workboy.h" + +#define GB_STRUCT_VERSION 13 + +#define GB_MODEL_FAMILY_MASK 0xF00 +#define GB_MODEL_DMG_FAMILY 0x000 +#define GB_MODEL_MGB_FAMILY 0x100 +#define GB_MODEL_CGB_FAMILY 0x200 +#define GB_MODEL_PAL_BIT 0x1000 +#define GB_MODEL_NO_SFC_BIT 0x2000 + +#ifdef GB_INTERNAL +#if __clang__ +#define UNROLL _Pragma("unroll") +#elif __GNUC__ >= 8 +#define UNROLL _Pragma("GCC unroll 8") +#else +#define UNROLL +#endif + +#endif + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define GB_BIG_ENDIAN +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define GB_LITTLE_ENDIAN +#else +#error Unable to detect endianess +#endif + +#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) +#define __builtin_bswap16(x) ({ typeof(x) _x = (x); _x >> 8 | _x << 8; }) +#endif + +typedef struct { + struct { + uint8_t r, g, b; + } colors[5]; +} GB_palette_t; + +extern const GB_palette_t GB_PALETTE_GREY; +extern const GB_palette_t GB_PALETTE_DMG; +extern const GB_palette_t GB_PALETTE_MGB; +extern const GB_palette_t GB_PALETTE_GBL; + +typedef union { + struct { + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t days; + uint8_t high; + }; + uint8_t data[5]; +} GB_rtc_time_t; + +typedef enum { + // GB_MODEL_DMG_0 = 0x000, + // GB_MODEL_DMG_A = 0x001, + GB_MODEL_DMG_B = 0x002, + // GB_MODEL_DMG_C = 0x003, + GB_MODEL_SGB = 0x004, + GB_MODEL_SGB_NTSC = GB_MODEL_SGB, + GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT, + GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT, + GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC, + GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT, + // GB_MODEL_MGB = 0x100, + GB_MODEL_SGB2 = 0x101, + GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, + // GB_MODEL_CGB_0 = 0x200, + // GB_MODEL_CGB_A = 0x201, + // GB_MODEL_CGB_B = 0x202, + GB_MODEL_CGB_C = 0x203, + // GB_MODEL_CGB_D = 0x204, + GB_MODEL_CGB_E = 0x205, + GB_MODEL_AGB = 0x206, +} GB_model_t; + +enum { + GB_REGISTER_AF, + GB_REGISTER_BC, + GB_REGISTER_DE, + GB_REGISTER_HL, + GB_REGISTER_SP, + GB_REGISTERS_16_BIT /* Count */ +}; + +/* Todo: Actually use these! */ +enum { + GB_CARRY_FLAG = 16, + GB_HALF_CARRY_FLAG = 32, + GB_SUBTRACT_FLAG = 64, + GB_ZERO_FLAG = 128, +}; + +typedef enum { + GB_BORDER_SGB, + GB_BORDER_NEVER, + GB_BORDER_ALWAYS, +} GB_border_mode_t; + +#define GB_MAX_IR_QUEUE 256 + +enum { + /* Joypad and Serial */ + GB_IO_JOYP = 0x00, // Joypad (R/W) + GB_IO_SB = 0x01, // Serial transfer data (R/W) + GB_IO_SC = 0x02, // Serial Transfer Control (R/W) + + /* Missing */ + + /* Timers */ + GB_IO_DIV = 0x04, // Divider Register (R/W) + GB_IO_TIMA = 0x05, // Timer counter (R/W) + GB_IO_TMA = 0x06, // Timer Modulo (R/W) + GB_IO_TAC = 0x07, // Timer Control (R/W) + + /* Missing */ + + GB_IO_IF = 0x0f, // Interrupt Flag (R/W) + + /* Sound */ + GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W) + GB_IO_NR11 = 0x11, // Channel 1 Sound length/Wave pattern duty (R/W) + GB_IO_NR12 = 0x12, // Channel 1 Volume Envelope (R/W) + GB_IO_NR13 = 0x13, // Channel 1 Frequency lo (Write Only) + GB_IO_NR14 = 0x14, // Channel 1 Frequency hi (R/W) + /* NR20 does not exist */ + GB_IO_NR21 = 0x16, // Channel 2 Sound Length/Wave Pattern Duty (R/W) + GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W) + GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W) + GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W) + GB_IO_NR30 = 0x1a, // Channel 3 Sound on/off (R/W) + GB_IO_NR31 = 0x1b, // Channel 3 Sound Length + GB_IO_NR32 = 0x1c, // Channel 3 Select output level (R/W) + GB_IO_NR33 = 0x1d, // Channel 3 Frequency's lower data (W) + GB_IO_NR34 = 0x1e, // Channel 3 Frequency's higher data (R/W) + /* NR40 does not exist */ + GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W) + GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W) + GB_IO_NR43 = 0x22, // Channel 4 Polynomial Counter (R/W) + GB_IO_NR44 = 0x23, // Channel 4 Counter/consecutive, Inital (R/W) + GB_IO_NR50 = 0x24, // Channel control / ON-OFF / Volume (R/W) + GB_IO_NR51 = 0x25, // Selection of Sound output terminal (R/W) + GB_IO_NR52 = 0x26, // Sound on/off + + /* Missing */ + + GB_IO_WAV_START = 0x30, // Wave pattern start + GB_IO_WAV_END = 0x3f, // Wave pattern end + + /* Graphics */ + GB_IO_LCDC = 0x40, // LCD Control (R/W) + GB_IO_STAT = 0x41, // LCDC Status (R/W) + GB_IO_SCY = 0x42, // Scroll Y (R/W) + GB_IO_SCX = 0x43, // Scroll X (R/W) + GB_IO_LY = 0x44, // LCDC Y-Coordinate (R) + GB_IO_LYC = 0x45, // LY Compare (R/W) + GB_IO_DMA = 0x46, // DMA Transfer and Start Address (W) + GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only + GB_IO_OBP0 = 0x48, // Object Palette 0 Data (R/W) - Non CGB Mode Only + GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only + GB_IO_WY = 0x4a, // Window Y Position (R/W) + GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W) + // Has some undocumented compatibility flags written at boot. + // Unfortunately it is not readable or writable after boot has finished, so research of this + // register is quite limited. The value written to this register, however, can be controlled + // in some cases. + GB_IO_KEY0 = 0x4c, + + /* General CGB features */ + GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch + + /* Missing */ + + GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank + GB_IO_BANK = 0x50, // Write to disable the BIOS mapping + + /* CGB DMA */ + GB_IO_HDMA1 = 0x51, // CGB Mode Only - New DMA Source, High + GB_IO_HDMA2 = 0x52, // CGB Mode Only - New DMA Source, Low + GB_IO_HDMA3 = 0x53, // CGB Mode Only - New DMA Destination, High + GB_IO_HDMA4 = 0x54, // CGB Mode Only - New DMA Destination, Low + GB_IO_HDMA5 = 0x55, // CGB Mode Only - New DMA Length/Mode/Start + + /* IR */ + GB_IO_RP = 0x56, // CGB Mode Only - Infrared Communications Port + + /* Missing */ + + /* CGB Paletts */ + GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index + GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data + GB_IO_OBPI = 0x6a, // CGB Mode Only - Sprite Palette Index + GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite Palette Data + GB_IO_OPRI = 0x6c, // Affects object priority (X based or index based) + + /* Missing */ + + GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank + GB_IO_UNKNOWN2 = 0x72, // (00h) - Bit 0-7 (Read/Write) + GB_IO_UNKNOWN3 = 0x73, // (00h) - Bit 0-7 (Read/Write) + GB_IO_UNKNOWN4 = 0x74, // (00h) - Bit 0-7 (Read/Write) - CGB Mode Only + GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write) + GB_IO_PCM_12 = 0x76, // Channels 1 and 2 amplitudes + GB_IO_PCM_34 = 0x77, // Channels 3 and 4 amplitudes + GB_IO_UNKNOWN8 = 0x7F, // Unknown, write only +}; + +typedef enum { + GB_LOG_BOLD = 1, + GB_LOG_DASHED_UNDERLINE = 2, + GB_LOG_UNDERLINE = 4, + GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE +} GB_log_attributes; + +typedef enum { + GB_BOOT_ROM_DMG0, + GB_BOOT_ROM_DMG, + GB_BOOT_ROM_MGB, + GB_BOOT_ROM_SGB, + GB_BOOT_ROM_SGB2, + GB_BOOT_ROM_CGB0, + GB_BOOT_ROM_CGB, + GB_BOOT_ROM_AGB, +} GB_boot_rom_t; + +#ifdef GB_INTERNAL +#define LCDC_PERIOD 70224 +#define CPU_FREQUENCY 0x400000 +#define SGB_NTSC_FREQUENCY (21477272 / 5) +#define SGB_PAL_FREQUENCY (21281370 / 5) +#define DIV_CYCLES (0x100) +#define INTERNAL_DIV_CYCLES (0x40000) + +#if !defined(MIN) +#define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) +#endif + +#if !defined(MAX) +#define MAX(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) +#endif +#endif + +typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes); +typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb); +typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); +typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, uint64_t cycles_since_last_update); +typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, double rumble_amplitude); +typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send); +typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value); +typedef void (*GB_icd_pixel_callback_t)(GB_gameboy_t *gb, uint8_t row); +typedef void (*GB_icd_hreset_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type); + +typedef struct { + bool state; + uint64_t delay; +} GB_ir_queue_item_t; + +struct GB_breakpoint_s; +struct GB_watchpoint_s; + +typedef struct { + uint8_t pixel; // Color, 0-3 + uint8_t palette; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG) + uint8_t priority; // Sprite priority – 0 in DMG, OAM index in CGB + bool bg_priority; // For sprite FIFO – the BG priority bit. For the BG FIFO – the CGB attributes priority bit +} GB_fifo_item_t; + +#define GB_FIFO_LENGTH 16 +typedef struct { + GB_fifo_item_t fifo[GB_FIFO_LENGTH]; + uint8_t read_end; + uint8_t write_end; +} GB_fifo_t; + +/* When state saving, each section is dumped independently of other sections. + This allows adding data to the end of the section without worrying about future compatibility. + Some other changes might be "safe" as well. + This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64 + bit platforms. */ + +#ifdef GB_INTERNAL +struct GB_gameboy_s { +#else +struct GB_gameboy_internal_s { +#endif + GB_SECTION(header, + /* The magic makes sure a state file is: + - Indeed a SameBoy state file. + - Has the same endianess has the current platform. */ + volatile uint32_t magic; + /* The version field makes sure we don't load save state files with a completely different structure. + This happens when struct fields are removed/resized in an backward incompatible manner. */ + uint32_t version; + ); + + GB_SECTION(core_state, + /* Registers */ + uint16_t pc; + union { + uint16_t registers[GB_REGISTERS_16_BIT]; + struct { + uint16_t af, + bc, + de, + hl, + sp; + }; + struct { +#ifdef GB_BIG_ENDIAN + uint8_t a, f, + b, c, + d, e, + h, l; +#else + uint8_t f, a, + c, b, + e, d, + l, h; +#endif + }; + + }; + uint8_t ime; + uint8_t interrupt_enable; + uint8_t cgb_ram_bank; + + /* CPU and General Hardware Flags*/ + GB_model_t model; + bool cgb_mode; + bool cgb_double_speed; + bool halted; + bool stopped; + bool boot_rom_finished; + bool ime_toggle; /* ei has delayed a effect.*/ + bool halt_bug; + bool just_halted; + + /* Misc state */ + bool infrared_input; + GB_printer_t printer; + uint8_t extra_oam[0xff00 - 0xfea0]; + uint32_t ram_size; // Different between CGB and DMG + GB_workboy_t workboy; + ); + + /* DMA and HDMA */ + GB_SECTION(dma, + bool hdma_on; + bool hdma_on_hblank; + uint8_t hdma_steps_left; + int16_t hdma_cycles; // in 8MHz units + uint16_t hdma_current_src, hdma_current_dest; + + uint8_t dma_steps_left; + uint8_t dma_current_dest; + uint16_t dma_current_src; + int16_t dma_cycles; + bool is_dma_restarting; + uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */ + bool hdma_starting; + ); + + /* MBC */ + GB_SECTION(mbc, + uint16_t mbc_rom_bank; + uint8_t mbc_ram_bank; + uint32_t mbc_ram_size; + bool mbc_ram_enable; + union { + struct { + uint8_t bank_low:5; + uint8_t bank_high:2; + uint8_t mode:1; + } mbc1; + + struct { + uint8_t rom_bank:4; + } mbc2; + + struct { + uint8_t rom_bank:8; + uint8_t ram_bank:3; + } mbc3; + + struct { + uint8_t rom_bank_low; + uint8_t rom_bank_high:1; + uint8_t ram_bank:4; + } mbc5; + + struct { + uint8_t bank_low:6; + uint8_t bank_high:3; + bool mode:1; + bool ir_mode:1; + } huc1; + + struct { + uint8_t rom_bank:7; + uint8_t padding:1; + uint8_t ram_bank:4; + } huc3; + }; + uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ + bool camera_registers_mapped; + uint8_t camera_registers[0x36]; + bool rumble_state; + bool cart_ir; + + // TODO: move to huc3/mbc3 struct when breaking save compat + uint8_t huc3_mode; + uint8_t huc3_access_index; + uint16_t huc3_minutes, huc3_days; + uint16_t huc3_alarm_minutes, huc3_alarm_days; + bool huc3_alarm_enabled; + uint8_t huc3_read; + uint8_t huc3_access_flags; + bool mbc3_rtc_mapped; + ); + + + /* HRAM and HW Registers */ + GB_SECTION(hram, + uint8_t hram[0xFFFF - 0xFF80]; + uint8_t io_registers[0x80]; + ); + + /* Timing */ + GB_SECTION(timing, + GB_UNIT(display); + GB_UNIT(div); + uint16_t div_counter; + uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ + uint16_t serial_cycles; + uint16_t serial_length; + uint8_t double_speed_alignment; + uint8_t serial_count; + ); + + /* APU */ + GB_SECTION(apu, + GB_apu_t apu; + ); + + /* RTC */ + GB_SECTION(rtc, + GB_rtc_time_t rtc_real, rtc_latched; + uint64_t last_rtc_second; + bool rtc_latch; + ); + + /* Video Display */ + GB_SECTION(video, + uint32_t vram_size; // Different between CGB and DMG + uint8_t cgb_vram_bank; + uint8_t oam[0xA0]; + uint8_t background_palettes_data[0x40]; + uint8_t sprite_palettes_data[0x40]; + uint8_t position_in_line; + bool stat_interrupt_line; + uint8_t effective_scx; + uint8_t window_y; + /* The LCDC will skip the first frame it renders after turning it on. + On the CGB, a frame is not skipped if the previous frame was skipped as well. + See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */ + + /* TODO: Drop this and properly emulate the dropped vreset signal*/ + enum { + GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state, + // on a CGB, the previous frame is repeated (which might be + // blank if the LCD was off for more than a few cycles) + GB_FRAMESKIP_FIRST_FRAME_SKIPPED, // This state is 'skipped' when emulating a DMG + GB_FRAMESKIP_SECOND_FRAME_RENDERED, + } frame_skip_state; + bool oam_read_blocked; + bool vram_read_blocked; + bool oam_write_blocked; + bool vram_write_blocked; + bool fifo_insertion_glitch; + uint8_t current_line; + uint16_t ly_for_comparison; + GB_fifo_t bg_fifo, oam_fifo; + uint8_t fetcher_x; + uint8_t fetcher_y; + uint16_t cycles_for_line; + uint8_t current_tile; + uint8_t current_tile_attributes; + uint8_t current_tile_data[2]; + uint8_t fetcher_state; + bool window_is_being_fetched; + bool wx166_glitch; + bool wx_triggered; + uint8_t visible_objs[10]; + uint8_t obj_comparators[10]; + uint8_t n_visible_objs; + uint8_t oam_search_index; + uint8_t accessed_oam_row; + uint8_t extra_penalty_for_sprite_at_0; + uint8_t mode_for_interrupt; + bool lyc_interrupt_line; + bool cgb_palettes_blocked; + uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases. + uint32_t cycles_in_stop_mode; + uint8_t object_priority; + bool oam_ppu_blocked; + bool vram_ppu_blocked; + bool cgb_palettes_ppu_blocked; + bool object_fetch_aborted; + bool during_object_fetch; + uint16_t object_low_line_address; + bool wy_triggered; + uint8_t window_tile_x; + uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases. + bool is_odd_frame; + uint16_t last_tile_data_address; + uint16_t last_tile_index_address; + bool cgb_repeated_a_frame; + ); + + /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ + /* This data is reserved on reset and must come last in the struct */ + GB_SECTION(unsaved, + /* ROM */ + uint8_t *rom; + uint32_t rom_size; + const GB_cartridge_t *cartridge_type; + enum { + GB_STANDARD_MBC1_WIRING, + GB_MBC1M_WIRING, + } mbc1_wiring; + bool is_mbc30; + + unsigned pending_cycles; + + /* Various RAMs */ + uint8_t *ram; + uint8_t *vram; + uint8_t *mbc_ram; + + /* I/O */ + uint32_t *screen; + uint32_t background_palettes_rgb[0x20]; + uint32_t sprite_palettes_rgb[0x20]; + const GB_palette_t *dmg_palette; + GB_color_correction_mode_t color_correction_mode; + bool keys[4][GB_KEY_MAX]; + GB_border_mode_t border_mode; + GB_sgb_border_t borrowed_border; + bool tried_loading_sgb_border; + bool has_sgb_border; + + /* Timing */ + uint64_t last_sync; + uint64_t cycles_since_last_sync; // In 8MHz units + + /* Audio */ + GB_apu_output_t apu_output; + + /* Callbacks */ + void *user_data; + GB_log_callback_t log_callback; + GB_input_callback_t input_callback; + GB_input_callback_t async_input_callback; + GB_rgb_encode_callback_t rgb_encode_callback; + GB_vblank_callback_t vblank_callback; + GB_infrared_callback_t infrared_callback; + GB_camera_get_pixel_callback_t camera_get_pixel_callback; + GB_camera_update_request_callback_t camera_update_request_callback; + GB_rumble_callback_t rumble_callback; + GB_serial_transfer_bit_start_callback_t serial_transfer_bit_start_callback; + GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback; + GB_update_input_hint_callback_t update_input_hint_callback; + GB_joyp_write_callback_t joyp_write_callback; + GB_icd_pixel_callback_t icd_pixel_callback; + GB_icd_vreset_callback_t icd_hreset_callback; + GB_icd_vreset_callback_t icd_vreset_callback; + GB_read_memory_callback_t read_memory_callback; + GB_boot_rom_load_callback_t boot_rom_load_callback; + GB_print_image_callback_t printer_callback; + GB_workboy_set_time_callback workboy_set_time_callback; + GB_workboy_get_time_callback workboy_get_time_callback; + + /* IR */ + uint64_t cycles_since_ir_change; // In 8MHz units + uint64_t cycles_since_input_ir_change; // In 8MHz units + GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE]; + size_t ir_queue_length; + + /*** Debugger ***/ + volatile bool debug_stopped, debug_disable; + bool debug_fin_command, debug_next_command; + + /* Breakpoints */ + uint16_t n_breakpoints; + struct GB_breakpoint_s *breakpoints; + bool has_jump_to_breakpoints, has_software_breakpoints; + void *nontrivial_jump_state; + bool non_trivial_jump_breakpoint_occured; + + /* SLD (Todo: merge with backtrace) */ + bool stack_leak_detection; + signed debug_call_depth; + uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */ + uint16_t addr_for_call_depth[0x200]; + + /* Backtrace */ + unsigned backtrace_size; + uint16_t backtrace_sps[0x200]; + struct { + uint16_t bank; + uint16_t addr; + } backtrace_returns[0x200]; + + /* Watchpoints */ + uint16_t n_watchpoints; + struct GB_watchpoint_s *watchpoints; + + /* Symbol tables */ + GB_symbol_map_t *bank_symbols[0x200]; + GB_reversed_symbol_map_t reversed_symbol_map; + + /* Ticks command */ + uint64_t debugger_ticks; + + /* Rewind */ +#define GB_REWIND_FRAMES_PER_KEY 255 + size_t rewind_buffer_length; + struct { + uint8_t *key_state; + uint8_t *compressed_states[GB_REWIND_FRAMES_PER_KEY]; + unsigned pos; + } *rewind_sequences; // lasts about 4 seconds + size_t rewind_pos; + + /* SGB - saved and allocated optionally */ + GB_sgb_t *sgb; + + double sgb_intro_jingle_phases[7]; + double sgb_intro_sweep_phase; + double sgb_intro_sweep_previous_sample; + + /* Cheats */ + bool cheat_enabled; + size_t cheat_count; + GB_cheat_t **cheats; + GB_cheat_hash_t *cheat_hash[256]; + + /* Misc */ + bool turbo; + bool turbo_dont_skip; + bool disable_rendering; + uint8_t boot_rom[0x900]; + bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank + uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units + double clock_multiplier; + GB_rumble_mode_t rumble_mode; + uint32_t rumble_on_cycles; + uint32_t rumble_off_cycles; + + /* Temporary state */ + bool wx_just_changed; + ); +}; + +#ifndef GB_INTERNAL +struct GB_gameboy_s { + char __internal[sizeof(struct GB_gameboy_internal_s)]; +}; +#endif + + +#ifndef __printflike +/* Missing from Linux headers. */ +#define __printflike(fmtarg, firstvararg) \ +__attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#endif + +void GB_init(GB_gameboy_t *gb, GB_model_t model); +bool GB_is_inited(GB_gameboy_t *gb); +bool GB_is_cgb(GB_gameboy_t *gb); +bool GB_is_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 +bool GB_is_hle_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 and the SFC/SNES side is HLE'd +GB_model_t GB_get_model(GB_gameboy_t *gb); +void GB_free(GB_gameboy_t *gb); +void GB_reset(GB_gameboy_t *gb); +void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model); + +/* Returns the time passed, in 8MHz ticks. */ +uint8_t GB_run(GB_gameboy_t *gb); +/* Returns the time passed since the last frame, in nanoseconds */ +uint64_t GB_run_frame(GB_gameboy_t *gb); + +typedef enum { + GB_DIRECT_ACCESS_ROM, + GB_DIRECT_ACCESS_RAM, + GB_DIRECT_ACCESS_CART_RAM, + GB_DIRECT_ACCESS_VRAM, + GB_DIRECT_ACCESS_HRAM, + GB_DIRECT_ACCESS_IO, /* Warning: Some registers can only be read/written correctly via GB_memory_read/write. */ + GB_DIRECT_ACCESS_BOOTROM, + GB_DIRECT_ACCESS_OAM, + GB_DIRECT_ACCESS_BGP, + GB_DIRECT_ACCESS_OBP, + GB_DIRECT_ACCESS_IE, +} GB_direct_access_t; + +/* Returns a mutable pointer to various hardware memories. If that memory is banked, the current bank + is returned at *bank, even if only a portion of the memory is banked. */ +void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank); + +void *GB_get_user_data(GB_gameboy_t *gb); +void GB_set_user_data(GB_gameboy_t *gb, void *data); + + + +int GB_load_boot_rom(GB_gameboy_t *gb, const char *path); +void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size); +int GB_load_rom(GB_gameboy_t *gb, const char *path); +void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); +int GB_load_isx(GB_gameboy_t *gb, const char *path); + +int GB_save_battery_size(GB_gameboy_t *gb); +int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size); +int GB_save_battery(GB_gameboy_t *gb, const char *path); + +void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); +void GB_load_battery(GB_gameboy_t *gb, const char *path); + +void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip); +void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled); + +void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3); +void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) __printflike(3, 4); + +void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output); +void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode); + +void GB_set_infrared_input(GB_gameboy_t *gb, bool state); +void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, uint64_t cycles_after_previous_change); /* In 8MHz units*/ + +void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback); +void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback); +void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback); +void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback); +void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback); +void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback); +void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); +void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback); +/* Called when a new boot ROM is needed. The callback should call GB_load_boot_rom or GB_load_boot_rom_from_buffer */ +void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback); + +void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette); + +/* These APIs are used when using internal clock */ +void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback); +void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback); + +/* These APIs are used when using external clock */ +bool GB_serial_get_data_bit(GB_gameboy_t *gb); +void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data); + +void GB_disconnect_serial(GB_gameboy_t *gb); + +/* For cartridges with an alarm clock */ +unsigned GB_time_to_alarm(GB_gameboy_t *gb); // 0 if no alarm + +/* For integration with SFC/SNES emulators */ +void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback); +void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback); +void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback); +void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback); + +#ifdef GB_INTERNAL +uint32_t GB_get_clock_rate(GB_gameboy_t *gb); +#endif +void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); + +unsigned GB_get_screen_width(GB_gameboy_t *gb); +unsigned GB_get_screen_height(GB_gameboy_t *gb); +double GB_get_usual_frame_rate(GB_gameboy_t *gb); +unsigned GB_get_player_count(GB_gameboy_t *gb); + +#endif /* GB_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/gb_struct_def.h b/waterbox/bsnescore/bsnes/gb/Core/gb_struct_def.h new file mode 100644 index 0000000000..0e0ebd12ee --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/gb_struct_def.h @@ -0,0 +1,5 @@ +#ifndef gb_struct_def_h +#define gb_struct_def_h +struct GB_gameboy_s; +typedef struct GB_gameboy_s GB_gameboy_t; +#endif diff --git a/waterbox/bsnescore/bsnes/gb/Core/graphics/agb_border.inc b/waterbox/bsnescore/bsnes/gb/Core/graphics/agb_border.inc new file mode 100644 index 0000000000..dd4ebbe88f --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/graphics/agb_border.inc @@ -0,0 +1,522 @@ +static const uint16_t palette[] = { + 0x410A, 0x0421, 0x35AD, 0x4A52, 0x7FFF, 0x2D49, 0x0C42, 0x1484, + 0x18A5, 0x20C6, 0x6718, 0x5D6E, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const uint16_t tilemap[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0001, 0x0002, 0x0003, 0x0003, 0x0003, 0x0004, 0x0004, + 0x0004, 0x0004, 0x0004, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0004, 0x0004, 0x0004, 0x0004, + 0x0004, 0x0004, 0x0003, 0x0003, 0x0003, 0x4002, 0x4001, 0x0000, + 0x0000, 0x0006, 0x0007, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x4007, 0x4006, 0x0000, + 0x0000, 0x0009, 0x0008, 0x0008, 0x0008, 0x000A, 0x000B, 0x000B, + 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, + 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, 0x000B, + 0x000B, 0x000B, 0x400A, 0x0008, 0x0008, 0x0008, 0xC009, 0x0000, + 0x0000, 0x000C, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x400C, 0x0000, + 0x0000, 0x000E, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC00E, 0x0000, + 0x0000, 0x000F, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x400F, 0x0000, + 0x0000, 0x0010, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC010, 0x0000, + 0x0000, 0x0010, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC010, 0x0000, + 0x0000, 0x0011, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC011, 0x0000, + 0x0000, 0x0011, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC011, 0x0000, + 0x0000, 0x0012, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4012, 0x0000, + 0x0000, 0x0013, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC013, 0x0000, + 0x0014, 0x0015, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4015, 0x4014, + 0x0016, 0x0017, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC017, 0xC016, + 0x0016, 0x0017, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC017, 0xC016, + 0x0018, 0x0019, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x4019, 0x4018, + 0x001A, 0x001B, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0xC01B, 0xC01A, + 0x001C, 0x001D, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x401D, 0x401C, + 0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E, + 0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E, + 0x001E, 0x0008, 0x0008, 0x0008, 0x0008, 0x000D, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC00D, 0x0008, 0x0008, 0x0008, 0x0008, 0xC01E, + 0x001F, 0x801D, 0x0008, 0x0008, 0x0008, 0x0020, 0x0021, 0x0022, + 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, + 0x002B, 0x002C, 0x002D, 0x002D, 0x002D, 0x002D, 0x002D, 0x002D, + 0x002E, 0x0021, 0x4020, 0x0008, 0x0008, 0x0008, 0xC01D, 0x401F, + 0x002F, 0x0030, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0031, + 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, + 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, + 0x0042, 0x0043, 0x0008, 0x0008, 0x0008, 0x0008, 0x4030, 0x402F, + 0x0044, 0x0045, 0x0046, 0x0047, 0x0008, 0x0008, 0x0048, 0x0049, + 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, + 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, + 0x005A, 0x005B, 0x0008, 0x0008, 0x4047, 0x4046, 0x4045, 0x4044, + 0x0000, 0x0000, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, + 0x0061, 0x0062, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, + 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x4062, 0x0061, + 0x0061, 0x4060, 0x405F, 0x405E, 0x405D, 0x405C, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1F, 0x01, + 0x7F, 0x1F, 0xFF, 0x7E, 0xFF, 0xE1, 0xFF, 0x9F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x1E, + 0x1F, 0x60, 0x7F, 0x80, 0xFF, 0x00, 0xBF, 0x40, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x03, 0x01, 0x07, 0x03, 0x07, 0x03, 0x07, 0x06, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x01, 0x02, 0x03, 0x04, 0x03, 0x04, 0x06, 0x01, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0x40, 0x7F, 0x80, 0x7F, 0x80, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, + 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, 0x0F, 0x0E, + 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, + 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, 0x0E, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, + 0x0F, 0x0E, 0x0F, 0x0E, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x0E, 0x01, 0x0E, 0x01, 0x1B, 0x04, 0x1B, 0x04, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, + 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, 0x1F, 0x1B, + 0x1F, 0x1B, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, + 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, 0x1B, 0x04, + 0x1B, 0x04, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, + 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, + 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, 0x3F, 0x37, + 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, + 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, 0x37, 0x08, + 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, + 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, + 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, + 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, + 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0xFF, 0xDF, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0x6F, 0x10, 0x6F, 0x10, 0x6F, 0x10, 0xDF, 0x20, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xDF, + 0xFF, 0xDF, 0xFF, 0xDF, 0xFF, 0xBF, 0xFF, 0xBF, + 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, 0xDF, 0x20, + 0xDF, 0x20, 0xDF, 0x20, 0xBF, 0x40, 0xBF, 0x40, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, + 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, + 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, + 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, 0x40, + 0x01, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xBF, 0x40, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, 0x06, 0x01, + 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, + 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0xFF, 0xE7, 0xF8, 0xDF, 0xE3, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x00, 0xE4, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3F, 0xFF, 0xCE, 0x3F, 0xF5, 0x8E, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0x00, 0x4E, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x1F, 0xFF, 0xEE, 0x1F, 0xB5, 0x4E, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x1F, 0x00, 0x0E, 0xA0, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x07, 0xFF, 0xFB, 0x07, 0x04, 0x73, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x03, 0x88, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0xFF, 0x7F, 0x80, 0x82, 0x39, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x80, 0x00, 0x01, 0x44, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x01, 0xFE, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x83, 0x7C, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x42, 0x01, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xBB, 0x7C, 0x4F, 0xB0, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x7C, 0x00, 0xB1, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x07, 0xFF, 0xF9, 0x06, 0xE7, 0xF8, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x06, 0x00, 0x08, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x0E, 0xFF, 0xF5, 0x0E, 0x9B, 0x74, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x0E, 0x00, 0x94, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x1F, 0xFF, 0xF7, 0x0F, 0xBF, 0x47, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0x00, 0xA7, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01, + 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, + 0x03, 0x04, 0x01, 0x02, 0x01, 0x02, 0x00, 0x01, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xDF, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0xBF, 0x40, 0xBF, 0x40, 0xDF, 0x20, + 0xB0, 0xD8, 0xA0, 0xD3, 0x67, 0x84, 0x47, 0xA4, + 0x61, 0x81, 0xA0, 0xD0, 0xB4, 0xCA, 0x7E, 0x81, + 0xD7, 0x08, 0xCC, 0x13, 0x98, 0x20, 0x98, 0x00, + 0x9E, 0x20, 0xCF, 0x00, 0xCD, 0x02, 0x80, 0x01, + 0x32, 0x2D, 0x13, 0x6D, 0x34, 0x48, 0xFC, 0x02, + 0x7C, 0x00, 0x78, 0x05, 0x30, 0x49, 0x20, 0x50, + 0xCD, 0x00, 0xAC, 0x40, 0x49, 0x82, 0x01, 0x02, + 0x07, 0x80, 0xC2, 0x05, 0x86, 0x41, 0x9F, 0x40, + 0x15, 0x2E, 0x09, 0x06, 0x09, 0x16, 0x0B, 0xD4, + 0xC6, 0x49, 0x8E, 0x40, 0xCF, 0xC8, 0x06, 0x01, + 0xCE, 0x20, 0xE6, 0x10, 0xE6, 0x00, 0x24, 0xD0, + 0x39, 0x80, 0x38, 0x01, 0x31, 0x00, 0xF8, 0x00, + 0x0C, 0x8B, 0x85, 0x8A, 0x03, 0x84, 0x27, 0x20, + 0x22, 0x35, 0x12, 0x34, 0x20, 0x12, 0x10, 0x20, + 0x73, 0x00, 0x72, 0x08, 0x7C, 0x80, 0xDC, 0x01, + 0xC8, 0x11, 0xC9, 0x06, 0xCD, 0x22, 0xEF, 0x10, + 0x83, 0x44, 0x86, 0x01, 0x03, 0x85, 0x26, 0x21, + 0x46, 0x69, 0x46, 0x68, 0x8E, 0xCA, 0x86, 0x88, + 0x39, 0x40, 0x78, 0x84, 0x7C, 0x80, 0xD8, 0x01, + 0x90, 0x29, 0xD1, 0x28, 0x73, 0x00, 0xB3, 0x40, + 0x00, 0x01, 0x01, 0x00, 0x3F, 0x00, 0x3F, 0x40, + 0x03, 0x02, 0x01, 0x02, 0x41, 0x7C, 0x7F, 0x00, + 0xFE, 0x00, 0xFF, 0x00, 0xC0, 0x00, 0x80, 0x40, + 0xFC, 0x00, 0xFC, 0x00, 0x80, 0x02, 0xC0, 0x00, + 0xC0, 0x00, 0x80, 0x4C, 0xCC, 0x43, 0x8E, 0x52, + 0x80, 0x4C, 0x80, 0x00, 0x12, 0x1E, 0x9E, 0x00, + 0x7F, 0x00, 0x33, 0x0C, 0x32, 0x01, 0x23, 0x50, + 0x33, 0x4C, 0x7F, 0x00, 0x61, 0x80, 0xF1, 0x00, + 0x7C, 0x02, 0x30, 0x48, 0x31, 0x40, 0x61, 0x50, + 0x87, 0xE4, 0xE3, 0x84, 0x23, 0x44, 0x43, 0x44, + 0x85, 0x42, 0x87, 0x40, 0x8F, 0x50, 0x8C, 0x12, + 0x78, 0x00, 0x18, 0x20, 0xB8, 0x00, 0x98, 0x24, + 0x03, 0x04, 0x03, 0xE0, 0xF1, 0x12, 0xF0, 0x09, + 0xF9, 0x09, 0xF9, 0x08, 0xE1, 0x12, 0xF1, 0x12, + 0xF8, 0x00, 0x1E, 0xE0, 0x0C, 0x02, 0x07, 0x08, + 0x07, 0x00, 0x06, 0x00, 0x1C, 0x02, 0x0C, 0x00, + 0x9F, 0x91, 0x86, 0x88, 0xC4, 0x4C, 0x80, 0x4C, + 0xE1, 0x20, 0xC1, 0x22, 0x23, 0xD4, 0x22, 0xD5, + 0x60, 0x00, 0xFB, 0x00, 0x37, 0x00, 0x73, 0x0C, + 0x1F, 0x00, 0x3C, 0x00, 0xC8, 0x14, 0xC9, 0x14, + 0x16, 0x2F, 0x76, 0x4F, 0x2D, 0xDE, 0xDD, 0xBE, + 0xBA, 0x7D, 0x7A, 0xFD, 0x7A, 0xFD, 0xF4, 0xF8, + 0xCF, 0x00, 0x8F, 0x00, 0x5E, 0x80, 0xBE, 0x00, + 0x7D, 0x00, 0xFC, 0x00, 0xFC, 0x01, 0xF9, 0x02, + 0xFF, 0x00, 0xBF, 0x78, 0x86, 0x09, 0x86, 0x89, + 0x06, 0x25, 0x02, 0x25, 0x42, 0x45, 0x60, 0x11, + 0x00, 0x00, 0x09, 0x00, 0x70, 0x81, 0x70, 0x09, + 0xDC, 0x21, 0xD8, 0x01, 0x98, 0x25, 0xCC, 0x13, + 0xFF, 0x00, 0xF3, 0xF8, 0x02, 0x03, 0x01, 0x30, + 0x39, 0x09, 0x30, 0x09, 0x31, 0x09, 0x20, 0x19, + 0x00, 0x00, 0x01, 0x04, 0xFC, 0x00, 0xCF, 0x30, + 0xE6, 0x00, 0xE6, 0x01, 0xE6, 0x00, 0xF6, 0x08, + 0xFF, 0x00, 0xFA, 0xC7, 0x18, 0x21, 0x09, 0x10, + 0x88, 0x99, 0x93, 0x1A, 0x83, 0x11, 0xC2, 0x41, + 0x00, 0x00, 0x00, 0x20, 0xC6, 0x21, 0xFF, 0x00, + 0x67, 0x00, 0xE4, 0x08, 0x6F, 0x10, 0x3C, 0x00, + 0xFD, 0x02, 0xB5, 0x3A, 0xC7, 0x44, 0x03, 0x84, + 0x83, 0x24, 0x21, 0xB0, 0x21, 0x12, 0x21, 0x02, + 0x02, 0x00, 0x02, 0x40, 0x3C, 0x00, 0xF8, 0x00, + 0xD8, 0x24, 0x4C, 0x92, 0xEC, 0x00, 0xCC, 0x12, + 0xFF, 0x00, 0xFF, 0xF3, 0x1C, 0x14, 0x0C, 0x04, + 0x00, 0x0C, 0x04, 0x24, 0x00, 0x24, 0x10, 0x30, + 0x00, 0x00, 0x10, 0x04, 0xE3, 0x00, 0xFB, 0x00, + 0xF3, 0x08, 0xDB, 0x20, 0xDB, 0x04, 0xCF, 0x00, + 0xFF, 0x00, 0xEC, 0x3E, 0xC1, 0x01, 0x01, 0x8E, + 0x8F, 0x10, 0x0F, 0x90, 0x0F, 0x90, 0x0D, 0x09, + 0x00, 0x00, 0x20, 0x01, 0x7E, 0x00, 0xF1, 0x0E, + 0xE0, 0x10, 0x60, 0x10, 0x60, 0x10, 0x79, 0x82, + 0xFF, 0x00, 0x7F, 0xFC, 0x03, 0x82, 0x01, 0x9E, + 0x13, 0x80, 0x03, 0x80, 0x03, 0x9C, 0x0F, 0x90, + 0x00, 0x00, 0x02, 0x00, 0x7C, 0x80, 0x60, 0x9C, + 0x60, 0x9C, 0x7C, 0x80, 0x60, 0x9C, 0x70, 0x80, + 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, + 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xEF, 0xFF, 0xF7, 0x7F, 0x7B, 0x3F, 0x3C, + 0x1F, 0x1F, 0x0F, 0x0F, 0x03, 0x03, 0x00, 0x00, + 0xEF, 0x10, 0x77, 0x88, 0x3B, 0x44, 0x1C, 0x23, + 0x0F, 0x10, 0x03, 0x0C, 0x00, 0x03, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x3F, 0xFF, 0xC3, 0xFF, 0xFC, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3F, 0xC0, 0xC3, 0x3C, 0xFC, 0x03, 0x3F, 0xC0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC1, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xC0, 0xC1, 0x3E, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xEA, 0x14, 0xC0, 0x00, 0x80, 0x21, 0x7F, 0x92, + 0x9F, 0xE0, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x27, 0x18, 0x7F, 0x00, 0x1E, 0x61, 0x9A, 0x04, + 0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x73, 0x53, 0x47, 0x44, 0x46, 0x25, 0xFD, 0x03, + 0xF9, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8C, 0x00, 0xD8, 0x20, 0x1D, 0xA0, 0x03, 0x00, + 0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC7, 0xE1, 0xE6, 0x05, 0x42, 0xA5, 0xBF, 0xC0, + 0x9F, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x18, 0x24, 0x38, 0x01, 0xB8, 0x05, 0xC0, 0x00, + 0xE0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x21, 0x11, 0x31, 0x49, 0x33, 0x4A, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xDE, 0x00, 0x87, 0x48, 0x84, 0x48, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xCC, 0x02, 0x8E, 0x4A, 0xCC, 0x42, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x71, 0x08, 0x39, 0x00, 0x31, 0x02, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3D, 0x40, 0x03, 0x02, 0x03, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBC, 0x02, 0xFC, 0x00, 0xFE, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x12, 0x82, 0x80, 0x80, 0x01, 0x83, 0xFF, 0x00, + 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x61, 0x1C, 0x7F, 0x00, 0x7C, 0x82, 0x00, 0x00, + 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x22, 0x52, 0x30, 0xC0, 0x58, 0xA4, 0x8F, 0x72, + 0x73, 0xFC, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8C, 0x11, 0x4F, 0x90, 0xA3, 0x0C, 0x73, 0x00, + 0xFC, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x23, 0xA4, 0x06, 0x0D, 0x05, 0x1B, 0xBB, 0x07, + 0xE7, 0x1F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x98, 0x44, 0xF5, 0x08, 0xEB, 0x00, 0x87, 0x40, + 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x66, 0x85, 0xE2, 0xA5, 0x66, 0x81, 0xBF, 0xC1, + 0x99, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x99, 0x00, 0xB9, 0x00, 0x9D, 0x20, 0xC1, 0x00, + 0xE7, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF6, 0xFA, 0xFC, 0xF2, 0xF7, 0xF8, 0xFB, 0xFC, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF9, 0x00, 0xF1, 0x02, 0xF8, 0x00, 0xFC, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x52, 0x53, 0x30, 0x23, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8C, 0x21, 0xCC, 0x13, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x03, 0x03, 0x06, 0xFE, 0x01, 0xF9, 0x07, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFD, 0x02, 0xFA, 0x04, 0x01, 0x00, 0x07, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x86, 0x05, 0x46, 0xA0, 0x5F, 0xB8, 0xBF, 0xC0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x38, 0x41, 0x99, 0x26, 0xB8, 0x00, 0xC0, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x30, 0x28, 0x09, 0x09, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC6, 0x09, 0xE6, 0x10, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x20, 0x38, 0x38, 0x20, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xD7, 0x08, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x80, 0x41, 0xA1, 0x61, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3E, 0x40, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x01, 0x82, 0x01, 0x82, 0xFF, 0x00, 0xFC, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7C, 0x82, 0x7C, 0x82, 0x00, 0x00, 0x03, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3F, 0x3F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x3C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFE, 0xFF, 0xFF, 0x3F, 0x3F, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0x01, 0x3F, 0xC0, 0x01, 0x3E, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xC0, 0xC0, 0x3F, 0xFF, 0x00, 0x3F, 0xC0, + 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x3F, 0xC0, 0xC0, 0x3F, 0xFF, 0x00, + 0x3F, 0xC0, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFC, + 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFC, 0xFC, 0x03, + 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x03, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, + 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFC, + 0xFC, 0x03, 0xFF, 0x00, 0x03, 0xFC, 0x00, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, +}; diff --git a/waterbox/bsnescore/bsnes/gb/Core/graphics/cgb_border.inc b/waterbox/bsnescore/bsnes/gb/Core/graphics/cgb_border.inc new file mode 100644 index 0000000000..755312a434 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/graphics/cgb_border.inc @@ -0,0 +1,446 @@ +static const uint16_t palette[] = { + 0x7C1A, 0x0000, 0x0011, 0x3DEF, 0x6318, 0x7FFF, 0x1EBA, 0x19AF, + 0x1EAF, 0x4648, 0x7FC0, 0x2507, 0x1484, 0x5129, 0x5010, 0x2095, +}; + +static const uint16_t tilemap[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0001, 0x0002, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x4002, 0x4001, 0x0000, + 0x0000, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4004, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0007, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, + 0x0008, 0x0008, 0x4007, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x000A, 0x000B, 0x400A, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x800A, 0x000C, 0xC00A, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x0006, 0x0005, 0x0005, 0x0005, 0x0009, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC009, 0x0005, 0x0005, 0x0005, 0xC006, 0x0000, + 0x0000, 0x000D, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x000E, + 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, + 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, + 0x001F, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x400D, 0x0000, + 0x0000, 0x0020, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0021, + 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, + 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, + 0x0032, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4020, 0x0000, + 0x0000, 0x0033, 0x0034, 0x0035, 0x0036, 0x0005, 0x0005, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0005, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, + 0x0047, 0x0005, 0x0005, 0x4036, 0x4035, 0x4034, 0x4033, 0x0000, + 0x0000, 0x0000, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, + 0x004E, 0x004E, 0x004F, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, + 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x404F, 0x004E, 0x004E, + 0x404D, 0x004C, 0x404B, 0x404A, 0x4049, 0x4048, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x08, + 0x01, 0x11, 0x06, 0x26, 0x04, 0x24, 0x08, 0x48, + 0x00, 0x00, 0x01, 0x01, 0x07, 0x07, 0x0F, 0x0F, + 0x1E, 0x1F, 0x39, 0x3F, 0x3B, 0x3F, 0x77, 0x7F, + 0x00, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x7F, 0x7F, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x08, 0x48, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x77, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42, + 0xBD, 0xBD, 0x7E, 0x66, 0x7E, 0xFF, 0x7E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0xFF, + 0x7E, 0xFF, 0xFF, 0xE7, 0x7E, 0x7E, 0x7E, 0x7E, + 0x7E, 0xFF, 0x3C, 0xFF, 0x81, 0xFF, 0xC3, 0xFF, + 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7E, 0x7E, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x81, + 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, + 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, 0x6F, 0x7F, + 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xDF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x78, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xC7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x3C, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xE3, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0x9F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0xE1, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xD8, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x27, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x9F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC2, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xE0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x84, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x08, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, + 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, 0x08, 0x48, + 0x08, 0x48, 0x04, 0x24, 0x04, 0x24, 0x02, 0x12, + 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, 0x37, 0x7F, + 0x37, 0x7F, 0x1B, 0x3F, 0x1B, 0x3F, 0x0D, 0x1F, + 0x0F, 0x08, 0x0E, 0x00, 0x1E, 0x12, 0x1E, 0x12, + 0x1F, 0x10, 0x0F, 0x08, 0x02, 0x02, 0x00, 0x00, + 0xF7, 0xF8, 0xFF, 0xF0, 0xED, 0xE3, 0xED, 0xE1, + 0xEF, 0xE0, 0xF7, 0xF0, 0xFD, 0xFC, 0xFF, 0xFF, + 0xF0, 0x10, 0x40, 0x00, 0x41, 0x41, 0x00, 0x00, + 0x83, 0x82, 0xE3, 0x20, 0xC7, 0x04, 0xC7, 0x00, + 0xEF, 0x1F, 0xFF, 0x1F, 0xBE, 0xFF, 0xFF, 0xFE, + 0x7D, 0x7E, 0xDF, 0x3C, 0xFB, 0x18, 0xFF, 0x18, + 0x60, 0x00, 0x70, 0x00, 0xF8, 0x08, 0xB0, 0x00, + 0xD8, 0x40, 0x3C, 0x24, 0x5C, 0x44, 0xFC, 0x00, + 0xFF, 0x8F, 0xFF, 0x0F, 0xF7, 0x07, 0xFF, 0x07, + 0xBF, 0x47, 0xDB, 0x47, 0xBB, 0x03, 0xFF, 0x03, + 0x3C, 0x04, 0x78, 0x00, 0x78, 0x00, 0xEC, 0x80, + 0xFE, 0x92, 0xE7, 0x83, 0xE5, 0x80, 0x4F, 0x08, + 0xFB, 0x83, 0xFF, 0x83, 0xFF, 0x83, 0x7F, 0x83, + 0x6D, 0x93, 0x7C, 0x10, 0x7F, 0x10, 0xF7, 0x10, + 0x3C, 0x00, 0x7C, 0x40, 0x78, 0x00, 0xC8, 0x80, + 0xFC, 0x24, 0xBC, 0x24, 0xFD, 0x65, 0x3D, 0x25, + 0xFF, 0xC3, 0xBF, 0x83, 0xFF, 0x83, 0x7F, 0x03, + 0xDB, 0x23, 0xDB, 0x23, 0x9A, 0x67, 0xDA, 0x47, + 0xFF, 0x80, 0xFF, 0x80, 0xE0, 0x80, 0x40, 0x00, + 0xFF, 0x01, 0xFF, 0x01, 0xDF, 0x1F, 0xE0, 0x20, + 0x7F, 0x80, 0x7F, 0x00, 0x7F, 0x1F, 0xFF, 0x1F, + 0xFE, 0x00, 0xFE, 0x00, 0xE0, 0x01, 0xDF, 0x3F, + 0xBF, 0xA0, 0xB9, 0xA0, 0x10, 0x00, 0x11, 0x01, + 0x3B, 0x00, 0x3F, 0x00, 0x7E, 0x4E, 0x78, 0x48, + 0x5F, 0x40, 0x5F, 0xC0, 0xFF, 0xC7, 0xFE, 0xC7, + 0xFF, 0xC0, 0xFF, 0xC0, 0xB1, 0xC4, 0xB7, 0x8F, + 0xE3, 0x22, 0xC7, 0x04, 0xCE, 0x08, 0xE6, 0x20, + 0xCE, 0x42, 0xDE, 0x52, 0xFE, 0x32, 0xFC, 0x30, + 0xDD, 0x3E, 0xFB, 0x18, 0xF7, 0x18, 0xDF, 0x31, + 0xBD, 0x31, 0xAD, 0x23, 0xCD, 0x31, 0xCF, 0x11, + 0xFE, 0x02, 0x9E, 0x00, 0x86, 0x80, 0x06, 0x00, + 0x03, 0x00, 0x02, 0x00, 0x07, 0x01, 0x07, 0x01, + 0xFD, 0x03, 0xFF, 0x01, 0x7F, 0xF0, 0xFF, 0xF8, + 0xFF, 0xF8, 0xFF, 0xF8, 0xFE, 0xF8, 0xFE, 0xF1, + 0x38, 0x08, 0x71, 0x41, 0x1F, 0x06, 0x39, 0x20, + 0x0F, 0x00, 0x0F, 0x01, 0x04, 0x00, 0x0C, 0x00, + 0xF7, 0x87, 0xBE, 0xC6, 0xF9, 0xC6, 0xDF, 0xE0, + 0xFF, 0xE0, 0xFE, 0xF1, 0xFF, 0xF1, 0xFF, 0xF1, + 0x70, 0x10, 0xE0, 0x20, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xEF, 0x1F, 0xDF, 0x1F, 0xFF, 0x3F, 0xFF, 0x7F, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3F, 0x3F, 0x7F, 0x7F, 0x78, 0x78, 0xF0, 0xF0, + 0xF0, 0xF0, 0xE0, 0xE0, 0xE0, 0xE0, 0xF0, 0xF0, + 0xDF, 0xFF, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, + 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, + 0xE7, 0xE0, 0xFF, 0xF0, 0xFE, 0xF0, 0x1C, 0x00, + 0x3C, 0x20, 0x3C, 0x24, 0x3C, 0x24, 0x3C, 0x20, + 0xFF, 0xFF, 0xEF, 0xFF, 0x6F, 0xFF, 0xFF, 0xFF, + 0xDF, 0xFF, 0xDB, 0xFF, 0xDB, 0xFF, 0xDF, 0xFF, + 0xF8, 0x00, 0xFC, 0xC0, 0x1F, 0x03, 0x1F, 0x13, + 0x1F, 0x13, 0x1E, 0x02, 0x1E, 0x02, 0x3E, 0x02, + 0xFF, 0xFF, 0x3F, 0xFF, 0xFC, 0xFF, 0xEC, 0xFF, + 0xED, 0xFE, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x21, 0x21, + 0x20, 0x21, 0x00, 0x01, 0x00, 0x01, 0x40, 0x41, + 0x8F, 0x7F, 0x1F, 0xFF, 0x1F, 0xFF, 0x3F, 0xDE, + 0x1F, 0xFE, 0x3F, 0xFE, 0x3F, 0xFE, 0x7F, 0xBE, + 0x40, 0x7F, 0x84, 0xFF, 0x11, 0xF1, 0x20, 0xE0, + 0x20, 0xE0, 0x01, 0xC1, 0x01, 0xC1, 0x22, 0xE3, + 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x0E, 0xFF, 0x1F, + 0xDF, 0x3F, 0xFE, 0x3F, 0xFF, 0x3E, 0xFD, 0x1E, + 0x47, 0xC0, 0x27, 0xE0, 0x2F, 0xE8, 0x0F, 0xE9, + 0x0F, 0xE1, 0x0F, 0xE0, 0x3F, 0xF0, 0x3F, 0xF1, + 0xF8, 0x3F, 0xF8, 0x1F, 0xF0, 0x1F, 0xF0, 0x1F, + 0xF0, 0x1F, 0xF0, 0x1F, 0xE0, 0x1F, 0xC0, 0x3F, + 0xFC, 0x00, 0xFE, 0xE2, 0x1E, 0x12, 0x1E, 0x12, + 0x3E, 0x22, 0xFC, 0x00, 0xF8, 0x08, 0xF0, 0x10, + 0x03, 0xFF, 0x01, 0xFF, 0xE1, 0xFF, 0xE1, 0xFF, + 0xC1, 0xFF, 0x03, 0xFF, 0x07, 0xFF, 0x0F, 0xFF, + 0x01, 0x11, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x1F, 0x07, 0x0F, 0x03, 0x07, 0x01, 0x03, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x40, 0x40, 0x30, 0x30, + 0x0C, 0x0C, 0x03, 0xC3, 0x00, 0x30, 0x00, 0x0C, + 0xFF, 0xFF, 0x7F, 0xFF, 0xBF, 0xFF, 0xCF, 0xFF, + 0xF3, 0xFF, 0x3C, 0xFF, 0x0F, 0x3F, 0x03, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC0, 0xC0, 0x3C, 0x3C, 0x03, 0x03, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3F, 0xFF, 0xC3, 0xFF, 0xFC, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, + 0x15, 0x15, 0x3F, 0x20, 0x2F, 0x20, 0x06, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xEA, 0xE6, 0xDF, 0xC0, 0xDF, 0xE0, 0xF9, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xE6, 0x20, 0x9E, 0x12, 0x0C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xDF, 0x30, 0xED, 0x31, 0xFF, 0x63, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0E, 0x02, 0x1E, 0x12, 0x0D, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFD, 0x03, 0xED, 0xE1, 0xFE, 0xF1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4F, 0x08, 0xE6, 0x20, 0xE7, 0x21, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0x18, 0xDF, 0x18, 0xDE, 0x18, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xB9, 0xA1, 0x11, 0x01, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5E, 0x46, 0xFE, 0xC6, 0xFF, 0xC6, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x3F, 0xFF, 0x01, 0xFF, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xC0, 0x01, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7E, 0x4E, 0x3F, 0x00, 0x3E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xB1, 0x8E, 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xEE, 0x20, 0x8F, 0x08, 0x85, 0x84, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xDF, 0x30, 0xF7, 0x38, 0x7B, 0x7C, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xAE, 0xA2, 0xF8, 0x00, 0xE8, 0x08, 0xC0, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5D, 0xE1, 0xFF, 0x03, 0xF7, 0x0F, 0x3F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0E, 0x02, 0x1E, 0x12, 0x1E, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFD, 0xF1, 0xED, 0xF1, 0xED, 0xE3, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFB, 0xFB, 0x7F, 0x7F, 0x3F, 0x3F, 0x0C, 0x0C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x75, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xDF, 0xC1, 0xDF, 0xD0, 0x8F, 0x88, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0xEF, 0xFF, 0x77, 0xFF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFA, 0x82, 0xF8, 0x08, 0xE0, 0x00, 0x81, 0x81, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7E, 0xFD, 0xF4, 0xFF, 0xFC, 0xFF, 0x7E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x7D, 0x7D, 0x02, 0x02, 0x02, 0x02, 0xFC, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFE, 0x01, 0xFF, 0x01, 0xFF, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x1C, 0xFF, 0x00, 0xFF, 0x41, 0x7F, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF7, 0x08, 0xFF, 0x00, 0xFF, 0x80, 0xE7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x5E, 0xC2, 0x9C, 0x80, 0x1C, 0x04, 0x08, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE1, 0x3F, 0xE3, 0x7F, 0xE3, 0xFF, 0xF7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x80, 0x78, 0x08, 0x78, 0x48, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0xFF, 0x87, 0xFF, 0x87, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xC0, 0x00, 0x3C, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xFF, 0x03, 0x3F, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1C, 0x1C, 0x03, 0x03, 0x00, 0xE0, 0x00, 0x1C, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE3, 0xFF, 0xFC, 0xFF, 0x1F, 0xFF, 0x03, 0x1F, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0xFC, 0x03, 0x03, 0x00, 0x00, + 0x00, 0xFC, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x03, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, + 0x03, 0xFF, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xC0, 0xFF, + 0xFF, 0xFF, 0x3F, 0xFF, 0x00, 0x3F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, + 0x3F, 0x3F, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, + 0xC0, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0x00, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF +}; diff --git a/waterbox/bsnescore/bsnes/gb/Core/graphics/dmg_border.inc b/waterbox/bsnescore/bsnes/gb/Core/graphics/dmg_border.inc new file mode 100644 index 0000000000..7db0673a4d --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/graphics/dmg_border.inc @@ -0,0 +1,558 @@ +static const uint16_t palette[] = { + 0x0000, 0x0011, 0x18C6, 0x001A, 0x318C, 0x39CE, 0x5294, 0x5AD6, + 0x739C, 0x45A8, 0x4520, 0x18A5, 0x4A32, 0x2033, 0x20EC, 0x0000, +}; + +static const uint16_t tilemap[] = { + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0001, 0x0003, 0x0004, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x4004, 0x4003, 0x0001, + 0x0001, 0x0006, 0x0007, 0x0007, 0x0007, 0x0008, 0x0009, 0x000A, + 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x0011, 0x0012, + 0x0013, 0x0014, 0x0015, 0x000E, 0x0016, 0x0017, 0x0018, 0x0019, + 0x001A, 0x001B, 0x001C, 0x0007, 0x0007, 0x0007, 0x4006, 0x0001, + 0x0001, 0x001D, 0x001E, 0x001E, 0x001E, 0x001F, 0x0020, 0x0021, + 0x0022, 0x0023, 0x0024, 0x0025, 0x4024, 0x0026, 0x0025, 0x0025, + 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, + 0x002F, 0x0030, 0x0031, 0x001E, 0x001E, 0x001E, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0034, 0x0035, 0x4034, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x8034, 0x0036, 0xC034, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0xC01D, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0x0037, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0032, 0x0038, 0x0001, + 0x0001, 0x001D, 0x0032, 0x0032, 0x0032, 0x0033, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xC033, 0x0032, 0x0032, 0x0039, 0x003A, 0x0001, + 0x0001, 0x003B, 0x003C, 0x0032, 0x0032, 0xC03C, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0001, 0x0001, + 0x0001, 0x0042, 0x0043, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, 0x0044, + 0x0044, 0x0044, 0x0045, 0x0046, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, + 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, + 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x0001, 0x006C, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, +}; + +const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x01, 0xFE, 0x06, 0xF9, 0x08, 0xF7, + 0x11, 0xEF, 0x22, 0xDB, 0x20, 0xDB, 0x40, 0xB7, + 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF8, 0x00, + 0xF1, 0x00, 0xE6, 0x04, 0xE4, 0x00, 0xC8, 0x00, + 0x7F, 0x80, 0x80, 0x7F, 0x00, 0xFF, 0x7F, 0xFF, + 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0xB7, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0xC8, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFF, 0x02, 0xFF, + 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xDD, 0x00, 0xC9, + 0x14, 0xFF, 0x14, 0xFF, 0x14, 0xFF, 0x00, 0xC9, + 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x36, 0x22, + 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x36, 0x22, + 0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xDF, 0x01, 0xCF, + 0x11, 0xFF, 0x11, 0xFF, 0x11, 0xFF, 0x01, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x31, 0x20, + 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x31, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xC2, 0xFF, 0x03, 0xFF, + 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x03, 0x00, + 0x03, 0x01, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xFF, 0x18, 0xFF, + 0x08, 0x4E, 0x08, 0x4E, 0x09, 0x1F, 0x08, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, + 0xB9, 0x10, 0xB9, 0xA1, 0xE9, 0xA0, 0xEB, 0x41, + 0x00, 0xFF, 0x00, 0xFF, 0x4F, 0xFF, 0x02, 0x1F, + 0x02, 0x4F, 0x02, 0x4F, 0xF2, 0xFF, 0x02, 0xE7, + 0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0xE2, 0xA0, + 0xB2, 0xA0, 0xB2, 0x10, 0xF2, 0x00, 0x1A, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0xBC, 0xFD, 0x22, 0xFB, + 0x22, 0xFB, 0x3C, 0xFD, 0x24, 0xFF, 0x26, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x26, 0x00, + 0x26, 0x00, 0x3E, 0x00, 0x24, 0x00, 0x26, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x50, 0xFF, 0x49, 0xEF, + 0x49, 0xF0, 0x46, 0xFF, 0x49, 0xF0, 0x49, 0xEF, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x59, 0x00, + 0x4F, 0x06, 0x46, 0x00, 0x4F, 0x06, 0x59, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x88, 0xFF, 0x00, 0x72, + 0x00, 0xF2, 0x05, 0xFF, 0x00, 0xF8, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x8D, 0x08, + 0x0D, 0x05, 0x05, 0x00, 0x07, 0x05, 0x87, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0x8A, 0xFF, 0x02, 0x27, + 0x02, 0x27, 0x52, 0xFF, 0x02, 0x8F, 0x02, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x8A, 0x00, 0xDA, 0x88, + 0xDA, 0x50, 0x52, 0x00, 0x72, 0x50, 0x72, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xFA, 0xFF, 0x22, 0xFF, + 0x22, 0xFF, 0x23, 0xFF, 0x22, 0xFF, 0x22, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x22, 0x00, + 0x22, 0x00, 0x23, 0x00, 0x22, 0x00, 0x22, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x20, 0xFF, 0xE0, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0xE0, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x33, 0x37, 0x00, 0x77, + 0x80, 0xFF, 0x20, 0x27, 0x08, 0xFF, 0x00, 0x77, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x40, 0x88, 0x88, + 0x80, 0x00, 0xF8, 0x50, 0x08, 0x00, 0x88, 0x88, + 0x00, 0xFF, 0x00, 0xFF, 0xEF, 0xFF, 0x88, 0xFF, + 0x88, 0xFF, 0x8F, 0xFF, 0x88, 0xFF, 0x88, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x88, 0x00, + 0x88, 0x00, 0x8F, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0xFD, 0x80, 0xF9, + 0x84, 0xFF, 0xF4, 0xFF, 0x84, 0xFF, 0x80, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x86, 0x02, + 0x84, 0x00, 0xF4, 0x00, 0x84, 0x00, 0x86, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0xC0, 0xDF, 0x00, 0xCF, + 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x00, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x30, 0x20, + 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x30, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0x30, 0x36, 0x00, 0x74, + 0x82, 0xFF, 0x22, 0x27, 0x0A, 0xFF, 0x00, 0x74, + 0x00, 0x00, 0x00, 0x00, 0xF9, 0x40, 0x8B, 0x89, + 0x82, 0x00, 0xFA, 0x50, 0x0A, 0x00, 0x8B, 0x89, + 0x00, 0xFF, 0x00, 0xFF, 0xE2, 0xEF, 0x02, 0xE7, + 0x0A, 0xFF, 0x0A, 0xFF, 0x0A, 0xFF, 0x00, 0xE4, + 0x00, 0x00, 0x00, 0x00, 0xF2, 0x00, 0x1A, 0x10, + 0x0A, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x1B, 0x12, + 0x00, 0xFF, 0x00, 0xFF, 0x14, 0xFF, 0x16, 0xFF, + 0x14, 0xFC, 0x15, 0xFE, 0x14, 0xFF, 0x04, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x00, + 0x17, 0x01, 0x15, 0x00, 0x14, 0x00, 0x34, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0xFF, 0x28, 0xFF, + 0x28, 0xFF, 0xA8, 0x7F, 0x28, 0x3F, 0x68, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x28, 0x00, + 0x28, 0x00, 0xA8, 0x00, 0xE8, 0x80, 0x68, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x3F, + 0x40, 0xFF, 0x40, 0xFF, 0x40, 0xFF, 0x00, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x80, + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x80, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xC1, 0xDD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC1, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x4A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x0A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x22, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x67, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x8F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xA2, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF9, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC0, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x66, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF9, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xE0, 0xEE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC4, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE4, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x2F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x3C, 0xFF, + 0x7E, 0xFF, 0xE7, 0xE7, 0xFF, 0x7E, 0xFF, 0x7E, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, + 0x81, 0xC3, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x7E, 0xFF, 0x3C, 0xFF, 0x00, 0x7E, 0x81, + 0x3C, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0xC3, 0x81, + 0x7E, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, + 0x00, 0xF7, 0x00, 0xED, 0x00, 0xED, 0x00, 0xED, + 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x11, 0x02, 0x11, 0x02, 0x11, 0x02, + 0x00, 0xED, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, + 0x00, 0xB7, 0x00, 0xB7, 0x00, 0x6F, 0x00, 0x6F, + 0x11, 0x02, 0x23, 0x04, 0x23, 0x04, 0x23, 0x04, + 0x47, 0x08, 0x47, 0x08, 0x8F, 0x10, 0x8F, 0x10, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF7, + 0x00, 0xEE, 0x00, 0xDD, 0x00, 0xBB, 0x00, 0x77, + 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x10, 0x01, 0x21, 0x02, 0x43, 0x04, 0x87, 0x08, + 0x00, 0xDF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x1F, 0x20, 0x3F, 0x40, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xB7, + 0x00, 0xB7, 0x00, 0xDB, 0x00, 0xDD, 0x00, 0xEE, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x88, 0x40, + 0x88, 0x40, 0xC4, 0x20, 0xC2, 0x20, 0xE1, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFC, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x1C, 0x00, 0xE0, 0x00, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xF3, 0x00, 0xEF, + 0x00, 0x1C, 0x00, 0xF3, 0x00, 0xEF, 0x00, 0x1F, + 0x01, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0xE0, 0x03, 0x03, 0x0C, 0x0F, 0x10, 0x1F, 0xE0, + 0x00, 0xEF, 0x00, 0xDF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x0F, 0x10, 0x1F, 0x20, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xF7, 0x00, 0xF9, 0x00, 0xFE, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF0, 0x08, 0xF8, 0x06, 0xFE, 0x01, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x80, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x03, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0x03, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x03, 0x03, 0x1C, 0x1F, 0xE0, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x21, 0xDE, 0x00, 0x7F, + 0x0C, 0xF3, 0x19, 0xE0, 0x10, 0xEE, 0x08, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x3F, 0x80, 0xFF, + 0x00, 0xFF, 0x0E, 0xF7, 0x1F, 0xE1, 0x07, 0xF8, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0xBF, + 0x40, 0xBE, 0x80, 0x3F, 0x02, 0xFD, 0x00, 0xFB, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0xC0, + 0x7F, 0x81, 0xFE, 0x41, 0xFC, 0x03, 0xFC, 0x07, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF7, 0x00, 0xFF, + 0x00, 0xFB, 0x04, 0xFB, 0x24, 0xDB, 0x64, 0x9B, + 0xFF, 0x00, 0xFF, 0x00, 0x87, 0x78, 0x07, 0xF8, + 0x07, 0xFC, 0x07, 0xF8, 0x03, 0xFC, 0x43, 0xBC, + 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xDE, 0x20, 0xDF, + 0x20, 0xDF, 0x00, 0xFF, 0x04, 0xFB, 0x04, 0xFB, + 0xFF, 0x00, 0xFF, 0x00, 0xE0, 0x3F, 0xC0, 0x3F, + 0xC0, 0x3F, 0xC0, 0x3F, 0xC0, 0x3F, 0xC0, 0x3F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x00, 0xFF, + 0x00, 0x77, 0x00, 0x7F, 0x80, 0x6F, 0x82, 0x7D, + 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xF8, 0x07, + 0xF8, 0x8F, 0xF0, 0x8F, 0x70, 0x9F, 0x60, 0x9F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x24, 0xCB, + 0x24, 0xDB, 0x20, 0xDF, 0x20, 0xDF, 0x00, 0xDF, + 0xFF, 0x00, 0xFF, 0x00, 0x1C, 0xE7, 0x18, 0xF7, + 0x18, 0xE7, 0x18, 0xE7, 0x38, 0xC7, 0x38, 0xE7, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xFC, + 0x7E, 0x81, 0x80, 0x01, 0x80, 0x7F, 0xF8, 0x03, + 0xFF, 0x00, 0xFF, 0x00, 0x01, 0xFE, 0x01, 0xFF, + 0x01, 0xFE, 0x7F, 0xFE, 0x7F, 0x80, 0x07, 0xFC, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x40, 0xBF, + 0x47, 0xB8, 0x08, 0xF0, 0x08, 0xF7, 0x0E, 0xF1, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x80, 0x7F, + 0x80, 0x7F, 0x87, 0x7F, 0x87, 0x78, 0x80, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x40, 0x9F, 0x00, 0xFF, + 0x10, 0xEF, 0x90, 0x6F, 0x10, 0xEB, 0x14, 0xEB, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x1F, 0xE0, + 0x0E, 0xF1, 0x0C, 0xF3, 0x0C, 0xF7, 0x18, 0xE7, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0x9F, 0x00, 0xFF, + 0x0C, 0xF3, 0x31, 0xC0, 0x60, 0x9F, 0x40, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x00, 0xFF, + 0x00, 0xFF, 0x1E, 0xEF, 0x3F, 0xC0, 0x7F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x77, 0x04, 0xDB, + 0x00, 0xFB, 0x10, 0xEF, 0x00, 0xFD, 0x80, 0x77, + 0xFF, 0x00, 0xFF, 0x00, 0x78, 0x8F, 0x38, 0xE7, + 0x1C, 0xE7, 0x0C, 0xF3, 0x0E, 0xF3, 0x0E, 0xF9, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0x00, 0xFF, + 0x40, 0xB7, 0x00, 0xEF, 0x01, 0xDE, 0x02, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0x7C, 0x83, 0x78, 0x87, + 0x38, 0xCF, 0x30, 0xDF, 0x21, 0xFE, 0x03, 0xFD, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xDF, 0x60, 0x9F, + 0xC0, 0x3F, 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x3F, 0xC0, + 0x7F, 0x80, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x01, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFE, 0x01, 0xFC, 0x03, 0xFC, 0x07, 0xFE, 0x03, + 0x00, 0xFF, 0x40, 0x3F, 0x30, 0x8F, 0x00, 0xF7, + 0x80, 0x7F, 0x30, 0xCF, 0x01, 0xFE, 0x87, 0x78, + 0x01, 0xFE, 0x80, 0xFF, 0xE0, 0x5F, 0xF8, 0x0F, + 0x78, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFC, + 0x00, 0xFF, 0x08, 0xF7, 0x80, 0x6F, 0x80, 0x7F, + 0x80, 0x5F, 0x87, 0x78, 0x04, 0x7B, 0x08, 0x73, + 0xF8, 0x07, 0xF0, 0x0F, 0x70, 0x9F, 0x60, 0x9F, + 0x60, 0xBF, 0xC3, 0x3C, 0x87, 0xF8, 0x87, 0xFC, + 0xA0, 0x1F, 0x80, 0x7D, 0xE2, 0x1D, 0x02, 0xFD, + 0x02, 0xFD, 0xF0, 0x0F, 0x10, 0xEE, 0x11, 0xEE, + 0x43, 0xFC, 0xE3, 0x1E, 0x03, 0xFC, 0x01, 0xFE, + 0x01, 0xFE, 0xE1, 0x1E, 0xE1, 0x1F, 0xE1, 0x1E, + 0x44, 0xBB, 0x48, 0xB3, 0x48, 0xB7, 0x08, 0xF7, + 0x0A, 0xF5, 0x02, 0xF5, 0x80, 0x77, 0x90, 0x67, + 0x84, 0x7B, 0x84, 0x7F, 0x84, 0x7B, 0x84, 0x7B, + 0x8C, 0x73, 0x8C, 0x7B, 0x0E, 0xF9, 0x0E, 0xF9, + 0x86, 0x59, 0x06, 0xF9, 0x48, 0xB3, 0x08, 0xF7, + 0x10, 0xE7, 0x14, 0xEB, 0x24, 0xCB, 0x20, 0xDF, + 0x60, 0xBF, 0x44, 0xBB, 0x04, 0xFF, 0x0C, 0xF3, + 0x0C, 0xFB, 0x18, 0xE7, 0x18, 0xF7, 0x38, 0xC7, + 0x08, 0xD7, 0x48, 0x97, 0x48, 0xB7, 0x41, 0xBE, + 0x41, 0xBE, 0x01, 0xBE, 0x10, 0xAF, 0x90, 0x2F, + 0x30, 0xEF, 0x30, 0xEF, 0x30, 0xCF, 0x30, 0xCF, + 0x70, 0x8F, 0x70, 0xCF, 0x60, 0xDF, 0x60, 0xDF, + 0x04, 0xFB, 0x04, 0xFB, 0xFC, 0x03, 0x00, 0xFF, + 0x00, 0xFF, 0xF8, 0x02, 0x05, 0xFA, 0x05, 0xFA, + 0x03, 0xFC, 0x03, 0xFC, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x07, 0xFD, 0x02, 0xFD, 0x06, 0xF9, + 0x80, 0x7F, 0x80, 0x7F, 0x0F, 0xF0, 0x10, 0xE7, + 0x10, 0xEE, 0x1E, 0xE1, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x0E, 0xF1, 0x0F, 0xF8, + 0x0F, 0xF1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x60, 0x8F, 0x00, 0xDF, 0x00, 0xFF, 0x00, 0xEF, + 0x04, 0xEB, 0x20, 0xCF, 0x22, 0xDD, 0xC1, 0x1E, + 0x38, 0xD7, 0x38, 0xE7, 0x18, 0xE7, 0x18, 0xF7, + 0x18, 0xF7, 0x1C, 0xF3, 0x3E, 0xC1, 0x7F, 0xA0, + 0x80, 0x3F, 0x80, 0x7F, 0x80, 0x7F, 0x01, 0xFE, + 0x00, 0xBD, 0x18, 0xE7, 0x00, 0xFF, 0x83, 0x7C, + 0x7F, 0xC0, 0x7F, 0x80, 0x7F, 0x80, 0x7E, 0x81, + 0x7E, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x81, 0x76, 0x80, 0x77, 0x10, 0xE7, 0x10, 0xEF, + 0x10, 0xEF, 0x21, 0xCE, 0x41, 0x9E, 0x81, 0x3E, + 0x0E, 0xF9, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF0, + 0x1F, 0xE0, 0x3E, 0xD1, 0x7E, 0xA1, 0xFE, 0x41, + 0x04, 0xF9, 0x08, 0xF3, 0x18, 0xE7, 0x10, 0xEF, + 0x10, 0xEF, 0x10, 0xEF, 0x00, 0xEF, 0x20, 0xCF, + 0x07, 0xFA, 0x07, 0xFC, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF0, 0x1F, 0xE0, 0x1F, 0xF0, 0x1F, 0xF0, + 0x7C, 0x01, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x78, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0F, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x70, 0x8E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC3, 0x18, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x24, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x8F, 0x70, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF8, 0x03, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x04, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x3E, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xC1, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xE0, 0x1F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, +}; diff --git a/waterbox/bsnescore/bsnes/gb/Core/graphics/sgb_animation_logo.inc b/waterbox/bsnescore/bsnes/gb/Core/graphics/sgb_animation_logo.inc new file mode 100644 index 0000000000..75075f4921 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/graphics/sgb_animation_logo.inc @@ -0,0 +1,563 @@ +static uint8_t animation_logo[] = { + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x3, 0x3, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, + 0x4, 0x4, 0x4, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, + 0x4, 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE, 0xE, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, + 0x4, 0x4, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x4, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x1, + 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x0, 0x0, 0x1, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0xC, 0xC, 0xC, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0x1, + 0x0, 0x0, 0xE, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7, 0x7, 0x7, 0x9, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0x1, + 0x1, 0xE, 0xE, 0xE, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x1, 0x0, 0x0, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x7, 0x7, 0x7, 0x9, 0x1, 0x1, 0x1, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC, + 0xC, 0xC, 0xC, 0xC, 0x1, 0x1, 0xC, 0xC, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0xE, + 0xE, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x1, 0x1, 0x0, 0x1, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, + 0x7, 0x7, 0x9, 0x1, 0x1, 0x0, 0x0, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0xC, 0xC, + 0xC, 0xC, 0x1, 0x1, 0x0, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0xE, + 0xE, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x5, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, + 0x7, 0x7, 0x1, 0x1, 0x0, 0x0, 0x1, 0x9, 0x9, 0x9, 0x0, 0x0, 0x0, 0x1, 0xC, 0xC, + 0xC, 0x1, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0x1, 0xD, 0x1, 0x1, 0x1, + 0x1, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, + 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, + 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0xC, 0xC, 0xC, + 0xC, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x0, + 0xE, 0xE, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, + 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, + 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, + 0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, + 0x1, 0x1, 0x0, 0x1, 0xC, 0xC, 0xC, 0x1, 0x1, 0x0, 0x1, 0xD, 0x1, 0x1, 0x0, 0xF, + 0xF, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x5, 0x5, 0x5, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, + 0x1, 0x0, 0x1, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x1, 0xF, + 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x7, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x1, 0xC, 0xC, 0xB, 0xB, + 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0xF, 0xF, + 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, + 0x0, 0x0, 0x1, 0x6, 0x6, 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x1, 0x0, 0xC, 0xC, 0xB, 0xB, 0x1, + 0xC, 0xC, 0xC, 0xC, 0x1, 0x1, 0x1, 0x0, 0x1, 0xD, 0x1, 0x1, 0x0, 0x1, 0xF, 0xF, + 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x6, 0x6, 0x7, 0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xB, 0xB, 0xB, 0x1, + 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x4, 0x1, 0x0, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, + 0x0, 0x6, 0x6, 0x7, 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x8, 0x1, + 0x0, 0x0, 0x0, 0x1, 0x9, 0x9, 0xA, 0x1, 0x1, 0x0, 0xC, 0xB, 0xB, 0xB, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, + 0x1, 0x6, 0x7, 0x7, 0x7, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x8, 0xA, 0x1, + 0x0, 0x0, 0x0, 0x9, 0x9, 0xA, 0xA, 0x1, 0x0, 0x1, 0xB, 0xB, 0xB, 0xD, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x6, 0x6, 0x1, 0x1, 0x0, 0x1, + 0x6, 0x1, 0x7, 0x7, 0x7, 0x1, 0x0, 0x0, 0x1, 0x7, 0x7, 0x8, 0x8, 0xA, 0xA, 0x1, + 0x0, 0x0, 0xA, 0xA, 0xA, 0xA, 0x1, 0x1, 0x0, 0xB, 0xB, 0x1, 0xD, 0xD, 0xD, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x5, + 0x5, 0x1, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x0, 0x6, 0x6, 0x1, 0x0, 0x1, 0x6, + 0x1, 0x1, 0x7, 0x7, 0x7, 0x1, 0x0, 0x1, 0x7, 0x7, 0x8, 0x8, 0x1, 0x1, 0xA, 0xA, + 0x1, 0xA, 0xA, 0xA, 0xA, 0x1, 0x1, 0xB, 0xB, 0xB, 0x1, 0x1, 0x1, 0xD, 0xD, 0x1, + 0x0, 0x0, 0x0, 0x1, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x1, 0x0, 0x1, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, + 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, 0x1, 0x1, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, 0x1, + 0x1, 0x0, 0x1, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x8, 0x8, 0x8, 0x1, 0x0, 0x1, 0xA, + 0xA, 0xA, 0xA, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0x1, 0x1, 0x0, 0x0, 0xD, 0xD, 0xD, + 0xD, 0xD, 0xD, 0xD, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, + 0xF, 0xF, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x5, + 0x5, 0x5, 0x5, 0x5, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0x1, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, 0x1, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, + 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0xD, + 0xD, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xF, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x8, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x1, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, + 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, + 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, + 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 +}; +static const unsigned animation_logo_height = sizeof(animation_logo) / 160; diff --git a/waterbox/bsnescore/bsnes/gb/Core/graphics/sgb_border.inc b/waterbox/bsnescore/bsnes/gb/Core/graphics/sgb_border.inc new file mode 100644 index 0000000000..d7d0a5c980 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/graphics/sgb_border.inc @@ -0,0 +1,658 @@ +static const uint16_t palette[] = { + 0x0000, 0x0011, 0x18C6, 0x001A, 0x318C, 0x39CE, 0x5294, 0x5AD6, + 0x739C, 0x45A8, 0x4520, 0x18A5, 0x4631, 0x2033, 0x20EC, 0x18B7 +}; + +static const uint16_t tilemap[] = { + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, + 0x1001, 0x1003, 0x1004, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, + 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, + 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, + 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x5004, 0x5003, 0x1001, + 0x1001, 0x1006, 0x1007, 0x1007, 0x1007, 0x1008, 0x1009, 0x100A, + 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, 0x1011, 0x1012, + 0x1013, 0x1014, 0x1015, 0x100E, 0x1016, 0x1017, 0x1018, 0x1019, + 0x101A, 0x101B, 0x101C, 0x1007, 0x1007, 0x1007, 0x5006, 0x1001, + 0x1001, 0x101D, 0x101E, 0x101E, 0x101E, 0x101F, 0x1020, 0x1021, + 0x1022, 0x1023, 0x1024, 0x1025, 0x5024, 0x1026, 0x1025, 0x1025, + 0x1027, 0x1028, 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, + 0x102F, 0x1030, 0x1031, 0x101E, 0x101E, 0x101E, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1034, 0x1035, 0x5034, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x8034, 0x1036, 0xC034, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0x1037, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0x1038, 0x1001, + 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, + 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1039, 0x103A, 0x1001, + 0x1001, 0x103B, 0x103C, 0x1032, 0x1032, 0xC03C, 0x103D, 0x103D, + 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, + 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, + 0x103D, 0x103D, 0x103E, 0x103F, 0x1040, 0x1041, 0x1001, 0x1001, + 0x1001, 0x1042, 0x1043, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, + 0x1044, 0x1044, 0x1045, 0x1046, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1047, 0x1048, 0x1049, + 0x104A, 0x104B, 0x104C, 0x104D, 0x104E, 0x104F, 0x1050, 0x1051, + 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, 0x1058, 0x1059, + 0x105A, 0x105B, 0x105C, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x105D, 0x105E, 0x105F, + 0x1060, 0x1061, 0x1062, 0x1063, 0x1064, 0x1065, 0x1066, 0x1067, + 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, 0x106D, 0x106E, 0x106F, + 0x1070, 0x1071, 0x1072, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, + 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1073, 0x1074, 0x1075, + 0x1076, 0x1077, 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, + 0x107E, 0x107F, 0x1080, 0x1081, 0x1082, 0x1083, 0x507A, 0x1084, + 0x1001, 0x1085, 0x507A, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, +}; + +const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x01, 0xFE, 0x06, 0xF9, 0x08, 0xF7, + 0x11, 0xEF, 0x22, 0xDB, 0x20, 0xDB, 0x40, 0xB7, + 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF8, 0x00, + 0xF1, 0x00, 0xE6, 0x04, 0xE4, 0x00, 0xC8, 0x00, + 0x7F, 0x80, 0x80, 0x7F, 0x00, 0xFF, 0x7F, 0xFF, + 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0xB7, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0xC8, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFF, 0x02, 0xFF, + 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xDD, 0x00, 0xC9, + 0x14, 0xFF, 0x14, 0xFF, 0x14, 0xFF, 0x00, 0xC9, + 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x36, 0x22, + 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x36, 0x22, + 0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xDF, 0x01, 0xCF, + 0x11, 0xFF, 0x11, 0xFF, 0x11, 0xFF, 0x01, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x31, 0x20, + 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x31, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xC2, 0xFF, 0x03, 0xFF, + 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFF, 0x02, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x03, 0x00, + 0x03, 0x01, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xFF, 0x18, 0xFF, + 0x08, 0x4E, 0x08, 0x4E, 0x09, 0x1F, 0x08, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, + 0xB9, 0x10, 0xB9, 0xA1, 0xE9, 0xA0, 0xEB, 0x41, + 0x00, 0xFF, 0x00, 0xFF, 0x4F, 0xFF, 0x02, 0x1F, + 0x02, 0x4F, 0x02, 0x4F, 0xF2, 0xFF, 0x02, 0xE7, + 0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0xE2, 0xA0, + 0xB2, 0xA0, 0xB2, 0x10, 0xF2, 0x00, 0x1A, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0xBC, 0xFD, 0x22, 0xFB, + 0x22, 0xFB, 0x3C, 0xFD, 0x24, 0xFF, 0x26, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x26, 0x00, + 0x26, 0x00, 0x3E, 0x00, 0x24, 0x00, 0x26, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x50, 0xFF, 0x49, 0xEF, + 0x49, 0xF0, 0x46, 0xFF, 0x49, 0xF0, 0x49, 0xEF, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x59, 0x00, + 0x4F, 0x06, 0x46, 0x00, 0x4F, 0x06, 0x59, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x88, 0xFF, 0x00, 0x72, + 0x00, 0xF2, 0x05, 0xFF, 0x00, 0xF8, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x8D, 0x08, + 0x0D, 0x05, 0x05, 0x00, 0x07, 0x05, 0x87, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0x8A, 0xFF, 0x02, 0x27, + 0x02, 0x27, 0x52, 0xFF, 0x02, 0x8F, 0x02, 0x8F, + 0x00, 0x00, 0x00, 0x00, 0x8A, 0x00, 0xDA, 0x88, + 0xDA, 0x50, 0x52, 0x00, 0x72, 0x50, 0x72, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0xFA, 0xFF, 0x22, 0xFF, + 0x22, 0xFF, 0x23, 0xFF, 0x22, 0xFF, 0x22, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x22, 0x00, + 0x22, 0x00, 0x23, 0x00, 0x22, 0x00, 0x22, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x20, 0xFF, 0xE0, 0xFF, 0x20, 0xFF, 0x20, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0xE0, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x33, 0x37, 0x00, 0x77, + 0x80, 0xFF, 0x20, 0x27, 0x08, 0xFF, 0x00, 0x77, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x40, 0x88, 0x88, + 0x80, 0x00, 0xF8, 0x50, 0x08, 0x00, 0x88, 0x88, + 0x00, 0xFF, 0x00, 0xFF, 0xEF, 0xFF, 0x88, 0xFF, + 0x88, 0xFF, 0x8F, 0xFF, 0x88, 0xFF, 0x88, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x88, 0x00, + 0x88, 0x00, 0x8F, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0xFD, 0x80, 0xF9, + 0x84, 0xFF, 0xF4, 0xFF, 0x84, 0xFF, 0x80, 0xF9, + 0x00, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x86, 0x02, + 0x84, 0x00, 0xF4, 0x00, 0x84, 0x00, 0x86, 0x02, + 0x00, 0xFF, 0x00, 0xFF, 0xC0, 0xDF, 0x00, 0xCF, + 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x00, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x30, 0x20, + 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x30, 0x20, + 0x00, 0xFF, 0x00, 0xFF, 0x30, 0x36, 0x00, 0x74, + 0x82, 0xFF, 0x22, 0x27, 0x0A, 0xFF, 0x00, 0x74, + 0x00, 0x00, 0x00, 0x00, 0xF9, 0x40, 0x8B, 0x89, + 0x82, 0x00, 0xFA, 0x50, 0x0A, 0x00, 0x8B, 0x89, + 0x00, 0xFF, 0x00, 0xFF, 0xE2, 0xEF, 0x02, 0xE7, + 0x0A, 0xFF, 0x0A, 0xFF, 0x0A, 0xFF, 0x00, 0xE4, + 0x00, 0x00, 0x00, 0x00, 0xF2, 0x00, 0x1A, 0x10, + 0x0A, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x1B, 0x12, + 0x00, 0xFF, 0x00, 0xFF, 0x14, 0xFF, 0x16, 0xFF, + 0x14, 0xFC, 0x15, 0xFE, 0x14, 0xFF, 0x04, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x00, + 0x17, 0x01, 0x15, 0x00, 0x14, 0x00, 0x34, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0xFF, 0x28, 0xFF, + 0x28, 0xFF, 0xA8, 0x7F, 0x28, 0x3F, 0x68, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x28, 0x00, + 0x28, 0x00, 0xA8, 0x00, 0xE8, 0x80, 0x68, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x3F, + 0x40, 0xFF, 0x40, 0xFF, 0x40, 0xFF, 0x00, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x80, + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x80, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xC1, 0xDD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC1, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x4A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x0A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x22, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x67, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x8F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xA2, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF9, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC0, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x60, 0x66, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF9, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xE0, 0xEE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xF1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xC4, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xE4, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x2F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x3C, 0xFF, + 0x7E, 0xFF, 0xE7, 0xE7, 0xFF, 0x7E, 0xFF, 0x7E, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, + 0x81, 0xC3, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0x7E, 0xFF, 0x3C, 0xFF, 0x00, 0x7E, 0x81, + 0x3C, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0xC3, 0x81, + 0x7E, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, + 0x00, 0xF7, 0x00, 0xED, 0x00, 0xED, 0x00, 0xED, + 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, + 0x09, 0x00, 0x11, 0x02, 0x11, 0x02, 0x11, 0x02, + 0x00, 0xED, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB, + 0x00, 0xB7, 0x00, 0xB7, 0x00, 0x6F, 0x00, 0x6F, + 0x11, 0x02, 0x23, 0x04, 0x23, 0x04, 0x23, 0x04, + 0x47, 0x08, 0x47, 0x08, 0x8F, 0x10, 0x8F, 0x10, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF7, + 0x00, 0xEE, 0x00, 0xDD, 0x00, 0xBB, 0x00, 0x77, + 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x10, 0x01, 0x21, 0x02, 0x43, 0x04, 0x87, 0x08, + 0x00, 0xDF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x1F, 0x20, 0x3F, 0x40, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xB7, + 0x00, 0xB7, 0x00, 0xDB, 0x00, 0xDD, 0x00, 0xEE, + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x88, 0x40, + 0x88, 0x40, 0xC4, 0x20, 0xC2, 0x20, 0xE1, 0x10, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFC, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x1C, 0x00, 0xE0, 0x00, + 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xF3, 0x00, 0xEF, + 0x00, 0x1C, 0x00, 0xF3, 0x00, 0xEF, 0x00, 0x1F, + 0x01, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0xE0, 0x03, 0x03, 0x0C, 0x0F, 0x10, 0x1F, 0xE0, + 0x00, 0xEF, 0x00, 0xDF, 0x00, 0xBF, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x0F, 0x10, 0x1F, 0x20, 0x3F, 0x40, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xF7, 0x00, 0xF9, 0x00, 0xFE, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xF0, 0x08, 0xF8, 0x06, 0xFE, 0x01, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x80, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0x03, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0x03, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0xFC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x03, 0x03, 0x1C, 0x1F, 0xE0, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x01, 0xFF, 0x01, 0xFD, 0x03, 0xFF, 0x03, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x01, 0xFE, 0x02, 0xFE, 0x02, 0xFC, 0x00, + 0x0E, 0xEE, 0x3F, 0xFF, 0x75, 0x71, 0xFB, 0xE7, + 0xE3, 0xCB, 0xC7, 0x9F, 0x07, 0x3E, 0x84, 0x7C, + 0xFB, 0x1B, 0xE6, 0x26, 0x8E, 0x82, 0x3E, 0x22, + 0x7C, 0x54, 0x7D, 0x25, 0xF9, 0x40, 0xFB, 0x01, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x80, 0xFF, + 0x00, 0x7F, 0x80, 0x4F, 0x31, 0x7F, 0x71, 0xFD, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x80, + 0xFF, 0x00, 0xFF, 0x30, 0xFF, 0xB1, 0xDE, 0x52, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0x7B, 0x87, 0xFF, 0x8E, 0xFE, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x84, 0xFA, 0x82, 0xF9, 0x88, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xFD, 0xE3, 0x7B, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xC3, 0xBC, 0x24, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x01, 0xFF, 0x03, 0xFF, 0xE3, 0xFB, 0xF7, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x01, 0xFE, 0x02, 0x7C, 0x64, 0xFC, 0xB4, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x7F, 0x80, 0xFF, 0xA0, 0x2F, 0xF0, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x70, 0x8F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xFD, 0x00, 0xF7, + 0x00, 0xFF, 0x11, 0xEE, 0x11, 0xEE, 0x10, 0xEF, + 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x03, 0xF8, 0x0F, + 0xF0, 0x0F, 0xE0, 0x1F, 0xE1, 0x1E, 0xE0, 0x1F, + 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xE7, 0x00, 0xFB, + 0xC4, 0x3B, 0x98, 0x03, 0x00, 0xEF, 0x80, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0xF8, 0x07, 0xFC, + 0x07, 0xF8, 0xEF, 0x74, 0xFF, 0x10, 0x7F, 0x80, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xEF, 0x00, 0xFF, 0x22, 0xDD, 0x06, 0xB9, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x07, 0xF0, 0x0F, + 0xF0, 0x1F, 0xE0, 0x1F, 0xC0, 0x3F, 0xC4, 0x7B, + 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7D, 0x02, 0xFD, + 0x02, 0xBD, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF, + 0xFF, 0x00, 0xFF, 0x00, 0x7E, 0x83, 0x7C, 0x83, + 0x7C, 0xC3, 0x7C, 0x83, 0x3C, 0xC3, 0x3C, 0xC3, + 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xEF, 0x00, 0xFF, + 0x00, 0xF7, 0x00, 0xF7, 0x48, 0xB6, 0x48, 0xB7, + 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF8, 0x0F, 0xF8, 0x07, 0xF9, 0x06, 0xF9, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x02, 0xFC, + 0x02, 0x7D, 0x02, 0xFD, 0x02, 0xFD, 0x20, 0xDD, + 0xFF, 0x00, 0xFF, 0x00, 0xC1, 0x7E, 0x81, 0x7F, + 0x81, 0xFE, 0x01, 0xFE, 0x03, 0xFC, 0x03, 0xFE, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x40, 0xBF, + 0x47, 0xB8, 0x08, 0xF0, 0x08, 0xF7, 0x0F, 0xF0, + 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x80, 0x7F, + 0x80, 0x7F, 0x87, 0x7F, 0x87, 0x78, 0x80, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x24, 0xCB, + 0xE4, 0x1B, 0x00, 0x1F, 0x00, 0xFF, 0x80, 0x3F, + 0xFF, 0x00, 0xFF, 0x00, 0x1C, 0xE7, 0x18, 0xF7, + 0x18, 0xE7, 0xF8, 0xE7, 0xF8, 0x07, 0x78, 0xC7, + 0x00, 0xFF, 0x00, 0xFF, 0x04, 0xF9, 0x00, 0xFF, + 0x71, 0x8E, 0x89, 0x06, 0x81, 0x7E, 0xE1, 0x1E, + 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFE, 0x01, 0xFE, + 0x00, 0xFF, 0x70, 0xFF, 0x70, 0x8F, 0x01, 0xFE, + 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xF9, 0x00, 0xFF, + 0x00, 0xFF, 0x03, 0xFC, 0x06, 0xB9, 0x44, 0xBB, + 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xF0, 0x0F, + 0xE0, 0x1F, 0xC1, 0x3E, 0xC3, 0x7C, 0x87, 0x78, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF7, 0x00, 0xFD, + 0xC0, 0x3F, 0x11, 0x0E, 0x00, 0xFF, 0x08, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x03, 0xFE, + 0x01, 0xFE, 0xE0, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0x77, 0x40, 0xBF, + 0x04, 0xBB, 0x00, 0xFE, 0x00, 0xDD, 0x00, 0x7F, + 0xFF, 0x00, 0xFF, 0x00, 0x87, 0xF8, 0x87, 0x78, + 0xC3, 0x7C, 0xC3, 0x3D, 0xE2, 0x3F, 0xE0, 0x9F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFD, 0x06, 0xF9, + 0x0C, 0x73, 0x08, 0xF7, 0x10, 0xE7, 0x20, 0xCF, + 0xFF, 0x00, 0xFF, 0x00, 0xC3, 0x3E, 0x83, 0x7C, + 0x87, 0xF8, 0x0F, 0xF0, 0x1F, 0xE8, 0x3F, 0xD0, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF7, 0x00, 0xFF, + 0x01, 0xDE, 0x06, 0xF8, 0x1C, 0xC3, 0x00, 0xF3, + 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x0F, 0xE0, 0x1F, + 0xE0, 0x3F, 0xC3, 0x3D, 0xE7, 0x38, 0xFF, 0x0C, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xDF, 0x00, 0xFF, + 0x00, 0xF7, 0x08, 0x77, 0x08, 0xF7, 0x08, 0xF7, + 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x0F, 0xF0, + 0x0F, 0xF8, 0x87, 0xF8, 0x87, 0x78, 0x07, 0xF8, + 0x03, 0xFF, 0x03, 0xFF, 0x01, 0xFF, 0x00, 0xFE, + 0x18, 0xDF, 0x1C, 0xFD, 0x0F, 0xEF, 0x07, 0xF7, + 0xFC, 0x00, 0xFE, 0x02, 0xFF, 0x01, 0xFF, 0x01, + 0xFF, 0x38, 0xF3, 0x12, 0xF9, 0x19, 0xFC, 0x0C, + 0x02, 0x79, 0x80, 0xFD, 0xC0, 0xDF, 0xF0, 0xFE, + 0x79, 0x3F, 0x19, 0xDB, 0x19, 0xFB, 0xF9, 0xF7, + 0xFF, 0x84, 0xFF, 0x82, 0x7F, 0x60, 0x9F, 0x91, + 0xEF, 0xA9, 0xF6, 0x34, 0xFE, 0x1C, 0x1F, 0x11, + 0x63, 0xEF, 0xF3, 0xEB, 0xC6, 0xCE, 0xEF, 0xDE, + 0x8C, 0x9C, 0xDE, 0xBD, 0x9C, 0x9D, 0xFF, 0xEF, + 0x9E, 0x02, 0xBC, 0xA4, 0x3D, 0x14, 0x7B, 0x4A, + 0x73, 0x21, 0xF7, 0x94, 0xF7, 0xF6, 0xFE, 0xEE, + 0x8D, 0xEC, 0x9E, 0x7D, 0x1C, 0x5B, 0x38, 0xFA, + 0x79, 0xF7, 0x71, 0x75, 0xF3, 0xF3, 0xEF, 0xCF, + 0xF3, 0x90, 0xF7, 0x14, 0xEF, 0xA8, 0xEF, 0x2D, + 0xCF, 0x41, 0x8E, 0x8A, 0x3C, 0x3C, 0x39, 0x19, + 0x67, 0xFF, 0xEF, 0xFE, 0xEC, 0xDC, 0xCF, 0xCF, + 0xDD, 0xDC, 0xDC, 0x9F, 0x2C, 0x2F, 0xD7, 0xC7, + 0xB9, 0x21, 0xBB, 0xAA, 0xB3, 0x81, 0x76, 0x76, + 0x77, 0x76, 0xE7, 0xA4, 0xD7, 0x44, 0xFB, 0xCB, + 0xB3, 0x37, 0x73, 0x72, 0xF4, 0xEC, 0xEF, 0xCD, + 0xCD, 0x09, 0x11, 0xF3, 0x29, 0xA7, 0xF1, 0xCF, + 0xCD, 0x49, 0xDF, 0xDE, 0xBF, 0xA5, 0x7F, 0x5D, + 0xF6, 0x32, 0xFE, 0x14, 0xFE, 0x70, 0xFF, 0xC1, + 0xF0, 0x77, 0xF0, 0x67, 0xE0, 0xCF, 0x80, 0x97, + 0xC8, 0xBB, 0x98, 0xBB, 0x90, 0xD3, 0xE8, 0xE7, + 0xDF, 0x58, 0xBF, 0x28, 0x7F, 0x50, 0x7F, 0x28, + 0xF7, 0x84, 0xFF, 0xDC, 0xEF, 0xA4, 0xDF, 0xC0, + 0x00, 0xFF, 0x04, 0xF3, 0x03, 0xF8, 0x00, 0xFF, + 0x08, 0xF7, 0x03, 0xFC, 0x00, 0xBF, 0x18, 0xC7, + 0xF0, 0x0F, 0xF8, 0x0F, 0xFE, 0x05, 0xFF, 0x00, + 0xE7, 0x18, 0xC0, 0x3F, 0xC0, 0x7F, 0xE0, 0x3F, + 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF6, 0x08, 0x77, + 0x08, 0xF5, 0x08, 0xF7, 0x10, 0xE7, 0x70, 0x87, + 0x1F, 0xE0, 0x0F, 0xF0, 0x07, 0xF9, 0x86, 0xF9, + 0x86, 0x7B, 0x0C, 0xF3, 0x08, 0xFF, 0x38, 0xCF, + 0x0A, 0xF1, 0x88, 0x77, 0x0E, 0xF1, 0x00, 0xFF, + 0x00, 0xFF, 0x7F, 0x80, 0x41, 0xBE, 0x81, 0x3E, + 0x84, 0x7F, 0x0E, 0xF1, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x3E, 0xC1, 0x7E, 0x81, 0x7E, 0xC1, + 0x04, 0xFB, 0x04, 0xDB, 0x24, 0xDB, 0x20, 0xDF, + 0x20, 0xDF, 0x00, 0xFF, 0x08, 0xE7, 0x19, 0xE6, + 0x38, 0xC7, 0x38, 0xE7, 0x38, 0xC7, 0x18, 0xE7, + 0x18, 0xE7, 0x18, 0xE7, 0x10, 0xFF, 0x10, 0xEF, + 0x48, 0xB5, 0x80, 0x3F, 0x84, 0x7B, 0x80, 0x7F, + 0xA1, 0x5E, 0x21, 0x5E, 0x02, 0x7C, 0x02, 0x7D, + 0x46, 0xBB, 0x44, 0xFB, 0x40, 0xBF, 0x40, 0xBF, + 0xC0, 0x3F, 0xC1, 0xBE, 0xE1, 0x9F, 0xE3, 0x9C, + 0x60, 0x9D, 0x64, 0x99, 0x84, 0x3B, 0x84, 0x7B, + 0x04, 0x7B, 0x40, 0xBB, 0x41, 0xBA, 0x09, 0xF2, + 0x03, 0xFE, 0x43, 0xBE, 0x43, 0xFC, 0xC3, 0x3C, + 0xC7, 0xB8, 0x87, 0x7C, 0x86, 0x7D, 0x86, 0x7D, + 0x80, 0x7F, 0x80, 0x7F, 0x8F, 0x70, 0x10, 0xEF, + 0x10, 0xEF, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x48, 0xB7, 0x48, 0xB7, 0xC0, 0x3F, 0x01, 0xFE, + 0x01, 0xFE, 0x81, 0x2E, 0x50, 0xAF, 0x50, 0xAF, + 0x30, 0xCF, 0x30, 0xCF, 0xF0, 0x0F, 0xF0, 0x0F, + 0xF0, 0x0F, 0x70, 0xDF, 0x20, 0xDF, 0x60, 0x9F, + 0x06, 0xF8, 0x00, 0xFD, 0xF0, 0x0F, 0x00, 0x7E, + 0x00, 0xEE, 0xE2, 0x1C, 0x02, 0xFD, 0x0C, 0xF1, + 0x03, 0xFD, 0x03, 0xFE, 0xE1, 0x1E, 0xF1, 0x8F, + 0xF1, 0x1F, 0x01, 0xFF, 0x03, 0xFC, 0x07, 0xFA, + 0x08, 0xF3, 0x08, 0xF7, 0x08, 0xF7, 0x00, 0xFF, + 0x40, 0xBB, 0x01, 0xFE, 0x20, 0xDF, 0x18, 0xE7, + 0x87, 0x7C, 0x87, 0x78, 0x87, 0x78, 0x87, 0x78, + 0x87, 0x7C, 0xC0, 0x3F, 0xE0, 0x1F, 0xF0, 0x0F, + 0x08, 0xF7, 0x08, 0xF7, 0x01, 0xFE, 0x11, 0xEE, + 0x01, 0xDE, 0x82, 0x7C, 0x04, 0xF9, 0x38, 0xC3, + 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xE0, 0x1F, + 0xE1, 0x3E, 0x03, 0xFD, 0x07, 0xFA, 0x0F, 0xF4, + 0x10, 0x6F, 0x00, 0x7F, 0x01, 0x7E, 0x01, 0xFE, + 0x01, 0xFE, 0x11, 0xEE, 0x10, 0xEE, 0x12, 0xEC, + 0xE0, 0x9F, 0xF0, 0x8F, 0xF0, 0x8F, 0xF0, 0x0F, + 0xF0, 0x0F, 0xE1, 0x1E, 0xE1, 0x1F, 0xE1, 0x1F, + 0x40, 0x9F, 0x80, 0x3F, 0x80, 0x7F, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0xA0, 0x7F, 0xC0, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFB, 0x08, 0xF7, 0x00, 0xFF, + 0x01, 0xBE, 0x03, 0xFC, 0x00, 0x7F, 0x80, 0x7F, + 0xFE, 0x01, 0xFC, 0x07, 0xF0, 0x0F, 0xE0, 0x1F, + 0xC1, 0x7E, 0x80, 0x7F, 0x80, 0xFF, 0x00, 0xFF, + 0x08, 0xF7, 0x10, 0xE7, 0x60, 0x8F, 0xC0, 0x3F, + 0x80, 0x7F, 0xE0, 0x0F, 0x00, 0xEF, 0x00, 0xEF, + 0x0F, 0xF0, 0x1F, 0xE8, 0x3F, 0xD0, 0x7F, 0x80, + 0xFF, 0x00, 0x1F, 0xF0, 0x1F, 0xF0, 0x1F, 0xF0, + 0x02, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x04, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xD0, 0xC6, 0x00, 0x1F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xC9, 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xE7, 0x86, 0x01, 0x39, 0x01, 0xFF, 0x03, 0xFF, + 0x03, 0xFF, 0x00, 0xFC, 0x00, 0xFE, 0x00, 0xFF, + 0xFF, 0x9E, 0xFF, 0xC7, 0xFE, 0x00, 0xFE, 0x02, + 0xFF, 0x03, 0xFF, 0x02, 0xFF, 0x01, 0xFF, 0x00, + 0xC3, 0xD3, 0xC0, 0xBC, 0x80, 0xBF, 0x00, 0x7F, + 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0x7F, 0x6B, 0x7F, 0x03, 0xFF, 0xC0, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, + 0xC7, 0x1B, 0x00, 0x7C, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x23, 0xFF, 0x83, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC0, 0x1F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x20, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x50, 0x4F, 0x00, 0x9F, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x40, 0xFF, 0x60, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x07, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xC7, 0x18, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF7, 0x08, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0C, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x12, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x38, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x8F, 0x30, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xF0, 0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x03, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x0C, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x0E, 0xF1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x7F, 0x80, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00 +}; diff --git a/waterbox/bsnescore/bsnes/gb/Core/joypad.c b/waterbox/bsnescore/bsnes/gb/Core/joypad.c new file mode 100644 index 0000000000..b8d4fdb475 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/joypad.c @@ -0,0 +1,92 @@ +#include "gb.h" +#include + +void GB_update_joyp(GB_gameboy_t *gb) +{ + if (gb->model & GB_MODEL_NO_SFC_BIT) return; + + uint8_t key_selection = 0; + uint8_t previous_state = 0; + + /* Todo: add delay to key selection */ + previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; + key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3; + gb->io_registers[GB_IO_JOYP] &= 0xF0; + uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1) & 3) : 0; + switch (key_selection) { + case 3: + if (gb->sgb && gb->sgb->player_count > 1) { + gb->io_registers[GB_IO_JOYP] |= 0xF - current_player; + } + else { + /* Nothing is wired, all up */ + gb->io_registers[GB_IO_JOYP] |= 0x0F; + } + break; + + case 2: + /* Direction keys */ + for (uint8_t i = 0; i < 4; i++) { + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i]) << i; + } + /* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */ + if (!(gb->io_registers[GB_IO_JOYP] & 1)) { + gb->io_registers[GB_IO_JOYP] |= 2; + } + if (!(gb->io_registers[GB_IO_JOYP] & 4)) { + gb->io_registers[GB_IO_JOYP] |= 8; + } + break; + + case 1: + /* Other keys */ + for (uint8_t i = 0; i < 4; i++) { + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i + 4]) << i; + } + break; + + case 0: + for (uint8_t i = 0; i < 4; i++) { + gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[current_player][i] || gb->keys[current_player][i + 4])) << i; + } + break; + + default: + break; + } + + /* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */ + if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { + /* The joypad interrupt DOES occur on CGB (Tested on CGB-E), unlike what some documents say. */ + gb->io_registers[GB_IO_IF] |= 0x10; + } + + gb->io_registers[GB_IO_JOYP] |= 0xC0; +} + +void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value) +{ + uint8_t previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; + gb->io_registers[GB_IO_JOYP] &= 0xF0; + gb->io_registers[GB_IO_JOYP] |= value & 0xF; + + if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) { + gb->io_registers[GB_IO_IF] |= 0x10; + } + gb->io_registers[GB_IO_JOYP] |= 0xC0; +} + +void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed) +{ + assert(index >= 0 && index < GB_KEY_MAX); + gb->keys[0][index] = pressed; + GB_update_joyp(gb); +} + +void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed) +{ + assert(index >= 0 && index < GB_KEY_MAX); + assert(player < 4); + gb->keys[player][index] = pressed; + GB_update_joyp(gb); +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/joypad.h b/waterbox/bsnescore/bsnes/gb/Core/joypad.h new file mode 100644 index 0000000000..21fad5343f --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/joypad.h @@ -0,0 +1,25 @@ +#ifndef joypad_h +#define joypad_h +#include "gb_struct_def.h" +#include + +typedef enum { + GB_KEY_RIGHT, + GB_KEY_LEFT, + GB_KEY_UP, + GB_KEY_DOWN, + GB_KEY_A, + GB_KEY_B, + GB_KEY_SELECT, + GB_KEY_START, + GB_KEY_MAX +} GB_key_t; + +void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed); +void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed); +void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value); + +#ifdef GB_INTERNAL +void GB_update_joyp(GB_gameboy_t *gb); +#endif +#endif /* joypad_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/mbc.c b/waterbox/bsnescore/bsnes/gb/Core/mbc.c new file mode 100644 index 0000000000..225968178d --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/mbc.c @@ -0,0 +1,167 @@ +#include +#include +#include +#include "gb.h" + +const GB_cartridge_t GB_cart_defs[256] = { + // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type + /* MBC SUBTYPE RAM BAT. RTC RUMB. */ + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 00h ROM ONLY + { GB_MBC1 , GB_STANDARD_MBC, false, false, false, false}, // 01h MBC1 + { GB_MBC1 , GB_STANDARD_MBC, true , false, false, false}, // 02h MBC1+RAM + { GB_MBC1 , GB_STANDARD_MBC, true , true , false, false}, // 03h MBC1+RAM+BATTERY + [5] = + { GB_MBC2 , GB_STANDARD_MBC, true , false, false, false}, // 05h MBC2 + { GB_MBC2 , GB_STANDARD_MBC, true , true , false, false}, // 06h MBC2+BATTERY + [8] = + { GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h ROM+RAM + { GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY + [0xB] = + /* Todo: Not supported yet */ + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh MMM01 + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch MMM01+RAM + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY + [0xF] = + { GB_MBC3 , GB_STANDARD_MBC, false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY + { GB_MBC3 , GB_STANDARD_MBC, true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY + { GB_MBC3 , GB_STANDARD_MBC, false, false, false, false}, // 11h MBC3 + { GB_MBC3 , GB_STANDARD_MBC, true , false, false, false}, // 12h MBC3+RAM + { GB_MBC3 , GB_STANDARD_MBC, true , true , false, false}, // 13h MBC3+RAM+BATTERY + [0x19] = + { GB_MBC5 , GB_STANDARD_MBC, false, false, false, false}, // 19h MBC5 + { GB_MBC5 , GB_STANDARD_MBC, true , false, false, false}, // 1Ah MBC5+RAM + { GB_MBC5 , GB_STANDARD_MBC, true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + { GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE + { GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM + { GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + [0xFC] = + { GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported) + { GB_HUC3 , GB_STANDARD_MBC, true , true , true, false}, // FEh HuC3 + { GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY +}; + +void GB_update_mbc_mappings(GB_gameboy_t *gb) +{ + switch (gb->cartridge_type->mbc_type) { + case GB_NO_MBC: return; + case GB_MBC1: + switch (gb->mbc1_wiring) { + case GB_STANDARD_MBC1_WIRING: + gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5); + if (gb->mbc1.mode == 0) { + gb->mbc_ram_bank = 0; + gb->mbc_rom0_bank = 0; + } + else { + gb->mbc_ram_bank = gb->mbc1.bank_high; + gb->mbc_rom0_bank = gb->mbc1.bank_high << 5; + } + if ((gb->mbc_rom_bank & 0x1F) == 0) { + gb->mbc_rom_bank++; + } + break; + case GB_MBC1M_WIRING: + gb->mbc_rom_bank = (gb->mbc1.bank_low & 0xF) | (gb->mbc1.bank_high << 4); + if (gb->mbc1.mode == 0) { + gb->mbc_ram_bank = 0; + gb->mbc_rom0_bank = 0; + } + else { + gb->mbc_rom0_bank = gb->mbc1.bank_high << 4; + gb->mbc_ram_bank = 0; + } + if ((gb->mbc1.bank_low & 0x1F) == 0) { + gb->mbc_rom_bank++; + } + break; + } + break; + case GB_MBC2: + gb->mbc_rom_bank = gb->mbc2.rom_bank; + if ((gb->mbc_rom_bank & 0xF) == 0) { + gb->mbc_rom_bank = 1; + } + break; + case GB_MBC3: + gb->mbc_rom_bank = gb->mbc3.rom_bank; + gb->mbc_ram_bank = gb->mbc3.ram_bank; + if (!gb->is_mbc30) { + gb->mbc_rom_bank &= 0x7F; + } + if (gb->mbc_rom_bank == 0) { + gb->mbc_rom_bank = 1; + } + break; + case GB_MBC5: + gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8); + gb->mbc_ram_bank = gb->mbc5.ram_bank; + break; + case GB_HUC1: + if (gb->huc1.mode == 0) { + gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6); + gb->mbc_ram_bank = 0; + } + else { + gb->mbc_rom_bank = gb->huc1.bank_low; + gb->mbc_ram_bank = gb->huc1.bank_high; + } + break; + case GB_HUC3: + gb->mbc_rom_bank = gb->huc3.rom_bank; + gb->mbc_ram_bank = gb->huc3.ram_bank; + break; + } +} + +void GB_configure_cart(GB_gameboy_t *gb) +{ + gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]]; + + if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) { + GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n"); + gb->cartridge_type = &GB_cart_defs[0x11]; + } + else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) { + GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]); + } + + if (gb->cartridge_type->has_ram) { + if (gb->cartridge_type->mbc_type == GB_MBC2) { + gb->mbc_ram_size = 0x200; + } + else { + static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; + gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; + } + + if (gb->mbc_ram_size) { + gb->mbc_ram = malloc(gb->mbc_ram_size); + } + + /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */ + memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); + } + + /* MBC1 has at least 3 types of wiring (We currently support two (Standard and 4bit-MBC1M) of these). + See http://forums.nesdev.com/viewtopic.php?f=20&t=14099 */ + + /* Attempt to "guess" wiring */ + if (gb->cartridge_type->mbc_type == GB_MBC1) { + if (gb->rom_size >= 0x44000 && memcmp(gb->rom + 0x104, gb->rom + 0x40104, 0x30) == 0) { + gb->mbc1_wiring = GB_MBC1M_WIRING; + } + } + + /* Detect MBC30 */ + if (gb->cartridge_type->mbc_type == GB_MBC3) { + if (gb->rom_size > 0x200000 || gb->mbc_ram_size > 0x8000) { + gb->is_mbc30 = true; + } + } + + /* Set MBC5's bank to 1 correctly */ + if (gb->cartridge_type->mbc_type == GB_MBC5) { + gb->mbc5.rom_bank_low = 1; + } +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/mbc.h b/waterbox/bsnescore/bsnes/gb/Core/mbc.h new file mode 100644 index 0000000000..6a23300f52 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/mbc.h @@ -0,0 +1,32 @@ +#ifndef MBC_h +#define MBC_h +#include "gb_struct_def.h" +#include + +typedef struct { + enum { + GB_NO_MBC, + GB_MBC1, + GB_MBC2, + GB_MBC3, + GB_MBC5, + GB_HUC1, + GB_HUC3, + } mbc_type; + enum { + GB_STANDARD_MBC, + GB_CAMERA, + } mbc_subtype; + bool has_ram; + bool has_battery; + bool has_rtc; + bool has_rumble; +} GB_cartridge_t; + +#ifdef GB_INTERNAL +extern const GB_cartridge_t GB_cart_defs[256]; +void GB_update_mbc_mappings(GB_gameboy_t *gb); +void GB_configure_cart(GB_gameboy_t *gb); +#endif + +#endif /* MBC_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/memory.c b/waterbox/bsnescore/bsnes/gb/Core/memory.c new file mode 100644 index 0000000000..f73209e3fe --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/memory.c @@ -0,0 +1,1213 @@ +#include +#include +#include "gb.h" + +typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); +typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); + +typedef enum { + GB_BUS_MAIN, /* In DMG: Cart and RAM. In CGB: Cart only */ + GB_BUS_RAM, /* In CGB only. */ + GB_BUS_VRAM, + GB_BUS_INTERNAL, /* Anything in highram. Might not be the most correct name. */ +} GB_bus_t; + +static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x8000) { + return GB_BUS_MAIN; + } + if (addr < 0xA000) { + return GB_BUS_VRAM; + } + if (addr < 0xC000) { + return GB_BUS_MAIN; + } + if (addr < 0xFE00) { + return GB_is_cgb(gb)? GB_BUS_RAM : GB_BUS_MAIN; + } + return GB_BUS_INTERNAL; +} + +static uint8_t bitwise_glitch(uint8_t a, uint8_t b, uint8_t c) +{ + return ((a ^ c) & (b ^ c)) ^ c; +} + +static uint8_t bitwise_glitch_read(uint8_t a, uint8_t b, uint8_t c) +{ + return b | (a & c); +} + +static uint8_t bitwise_glitch_read_increase(uint8_t a, uint8_t b, uint8_t c, uint8_t d) +{ + return (b & (a | c | d)) | (a & c & d); +} + +void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address) +{ + if (GB_is_cgb(gb)) return; + + if (address >= 0xFE00 && address < 0xFF00) { + if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) { + gb->oam[gb->accessed_oam_row] = bitwise_glitch(gb->oam[gb->accessed_oam_row], + gb->oam[gb->accessed_oam_row - 8], + gb->oam[gb->accessed_oam_row - 4]); + gb->oam[gb->accessed_oam_row + 1] = bitwise_glitch(gb->oam[gb->accessed_oam_row + 1], + gb->oam[gb->accessed_oam_row - 7], + gb->oam[gb->accessed_oam_row - 3]); + for (unsigned i = 2; i < 8; i++) { + gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 8 + i]; + } + } + } +} + +void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address) +{ + if (GB_is_cgb(gb)) return; + + if (address >= 0xFE00 && address < 0xFF00) { + if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) { + gb->oam[gb->accessed_oam_row - 8] = + gb->oam[gb->accessed_oam_row] = bitwise_glitch_read(gb->oam[gb->accessed_oam_row], + gb->oam[gb->accessed_oam_row - 8], + gb->oam[gb->accessed_oam_row - 4]); + gb->oam[gb->accessed_oam_row - 7] = + gb->oam[gb->accessed_oam_row + 1] = bitwise_glitch_read(gb->oam[gb->accessed_oam_row + 1], + gb->oam[gb->accessed_oam_row - 7], + gb->oam[gb->accessed_oam_row - 3]); + for (unsigned i = 2; i < 8; i++) { + gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 8 + i]; + } + } + } +} + +void GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address) +{ + if (GB_is_cgb(gb)) return; + + if (address >= 0xFE00 && address < 0xFF00) { + if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 0x20 && gb->accessed_oam_row < 0x98) { + gb->oam[gb->accessed_oam_row - 0x8] = bitwise_glitch_read_increase(gb->oam[gb->accessed_oam_row - 0x10], + gb->oam[gb->accessed_oam_row - 0x08], + gb->oam[gb->accessed_oam_row ], + gb->oam[gb->accessed_oam_row - 0x04] + ); + gb->oam[gb->accessed_oam_row - 0x7] = bitwise_glitch_read_increase(gb->oam[gb->accessed_oam_row - 0x0f], + gb->oam[gb->accessed_oam_row - 0x07], + gb->oam[gb->accessed_oam_row + 0x01], + gb->oam[gb->accessed_oam_row - 0x03] + ); + for (unsigned i = 0; i < 8; i++) { + gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x08 + i]; + } + } + } +} + +static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) +{ + if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting) || addr >= 0xFE00) return false; + return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src); +} + +static bool effective_ir_input(GB_gameboy_t *gb) +{ + return gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1) || gb->cart_ir; +} + +static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x100 && !gb->boot_rom_finished) { + return gb->boot_rom[addr]; + } + + if (addr >= 0x200 && addr < 0x900 && GB_is_cgb(gb) && !gb->boot_rom_finished) { + return gb->boot_rom[addr]; + } + + if (!gb->rom_size) { + return 0xFF; + } + unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; +} + +static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr) +{ + unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; +} + +static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->vram_read_blocked) { + return 0xFF; + } + if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) { + if (addr & 0x1000) { + addr = gb->last_tile_index_address; + } + else if (gb->last_tile_data_address & 0x1000) { + /* TODO: This is case is more complicated then the rest and differ between revisions + It's probably affected by how VRAM is layed out, might be easier after a decap is done*/ + } + else { + addr = gb->last_tile_data_address; + } + } + return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000]; +} + +static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->cartridge_type->mbc_type == GB_HUC3) { + switch (gb->huc3_mode) { + case 0xC: // RTC read + if (gb->huc3_access_flags == 0x2) { + return 1; + } + return gb->huc3_read; + case 0xD: // RTC status + return 1; + case 0xE: // IR mode + return effective_ir_input(gb); // TODO: What are the other bits? + default: + GB_log(gb, "Unsupported HuC-3 mode %x read: %04x\n", gb->huc3_mode, addr); + return 1; // TODO: What happens in this case? + case 0: // TODO: R/O RAM? (or is it disabled?) + case 0xA: // RAM + break; + } + } + + if ((!gb->mbc_ram_enable) && + gb->cartridge_type->mbc_subtype != GB_CAMERA && + gb->cartridge_type->mbc_type != GB_HUC1 && + gb->cartridge_type->mbc_type != GB_HUC3) { + return 0xFF; + } + + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { + return 0xc0 | effective_ir_input(gb); + } + + if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && + gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) { + /* RTC read */ + gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */ + return gb->rtc_latched.data[gb->mbc_ram_bank]; + } + + if (gb->camera_registers_mapped) { + return GB_camera_read_register(gb, addr); + } + + if (!gb->mbc_ram || !gb->mbc_ram_size) { + return 0xFF; + } + + if (gb->cartridge_type->mbc_subtype == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xa100 && addr < 0xaf00) { + return GB_camera_read_image(gb, addr - 0xa100); + } + + uint8_t effective_bank = gb->mbc_ram_bank; + if (gb->cartridge_type->mbc_type == GB_MBC3 && !gb->is_mbc30) { + effective_bank &= 0x3; + } + uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + effective_bank * 0x2000) & (gb->mbc_ram_size - 1)]; + if (gb->cartridge_type->mbc_type == GB_MBC2) { + ret |= 0xF0; + } + return ret; +} + +static uint8_t read_ram(GB_gameboy_t *gb, uint16_t addr) +{ + return gb->ram[addr & 0x0FFF]; +} + +static uint8_t read_banked_ram(GB_gameboy_t *gb, uint16_t addr) +{ + return gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000]; +} + +static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) +{ + + if (gb->hdma_on) { + return gb->last_opcode_read; + } + + if (addr < 0xFE00) { + return read_banked_ram(gb, addr); + } + + if (addr < 0xFF00) { + if (gb->oam_write_blocked && !GB_is_cgb(gb)) { + GB_trigger_oam_bug_read(gb, addr); + return 0xff; + } + + if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { + /* Todo: Does reading from OAM during DMA causes the OAM bug? */ + return 0xff; + } + + if (gb->oam_read_blocked) { + if (!GB_is_cgb(gb)) { + if (addr < 0xFEA0) { + if (gb->accessed_oam_row == 0) { + gb->oam[(addr & 0xf8)] = + gb->oam[0] = bitwise_glitch_read(gb->oam[0], + gb->oam[(addr & 0xf8)], + gb->oam[(addr & 0xfe)]); + gb->oam[(addr & 0xf8) + 1] = + gb->oam[1] = bitwise_glitch_read(gb->oam[1], + gb->oam[(addr & 0xf8) + 1], + gb->oam[(addr & 0xfe) | 1]); + for (unsigned i = 2; i < 8; i++) { + gb->oam[i] = gb->oam[(addr & 0xf8) + i]; + } + } + else if (gb->accessed_oam_row == 0xa0) { + gb->oam[0x9e] = bitwise_glitch_read(gb->oam[0x9c], + gb->oam[0x9e], + gb->oam[(addr & 0xf8) | 6]); + gb->oam[0x9f] = bitwise_glitch_read(gb->oam[0x9d], + gb->oam[0x9f], + gb->oam[(addr & 0xf8) | 7]); + + for (unsigned i = 0; i < 8; i++) { + gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i]; + } + } + } + } + return 0xff; + } + + if (addr < 0xFEA0) { + return gb->oam[addr & 0xFF]; + } + + if (gb->oam_read_blocked) { + return 0xFF; + } + + switch (gb->model) { + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: + return (addr & 0xF0) | ((addr >> 4) & 0xF); + + /* + case GB_MODEL_CGB_D: + if (addr > 0xfec0) { + addr |= 0xf0; + } + return gb->extra_oam[addr - 0xfea0]; + */ + + case GB_MODEL_CGB_C: + /* + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: + */ + addr &= ~0x18; + return gb->extra_oam[addr - 0xfea0]; + + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + break; + } + } + + if (addr < 0xFF00) { + + return 0; + + } + + if (addr < 0xFF80) { + switch (addr & 0xFF) { + case GB_IO_IF: + return gb->io_registers[GB_IO_IF] | 0xE0; + case GB_IO_TAC: + return gb->io_registers[GB_IO_TAC] | 0xF8; + case GB_IO_STAT: + return gb->io_registers[GB_IO_STAT] | 0x80; + case GB_IO_OPRI: + if (!GB_is_cgb(gb)) { + return 0xFF; + } + return gb->io_registers[GB_IO_OPRI] | 0xFE; + + case GB_IO_PCM_12: + if (!GB_is_cgb(gb)) return 0xFF; + return ((gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) | + (gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[0] : 0xFF); + case GB_IO_PCM_34: + if (!GB_is_cgb(gb)) return 0xFF; + return ((gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) | + (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[1] : 0xFF); + case GB_IO_JOYP: + GB_timing_sync(gb); + case GB_IO_TMA: + case GB_IO_LCDC: + case GB_IO_SCY: + case GB_IO_SCX: + case GB_IO_LY: + case GB_IO_LYC: + case GB_IO_BGP: + case GB_IO_OBP0: + case GB_IO_OBP1: + case GB_IO_WY: + case GB_IO_WX: + case GB_IO_SC: + case GB_IO_SB: + case GB_IO_DMA: + return gb->io_registers[addr & 0xFF]; + case GB_IO_TIMA: + if (gb->tima_reload_state == GB_TIMA_RELOADING) { + return 0; + } + return gb->io_registers[GB_IO_TIMA]; + case GB_IO_DIV: + return gb->div_counter >> 8; + case GB_IO_HDMA5: + if (!gb->cgb_mode) return 0xFF; + return ((gb->hdma_on || gb->hdma_on_hblank)? 0 : 0x80) | ((gb->hdma_steps_left - 1) & 0x7F); + case GB_IO_SVBK: + if (!gb->cgb_mode) { + return 0xFF; + } + return gb->cgb_ram_bank | ~0x7; + case GB_IO_VBK: + if (!GB_is_cgb(gb)) { + return 0xFF; + } + return gb->cgb_vram_bank | ~0x1; + + /* Todo: It seems that a CGB in DMG mode can access BGPI and OBPI, but not BGPD and OBPD? */ + case GB_IO_BGPI: + case GB_IO_OBPI: + if (!GB_is_cgb(gb)) { + return 0xFF; + } + return gb->io_registers[addr & 0xFF] | 0x40; + + case GB_IO_BGPD: + case GB_IO_OBPD: + { + if (!gb->cgb_mode && gb->boot_rom_finished) { + return 0xFF; + } + if (gb->cgb_palettes_blocked) { + return 0xFF; + } + uint8_t index_reg = (addr & 0xFF) - 1; + return ((addr & 0xFF) == GB_IO_BGPD? + gb->background_palettes_data : + gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F]; + } + + case GB_IO_KEY1: + if (!gb->cgb_mode) { + return 0xFF; + } + return (gb->io_registers[GB_IO_KEY1] & 0x7F) | (gb->cgb_double_speed? 0xFE : 0x7E); + + case GB_IO_RP: { + if (!gb->cgb_mode) return 0xFF; + /* You will read your own IR LED if it's on. */ + uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C; + if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && effective_ir_input(gb)) { + ret |= 2; + } + return ret; + } + case GB_IO_UNKNOWN2: + case GB_IO_UNKNOWN3: + return GB_is_cgb(gb)? gb->io_registers[addr & 0xFF] : 0xFF; + case GB_IO_UNKNOWN4: + return gb->cgb_mode? gb->io_registers[addr & 0xFF] : 0xFF; + case GB_IO_UNKNOWN5: + return GB_is_cgb(gb)? gb->io_registers[addr & 0xFF] | 0x8F : 0xFF; + default: + if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { + return GB_apu_read(gb, addr & 0xFF); + } + return 0xFF; + } + /* Hardware registers */ + return 0; + } + + if (addr == 0xFFFF) { + /* Interrupt Mask */ + return gb->interrupt_enable; + } + + /* HRAM */ + return gb->hram[addr - 0xFF80]; +} + +static GB_read_function_t * const read_map[] = +{ + read_rom, read_rom, read_rom, read_rom, /* 0XXX, 1XXX, 2XXX, 3XXX */ + read_mbc_rom, read_mbc_rom, read_mbc_rom, read_mbc_rom, /* 4XXX, 5XXX, 6XXX, 7XXX */ + read_vram, read_vram, /* 8XXX, 9XXX */ + read_mbc_ram, read_mbc_ram, /* AXXX, BXXX */ + read_ram, read_banked_ram, /* CXXX, DXXX */ + read_ram, read_high_memory, /* EXXX FXXX */ +}; + +void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback) +{ + gb->read_memory_callback = callback; +} + +uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->n_watchpoints) { + GB_debugger_test_read_watchpoint(gb, addr); + } + if (is_addr_in_dma_use(gb, addr)) { + addr = gb->dma_current_src; + } + uint8_t data = read_map[addr >> 12](gb, addr); + GB_apply_cheat(gb, addr, &data); + if (gb->read_memory_callback) { + data = gb->read_memory_callback(gb, addr, data); + } + return data; +} + +static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + switch (gb->cartridge_type->mbc_type) { + case GB_NO_MBC: return; + case GB_MBC1: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc1.bank_low = value; break; + case 0x4000: case 0x5000: gb->mbc1.bank_high = value; break; + case 0x6000: case 0x7000: gb->mbc1.mode = value; break; + } + break; + case GB_MBC2: + switch (addr & 0x4100) { + case 0x0000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x0100: gb->mbc2.rom_bank = value; break; + } + break; + case GB_MBC3: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break; + case 0x4000: case 0x5000: + gb->mbc3.ram_bank = value; + gb->mbc3_rtc_mapped = value & 8; + break; + case 0x6000: case 0x7000: + if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct */ + memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); + } + gb->rtc_latch = value & 1; + break; + } + break; + case GB_MBC5: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: gb->mbc5.rom_bank_low = value; break; + case 0x3000: gb->mbc5.rom_bank_high = value; break; + case 0x4000: case 0x5000: + if (gb->cartridge_type->has_rumble) { + if (!!(value & 8) != gb->rumble_state) { + gb->rumble_state = !gb->rumble_state; + } + value &= 7; + } + gb->mbc5.ram_bank = value; + gb->camera_registers_mapped = (value & 0x10) && gb->cartridge_type->mbc_subtype == GB_CAMERA; + break; + } + break; + case GB_HUC1: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break; + case 0x2000: case 0x3000: gb->huc1.bank_low = value; break; + case 0x4000: case 0x5000: gb->huc1.bank_high = value; break; + case 0x6000: case 0x7000: gb->huc1.mode = value; break; + } + break; + case GB_HUC3: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: + gb->huc3_mode = value & 0xF; + gb->mbc_ram_enable = gb->huc3_mode == 0xA; + break; + case 0x2000: case 0x3000: gb->huc3.rom_bank = value; break; + case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break; + } + break; + } + GB_update_mbc_mappings(gb); +} + +static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (gb->vram_write_blocked) { + //GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr); + return; + } + /* TODO: not verified */ + if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) { + if (addr & 0x1000) { + addr = gb->last_tile_index_address; + } + else if (gb->last_tile_data_address & 0x1000) { + /* TODO: This is case is more complicated then the rest and differ between revisions + It's probably affected by how VRAM is layed out, might be easier after a decap is done */ + } + else { + addr = gb->last_tile_data_address; + } + } + gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; +} + +static bool huc3_write(GB_gameboy_t *gb, uint8_t value) +{ + switch (gb->huc3_mode) { + case 0xB: // RTC Write + switch (value >> 4) { + case 1: + if (gb->huc3_access_index < 3) { + gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF; + } + else if (gb->huc3_access_index < 7) { + gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF; + } + else { + // GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index); + } + gb->huc3_access_index++; + break; + case 2: + case 3: + if (gb->huc3_access_index < 3) { + gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4)); + gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4)); + } + else if (gb->huc3_access_index < 7) { + gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4)); + gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4)); + } + else if (gb->huc3_access_index >= 0x58 && gb->huc3_access_index <= 0x5a) { + gb->huc3_alarm_minutes &= ~(0xF << ((gb->huc3_access_index - 0x58) * 4)); + gb->huc3_alarm_minutes |= ((value & 0xF) << ((gb->huc3_access_index - 0x58) * 4)); + } + else if (gb->huc3_access_index >= 0x5b && gb->huc3_access_index <= 0x5e) { + gb->huc3_alarm_days &= ~(0xF << ((gb->huc3_access_index - 0x5b) * 4)); + gb->huc3_alarm_days |= ((value & 0xF) << ((gb->huc3_access_index - 0x5b) * 4)); + } + else if (gb->huc3_access_index == 0x5f) { + gb->huc3_alarm_enabled = value & 1; + } + else { + // GB_log(gb, "Attempting to write %x to unsupported HuC-3 register: %03x\n", value & 0xF, gb->huc3_access_index); + } + if ((value >> 4) == 3) { + gb->huc3_access_index++; + } + break; + case 4: + gb->huc3_access_index &= 0xF0; + gb->huc3_access_index |= value & 0xF; + break; + case 5: + gb->huc3_access_index &= 0x0F; + gb->huc3_access_index |= (value & 0xF) << 4; + break; + case 6: + gb->huc3_access_flags = (value & 0xF); + break; + + default: + break; + } + + return true; + case 0xD: // RTC status + // Not sure what writes here mean, they're always 0xFE + return true; + case 0xE: { // IR mode + bool old_input = effective_ir_input(gb); + gb->cart_ir = value & 1; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + if (gb->infrared_callback) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + } + gb->cycles_since_ir_change = 0; + } + return true; + } + case 0xC: + return true; + default: + return false; + case 0: // Disabled + case 0xA: // RAM + return false; + } +} + +static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (gb->cartridge_type->mbc_type == GB_HUC3) { + if (huc3_write(gb, value)) return; + } + + if (gb->camera_registers_mapped) { + GB_camera_write_register(gb, addr, value); + return; + } + + if ((!gb->mbc_ram_enable) + && gb->cartridge_type->mbc_type != GB_HUC1) return; + + if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { + bool old_input = effective_ir_input(gb); + gb->cart_ir = value & 1; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + if (gb->infrared_callback) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + } + gb->cycles_since_ir_change = 0; + } + return; + } + + if (gb->cartridge_type->has_rtc && gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) { + gb->rtc_latched.data[gb->mbc_ram_bank] = gb->rtc_real.data[gb->mbc_ram_bank] = value; + return; + } + + if (!gb->mbc_ram || !gb->mbc_ram_size) { + return; + } + + uint8_t effective_bank = gb->mbc_ram_bank; + if (gb->cartridge_type->mbc_type == GB_MBC3 && !gb->is_mbc30) { + effective_bank &= 0x3; + } + + gb->mbc_ram[((addr & 0x1FFF) + effective_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value; +} + +static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + gb->ram[addr & 0x0FFF] = value; +} + +static void write_banked_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value; +} + +static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (addr < 0xFE00) { + GB_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr); + write_banked_ram(gb, addr, value); + return; + } + + if (addr < 0xFF00) { + if (gb->oam_write_blocked) { + GB_trigger_oam_bug(gb, addr); + return; + } + + if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { + /* Todo: Does writing to OAM during DMA causes the OAM bug? */ + return; + } + + if (GB_is_cgb(gb)) { + if (addr < 0xFEA0) { + gb->oam[addr & 0xFF] = value; + } + switch (gb->model) { + /* + case GB_MODEL_CGB_D: + if (addr > 0xfec0) { + addr |= 0xf0; + } + gb->extra_oam[addr - 0xfea0] = value; + break; + */ + case GB_MODEL_CGB_C: + /* + case GB_MODEL_CGB_B: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: + */ + addr &= ~0x18; + gb->extra_oam[addr - 0xfea0] = value; + break; + case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + case GB_MODEL_CGB_E: + case GB_MODEL_AGB: + break; + } + return; + } + + if (addr < 0xFEA0) { + if (gb->accessed_oam_row == 0xa0) { + for (unsigned i = 0; i < 8; i++) { + if ((i & 6) != (addr & 6)) { + gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i]; + } + else { + gb->oam[(addr & 0xf8) + i] = bitwise_glitch(gb->oam[(addr & 0xf8) + i], gb->oam[0x9c], gb->oam[0x98 + i]); + } + } + } + + gb->oam[addr & 0xFF] = value; + + if (gb->accessed_oam_row == 0) { + gb->oam[0] = bitwise_glitch(gb->oam[0], + gb->oam[(addr & 0xf8)], + gb->oam[(addr & 0xfe)]); + gb->oam[1] = bitwise_glitch(gb->oam[1], + gb->oam[(addr & 0xf8) + 1], + gb->oam[(addr & 0xfe) | 1]); + for (unsigned i = 2; i < 8; i++) { + gb->oam[i] = gb->oam[(addr & 0xf8) + i]; + } + } + } + else if (gb->accessed_oam_row == 0) { + gb->oam[addr & 0x7] = value; + } + return; + } + + /* Todo: Clean this code up: use a function table and move relevant code to display.c and timing.c + (APU read and writes are already at apu.c) */ + if (addr < 0xFF80) { + /* Hardware registers */ + switch (addr & 0xFF) { + case GB_IO_WY: + if (value == gb->current_line) { + gb->wy_triggered = true; + } + case GB_IO_WX: + case GB_IO_IF: + case GB_IO_SCX: + case GB_IO_SCY: + case GB_IO_BGP: + case GB_IO_OBP0: + case GB_IO_OBP1: + case GB_IO_SB: + case GB_IO_UNKNOWN2: + case GB_IO_UNKNOWN3: + case GB_IO_UNKNOWN4: + case GB_IO_UNKNOWN5: + gb->io_registers[addr & 0xFF] = value; + return; + case GB_IO_OPRI: + if ((!gb->boot_rom_finished || (gb->io_registers[GB_IO_KEY0] & 8)) && GB_is_cgb(gb)) { + gb->io_registers[addr & 0xFF] = value; + gb->object_priority = (value & 1) ? GB_OBJECT_PRIORITY_X : GB_OBJECT_PRIORITY_INDEX; + } + else if (gb->cgb_mode) { + gb->io_registers[addr & 0xFF] = value; + + } + return; + case GB_IO_LYC: + + /* TODO: Probably completely wrong in double speed mode */ + + /* TODO: This hack is disgusting */ + if (gb->display_state == 29 && GB_is_cgb(gb)) { + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + gb->ly_for_comparison = 0; + } + + gb->io_registers[addr & 0xFF] = value; + + /* These are the states when LY changes, let the display routine call GB_STAT_update for use + so it correctly handles T-cycle accurate LYC writes */ + if (!GB_is_cgb(gb) || ( + gb->display_state != 35 && + gb->display_state != 26 && + gb->display_state != 15 && + gb->display_state != 16)) { + + /* More hacks to make LYC write conflicts work */ + if (gb->display_state == 14 && GB_is_cgb(gb)) { + gb->ly_for_comparison = 153; + GB_STAT_update(gb); + gb->ly_for_comparison = -1; + } + else { + GB_STAT_update(gb); + } + } + return; + + case GB_IO_TIMA: + if (gb->tima_reload_state != GB_TIMA_RELOADED) { + gb->io_registers[GB_IO_TIMA] = value; + } + return; + + case GB_IO_TMA: + gb->io_registers[GB_IO_TMA] = value; + if (gb->tima_reload_state != GB_TIMA_RUNNING) { + gb->io_registers[GB_IO_TIMA] = value; + } + return; + + case GB_IO_TAC: + GB_emulate_timer_glitch(gb, gb->io_registers[GB_IO_TAC], value); + gb->io_registers[GB_IO_TAC] = value; + return; + + + case GB_IO_LCDC: + if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { + gb->display_cycles = 0; + gb->display_state = 0; + if (GB_is_sgb(gb)) { + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + } + else if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) { + gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON; + } + } + else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) { + /* Sync after turning off LCD */ + GB_timing_sync(gb); + GB_lcd_off(gb); + } + /* Handle disabling objects while already fetching an object */ + if ((gb->io_registers[GB_IO_LCDC] & 2) && !(value & 2)) { + if (gb->during_object_fetch) { + gb->cycles_for_line += gb->display_cycles; + gb->display_cycles = 0; + gb->object_fetch_aborted = true; + } + } + gb->io_registers[GB_IO_LCDC] = value; + if (!(value & 0x20)) { + gb->wx_triggered = false; + gb->wx166_glitch = false; + } + return; + + case GB_IO_STAT: + /* Delete previous R/W bits */ + gb->io_registers[GB_IO_STAT] &= 7; + /* Set them by value */ + gb->io_registers[GB_IO_STAT] |= value & ~7; + /* Set unused bit to 1 */ + gb->io_registers[GB_IO_STAT] |= 0x80; + + GB_STAT_update(gb); + return; + + case GB_IO_DIV: + /* Reset the div state machine */ + gb->div_state = 0; + gb->div_cycles = 0; + return; + + case GB_IO_JOYP: + if (gb->joyp_write_callback) { + gb->joyp_write_callback(gb, value); + GB_update_joyp(gb); + } + else if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) { + GB_sgb_write(gb, value); + gb->io_registers[GB_IO_JOYP] = (value & 0xF0) | (gb->io_registers[GB_IO_JOYP] & 0x0F); + GB_update_joyp(gb); + } + return; + + case GB_IO_BANK: + gb->boot_rom_finished = true; + return; + + case GB_IO_KEY0: + if (GB_is_cgb(gb) && !gb->boot_rom_finished) { + gb->cgb_mode = !(value & 0xC); /* The real "contents" of this register aren't quite known yet. */ + gb->io_registers[GB_IO_KEY0] = value; + } + return; + + case GB_IO_DMA: + if (gb->dma_steps_left) { + /* This is not correct emulation, since we're not really delaying the second DMA. + One write that should have happened in the first DMA will not happen. However, + since that byte will be overwritten by the second DMA before it can actually be + read, it doesn't actually matter. */ + gb->is_dma_restarting = true; + } + gb->dma_cycles = -7; + gb->dma_current_dest = 0; + gb->dma_current_src = value << 8; + gb->dma_steps_left = 0xa0; + gb->io_registers[GB_IO_DMA] = value; + return; + case GB_IO_SVBK: + if (!gb->cgb_mode) { + return; + } + gb->cgb_ram_bank = value & 0x7; + if (!gb->cgb_ram_bank) { + gb->cgb_ram_bank++; + } + return; + case GB_IO_VBK: + if (!gb->cgb_mode) { + return; + } + gb->cgb_vram_bank = value & 0x1; + return; + + case GB_IO_BGPI: + case GB_IO_OBPI: + if (!GB_is_cgb(gb)) { + return; + } + gb->io_registers[addr & 0xFF] = value; + return; + case GB_IO_BGPD: + case GB_IO_OBPD: + if (!gb->cgb_mode && gb->boot_rom_finished) { + /* Todo: Due to the behavior of a broken Game & Watch Gallery 2 ROM on a real CGB. A proper test ROM + is required. */ + return; + } + + uint8_t index_reg = (addr & 0xFF) - 1; + if (gb->cgb_palettes_blocked) { + if (gb->io_registers[index_reg] & 0x80) { + gb->io_registers[index_reg]++; + gb->io_registers[index_reg] |= 0x80; + } + return; + } + ((addr & 0xFF) == GB_IO_BGPD? + gb->background_palettes_data : + gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F] = value; + GB_palette_changed(gb, (addr & 0xFF) == GB_IO_BGPD, gb->io_registers[index_reg] & 0x3F); + if (gb->io_registers[index_reg] & 0x80) { + gb->io_registers[index_reg]++; + gb->io_registers[index_reg] |= 0x80; + } + return; + case GB_IO_KEY1: + if (!gb->cgb_mode) { + return; + } + gb->io_registers[GB_IO_KEY1] = value; + return; + case GB_IO_HDMA1: + if (gb->cgb_mode) { + gb->hdma_current_src &= 0xF0; + gb->hdma_current_src |= value << 8; + } + return; + case GB_IO_HDMA2: + if (gb->cgb_mode) { + gb->hdma_current_src &= 0xFF00; + gb->hdma_current_src |= value & 0xF0; + } + return; + case GB_IO_HDMA3: + if (gb->cgb_mode) { + gb->hdma_current_dest &= 0xF0; + gb->hdma_current_dest |= value << 8; + } + return; + case GB_IO_HDMA4: + if (gb->cgb_mode) { + gb->hdma_current_dest &= 0x1F00; + gb->hdma_current_dest |= value & 0xF0; + } + return; + case GB_IO_HDMA5: + if (!gb->cgb_mode) return; + if ((value & 0x80) == 0 && gb->hdma_on_hblank) { + gb->hdma_on_hblank = false; + return; + } + gb->hdma_on = (value & 0x80) == 0; + gb->hdma_on_hblank = (value & 0x80) != 0; + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0) { + gb->hdma_on = true; + } + gb->io_registers[GB_IO_HDMA5] = value; + gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1; + /* Todo: Verify this. Gambatte's DMA tests require this. */ + if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) { + gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4; + } + gb->hdma_cycles = -12; + return; + + /* Todo: what happens when starting a transfer during a transfer? + What happens when starting a transfer during external clock? + */ + case GB_IO_SC: + if (!gb->cgb_mode) { + value |= 2; + } + gb->io_registers[GB_IO_SC] = value | (~0x83); + if ((value & 0x80) && (value & 0x1) ) { + gb->serial_length = gb->cgb_mode && (value & 2)? 16 : 512; + gb->serial_count = 0; + /* Todo: This is probably incorrect for CGB's faster clock mode. */ + gb->serial_cycles &= 0xFF; + if (gb->serial_transfer_bit_start_callback) { + gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); + } + } + else { + gb->serial_length = 0; + } + return; + + case GB_IO_RP: { + if (!GB_is_cgb(gb)) { + return; + } + bool old_input = effective_ir_input(gb); + gb->io_registers[GB_IO_RP] = value; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + if (gb->infrared_callback) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + } + gb->cycles_since_ir_change = 0; + } + return; + } + + default: + if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { + GB_apu_write(gb, addr & 0xFF, value); + return; + } + GB_log(gb, "Wrote %02x to %04x (HW Register)\n", value, addr); + return; + } + } + + if (addr == 0xFFFF) { + /* Interrupt mask */ + gb->interrupt_enable = value; + return; + } + + /* HRAM */ + gb->hram[addr - 0xFF80] = value; +} + + + +static GB_write_function_t * const write_map[] = +{ + write_mbc, write_mbc, write_mbc, write_mbc, /* 0XXX, 1XXX, 2XXX, 3XXX */ + write_mbc, write_mbc, write_mbc, write_mbc, /* 4XXX, 5XXX, 6XXX, 7XXX */ + write_vram, write_vram, /* 8XXX, 9XXX */ + write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */ + write_ram, write_banked_ram, /* CXXX, DXXX */ + write_ram, write_high_memory, /* EXXX FXXX */ +}; + +void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (gb->n_watchpoints) { + GB_debugger_test_write_watchpoint(gb, addr, value); + } + if (is_addr_in_dma_use(gb, addr)) { + /* Todo: What should happen? Will this affect DMA? Will data be written? What and where? */ + return; + } + write_map[addr >> 12](gb, addr, value); +} + +void GB_dma_run(GB_gameboy_t *gb) +{ + while (gb->dma_cycles >= 4 && gb->dma_steps_left) { + /* Todo: measure this value */ + gb->dma_cycles -= 4; + gb->dma_steps_left--; + + if (gb->dma_current_src < 0xe000) { + gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); + } + else { + /* Todo: Not correct on the CGB */ + gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000); + } + + /* dma_current_src must be the correct value during GB_read_memory */ + gb->dma_current_src++; + if (!gb->dma_steps_left) { + gb->is_dma_restarting = false; + } + } +} + +void GB_hdma_run(GB_gameboy_t *gb) +{ + if (!gb->hdma_on) return; + + while (gb->hdma_cycles >= 0x4) { + gb->hdma_cycles -= 0x4; + + GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++))); + + if ((gb->hdma_current_dest & 0xf) == 0) { + if (--gb->hdma_steps_left == 0) { + gb->hdma_on = false; + gb->hdma_on_hblank = false; + gb->hdma_starting = false; + gb->io_registers[GB_IO_HDMA5] &= 0x7F; + break; + } + if (gb->hdma_on_hblank) { + gb->hdma_on = false; + break; + } + } + } +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/memory.h b/waterbox/bsnescore/bsnes/gb/Core/memory.h new file mode 100644 index 0000000000..f0d03907fb --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/memory.h @@ -0,0 +1,18 @@ +#ifndef memory_h +#define memory_h +#include "gb_struct_def.h" +#include + +typedef uint8_t (*GB_read_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, uint8_t data); +void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback); + +uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); +void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +#ifdef GB_INTERNAL +void GB_dma_run(GB_gameboy_t *gb); +void GB_hdma_run(GB_gameboy_t *gb); +void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address); +void GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address); +#endif + +#endif /* memory_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/printer.c b/waterbox/bsnescore/bsnes/gb/Core/printer.c new file mode 100644 index 0000000000..f04e54ddfe --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/printer.c @@ -0,0 +1,216 @@ +#include "gb.h" + +/* TODO: Emulation is VERY basic and assumes the ROM correctly uses the printer's interface. + Incorrect usage is not correctly emulated, as it's not well documented, nor do I + have my own GB Printer to figure it out myself. + + It also does not currently emulate communication timeout, which means that a bug + might prevent the printer operation until the GameBoy is restarted. + + Also, field mask values are assumed. */ + +static void handle_command(GB_gameboy_t *gb) +{ + + switch (gb->printer.command_id) { + case GB_PRINTER_INIT_COMMAND: + gb->printer.status = 0; + gb->printer.image_offset = 0; + break; + + case GB_PRINTER_START_COMMAND: + if (gb->printer.command_length == 4) { + gb->printer.status = 6; /* Printing */ + uint32_t image[gb->printer.image_offset]; + uint8_t palette = gb->printer.command_data[2]; + uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xff, 0xff, 0xff), + gb->rgb_encode_callback(gb, 0xaa, 0xaa, 0xaa), + gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55), + gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)}; + for (unsigned i = 0; i < gb->printer.image_offset; i++) { + image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3]; + } + + if (gb->printer_callback) { + gb->printer_callback(gb, image, gb->printer.image_offset / 160, + gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7, + gb->printer.command_data[3] & 0x7F); + } + + gb->printer.image_offset = 0; + } + break; + + case GB_PRINTER_DATA_COMMAND: + if (gb->printer.command_length == GB_PRINTER_DATA_SIZE) { + gb->printer.image_offset %= sizeof(gb->printer.image); + gb->printer.status = 8; /* Received 0x280 bytes */ + + uint8_t *byte = gb->printer.command_data; + + for (unsigned row = 2; row--; ) { + for (unsigned tile_x = 0; tile_x < 160 / 8; tile_x++) { + for (unsigned y = 0; y < 8; y++, byte += 2) { + for (unsigned x_pixel = 0; x_pixel < 8; x_pixel++) { + gb->printer.image[gb->printer.image_offset + tile_x * 8 + x_pixel + y * 160] = + ((*byte) >> 7) | (((*(byte + 1)) >> 7) << 1); + (*byte) <<= 1; + (*(byte + 1)) <<= 1; + } + } + } + + gb->printer.image_offset += 8 * 160; + } + } + + case GB_PRINTER_NOP_COMMAND: + default: + break; + } +} + + +static void byte_reieve_completed(GB_gameboy_t *gb, uint8_t byte_received) +{ + gb->printer.byte_to_send = 0; + switch (gb->printer.command_state) { + case GB_PRINTER_COMMAND_MAGIC1: + if (byte_received != 0x88) { + return; + } + gb->printer.status &= ~1; + gb->printer.command_length = 0; + gb->printer.checksum = 0; + break; + + case GB_PRINTER_COMMAND_MAGIC2: + if (byte_received != 0x33) { + if (byte_received != 0x88) { + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + } + return; + } + break; + + case GB_PRINTER_COMMAND_ID: + gb->printer.command_id = byte_received & 0xF; + break; + + case GB_PRINTER_COMMAND_COMPRESSION: + gb->printer.compression = byte_received & 1; + break; + + case GB_PRINTER_COMMAND_LENGTH_LOW: + gb->printer.length_left = byte_received; + break; + + case GB_PRINTER_COMMAND_LENGTH_HIGH: + gb->printer.length_left |= (byte_received & 3) << 8; + break; + + case GB_PRINTER_COMMAND_DATA: + if (gb->printer.command_length != GB_PRINTER_MAX_COMMAND_LENGTH) { + if (gb->printer.compression) { + if (!gb->printer.compression_run_lenth) { + gb->printer.compression_run_is_compressed = byte_received & 0x80; + gb->printer.compression_run_lenth = (byte_received & 0x7F) + 1 + gb->printer.compression_run_is_compressed; + } + else if (gb->printer.compression_run_is_compressed) { + while (gb->printer.compression_run_lenth) { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + gb->printer.compression_run_lenth--; + if (gb->printer.command_length == GB_PRINTER_MAX_COMMAND_LENGTH) { + gb->printer.compression_run_lenth = 0; + } + } + } + else { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + gb->printer.compression_run_lenth--; + } + } + else { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + } + } + gb->printer.length_left--; + break; + + case GB_PRINTER_COMMAND_CHECKSUM_LOW: + gb->printer.checksum ^= byte_received; + break; + + case GB_PRINTER_COMMAND_CHECKSUM_HIGH: + gb->printer.checksum ^= byte_received << 8; + if (gb->printer.checksum) { + gb->printer.status |= 1; /* Checksum error*/ + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + return; + } + gb->printer.byte_to_send = 0x81; + + break; + case GB_PRINTER_COMMAND_ACTIVE: + if ((gb->printer.command_id & 0xF) == GB_PRINTER_INIT_COMMAND) { + /* Games expect INIT commands to return 0? */ + gb->printer.byte_to_send = 0; + } + else { + gb->printer.byte_to_send = gb->printer.status; + } + break; + case GB_PRINTER_COMMAND_STATUS: + + /* Printing is done instantly, but let the game recieve a 6 (Printing) status at least once, for compatibility */ + if (gb->printer.status == 6) { + gb->printer.status = 4; /* Done */ + } + + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + handle_command(gb); + return; + } + + if (gb->printer.command_state >= GB_PRINTER_COMMAND_ID && gb->printer.command_state < GB_PRINTER_COMMAND_CHECKSUM_LOW) { + gb->printer.checksum += byte_received; + } + + if (gb->printer.command_state != GB_PRINTER_COMMAND_DATA) { + gb->printer.command_state++; + } + + if (gb->printer.command_state == GB_PRINTER_COMMAND_DATA) { + if (gb->printer.length_left == 0) { + gb->printer.command_state++; + } + } +} + +static void serial_start(GB_gameboy_t *gb, bool bit_received) +{ + gb->printer.byte_being_received <<= 1; + gb->printer.byte_being_received |= bit_received; + gb->printer.bits_received++; + if (gb->printer.bits_received == 8) { + byte_reieve_completed(gb, gb->printer.byte_being_received); + gb->printer.bits_received = 0; + gb->printer.byte_being_received = 0; + } +} + +static bool serial_end(GB_gameboy_t *gb) +{ + bool ret = gb->printer.bit_to_send; + gb->printer.bit_to_send = gb->printer.byte_to_send & 0x80; + gb->printer.byte_to_send <<= 1; + return ret; +} + +void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback) +{ + memset(&gb->printer, 0, sizeof(gb->printer)); + GB_set_serial_transfer_bit_start_callback(gb, serial_start); + GB_set_serial_transfer_bit_end_callback(gb, serial_end); + gb->printer_callback = callback; +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/printer.h b/waterbox/bsnescore/bsnes/gb/Core/printer.h new file mode 100644 index 0000000000..b29650ff7e --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/printer.h @@ -0,0 +1,64 @@ +#ifndef printer_h +#define printer_h +#include +#include +#include "gb_struct_def.h" +#define GB_PRINTER_MAX_COMMAND_LENGTH 0x280 +#define GB_PRINTER_DATA_SIZE 0x280 + +typedef void (*GB_print_image_callback_t)(GB_gameboy_t *gb, + uint32_t *image, + uint8_t height, + uint8_t top_margin, + uint8_t bottom_margin, + uint8_t exposure); + + +typedef struct +{ + /* Communication state machine */ + + enum { + GB_PRINTER_COMMAND_MAGIC1, + GB_PRINTER_COMMAND_MAGIC2, + GB_PRINTER_COMMAND_ID, + GB_PRINTER_COMMAND_COMPRESSION, + GB_PRINTER_COMMAND_LENGTH_LOW, + GB_PRINTER_COMMAND_LENGTH_HIGH, + GB_PRINTER_COMMAND_DATA, + GB_PRINTER_COMMAND_CHECKSUM_LOW, + GB_PRINTER_COMMAND_CHECKSUM_HIGH, + GB_PRINTER_COMMAND_ACTIVE, + GB_PRINTER_COMMAND_STATUS, + } command_state : 8; + enum { + GB_PRINTER_INIT_COMMAND = 1, + GB_PRINTER_START_COMMAND = 2, + GB_PRINTER_DATA_COMMAND = 4, + GB_PRINTER_NOP_COMMAND = 0xF, + } command_id : 8; + bool compression; + uint16_t length_left; + uint8_t command_data[GB_PRINTER_MAX_COMMAND_LENGTH]; + uint16_t command_length; + uint16_t checksum; + uint8_t status; + uint8_t byte_to_send; + + uint8_t image[160 * 200]; + uint16_t image_offset; + + /* TODO: Delete me. */ + uint64_t padding; + + uint8_t compression_run_lenth; + bool compression_run_is_compressed; + + uint8_t bits_received; + uint8_t byte_being_received; + bool bit_to_send; +} GB_printer_t; + + +void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback); +#endif diff --git a/waterbox/bsnescore/bsnes/gb/Core/random.c b/waterbox/bsnescore/bsnes/gb/Core/random.c new file mode 100644 index 0000000000..cc4d4d3a6c --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/random.c @@ -0,0 +1,38 @@ +#include "random.h" +#include + +static uint64_t seed; +static bool enabled = true; + +uint8_t GB_random(void) +{ + if (!enabled) return 0; + + seed *= 0x27BB2EE687B0B0FDL; + seed += 0xB504F32D; + return seed >> 56; +} + +uint32_t GB_random32(void) +{ + GB_random(); + return seed >> 32; +} + +void GB_random_seed(uint64_t new_seed) +{ + seed = new_seed; +} + +void GB_random_set_enabled(bool enable) +{ + enabled = enable; +} + +static void __attribute__((constructor)) init_seed(void) +{ + seed = time(NULL); + for (unsigned i = 64; i--;) { + GB_random(); + } +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/random.h b/waterbox/bsnescore/bsnes/gb/Core/random.h new file mode 100644 index 0000000000..8ab0e502c6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/random.h @@ -0,0 +1,12 @@ +#ifndef random_h +#define random_h + +#include +#include + +uint8_t GB_random(void); +uint32_t GB_random32(void); +void GB_random_seed(uint64_t seed); +void GB_random_set_enabled(bool enable); + +#endif /* random_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/rewind.c b/waterbox/bsnescore/bsnes/gb/Core/rewind.c new file mode 100644 index 0000000000..c3900d6090 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/rewind.c @@ -0,0 +1,208 @@ +#include "gb.h" +#include +#include +#include +#include + +static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t uncompressed_size) +{ + size_t malloc_size = 0x1000; + uint8_t *compressed = malloc(malloc_size); + size_t counter_pos = 0; + size_t data_pos = sizeof(uint16_t); + bool prev_mode = true; + *(uint16_t *)compressed = 0; +#define COUNTER (*(uint16_t *)&compressed[counter_pos]) +#define DATA (compressed[data_pos]) + + while (uncompressed_size) { + if (prev_mode) { + if (*data == *prev && COUNTER != 0xffff) { + COUNTER++; + data++; + prev++; + uncompressed_size--; + } + else { + prev_mode = false; + counter_pos += sizeof(uint16_t); + data_pos = counter_pos + sizeof(uint16_t); + if (data_pos >= malloc_size) { + malloc_size *= 2; + compressed = realloc(compressed, malloc_size); + } + COUNTER = 0; + } + } + else { + if (*data != *prev && COUNTER != 0xffff) { + COUNTER++; + DATA = *data; + data_pos++; + data++; + prev++; + uncompressed_size--; + if (data_pos >= malloc_size) { + malloc_size *= 2; + compressed = realloc(compressed, malloc_size); + } + } + else { + prev_mode = true; + counter_pos = data_pos; + data_pos = counter_pos + sizeof(uint16_t); + if (counter_pos >= malloc_size - 1) { + malloc_size *= 2; + compressed = realloc(compressed, malloc_size); + } + COUNTER = 0; + } + } + } + + return realloc(compressed, data_pos); +#undef DATA +#undef COUNTER +} + + +static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest, size_t uncompressed_size) +{ + size_t counter_pos = 0; + size_t data_pos = sizeof(uint16_t); + bool prev_mode = true; +#define COUNTER (*(uint16_t *)&data[counter_pos]) +#define DATA (data[data_pos]) + + while (uncompressed_size) { + if (prev_mode) { + if (COUNTER) { + COUNTER--; + *(dest++) = *(prev++); + uncompressed_size--; + } + else { + prev_mode = false; + counter_pos += sizeof(uint16_t); + data_pos = counter_pos + sizeof(uint16_t); + } + } + else { + if (COUNTER) { + COUNTER--; + *(dest++) = DATA; + data_pos++; + prev++; + uncompressed_size--; + } + else { + prev_mode = true; + counter_pos = data_pos; + data_pos += sizeof(uint16_t); + } + } + } +#undef DATA +#undef COUNTER +} + +void GB_rewind_push(GB_gameboy_t *gb) +{ + const size_t save_size = GB_get_save_state_size(gb); + if (!gb->rewind_sequences) { + if (gb->rewind_buffer_length) { + gb->rewind_sequences = malloc(sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length); + memset(gb->rewind_sequences, 0, sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length); + gb->rewind_pos = 0; + } + else { + return; + } + } + + if (gb->rewind_sequences[gb->rewind_pos].pos == GB_REWIND_FRAMES_PER_KEY) { + gb->rewind_pos++; + if (gb->rewind_pos == gb->rewind_buffer_length) { + gb->rewind_pos = 0; + } + if (gb->rewind_sequences[gb->rewind_pos].key_state) { + free(gb->rewind_sequences[gb->rewind_pos].key_state); + gb->rewind_sequences[gb->rewind_pos].key_state = NULL; + } + for (unsigned i = 0; i < GB_REWIND_FRAMES_PER_KEY; i++) { + if (gb->rewind_sequences[gb->rewind_pos].compressed_states[i]) { + free(gb->rewind_sequences[gb->rewind_pos].compressed_states[i]); + gb->rewind_sequences[gb->rewind_pos].compressed_states[i] = 0; + } + } + gb->rewind_sequences[gb->rewind_pos].pos = 0; + } + + if (!gb->rewind_sequences[gb->rewind_pos].key_state) { + gb->rewind_sequences[gb->rewind_pos].key_state = malloc(save_size); + GB_save_state_to_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state); + } + else { + uint8_t *save_state = malloc(save_size); + GB_save_state_to_buffer(gb, save_state); + gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos++] = + state_compress(gb->rewind_sequences[gb->rewind_pos].key_state, save_state, save_size); + free(save_state); + } + +} + +bool GB_rewind_pop(GB_gameboy_t *gb) +{ + if (!gb->rewind_sequences || !gb->rewind_sequences[gb->rewind_pos].key_state) { + return false; + } + + const size_t save_size = GB_get_save_state_size(gb); + if (gb->rewind_sequences[gb->rewind_pos].pos == 0) { + GB_load_state_from_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state, save_size); + free(gb->rewind_sequences[gb->rewind_pos].key_state); + gb->rewind_sequences[gb->rewind_pos].key_state = NULL; + gb->rewind_pos = gb->rewind_pos == 0? gb->rewind_buffer_length - 1 : gb->rewind_pos - 1; + return true; + } + + uint8_t *save_state = malloc(save_size); + state_decompress(gb->rewind_sequences[gb->rewind_pos].key_state, + gb->rewind_sequences[gb->rewind_pos].compressed_states[--gb->rewind_sequences[gb->rewind_pos].pos], + save_state, + save_size); + free(gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos]); + gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos] = NULL; + GB_load_state_from_buffer(gb, save_state, save_size); + free(save_state); + return true; +} + +void GB_rewind_free(GB_gameboy_t *gb) +{ + if (!gb->rewind_sequences) return; + for (unsigned i = 0; i < gb->rewind_buffer_length; i++) { + if (gb->rewind_sequences[i].key_state) { + free(gb->rewind_sequences[i].key_state); + } + for (unsigned j = 0; j < GB_REWIND_FRAMES_PER_KEY; j++) { + if (gb->rewind_sequences[i].compressed_states[j]) { + free(gb->rewind_sequences[i].compressed_states[j]); + } + } + } + free(gb->rewind_sequences); + gb->rewind_sequences = NULL; +} + +void GB_set_rewind_length(GB_gameboy_t *gb, double seconds) +{ + GB_rewind_free(gb); + if (seconds == 0) { + gb->rewind_buffer_length = 0; + } + else { + gb->rewind_buffer_length = (size_t) ceil(seconds * CPU_FREQUENCY / LCDC_PERIOD / GB_REWIND_FRAMES_PER_KEY); + } +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/rewind.h b/waterbox/bsnescore/bsnes/gb/Core/rewind.h new file mode 100644 index 0000000000..ad54841084 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/rewind.h @@ -0,0 +1,14 @@ +#ifndef rewind_h +#define rewind_h + +#include +#include "gb_struct_def.h" + +#ifdef GB_INTERNAL +void GB_rewind_push(GB_gameboy_t *gb); +void GB_rewind_free(GB_gameboy_t *gb); +#endif +bool GB_rewind_pop(GB_gameboy_t *gb); +void GB_set_rewind_length(GB_gameboy_t *gb, double seconds); + +#endif diff --git a/waterbox/bsnescore/bsnes/gb/Core/rumble.c b/waterbox/bsnescore/bsnes/gb/Core/rumble.c new file mode 100644 index 0000000000..8cbe20d13e --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/rumble.c @@ -0,0 +1,53 @@ +#include "rumble.h" +#include "gb.h" + +void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode) +{ + gb->rumble_mode = mode; + if (gb->rumble_callback) { + gb->rumble_callback(gb, 0); + } +} + +void GB_handle_rumble(GB_gameboy_t *gb) +{ + if (gb->rumble_callback) { + if (gb->rumble_mode == GB_RUMBLE_DISABLED) { + return; + } + if (gb->cartridge_type->has_rumble) { + if (gb->rumble_on_cycles + gb->rumble_off_cycles) { + gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles)); + gb->rumble_on_cycles = gb->rumble_off_cycles = 0; + } + } + else if (gb->rumble_mode == GB_RUMBLE_ALL_GAMES) { + unsigned volume = (gb->io_registers[GB_IO_NR50] & 7) + 1 + ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; + unsigned ch4_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 8) + !!(gb->io_registers[GB_IO_NR51] & 0x80)); + unsigned ch1_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 1) + !!(gb->io_registers[GB_IO_NR51] & 0x10)); + + double ch4_rumble = (MIN(gb->apu.noise_channel.sample_length * (gb->apu.noise_channel.narrow? 8 : 1) , 4096) * ((signed) gb->apu.noise_channel.current_volume * gb->apu.noise_channel.current_volume * ch4_volume / 32.0 - 50) - 2048) / 2048.0; + + ch4_rumble = MIN(ch4_rumble, 1.0); + ch4_rumble = MAX(ch4_rumble, 0.0); + + double ch1_rumble = 0; + if (gb->apu.sweep_enabled && ((gb->io_registers[GB_IO_NR10] >> 4) & 7)) { + double sweep_speed = (gb->io_registers[GB_IO_NR10] & 7) / (double)((gb->io_registers[GB_IO_NR10] >> 4) & 7); + ch1_rumble = gb->apu.square_channels[GB_SQUARE_1].current_volume * ch1_volume / 32.0 * sweep_speed / 8.0 - 0.5; + ch1_rumble = MIN(ch1_rumble, 1.0); + ch1_rumble = MAX(ch1_rumble, 0.0); + } + + if (!gb->apu.is_active[GB_NOISE]) { + ch4_rumble = 0; + } + + if (!gb->apu.is_active[GB_SQUARE_1]) { + ch1_rumble = 0; + } + + gb->rumble_callback(gb, MIN(MAX(ch1_rumble / 2 + ch4_rumble, 0.0), 1.0)); + } + } +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/rumble.h b/waterbox/bsnescore/bsnes/gb/Core/rumble.h new file mode 100644 index 0000000000..eae9f372b6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/rumble.h @@ -0,0 +1,17 @@ +#ifndef rumble_h +#define rumble_h + +#include "gb_struct_def.h" + +typedef enum { + GB_RUMBLE_DISABLED, + GB_RUMBLE_CARTRIDGE_ONLY, + GB_RUMBLE_ALL_GAMES +} GB_rumble_mode_t; + +#ifdef GB_INTERNAL +void GB_handle_rumble(GB_gameboy_t *gb); +#endif +void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode); + +#endif /* rumble_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/save_state.c b/waterbox/bsnescore/bsnes/gb/Core/save_state.c new file mode 100644 index 0000000000..9ef6ae35b7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/save_state.c @@ -0,0 +1,413 @@ +#include "gb.h" +#include +#include + +static bool dump_section(FILE *f, const void *src, uint32_t size) +{ + if (fwrite(&size, 1, sizeof(size), f) != sizeof(size)) { + return false; + } + + if (fwrite(src, 1, size, f) != size) { + return false; + } + + return true; +} + +#define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) + +/* Todo: we need a sane and protable save state format. */ +int GB_save_state(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "wb"); + if (!f) { + GB_log(gb, "Could not open save state: %s.\n", strerror(errno)); + return errno; + } + + if (fwrite(GB_GET_SECTION(gb, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; + if (!DUMP_SECTION(gb, f, core_state)) goto error; + if (!DUMP_SECTION(gb, f, dma )) goto error; + if (!DUMP_SECTION(gb, f, mbc )) goto error; + if (!DUMP_SECTION(gb, f, hram )) goto error; + if (!DUMP_SECTION(gb, f, timing )) goto error; + if (!DUMP_SECTION(gb, f, apu )) goto error; + if (!DUMP_SECTION(gb, f, rtc )) goto error; + if (!DUMP_SECTION(gb, f, video )) goto error; + + if (GB_is_hle_sgb(gb)) { + if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; + } + + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + goto error; + } + + if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) { + goto error; + } + + if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { + goto error; + } + + errno = 0; + +error: + fclose(f); + return errno; +} + +#undef DUMP_SECTION + +size_t GB_get_save_state_size(GB_gameboy_t *gb) +{ + return GB_SECTION_SIZE(header) + + GB_SECTION_SIZE(core_state) + sizeof(uint32_t) + + GB_SECTION_SIZE(dma ) + sizeof(uint32_t) + + GB_SECTION_SIZE(mbc ) + sizeof(uint32_t) + + GB_SECTION_SIZE(hram ) + sizeof(uint32_t) + + GB_SECTION_SIZE(timing ) + sizeof(uint32_t) + + GB_SECTION_SIZE(apu ) + sizeof(uint32_t) + + GB_SECTION_SIZE(rtc ) + sizeof(uint32_t) + + GB_SECTION_SIZE(video ) + sizeof(uint32_t) + + (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0) + + gb->mbc_ram_size + + gb->ram_size + + gb->vram_size; +} + +/* A write-line function for memory copying */ +static void buffer_write(const void *src, size_t size, uint8_t **dest) +{ + memcpy(*dest, src, size); + *dest += size; +} + +static void buffer_dump_section(uint8_t **buffer, const void *src, uint32_t size) +{ + buffer_write(&size, sizeof(size), buffer); + buffer_write(src, size, buffer); +} + +#define DUMP_SECTION(gb, buffer, section) buffer_dump_section(&buffer, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) +void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer) +{ + buffer_write(GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header), &buffer); + DUMP_SECTION(gb, buffer, core_state); + DUMP_SECTION(gb, buffer, dma ); + DUMP_SECTION(gb, buffer, mbc ); + DUMP_SECTION(gb, buffer, hram ); + DUMP_SECTION(gb, buffer, timing ); + DUMP_SECTION(gb, buffer, apu ); + DUMP_SECTION(gb, buffer, rtc ); + DUMP_SECTION(gb, buffer, video ); + + if (GB_is_hle_sgb(gb)) { + buffer_dump_section(&buffer, gb->sgb, sizeof(*gb->sgb)); + } + + + buffer_write(gb->mbc_ram, gb->mbc_ram_size, &buffer); + buffer_write(gb->ram, gb->ram_size, &buffer); + buffer_write(gb->vram, gb->vram_size, &buffer); +} + +/* Best-effort read function for maximum future compatibility. */ +static bool read_section(FILE *f, void *dest, uint32_t size, bool fix_broken_windows_saves) +{ + uint32_t saved_size = 0; + if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) { + return false; + } + + if (fix_broken_windows_saves) { + if (saved_size < 4) { + return false; + } + saved_size -= 4; + fseek(f, 4, SEEK_CUR); + } + + if (saved_size <= size) { + if (fread(dest, 1, saved_size, f) != saved_size) { + return false; + } + } + else { + if (fread(dest, 1, size, f) != size) { + return false; + } + fseek(f, saved_size - size, SEEK_CUR); + } + + return true; +} +#undef DUMP_SECTION + +static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) +{ + if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) { + /* This is a save state with a bad printer struct from a 32-bit OS */ + memcpy(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam); + } + if (save->ram_size == 0) { + /* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially + incorrect RAM amount if it's a CGB instance */ + if (GB_is_cgb(save)) { + save->ram_size = 0x2000 * 8; // Incorrect RAM size + } + else { + save->ram_size = gb->ram_size; + } + } + + if (gb->version != save->version) { + GB_log(gb, "The save state is for a different version of SameBoy.\n"); + return false; + } + + if (gb->mbc_ram_size < save->mbc_ram_size) { + GB_log(gb, "The save state has non-matching MBC RAM size.\n"); + return false; + } + + if (gb->vram_size != save->vram_size) { + GB_log(gb, "The save state has non-matching VRAM size. Try changing the emulated model.\n"); + return false; + } + + if (GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) { + GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_hle_sgb(save)? "" : "not "); + return false; + } + + if (gb->ram_size != save->ram_size) { + if (gb->ram_size == 0x1000 * 8 && save->ram_size == 0x2000 * 8) { + /* A bug in versions prior to 0.12 made CGB instances allocate twice the ammount of RAM. + Ignore this issue to retain compatibility with older, 0.11, save states. */ + } + else { + GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n"); + return false; + } + } + + return true; +} + +static void sanitize_state(GB_gameboy_t *gb) +{ + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, false, i * 2); + GB_palette_changed(gb, true, i * 2); + } + + gb->bg_fifo.read_end &= 0xF; + gb->bg_fifo.write_end &= 0xF; + gb->oam_fifo.read_end &= 0xF; + gb->oam_fifo.write_end &= 0xF; + gb->object_low_line_address &= gb->vram_size & ~1; + gb->fetcher_x &= 0x1f; + if (gb->lcd_x > gb->position_in_line) { + gb->lcd_x = gb->position_in_line; + } + + if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { + gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; + } +} + +#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves) + +int GB_load_state(GB_gameboy_t *gb, const char *path) +{ + GB_gameboy_t save; + + /* Every unread value should be kept the same. */ + memcpy(&save, gb, sizeof(save)); + /* ...Except ram size, we use it to detect old saves with incorrect ram sizes */ + save.ram_size = 0; + + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open save state: %s.\n", strerror(errno)); + return errno; + } + + bool fix_broken_windows_saves = false; + if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; + if (save.magic == 0) { + /* Potentially legacy, broken Windows save state */ + fseek(f, 4, SEEK_SET); + if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; + fix_broken_windows_saves = true; + } + if (gb->magic != save.magic) { + GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); + return false; + } + if (!READ_SECTION(&save, f, core_state)) goto error; + if (!READ_SECTION(&save, f, dma )) goto error; + if (!READ_SECTION(&save, f, mbc )) goto error; + if (!READ_SECTION(&save, f, hram )) goto error; + if (!READ_SECTION(&save, f, timing )) goto error; + if (!READ_SECTION(&save, f, apu )) goto error; + if (!READ_SECTION(&save, f, rtc )) goto error; + if (!READ_SECTION(&save, f, video )) goto error; + + if (!verify_and_update_state_compatibility(gb, &save)) { + errno = -1; + goto error; + } + + if (GB_is_hle_sgb(gb)) { + if (!read_section(f, gb->sgb, sizeof(*gb->sgb), false)) goto error; + } + + memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); + if (fread(gb->mbc_ram, 1, save.mbc_ram_size, f) != save.mbc_ram_size) { + fclose(f); + return EIO; + } + + if (fread(gb->ram, 1, gb->ram_size, f) != gb->ram_size) { + fclose(f); + return EIO; + } + + /* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */ + fseek(f, save.ram_size - gb->ram_size, SEEK_CUR); + + if (fread(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { + fclose(f); + return EIO; + } + + size_t orig_ram_size = gb->ram_size; + memcpy(gb, &save, sizeof(save)); + gb->ram_size = orig_ram_size; + + errno = 0; + + sanitize_state(gb); + +error: + fclose(f); + return errno; +} + +#undef READ_SECTION + +/* An read-like function for buffer-copying */ +static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, size_t *buffer_length) +{ + if (length > *buffer_length) { + length = *buffer_length; + } + + memcpy(dest, *buffer, length); + *buffer += length; + *buffer_length -= length; + + return length; +} + +static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size, bool fix_broken_windows_saves) +{ + uint32_t saved_size = 0; + if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) { + return false; + } + + if (saved_size > *buffer_length) return false; + + if (fix_broken_windows_saves) { + if (saved_size < 4) { + return false; + } + saved_size -= 4; + *buffer += 4; + } + + if (saved_size <= size) { + if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) { + return false; + } + } + else { + if (buffer_read(dest, size, buffer, buffer_length) != size) { + return false; + } + *buffer += saved_size - size; + *buffer_length -= saved_size - size; + } + + return true; +} + +#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves) +int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length) +{ + GB_gameboy_t save; + + /* Every unread value should be kept the same. */ + memcpy(&save, gb, sizeof(save)); + bool fix_broken_windows_saves = false; + + if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1; + if (save.magic == 0) { + /* Potentially legacy, broken Windows save state*/ + buffer -= GB_SECTION_SIZE(header) - 4; + length += GB_SECTION_SIZE(header) - 4; + if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1; + fix_broken_windows_saves = true; + } + if (gb->magic != save.magic) { + GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); + return false; + } + if (!READ_SECTION(&save, buffer, length, core_state)) return -1; + if (!READ_SECTION(&save, buffer, length, dma )) return -1; + if (!READ_SECTION(&save, buffer, length, mbc )) return -1; + if (!READ_SECTION(&save, buffer, length, hram )) return -1; + if (!READ_SECTION(&save, buffer, length, timing )) return -1; + if (!READ_SECTION(&save, buffer, length, apu )) return -1; + if (!READ_SECTION(&save, buffer, length, rtc )) return -1; + if (!READ_SECTION(&save, buffer, length, video )) return -1; + + + if (!verify_and_update_state_compatibility(gb, &save)) { + return -1; + } + + if (GB_is_hle_sgb(gb)) { + if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb), false)) return -1; + } + + memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); + if (buffer_read(gb->mbc_ram, save.mbc_ram_size, &buffer, &length) != save.mbc_ram_size) { + return -1; + } + + if (buffer_read(gb->ram, gb->ram_size, &buffer, &length) != gb->ram_size) { + return -1; + } + + if (buffer_read(gb->vram, gb->vram_size, &buffer, &length) != gb->vram_size) { + return -1; + } + + /* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */ + buffer += save.ram_size - gb->ram_size; + length -= save.ram_size - gb->ram_size; + + memcpy(gb, &save, sizeof(save)); + + sanitize_state(gb); + + return 0; +} + +#undef READ_SECTION diff --git a/waterbox/bsnescore/bsnes/gb/Core/save_state.h b/waterbox/bsnescore/bsnes/gb/Core/save_state.h new file mode 100644 index 0000000000..8e5fc4e0e9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/save_state.h @@ -0,0 +1,30 @@ +/* Macros to make the GB_gameboy_t struct more future compatible when state saving */ +#ifndef save_state_h +#define save_state_h +#include + +#define GB_PADDING(type, old_usage) type old_usage##__do_not_use + +#ifdef __cplusplus +/* For bsnes integration. C++ code does not need section information, and throws a fit over certain types such + as anonymous enums inside unions */ +#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__ +#else +#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) union {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0] +#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start)) +#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start)) +#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start)) +#endif + +#define GB_aligned_double __attribute__ ((aligned (8))) double + + +/* Public calls related to save states */ +int GB_save_state(GB_gameboy_t *gb, const char *path); +size_t GB_get_save_state_size(GB_gameboy_t *gb); +/* Assumes buffer is big enough to contain the save state. Use with GB_get_save_state_size(). */ +void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer); + +int GB_load_state(GB_gameboy_t *gb, const char *path); +int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); +#endif /* save_state_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/sgb.c b/waterbox/bsnescore/bsnes/gb/Core/sgb.c new file mode 100644 index 0000000000..c77b0db696 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/sgb.c @@ -0,0 +1,913 @@ +#include "gb.h" +#include "random.h" +#include +#include + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +#define INTRO_ANIMATION_LENGTH 200 + +enum { + PAL01 = 0x00, + PAL23 = 0x01, + PAL03 = 0x02, + PAL12 = 0x03, + ATTR_BLK = 0x04, + ATTR_LIN = 0x05, + ATTR_DIV = 0x06, + ATTR_CHR = 0x07, + PAL_SET = 0x0A, + PAL_TRN = 0x0B, + DATA_SND = 0x0F, + MLT_REQ = 0x11, + CHR_TRN = 0x13, + PCT_TRN = 0x14, + ATTR_TRN = 0x15, + ATTR_SET = 0x16, + MASK_EN = 0x17, +}; + +typedef enum { + MASK_DISABLED, + MASK_FREEZE, + MASK_BLACK, + MASK_COLOR_0, +} mask_mode_t; + +typedef enum { + TRANSFER_LOW_TILES, + TRANSFER_HIGH_TILES, + TRANSFER_BORDER_DATA, + TRANSFER_PALETTES, + TRANSFER_ATTRIBUTES, +} transfer_dest_t; + +#define SGB_PACKET_SIZE 16 +static inline void pal_command(GB_gameboy_t *gb, unsigned first, unsigned second) +{ + gb->sgb->effective_palettes[0] = gb->sgb->effective_palettes[4] = + gb->sgb->effective_palettes[8] = gb->sgb->effective_palettes[12] = + gb->sgb->command[1] | (gb->sgb->command[2] << 8); + + for (unsigned i = 0; i < 3; i++) { + gb->sgb->effective_palettes[first * 4 + i + 1] = gb->sgb->command[3 + i * 2] | (gb->sgb->command[4 + i * 2] << 8); + } + + for (unsigned i = 0; i < 3; i++) { + gb->sgb->effective_palettes[second * 4 + i + 1] = gb->sgb->command[9 + i * 2] | (gb->sgb->command[10 + i * 2] << 8); + } +} + +static inline void load_attribute_file(GB_gameboy_t *gb, unsigned file_index) +{ + if (file_index > 0x2C) return; + uint8_t *output = gb->sgb->attribute_map; + for (unsigned i = 0; i < 90; i++) { + uint8_t byte = gb->sgb->attribute_files[file_index * 90 + i]; + for (unsigned j = 4; j--;) { + *(output++) = byte >> 6; + byte <<= 2; + } + } +} + +static const uint16_t built_in_palettes[] = +{ + 0x67BF, 0x265B, 0x10B5, 0x2866, + 0x637B, 0x3AD9, 0x0956, 0x0000, + 0x7F1F, 0x2A7D, 0x30F3, 0x4CE7, + 0x57FF, 0x2618, 0x001F, 0x006A, + 0x5B7F, 0x3F0F, 0x222D, 0x10EB, + 0x7FBB, 0x2A3C, 0x0015, 0x0900, + 0x2800, 0x7680, 0x01EF, 0x2FFF, + 0x73BF, 0x46FF, 0x0110, 0x0066, + 0x533E, 0x2638, 0x01E5, 0x0000, + 0x7FFF, 0x2BBF, 0x00DF, 0x2C0A, + 0x7F1F, 0x463D, 0x74CF, 0x4CA5, + 0x53FF, 0x03E0, 0x00DF, 0x2800, + 0x433F, 0x72D2, 0x3045, 0x0822, + 0x7FFA, 0x2A5F, 0x0014, 0x0003, + 0x1EED, 0x215C, 0x42FC, 0x0060, + 0x7FFF, 0x5EF7, 0x39CE, 0x0000, + 0x4F5F, 0x630E, 0x159F, 0x3126, + 0x637B, 0x121C, 0x0140, 0x0840, + 0x66BC, 0x3FFF, 0x7EE0, 0x2C84, + 0x5FFE, 0x3EBC, 0x0321, 0x0000, + 0x63FF, 0x36DC, 0x11F6, 0x392A, + 0x65EF, 0x7DBF, 0x035F, 0x2108, + 0x2B6C, 0x7FFF, 0x1CD9, 0x0007, + 0x53FC, 0x1F2F, 0x0E29, 0x0061, + 0x36BE, 0x7EAF, 0x681A, 0x3C00, + 0x7BBE, 0x329D, 0x1DE8, 0x0423, + 0x739F, 0x6A9B, 0x7293, 0x0001, + 0x5FFF, 0x6732, 0x3DA9, 0x2481, + 0x577F, 0x3EBC, 0x456F, 0x1880, + 0x6B57, 0x6E1B, 0x5010, 0x0007, + 0x0F96, 0x2C97, 0x0045, 0x3200, + 0x67FF, 0x2F17, 0x2230, 0x1548, +}; + +static const struct { + char name[16]; + unsigned palette_index; +} palette_assignments[] = +{ + {"ZELDA", 5}, + {"SUPER MARIOLAND", 6}, + {"MARIOLAND2", 0x14}, + {"SUPERMARIOLAND3", 2}, + {"KIRBY DREAM LAND", 0xB}, + {"HOSHINOKA-BI", 0xB}, + {"KIRBY'S PINBALL", 3}, + {"YOSSY NO TAMAGO", 0xC}, + {"MARIO & YOSHI", 0xC}, + {"YOSSY NO COOKIE", 4}, + {"YOSHI'S COOKIE", 4}, + {"DR.MARIO", 0x12}, + {"TETRIS", 0x11}, + {"YAKUMAN", 0x13}, + {"METROID2", 0x1F}, + {"KAERUNOTAMENI", 9}, + {"GOLF", 0x18}, + {"ALLEY WAY", 0x16}, + {"BASEBALL", 0xF}, + {"TENNIS", 0x17}, + {"F1RACE", 0x1E}, + {"KID ICARUS", 0xE}, + {"QIX", 0x19}, + {"SOLARSTRIKER", 7}, + {"X", 0x1C}, + {"GBWARS", 0x15}, +}; + +static void command_ready(GB_gameboy_t *gb) +{ + /* SGB header commands are used to send the contents of the header to the SNES CPU. + A header command looks like this: + Command ID: 0b1111xxx1, where xxx is the packet index. (e.g. F1 for [0x104, 0x112), F3 for [0x112, 0x120)) + Checksum: Simple one byte sum for the following content bytes + 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */ + + if ((gb->sgb->command[0] & 0xF1) == 0xF1) { + if (gb->boot_rom_finished) return; + + uint8_t checksum = 0; + for (unsigned i = 2; i < 0x10; i++) { + checksum += gb->sgb->command[i]; + } + if (checksum != gb->sgb->command[1]) { + GB_log(gb, "Failed checksum for SGB header command, disabling SGB features\n"); + gb->sgb->disable_commands = true; + return; + } + unsigned index = (gb->sgb->command[0] >> 1) & 7; + if (index > 5) { + return; + } + memcpy(&gb->sgb->received_header[index * 14], &gb->sgb->command[2], 14); + if (gb->sgb->command[0] == 0xfb) { + if (gb->sgb->received_header[0x42] != 3 || gb->sgb->received_header[0x47] != 0x33) { + gb->sgb->disable_commands = true; + for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) { + if (memcmp(palette_assignments[i].name, &gb->sgb->received_header[0x30], sizeof(palette_assignments[i].name)) == 0) { + gb->sgb->effective_palettes[0] = built_in_palettes[palette_assignments[i].palette_index * 4 - 4]; + gb->sgb->effective_palettes[1] = built_in_palettes[palette_assignments[i].palette_index * 4 + 1 - 4]; + gb->sgb->effective_palettes[2] = built_in_palettes[palette_assignments[i].palette_index * 4 + 2 - 4]; + gb->sgb->effective_palettes[3] = built_in_palettes[palette_assignments[i].palette_index * 4 + 3 - 4]; + break; + } + } + } + } + return; + } + + /* Ignore malformed commands (0 length)*/ + if ((gb->sgb->command[0] & 7) == 0) return; + + switch (gb->sgb->command[0] >> 3) { + case PAL01: + pal_command(gb, 0, 1); + break; + case PAL23: + pal_command(gb, 2, 3); + break; + case PAL03: + pal_command(gb, 0, 3); + break; + case PAL12: + pal_command(gb, 1, 2); + break; + case ATTR_BLK: { + struct { + uint8_t count; + struct { + uint8_t control; + uint8_t palettes; + uint8_t left, top, right, bottom; + } data[]; + } *command = (void *)(gb->sgb->command + 1); + if (command->count > 0x12) return; + + for (unsigned i = 0; i < command->count; i++) { + bool inside = command->data[i].control & 1; + bool middle = command->data[i].control & 2; + bool outside = command->data[i].control & 4; + uint8_t inside_palette = command->data[i].palettes & 0x3; + uint8_t middle_palette = (command->data[i].palettes >> 2) & 0x3; + uint8_t outside_palette = (command->data[i].palettes >> 4) & 0x3; + + if (inside && !middle && !outside) { + middle = true; + middle_palette = inside_palette; + } + else if (outside && !middle && !inside) { + middle = true; + middle_palette = outside_palette; + } + + command->data[i].left &= 0x1F; + command->data[i].top &= 0x1F; + command->data[i].right &= 0x1F; + command->data[i].bottom &= 0x1F; + + for (unsigned y = 0; y < 18; y++) { + for (unsigned x = 0; x < 20; x++) { + if (x < command->data[i].left || x > command->data[i].right || + y < command->data[i].top || y > command->data[i].bottom) { + if (outside) { + gb->sgb->attribute_map[x + 20 * y] = outside_palette; + } + } + else if (x > command->data[i].left && x < command->data[i].right && + y > command->data[i].top && y < command->data[i].bottom) { + if (inside) { + gb->sgb->attribute_map[x + 20 * y] = inside_palette; + } + } + else if (middle) { + gb->sgb->attribute_map[x + 20 * y] = middle_palette; + } + } + } + } + break; + } + case ATTR_CHR: { + struct __attribute__((packed)) { + uint8_t x, y; + uint16_t length; + uint8_t direction; + uint8_t data[]; + } *command = (void *)(gb->sgb->command + 1); + + uint16_t count = command->length; +#ifdef GB_BIG_ENDIAN + count = __builtin_bswap16(count); +#endif + uint8_t x = command->x; + uint8_t y = command->y; + if (x >= 20 || y >= 18 || (count + 3) / 4 > sizeof(gb->sgb->command) - sizeof(*command) - 1) { + /* TODO: Verify with the SFC BIOS */ + break; + } + + for (unsigned i = 0; i < count; i++) { + uint8_t palette = (command->data[i / 4] >> (((~i) & 3) << 1)) & 3; + gb->sgb->attribute_map[x + 20 * y] = palette; + if (command->direction) { + y++; + if (y == 18) { + x++; + y = 0; + if (x == 20) { + x = 0; + } + } + } + else { + x++; + if (x == 20) { + y++; + x = 0; + if (y == 18) { + y = 0; + } + } + } + } + + break; + } + case ATTR_LIN: { + struct { + uint8_t count; + uint8_t data[]; + } *command = (void *)(gb->sgb->command + 1); + if (command->count > sizeof(gb->sgb->command) - 2) return; + + for (unsigned i = 0; i < command->count; i++) { + bool horizontal = command->data[i] & 0x80; + uint8_t palette = (command->data[i] >> 5) & 0x3; + uint8_t line = (command->data[i]) & 0x1F; + + if (horizontal) { + if (line > 18) continue; + for (unsigned x = 0; x < 20; x++) { + gb->sgb->attribute_map[x + 20 * line] = palette; + } + } + else { + if (line > 20) continue; + for (unsigned y = 0; y < 18; y++) { + gb->sgb->attribute_map[line + 20 * y] = palette; + } + } + } + break; + } + case ATTR_DIV: { + uint8_t high_palette = gb->sgb->command[1] & 3; + uint8_t low_palette = (gb->sgb->command[1] >> 2) & 3; + uint8_t middle_palette = (gb->sgb->command[1] >> 4) & 3; + bool horizontal = gb->sgb->command[1] & 0x40; + uint8_t line = gb->sgb->command[2] & 0x1F; + + for (unsigned y = 0; y < 18; y++) { + for (unsigned x = 0; x < 20; x++) { + if ((horizontal? y : x) < line) { + gb->sgb->attribute_map[x + 20 * y] = low_palette; + } + else if ((horizontal? y : x) == line) { + gb->sgb->attribute_map[x + 20 * y] = middle_palette; + } + else { + gb->sgb->attribute_map[x + 20 * y] = high_palette; + } + } + } + + break; + } + case PAL_SET: + memcpy(&gb->sgb->effective_palettes[0], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[1] + (gb->sgb->command[2] & 1) * 0x100)], + 8); + memcpy(&gb->sgb->effective_palettes[4], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[3] + (gb->sgb->command[4] & 1) * 0x100)], + 8); + memcpy(&gb->sgb->effective_palettes[8], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[5] + (gb->sgb->command[6] & 1) * 0x100)], + 8); + memcpy(&gb->sgb->effective_palettes[12], + &gb->sgb->ram_palettes[4 * (gb->sgb->command[7] + (gb->sgb->command[8] & 1) * 0x100)], + 8); + + gb->sgb->effective_palettes[12] = gb->sgb->effective_palettes[8] = + gb->sgb->effective_palettes[4] = gb->sgb->effective_palettes[0]; + + if (gb->sgb->command[9] & 0x80) { + load_attribute_file(gb, gb->sgb->command[9] & 0x3F); + } + + if (gb->sgb->command[9] & 0x40) { + gb->sgb->mask_mode = MASK_DISABLED; + } + break; + case PAL_TRN: + gb->sgb->vram_transfer_countdown = 2; + gb->sgb->transfer_dest = TRANSFER_PALETTES; + break; + case DATA_SND: + // Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this + break; + case MLT_REQ: + if (gb->sgb->player_count == 1) { + gb->sgb->current_player = 0; + } + gb->sgb->player_count = (gb->sgb->command[1] & 3) + 1; /* Todo: When breaking save state comaptibility, + fix this to be 0 based. */ + if (gb->sgb->player_count == 3) { + gb->sgb->current_player++; + } + gb->sgb->mlt_lock = true; + break; + case CHR_TRN: + gb->sgb->vram_transfer_countdown = 2; + gb->sgb->transfer_dest = (gb->sgb->command[1] & 1)? TRANSFER_HIGH_TILES : TRANSFER_LOW_TILES; + break; + case PCT_TRN: + gb->sgb->vram_transfer_countdown = 2; + gb->sgb->transfer_dest = TRANSFER_BORDER_DATA; + break; + case ATTR_TRN: + gb->sgb->vram_transfer_countdown = 2; + gb->sgb->transfer_dest = TRANSFER_ATTRIBUTES; + break; + case ATTR_SET: + load_attribute_file(gb, gb->sgb->command[0] & 0x3F); + + if (gb->sgb->command[0] & 0x40) { + gb->sgb->mask_mode = MASK_DISABLED; + } + break; + case MASK_EN: + gb->sgb->mask_mode = gb->sgb->command[1] & 3; + break; + default: + if ((gb->sgb->command[0] >> 3) == 8 && + (gb->sgb->command[1] & ~0x80) == 0 && + (gb->sgb->command[2] & ~0x80) == 0) { + /* Mute/dummy sound commands, ignore this command as it's used by many games at startup */ + break; + } + GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3); + for (unsigned i = 0; i < gb->sgb->command_write_index / 8; i++) { + GB_log(gb, "%02x ", gb->sgb->command[i]); + } + GB_log(gb, "\n"); + } +} + +void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) +{ + if (!GB_is_sgb(gb)) return; + if (!GB_is_hle_sgb(gb)) { + /* Notify via callback */ + return; + } + if (gb->sgb->disable_commands) return; + if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) { + return; + } + + uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; + if ((gb->sgb->command[0] & 0xF1) == 0xF1) { + command_size = SGB_PACKET_SIZE * 8; + } + + if ((value & 0x20) == 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) != 0) { + gb->sgb->mlt_lock ^= true; + } + + switch ((value >> 4) & 3) { + case 3: + gb->sgb->ready_for_pulse = true; + if ((gb->sgb->player_count & 1) == 0 && !gb->sgb->mlt_lock) { + gb->sgb->current_player++; + gb->sgb->current_player &= 3; + gb->sgb->mlt_lock = true; + } + break; + + case 2: // Zero + if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; + if (gb->sgb->ready_for_stop) { + if (gb->sgb->command_write_index == command_size) { + command_ready(gb); + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); + } + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = false; + gb->sgb->ready_for_stop = false; + } + else { + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; + } + } + break; + case 1: // One + if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; + if (gb->sgb->ready_for_stop) { + GB_log(gb, "Corrupt SGB command.\n"); + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = false; + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); + } + else { + gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7); + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; + } + } + break; + + case 0: + if (!gb->sgb->ready_for_pulse) return; + gb->sgb->ready_for_write = true; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) != 0 || + gb->sgb->command_write_index == 0 || + gb->sgb->ready_for_stop) { + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); + gb->sgb->ready_for_stop = false; + } + break; + + default: + break; + } +} + +static uint32_t convert_rgb15(GB_gameboy_t *gb, uint16_t color) +{ + return GB_convert_rgb15(gb, color, false); +} + +static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_t fade) +{ + uint8_t r = ((color) & 0x1F) - fade; + uint8_t g = ((color >> 5) & 0x1F) - fade; + uint8_t b = ((color >> 10) & 0x1F) - fade; + + if (r >= 0x20) r = 0; + if (g >= 0x20) g = 0; + if (b >= 0x20) b = 0; + + color = r | (g << 5) | (b << 10); + + return GB_convert_rgb15(gb, color, false); +} + +#include +static void render_boot_animation (GB_gameboy_t *gb) +{ +#include "graphics/sgb_animation_logo.inc" + uint32_t *output = gb->screen; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 48 + 40 * 256; + } + uint8_t *input = animation_logo; + unsigned fade_blue = 0; + unsigned fade_red = 0; + if (gb->sgb->intro_animation < 80 - 32) { + fade_blue = 32; + } + else if (gb->sgb->intro_animation < 80) { + fade_blue = 80 - gb->sgb->intro_animation; + } + else if (gb->sgb->intro_animation > INTRO_ANIMATION_LENGTH - 32) { + fade_red = fade_blue = gb->sgb->intro_animation - INTRO_ANIMATION_LENGTH + 32; + } + uint32_t colors[] = { + convert_rgb15(gb, 0), + convert_rgb15_with_fade(gb, 0x14A5, fade_blue), + convert_rgb15_with_fade(gb, 0x54E0, fade_blue), + convert_rgb15_with_fade(gb, 0x0019, fade_red), + convert_rgb15(gb, 0x0011), + convert_rgb15(gb, 0x0009), + }; + unsigned y_min = (144 - animation_logo_height) / 2; + unsigned y_max = y_min + animation_logo_height; + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + if (y < y_min || y >= y_max) { + *(output++) = colors[0]; + } + else { + uint8_t color = *input; + if (color >= 3) { + if (color == gb->sgb->intro_animation / 2 - 3) { + color = 5; + } + else if (color == gb->sgb->intro_animation / 2 - 4) { + color = 4; + } + else if (color < gb->sgb->intro_animation / 2 - 4) { + color = 3; + } + else { + color = 0; + } + } + *(output++) = colors[color]; + input++; + } + } + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } + } +} + +static void render_jingle(GB_gameboy_t *gb, size_t count); +void GB_sgb_render(GB_gameboy_t *gb) +{ + if (gb->apu_output.sample_rate) { + render_jingle(gb, gb->apu_output.sample_rate / GB_get_usual_frame_rate(gb)); + } + + if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++; + + if (gb->sgb->vram_transfer_countdown) { + if (--gb->sgb->vram_transfer_countdown == 0) { + if (gb->sgb->transfer_dest == TRANSFER_LOW_TILES || gb->sgb->transfer_dest == TRANSFER_HIGH_TILES) { + uint8_t *base = &gb->sgb->pending_border.tiles[gb->sgb->transfer_dest == TRANSFER_HIGH_TILES ? 0x80 * 8 * 8 : 0]; + for (unsigned tile = 0; tile < 0x80; tile++) { + unsigned tile_x = (tile % 10) * 16; + unsigned tile_y = (tile / 10) * 8; + for (unsigned y = 0; y < 0x8; y++) { + for (unsigned x = 0; x < 0x8; x++) { + base[tile * 8 * 8 + y * 8 + x] = gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] + + gb->sgb->screen_buffer[(tile_x + x + 8) + (tile_y + y) * 160] * 4; + } + } + } + + } + else { + unsigned size = 0; + uint16_t *data = NULL; + + switch (gb->sgb->transfer_dest) { + case TRANSFER_PALETTES: + size = 0x100; + data = gb->sgb->ram_palettes; + break; + case TRANSFER_BORDER_DATA: + size = 0x88; + data = gb->sgb->pending_border.raw_data; + break; + case TRANSFER_ATTRIBUTES: + size = 0xFE; + data = (uint16_t *)gb->sgb->attribute_files; + break; + default: + return; // Corrupt state? + } + + for (unsigned tile = 0; tile < size; tile++) { + unsigned tile_x = (tile % 20) * 8; + unsigned tile_y = (tile / 20) * 8; + for (unsigned y = 0; y < 0x8; y++) { + static const uint16_t pixel_to_bits[4] = {0x0000, 0x0080, 0x8000, 0x8080}; + *data = 0; + for (unsigned x = 0; x < 8; x++) { + *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x; + } +#ifdef GB_BIG_ENDIAN + if (gb->sgb->transfer_dest == TRANSFER_ATTRIBUTES) { + *data = __builtin_bswap16(*data); + } +#endif + data++; + } + } + if (gb->sgb->transfer_dest == TRANSFER_BORDER_DATA) { + gb->sgb->border_animation = 64; + } + } + } + } + + if (!gb->screen || !gb->rgb_encode_callback || gb->disable_rendering) return; + + uint32_t colors[4 * 4]; + for (unsigned i = 0; i < 4 * 4; i++) { + colors[i] = convert_rgb15(gb, gb->sgb->effective_palettes[i]); + } + + if (gb->sgb->mask_mode != MASK_FREEZE) { + memcpy(gb->sgb->effective_screen_buffer, + gb->sgb->screen_buffer, + sizeof(gb->sgb->effective_screen_buffer)); + } + + if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) { + render_boot_animation(gb); + } + else { + uint32_t *output = gb->screen; + if (gb->border_mode != GB_BORDER_NEVER) { + output += 48 + 40 * 256; + } + uint8_t *input = gb->sgb->effective_screen_buffer; + switch ((mask_mode_t) gb->sgb->mask_mode) { + case MASK_DISABLED: + case MASK_FREEZE: { + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3; + *(output++) = colors[(*(input++) & 3) + palette * 4]; + } + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } + } + break; + } + case MASK_BLACK: + { + uint32_t black = convert_rgb15(gb, 0); + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + *(output++) = black; + } + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } + } + break; + } + case MASK_COLOR_0: + { + for (unsigned y = 0; y < 144; y++) { + for (unsigned x = 0; x < 160; x++) { + *(output++) = colors[0]; + } + if (gb->border_mode != GB_BORDER_NEVER) { + output += 256 - 160; + } + } + break; + } + } + } + + uint32_t border_colors[16 * 4]; + if (gb->sgb->border_animation == 0 || gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) { + for (unsigned i = 0; i < 16 * 4; i++) { + border_colors[i] = convert_rgb15(gb, gb->sgb->border.palette[i]); + } + } + else if (gb->sgb->border_animation > 32) { + gb->sgb->border_animation--; + for (unsigned i = 0; i < 16 * 4; i++) { + border_colors[i] = convert_rgb15_with_fade(gb, gb->sgb->border.palette[i], 64 - gb->sgb->border_animation); + } + } + else { + gb->sgb->border_animation--; + for (unsigned i = 0; i < 16 * 4; i++) { + border_colors[i] = convert_rgb15_with_fade(gb, gb->sgb->border.palette[i], gb->sgb->border_animation); + } + } + + + if (gb->sgb->border_animation == 32) { + memcpy(&gb->sgb->border, &gb->sgb->pending_border, sizeof(gb->sgb->border)); + } + + for (unsigned tile_y = 0; tile_y < 28; tile_y++) { + for (unsigned tile_x = 0; tile_x < 32; tile_x++) { + bool gb_area = false; + if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) { + gb_area = true; + } + else if (gb->border_mode == GB_BORDER_NEVER) { + continue; + } + uint16_t tile = gb->sgb->border.map[tile_x + tile_y * 32]; + uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; + uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; + uint8_t palette = (tile >> 10) & 3; + for (unsigned y = 0; y < 8; y++) { + for (unsigned x = 0; x < 8; x++) { + uint8_t color = gb->sgb->border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; + uint32_t *output = gb->screen; + if (gb->border_mode == GB_BORDER_NEVER) { + output += (tile_x - 6) * 8 + x + ((tile_y - 5) * 8 + y) * 160; + } + else { + output += tile_x * 8 + x + (tile_y * 8 + y) * 256; + } + if (color == 0) { + if (gb_area) continue; + *output = colors[0]; + } + else { + *output = border_colors[color + palette * 16]; + } + } + } + } + } +} + +void GB_sgb_load_default_data(GB_gameboy_t *gb) +{ + +#include "graphics/sgb_border.inc" + + memcpy(gb->sgb->border.map, tilemap, sizeof(tilemap)); + memcpy(gb->sgb->border.palette, palette, sizeof(palette)); + + /* Expand tileset */ + for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) { + for (unsigned y = 0; y < 8; y++) { + for (unsigned x = 0; x < 8; x++) { + gb->sgb->border.tiles[tile * 8 * 8 + y * 8 + x] = + (tiles[tile * 32 + y * 2 + 0] & (1 << (7 ^ x)) ? 1 : 0) | + (tiles[tile * 32 + y * 2 + 1] & (1 << (7 ^ x)) ? 2 : 0) | + (tiles[tile * 32 + y * 2 + 16] & (1 << (7 ^ x)) ? 4 : 0) | + (tiles[tile * 32 + y * 2 + 17] & (1 << (7 ^ x)) ? 8 : 0); + } + } + } + + if (gb->model != GB_MODEL_SGB2) { + /* Delete the "2" */ + gb->sgb->border.map[25 * 32 + 25] = gb->sgb->border.map[25 * 32 + 26] = + gb->sgb->border.map[26 * 32 + 25] = gb->sgb->border.map[26 * 32 + 26] = + gb->sgb->border.map[27 * 32 + 25] = gb->sgb->border.map[27 * 32 + 26] = + gb->sgb->border.map[0]; + + /* Re-center */ + memmove(&gb->sgb->border.map[25 * 32 + 1], &gb->sgb->border.map[25 * 32], (32 * 3 - 1) * sizeof(gb->sgb->border.map[0])); + } + gb->sgb->effective_palettes[0] = built_in_palettes[0]; + gb->sgb->effective_palettes[1] = built_in_palettes[1]; + gb->sgb->effective_palettes[2] = built_in_palettes[2]; + gb->sgb->effective_palettes[3] = built_in_palettes[3]; +} + +static double fm_synth(double phase) +{ + return (sin(phase * M_PI * 2) + + sin(phase * M_PI * 2 + sin(phase * M_PI * 2)) + + sin(phase * M_PI * 2 + sin(phase * M_PI * 3)) + + sin(phase * M_PI * 2 + sin(phase * M_PI * 4))) / 4; +} + +static double fm_sweep(double phase) +{ + double ret = 0; + for (unsigned i = 0; i < 8; i++) { + ret += sin((phase * M_PI * 2 + sin(phase * M_PI * 8) / 4) * pow(1.25, i)) * (8 - i) / 36; + } + return ret; +} +static double random_double(void) +{ + return ((signed)(GB_random32() % 0x10001) - 0x8000) / (double) 0x8000; +} + +static void render_jingle(GB_gameboy_t *gb, size_t count) +{ + const double frequencies[7] = { + 466.16, // Bb4 + 587.33, // D5 + 698.46, // F5 + 830.61, // Ab5 + 1046.50, // C6 + 1244.51, // Eb6 + 1567.98, // G6 + }; + + assert(gb->apu_output.sample_callback); + + if (gb->sgb->intro_animation < 0) { + GB_sample_t sample = {0, 0}; + for (unsigned i = 0; i < count; i++) { + gb->apu_output.sample_callback(gb, &sample); + } + return; + } + + if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return; + + signed jingle_stage = (gb->sgb->intro_animation - 64) / 3; + double sweep_cutoff_ratio = 2000.0 * pow(2, gb->sgb->intro_animation / 20.0) / gb->apu_output.sample_rate; + double sweep_phase_shift = 1000.0 * pow(2, gb->sgb->intro_animation / 40.0) / gb->apu_output.sample_rate; + if (sweep_cutoff_ratio > 1) { + sweep_cutoff_ratio = 1; + } + + GB_sample_t stereo; + for (unsigned i = 0; i < count; i++) { + double sample = 0; + for (signed f = 0; f < 7 && f < jingle_stage; f++) { + sample += fm_synth(gb->sgb_intro_jingle_phases[f]) * + (0.75 * pow(0.5, jingle_stage - f) + 0.25) / 5.0; + gb->sgb_intro_jingle_phases[f] += frequencies[f] / gb->apu_output.sample_rate; + } + if (gb->sgb->intro_animation > 100) { + sample *= pow((INTRO_ANIMATION_LENGTH - gb->sgb->intro_animation) / (INTRO_ANIMATION_LENGTH - 100.0), 3); + } + + if (gb->sgb->intro_animation < 120) { + double next = fm_sweep(gb->sgb_intro_sweep_phase) * 0.3 + random_double() * 0.7; + gb->sgb_intro_sweep_phase += sweep_phase_shift; + + gb->sgb_intro_sweep_previous_sample = next * (sweep_cutoff_ratio) + + gb->sgb_intro_sweep_previous_sample * (1 - sweep_cutoff_ratio); + sample += gb->sgb_intro_sweep_previous_sample * pow((120 - gb->sgb->intro_animation) / 120.0, 2) * 0.8; + } + + stereo.left = stereo.right = sample * 0x7000; + gb->apu_output.sample_callback(gb, &stereo); + } + + return; +} + diff --git a/waterbox/bsnescore/bsnes/gb/Core/sgb.h b/waterbox/bsnescore/bsnes/gb/Core/sgb.h new file mode 100644 index 0000000000..aae9f755c9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/sgb.h @@ -0,0 +1,67 @@ +#ifndef sgb_h +#define sgb_h +#include "gb_struct_def.h" +#include +#include + +typedef struct GB_sgb_s GB_sgb_t; +typedef struct { + uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/ + union { + struct { + uint16_t map[32 * 32]; + uint16_t palette[16 * 4]; + }; + uint16_t raw_data[0x440]; + }; +} GB_sgb_border_t; + +#ifdef GB_INTERNAL +struct GB_sgb_s { + uint8_t command[16 * 7]; + uint16_t command_write_index; + bool ready_for_pulse; + bool ready_for_write; + bool ready_for_stop; + bool disable_commands; + + /* Screen buffer */ + uint8_t screen_buffer[160 * 144]; // Live image from the Game Boy + uint8_t effective_screen_buffer[160 * 144]; // Image actually rendered to the screen + + /* Multiplayer Input */ + uint8_t player_count, current_player; + + /* Mask */ + uint8_t mask_mode; + + /* Data Transfer */ + uint8_t vram_transfer_countdown, transfer_dest; + + /* Border */ + GB_sgb_border_t border, pending_border; + uint8_t border_animation; + + /* Colorization */ + uint16_t effective_palettes[4 * 4]; + uint16_t ram_palettes[4 * 512]; + uint8_t attribute_map[20 * 18]; + uint8_t attribute_files[0xFE0]; + + /* Intro */ + int16_t intro_animation; + + /* GB Header */ + uint8_t received_header[0x54]; + + /* Multiplayer (cont) */ + bool mlt_lock; +}; + +void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); +void GB_sgb_render(GB_gameboy_t *gb); +void GB_sgb_load_default_data(GB_gameboy_t *gb); + +#endif + +#endif diff --git a/waterbox/bsnescore/bsnes/gb/Core/sm83_cpu.c b/waterbox/bsnescore/bsnes/gb/Core/sm83_cpu.c new file mode 100644 index 0000000000..3b3ecebb30 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/sm83_cpu.c @@ -0,0 +1,1590 @@ +#include +#include +#include +#include "gb.h" + + +typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); + +typedef enum { + /* Default behavior. If the CPU writes while another component reads, it reads the old value */ + GB_CONFLICT_READ_OLD, + /* If the CPU writes while another component reads, it reads the new value */ + GB_CONFLICT_READ_NEW, + /* If the CPU and another component write at the same time, the CPU's value "wins" */ + GB_CONFLICT_WRITE_CPU, + /* Register specific values */ + GB_CONFLICT_STAT_CGB, + GB_CONFLICT_STAT_DMG, + GB_CONFLICT_PALETTE_DMG, + GB_CONFLICT_PALETTE_CGB, + GB_CONFLICT_DMG_LCDC, + GB_CONFLICT_SGB_LCDC, + GB_CONFLICT_WX, +} GB_conflict_t; + +/* Todo: How does double speed mode affect these? */ +static const GB_conflict_t cgb_conflict_map[0x80] = { + [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, + [GB_IO_LYC] = GB_CONFLICT_WRITE_CPU, + [GB_IO_STAT] = GB_CONFLICT_STAT_CGB, + [GB_IO_BGP] = GB_CONFLICT_PALETTE_CGB, + [GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB, + [GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB, + + /* Todo: most values not verified, and probably differ between revisions */ +}; + +/* Todo: verify on an MGB */ +static const GB_conflict_t dmg_conflict_map[0x80] = { + [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, + [GB_IO_LYC] = GB_CONFLICT_READ_OLD, + [GB_IO_LCDC] = GB_CONFLICT_DMG_LCDC, + [GB_IO_SCY] = GB_CONFLICT_READ_NEW, + [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, + + [GB_IO_BGP] = GB_CONFLICT_PALETTE_DMG, + [GB_IO_OBP0] = GB_CONFLICT_PALETTE_DMG, + [GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG, + [GB_IO_WY] = GB_CONFLICT_READ_OLD, + [GB_IO_WX] = GB_CONFLICT_WX, + + /* Todo: these were not verified at all */ + [GB_IO_SCX] = GB_CONFLICT_READ_NEW, +}; + +/* Todo: Verify on an SGB1 */ +static const GB_conflict_t sgb_conflict_map[0x80] = { + [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, + [GB_IO_LYC] = GB_CONFLICT_READ_OLD, + [GB_IO_LCDC] = GB_CONFLICT_SGB_LCDC, + [GB_IO_SCY] = GB_CONFLICT_READ_NEW, + [GB_IO_STAT] = GB_CONFLICT_STAT_DMG, + + [GB_IO_BGP] = GB_CONFLICT_READ_NEW, + [GB_IO_OBP0] = GB_CONFLICT_READ_NEW, + [GB_IO_OBP1] = GB_CONFLICT_READ_NEW, + [GB_IO_WY] = GB_CONFLICT_READ_OLD, + [GB_IO_WX] = GB_CONFLICT_WX, + + /* Todo: these were not verified at all */ + [GB_IO_SCX] = GB_CONFLICT_READ_NEW, +}; + +static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + uint8_t ret = GB_read_memory(gb, addr); + gb->pending_cycles = 4; + return ret; +} + +static uint8_t cycle_read_inc_oam_bug(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + GB_trigger_oam_bug_read_increase(gb, addr); /* Todo: test T-cycle timing */ + uint8_t ret = GB_read_memory(gb, addr); + gb->pending_cycles = 4; + return ret; +} + +/* A special case for IF during ISR, returns the old value of IF. */ +/* TODO: Verify the timing, it might be wrong in cases where, in the same M cycle, IF + is both read be the CPU, modified by the ISR, and modified by an actual interrupt. + If this timing proves incorrect, the ISR emulation must be updated so IF reads are + timed correctly. */ +static uint8_t cycle_write_if(GB_gameboy_t *gb, uint8_t value) +{ + assert(gb->pending_cycles); + GB_advance_cycles(gb, gb->pending_cycles); + uint8_t old = (gb->io_registers[GB_IO_IF]) & 0x1F; + GB_write_memory(gb, 0xFF00 + GB_IO_IF, value); + gb->pending_cycles = 4; + return old; +} + +static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + assert(gb->pending_cycles); + GB_conflict_t conflict = GB_CONFLICT_READ_OLD; + if ((addr & 0xFF80) == 0xFF00) { + const GB_conflict_t *map = NULL; + if (GB_is_cgb(gb)) { + map = cgb_conflict_map; + } + else if (GB_is_sgb(gb)) { + map = sgb_conflict_map; + } + else { + map = dmg_conflict_map; + } + conflict = map[addr & 0x7F]; + } + switch (conflict) { + case GB_CONFLICT_READ_OLD: + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + return; + + case GB_CONFLICT_READ_NEW: + GB_advance_cycles(gb, gb->pending_cycles - 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + return; + + case GB_CONFLICT_WRITE_CPU: + GB_advance_cycles(gb, gb->pending_cycles + 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + return; + + /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */ + case GB_CONFLICT_STAT_DMG: + GB_advance_cycles(gb, gb->pending_cycles); + /* State 7 is the edge between HBlank and OAM mode, and it behaves a bit weird. + The OAM interrupt seems to be blocked by HBlank interrupts in that case, despite + the timing not making much sense for that. + This is a hack to simulate this effect */ + if (gb->display_state == 7 && (gb->io_registers[GB_IO_STAT] & 0x28) == 0x08) { + GB_write_memory(gb, addr, ~0x20); + } + else { + GB_write_memory(gb, addr, 0xFF); + } + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + return; + + case GB_CONFLICT_STAT_CGB: { + /* Todo: Verify this with SCX adjustments */ + /* The LYC bit behaves differently */ + uint8_t old_value = GB_read_memory(gb, addr); + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, (old_value & 0x40) | (value & ~0x40)); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + return; + } + + /* There is some "time travel" going on with these two values, as it appears + that there's some off-by-1-T-cycle timing issue in the PPU implementation. + + This is should be accurate for every measureable scenario, though. */ + + case GB_CONFLICT_PALETTE_DMG: { + GB_advance_cycles(gb, gb->pending_cycles - 2); + uint8_t old_value = GB_read_memory(gb, addr); + GB_write_memory(gb, addr, value | old_value); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + return; + } + + case GB_CONFLICT_PALETTE_CGB: { + GB_advance_cycles(gb, gb->pending_cycles - 2); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 6; + return; + } + + case GB_CONFLICT_DMG_LCDC: { + /* Similar to the palette registers, these interact directly with the LCD, so they appear to be affected by it. Both my DMG (B, blob) and Game Boy Light behave this way though. + + Additionally, LCDC.1 is very nasty because on the it is read both by the FIFO when popping pixels, + and the sprite-fetching state machine, and both behave differently when it comes to access conflicts. + Hacks ahead. + */ + + + + uint8_t old_value = GB_read_memory(gb, addr); + GB_advance_cycles(gb, gb->pending_cycles - 2); + + if (/* gb->model != GB_MODEL_MGB && */ gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) { + old_value &= ~2; + } + + GB_write_memory(gb, addr, old_value | (value & 1)); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + return; + } + + case GB_CONFLICT_SGB_LCDC: { + /* Simplified version of the above */ + + uint8_t old_value = GB_read_memory(gb, addr); + GB_advance_cycles(gb, gb->pending_cycles - 2); + /* Hack to force aborting object fetch */ + GB_write_memory(gb, addr, value); + GB_write_memory(gb, addr, old_value); + GB_advance_cycles(gb, 1); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 5; + return; + } + + case GB_CONFLICT_WX: + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->wx_just_changed = true; + GB_advance_cycles(gb, 1); + gb->wx_just_changed = false; + gb->pending_cycles = 3; + return; + } +} + +static void cycle_no_access(GB_gameboy_t *gb) +{ + gb->pending_cycles += 4; +} + +static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id) +{ + if (GB_is_cgb(gb)) { + /* Slight optimization */ + gb->pending_cycles += 4; + return; + } + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ + gb->pending_cycles = 4; + +} + +static void flush_pending_cycles(GB_gameboy_t *gb) +{ + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + gb->pending_cycles = 0; +} + +/* Todo: test if multi-byte opcodes trigger the OAM bug correctly */ + +static void ill(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_log(gb, "Illegal Opcode. Halting.\n"); + gb->interrupt_enable = 0; + gb->halted = true; +} + +static void nop(GB_gameboy_t *gb, uint8_t opcode) +{ +} + +static void enter_stop_mode(GB_gameboy_t *gb) +{ + gb->stopped = true; + gb->oam_ppu_blocked = !gb->oam_read_blocked; + gb->vram_ppu_blocked = !gb->vram_read_blocked; + gb->cgb_palettes_ppu_blocked = !gb->cgb_palettes_blocked; +} + +static void leave_stop_mode(GB_gameboy_t *gb) +{ + /* The CPU takes more time to wake up then the other components */ + for (unsigned i = 0x200; i--;) { + GB_advance_cycles(gb, 0x10); + } + gb->stopped = false; + gb->oam_ppu_blocked = false; + gb->vram_ppu_blocked = false; + gb->cgb_palettes_ppu_blocked = false; +} + +static void stop(GB_gameboy_t *gb, uint8_t opcode) +{ + if (gb->io_registers[GB_IO_KEY1] & 0x1) { + flush_pending_cycles(gb); + bool needs_alignment = false; + + GB_advance_cycles(gb, 0x4); + /* Make sure we keep the CPU ticks aligned correctly when returning from double speed mode */ + if (gb->double_speed_alignment & 7) { + GB_advance_cycles(gb, 0x4); + needs_alignment = true; + } + + gb->cgb_double_speed ^= true; + gb->io_registers[GB_IO_KEY1] = 0; + + enter_stop_mode(gb); + leave_stop_mode(gb); + + if (!needs_alignment) { + GB_advance_cycles(gb, 0x4); + } + + } + else { + GB_timing_sync(gb); + if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { + /* HW Bug? When STOP is executed while a button is down, the CPU halts forever + yet the other hardware keeps running. */ + gb->interrupt_enable = 0; + gb->halted = true; + } + else { + enter_stop_mode(gb); + } + } + + /* Todo: is PC being actually read? */ + gb->pc++; +} + +/* Operand naming conventions for functions: + r = 8-bit register + lr = low 8-bit register + hr = high 8-bit register + rr = 16-bit register + d8 = 8-bit imm + d16 = 16-bit imm + d.. = [..] + cc = condition code (z, nz, c, nc) + */ + +static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint16_t value; + register_id = (opcode >> 4) + 1; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + value |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; + gb->registers[register_id] = value; +} + +static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = (opcode >> 4) + 1; + cycle_write(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); +} + +static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id = (opcode >> 4) + 1; + cycle_oam_bug(gb, register_id); + gb->registers[register_id]++; +} + +static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] += 0x100; + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} +static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] -= 0x100; + gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F00) == 0xF00) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] &= 0xFF; + gb->registers[register_id] |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; +} + +static void rlca(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; + + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x0100; + } +} + +static void rla(GB_gameboy_t *gb, uint8_t opcode) +{ + bool bit7 = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; + bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + if (carry) { + gb->registers[GB_REGISTER_AF] |= 0x0100; + } + if (bit7) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify order is correct */ + uint16_t addr; + addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; + cycle_write(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); + cycle_write(gb, addr + 1, gb->registers[GB_REGISTER_SP] >> 8); +} + +static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t hl = gb->registers[GB_REGISTER_HL]; + uint16_t rr; + uint8_t register_id; + cycle_no_access(gb); + register_id = (opcode >> 4) + 1; + rr = gb->registers[register_id]; + gb->registers[GB_REGISTER_HL] = hl + rr; + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG); + + /* The meaning of the Half Carry flag is really hard to track -_- */ + if (((hl & 0xFFF) + (rr & 0xFFF)) & 0x1000) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ( ((unsigned) hl + (unsigned) rr) & 0x10000) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = (opcode >> 4) + 1; + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[register_id]) << 8; +} + +static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id = (opcode >> 4) + 1; + cycle_oam_bug(gb, register_id); + gb->registers[register_id]--; +} + +static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint8_t value; + register_id = (opcode >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) + 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} +static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint8_t value; + register_id = (opcode >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) - 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F) == 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = (opcode >> 4) + 1; + gb->registers[register_id] &= 0xFF00; + gb->registers[register_id] |= cycle_read_inc_oam_bug(gb, gb->pc++); +} + +static void rrca(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry = (gb->registers[GB_REGISTER_AF] & 0x100) != 0; + + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x8000; + } +} + +static void rra(GB_gameboy_t *gb, uint8_t opcode) +{ + bool bit1 = (gb->registers[GB_REGISTER_AF] & 0x0100) != 0; + bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + if (carry) { + gb->registers[GB_REGISTER_AF] |= 0x8000; + } + if (bit1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify timing */ + gb->pc += (int8_t)cycle_read_inc_oam_bug(gb, gb->pc) + 1; + cycle_no_access(gb); +} + +static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 1: + return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 2: + return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + case 3: + return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + } + + return false; +} + +static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int8_t offset = cycle_read_inc_oam_bug(gb, gb->pc++); + if (condition_code(gb, opcode)) { + gb->pc += offset; + cycle_no_access(gb); + } +} + +static void daa(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t result = gb->registers[GB_REGISTER_AF] >> 8; + + gb->registers[GB_REGISTER_AF] &= ~(0xFF00 | GB_ZERO_FLAG); + + if (gb->registers[GB_REGISTER_AF] & GB_SUBTRACT_FLAG) { + if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { + result = (result - 0x06) & 0xFF; + } + + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + result -= 0x60; + } + } + else { + if ((gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) || (result & 0x0F) > 0x09) { + result += 0x06; + } + + if ((gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) || result > 0x9F) { + result += 0x60; + } + } + + if ((result & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + + if ((result & 0x100) == 0x100) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + + gb->registers[GB_REGISTER_AF] &= ~GB_HALF_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] |= result << 8; +} + +static void cpl(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->registers[GB_REGISTER_AF] ^= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG; +} + +static void scf(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); +} + +static void ccf(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->registers[GB_REGISTER_AF] ^= GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); +} + +static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) +{ + cycle_write(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) +{ + cycle_write(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_HL]++) << 8; +} + +static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_HL]--) << 8; +} + +static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) + 1; + cycle_write(gb, gb->registers[GB_REGISTER_HL], value); + + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + if ((value & 0x0F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) - 1; + cycle_write(gb, gb->registers[GB_REGISTER_HL], value); + + gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + if ((value & 0x0F) == 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t data = cycle_read_inc_oam_bug(gb, gb->pc++); + cycle_write(gb, gb->registers[GB_REGISTER_HL], data); +} + +static uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t src_register_id; + uint8_t src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = opcode & 1; + if (src_register_id == GB_REGISTER_AF) { + if (src_low) { + return gb->registers[GB_REGISTER_AF] >> 8; + } + return cycle_read(gb, gb->registers[GB_REGISTER_HL]); + } + if (src_low) { + return gb->registers[src_register_id] & 0xFF; + } + return gb->registers[src_register_id] >> 8; +} + +static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) +{ + uint8_t src_register_id; + uint8_t src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = opcode & 1; + + if (src_register_id == GB_REGISTER_AF) { + if (src_low) { + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= value << 8; + } + else { + cycle_write(gb, gb->registers[GB_REGISTER_HL], value); + } + } + else { + if (src_low) { + gb->registers[src_register_id] &= 0xFF00; + gb->registers[src_register_id] |= value; + } + else { + gb->registers[src_register_id] &= 0xFF; + gb->registers[src_register_id] |= value << 8; + } + } +} + +/* The LD r,r instruction is extremely common and extremely simple. Decoding this opcode at runtime is a significent + performance hit, so we generate functions for every ld x,y couple (including [hl]) at compile time using macros. */ + +/* Todo: It's probably wise to do the same to all opcodes. */ + +#define LD_X_Y(x, y) \ +static void ld_##x##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ + gb->x = gb->y;\ +} + +#define LD_X_DHL(x) \ +static void ld_##x##_##dhl(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +gb->x = cycle_read(gb, gb->registers[GB_REGISTER_HL]); \ +} + +#define LD_DHL_Y(y) \ +static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +cycle_write(gb, gb->registers[GB_REGISTER_HL], gb->y); \ +} + +LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) +LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a) +LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a) +LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a) +LD_X_Y(h,b) LD_X_Y(h,c) LD_X_Y(h,d) LD_X_Y(h,e) LD_X_Y(h,l) LD_X_DHL(h) LD_X_Y(h,a) +LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL(l) LD_X_Y(l,a) +LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a) +LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a) + +// fire the debugger if software breakpoints are enabled +static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode) +{ + if (gb->has_software_breakpoints) { + gb->debug_stopped = true; + } +} + +static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a + value) << 8; + if ((uint8_t)(a + value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) + ((unsigned) value) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8; + + if ((uint8_t)(a + value + carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; + + if ((uint8_t) (a - value - carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a | value) << 8; + if ((a | value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void halt(GB_gameboy_t *gb, uint8_t opcode) +{ + assert(gb->pending_cycles == 4); + gb->pending_cycles = 0; + GB_advance_cycles(gb, 4); + + gb->halted = true; + /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ + if (((gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0)) { + if (gb->ime) { + gb->halted = false; + gb->pc--; + } + else { + gb->halted = false; + gb->halt_bug = true; + } + } + gb->just_halted = true; +} + +static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = ((opcode >> 4) + 1) & 3; + gb->registers[register_id] = cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_SP]++); + gb->registers[register_id] |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8; + gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test. +} + +static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8); + if (condition_code(gb, opcode)) { + cycle_no_access(gb); + gb->pc = addr; + } +} + +static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc); + addr |= (cycle_read_inc_oam_bug(gb, gb->pc + 1) << 8); + cycle_no_access(gb); + gb->pc = addr; + +} + +static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8); + if (condition_code(gb, opcode)) { + cycle_oam_bug(gb, GB_REGISTER_SP); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + gb->pc = addr; + + GB_debugger_call_hook(gb, call_addr); + } +} + +static void push_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + cycle_oam_bug(gb, GB_REGISTER_SP); + register_id = ((opcode >> 4) + 1) & 3; + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) >> 8); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); +} + +static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a + value) << 8; + if ((uint8_t) (a + value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) + ((unsigned) value) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8; + + if (gb->registers[GB_REGISTER_AF] == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) + ((unsigned) value) + carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; + + if ((uint8_t) (a - value - carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned) a) - ((unsigned) value) - carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a | value) << 8; + if ((a | value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + value = cycle_read_inc_oam_bug(gb, gb->pc++); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void rst(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + cycle_oam_bug(gb, GB_REGISTER_SP); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + gb->pc = opcode ^ 0xC7; + GB_debugger_call_hook(gb, call_addr); +} + +static void ret(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_debugger_ret_hook(gb); + gb->pc = cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_SP]++); + gb->pc |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8; + cycle_no_access(gb); +} + +static void reti(GB_gameboy_t *gb, uint8_t opcode) +{ + ret(gb, opcode); + gb->ime = true; +} + +static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + cycle_no_access(gb); + ret(gb, opcode); + } + else { + cycle_no_access(gb); + } +} + +static void call_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8); + cycle_oam_bug(gb, GB_REGISTER_SP); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + gb->pc = addr; + GB_debugger_call_hook(gb, call_addr); +} + +static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t temp = cycle_read_inc_oam_bug(gb, gb->pc++); + cycle_write(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->registers[GB_REGISTER_AF] &= 0xFF; + uint8_t temp = cycle_read_inc_oam_bug(gb, gb->pc++); + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + temp) << 8; +} + +static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) +{ + cycle_write(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; +} + +static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t offset; + uint16_t sp = gb->registers[GB_REGISTER_SP]; + offset = (int8_t) cycle_read_inc_oam_bug(gb, gb->pc++); + cycle_no_access(gb); + cycle_no_access(gb); + gb->registers[GB_REGISTER_SP] += offset; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + + /* A new instruction, a new meaning for Half Carry! */ + if ((sp & 0xF) + (offset & 0xF) > 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((sp & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->pc = gb->registers[GB_REGISTER_HL]; +} + +static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr; + addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; + cycle_write(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); +} + +static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr; + gb->registers[GB_REGISTER_AF] &= 0xFF; + addr = cycle_read_inc_oam_bug(gb, gb->pc++); + addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, addr) << 8; +} + +static void di(GB_gameboy_t *gb, uint8_t opcode) +{ + /* DI is NOT delayed, not even on a CGB. Mooneye's di_timing-GS test fails on a CGB + for different reasons. */ + gb->ime = false; +} + +static void ei(GB_gameboy_t *gb, uint8_t opcode) +{ + /* ei is actually "disable interrupts for one instruction, then enable them". */ + if (!gb->ime && !gb->ime_toggle) { + gb->ime_toggle = true; + } +} + +static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t offset; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + offset = (int8_t) cycle_read_inc_oam_bug(gb, gb->pc++); + cycle_no_access(gb); + gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset; + + if ((gb->registers[GB_REGISTER_SP] & 0xF) + (offset & 0xF) > 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[GB_REGISTER_SP] & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + gb->registers[GB_REGISTER_SP] = gb->registers[GB_REGISTER_HL]; + cycle_no_access(gb); +} + +static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value << 1) | carry); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (!(value << 1)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + value = get_src_value(gb, opcode); + carry = (value & 0x01) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rl_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + bool bit7; + value = get_src_value(gb, opcode); + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bit7 = (value & 0x80) != 0; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value << 1) | carry; + set_src_value(gb, opcode, value); + if (bit7) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rr_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + bool bit1; + + value = get_src_value(gb, opcode); + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bit1 = (value & 0x1) != 0; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (bit1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void sla_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + bool carry; + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value << 1)); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if ((value & 0x7F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void sra_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t bit7; + uint8_t value; + value = get_src_value(gb, opcode); + bit7 = value & 0x80; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + if (value & 1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + value = (value >> 1) | bit7; + set_src_value(gb, opcode, value); + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void srl_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + value = get_src_value(gb, opcode); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value >> 1)); + if (value & 1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (!(value >> 1)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void swap_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + value = get_src_value(gb, opcode); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value >> 4) | (value << 4)); + if (!value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void bit_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + uint8_t bit; + value = get_src_value(gb, opcode); + bit = 1 << ((opcode >> 3) & 7); + if ((opcode & 0xC0) == 0x40) { /* Bit */ + gb->registers[GB_REGISTER_AF] &= 0xFF00 | GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + if (!(bit & value)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + } + else if ((opcode & 0xC0) == 0x80) { /* res */ + set_src_value(gb, opcode, value & ~bit); + } + else if ((opcode & 0xC0) == 0xC0) { /* set */ + set_src_value(gb, opcode, value | bit); + } +} + +static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) +{ + opcode = cycle_read_inc_oam_bug(gb, gb->pc++); + switch (opcode >> 3) { + case 0: + rlc_r(gb, opcode); + break; + case 1: + rrc_r(gb, opcode); + break; + case 2: + rl_r(gb, opcode); + break; + case 3: + rr_r(gb, opcode); + break; + case 4: + sla_r(gb, opcode); + break; + case 5: + sra_r(gb, opcode); + break; + case 6: + swap_r(gb, opcode); + break; + case 7: + srl_r(gb, opcode); + break; + default: + bit_r(gb, opcode); + break; + } +} + +static GB_opcode_t *opcodes[256] = { + /* X0 X1 X2 X3 X4 X5 X6 X7 */ + /* X8 X9 Xa Xb Xc Xd Xe Xf */ + nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */ + ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca, + stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */ + jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra, + jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */ + jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl, + jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */ + jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf, + ld_b_b, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */ + ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a, + ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */ + ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a, + ld_h_b, ld_h_c, ld_h_d, ld_h_e, nop, ld_h_l, ld_h_dhl, ld_h_a, /* 6X */ + ld_l_b, ld_l_c, ld_l_d, ld_l_e, ld_l_h, nop, ld_l_dhl, ld_l_a, + ld_dhl_b, ld_dhl_c, ld_dhl_d, ld_dhl_e, ld_dhl_h, ld_dhl_l, halt, ld_dhl_a, /* 7X */ + ld_a_b, ld_a_c, ld_a_d, ld_a_e, ld_a_h, ld_a_l, ld_a_dhl, nop, + add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */ + adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, + sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */ + sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, + and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */ + xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, + or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */ + cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, + ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */ + ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst, + ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */ + ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst, + ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */ + add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst, + ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */ + ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst, +}; +void GB_cpu_run(GB_gameboy_t *gb) +{ + if (gb->hdma_on) { + GB_advance_cycles(gb, 4); + return; + } + if (gb->stopped) { + GB_timing_sync(gb); + GB_advance_cycles(gb, 4); + if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { + leave_stop_mode(gb); + GB_advance_cycles(gb, 8); + } + return; + } + + if ((gb->interrupt_enable & 0x10) && (gb->ime || gb->halted)) { + GB_timing_sync(gb); + } + + if (gb->halted && !GB_is_cgb(gb) && !gb->just_halted) { + GB_advance_cycles(gb, 2); + } + + uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; + + if (gb->halted) { + GB_advance_cycles(gb, (GB_is_cgb(gb) || gb->just_halted) ? 4 : 2); + } + gb->just_halted = false; + + bool effective_ime = gb->ime; + if (gb->ime_toggle) { + gb->ime = !gb->ime; + gb->ime_toggle = false; + } + + /* Wake up from HALT mode without calling interrupt code. */ + if (gb->halted && !effective_ime && interrupt_queue) { + gb->halted = false; + } + + /* Call interrupt */ + else if (effective_ime && interrupt_queue) { + gb->halted = false; + uint16_t call_addr = gb->pc; + + cycle_no_access(gb); + cycle_no_access(gb); + GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ + cycle_no_access(gb); + + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); + interrupt_queue = gb->interrupt_enable; + + if (gb->registers[GB_REGISTER_SP] == GB_IO_IF + 0xFF00 + 1) { + gb->registers[GB_REGISTER_SP]--; + interrupt_queue &= cycle_write_if(gb, (gb->pc) & 0xFF); + } + else { + cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F; + } + + if (interrupt_queue) { + uint8_t interrupt_bit = 0; + while (!(interrupt_queue & 1)) { + interrupt_queue >>= 1; + interrupt_bit++; + } + gb->io_registers[GB_IO_IF] &= ~(1 << interrupt_bit); + gb->pc = interrupt_bit * 8 + 0x40; + } + else { + gb->pc = 0; + } + gb->ime = false; + GB_debugger_call_hook(gb, call_addr); + } + /* Run mode */ + else if (!gb->halted) { + gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); + if (gb->halt_bug) { + gb->pc--; + gb->halt_bug = false; + } + opcodes[gb->last_opcode_read](gb, gb->last_opcode_read); + } + + flush_pending_cycles(gb); + + if (gb->hdma_starting) { + gb->hdma_starting = false; + gb->hdma_on = true; + gb->hdma_cycles = -8; + } +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/sm83_cpu.h b/waterbox/bsnescore/bsnes/gb/Core/sm83_cpu.h new file mode 100644 index 0000000000..49fa80b5a9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/sm83_cpu.h @@ -0,0 +1,11 @@ +#ifndef sm83_cpu_h +#define sm83_cpu_h +#include "gb_struct_def.h" +#include + +void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count); +#ifdef GB_INTERNAL +void GB_cpu_run(GB_gameboy_t *gb); +#endif + +#endif /* sm83_cpu_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/sm83_disassembler.c b/waterbox/bsnescore/bsnes/gb/Core/sm83_disassembler.c new file mode 100644 index 0000000000..7dacd9ebce --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/sm83_disassembler.c @@ -0,0 +1,789 @@ +#include +#include +#include "gb.h" + + +typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc); + +static void ill(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, ".BYTE $%02x\n", opcode); + (*pc)++; +} + +static void nop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "NOP\n"); + (*pc)++; +} + +static void stop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint8_t next = GB_read_memory(gb, (*pc)++); + if (next) { + GB_log(gb, "CORRUPTED STOP (%02x)\n", next); + } + else { + GB_log(gb, "STOP\n"); + } +} + +static char *register_names[] = {"af", "bc", "de", "hl", "sp"}; + +static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + uint16_t value; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + value = GB_read_memory(gb, (*pc)++); + value |= GB_read_memory(gb, (*pc)++) << 8; + const char *symbol = GB_debugger_name_for_address(gb, value); + if (symbol) { + GB_log(gb, "LD %s, %s ; =$%04x\n", register_names[register_id], symbol, value); + } + else { + GB_log(gb, "LD %s, $%04x\n", register_names[register_id], value); + } +} + +static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + GB_log(gb, "LD [%s], a\n", register_names[register_id]); +} + +static void inc_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + GB_log(gb, "INC %s\n", register_names[register_id]); +} + +static void inc_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + (*pc)++; + register_id = ((opcode >> 4) + 1) & 0x03; + GB_log(gb, "INC %c\n", register_names[register_id][0]); + +} +static void dec_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + (*pc)++; + register_id = ((opcode >> 4) + 1) & 0x03; + GB_log(gb, "DEC %c\n", register_names[register_id][0]); +} + +static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + (*pc)++; + register_id = ((opcode >> 4) + 1) & 0x03; + GB_log(gb, "LD %c, $%02x\n", register_names[register_id][0], GB_read_memory(gb, (*pc)++)); +} + +static void rlca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RLCA\n"); +} + +static void rla(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RLA\n"); +} + +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint16_t addr; + (*pc)++; + addr = GB_read_memory(gb, (*pc)++); + addr |= GB_read_memory(gb, (*pc)++) << 8; + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "LD [%s], sp ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "LD [$%04x], sp\n", addr); + } +} + +static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + (*pc)++; + register_id = (opcode >> 4) + 1; + GB_log(gb, "ADD hl, %s\n", register_names[register_id]); +} + +static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + GB_log(gb, "LD a, [%s]\n", register_names[register_id]); +} + +static void dec_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + GB_log(gb, "DEC %s\n", register_names[register_id]); +} + +static void inc_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + + GB_log(gb, "INC %c\n", register_names[register_id][1]); +} +static void dec_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + + GB_log(gb, "DEC %c\n", register_names[register_id][1]); +} + +static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1; + + GB_log(gb, "LD %c, $%02x\n", register_names[register_id][1], GB_read_memory(gb, (*pc)++)); +} + +static void rrca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "RRCA\n"); + (*pc)++; +} + +static void rra(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "RRA\n"); + (*pc)++; +} + +static void jr_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1; + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR %s ; =$%04x\n", symbol, addr); + } + else { + GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR $%04x\n", addr); + } + (*pc)++; +} + +static const char *condition_code(uint8_t opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return "nz"; + case 1: + return "z"; + case 2: + return "nc"; + case 3: + return "c"; + } + + return NULL; +} + +static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1; + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr); + } + else { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, $%04x\n", condition_code(opcode), addr); + } + (*pc)++; +} + +static void daa(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "DAA\n"); + (*pc)++; +} + +static void cpl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "CPL\n"); + (*pc)++; +} + +static void scf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "SCF\n"); + (*pc)++; +} + +static void ccf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "CCF\n"); + (*pc)++; +} + +static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "LD [hli], a\n"); + (*pc)++; +} + +static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "LD [hld], a\n"); + (*pc)++; +} + +static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "LD a, [hli]\n"); + (*pc)++; +} + +static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "LD a, [hld]\n"); + (*pc)++; +} + +static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "INC [hl]\n"); + (*pc)++; +} + +static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + GB_log(gb, "DEC [hl]\n"); + (*pc)++; +} + +static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LD [hl], $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static const char *get_src_name(uint8_t opcode) +{ + uint8_t src_register_id; + uint8_t src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = (opcode & 1); + if (src_register_id == GB_REGISTER_AF) { + return src_low? "a": "[hl]"; + } + if (src_low) { + return register_names[src_register_id] + 1; + } + static const char *high_register_names[] = {"a", "b", "d", "h"}; + return high_register_names[src_register_id]; +} + +static const char *get_dst_name(uint8_t opcode) +{ + uint8_t dst_register_id; + uint8_t dst_low; + dst_register_id = ((opcode >> 4) + 1) & 3; + dst_low = opcode & 8; + if (dst_register_id == GB_REGISTER_AF) { + return dst_low? "a": "[hl]"; + } + if (dst_low) { + return register_names[dst_register_id] + 1; + } + static const char *high_register_names[] = {"a", "b", "d", "h"}; + return high_register_names[dst_register_id]; +} + +static void ld_r_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LD %s, %s\n", get_dst_name(opcode), get_src_name(opcode)); +} + +static void add_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "ADD %s\n", get_src_name(opcode)); +} + +static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "ADC %s\n", get_src_name(opcode)); +} + +static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SUB %s\n", get_src_name(opcode)); +} + +static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SBC %s\n", get_src_name(opcode)); +} + +static void and_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "AND %s\n", get_src_name(opcode)); +} + +static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "XOR %s\n", get_src_name(opcode)); +} + +static void or_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "OR %s\n", get_src_name(opcode)); +} + +static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "CP %s\n", get_src_name(opcode)); +} + +static void halt(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "HALT\n"); +} + +static void ret_cc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "RET %s\n", condition_code(opcode)); +} + +static void pop_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = ((GB_read_memory(gb, (*pc)++) >> 4) + 1) & 3; + GB_log(gb, "POP %s\n", register_names[register_id]); +} + +static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr); + } + else { + GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, $%04x\n", condition_code(opcode), addr); + } + (*pc) += 2; +} + +static void jp_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "JP %s ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "JP $%04x\n", addr); + } + (*pc) += 2; +} + +static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "CALL %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr); + } + else { + GB_log(gb, "CALL %s, $%04x\n", condition_code(opcode), addr); + } + (*pc) += 2; +} + +static void push_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t register_id; + register_id = ((GB_read_memory(gb, (*pc)++) >> 4) + 1) & 3; + GB_log(gb, "PUSH %s\n", register_names[register_id]); +} + +static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "ADD $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "ADC $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SUB $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SBC $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "AND $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "XOR $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "OR $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "CP $%02x\n", GB_read_memory(gb, (*pc)++)); +} + +static void rst(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RST $%02x\n", opcode ^ 0xC7); + +} + +static void ret(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_attributed_log(gb, GB_LOG_UNDERLINE, "RET\n"); +} + +static void reti(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_attributed_log(gb, GB_LOG_UNDERLINE, "RETI\n"); +} + +static void call_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "CALL %s ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "CALL $%04x\n", addr); + } + (*pc) += 2; +} + +static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint8_t addr = GB_read_memory(gb, (*pc)++); + const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr); + if (symbol) { + GB_log(gb, "LDH [%s & $FF], a ; =$%02x\n", symbol, addr); + } + else { + GB_log(gb, "LDH [$%02x], a\n", addr); + } +} + +static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint8_t addr = GB_read_memory(gb, (*pc)++); + const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr); + if (symbol) { + GB_log(gb, "LDH a, [%s & $FF] ; =$%02x\n", symbol, addr); + } + else { + GB_log(gb, "LDH a, [$%02x]\n", addr); + } +} + +static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LDH [c], a\n"); +} + +static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LDH a, [c]\n"); +} + +static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + int8_t temp = GB_read_memory(gb, (*pc)++); + GB_log(gb, "ADD SP, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); +} + +static void jp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "JP hl\n"); +} + +static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "LD [%s], a ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "LD [$%04x], a\n", addr); + } + (*pc) += 2; +} + +static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8); + const char *symbol = GB_debugger_name_for_address(gb, addr); + if (symbol) { + GB_log(gb, "LD a, [%s] ; =$%04x\n", symbol, addr); + } + else { + GB_log(gb, "LD a, [$%04x]\n", addr); + } + (*pc) += 2; +} + +static void di(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "DI\n"); +} + +static void ei(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "EI\n"); +} + +static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + int8_t temp = GB_read_memory(gb, (*pc)++); + GB_log(gb, "LD hl, sp, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp); +} + +static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "LD sp, hl\n"); +} + +static void rlc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RLC %s\n", get_src_name(opcode)); +} + +static void rrc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RRC %s\n", get_src_name(opcode)); +} + +static void rl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RL %s\n", get_src_name(opcode)); +} + +static void rr_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "RR %s\n", get_src_name(opcode)); +} + +static void sla_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SLA %s\n", get_src_name(opcode)); +} + +static void sra_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SRA %s\n", get_src_name(opcode)); +} + +static void srl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SRL %s\n", get_src_name(opcode)); +} + +static void swap_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + (*pc)++; + GB_log(gb, "SWAP %s\n", get_src_name(opcode)); +} + +static void bit_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + uint8_t bit; + (*pc)++; + bit = ((opcode >> 3) & 7); + if ((opcode & 0xC0) == 0x40) { /* Bit */ + GB_log(gb, "BIT %s, %d\n", get_src_name(opcode), bit); + } + else if ((opcode & 0xC0) == 0x80) { /* res */ + GB_log(gb, "RES %s, %d\n", get_src_name(opcode), bit); + } + else if ((opcode & 0xC0) == 0xC0) { /* set */ + GB_log(gb, "SET %s, %d\n", get_src_name(opcode), bit); + } +} + +static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) +{ + opcode = GB_read_memory(gb, ++*pc); + switch (opcode >> 3) { + case 0: + rlc_r(gb, opcode, pc); + break; + case 1: + rrc_r(gb, opcode, pc); + break; + case 2: + rl_r(gb, opcode, pc); + break; + case 3: + rr_r(gb, opcode, pc); + break; + case 4: + sla_r(gb, opcode, pc); + break; + case 5: + sra_r(gb, opcode, pc); + break; + case 6: + swap_r(gb, opcode, pc); + break; + case 7: + srl_r(gb, opcode, pc); + break; + default: + bit_r(gb, opcode, pc); + break; + } +} + +static GB_opcode_t *opcodes[256] = { + /* X0 X1 X2 X3 X4 X5 X6 X7 */ + /* X8 X9 Xa Xb Xc Xd Xe Xf */ + nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */ + ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca, + stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */ + jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra, + jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */ + jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl, + jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */ + jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 4X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 5X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 6X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, halt, ld_r_r, /* 7X */ + ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, + add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */ + adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, + sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */ + sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, + and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */ + xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, + or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */ + cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, + ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */ + ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst, + ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */ + ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst, + ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */ + add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst, + ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */ + ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst, +}; + + + +void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count) +{ + const GB_bank_symbol_t *function_symbol = GB_debugger_find_symbol(gb, pc); + + if (function_symbol && pc - function_symbol->addr > 0x1000) { + function_symbol = NULL; + } + + if (function_symbol && pc != function_symbol->addr) { + GB_log(gb, "%s:\n", function_symbol->name); + } + + uint16_t current_function = function_symbol? function_symbol->addr : 0; + + while (count--) { + function_symbol = GB_debugger_find_symbol(gb, pc); + if (function_symbol && function_symbol->addr == pc) { + if (current_function != function_symbol->addr) { + GB_log(gb, "\n"); + } + GB_log(gb, "%s:\n", function_symbol->name); + } + if (function_symbol) { + GB_log(gb, "%s%04x <+%03x>: ", pc == gb->pc? " ->": " ", pc, pc - function_symbol->addr); + } + else { + GB_log(gb, "%s%04x: ", pc == gb->pc? " ->": " ", pc); + } + uint8_t opcode = GB_read_memory(gb, pc); + opcodes[opcode](gb, opcode, &pc); + } +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/symbol_hash.c b/waterbox/bsnescore/bsnes/gb/Core/symbol_hash.c new file mode 100644 index 0000000000..75a7837dd1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/symbol_hash.c @@ -0,0 +1,108 @@ +#include "gb.h" +#include +#include +#include +#include + +static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr) +{ + if (!map->symbols) { + return 0; + } + ssize_t min = 0; + ssize_t max = map->n_symbols; + while (min < max) { + size_t pivot = (min + max) / 2; + if (map->symbols[pivot].addr == addr) return pivot; + if (map->symbols[pivot].addr > addr) { + max = pivot; + } + else { + min = pivot + 1; + } + } + return (size_t) min; +} + +GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name) +{ + size_t index = GB_map_find_symbol_index(map, addr); + + map->symbols = realloc(map->symbols, (map->n_symbols + 1) * sizeof(map->symbols[0])); + memmove(&map->symbols[index + 1], &map->symbols[index], (map->n_symbols - index) * sizeof(map->symbols[0])); + map->symbols[index].addr = addr; + map->symbols[index].name = strdup(name); + map->n_symbols++; + return &map->symbols[index]; +} + +const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr) +{ + if (!map) return NULL; + size_t index = GB_map_find_symbol_index(map, addr); + if (index < map->n_symbols && map->symbols[index].addr != addr) { + index--; + } + if (index < map->n_symbols) { + return &map->symbols[index]; + } + return NULL; +} + +GB_symbol_map_t *GB_map_alloc(void) +{ + GB_symbol_map_t *map = malloc(sizeof(*map)); + memset(map, 0, sizeof(*map)); + return map; +} + +void GB_map_free(GB_symbol_map_t *map) +{ + for (unsigned i = 0; i < map->n_symbols; i++) { + free(map->symbols[i].name); + } + + if (map->symbols) { + free(map->symbols); + } + + free(map); +} + +static unsigned hash_name(const char *name) +{ + unsigned r = 0; + while (*name) { + r <<= 1; + if (r & 0x400) { + r ^= 0x401; + } + r += (unsigned char)*(name++); + } + + return r & 0x3FF; +} + +void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *bank_symbol) +{ + unsigned hash = hash_name(bank_symbol->name); + GB_symbol_t *symbol = malloc(sizeof(*symbol)); + symbol->name = bank_symbol->name; + symbol->addr = bank_symbol->addr; + symbol->bank = bank; + symbol->next = map->buckets[hash]; + map->buckets[hash] = symbol; +} + +const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name) +{ + unsigned hash = hash_name(name); + GB_symbol_t *symbol = map->buckets[hash]; + + while (symbol) { + if (strcmp(symbol->name, name) == 0) return symbol; + symbol = symbol->next; + } + + return NULL; +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/symbol_hash.h b/waterbox/bsnescore/bsnes/gb/Core/symbol_hash.h new file mode 100644 index 0000000000..2a03c96bbb --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/symbol_hash.h @@ -0,0 +1,38 @@ +#ifndef symbol_hash_h +#define symbol_hash_h + +#include +#include +#include + +typedef struct { + char *name; + uint16_t addr; +} GB_bank_symbol_t; + +typedef struct GB_symbol_s { + struct GB_symbol_s *next; + const char *name; + uint16_t bank; + uint16_t addr; +} GB_symbol_t; + +typedef struct { + GB_bank_symbol_t *symbols; + size_t n_symbols; +} GB_symbol_map_t; + +typedef struct { + GB_symbol_t *buckets[0x400]; +} GB_reversed_symbol_map_t; + +#ifdef GB_INTERNAL +void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *symbol); +const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name); +GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name); +const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr); +GB_symbol_map_t *GB_map_alloc(void); +void GB_map_free(GB_symbol_map_t *map); +#endif + +#endif /* symbol_hash_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/timing.c b/waterbox/bsnescore/bsnes/gb/Core/timing.c new file mode 100644 index 0000000000..965ba27c98 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/timing.c @@ -0,0 +1,327 @@ +#include "gb.h" +#ifdef _WIN32 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif +#include +#else +#include +#endif + +static const unsigned GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; + +#ifndef GB_DISABLE_TIMEKEEPING +static int64_t get_nanoseconds(void) +{ +#ifndef _WIN32 + struct timeval now; + gettimeofday(&now, NULL); + return (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; +#else + FILETIME time; + GetSystemTimeAsFileTime(&time); + return (((int64_t)time.dwHighDateTime << 32) | time.dwLowDateTime) * 100L; +#endif +} + +static void nsleep(uint64_t nanoseconds) +{ +#ifndef _WIN32 + struct timespec sleep = {0, nanoseconds}; + nanosleep(&sleep, NULL); +#else + HANDLE timer; + LARGE_INTEGER time; + timer = CreateWaitableTimer(NULL, true, NULL); + time.QuadPart = -(nanoseconds / 100L); + SetWaitableTimer(timer, &time, 0, NULL, NULL, false); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +#endif +} + +bool GB_timing_sync_turbo(GB_gameboy_t *gb) +{ + if (!gb->turbo_dont_skip) { + int64_t nanoseconds = get_nanoseconds(); + if (nanoseconds <= gb->last_sync + (1000000000LL * LCDC_PERIOD / GB_get_clock_rate(gb))) { + return true; + } + gb->last_sync = nanoseconds; + } + return false; +} + +void GB_timing_sync(GB_gameboy_t *gb) +{ + if (gb->turbo) { + gb->cycles_since_last_sync = 0; + return; + } + /* Prevent syncing if not enough time has passed.*/ + if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return; + + uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ + int64_t nanoseconds = get_nanoseconds(); + int64_t time_to_sleep = target_nanoseconds + gb->last_sync - nanoseconds; + if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1000000000LL / GB_get_clock_rate(gb)) { + nsleep(time_to_sleep); + gb->last_sync += target_nanoseconds; + } + else { + gb->last_sync = nanoseconds; + } + + gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); + } +} +#else + +bool GB_timing_sync_turbo(GB_gameboy_t *gb) +{ + return false; +} + +void GB_timing_sync(GB_gameboy_t *gb) +{ +} + +#endif +static void GB_ir_run(GB_gameboy_t *gb) +{ + if (gb->ir_queue_length == 0) return; + if (gb->cycles_since_input_ir_change >= gb->ir_queue[0].delay) { + gb->cycles_since_input_ir_change -= gb->ir_queue[0].delay; + gb->infrared_input = gb->ir_queue[0].state; + gb->ir_queue_length--; + memmove(&gb->ir_queue[0], &gb->ir_queue[1], sizeof(gb->ir_queue[0]) * (gb->ir_queue_length)); + } +} + +static void advance_tima_state_machine(GB_gameboy_t *gb) +{ + if (gb->tima_reload_state == GB_TIMA_RELOADED) { + gb->tima_reload_state = GB_TIMA_RUNNING; + } + else if (gb->tima_reload_state == GB_TIMA_RELOADING) { + gb->io_registers[GB_IO_IF] |= 4; + gb->tima_reload_state = GB_TIMA_RELOADED; + } +} + +static void increase_tima(GB_gameboy_t *gb) +{ + gb->io_registers[GB_IO_TIMA]++; + if (gb->io_registers[GB_IO_TIMA] == 0) { + gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA]; + gb->tima_reload_state = GB_TIMA_RELOADING; + } +} + +static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) +{ + /* TIMA increases when a specific high-bit becomes a low-bit. */ + value &= INTERNAL_DIV_CYCLES - 1; + uint32_t triggers = gb->div_counter & ~value; + if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { + increase_tima(gb); + } + + /* TODO: Can switching to double speed mode trigger an event? */ + if (triggers & (gb->cgb_double_speed? 0x2000 : 0x1000)) { + GB_apu_run(gb); + GB_apu_div_event(gb); + } + gb->div_counter = value; +} + +static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) +{ + if (gb->stopped) { + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + return; + } + + GB_STATE_MACHINE(gb, div, cycles, 1) { + GB_STATE(gb, div, 1); + GB_STATE(gb, div, 2); + GB_STATE(gb, div, 3); + } + + GB_set_internal_div_counter(gb, 0); +main: + GB_SLEEP(gb, div, 1, 3); + while (true) { + advance_tima_state_machine(gb); + GB_set_internal_div_counter(gb, gb->div_counter + 4); + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + GB_SLEEP(gb, div, 2, 4); + } + + /* Todo: This is ugly to allow compatibility with 0.11 save states. Fix me when breaking save compatibility */ + { + div3: + /* Compensate for lack of prefetch emulation, as well as DIV's internal initial value */ + GB_set_internal_div_counter(gb, 8); + goto main; + } +} + +static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) +{ + if (gb->serial_length == 0) { + gb->serial_cycles += cycles; + return; + } + + while (cycles > gb->serial_length) { + advance_serial(gb, gb->serial_length); + cycles -= gb->serial_length; + } + + uint16_t previous_serial_cycles = gb->serial_cycles; + gb->serial_cycles += cycles; + if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) { + gb->serial_count++; + if (gb->serial_count == 8) { + gb->serial_length = 0; + gb->serial_count = 0; + gb->io_registers[GB_IO_SC] &= ~0x80; + gb->io_registers[GB_IO_IF] |= 8; + } + + gb->io_registers[GB_IO_SB] <<= 1; + + if (gb->serial_transfer_bit_end_callback) { + gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb); + } + else { + gb->io_registers[GB_IO_SB] |= 1; + } + + if (gb->serial_length) { + /* Still more bits to send */ + if (gb->serial_transfer_bit_start_callback) { + gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); + } + } + + } + return; + +} + +void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) +{ + gb->apu.pcm_mask[0] = gb->apu.pcm_mask[1] = 0xFF; // Sort of hacky, but too many cross-component interactions to do it right + // Affected by speed boost + gb->dma_cycles += cycles; + + GB_timers_run(gb, cycles); + if (!gb->stopped) { + advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode + } + + gb->debugger_ticks += cycles; + + if (!gb->cgb_double_speed) { + cycles <<= 1; + } + + // Not affected by speed boost + gb->double_speed_alignment += cycles; + gb->hdma_cycles += cycles; + gb->apu_output.sample_cycles += cycles; + gb->cycles_since_ir_change += cycles; + gb->cycles_since_input_ir_change += cycles; + gb->cycles_since_last_sync += cycles; + gb->cycles_since_run += cycles; + + if (gb->rumble_state) { + gb->rumble_on_cycles++; + } + else { + gb->rumble_off_cycles++; + } + + if (!gb->stopped) { // TODO: Verify what happens in STOP mode + GB_dma_run(gb); + GB_hdma_run(gb); + } + GB_apu_run(gb); + GB_display_run(gb, cycles); + GB_ir_run(gb); +} + +/* + This glitch is based on the expected results of mooneye-gb rapid_toggle test. + This glitch happens because how TIMA is increased, see GB_set_internal_div_counter. + According to GiiBiiAdvance, GBC's behavior is different, but this was not tested or implemented. +*/ +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) +{ + /* Glitch only happens when old_tac is enabled. */ + if (!(old_tac & 4)) return; + + unsigned old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3]; + unsigned new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3]; + + /* The bit used for overflow testing must have been 1 */ + if (gb->div_counter & old_clocks) { + /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ + if (!(new_tac & 4) || gb->div_counter & new_clocks) { + increase_tima(gb); + } + } +} + +void GB_rtc_run(GB_gameboy_t *gb) +{ + if (gb->cartridge_type->mbc_type == GB_HUC3) { + time_t current_time = time(NULL); + while (gb->last_rtc_second / 60 < current_time / 60) { + gb->last_rtc_second += 60; + gb->huc3_minutes++; + if (gb->huc3_minutes == 60 * 24) { + gb->huc3_days++; + gb->huc3_minutes = 0; + } + } + return; + } + + if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ + time_t current_time = time(NULL); + + while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { + gb->last_rtc_second += 60 * 60 * 24; + if (++gb->rtc_real.days == 0) { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + gb->rtc_real.high ^= 1; + } + } + + while (gb->last_rtc_second < current_time) { + gb->last_rtc_second++; + if (++gb->rtc_real.seconds == 60) { + gb->rtc_real.seconds = 0; + if (++gb->rtc_real.minutes == 60) { + gb->rtc_real.minutes = 0; + if (++gb->rtc_real.hours == 24) { + gb->rtc_real.hours = 0; + if (++gb->rtc_real.days == 0) { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + gb->rtc_real.high ^= 1; + } + } + } + } + } + } +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/timing.h b/waterbox/bsnescore/bsnes/gb/Core/timing.h new file mode 100644 index 0000000000..d4fa07f946 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/timing.h @@ -0,0 +1,41 @@ +#ifndef timing_h +#define timing_h +#include "gb_struct_def.h" + +#ifdef GB_INTERNAL +void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); +void GB_rtc_run(GB_gameboy_t *gb); +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); +bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ +void GB_timing_sync(GB_gameboy_t *gb); + +enum { + GB_TIMA_RUNNING = 0, + GB_TIMA_RELOADING = 1, + GB_TIMA_RELOADED = 2 +}; + + +#define GB_SLEEP(gb, unit, state, cycles) do {\ + (gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \ + if ((gb)->unit##_cycles <= 0) {\ + (gb)->unit##_state = state;\ + return;\ + unit##state:; \ + }\ +} while (0) + +#define GB_STATE_MACHINE(gb, unit, cycles, divisor) \ +static const int __state_machine_divisor = divisor;\ +(gb)->unit##_cycles += cycles; \ +if ((gb)->unit##_cycles <= 0) {\ + return;\ +}\ +switch ((gb)->unit##_state) +#endif + +#define GB_STATE(gb, unit, state) case state: goto unit##state + +#define GB_UNIT(unit) int32_t unit##_cycles, unit##_state + +#endif /* timing_h */ diff --git a/waterbox/bsnescore/bsnes/gb/Core/workboy.c b/waterbox/bsnescore/bsnes/gb/Core/workboy.c new file mode 100644 index 0000000000..3b103796f7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/workboy.c @@ -0,0 +1,169 @@ +#include "gb.h" +#include + +static inline uint8_t int_to_bcd(uint8_t i) +{ + return (i % 10) + ((i / 10) << 4); +} + +static inline uint8_t bcd_to_int(uint8_t i) +{ + return (i & 0xF) + (i >> 4) * 10; +} + +/* + Note: This peripheral was never released. This is a hacky software reimplementation of it that allows + reaccessing all of the features present in Workboy's ROM. Some of the implementation details are + obviously wrong, but without access to the actual hardware, this is the best I can do. +*/ + +static void serial_start(GB_gameboy_t *gb, bool bit_received) +{ + gb->workboy.byte_being_received <<= 1; + gb->workboy.byte_being_received |= bit_received; + gb->workboy.bits_received++; + if (gb->workboy.bits_received == 8) { + gb->workboy.byte_to_send = 0; + if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'R') { + gb->workboy.byte_to_send = 'D'; + gb->workboy.key = GB_WORKBOY_NONE; + gb->workboy.mode = gb->workboy.byte_being_received; + gb->workboy.buffer_index = 1; + + time_t time = gb->workboy_get_time_callback(gb); + struct tm tm; + tm = *localtime(&time); + memset(gb->workboy.buffer, 0, sizeof(gb->workboy.buffer)); + + gb->workboy.buffer[0] = 4; // Unknown, unused, but appears to be expected to be 4 + gb->workboy.buffer[2] = int_to_bcd(tm.tm_sec); // Seconds, BCD + gb->workboy.buffer[3] = int_to_bcd(tm.tm_min); // Minutes, BCD + gb->workboy.buffer[4] = int_to_bcd(tm.tm_hour); // Hours, BCD + gb->workboy.buffer[5] = int_to_bcd(tm.tm_mday); // Days, BCD. Upper most 2 bits are added to Year for some reason + gb->workboy.buffer[6] = int_to_bcd(tm.tm_mon + 1); // Months, BCD + gb->workboy.buffer[0xF] = tm.tm_year; // Years, plain number, since 1900 + + } + else if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'W') { + gb->workboy.byte_to_send = 'D'; // It is actually unknown what this value should be + gb->workboy.key = GB_WORKBOY_NONE; + gb->workboy.mode = gb->workboy.byte_being_received; + gb->workboy.buffer_index = 0; + } + else if (gb->workboy.mode != 'W' && (gb->workboy.byte_being_received == 'O' || gb->workboy.mode == 'O')) { + gb->workboy.mode = 'O'; + gb->workboy.byte_to_send = gb->workboy.key; + if (gb->workboy.key != GB_WORKBOY_NONE) { + if (gb->workboy.key & GB_WORKBOY_REQUIRE_SHIFT) { + gb->workboy.key &= ~GB_WORKBOY_REQUIRE_SHIFT; + if (gb->workboy.shift_down) { + gb->workboy.byte_to_send = gb->workboy.key; + gb->workboy.key = GB_WORKBOY_NONE; + } + else { + gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_DOWN; + gb->workboy.shift_down = true; + } + } + else if (gb->workboy.key & GB_WORKBOY_FORBID_SHIFT) { + gb->workboy.key &= ~GB_WORKBOY_FORBID_SHIFT; + if (!gb->workboy.shift_down) { + gb->workboy.byte_to_send = gb->workboy.key; + gb->workboy.key = GB_WORKBOY_NONE; + } + else { + gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_UP; + gb->workboy.shift_down = false; + } + } + else { + if (gb->workboy.key == GB_WORKBOY_SHIFT_DOWN) { + gb->workboy.shift_down = true; + gb->workboy.user_shift_down = true; + } + else if (gb->workboy.key == GB_WORKBOY_SHIFT_UP) { + gb->workboy.shift_down = false; + gb->workboy.user_shift_down = false; + } + gb->workboy.byte_to_send = gb->workboy.key; + gb->workboy.key = GB_WORKBOY_NONE; + } + } + } + else if (gb->workboy.mode == 'R') { + if (gb->workboy.buffer_index / 2 >= sizeof(gb->workboy.buffer)) { + gb->workboy.byte_to_send = 0; + } + else { + if (gb->workboy.buffer_index & 1) { + gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] & 0xF]; + } + else { + gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] >> 4]; + } + gb->workboy.buffer_index++; + } + } + else if (gb->workboy.mode == 'W') { + gb->workboy.byte_to_send = 'D'; + if (gb->workboy.buffer_index < 2) { + gb->workboy.buffer_index++; + } + else if ((gb->workboy.buffer_index - 2) < sizeof(gb->workboy.buffer)) { + gb->workboy.buffer[gb->workboy.buffer_index - 2] = gb->workboy.byte_being_received; + gb->workboy.buffer_index++; + if (gb->workboy.buffer_index - 2 == sizeof(gb->workboy.buffer)) { + struct tm tm = {0,}; + tm.tm_sec = bcd_to_int(gb->workboy.buffer[7]); + tm.tm_min = bcd_to_int(gb->workboy.buffer[8]); + tm.tm_hour = bcd_to_int(gb->workboy.buffer[9]); + tm.tm_mday = bcd_to_int(gb->workboy.buffer[0xA]); + tm.tm_mon = bcd_to_int(gb->workboy.buffer[0xB] & 0x3F) - 1; + tm.tm_year = (uint8_t)(gb->workboy.buffer[0x14] + (gb->workboy.buffer[0xA] >> 6)); // What were they thinking? + gb->workboy_set_time_callback(gb, mktime(&tm)); + gb->workboy.mode = 'O'; + } + } + } + gb->workboy.bits_received = 0; + gb->workboy.byte_being_received = 0; + } +} + +static bool serial_end(GB_gameboy_t *gb) +{ + bool ret = gb->workboy.bit_to_send; + gb->workboy.bit_to_send = gb->workboy.byte_to_send & 0x80; + gb->workboy.byte_to_send <<= 1; + return ret; +} + +void GB_connect_workboy(GB_gameboy_t *gb, + GB_workboy_set_time_callback set_time_callback, + GB_workboy_get_time_callback get_time_callback) +{ + memset(&gb->workboy, 0, sizeof(gb->workboy)); + GB_set_serial_transfer_bit_start_callback(gb, serial_start); + GB_set_serial_transfer_bit_end_callback(gb, serial_end); + gb->workboy_set_time_callback = set_time_callback; + gb->workboy_get_time_callback = get_time_callback; +} + +bool GB_workboy_is_enabled(GB_gameboy_t *gb) +{ + return gb->workboy.mode; +} + +void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key) +{ + if (gb->workboy.user_shift_down != gb->workboy.shift_down && + (key & (GB_WORKBOY_REQUIRE_SHIFT | GB_WORKBOY_FORBID_SHIFT)) == 0) { + if (gb->workboy.user_shift_down) { + key |= GB_WORKBOY_REQUIRE_SHIFT; + } + else { + key |= GB_WORKBOY_FORBID_SHIFT; + } + } + gb->workboy.key = key; +} diff --git a/waterbox/bsnescore/bsnes/gb/Core/workboy.h b/waterbox/bsnescore/bsnes/gb/Core/workboy.h new file mode 100644 index 0000000000..d21f273169 --- /dev/null +++ b/waterbox/bsnescore/bsnes/gb/Core/workboy.h @@ -0,0 +1,118 @@ +#ifndef workboy_h +#define workboy_h +#include +#include +#include +#include "gb_struct_def.h" + + +typedef struct { + uint8_t byte_to_send; + bool bit_to_send; + uint8_t byte_being_received; + uint8_t bits_received; + uint8_t mode; + uint8_t key; + bool shift_down; + bool user_shift_down; + uint8_t buffer[0x15]; + uint8_t buffer_index; // In nibbles during read, in bytes during write +} GB_workboy_t; + +typedef void (*GB_workboy_set_time_callback)(GB_gameboy_t *gb, time_t time); +typedef time_t (*GB_workboy_get_time_callback)(GB_gameboy_t *gb); + +enum { + GB_WORKBOY_NONE = 0xFF, + GB_WORKBOY_REQUIRE_SHIFT = 0x40, + GB_WORKBOY_FORBID_SHIFT = 0x80, + + GB_WORKBOY_CLOCK = 1, + GB_WORKBOY_TEMPERATURE = 2, + GB_WORKBOY_MONEY = 3, + GB_WORKBOY_CALCULATOR = 4, + GB_WORKBOY_DATE = 5, + GB_WORKBOY_CONVERSION = 6, + GB_WORKBOY_RECORD = 7, + GB_WORKBOY_WORLD = 8, + GB_WORKBOY_PHONE = 9, + GB_WORKBOY_ESCAPE = 10, + GB_WORKBOY_BACKSPACE = 11, + GB_WORKBOY_UNKNOWN = 12, + GB_WORKBOY_LEFT = 13, + GB_WORKBOY_Q = 17, + GB_WORKBOY_1 = 17 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_W = 18, + GB_WORKBOY_2 = 18 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_E = 19, + GB_WORKBOY_3 = 19 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_R = 20, + GB_WORKBOY_T = 21, + GB_WORKBOY_Y = 22 , + GB_WORKBOY_U = 23 , + GB_WORKBOY_I = 24, + GB_WORKBOY_EXCLAMATION_MARK = 24 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_O = 25, + GB_WORKBOY_TILDE = 25 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_P = 26, + GB_WORKBOY_ASTERISK = 26 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_DOLLAR = 27 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_HASH = 27 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_A = 28, + GB_WORKBOY_4 = 28 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_S = 29, + GB_WORKBOY_5 = 29 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_D = 30, + GB_WORKBOY_6 = 30 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_F = 31, + GB_WORKBOY_PLUS = 31 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_G = 32, + GB_WORKBOY_MINUS = 32 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_H = 33, + GB_WORKBOY_J = 34, + GB_WORKBOY_K = 35, + GB_WORKBOY_LEFT_PARENTHESIS = 35 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_L = 36, + GB_WORKBOY_RIGHT_PARENTHESIS = 36 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_SEMICOLON = 37 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_COLON = 37, + GB_WORKBOY_ENTER = 38, + GB_WORKBOY_SHIFT_DOWN = 39, + GB_WORKBOY_Z = 40, + GB_WORKBOY_7 = 40 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_X = 41, + GB_WORKBOY_8 = 41 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_C = 42, + GB_WORKBOY_9 = 42 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_V = 43, + GB_WORKBOY_DECIMAL_POINT = 43 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_B = 44, + GB_WORKBOY_PERCENT = 44 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_N = 45, + GB_WORKBOY_EQUAL = 45 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_M = 46, + GB_WORKBOY_COMMA = 47 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_LT = 47 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_DOT = 48 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_GT = 48 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_SLASH = 49 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_QUESTION_MARK = 49 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_SHIFT_UP = 50, + GB_WORKBOY_0 = 51 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_UMLAUT = 51, + GB_WORKBOY_SPACE = 52, + GB_WORKBOY_QUOTE = 53 | GB_WORKBOY_FORBID_SHIFT, + GB_WORKBOY_AT = 53 | GB_WORKBOY_REQUIRE_SHIFT, + GB_WORKBOY_UP = 54, + GB_WORKBOY_DOWN = 55, + GB_WORKBOY_RIGHT = 56, +}; + + +void GB_connect_workboy(GB_gameboy_t *gb, + GB_workboy_set_time_callback set_time_callback, + GB_workboy_get_time_callback get_time_callback); +bool GB_workboy_is_enabled(GB_gameboy_t *gb); +void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key); + +#endif diff --git a/waterbox/bsnescore/bsnes/heuristics/bs-memory.cpp b/waterbox/bsnescore/bsnes/heuristics/bs-memory.cpp new file mode 100644 index 0000000000..ffb255c5e4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/heuristics/bs-memory.cpp @@ -0,0 +1,33 @@ +namespace Heuristics { + +struct BSMemory { + BSMemory(vector& data, string location); + explicit operator bool() const; + auto manifest() const -> string; + +private: + vector& data; + string location; +}; + +BSMemory::BSMemory(vector& data, string location) : data(data), location(location) { +} + +BSMemory::operator bool() const { + return data.size() >= 0x8000; +} + +auto BSMemory::manifest() const -> string { + if(!operator bool()) return {}; + + string output; + output.append("game\n"); + output.append(" sha256: ", Hash::SHA256(data).digest(), "\n"); + output.append(" label: ", Location::prefix(location), "\n"); + output.append(" name: ", Location::prefix(location), "\n"); + output.append(" board\n"); + output.append(Memory{}.type("Flash").size(data.size()).content("Program").text()); + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/heuristics/game-boy.cpp b/waterbox/bsnescore/bsnes/heuristics/game-boy.cpp new file mode 100644 index 0000000000..411a1cca3d --- /dev/null +++ b/waterbox/bsnescore/bsnes/heuristics/game-boy.cpp @@ -0,0 +1,298 @@ +namespace Heuristics { + +struct GameBoy { + GameBoy(vector& data, string location); + explicit operator bool() const; + auto manifest() const -> string; + +private: + auto read(uint offset) const -> uint8_t { return data[headerAddress + offset]; } + + vector& data; + string location; + uint headerAddress = 0; +}; + +GameBoy::GameBoy(vector& data, string location) : data(data), location(location) { + headerAddress = data.size() < 0x8000 ? data.size() : data.size() - 0x8000; + if(read(0x0104) == 0xce && read(0x0105) == 0xed && read(0x0106) == 0x66 && read(0x0107) == 0x66 + && read(0x0108) == 0xcc && read(0x0109) == 0x0d && read(0x0147) >= 0x0b && read(0x0147) <= 0x0d + ) { //MMM01 stores header at bottom of data[] + } else { //all other mappers store header at top of data[] + headerAddress = 0; + } +} + +GameBoy::operator bool() const { + return data.size() >= 0x4000; +} + +auto GameBoy::manifest() const -> string { + if(!operator bool()) return {}; + + bool black = (read(0x0143) & 0xc0) == 0x80; //cartridge works in DMG+CGB mode + bool clear = (read(0x0143) & 0xc0) == 0xc0; //cartridge works in CGB mode only + + bool ram = false; + bool battery = false; + bool eeprom = false; + bool flash = false; + bool rtc = false; + bool accelerometer = false; + bool rumble = false; + + uint romSize = 0; + uint ramSize = 0; + uint eepromSize = 0; + uint flashSize = 0; + uint rtcSize = 0; + + string mapper = "MBC0"; + + switch(read(0x0147)) { + + case 0x00: + mapper = "MBC0"; + break; + + case 0x01: + mapper = "MBC1"; + break; + + case 0x02: + mapper = "MBC1"; + ram = true; + break; + + case 0x03: + mapper = "MBC1"; + battery = true; + ram = true; + break; + + case 0x05: + mapper = "MBC2"; + ram = true; + break; + + case 0x06: + mapper = "MBC2"; + battery = true; + ram = true; + break; + + case 0x08: + mapper = "MBC0"; + ram = true; + break; + + case 0x09: + mapper = "MBC0"; + battery = true; + ram = true; + break; + + case 0x0b: + mapper = "MMM01"; + break; + + case 0x0c: + mapper = "MMM01"; + ram = true; + break; + + case 0x0d: + mapper = "MMM01"; + battery = true; + ram = true; + break; + + case 0x0f: + mapper = "MBC3"; + battery = true; + rtc = true; + break; + + case 0x10: + mapper = "MBC3"; + battery = true; + ram = true; + rtc = true; + break; + + case 0x11: + mapper = "MBC3"; + break; + + case 0x12: + mapper = "MBC3"; + ram = true; + break; + + case 0x13: + mapper = "MBC3"; + battery = true; + ram = true; + break; + + case 0x19: + mapper = "MBC5"; + break; + + case 0x1a: + mapper = "MBC5"; + ram = true; + break; + + case 0x1b: + mapper = "MBC5"; + battery = true; + ram = true; + break; + + case 0x1c: + mapper = "MBC5"; + rumble = true; + break; + + case 0x1d: + mapper = "MBC5"; + ram = true; + rumble = true; + break; + + case 0x1e: + mapper = "MBC5"; + battery = true; + ram = true; + rumble = true; + break; + + case 0x20: + mapper = "MBC6"; + flash = true; + battery = true; + ram = true; + break; + + case 0x22: + mapper = "MBC7"; + battery = true; + eeprom = true; + accelerometer = true; + rumble = true; + break; + + case 0xfc: + mapper = "CAMERA"; + break; + + case 0xfd: + mapper = "TAMA"; + battery = true; + ram = true; + rtc = true; + break; + + case 0xfe: + mapper = "HuC3"; + break; + + case 0xff: + mapper = "HuC1"; + battery = true; + ram = true; + break; + + } + + //Game Boy: title = $0134-0143 + //Game Boy Color (early games): title = $0134-0142; model = $0143 + //Game Boy Color (later games): title = $0134-013e; serial = $013f-0142; model = $0143 + string title; + for(uint n : range(black || clear ? 15 : 16)) { + char byte = read(0x0134 + n); + if(byte < 0x20 || byte > 0x7e) byte = ' '; + title.append(byte); + } + + string serial = title.slice(-4); + if(!black && !clear) serial = ""; + for(auto& byte : serial) { + if(byte >= 'A' && byte <= 'Z') continue; + //invalid serial + serial = ""; + break; + } + title.trimRight(serial, 1L); //remove the serial from the title, if it exists + title.strip(); //remove any excess whitespace from the title + + switch(read(0x0148)) { default: + case 0x00: romSize = 2 * 16 * 1024; break; + case 0x01: romSize = 4 * 16 * 1024; break; + case 0x02: romSize = 8 * 16 * 1024; break; + case 0x03: romSize = 16 * 16 * 1024; break; + case 0x04: romSize = 32 * 16 * 1024; break; + case 0x05: romSize = 64 * 16 * 1024; break; + case 0x06: romSize = 128 * 16 * 1024; break; + case 0x07: romSize = 256 * 16 * 1024; break; + case 0x52: romSize = 72 * 16 * 1024; break; + case 0x53: romSize = 80 * 16 * 1024; break; + case 0x54: romSize = 96 * 16 * 1024; break; + } + + switch(read(0x0149)) { default: + case 0x00: ramSize = 0 * 1024; break; + case 0x01: ramSize = 2 * 1024; break; + case 0x02: ramSize = 8 * 1024; break; + case 0x03: ramSize = 32 * 1024; break; + } + + if(mapper == "MBC2" && ram) ramSize = 256; + if(mapper == "MBC6" && ram) ramSize = 32 * 1024; + if(mapper == "TAMA" && ram) ramSize = 32; + + if(mapper == "MBC6" && flash) flashSize = 1024 * 1024; + + //Game Boy header does not specify EEPROM size: detect via game title instead + //Command Master: EEPROM = 512 bytes + //Kirby Tilt 'n' Tumble: EEPROM = 256 bytes + //Korokoro Kirby: EEPROM = 256 bytes + if(mapper == "MBC7" && eeprom) { + eepromSize = 256; //fallback guess; supported values are 128, 256, 512 + if(title == "CMASTER" && serial == "KCEJ") eepromSize = 512; + if(title == "KIRBY TNT" && serial == "KTNE") eepromSize = 256; + if(title == "KORO2 KIRBY" && serial == "KKKJ") eepromSize = 256; + } + + if(mapper == "MBC3" && rtc) rtcSize = 13; + if(mapper == "TAMA" && rtc) rtcSize = 21; + + string output; + output.append("game\n"); + output.append(" sha256: ", Hash::SHA256(data).digest(), "\n"); + output.append(" label: ", Location::prefix(location), "\n"); + output.append(" name: ", Location::prefix(location), "\n"); + output.append(" title: ", title, "\n"); +if(serial) + output.append(" serial: ", serial, "\n"); + output.append(" board: ", mapper, "\n"); + output.append(Memory{}.type("ROM").size(data.size()).content("Program").text()); +if(ram && ramSize && battery) + output.append(Memory{}.type("RAM").size(ramSize).content("Save").text()); +if(ram && ramSize && !battery) + output.append(Memory{}.type("RAM").size(ramSize).content("Save").isVolatile().text()); +if(eeprom && eepromSize) + output.append(Memory{}.type("EEPROM").size(eepromSize).content("Save").text()); +if(flash && flashSize) + output.append(Memory{}.type("Flash").size(flashSize).content("Download").text()); +if(rtc && rtcSize) + output.append(Memory{}.type("RTC").size(rtcSize).content("Time").text()); +if(accelerometer) + output.append(" accelerometer\n"); +if(rumble) + output.append(" rumble\n"); + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/heuristics/heuristics.cpp b/waterbox/bsnescore/bsnes/heuristics/heuristics.cpp new file mode 100644 index 0000000000..c564ac9567 --- /dev/null +++ b/waterbox/bsnescore/bsnes/heuristics/heuristics.cpp @@ -0,0 +1,34 @@ +namespace Heuristics { + +auto Memory::text() const -> string { + string output; + output.append(" memory\n"); + output.append(" type: ", _type, "\n"); + output.append(" size: 0x", hex(_size), "\n"); + output.append(" content: ", _content, "\n"); +if(_manufacturer) + output.append(" manufacturer: ", _manufacturer, "\n"); +if(_architecture) + output.append(" architecture: ", _architecture, "\n"); +if(_identifier) + output.append(" identifier: ", _identifier, "\n"); +if(_volatile) + output.append(" volatile\n"); + return output; +} + +auto Oscillator::text() const -> string { + string output; + output.append(" oscillator\n"); + output.append(" frequency: ", _frequency, "\n"); + return output; +} + +auto Slot::text() const -> string { + string output; + output.append(" slot\n"); + output.append(" type: ", _type, "\n"); + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/heuristics/heuristics.hpp b/waterbox/bsnescore/bsnes/heuristics/heuristics.hpp new file mode 100644 index 0000000000..a65dafa083 --- /dev/null +++ b/waterbox/bsnescore/bsnes/heuristics/heuristics.hpp @@ -0,0 +1,37 @@ +namespace Heuristics { + +struct Memory { + auto& type(string type) { _type = type; return *this; } + auto& size(natural size) { _size = size; return *this; } + auto& content(string content) { _content = content; return *this; } + auto& manufacturer(string manufacturer) { _manufacturer = manufacturer; return *this; } + auto& architecture(string architecture) { _architecture = architecture; return *this; } + auto& identifier(string identifier) { _identifier = identifier; return *this; } + auto& isVolatile() { _volatile = true; return *this; } + auto text() const -> string; + + string _type; + boolean _battery; + natural _size; + string _content; + string _manufacturer; + string _architecture; + string _identifier; + boolean _volatile; +}; + +struct Oscillator { + auto& frequency(natural frequency) { _frequency = frequency; return *this; } + auto text() const -> string; + + natural _frequency; +}; + +struct Slot { + auto& type(string type) { _type = type; return *this; } + auto text() const -> string; + + string _type; +}; + +} diff --git a/waterbox/bsnescore/bsnes/heuristics/super-famicom.cpp b/waterbox/bsnescore/bsnes/heuristics/super-famicom.cpp new file mode 100644 index 0000000000..674196e3eb --- /dev/null +++ b/waterbox/bsnescore/bsnes/heuristics/super-famicom.cpp @@ -0,0 +1,605 @@ +namespace Heuristics { + +struct SuperFamicom { + SuperFamicom(vector& data, string location); + explicit operator bool() const; + + auto manifest() const -> string; + auto region() const -> string; + auto videoRegion() const -> string; + auto revision() const -> string; + auto board() const -> string; + auto title() const -> string; + auto serial() const -> string; + auto romSize() const -> uint; + auto programRomSize() const -> uint; + auto dataRomSize() const -> uint; + auto expansionRomSize() const -> uint; + auto firmwareRomSize() const -> uint; + auto ramSize() const -> uint; + auto expansionRamSize() const -> uint; + auto nonVolatile() const -> bool; + +private: + auto size() const -> uint { return data.size(); } + auto scoreHeader(uint address) -> uint; + auto firmwareARM() const -> string; + auto firmwareEXNEC() const -> string; + auto firmwareGB() const -> string; + auto firmwareHITACHI() const -> string; + auto firmwareNEC() const -> string; + + vector& data; + string location; + uint headerAddress = 0; +}; + +SuperFamicom::SuperFamicom(vector& data, string location) : data(data), location(location) { + if((size() & 0x7fff) == 512) { + //remove header if present + memory::move(&data[0], &data[512], size() - 512); + data.resize(size() - 512); + } + + if(size() < 0x8000) return; //ignore images too small to be valid + + uint LoROM = scoreHeader( 0x7fb0); + uint HiROM = scoreHeader( 0xffb0); + uint ExLoROM = scoreHeader(0x407fb0); + uint ExHiROM = scoreHeader(0x40ffb0); + if(ExLoROM) ExLoROM += 4; + if(ExHiROM) ExHiROM += 4; + + if(LoROM >= HiROM && LoROM >= ExLoROM && LoROM >= ExHiROM) headerAddress = 0x7fb0; + else if(HiROM >= ExLoROM && HiROM >= ExHiROM) headerAddress = 0xffb0; + else if(ExLoROM >= ExHiROM) headerAddress = 0x407fb0; + else headerAddress = 0x40ffb0; +} + +SuperFamicom::operator bool() const { + return headerAddress; +} + +auto SuperFamicom::manifest() const -> string { + if(!operator bool()) return {}; + + string output; + output.append("game\n"); + output.append(" sha256: ", Hash::SHA256(data).digest(), "\n"); + output.append(" label: ", Location::prefix(location), "\n"); + output.append(" name: ", Location::prefix(location), "\n"); + output.append(" title: ", title(), "\n"); + output.append(" region: ", region(), "\n"); + output.append(" revision: ", revision(), "\n"); + output.append(" board: ", board(), "\n"); + + auto board = this->board().trimRight("#A", 1L).split("-"); + + if(auto size = romSize()) { + if(board(0) == "SPC7110" && size > 0x100000) { + output.append(Memory{}.type("ROM").size(0x100000).content("Program").text()); + output.append(Memory{}.type("ROM").size(size - 0x100000).content("Data").text()); + } else if(board(0) == "EXSPC7110" && size == 0x700000) { + //Tengai Maykou Zero (fan translation) + output.append(Memory{}.type("ROM").size(0x100000).content("Program").text()); + output.append(Memory{}.type("ROM").size(0x500000).content("Data").text()); + output.append(Memory{}.type("ROM").size(0x100000).content("Expansion").text()); + } else { + output.append(Memory{}.type("ROM").size(size).content("Program").text()); + } + } + + if(auto size = ramSize()) { + output.append(Memory{}.type("RAM").size(size).content("Save").text()); + } + + if(auto size = expansionRamSize()) { + output.append(Memory{}.type("RAM").size(size).content("Save").text()); + } + + if(0) { + } else if(board(0) == "ARM") { + output.append(Memory{}.type("ROM").size(0x20000).content("Program").manufacturer("SETA").architecture("ARM6").identifier(firmwareARM()).text()); + output.append(Memory{}.type("ROM").size( 0x8000).content("Data" ).manufacturer("SETA").architecture("ARM6").identifier(firmwareARM()).text()); + output.append(Memory{}.type("RAM").size( 0x4000).content("Data" ).manufacturer("SETA").architecture("ARM6").identifier(firmwareARM()).isVolatile().text()); + output.append(Oscillator{}.frequency(21'440'000).text()); + } else if(board(0) == "BS" && board(1) == "MCC") { + output.append(Memory{}.type("RAM").size(0x80000).content("Download").text()); + } else if(board(0) == "EXNEC") { + output.append(Memory{}.type("ROM").size(0xc000).content("Program").manufacturer("NEC").architecture("uPD96050").identifier(firmwareEXNEC()).text()); + output.append(Memory{}.type("ROM").size(0x1000).content("Data" ).manufacturer("NEC").architecture("uPD96050").identifier(firmwareEXNEC()).text()); + output.append(Memory{}.type("RAM").size(0x1000).content("Data" ).manufacturer("NEC").architecture("uPD96050").identifier(firmwareEXNEC()).text()); + output.append(Oscillator{}.frequency(firmwareEXNEC() == "ST010" ? 11'000'000 : 15'000'000).text()); + } else if(board(0) == "GB") { + output.append(Memory{}.type("ROM").size(0x100).content("Boot").manufacturer("Nintendo").architecture("LR35902").identifier(firmwareGB()).text()); + if(firmwareGB() == "SGB2") + output.append(Oscillator{}.frequency(20'971'520).text()); + } else if(board(0) == "GSU") { + //todo: MARIO CHIP 1 uses CPU oscillator + output.append(Oscillator{}.frequency(21'440'000).text()); + } else if(board(0) == "HITACHI") { + output.append(Memory{}.type("ROM").size(0xc00).content("Data").manufacturer("Hitachi").architecture("HG51BS169").identifier(firmwareHITACHI()).text()); + output.append(Memory{}.type("RAM").size(0xc00).content("Data").manufacturer("Hitachi").architecture("HG51BS169").identifier(firmwareHITACHI()).isVolatile().text()); + output.append(Oscillator{}.frequency(20'000'000).text()); + } else if(board(0) == "NEC") { + output.append(Memory{}.type("ROM").size(0x1800).content("Program").manufacturer("NEC").architecture("uPD7725").identifier(firmwareNEC()).text()); + output.append(Memory{}.type("ROM").size( 0x800).content("Data" ).manufacturer("NEC").architecture("uPD7725").identifier(firmwareNEC()).text()); + output.append(Memory{}.type("RAM").size( 0x200).content("Data" ).manufacturer("NEC").architecture("uPD7725").identifier(firmwareNEC()).isVolatile().text()); + output.append(Oscillator{}.frequency(7'600'000).text()); + } else if(board(0) == "SA1" || board(1) == "SA1") { //SA1-* or BS-SA1-* + output.append(Memory{}.type("RAM").size(0x800).content("Internal").isVolatile().text()); + } + + if(board.right() == "EPSONRTC") { + output.append(Memory{}.type("RTC").size(0x10).content("Time").manufacturer("Epson").text()); + } else if(board.right() == "SHARPRTC") { + output.append(Memory{}.type("RTC").size(0x10).content("Time").manufacturer("Sharp").text()); + } + + return output; +} + +auto SuperFamicom::region() const -> string { + //Unlicensed software (homebrew, ROM hacks, etc) often change the standard region code, + //and then neglect to change the extended header region code. Thanks to that, we can't + //decode and display the full game serial + region code. + return videoRegion(); + + string region; + + char A = data[headerAddress + 0x02]; //game type + char B = data[headerAddress + 0x03]; //game code + char C = data[headerAddress + 0x04]; //game code + char D = data[headerAddress + 0x05]; //region code (new; sometimes ambiguous) + auto E = data[headerAddress + 0x29]; //region code (old) + + auto valid = [](char n) { return (n >= '0' && n <= '9') || (n >= 'A' && n <= 'Z'); }; + if(data[headerAddress + 0x2a] == 0x33 && valid(A) && valid(B) & valid(C) & valid(D)) { + string code{A, B, C, D}; + if(D == 'B') region = {"SNS-", code, "-BRA"}; + if(D == 'C') region = {"SNSN-", code, "-ROC"}; + if(D == 'D') region = {"SNSP-", code, "-NOE"}; + if(D == 'E') region = {"SNS-", code, "-USA"}; + if(D == 'F') region = {"SNSP-", code, "-FRA"}; + if(D == 'H') region = {"SNSP-", code, "-HOL"}; + if(D == 'I') region = {"SNSP-", code, "-ITA"}; + if(D == 'J') region = {"SHVC-", code, "-JPN"}; + if(D == 'K') region = {"SNSN-", code, "-KOR"}; + if(D == 'N') region = {"SNS-", code, "-CAN"}; + if(D == 'P') region = {"SNSP-", code, "-EUR"}; + if(D == 'S') region = {"SNSP-", code, "-ESP"}; + if(D == 'U') region = {"SNSP-", code, "-AUS"}; + if(D == 'X') region = {"SNSP-", code, "-SCN"}; + } + + if(!region) { + if(E == 0x00) region = {"JPN"}; + if(E == 0x01) region = {"USA"}; + if(E == 0x02) region = {"EUR"}; + if(E == 0x03) region = {"SCN"}; + if(E == 0x06) region = {"FRA"}; + if(E == 0x07) region = {"HOL"}; + if(E == 0x08) region = {"ESP"}; + if(E == 0x09) region = {"NOE"}; + if(E == 0x0a) region = {"ITA"}; + if(E == 0x0b) region = {"ROC"}; + if(E == 0x0d) region = {"KOR"}; + if(E == 0x0f) region = {"CAN"}; + if(E == 0x10) region = {"BRA"}; + if(E == 0x11) region = {"AUS"}; + if(E == 0x12) region = {"SCN"}; + } + + return region ? region : "NTSC"; +} + +auto SuperFamicom::videoRegion() const -> string { + auto region = data[headerAddress + 0x29]; + if(region == 0x00) return "NTSC"; //JPN + if(region == 0x01) return "NTSC"; //USA + if(region == 0x0b) return "NTSC"; //ROC + if(region == 0x0d) return "NTSC"; //KOR + if(region == 0x0f) return "NTSC"; //CAN + if(region == 0x10) return "NTSC"; //BRA + return "PAL"; +} + +auto SuperFamicom::revision() const -> string { + string revision; + + char A = data[headerAddress + 0x02]; //game type + char B = data[headerAddress + 0x03]; //game code + char C = data[headerAddress + 0x04]; //game code + char D = data[headerAddress + 0x05]; //region code (new; sometimes ambiguous) + auto E = data[headerAddress + 0x29]; //region code (old) + uint F = data[headerAddress + 0x2b]; //revision code + + auto valid = [](char n) { return (n >= '0' && n <= '9') || (n >= 'A' && n <= 'Z'); }; + if(data[headerAddress + 0x2a] == 0x33 && valid(A) && valid(B) & valid(C) & valid(D)) { + string code{A, B, C, D}; + if(D == 'B') revision = {"SNS-", code, "-", F}; + if(D == 'C') revision = {"SNSN-", code, "-", F}; + if(D == 'D') revision = {"SNSP-", code, "-", F}; + if(D == 'E') revision = {"SNS-", code, "-", F}; + if(D == 'F') revision = {"SNSP-", code, "-", F}; + if(D == 'H') revision = {"SNSP-", code, "-", F}; + if(D == 'I') revision = {"SNSP-", code, "-", F}; + if(D == 'J') revision = {"SHVC-", code, "-", F}; + if(D == 'K') revision = {"SNSN-", code, "-", F}; + if(D == 'N') revision = {"SNS-", code, "-", F}; + if(D == 'P') revision = {"SNSP-", code, "-", F}; + if(D == 'S') revision = {"SNSP-", code, "-", F}; + if(D == 'U') revision = {"SNSP-", code, "-", F}; + if(D == 'X') revision = {"SNSP-", code, "-", F}; + } + + if(!revision) { + revision = {"1.", F}; + } + + return revision ? revision : string{"1.", F}; +} + +//format: [slot]-[coprocessor]-[mapper]-[ram]-[rtc] +auto SuperFamicom::board() const -> string { + string board; + + auto mapMode = data[headerAddress + 0x25]; + auto cartridgeTypeLo = data[headerAddress + 0x26] & 15; + auto cartridgeTypeHi = data[headerAddress + 0x26] >> 4; + auto cartridgeSubType = data[headerAddress + 0x0f]; + + string mode; + if(mapMode == 0x20 || mapMode == 0x30) mode = "LOROM-"; + if(mapMode == 0x21 || mapMode == 0x31) mode = "HIROM-"; + if(mapMode == 0x22 || mapMode == 0x32) mode = "SDD1-"; + if(mapMode == 0x23 || mapMode == 0x33) mode = "SA1-"; + if(mapMode == 0x25 || mapMode == 0x35) mode = "EXHIROM-"; + if(mapMode == 0x2a || mapMode == 0x3a) mode = "SPC7110-"; + + //many games will store an extra title character, overwriting the map mode + //further, ExLoROM mode is unofficial, and lacks a mapping mode value + if(!mode) { + if(headerAddress == 0x7fb0) mode = "LOROM-"; + if(headerAddress == 0xffb0) mode = "HIROM-"; + if(headerAddress == 0x407fb0) mode = "EXLOROM-"; + if(headerAddress == 0x40ffb0) mode = "EXHIROM-"; + } + + //this game's title ovewrites the map mode with '!' (0x21), but is a LOROM game + if(title() == "YUYU NO QUIZ DE GO!GO") mode = "LOROM-"; + + if(mode == "LOROM-" && headerAddress == 0x407fb0) mode = "EXLOROM-"; + + bool epsonRTC = false; + bool sharpRTC = false; + + if(serial() == "A9PJ") { + //Sufami Turbo (JPN) + board.append("ST-", mode); + } else if(serial() == "ZBSJ") { + //BS-X: Sore wa Namae o Nusumareta Machi no Monogatari (JPN) + board.append("BS-MCC-"); + } else if(serial() == "042J") { + //Super Game Boy 2 + board.append("GB-", mode); + } else if(serial().match("Z??J")) { + board.append("BS-", mode); + } else if(cartridgeTypeLo >= 0x3) { + if(cartridgeTypeHi == 0x0) board.append("NEC-", mode); + if(cartridgeTypeHi == 0x1) board.append("GSU-"); + if(cartridgeTypeHi == 0x2) board.append("OBC1-", mode); + if(cartridgeTypeHi == 0x3) board.append("SA1-"); + if(cartridgeTypeHi == 0x4) board.append("SDD1-"); + if(cartridgeTypeHi == 0x5) board.append(mode), sharpRTC = true; + if(cartridgeTypeHi == 0xe && cartridgeTypeLo == 0x3) board.append("GB-", mode); + if(cartridgeTypeHi == 0xf && cartridgeTypeLo == 0x5 && cartridgeSubType == 0x00) board.append("SPC7110-"); + if(cartridgeTypeHi == 0xf && cartridgeTypeLo == 0x9 && cartridgeSubType == 0x00) board.append("SPC7110-"), epsonRTC = true; + if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x01) board.append("EXNEC-", mode); + if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x02) board.append("ARM-", mode); + if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x10) board.append("HITACHI-", mode); + } + if(!board) board.append(mode); + + if(ramSize() || expansionRamSize()) board.append("RAM-"); + if(epsonRTC) board.append("EPSONRTC-"); + if(sharpRTC) board.append("SHARPRTC-"); + + board.trimRight("-", 1L); + + if(board.beginsWith( "LOROM-RAM") && romSize() <= 0x200000) board.append("#A"); + if(board.beginsWith("NEC-LOROM-RAM") && romSize() <= 0x100000) board.append("#A"); + + //Tengai Makyou Zero (fan translation) + if(board.beginsWith("SPC7110-") && data.size() == 0x700000) board.prepend("EX"); + + return board; +} + +auto SuperFamicom::title() const -> string { + string label; + + for(uint n = 0; n < 0x15; n++) { + auto x = data[headerAddress + 0x10 + n]; + auto y = n == 0x14 ? 0 : data[headerAddress + 0x11 + n]; + + //null terminator (padding) + if(x == 0x00 || x == 0xff); + + //ASCII + else if(x >= 0x20 && x <= 0x7e) label.append((char)x); + + //Shift-JIS (half-width katakana) + else if(x == 0xa1) label.append("。"); + else if(x == 0xa2) label.append("「"); + else if(x == 0xa3) label.append("」"); + else if(x == 0xa4) label.append("、"); + else if(x == 0xa5) label.append("・"); + else if(x == 0xa6) label.append("ヲ"); + else if(x == 0xa7) label.append("ァ"); + else if(x == 0xa8) label.append("ィ"); + else if(x == 0xa9) label.append("ゥ"); + else if(x == 0xaa) label.append("ェ"); + else if(x == 0xab) label.append("ォ"); + else if(x == 0xac) label.append("ャ"); + else if(x == 0xad) label.append("ュ"); + else if(x == 0xae) label.append("ョ"); + else if(x == 0xaf) label.append("ッ"); + else if(x == 0xb0) label.append("ー"); + + else if(x == 0xb1) label.append( "ア"); + else if(x == 0xb2) label.append( "イ"); + else if(x == 0xb3) label.append(y == 0xde ? "ヴ" : "ウ"); + else if(x == 0xb4) label.append( "エ"); + else if(x == 0xb5) label.append( "オ"); + + else if(x == 0xb6) label.append(y == 0xde ? "ガ" : "カ"); + else if(x == 0xb7) label.append(y == 0xde ? "ギ" : "キ"); + else if(x == 0xb8) label.append(y == 0xde ? "グ" : "ク"); + else if(x == 0xb9) label.append(y == 0xde ? "ゲ" : "ケ"); + else if(x == 0xba) label.append(y == 0xde ? "ゴ" : "コ"); + + else if(x == 0xbb) label.append(y == 0xde ? "ザ" : "サ"); + else if(x == 0xbc) label.append(y == 0xde ? "ジ" : "シ"); + else if(x == 0xbd) label.append(y == 0xde ? "ズ" : "ス"); + else if(x == 0xbe) label.append(y == 0xde ? "ゼ" : "セ"); + else if(x == 0xbf) label.append(y == 0xde ? "ゾ" : "ソ"); + + else if(x == 0xc0) label.append(y == 0xde ? "ダ" : "タ"); + else if(x == 0xc1) label.append(y == 0xde ? "ヂ" : "チ"); + else if(x == 0xc2) label.append(y == 0xde ? "ヅ" : "ツ"); + else if(x == 0xc3) label.append(y == 0xde ? "デ" : "テ"); + else if(x == 0xc4) label.append(y == 0xde ? "ド" : "ト"); + + else if(x == 0xc5) label.append("ナ"); + else if(x == 0xc6) label.append("ニ"); + else if(x == 0xc7) label.append("ヌ"); + else if(x == 0xc8) label.append("ネ"); + else if(x == 0xc9) label.append("ノ"); + + else if(x == 0xca) label.append(y == 0xdf ? "パ" : y == 0xde ? "バ" : "ハ"); + else if(x == 0xcb) label.append(y == 0xdf ? "ピ" : y == 0xde ? "ビ" : "ヒ"); + else if(x == 0xcc) label.append(y == 0xdf ? "プ" : y == 0xde ? "ブ" : "フ"); + else if(x == 0xcd) label.append(y == 0xdf ? "ペ" : y == 0xde ? "ベ" : "ヘ"); + else if(x == 0xce) label.append(y == 0xdf ? "ポ" : y == 0xde ? "ボ" : "ホ"); + + else if(x == 0xcf) label.append("マ"); + else if(x == 0xd0) label.append("ミ"); + else if(x == 0xd1) label.append("ム"); + else if(x == 0xd2) label.append("メ"); + else if(x == 0xd3) label.append("モ"); + + else if(x == 0xd4) label.append("ヤ"); + else if(x == 0xd5) label.append("ユ"); + else if(x == 0xd6) label.append("ヨ"); + + else if(x == 0xd7) label.append("ラ"); + else if(x == 0xd8) label.append("リ"); + else if(x == 0xd9) label.append("ル"); + else if(x == 0xda) label.append("レ"); + else if(x == 0xdb) label.append("ロ"); + + else if(x == 0xdc) label.append("ワ"); + else if(x == 0xdd) label.append("ン"); + + else if(x == 0xde) label.append("\xef\xbe\x9e"); //dakuten + else if(x == 0xdf) label.append("\xef\xbe\x9f"); //handakuten + + //unknown + else label.append("?"); + + //(han)dakuten skip + if(y == 0xde && x == 0xb3) n++; + if(y == 0xde && x >= 0xb6 && x <= 0xc4) n++; + if(y == 0xde && x >= 0xca && x <= 0xce) n++; + if(y == 0xdf && x >= 0xca && y <= 0xce) n++; + } + + return label.strip(); +} + +auto SuperFamicom::serial() const -> string { + char A = data[headerAddress + 0x02]; //game type + char B = data[headerAddress + 0x03]; //game code + char C = data[headerAddress + 0x04]; //game code + char D = data[headerAddress + 0x05]; //region code (new; sometimes ambiguous) + + auto valid = [](char n) { return (n >= '0' && n <= '9') || (n >= 'A' && n <= 'Z'); }; + if(data[headerAddress + 0x2a] == 0x33 && valid(A) && valid(B) & valid(C) & valid(D)) { + return {A, B, C, D}; + } + + return ""; +} + +auto SuperFamicom::romSize() const -> uint { + return size() - firmwareRomSize(); +} + +auto SuperFamicom::programRomSize() const -> uint { + if(board().beginsWith("SPC7110-")) return 0x100000; + if(board().beginsWith("EXSPC7110-")) return 0x100000; + return romSize(); +} + +auto SuperFamicom::dataRomSize() const -> uint { + if(board().beginsWith("SPC7110-")) return romSize() - 0x100000; + if(board().beginsWith("EXSPC7110-")) return 0x500000; + return 0; +} + +auto SuperFamicom::expansionRomSize() const -> uint { + if(board().beginsWith("EXSPC7110-")) return 0x100000; + return 0; +} + +//detect if any firmware is appended to the ROM image, and return its size if so +auto SuperFamicom::firmwareRomSize() const -> uint { + auto cartridgeTypeLo = data[headerAddress + 0x26] & 15; + auto cartridgeTypeHi = data[headerAddress + 0x26] >> 4; + auto cartridgeSubType = data[headerAddress + 0x0f]; + + if(serial() == "042J" || (cartridgeTypeLo == 0x3 && cartridgeTypeHi == 0xe)) { + //Game Boy + if((size() & 0x7fff) == 0x100) return 0x100; + } + + if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x10) { + //Hitachi HG51BS169 + if((size() & 0x7fff) == 0xc00) return 0xc00; + } + + if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0x0) { + //NEC uPD7725 + if((size() & 0x7fff) == 0x2000) return 0x2000; + } + + if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x01) { + //NEC uPD96050 + if((size() & 0xffff) == 0xd000) return 0xd000; + } + + if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x02) { + //ARM6 + if((size() & 0x3ffff) == 0x28000) return 0x28000; + } + + return 0; +} + +auto SuperFamicom::ramSize() const -> uint { + auto ramSize = data[headerAddress + 0x28] & 15; + if(ramSize > 8) ramSize = 8; + if(ramSize > 0) return 1024 << ramSize; + return 0; +} + +auto SuperFamicom::expansionRamSize() const -> uint { + if(data[headerAddress + 0x2a] == 0x33) { + auto ramSize = data[headerAddress + 0x0d] & 15; + if(ramSize > 8) ramSize = 8; + if(ramSize > 0) return 1024 << ramSize; + } + if((data[headerAddress + 0x26] >> 4) == 1) { + //GSU: Starfox / Starwing lacks an extended header; but still has expansion RAM + return 0x8000; + } + return 0; +} + +auto SuperFamicom::nonVolatile() const -> bool { + auto cartridgeTypeLo = data[headerAddress + 0x26] & 15; + return cartridgeTypeLo == 0x2 || cartridgeTypeLo == 0x5 || cartridgeTypeLo == 0x6; +} + +auto SuperFamicom::scoreHeader(uint address) -> uint { + int score = 0; + if(size() < address + 0x50) return score; + + uint8_t mapMode = data[address + 0x25] & ~0x10; //ignore FastROM bit + uint16_t complement = data[address + 0x2c] << 0 | data[address + 0x2d] << 8; + uint16_t checksum = data[address + 0x2e] << 0 | data[address + 0x2f] << 8; + uint16_t resetVector = data[address + 0x4c] << 0 | data[address + 0x4d] << 8; + if(resetVector < 0x8000) return score; //$00:0000-7fff is never ROM data + + uint8_t opcode = data[(address & ~0x7fff) | (resetVector & 0x7fff)]; //first instruction executed + + //most likely opcodes + if(opcode == 0x78 //sei + || opcode == 0x18 //clc (clc; xce) + || opcode == 0x38 //sec (sec; xce) + || opcode == 0x9c //stz $nnnn (stz $4200) + || opcode == 0x4c //jmp $nnnn + || opcode == 0x5c //jml $nnnnnn + ) score += 8; + + //plausible opcodes + if(opcode == 0xc2 //rep #$nn + || opcode == 0xe2 //sep #$nn + || opcode == 0xad //lda $nnnn + || opcode == 0xae //ldx $nnnn + || opcode == 0xac //ldy $nnnn + || opcode == 0xaf //lda $nnnnnn + || opcode == 0xa9 //lda #$nn + || opcode == 0xa2 //ldx #$nn + || opcode == 0xa0 //ldy #$nn + || opcode == 0x20 //jsr $nnnn + || opcode == 0x22 //jsl $nnnnnn + ) score += 4; + + //implausible opcodes + if(opcode == 0x40 //rti + || opcode == 0x60 //rts + || opcode == 0x6b //rtl + || opcode == 0xcd //cmp $nnnn + || opcode == 0xec //cpx $nnnn + || opcode == 0xcc //cpy $nnnn + ) score -= 4; + + //least likely opcodes + if(opcode == 0x00 //brk #$nn + || opcode == 0x02 //cop #$nn + || opcode == 0xdb //stp + || opcode == 0x42 //wdm + || opcode == 0xff //sbc $nnnnnn,x + ) score -= 8; + + if(checksum + complement == 0xffff) score += 4; + + if(address == 0x7fb0 && mapMode == 0x20) score += 2; + if(address == 0xffb0 && mapMode == 0x21) score += 2; + + return max(0, score); +} + +auto SuperFamicom::firmwareARM() const -> string { + return "ST018"; +} + +auto SuperFamicom::firmwareEXNEC() const -> string { + if(title() == "EXHAUST HEAT2") return "ST010"; + if(title() == "F1 ROC II") return "ST010"; + if(title() == "2DAN MORITA SHOUGI") return "ST011"; + return "ST010"; +} + +auto SuperFamicom::firmwareGB() const -> string { + if(title() == "Super GAMEBOY") return "SGB1"; + if(title() == "Super GAMEBOY2") return "SGB2"; + return "SGB1"; +} + +auto SuperFamicom::firmwareHITACHI() const -> string { + return "Cx4"; +} + +auto SuperFamicom::firmwareNEC() const -> string { + if(title() == "PILOTWINGS") return "DSP1"; + if(title() == "DUNGEON MASTER") return "DSP2"; + if(title() == "SDガンダムGX") return "DSP3"; + if(title() == "PLANETS CHAMP TG3000") return "DSP4"; + if(title() == "TOP GEAR 3000") return "DSP4"; + return "DSP1B"; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/adaptive-array.hpp b/waterbox/bsnescore/bsnes/nall/adaptive-array.hpp new file mode 100644 index 0000000000..9ca84e7c6c --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/adaptive-array.hpp @@ -0,0 +1,64 @@ +//deprecated + +#pragma once + +#include +#include + +namespace nall { + +template +struct adaptive_array { + auto capacity() const -> uint { return Capacity; } + auto size() const -> uint { return _size; } + + auto reset() -> void { + for(uint n : range(_size)) _pool.t[n].~T(); + _size = 0; + } + + auto operator[](uint index) -> T& { + #ifdef DEBUG + struct out_of_bounds {}; + if(index >= Capacity) throw out_of_bounds{}; + #endif + return _pool.t[index]; + } + + auto operator[](uint index) const -> const T& { + #ifdef DEBUG + struct out_of_bounds {}; + if(index >= Capacity) throw out_of_bounds{}; + #endif + return _pool.t[index]; + } + + auto append() -> T& { + new(_pool.t + _size) T; + return _pool.t[_size++]; + } + + auto append(const T& value) -> void { + new(_pool.t + _size++) T(value); + } + + auto append(T&& value) -> void { + new(_pool.t + _size++) T(move(value)); + } + + auto begin() { return &_pool.t[0]; } + auto end() { return &_pool.t[_size]; } + + auto begin() const { return &_pool.t[0]; } + auto end() const { return &_pool.t[_size]; } + +private: + union U { + U() {} + ~U() {} + T t[Capacity]; + } _pool; + uint _size = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/algorithm.hpp b/waterbox/bsnescore/bsnes/nall/algorithm.hpp new file mode 100644 index 0000000000..34565f11e2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/algorithm.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#undef min +#undef max + +namespace nall { + +template constexpr auto min(const T& t, const U& u) -> T { + return t < u ? t : (T)u; +} + +template constexpr auto min(const T& t, const U& u, P&&... p) -> T { + return t < u ? min(t, forward

(p)...) : min(u, forward

(p)...); +} + +template constexpr auto max(const T& t, const U& u) -> T { + return t > u ? t : (T)u; +} + +template constexpr auto max(const T& t, const U& u, P&&... p) -> T { + return t > u ? max(t, forward

(p)...) : max(u, forward

(p)...); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/any.hpp b/waterbox/bsnescore/bsnes/nall/any.hpp new file mode 100644 index 0000000000..d7e789a9fa --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/any.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include + +namespace nall { + +struct any { + any() = default; + any(const any& source) { operator=(source); } + any(any&& source) { operator=(move(source)); } + template any(const T& value) { operator=(value); } + ~any() { reset(); } + + explicit operator bool() const { return container; } + auto reset() -> void { if(container) { delete container; container = nullptr; } } + + auto type() const -> const std::type_info& { + return container ? container->type() : typeid(void); + } + + template auto is() const -> bool { + return type() == typeid(typename remove_reference::type); + } + + template auto get() -> T& { + if(!is()) throw; + return static_cast::type>*>(container)->value; + } + + template auto get() const -> const T& { + if(!is()) throw; + return static_cast::type>*>(container)->value; + } + + template auto get(const T& fallback) const -> const T& { + if(!is()) return fallback; + return static_cast::type>*>(container)->value; + } + + template auto operator=(const T& value) -> any& { + using auto_t = typename conditional::value, typename remove_extent::type>::type*, T>::type; + + if(type() == typeid(auto_t)) { + static_cast*>(container)->value = (auto_t)value; + } else { + if(container) delete container; + container = new holder((auto_t)value); + } + + return *this; + } + + auto operator=(const any& source) -> any& { + if(container) { delete container; container = nullptr; } + if(source.container) container = source.container->copy(); + return *this; + } + + auto operator=(any&& source) -> any& { + if(container) delete container; + container = source.container; + source.container = nullptr; + return *this; + } + +private: + struct placeholder { + virtual ~placeholder() = default; + virtual auto type() const -> const std::type_info& = 0; + virtual auto copy() const -> placeholder* = 0; + }; + placeholder* container = nullptr; + + template struct holder : placeholder { + holder(const T& value) : value(value) {} + auto type() const -> const std::type_info& { return typeid(T); } + auto copy() const -> placeholder* { return new holder(value); } + T value; + }; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/arguments.hpp b/waterbox/bsnescore/bsnes/nall/arguments.hpp new file mode 100644 index 0000000000..6fb706d13a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/arguments.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include + +namespace nall { + +struct Arguments { + Arguments(int argc, char** argv); + Arguments(vector arguments); + + explicit operator bool() const { return (bool)arguments; } + auto size() const -> uint { return arguments.size(); } + + auto operator[](uint index) -> string& { return arguments[index]; } + auto operator[](uint index) const -> const string& { return arguments[index]; } + + auto programPath() const -> string; + auto programName() const -> string; + auto programLocation() const -> string; + + auto find(string_view name) const -> bool; + auto find(string_view name, bool& argument) const -> bool; + auto find(string_view name, string& argument) const -> bool; + + auto begin() const { return arguments.begin(); } + auto end() const { return arguments.end(); } + + auto rbegin() const { return arguments.rbegin(); } + auto rend() const { return arguments.rend(); } + + auto take() -> string; + auto take(string_view name) -> bool; + auto take(string_view name, bool& argument) -> bool; + auto take(string_view name, string& argument) -> bool; + + auto begin() { return arguments.begin(); } + auto end() { return arguments.end(); } + + auto rbegin() { return arguments.rbegin(); } + auto rend() { return arguments.rend(); } + +private: + auto construct() -> void; + + string programArgument; + vector arguments; +}; + +inline auto Arguments::construct() -> void { + if(!arguments) return; + + //extract and pre-process program argument + programArgument = arguments.takeFirst(); + programArgument = {Path::real(programArgument), Location::file(programArgument)}; + + //normalize path and file arguments + for(auto& argument : arguments) { + if(directory::exists(argument)) argument.transform("\\", "/").trimRight("/").append("/"); + else if(file::exists(argument)) argument.transform("\\", "/").trimRight("/"); + } +} + +inline Arguments::Arguments(int argc, char** argv) { + #if defined(PLATFORM_WINDOWS) + utf8_arguments(argc, argv); + #endif + for(uint index : range(argc)) arguments.append(argv[index]); + construct(); +} + +inline Arguments::Arguments(vector arguments) { + this->arguments = arguments; + construct(); +} + +inline auto Arguments::programPath() const -> string { + return Location::path(programArgument); +} + +inline auto Arguments::programName() const -> string { + return Location::file(programArgument); +} + +inline auto Arguments::programLocation() const -> string { + return programArgument; +} + +inline auto Arguments::find(string_view name) const -> bool { + for(uint index : range(arguments.size())) { + if(arguments[index].match(name)) { + return true; + } + } + return false; +} + +inline auto Arguments::find(string_view name, bool& argument) const -> bool { + for(uint index : range(arguments.size())) { + if(arguments[index].match(name) && arguments.size() >= index + && (arguments[index + 1] == "true" || arguments[index + 1] == "false")) { + argument = arguments[index + 1] == "true"; + return true; + } + } + return false; +} + +inline auto Arguments::find(string_view name, string& argument) const -> bool { + for(uint index : range(arguments.size())) { + if(arguments[index].match(name) && arguments.size() >= index) { + argument = arguments[index + 1]; + return true; + } + } + return false; +} + +// + +inline auto Arguments::take() -> string { + if(!arguments) return {}; + return arguments.takeFirst(); +} + +inline auto Arguments::take(string_view name) -> bool { + for(uint index : range(arguments.size())) { + if(arguments[index].match(name)) { + arguments.remove(index); + return true; + } + } + return false; +} + +inline auto Arguments::take(string_view name, bool& argument) -> bool { + for(uint index : range(arguments.size())) { + if(arguments[index].match(name) && arguments.size() > index + 1 + && (arguments[index + 1] == "true" || arguments[index + 1] == "false")) { + arguments.remove(index); + argument = arguments.take(index) == "true"; + return true; + } + } + return false; +} + +inline auto Arguments::take(string_view name, string& argument) -> bool { + for(uint index : range(arguments.size())) { + if(arguments[index].match(name) && arguments.size() > index + 1) { + arguments.remove(index); + argument = arguments.take(index); + return true; + } + } + return false; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/arithmetic.hpp b/waterbox/bsnescore/bsnes/nall/arithmetic.hpp new file mode 100644 index 0000000000..388370f158 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/arithmetic.hpp @@ -0,0 +1,89 @@ +#pragma once + +//multi-precision arithmetic +//warning: each size is quadratically more expensive than the size before it! + +#include +#include +#include +#include + +#include + +namespace nall { + template struct ArithmeticNatural; + template<> struct ArithmeticNatural< 8> { using type = uint8_t; }; + template<> struct ArithmeticNatural< 16> { using type = uint16_t; }; + template<> struct ArithmeticNatural< 32> { using type = uint32_t; }; + template<> struct ArithmeticNatural< 64> { using type = uint64_t; }; + #if INTMAX_BITS >= 128 + template<> struct ArithmeticNatural<128> { using type = uint128_t; }; + #endif +} + +#if INTMAX_BITS < 128 +#define PairBits 128 +#define TypeBits 64 +#define HalfBits 32 +#include +#undef PairBits +#undef TypeBits +#undef HalfBits +#endif + +#define PairBits 256 +#define TypeBits 128 +#define HalfBits 64 +#include +#undef PairBits +#undef TypeBits +#undef HalfBits + +#define PairBits 512 +#define TypeBits 256 +#define HalfBits 128 +#include +#undef PairBits +#undef TypeBits +#undef HalfBits + +#define PairBits 1024 +#define TypeBits 512 +#define HalfBits 256 +#include +#undef PairBits +#undef TypeBits +#undef HalfBits + +#define PairBits 2048 +#define TypeBits 1024 +#define HalfBits 512 +#include +#undef PairBits +#undef TypeBits +#undef HalfBits + +#define PairBits 4096 +#define TypeBits 2048 +#define HalfBits 1024 +#include +#undef PairBits +#undef TypeBits +#undef HalfBits + +#define PairBits 8192 +#define TypeBits 4096 +#define HalfBits 2048 +#include +#undef PairBits +#undef TypeBits +#undef HalfBits + +namespace nall { + //TODO: these types are for expressing smaller bit ranges in class interfaces + //for instance, XChaCha20 taking a 192-bit nonce + //however, they still allow more bits than expressed ... + //some sort of wrapper needs to be devised to ensure these sizes are masked and wrap appropriately + + using uint192_t = uint256_t; +} diff --git a/waterbox/bsnescore/bsnes/nall/arithmetic/barrett.hpp b/waterbox/bsnescore/bsnes/nall/arithmetic/barrett.hpp new file mode 100644 index 0000000000..ffbaf7c5c9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/arithmetic/barrett.hpp @@ -0,0 +1,28 @@ +#pragma once + +namespace nall { + +template struct BarrettReduction { + using type = typename ArithmeticNatural<1 * Bits>::type; + using pair = typename ArithmeticNatural<2 * Bits>::type; + + explicit BarrettReduction(type modulo) : modulo(modulo), factor(pair(1) + -pair(modulo) / modulo) {} + + //return => value % modulo + inline auto operator()(pair value) const -> type { + pair hi, lo; + mul(value, factor, hi, lo); + pair remainder = value - hi * modulo; + return remainder < modulo ? remainder : remainder - modulo; + } + +private: + const pair modulo; + const pair factor; +}; + +template auto operator%(T value, const BarrettReduction& modulo) { + return modulo(value); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/arithmetic/natural.hpp b/waterbox/bsnescore/bsnes/nall/arithmetic/natural.hpp new file mode 100644 index 0000000000..fcc902b8df --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/arithmetic/natural.hpp @@ -0,0 +1,342 @@ +#define ConcatenateType(Size) uint##Size##_t +#define DeclareType(Size) ConcatenateType(Size) + +#define Pair DeclareType(PairBits) +#define Type DeclareType(TypeBits) +#define Half DeclareType(HalfBits) + +//pick the larger of two types to prevent unnecessary data clamping +#define Cast (typename conditional= sizeof(T), Pair, T>::type) + +namespace nall { +//namespace Arithmetic { + +struct Pair { + Pair() = default; + explicit constexpr Pair(const Pair& source) : hi(source.hi), lo(source.lo) {} + template constexpr Pair(const Hi& hi, const Lo& lo) : hi(hi), lo(lo) {} + template Pair(const T& source) { _set(*this, source); } + + explicit operator bool() const { return hi | lo; } + template operator T() const { T value; _get(*this, value); return value; } + + auto operator+() const -> Pair { return *this; } + auto operator-() const -> Pair { return Pair(0) - *this; } + auto operator~() const -> Pair { return {~hi, ~lo}; } + auto operator!() const -> bool { return !(hi || lo); } + + auto operator++() -> Pair& { lo++; hi += lo == 0; return *this; } + auto operator--() -> Pair& { hi -= lo == 0; lo--; return *this; } + + auto operator++(int) -> Pair { Pair r = *this; lo++; hi += lo == 0; return r; } + auto operator--(int) -> Pair { Pair r = *this; hi -= lo == 0; lo--; return r; } + + auto operator* (const Pair& rhs) const -> Pair { return mul(*this, rhs); } + auto operator/ (const Pair& rhs) const -> Pair { Pair q, r; div(*this, rhs, q, r); return q; } + auto operator% (const Pair& rhs) const -> Pair { Pair q, r; div(*this, rhs, q, r); return r; } + auto operator+ (const Pair& rhs) const -> Pair { return {hi + rhs.hi + (lo + rhs.lo < lo), lo + rhs.lo}; } + auto operator- (const Pair& rhs) const -> Pair { return {hi - rhs.hi - (lo - rhs.lo > lo), lo - rhs.lo}; } + auto operator<<(const Pair& rhs) const -> Pair { return shl(*this, rhs); } + auto operator>>(const Pair& rhs) const -> Pair { return shr(*this, rhs); } + auto operator& (const Pair& rhs) const -> Pair { return {hi & rhs.hi, lo & rhs.lo}; } + auto operator| (const Pair& rhs) const -> Pair { return {hi | rhs.hi, lo | rhs.lo}; } + auto operator^ (const Pair& rhs) const -> Pair { return {hi ^ rhs.hi, lo ^ rhs.lo}; } + auto operator==(const Pair& rhs) const -> bool { return hi == rhs.hi && lo == rhs.lo; } + auto operator!=(const Pair& rhs) const -> bool { return hi != rhs.hi || lo != rhs.lo; } + auto operator>=(const Pair& rhs) const -> bool { return hi > rhs.hi || (hi == rhs.hi && lo >= rhs.lo); } + auto operator<=(const Pair& rhs) const -> bool { return hi < rhs.hi || (hi == rhs.hi && lo <= rhs.lo); } + auto operator> (const Pair& rhs) const -> bool { return hi > rhs.hi || (hi == rhs.hi && lo > rhs.lo); } + auto operator< (const Pair& rhs) const -> bool { return hi < rhs.hi || (hi == rhs.hi && lo < rhs.lo); } + + template auto& operator*= (const T& rhs) { return *this = *this * Pair(rhs); } + template auto& operator/= (const T& rhs) { return *this = *this / Pair(rhs); } + template auto& operator%= (const T& rhs) { return *this = *this % Pair(rhs); } + template auto& operator+= (const T& rhs) { return *this = *this + Pair(rhs); } + template auto& operator-= (const T& rhs) { return *this = *this - Pair(rhs); } + template auto& operator<<=(const T& rhs) { return *this = *this << Pair(rhs); } + template auto& operator>>=(const T& rhs) { return *this = *this >> Pair(rhs); } + template auto& operator&= (const T& rhs) { return *this = *this & Pair(rhs); } + template auto& operator|= (const T& rhs) { return *this = *this | Pair(rhs); } + template auto& operator^= (const T& rhs) { return *this = *this ^ Pair(rhs); } + + template auto operator* (const T& rhs) const { return Cast(*this) * Cast(rhs); } + template auto operator/ (const T& rhs) const { return Cast(*this) / Cast(rhs); } + template auto operator% (const T& rhs) const { return Cast(*this) % Cast(rhs); } + template auto operator+ (const T& rhs) const { return Cast(*this) + Cast(rhs); } + template auto operator- (const T& rhs) const { return Cast(*this) - Cast(rhs); } + template auto operator<<(const T& rhs) const { return Cast(*this) << Cast(rhs); } + template auto operator>>(const T& rhs) const { return Cast(*this) >> Cast(rhs); } + template auto operator& (const T& rhs) const { return Cast(*this) & Cast(rhs); } + template auto operator| (const T& rhs) const { return Cast(*this) | Cast(rhs); } + template auto operator^ (const T& rhs) const { return Cast(*this) ^ Cast(rhs); } + + template auto operator==(const T& rhs) const -> bool { return Cast(*this) == Cast(rhs); } + template auto operator!=(const T& rhs) const -> bool { return Cast(*this) != Cast(rhs); } + template auto operator>=(const T& rhs) const -> bool { return Cast(*this) >= Cast(rhs); } + template auto operator<=(const T& rhs) const -> bool { return Cast(*this) <= Cast(rhs); } + template auto operator> (const T& rhs) const -> bool { return Cast(*this) > Cast(rhs); } + template auto operator< (const T& rhs) const -> bool { return Cast(*this) < Cast(rhs); } + +private: + Type lo; + Type hi; + + friend auto upper(const Pair&) -> Type; + friend auto lower(const Pair&) -> Type; + friend auto bits(Pair) -> uint; + friend auto square(const Pair&) -> Pair; + friend auto square(const Pair&, Pair&, Pair&) -> void; + friend auto mul(const Pair&, const Pair&) -> Pair; + friend auto mul(const Pair&, const Pair&, Pair&, Pair&) -> void; + friend auto div(const Pair&, const Pair&, Pair&, Pair&) -> void; + template friend auto shl(const Pair&, const T&) -> Pair; + template friend auto shr(const Pair&, const T&) -> Pair; +}; + +template<> struct ArithmeticNatural { + using type = Pair; +}; + +#define ConcatenateUDL(Size) _u##Size +#define DeclareUDL(Size) ConcatenateUDL(Size) + +alwaysinline auto operator"" DeclareUDL(PairBits)(const char* s) -> Pair { + Pair p = 0; + if(s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + while(*s) { + auto c = *s++; + if(c == '\''); + else if(c >= '0' && c <= '9') p = (p << 4) + (c - '0'); + else if(c >= 'a' && c <= 'f') p = (p << 4) + (c - 'a' + 10); + else if(c >= 'A' && c <= 'F') p = (p << 4) + (c - 'A' + 10); + else break; + } + } else { + while(*s) { + auto c = *s++; + if(c == '\''); + else if(c >= '0' && c <= '9') p = (p << 3) + (p << 1) + (c - '0'); + else break; + } + } + return p; +} + +#undef ConcatenateUDL +#undef DeclareUDL + +template alwaysinline auto _set(Pair& lhs, const T& rhs) -> enable_if_t<(sizeof(Pair) == sizeof(T))> { + lhs = rhs; +} + +template alwaysinline auto _set(Pair& lhs, const T& rhs) -> enable_if_t<(sizeof(Pair) > sizeof(T))> { + lhs = {0, rhs}; +} + +template alwaysinline auto _set(Pair& lhs, const T& rhs) -> enable_if_t<(sizeof(Pair) < sizeof(T))> { + lhs = {lower(rhs) >> TypeBits, lower(rhs)}; +} + +template alwaysinline auto _get(const Pair& lhs, T& rhs) -> enable_if_t<(sizeof(T) == sizeof(Pair))> { + rhs = lhs; +} + +template alwaysinline auto _get(const Pair& lhs, T& rhs) -> enable_if_t<(sizeof(T) > sizeof(Pair))> { + rhs = {0, lhs}; +} + +template alwaysinline auto _get(const Pair& lhs, T& rhs) -> enable_if_t<(sizeof(T) < sizeof(Pair))> { + rhs = lower(lhs); +} + +alwaysinline auto upper(const Pair& value) -> Type { return value.hi; } +alwaysinline auto lower(const Pair& value) -> Type { return value.lo; } + +alwaysinline auto bits(Pair value) -> uint { + if(value.hi) { + uint bits = TypeBits; + while(value.hi) value.hi >>= 1, bits++; + return bits; + } else { + uint bits = 0; + while(value.lo) value.lo >>= 1, bits++; + return bits; + } +} + +//Bits * Bits => Bits +inline auto square(const Pair& lhs) -> Pair { + static const Type Mask = (Type(0) - 1) >> HalfBits; + Type a = lhs.hi >> HalfBits, b = lhs.hi & Mask, c = lhs.lo >> HalfBits, d = lhs.lo & Mask; + Type dd = square(d), dc = d * c, db = d * b, da = d * a; + Type cc = square(c), cb = c * b; + + Pair r0 = Pair(dd); + Pair r1 = Pair(dc) + Pair(dc) + Pair(r0 >> HalfBits); + Pair r2 = Pair(db) + Pair(cc) + Pair(db) + Pair(r1 >> HalfBits); + Pair r3 = Pair(da) + Pair(cb) + Pair(cb) + Pair(da) + Pair(r2 >> HalfBits); + + return {(r3.lo & Mask) << HalfBits | (r2.lo & Mask), (r1.lo & Mask) << HalfBits | (r0.lo & Mask)}; +} + +//Bits * Bits => 2 * Bits +inline auto square(const Pair& lhs, Pair& hi, Pair& lo) -> void { + static const Type Mask = (Type(0) - 1) >> HalfBits; + Type a = lhs.hi >> HalfBits, b = lhs.hi & Mask, c = lhs.lo >> HalfBits, d = lhs.lo & Mask; + Type dd = square(d), dc = d * c, db = d * b, da = d * a; + Type cc = square(c), cb = c * b, ca = c * a; + Type bb = square(b), ba = b * a; + Type aa = square(a); + + Pair r0 = Pair(dd); + Pair r1 = Pair(dc) + Pair(dc) + Pair(r0 >> HalfBits); + Pair r2 = Pair(db) + Pair(cc) + Pair(db) + Pair(r1 >> HalfBits); + Pair r3 = Pair(da) + Pair(cb) + Pair(cb) + Pair(da) + Pair(r2 >> HalfBits); + Pair r4 = Pair(ca) + Pair(bb) + Pair(ca) + Pair(r3 >> HalfBits); + Pair r5 = Pair(ba) + Pair(ba) + Pair(r4 >> HalfBits); + Pair r6 = Pair(aa) + Pair(r5 >> HalfBits); + Pair r7 = Pair(r6 >> HalfBits); + + hi = {(r7.lo & Mask) << HalfBits | (r6.lo & Mask), (r5.lo & Mask) << HalfBits | (r4.lo & Mask)}; + lo = {(r3.lo & Mask) << HalfBits | (r2.lo & Mask), (r1.lo & Mask) << HalfBits | (r0.lo & Mask)}; +} + +//Bits * Bits => Bits +alwaysinline auto mul(const Pair& lhs, const Pair& rhs) -> Pair { + static const Type Mask = (Type(0) - 1) >> HalfBits; + Type a = lhs.hi >> HalfBits, b = lhs.hi & Mask, c = lhs.lo >> HalfBits, d = lhs.lo & Mask; + Type e = rhs.hi >> HalfBits, f = rhs.hi & Mask, g = rhs.lo >> HalfBits, h = rhs.lo & Mask; + + Pair r0 = Pair(d * h); + Pair r1 = Pair(c * h) + Pair(d * g) + Pair(r0 >> HalfBits); + Pair r2 = Pair(b * h) + Pair(c * g) + Pair(d * f) + Pair(r1 >> HalfBits); + Pair r3 = Pair(a * h) + Pair(b * g) + Pair(c * f) + Pair(d * e) + Pair(r2 >> HalfBits); + + return {(r3.lo & Mask) << HalfBits | (r2.lo & Mask), (r1.lo & Mask) << HalfBits | (r0.lo & Mask)}; +} + +//Bits * Bits => 2 * Bits +alwaysinline auto mul(const Pair& lhs, const Pair& rhs, Pair& hi, Pair& lo) -> void { + static const Type Mask = (Type(0) - 1) >> HalfBits; + Type a = lhs.hi >> HalfBits, b = lhs.hi & Mask, c = lhs.lo >> HalfBits, d = lhs.lo & Mask; + Type e = rhs.hi >> HalfBits, f = rhs.hi & Mask, g = rhs.lo >> HalfBits, h = rhs.lo & Mask; + + Pair r0 = Pair(d * h); + Pair r1 = Pair(c * h) + Pair(d * g) + Pair(r0 >> HalfBits); + Pair r2 = Pair(b * h) + Pair(c * g) + Pair(d * f) + Pair(r1 >> HalfBits); + Pair r3 = Pair(a * h) + Pair(b * g) + Pair(c * f) + Pair(d * e) + Pair(r2 >> HalfBits); + Pair r4 = Pair(a * g) + Pair(b * f) + Pair(c * e) + Pair(r3 >> HalfBits); + Pair r5 = Pair(a * f) + Pair(b * e) + Pair(r4 >> HalfBits); + Pair r6 = Pair(a * e) + Pair(r5 >> HalfBits); + Pair r7 = Pair(r6 >> HalfBits); + + hi = {(r7.lo & Mask) << HalfBits | (r6.lo & Mask), (r5.lo & Mask) << HalfBits | (r4.lo & Mask)}; + lo = {(r3.lo & Mask) << HalfBits | (r2.lo & Mask), (r1.lo & Mask) << HalfBits | (r0.lo & Mask)}; +} + +alwaysinline auto div(const Pair& lhs, const Pair& rhs, Pair& quotient, Pair& remainder) -> void { + if(!rhs) throw std::runtime_error("division by zero"); + quotient = 0, remainder = lhs; + if(!lhs || lhs < rhs) return; + + auto count = bits(lhs) - bits(rhs); + Pair x = rhs << count; + Pair y = Pair(1) << count; + if(x > remainder) x >>= 1, y >>= 1; + while(remainder >= rhs) { + if(remainder >= x) remainder -= x, quotient |= y; + x >>= 1, y >>= 1; + } +} + +template alwaysinline auto shl(const Pair& lhs, const T& rhs) -> Pair { + if(!rhs) return lhs; + auto shift = (uint)rhs; + if(shift < TypeBits) { + return {lhs.hi << shift | lhs.lo >> (TypeBits - shift), lhs.lo << shift}; + } else { + return {lhs.lo << (shift - TypeBits), 0}; + } +} + +template alwaysinline auto shr(const Pair& lhs, const T& rhs) -> Pair { + if(!rhs) return lhs; + auto shift = (uint)rhs; + if(shift < TypeBits) { + return {lhs.hi >> shift, lhs.hi << (TypeBits - shift) | lhs.lo >> shift}; + } else { + return {0, lhs.hi >> (shift - TypeBits)}; + } +} + +template alwaysinline auto rol(const Pair& lhs, const T& rhs) -> Pair { + return lhs << rhs | lhs >> (PairBits - rhs); +} + +template alwaysinline auto ror(const Pair& lhs, const T& rhs) -> Pair { + return lhs >> rhs | lhs << (PairBits - rhs); +} + +#define EI enable_if_t::value> + +template auto& operator*= (T& lhs, const Pair& rhs) { return lhs = lhs * T(rhs); } +template auto& operator/= (T& lhs, const Pair& rhs) { return lhs = lhs / T(rhs); } +template auto& operator%= (T& lhs, const Pair& rhs) { return lhs = lhs % T(rhs); } +template auto& operator+= (T& lhs, const Pair& rhs) { return lhs = lhs + T(rhs); } +template auto& operator-= (T& lhs, const Pair& rhs) { return lhs = lhs - T(rhs); } +template auto& operator<<=(T& lhs, const Pair& rhs) { return lhs = lhs << T(rhs); } +template auto& operator>>=(T& lhs, const Pair& rhs) { return lhs = lhs >> T(rhs); } +template auto& operator&= (T& lhs, const Pair& rhs) { return lhs = lhs & T(rhs); } +template auto& operator|= (T& lhs, const Pair& rhs) { return lhs = lhs | T(rhs); } +template auto& operator^= (T& lhs, const Pair& rhs) { return lhs = lhs ^ T(rhs); } + +template auto operator* (const T& lhs, const Pair& rhs) { return Cast(lhs) * Cast(rhs); } +template auto operator/ (const T& lhs, const Pair& rhs) { return Cast(lhs) / Cast(rhs); } +template auto operator% (const T& lhs, const Pair& rhs) { return Cast(lhs) % Cast(rhs); } +template auto operator+ (const T& lhs, const Pair& rhs) { return Cast(lhs) + Cast(rhs); } +template auto operator- (const T& lhs, const Pair& rhs) { return Cast(lhs) - Cast(rhs); } +template auto operator<<(const T& lhs, const Pair& rhs) { return Cast(lhs) << Cast(rhs); } +template auto operator>>(const T& lhs, const Pair& rhs) { return Cast(lhs) >> Cast(rhs); } +template auto operator& (const T& lhs, const Pair& rhs) { return Cast(lhs) & Cast(rhs); } +template auto operator| (const T& lhs, const Pair& rhs) { return Cast(lhs) | Cast(rhs); } +template auto operator^ (const T& lhs, const Pair& rhs) { return Cast(lhs) ^ Cast(rhs); } + +template auto operator==(const T& lhs, const Pair& rhs) { return Cast(lhs) == Cast(rhs); } +template auto operator!=(const T& lhs, const Pair& rhs) { return Cast(lhs) != Cast(rhs); } +template auto operator>=(const T& lhs, const Pair& rhs) { return Cast(lhs) >= Cast(rhs); } +template auto operator<=(const T& lhs, const Pair& rhs) { return Cast(lhs) <= Cast(rhs); } +template auto operator> (const T& lhs, const Pair& rhs) { return Cast(lhs) > Cast(rhs); } +template auto operator< (const T& lhs, const Pair& rhs) { return Cast(lhs) < Cast(rhs); } + +#undef EI + +template<> struct stringify { + stringify(Pair source) { + char _output[1 + sizeof(Pair) * 3]; + auto p = (char*)&_output; + do { + Pair quotient, remainder; + div(source, 10, quotient, remainder); + *p++ = remainder + '0'; + source = quotient; + } while(source); + _size = p - _output; + *p = 0; + for(int x = _size - 1, y = 0; x >= 0 && y < _size; x--, y++) _data[x] = _output[y]; + } + + auto data() const -> const char* { return _data; } + auto size() const -> uint { return _size; } + char _data[1 + sizeof(Pair) * 3]; + uint _size; +}; + +} + +#undef ConcatenateType +#undef DeclareType +#undef Pair +#undef Type +#undef Half +#undef Cast diff --git a/waterbox/bsnescore/bsnes/nall/arithmetic/unsigned.hpp b/waterbox/bsnescore/bsnes/nall/arithmetic/unsigned.hpp new file mode 100644 index 0000000000..5ed7ecd66d --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/arithmetic/unsigned.hpp @@ -0,0 +1,61 @@ +#pragma once + +namespace nall { + +template::value>> +inline auto upper(T value) -> T { + return value >> sizeof(T) * 4; +} + +template::value>> +inline auto lower(T value) -> T { + static const T Mask = ~T(0) >> sizeof(T) * 4; + return value & Mask; +} + +template::value>, enable_if_t::value>> +inline auto mul(T lhs, U rhs) -> uintmax { + return lhs * rhs; +} + +template::value>> +inline auto square(T value) -> uintmax { + return value * value; +} + +template +inline auto rol(T lhs, U rhs, enable_if_t::value>* = 0) -> T { + return lhs << rhs | lhs >> sizeof(T) * 8 - rhs; +} + +template +inline auto ror(T lhs, U rhs, enable_if_t::value>* = 0) -> T { + return lhs >> rhs | lhs << sizeof(T) * 8 - rhs; +} + +#if INTMAX_BITS >= 128 +inline auto operator"" _u128(const char* s) -> uint128_t { + uint128_t p = 0; + if(s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + while(*s) { + auto c = *s++; + if(c == '\''); + else if(c >= '0' && c <= '9') p = (p << 4) + (c - '0'); + else if(c >= 'a' && c <= 'f') p = (p << 4) + (c - 'a' + 10); + else if(c >= 'A' && c <= 'F') p = (p << 4) + (c - 'A' + 10); + else break; + } + } else { + while(*s) { + auto c = *s++; + if(c == '\''); + else if(c >= '0' && c <= '9') p = (p << 3) + (p << 1) + (c - '0'); + else break; + } + } + return p; +} +#endif + +} diff --git a/waterbox/bsnescore/bsnes/nall/array-span.hpp b/waterbox/bsnescore/bsnes/nall/array-span.hpp new file mode 100644 index 0000000000..96c791d41b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/array-span.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include + +namespace nall { + +template struct array_span : array_view { + using type = array_span; + using super = array_view; + + inline array_span() { + super::_data = nullptr; + super::_size = 0; + } + + inline array_span(nullptr_t) { + super::_data = nullptr; + super::_size = 0; + } + + inline array_span(void* data, uint64_t size) { + super::_data = (T*)data; + super::_size = (int)size; + } + + inline operator T*() { return (T*)super::operator const T*(); } + + inline auto operator[](uint index) -> T& { return (T&)super::operator[](index); } + + template inline auto data() -> U* { return (U*)super::_data; } + + inline auto begin() -> iterator { return {(T*)super::_data, (uint)0}; } + inline auto end() -> iterator { return {(T*)super::_data, (uint)super::_size}; } + + inline auto rbegin() -> reverse_iterator { return {(T*)super::_data, (uint)super::_size - 1}; } + inline auto rend() -> reverse_iterator { return {(T*)super::_data, (uint)-1}; } + + auto write(T value) -> void { + operator[](0) = value; + super::_data++; + super::_size--; + } + + auto span(uint offset, uint length) const -> type { + #ifdef DEBUG + struct out_of_bounds {}; + if(offset + length >= super::_size) throw out_of_bounds{}; + #endif + return {super::_data + offset, length}; + } + + //array_span specializations + template auto writel(U value, uint size) -> void; + template auto writem(U value, uint size) -> void; + template auto writevn(U value, uint size) -> void; + template auto writevi(U value, uint size) -> void; +}; + +//array_span + +template<> inline auto array_span::write(uint8_t value) -> void { + operator[](0) = value; + _data++; + _size--; +} + +template<> template inline auto array_span::writel(U value, uint size) -> void { + for(uint byte : range(size)) write(value >> byte * 8); +} + +template<> template inline auto array_span::writem(U value, uint size) -> void { + for(uint byte : reverse(range(size))) write(value >> byte * 8); +} + +template<> template inline auto array_span::writevn(U value, uint size) -> void { + while(true) { + auto byte = value & 0x7f; + value >>= 7; + if(value == 0) return write(0x80 | byte); + write(byte); + value--; + } +} + +template<> template inline auto array_span::writevi(U value, uint size) -> void { + bool negate = value < 0; + if(negate) value = ~value; + value = value << 1 | negate; + writevn(value); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/array-view.hpp b/waterbox/bsnescore/bsnes/nall/array-view.hpp new file mode 100644 index 0000000000..1c990b02b3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/array-view.hpp @@ -0,0 +1,138 @@ +#pragma once + +#include +#include +#include + +namespace nall { + +template struct array_view { + using type = array_view; + + inline array_view() { + _data = nullptr; + _size = 0; + } + + inline array_view(nullptr_t) { + _data = nullptr; + _size = 0; + } + + inline array_view(const void* data, uint64_t size) { + _data = (const T*)data; + _size = (int)size; + } + + inline explicit operator bool() const { return _data && _size > 0; } + + inline operator const T*() const { + #ifdef DEBUG + struct out_of_bounds {}; + if(_size < 0) throw out_of_bounds{}; + #endif + return _data; + } + + inline auto operator++() -> type& { _data++; _size--; return *this; } + inline auto operator--() -> type& { _data--; _size++; return *this; } + + inline auto operator++(int) -> type { auto copy = *this; ++(*this); return copy; } + inline auto operator--(int) -> type { auto copy = *this; --(*this); return copy; } + + inline auto operator-=(int distance) -> type& { _data -= distance; _size += distance; return *this; } + inline auto operator+=(int distance) -> type& { _data += distance; _size -= distance; return *this; } + + inline auto operator[](uint index) const -> const T& { + #ifdef DEBUG + struct out_of_bounds {}; + if(index >= _size) throw out_of_bounds{}; + #endif + return _data[index]; + } + + inline auto operator()(uint index, const T& fallback = {}) const -> T { + if(index >= _size) return fallback; + return _data[index]; + } + + template inline auto data() const -> const U* { return (const U*)_data; } + template inline auto size() const -> uint64_t { return _size * sizeof(T) / sizeof(U); } + + inline auto begin() const -> iterator_const { return {_data, (uint)0}; } + inline auto end() const -> iterator_const { return {_data, (uint)_size}; } + + inline auto rbegin() const -> reverse_iterator_const { return {_data, (uint)_size - 1}; } + inline auto rend() const -> reverse_iterator_const { return {_data, (uint)-1}; } + + auto read() -> T { + auto value = operator[](0); + _data++; + _size--; + return value; + } + + auto view(uint offset, uint length) const -> type { + #ifdef DEBUG + struct out_of_bounds {}; + if(offset + length >= _size) throw out_of_bounds{}; + #endif + return {_data + offset, length}; + } + + //array_view specializations + template auto readl(U& value, uint size) -> U; + template auto readm(U& value, uint size) -> U; + template auto readvn(U& value, uint size) -> U; + template auto readvi(U& value, uint size) -> U; + + template auto readl(U& value, uint offset, uint size) -> U { return view(offset, size).readl(value, size); } + + template auto readl(uint size) -> U { U value; return readl(value, size); } + template auto readm(uint size) -> U { U value; return readm(value, size); } + template auto readvn(uint size) -> U { U value; return readvn(value, size); } + template auto readvi(uint size) -> U { U value; return readvi(value, size); } + + template auto readl(uint offset, uint size) -> U { U value; return readl(value, offset, size); } + +protected: + const T* _data; + int _size; +}; + +//array_view + +template<> template inline auto array_view::readl(U& value, uint size) -> U { + value = 0; + for(uint byte : range(size)) value |= (U)read() << byte * 8; + return value; +} + +template<> template inline auto array_view::readm(U& value, uint size) -> U { + value = 0; + for(uint byte : reverse(range(size))) value |= (U)read() << byte * 8; + return value; +} + +template<> template inline auto array_view::readvn(U& value, uint size) -> U { + value = 0; + uint shift = 1; + while(true) { + auto byte = read(); + value += (byte & 0x7f) * shift; + if(byte & 0x80) break; + shift <<= 7; + value += shift; + } + return value; +} + +template<> template inline auto array_view::readvi(U& value, uint size) -> U { + value = readvn(); + bool negate = value & 1; + value >>= 1; + if(negate) value = ~value; + return value; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/array.hpp b/waterbox/bsnescore/bsnes/nall/array.hpp new file mode 100644 index 0000000000..2d705337db --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/array.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include +#include + +namespace nall { + +template struct array; + +//usage: int x[256] => array x +template struct array { + array() = default; + + array(const initializer_list& source) { + uint index = 0; + for(auto& value : source) { + operator[](index++) = value; + } + } + + operator array_span() { + return {data(), size()}; + } + + operator array_view() const { + return {data(), size()}; + } + + alwaysinline auto operator[](uint index) -> T& { + #ifdef DEBUG + struct out_of_bounds {}; + if(index >= Size) throw out_of_bounds{}; + #endif + return values[index]; + } + + alwaysinline auto operator[](uint index) const -> const T& { + #ifdef DEBUG + struct out_of_bounds {}; + if(index >= Size) throw out_of_bounds{}; + #endif + return values[index]; + } + + alwaysinline auto operator()(uint index, const T& fallback = {}) const -> const T& { + if(index >= Size) return fallback; + return values[index]; + } + + auto fill(const T& fill = {}) -> array& { + for(auto& value : values) value = fill; + return *this; + } + + auto data() -> T* { return values; } + auto data() const -> const T* { return values; } + auto size() const -> uint { return Size; } + + auto begin() -> T* { return &values[0]; } + auto end() -> T* { return &values[Size]; } + + auto begin() const -> const T* { return &values[0]; } + auto end() const -> const T* { return &values[Size]; } + +private: + T values[Size]; +}; + +template auto from_array(uint index) -> T { + static const array table{p...}; + struct out_of_bounds {}; + #if defined(DEBUG) + if(index >= sizeof...(p)) throw out_of_bounds{}; + #endif + return table[index]; +} + +template auto from_array(uint index) -> int64_t { + static const array table{p...}; + struct out_of_bounds {}; + #if defined(DEBUG) + if(index >= sizeof...(p)) throw out_of_bounds{}; + #endif + return table[index]; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/atoi.hpp b/waterbox/bsnescore/bsnes/nall/atoi.hpp new file mode 100644 index 0000000000..d6fba252cf --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/atoi.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include + +namespace nall { + +constexpr inline auto toBinary_(const char* s, uintmax sum = 0) -> uintmax { + return ( + *s == '0' || *s == '1' ? toBinary_(s + 1, (sum << 1) | *s - '0') : + *s == '\'' ? toBinary_(s + 1, sum) : + sum + ); +} + +constexpr inline auto toOctal_(const char* s, uintmax sum = 0) -> uintmax { + return ( + *s >= '0' && *s <= '7' ? toOctal_(s + 1, (sum << 3) | *s - '0') : + *s == '\'' ? toOctal_(s + 1, sum) : + sum + ); +} + +constexpr inline auto toDecimal_(const char* s, uintmax sum = 0) -> uintmax { + return ( + *s >= '0' && *s <= '9' ? toDecimal_(s + 1, (sum * 10) + *s - '0') : + *s == '\'' ? toDecimal_(s + 1, sum) : + sum + ); +} + +constexpr inline auto toHex_(const char* s, uintmax sum = 0) -> uintmax { + return ( + *s >= 'A' && *s <= 'F' ? toHex_(s + 1, (sum << 4) | *s - 'A' + 10) : + *s >= 'a' && *s <= 'f' ? toHex_(s + 1, (sum << 4) | *s - 'a' + 10) : + *s >= '0' && *s <= '9' ? toHex_(s + 1, (sum << 4) | *s - '0') : + *s == '\'' ? toHex_(s + 1, sum) : + sum + ); +} + +// + +constexpr inline auto toBinary(const char* s) -> uintmax { + return ( + *s == '0' && (*(s + 1) == 'B' || *(s + 1) == 'b') ? toBinary_(s + 2) : + *s == '%' ? toBinary_(s + 1) : toBinary_(s) + ); +} + +constexpr inline auto toOctal(const char* s) -> uintmax { + return ( + *s == '0' && (*(s + 1) == 'O' || *(s + 1) == 'o') ? toOctal_(s + 2) : + toOctal_(s) + ); +} + +constexpr inline auto toHex(const char* s) -> uintmax { + return ( + *s == '0' && (*(s + 1) == 'X' || *(s + 1) == 'x') ? toHex_(s + 2) : + *s == '$' ? toHex_(s + 1) : toHex_(s) + ); +} + +// + +constexpr inline auto toNatural(const char* s) -> uintmax { + return ( + *s == '0' && (*(s + 1) == 'B' || *(s + 1) == 'b') ? toBinary_(s + 2) : + *s == '0' && (*(s + 1) == 'O' || *(s + 1) == 'o') ? toOctal_(s + 2) : + *s == '0' && (*(s + 1) == 'X' || *(s + 1) == 'x') ? toHex_(s + 2) : + *s == '%' ? toBinary_(s + 1) : *s == '$' ? toHex_(s + 1) : toDecimal_(s) + ); +} + +constexpr inline auto toInteger(const char* s) -> intmax { + return ( + *s == '+' ? +toNatural(s + 1) : *s == '-' ? -toNatural(s + 1) : toNatural(s) + ); +} + +// + +inline auto toReal(const char* s) -> double { + return atof(s); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/beat/archive/container.hpp b/waterbox/bsnescore/bsnes/nall/beat/archive/container.hpp new file mode 100644 index 0000000000..bdfce1da88 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/beat/archive/container.hpp @@ -0,0 +1,200 @@ +#pragma once + +#include + +namespace nall::Beat::Archive { + +struct Container { + Container(array_view = {}); + ~Container(); + + auto isCompressed() const -> bool { return (bool)compression.type; } + auto isSigned() const -> bool { return (bool)signature.type; } + auto isEncrypted() const -> bool { return (bool)encryption.type; } + + auto compressLZSA() -> void; + auto signEd25519(uint256_t privateKey) -> void; + auto encryptXChaCha20(uint256_t privateKey, uint192_t nonce = 0) -> void; + + auto validate() -> bool; + auto decryptXChaCha20(uint256_t privateKey) -> bool; + auto verifyEd25519(uint256_t publicKey) -> bool; + auto decompressLZSA() -> bool; + + auto append(string name, string location) -> shared_pointer; + auto appendPath(string name) -> shared_pointer; + auto appendFile(string name, array_view memory) -> shared_pointer; + auto remove(string name) -> bool; + auto find(string name) -> shared_pointer; + auto sort() -> void; + + auto begin() { return nodes.begin(); } + auto end() { return nodes.end(); } + + auto begin() const { return nodes.begin(); } + auto end() const { return nodes.end(); } + + auto rbegin() { return nodes.rbegin(); } + auto rend() { return nodes.rend(); } + + auto rbegin() const { return nodes.rbegin(); } + auto rend() const { return nodes.rend(); } + + vector> nodes; + vector memory; + string metadata; + + struct Compression { + string type; + } compression; + + struct Signature { + string type; + uint256_t privateKey = 0; + uint256_t publicKey = 0; + uint512_t value = 0; + } signature; + + struct Encryption { + string type; + uint256_t privateKey = 0; + uint192_t nonce = 0; + } encryption; +}; + +Container::Container(array_view memory) { + this->memory.resize(memory.size()); + nall::memory::copy(this->memory.data(), memory.data(), memory.size()); +} + +Container::~Container() { + metadata = {}; + signature = {}; + encryption = {}; +} + +// + +auto Container::compressLZSA() -> void { + compression.type = "lzsa"; +} + +auto Container::signEd25519(uint256_t privateKey) -> void { + signature.type = "ed25519"; + signature.privateKey = privateKey; +} + +auto Container::encryptXChaCha20(uint256_t privateKey, uint192_t nonce) -> void { + if(!nonce) { + CSPRNG::XChaCha20 csprng; + nonce = csprng.random(); + } + + encryption.type = "xchacha20"; + encryption.privateKey = privateKey; + encryption.nonce = nonce; +} + +// + +auto Container::validate() -> bool { + array_view memory = this->memory; + if(memory.size() < 44) return false; //8 (metadata size) + 32 (SHA256) + 4 (signature) + + if(memory[memory.size() - 4] != 'B') return false; + if(memory[memory.size() - 3] != 'P') return false; + if(memory[memory.size() - 2] != 'A') return false; + if(memory[memory.size() - 1] != '1') return false; + + auto sha256 = memory.readl(memory.size() - 36, 32); + if(Hash::SHA256({memory.data(), memory.size() - 36}).value() != sha256) return false; + + auto size = memory.readl(memory.size() - 44, 8); + + if(size & 1ull << 63) { + size -= 1ull << 63; + metadata = memory.view(memory.size() - 44 - size, size); + uint64_t offset = memory.size() - 44 - size; + for(auto& byte : metadata) byte ^= offset++; + } else { + metadata = memory.view(memory.size() - 44 - size, size); + } + + auto document = BML::unserialize(metadata); + + if(auto node = document["archive/encryption"]) { + if(node.text() == "xchacha20") { + encryption.type = node.text(); + encryption.nonce = Decode::Base<57, uint192_t>(node["nonce"].text()); + } + } + + if(auto node = document["archive/signature"]) { + if(node.text() == "ed25519") { + signature.type = node.text(); + signature.publicKey = Decode::Base<57, uint256_t>(node["publicKey"].text()); + signature.value = Decode::Base<57, uint512_t>(node["value"].text()); + } + } + + if(auto node = document["archive/compression"]) { + compression.type = node.text(); + } + + return true; +} + +auto Container::decryptXChaCha20(uint256_t privateKey) -> bool { + encryption.privateKey = privateKey; + Cipher::XChaCha20 xchacha20{encryption.privateKey, encryption.nonce}; + auto size = memory.readl(memory.size() - 44, 8); + memory = xchacha20.decrypt(memory.view(0, memory.size() - 44 - size)); + return true; +} + +auto Container::verifyEd25519(uint256_t publicKey) -> bool { + EllipticCurve::Ed25519 ed25519; + auto size = memory.readl(memory.size() - 44, 8); + return ed25519.verify(memory.view(0, memory.size() - 44 - size), signature.value, publicKey); +} + +auto Container::decompressLZSA() -> bool { + memory = Decode::LZSA(memory); + return (bool)memory; +} + +// + +auto Container::append(string name, string location) -> shared_pointer { + for(auto& node : nodes) if(node->name == name) return {}; + if(auto node = Node::create(name, location)) return nodes.append(node), node; + return {}; +} + +auto Container::appendPath(string name) -> shared_pointer { + for(auto& node : nodes) if(node->name == name) return {}; + if(auto node = Node::createPath(name)) return nodes.append(node), node; + return {}; +} + +auto Container::appendFile(string name, array_view memory) -> shared_pointer { + for(auto& node : nodes) if(node->name == name) return {}; + if(auto node = Node::createFile(name, memory)) return nodes.append(node), node; + return {}; +} + +auto Container::remove(string name) -> bool { + if(auto offset = nodes.find([&](auto& node) { return node->name == name; })) return nodes.remove(*offset), true; + return false; +} + +auto Container::find(string name) -> shared_pointer { + if(auto offset = nodes.find([&](auto& node) { return node->name == name; })) return nodes[*offset]; + return {}; +} + +auto Container::sort() -> void { + nodes.sort([&](auto& lhs, auto& rhs) { return string::icompare(lhs->name, rhs->name) < 0; }); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/beat/archive/create.hpp b/waterbox/bsnescore/bsnes/nall/beat/archive/create.hpp new file mode 100644 index 0000000000..7014e48905 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/beat/archive/create.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include + +namespace nall::Beat::Archive { + +auto create(Container& container, string name) -> vector { + auto& metadata = container.metadata; + metadata = {}; + metadata.append("archive: ", Location::file(name), "\n"); + + vector memory; + + container.sort(); + for(auto& node : container.nodes) { + if(node->isFile()) { + node->offset = memory.size(); + memory.append(node->memory); + } + metadata.append(node->metadata()); + } + + metadata.append(" size: ", memory.size(), "\n"); + + if(container.compression.type == "lzsa") { + memory = Encode::LZSA(memory); + metadata.append(" compression: lzsa\n"); + metadata.append(" size: ", memory.size(), "\n"); + } + + if(container.signature.type == "ed25519") { + EllipticCurve::Ed25519 ed25519; + container.signature.publicKey = ed25519.publicKey(container.signature.privateKey); + container.signature.value = ed25519.sign(memory, container.signature.privateKey); + + metadata.append(" signature: ed25519\n"); + metadata.append(" publicKey: ", Encode::Base<57>(container.signature.publicKey), "\n"); + metadata.append(" value: ", Encode::Base<57>(container.signature.value), "\n"); + } + + for(auto& byte : metadata) memory.append(byte); + memory.appendl((uint64_t)metadata.size(), 8); + + auto sha256 = Hash::SHA256(memory).value(); + memory.appendl((uint256_t)sha256, 32); + + memory.append('B'); + memory.append('P'); + memory.append('A'); + memory.append('1'); + + if(container.encryption.type == "xchacha20") { + Cipher::XChaCha20 xchacha20{container.encryption.privateKey, container.encryption.nonce}; + memory = xchacha20.encrypt(memory); + + metadata = {}; + metadata.append("archive\n"); + metadata.append(" encryption: xchacha20\n"); + metadata.append(" nonce: ", Encode::Base<57>(container.encryption.nonce), "\n"); + + if(container.signature.type == "ed25519") { + EllipticCurve::Ed25519 ed25519; + container.signature.value = ed25519.sign(memory, container.signature.privateKey); + + metadata.append(" signature: ed25519\n"); + //metadata.append(" publicKey: ", Encode::Base<57>(container.signature.publicKey), "\n"); + metadata.append(" value: ", Encode::Base<57>(container.signature.value), "\n"); + } + + for(auto& byte : metadata) memory.append(byte ^ memory.size()); + memory.appendl((uint64_t)metadata.size() | 1ull << 63, 8); + + auto sha256 = Hash::SHA256(memory).value(); + memory.appendl((uint256_t)sha256, 32); + + memory.append('B'); + memory.append('P'); + memory.append('A'); + memory.append('1'); + } + + return memory; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/beat/archive/extract.hpp b/waterbox/bsnescore/bsnes/nall/beat/archive/extract.hpp new file mode 100644 index 0000000000..41e4889139 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/beat/archive/extract.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace nall::Beat::Archive { + +auto extract(Container& container) -> bool { + function extract = [&](auto metadata) { + if(metadata.name() != "path" && metadata.name() != "file") return; + shared_pointer node = new Node; + if(node->unserialize(container.memory, metadata)) { + container.nodes.append(node); + } + if(metadata.name() != "path") return; + for(auto node : metadata) extract(node); + }; + + container.nodes.reset(); + auto document = BML::unserialize(container.metadata); + for(auto node : document["archive"]) extract(node); + container.sort(); + + return true; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/beat/archive/node.hpp b/waterbox/bsnescore/bsnes/nall/beat/archive/node.hpp new file mode 100644 index 0000000000..a8821ff717 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/beat/archive/node.hpp @@ -0,0 +1,332 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall::Beat::Archive { + +struct Node { + static auto create(string name, string location) -> shared_pointer; + static auto createPath(string name) -> shared_pointer; + static auto createFile(string name, array_view memory) -> shared_pointer; + + explicit operator bool() const { return (bool)name; } + auto isPath() const -> bool { return name.endsWith("/"); } + auto isFile() const -> bool { return !name.endsWith("/"); } + auto isCompressed() const -> bool { return (bool)compression.type; } + + auto metadata(bool indented = true) const -> string; + auto compressLZSA() -> bool; + + auto unserialize(array_view container, Markup::Node metadata) -> bool; + auto decompress() -> bool; + + auto getTimestamp(string) const -> uint64_t; + auto getPermissions() const -> uint; + auto getOwner() const -> string; + auto getGroup() const -> string; + + //files and paths + string name; + + bool timestamps = false; + struct Timestamp { + string created; + string modified; + string accessed; + } timestamp; + + bool permissions = false; + struct Permission { + struct Owner { + string name; + bool readable = false; + bool writable = false; + bool executable = false; + } owner; + struct Group { + string name; + bool readable = false; + bool writable = false; + bool executable = false; + } group; + struct Other { + bool readable = false; + bool writable = false; + bool executable = false; + } other; + } permission; + + //files only + vector memory; + uint64_t offset = 0; + + struct Compression { + string type; + uint size = 0; //decompressed size; memory.size() == compressed size + } compression; +}; + +auto Node::create(string name, string location) -> shared_pointer { + if(!inode::exists(location)) return {}; + shared_pointer node = new Node; + + node->name = name; + + node->timestamps = true; + node->timestamp.created = chrono::utc::datetime(inode::timestamp(location, inode::time::create)); + node->timestamp.modified = chrono::utc::datetime(inode::timestamp(location, inode::time::modify)); + node->timestamp.accessed = chrono::utc::datetime(inode::timestamp(location, inode::time::access)); + + uint mode = inode::mode(location); + node->permissions = true; + node->permission.owner.name = inode::owner(location); + node->permission.group.name = inode::group(location); + node->permission.owner.readable = mode & 0400; + node->permission.owner.writable = mode & 0200; + node->permission.owner.executable = mode & 0100; + node->permission.group.readable = mode & 0040; + node->permission.group.writable = mode & 0020; + node->permission.group.executable = mode & 0010; + node->permission.other.readable = mode & 0004; + node->permission.other.writable = mode & 0002; + node->permission.other.executable = mode & 0001; + + if(file::exists(location)) { + node->memory = file::read(location); + } + + return node; +} + +auto Node::createPath(string name) -> shared_pointer { + if(!name) return {}; + shared_pointer node = new Node; + node->name = name; + return node; +} + +auto Node::createFile(string name, array_view memory) -> shared_pointer { + if(!name) return {}; + shared_pointer node = new Node; + node->name = name; + node->memory.resize(memory.size()); + memory::copy(node->memory.data(), memory.data(), memory.size()); + return node; +} + +auto Node::metadata(bool indented) const -> string { + string metadata; + if(!name) return metadata; + + string indent; + if(indented) { + indent.append(" "); + auto bytes = string{name}.trimRight("/"); + for(auto& byte : bytes) { + if(byte == '/') indent.append(" "); + } + } + + if(isPath()) { + metadata.append(indent, "path: ", name, "\n"); + } + + if(isFile()) { + metadata.append(indent, "file: ", name, "\n"); + } + + if(timestamps) { + metadata.append(indent, " timestamp\n"); + if(timestamp.created != timestamp.modified) + metadata.append(indent, " created: ", timestamp.created, "\n"); + metadata.append(indent, " modified: ", timestamp.modified, "\n"); + if(timestamp.accessed != timestamp.modified) + metadata.append(indent, " accessed: ", timestamp.accessed, "\n"); + } + + if(permissions) { + metadata.append(indent, " permission\n"); + metadata.append(indent, " owner: ", permission.owner.name, "\n"); + if(permission.owner.readable) + metadata.append(indent, " readable\n"); + if(permission.owner.writable) + metadata.append(indent, " writable\n"); + if(permission.owner.executable) + metadata.append(indent, " executable\n"); + metadata.append(indent, " group: ", permission.group.name, "\n"); + if(permission.group.readable) + metadata.append(indent, " readable\n"); + if(permission.group.writable) + metadata.append(indent, " writable\n"); + if(permission.group.executable) + metadata.append(indent, " executable\n"); + metadata.append(indent, " other\n"); + if(permission.other.readable) + metadata.append(indent, " readable\n"); + if(permission.other.writable) + metadata.append(indent, " writable\n"); + if(permission.other.executable) + metadata.append(indent, " executable\n"); + } + + if(isFile()) { + metadata.append(indent, " offset: ", offset, "\n"); + if(!isCompressed()) { + metadata.append(indent, " size: ", memory.size(), "\n"); + } else { + metadata.append(indent, " size: ", compression.size, "\n"); + metadata.append(indent, " compression: ", compression.type, "\n"); + metadata.append(indent, " size: ", memory.size(), "\n"); + } + } + + return metadata; +} + +auto Node::unserialize(array_view container, Markup::Node metadata) -> bool { + *this = {}; + if(!metadata.text()) return false; + + name = metadata.text(); + + if(auto node = metadata["timestamp"]) { + timestamps = true; + if(auto created = node["created" ]) timestamp.created = created.text(); + if(auto modified = node["modified"]) timestamp.modified = modified.text(); + if(auto accessed = node["accessed"]) timestamp.accessed = accessed.text(); + } + + if(auto node = metadata["permission"]) { + permissions = true; + if(auto owner = node["owner"]) { + permission.owner.name = owner.text(); + permission.owner.readable = (bool)owner["readable"]; + permission.owner.writable = (bool)owner["writable"]; + permission.owner.executable = (bool)owner["executable"]; + } + if(auto group = node["group"]) { + permission.group.name = group.text(); + permission.group.readable = (bool)group["readable"]; + permission.group.writable = (bool)group["writable"]; + permission.group.executable = (bool)group["executable"]; + } + if(auto other = node["other"]) { + permission.other.readable = (bool)other["readable"]; + permission.other.writable = (bool)other["writable"]; + permission.other.executable = (bool)other["executable"]; + } + } + + if(isPath()) return true; + + uint offset = metadata["offset"].natural(); + uint size = metadata["size"].natural(); + + if(metadata["compression"]) { + size = metadata["compression/size"].natural(); + compression.type = metadata["compression"].text(); + } + + if(offset + size >= container.size()) return false; + + memory.reallocate(size); + nall::memory::copy(memory.data(), container.view(offset, size), size); + return true; +} + +auto Node::compressLZSA() -> bool { + if(!memory) return true; //don't compress empty files + if(isCompressed()) return true; //don't recompress files + + auto compressedMemory = Encode::LZSA(memory); + if(compressedMemory.size() >= memory.size()) return true; //can't compress smaller than original size + + compression.type = "lzsa"; + compression.size = memory.size(); + memory = move(compressedMemory); + return true; +} + +auto Node::decompress() -> bool { + if(!isCompressed()) return true; + + if(compression.type == "lzsa") { + compression = {}; + memory = Decode::LZSA(memory); + return (bool)memory; + } + + return false; +} + +auto Node::getTimestamp(string type) const -> uint64_t { + if(!timestamps) return time(nullptr); + + string value = chrono::utc::datetime(); + if(type == "created" ) value = timestamp.created; + if(type == "modified") value = timestamp.modified; + if(type == "accessed") value = timestamp.accessed; + + #if !defined(PLATFORM_WINDOWS) + struct tm timeInfo{}; + if(strptime(value, "%Y-%m-%d %H:%M:%S", &timeInfo) != nullptr) { + //todo: not thread safe ... + auto tz = getenv("TZ"); + setenv("TZ", "", 1); + timeInfo.tm_isdst = -1; + auto result = mktime(&timeInfo); + if(tz) setenv("TZ", tz, 1); + else unsetenv("TZ"); + if(result != -1) return result; + } + #endif + + return time(nullptr); +} + +auto Node::getPermissions() const -> uint { + if(!permissions) return 0755; + uint mode = 0; + if(permission.owner.readable ) mode |= 0400; + if(permission.owner.writable ) mode |= 0200; + if(permission.owner.executable) mode |= 0100; + if(permission.group.readable ) mode |= 0040; + if(permission.group.writable ) mode |= 0020; + if(permission.group.executable) mode |= 0010; + if(permission.other.readable ) mode |= 0004; + if(permission.other.writable ) mode |= 0002; + if(permission.other.executable) mode |= 0001; + return mode; +} + +auto Node::getOwner() const -> string { + if(!permissions || !permission.owner.name) { + #if !defined(PLATFORM_WINDOWS) + struct passwd* pwd = getpwuid(getuid()); + assert(pwd); + return pwd->pw_name; + #endif + } + return permission.owner.name; +} + +auto Node::getGroup() const -> string { + if(!permissions || !permission.group.name) { + #if !defined(PLATFORM_WINDOWS) + struct group* grp = getgrgid(getgid()); + assert(grp); + return grp->gr_name; + #endif + } + return permission.group.name; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/beat/single/apply.hpp b/waterbox/bsnescore/bsnes/nall/beat/single/apply.hpp new file mode 100644 index 0000000000..cd81762ad9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/beat/single/apply.hpp @@ -0,0 +1,88 @@ +#pragma once + +namespace nall::Beat::Single { + +inline auto apply(array_view source, array_view beat, maybe manifest = {}, maybe result = {}) -> maybe> { + #define error(text) { if(result) *result = {"error: ", text}; return {}; } + #define warning(text) { if(result) *result = {"warning: ", text}; return target; } + #define success() { if(result) *result = ""; return target; } + if(beat.size() < 19) error("beat size mismatch"); + + vector target; + + uint beatOffset = 0; + auto read = [&]() -> uint8_t { + return beat[beatOffset++]; + }; + + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + auto write = [&](uint8_t data) { + target.append(data); + }; + + if(read() != 'B') error("beat header invalid"); + if(read() != 'P') error("beat header invalid"); + if(read() != 'S') error("beat header invalid"); + if(read() != '1') error("beat version mismatch"); + if(decode() != source.size()) error("source size mismatch"); + uint targetSize = decode(); + target.reserve(targetSize); + uint metadataSize = decode(); + for(uint n : range(metadataSize)) { + auto data = read(); + if(manifest) manifest->append((char)data); + } + + enum : uint { SourceRead, TargetRead, SourceCopy, TargetCopy }; + + uint sourceRelativeOffset = 0, targetRelativeOffset = 0; + while(beatOffset < beat.size() - 12) { + uint length = decode(); + uint mode = length & 3; + length = (length >> 2) + 1; + + if(mode == SourceRead) { + while(length--) write(source[target.size()]); + } else if(mode == TargetRead) { + while(length--) write(read()); + } else { + int offset = decode(); + offset = offset & 1 ? -(offset >> 1) : (offset >> 1); + if(mode == SourceCopy) { + sourceRelativeOffset += offset; + while(length--) write(source[sourceRelativeOffset++]); + } else { + targetRelativeOffset += offset; + while(length--) write(target[targetRelativeOffset++]); + } + } + } + + uint32_t sourceHash = 0, targetHash = 0, beatHash = 0; + for(uint shift : range(0, 32, 8)) sourceHash |= read() << shift; + for(uint shift : range(0, 32, 8)) targetHash |= read() << shift; + for(uint shift : range(0, 32, 8)) beatHash |= read() << shift; + + if(target.size() != targetSize) warning("target size mismatch"); + if(sourceHash != Hash::CRC32(source).value()) warning("source hash mismatch"); + if(targetHash != Hash::CRC32(target).value()) warning("target hash mismatch"); + if(beatHash != Hash::CRC32({beat.data(), beat.size() - 4}).value()) warning("beat hash mismatch"); + + success(); + #undef error + #undef warning + #undef success +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/beat/single/create.hpp b/waterbox/bsnescore/bsnes/nall/beat/single/create.hpp new file mode 100644 index 0000000000..940bdfcfb8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/beat/single/create.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include + +namespace nall::Beat::Single { + +inline auto create(array_view source, array_view target, string_view manifest = {}) -> vector { + vector beat; + + auto write = [&](uint8_t data) { + beat.append(data); + }; + + auto encode = [&](uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { write(0x80 | x); break; } + write(x); + data--; + } + }; + + write('B'), write('P'), write('S'), write('1'); + encode(source.size()), encode(target.size()), encode(manifest.size()); + for(auto& byte : manifest) write(byte); + + //generating lrcp() arrays for source requires O(4n) computations, and O(16m) memory, + //but it reduces find() complexity from O(n log m) to O(n + log m). and yet in practice, + //no matter how large n scales to, the O(n + log m) find() is paradoxically slower. + auto sourceArray = SuffixArray(source); + auto targetArray = SuffixArray(target).lpf(); + + enum : uint { SourceRead, TargetRead, SourceCopy, TargetCopy }; + uint outputOffset = 0, sourceRelativeOffset = 0, targetRelativeOffset = 0; + + uint targetReadLength = 0; + auto flush = [&] { + if(!targetReadLength) return; + encode(TargetRead | ((targetReadLength - 1) << 2)); + uint offset = outputOffset - targetReadLength; + while(targetReadLength) write(target[offset++]), targetReadLength--; + }; + + uint overlap = min(source.size(), target.size()); + while(outputOffset < target.size()) { + uint mode = TargetRead, longestLength = 3, longestOffset = 0; + int length = 0, offset = outputOffset; + + while(offset < overlap) { + if(source[offset] != target[offset]) break; + length++, offset++; + } + if(length > longestLength) { + mode = SourceRead, longestLength = length; + } + + sourceArray.find(length, offset, {target.data() + outputOffset, target.size() - outputOffset}); + if(length > longestLength) { + mode = SourceCopy, longestLength = length, longestOffset = offset; + } + + targetArray.previous(length, offset, outputOffset); + if(length > longestLength) { + mode = TargetCopy, longestLength = length, longestOffset = offset; + } + + if(mode == TargetRead) { + targetReadLength++; //queue writes to group sequential commands + outputOffset++; + } else { + flush(); + encode(mode | ((longestLength - 1) << 2)); + if(mode == SourceCopy) { + int relativeOffset = longestOffset - sourceRelativeOffset; + sourceRelativeOffset = longestOffset + longestLength; + encode(relativeOffset < 0 | abs(relativeOffset) << 1); + } + if(mode == TargetCopy) { + int relativeOffset = longestOffset - targetRelativeOffset; + targetRelativeOffset = longestOffset + longestLength; + encode(relativeOffset < 0 | abs(relativeOffset) << 1); + } + outputOffset += longestLength; + } + } + flush(); + + auto sourceHash = Hash::CRC32(source); + for(uint shift : range(0, 32, 8)) write(sourceHash.value() >> shift); + auto targetHash = Hash::CRC32(target); + for(uint shift : range(0, 32, 8)) write(targetHash.value() >> shift); + auto beatHash = Hash::CRC32(beat); + for(uint shift : range(0, 32, 8)) write(beatHash.value() >> shift); + + return beat; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/bit.hpp b/waterbox/bsnescore/bsnes/nall/bit.hpp new file mode 100644 index 0000000000..c56ea4a9dd --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/bit.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include + +namespace nall { + +template inline auto uclamp(const uintmax x) -> uintmax { + enum : uintmax { b = 1ull << (bits - 1), y = b * 2 - 1 }; + return y + ((x - y) & -(x < y)); //min(x, y); +} + +template inline auto uclip(const uintmax x) -> uintmax { + enum : uintmax { b = 1ull << (bits - 1), m = b * 2 - 1 }; + return (x & m); +} + +template inline auto sclamp(const intmax x) -> intmax { + enum : intmax { b = 1ull << (bits - 1), m = b - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; +} + +template inline auto sclip(const intmax x) -> intmax { + enum : uintmax { b = 1ull << (bits - 1), m = b * 2 - 1 }; + return ((x & m) ^ b) - b; +} + +namespace bit { + constexpr inline auto mask(const char* s, uintmax sum = 0) -> uintmax { + return ( + *s == '0' || *s == '1' ? mask(s + 1, (sum << 1) | 1) : + *s == ' ' || *s == '_' ? mask(s + 1, sum) : + *s ? mask(s + 1, sum << 1) : + sum + ); + } + + constexpr inline auto test(const char* s, uintmax sum = 0) -> uintmax { + return ( + *s == '0' || *s == '1' ? test(s + 1, (sum << 1) | (*s - '0')) : + *s == ' ' || *s == '_' ? test(s + 1, sum) : + *s ? test(s + 1, sum << 1) : + sum + ); + } + + //lowest(0b1110) == 0b0010 + constexpr inline auto lowest(const uintmax x) -> uintmax { + return x & -x; + } + + //clear_lowest(0b1110) == 0b1100 + constexpr inline auto clearLowest(const uintmax x) -> uintmax { + return x & (x - 1); + } + + //set_lowest(0b0101) == 0b0111 + constexpr inline auto setLowest(const uintmax x) -> uintmax { + return x | (x + 1); + } + + //count number of bits set in a byte + constexpr inline auto count(uintmax x) -> uint { + uint count = 0; + while(x) x &= x - 1, count++; //clear the least significant bit + return count; + } + + //return index of the first bit set (or zero of no bits are set) + //first(0b1000) == 3 + constexpr inline auto first(uintmax x) -> uint { + uint first = 0; + while(x) { if(x & 1) break; x >>= 1; first++; } + return first; + } + + //round up to next highest single bit: + //round(15) == 16, round(16) == 16, round(17) == 32 + constexpr inline auto round(uintmax x) -> uintmax { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/cd.hpp b/waterbox/bsnescore/bsnes/nall/cd.hpp new file mode 100644 index 0000000000..9fca9abab7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/cd.hpp @@ -0,0 +1,31 @@ +#pragma once + +/* CD-ROM sector functions. + * + * Implemented: + * eight-to-fourteen modulation (encoding and decoding) + * sync header creation and verification + * error detection code creation and verification + * reed-solomon product-code creation and verification + * sector scrambling and descrambling (currently unverified) + * + * Unimplemented: + * reed-solomon product-code correction + * cross-interleave reed-solomon creation, verification, and correction + * CD-ROM XA mode 2 forms 1 & 2 support + * subcode insertion and removal + * subcode decoding from CUE files + * channel frame expansion and reduction + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include diff --git a/waterbox/bsnescore/bsnes/nall/cd/crc16.hpp b/waterbox/bsnescore/bsnes/nall/cd/crc16.hpp new file mode 100644 index 0000000000..d0b34b1cd2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/cd/crc16.hpp @@ -0,0 +1,18 @@ +#pragma once + +//CRC-16/KERMIT + +namespace nall::CD { + +inline auto CRC16(array_view data) -> uint16_t { + uint16_t crc = 0; + while(data) { + crc ^= *data++ << 8; + for(uint bit : range(8)) { + crc = crc << 1 ^ (crc & 0x8000 ? 0x1021 : 0); + } + } + return ~crc; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/cd/edc.hpp b/waterbox/bsnescore/bsnes/nall/cd/edc.hpp new file mode 100644 index 0000000000..bcc645a57c --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/cd/edc.hpp @@ -0,0 +1,65 @@ +#pragma once + +//error detection code + +namespace nall::CD::EDC { + +//polynomial(x) = (x^16 + x^15 + x^2 + 1) * (x^16 + x^2 + x + 1) +inline auto polynomial(uint8_t x) -> uint32_t { + static uint32_t lookup[256]{}; + static bool once = false; + if(!once) { once = true; + for(uint n : range(256)) { + uint32_t edc = n; + for(uint b : range(8)) edc = edc >> 1 ^ (edc & 1 ? 0xd8018001 : 0); + lookup[n] = edc; + } + } + return lookup[x]; +} + +// + +inline auto create(array_view input) -> uint32_t { + uint32_t sum = 0; + for(auto& byte : input) sum = sum >> 8 ^ polynomial(sum ^ byte); + return sum; +} + +inline auto create(array_view input, array_span output) -> bool { + if(output.size() != 4) return false; + auto sum = create(input); + output[0] = sum >> 0; + output[1] = sum >> 8; + output[2] = sum >> 16; + output[3] = sum >> 24; + return true; +} + +inline auto createMode1(array_span sector) -> bool { + if(sector.size() != 2352) return false; + return create({sector, 2064}, {sector + 2064, 4}); +} + +// + +inline auto verify(array_view input, uint32_t edc) -> bool { + return edc == create(input); +} + +inline auto verify(array_view input, array_view compare) -> bool { + if(compare.size() != 4) return false; + auto sum = create(input); + if(compare[0] != uint8_t(sum >> 0)) return false; + if(compare[1] != uint8_t(sum >> 8)) return false; + if(compare[2] != uint8_t(sum >> 16)) return false; + if(compare[3] != uint8_t(sum >> 24)) return false; + return true; +} + +inline auto verifyMode1(array_view sector) -> bool { + if(sector.size() != 2352) return false; + return verify({sector, 2064}, {sector + 2064, 4}); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/cd/efm.hpp b/waterbox/bsnescore/bsnes/nall/cd/efm.hpp new file mode 100644 index 0000000000..ea4896a858 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/cd/efm.hpp @@ -0,0 +1,68 @@ +#pragma once + +//eight-to-fourteen modulation: +//separates each 1-bit by at least two 0-bits and at most ten 0-bits + +namespace nall::CD::EFM { + +//the algorithm to generate this table is unknown +inline auto lookup(uint8_t index) -> uint16_t { + static const uint16_t lookup[256] = { + 0x1220, 0x2100, 0x2420, 0x2220, 0x1100, 0x0110, 0x0420, 0x0900, + 0x1240, 0x2040, 0x2440, 0x2240, 0x1040, 0x0040, 0x0440, 0x0840, + 0x2020, 0x2080, 0x2480, 0x0820, 0x1080, 0x0080, 0x0480, 0x0880, + 0x1210, 0x2010, 0x2410, 0x2210, 0x1010, 0x0210, 0x0410, 0x0810, + 0x0020, 0x2108, 0x0220, 0x0920, 0x1108, 0x0108, 0x1020, 0x0908, + 0x1248, 0x2048, 0x2448, 0x2248, 0x1048, 0x0048, 0x0448, 0x0848, + 0x0100, 0x2088, 0x2488, 0x2110, 0x1088, 0x0088, 0x0488, 0x0888, + 0x1208, 0x2008, 0x2408, 0x2208, 0x1008, 0x0208, 0x0408, 0x0808, + 0x1224, 0x2124, 0x2424, 0x2224, 0x1124, 0x0024, 0x0424, 0x0924, + 0x1244, 0x2044, 0x2444, 0x2244, 0x1044, 0x0044, 0x0444, 0x0844, + 0x2024, 0x2084, 0x2484, 0x0824, 0x1084, 0x0084, 0x0484, 0x0884, + 0x1204, 0x2004, 0x2404, 0x2204, 0x1004, 0x0204, 0x0404, 0x0804, + 0x1222, 0x2122, 0x2422, 0x2222, 0x1122, 0x0022, 0x1024, 0x0922, + 0x1242, 0x2042, 0x2442, 0x2242, 0x1042, 0x0042, 0x0442, 0x0842, + 0x2022, 0x2082, 0x2482, 0x0822, 0x1082, 0x0082, 0x0482, 0x0882, + 0x1202, 0x0248, 0x2402, 0x2202, 0x1002, 0x0202, 0x0402, 0x0802, + 0x1221, 0x2121, 0x2421, 0x2221, 0x1121, 0x0021, 0x0421, 0x0921, + 0x1241, 0x2041, 0x2441, 0x2241, 0x1041, 0x0041, 0x0441, 0x0841, + 0x2021, 0x2081, 0x2481, 0x0821, 0x1081, 0x0081, 0x0481, 0x0881, + 0x1201, 0x2090, 0x2401, 0x2201, 0x1090, 0x0201, 0x0401, 0x0890, + 0x0221, 0x2109, 0x1110, 0x0121, 0x1109, 0x0109, 0x1021, 0x0909, + 0x1249, 0x2049, 0x2449, 0x2249, 0x1049, 0x0049, 0x0449, 0x0849, + 0x0120, 0x2089, 0x2489, 0x0910, 0x1089, 0x0089, 0x0489, 0x0889, + 0x1209, 0x2009, 0x2409, 0x2209, 0x1009, 0x0209, 0x0409, 0x0809, + 0x1120, 0x2111, 0x2490, 0x0224, 0x1111, 0x0111, 0x0490, 0x0911, + 0x0241, 0x2101, 0x0244, 0x0240, 0x1101, 0x0101, 0x0090, 0x0901, + 0x0124, 0x2091, 0x2491, 0x2120, 0x1091, 0x0091, 0x0491, 0x0891, + 0x1211, 0x2011, 0x2411, 0x2211, 0x1011, 0x0211, 0x0411, 0x0811, + 0x1102, 0x0102, 0x2112, 0x0902, 0x1112, 0x0112, 0x1022, 0x0912, + 0x2102, 0x2104, 0x0249, 0x0242, 0x1104, 0x0104, 0x0422, 0x0904, + 0x0122, 0x2092, 0x2492, 0x0222, 0x1092, 0x0092, 0x0492, 0x0892, + 0x1212, 0x2012, 0x2412, 0x2212, 0x1012, 0x0212, 0x0412, 0x0812, + }; + return lookup[index]; +} + +// + +inline auto encode(uint8_t data) -> uint16_t { + return lookup(data); +} + +// + +inline auto decode(uint16_t data) -> maybe { + static uint16_t table[1 << 14]; + static bool once = true; + if(once) { + once = false; + for(uint n : range(1 << 14)) table[n] = 0xffff; + for(uint n : range(1 << 8)) table[lookup(n)] = n; + } + uint16_t result = table[data & 0x3fff]; + if(result == 0xffff) return {}; + return (uint8_t)result; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/cd/rspc.hpp b/waterbox/bsnescore/bsnes/nall/cd/rspc.hpp new file mode 100644 index 0000000000..4f776dd2cd --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/cd/rspc.hpp @@ -0,0 +1,128 @@ +#pragma once + +//reed-solomon product code + +namespace nall::CD::RSPC { + +inline auto encodeP(array_view input, array_span parity) -> bool { + ReedSolomon<26,24> s; + uint lo = 0, hi = 43 * 2; + for(uint x : range(43)) { + for(uint w : range(2)) { //16-bit words + uint z = 0; + for(uint y : range(24)) { + s[z++] = input[(y * 43 + x) * 2 + w]; + } + s.generateParity(); + parity[lo++] = s[z++]; + parity[hi++] = s[z++]; + } + } + return true; +} + +inline auto encodeQ(array_view input, array_span parity) -> bool { + ReedSolomon<45,43> s; + uint lo = 0, hi = 26 * 2; + for(uint y : range(26)) { + for(uint w : range(2)) { + uint z = 0; + for(uint x : range(43)) { + s[z++] = input[((x * 44 + y * 43) * 2 + w) % (26 * 43 * 2)]; + } + s.generateParity(); + parity[lo++] = s[z++]; + parity[hi++] = s[z++]; + } + } + return true; +} + +inline auto encodeMode1(array_span sector) -> bool { + if(sector.size() != 2352) return false; + if(!encodeP({sector + 12, 2064}, {sector + 2076, 172})) return false; + if(!encodeQ({sector + 12, 2236}, {sector + 2248, 104})) return false; + return true; +} + +// + +inline auto decodeP(array_span input, array_span parity) -> int { + bool success = false; + bool failure = false; + ReedSolomon<26,24> s; + uint lo = 0, hi = 43 * 2; + for(uint x : range(43)) { + for(uint w : range(2)) { + uint z = 0; + for(uint y : range(24)) { + s[z++] = input[(y * 43 + x) * 2 + w]; + } + s[z++] = parity[lo++]; + s[z++] = parity[hi++]; + auto count = s.correctErrors(); + if(count < 0) { + failure = true; + } + if(count > 0) { + success = true; + z = 0; + for(uint y : range(24)) { + input[(y * 43 + x) * 2 + w] = s[z++]; + } + parity[lo - 1] = s[z++]; + parity[hi - 1] = s[z++]; + } + } + } + if(!success && !failure) return 0; //no errors remaining + return success ? 1 : -1; //return success even if there are some failures +} + +inline auto decodeQ(array_span input, array_span parity) -> int { + bool success = false; + bool failure = false; + ReedSolomon<45,43> s; + uint lo = 0, hi = 26 * 2; + for(uint y : range(26)) { + for(uint w : range(2)) { + uint z = 0; + for(uint x : range(43)) { + s[z++] = input[((x * 44 + y * 43) * 2 + w) % (26 * 43 * 2)]; + } + s[z++] = parity[lo++]; + s[z++] = parity[hi++]; + auto count = s.correctErrors(); + if(count < 0) { + failure = true; + } + if(count > 0) { + success = true; + z = 0; + for(uint x : range(43)) { + input[((x * 44 + y * 43) * 2 + w) % (26 * 43 * 2)] = s[z++]; + } + parity[lo - 1] = s[z++]; + parity[hi - 1] = s[z++]; + } + } + } + if(!success && !failure) return 0; + return success ? 1 : -1; +} + +inline auto decodeMode1(array_span sector) -> bool { + if(sector.size() != 2352) return false; + //P corrections can allow Q corrections that previously failed to succeed, and vice versa. + //the more iterations, the more chances to correct errors, but the more computationally expensive it is. + //there must be a limit on the amount of retries, or this function may get stuck in an infinite loop. + for(uint attempt : range(4)) { + auto p = decodeP({sector + 12, 2064}, {sector + 2076, 172}); + auto q = decodeQ({sector + 12, 2236}, {sector + 2248, 104}); + if(p == 0 && q == 0) return true; //no errors remaining + if(p < 0 && q < 0) return false; //no more errors correctable + } + return false; //exhausted all retries with errors remaining +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/cd/scrambler.hpp b/waterbox/bsnescore/bsnes/nall/cd/scrambler.hpp new file mode 100644 index 0000000000..cf7380e522 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/cd/scrambler.hpp @@ -0,0 +1,35 @@ +#pragma once + +namespace nall::CD::Scrambler { + +//polynomial(x) = x^15 + x + 1 +inline auto polynomial(uint x) -> uint8_t { + static uint8_t lookup[2340]{}; + static bool once = false; + if(!once) { once = true; + uint16_t shift = 0x0001; + for(uint n : range(2340)) { + lookup[n] = shift; + for(uint b : range(8)) { + bool carry = shift & 1 ^ shift >> 1 & 1; + shift = (carry << 15 | shift) >> 1; + } + } + } + return lookup[x]; +} + +// + +inline auto transform(array_span sector) -> bool { + if(sector.size() == 2352) sector += 12; //header is not scrambled + if(sector.size() != 2340) return false; //F1 frames only + + for(uint index : range(2340)) { + sector[index] ^= polynomial(index); + } + + return true; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/cd/session.hpp b/waterbox/bsnescore/bsnes/nall/cd/session.hpp new file mode 100644 index 0000000000..447592686b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/cd/session.hpp @@ -0,0 +1,478 @@ +#pragma once + +//subchannel processor +//note: this code is not tolerant to subchannel data that violates the Redbook standard + +namespace nall::CD { + +enum : int { InvalidLBA = 100 * 60 * 75 }; + +struct BCD { + static auto encode(uint8_t value) -> uint8_t { return value / 10 << 4 | value % 10; } + static auto decode(uint8_t value) -> uint8_t { return (value >> 4) * 10 + (value & 15); } +}; + +struct MSF { + uint8_t minute; //00-99 + uint8_t second; //00-59 + uint8_t frame = -1; //00-74 + + MSF() = default; + MSF(uint8_t m, uint8_t s, uint8_t f) : minute(m), second(s), frame(f) {} + MSF(int lba) { *this = fromLBA(lba); } + + explicit operator bool() const { + return minute <= 99 && second <= 59 && frame <= 74; + } + + static auto fromBCD(uint8_t minute, uint8_t second, uint8_t frame) -> MSF { + return {BCD::decode(minute), BCD::decode(second), BCD::decode(frame)}; + } + + static auto fromLBA(int lba) -> MSF { + if(lba < 0) lba = 100 * 60 * 75 + lba; + if(lba >= 100 * 60 * 75) return {}; + uint8_t minute = lba / 75 / 60 % 100; + uint8_t second = lba / 75 % 60; + uint8_t frame = lba % 75; + return {minute, second, frame}; + } + + auto toLBA() const -> int { + int lba = minute * 60 * 75 + second * 75 + frame; + if(minute < 90) return lba; + return -(100 * 60 * 75 - lba); + } + + //for debugging purposes + auto toString() const -> string { + if(!operator bool()) return "??:??:??"; + return {pad(minute, 2, '0'), ":", pad(second, 2, '0'), ":", pad(frame, 2, '0')}; + } +}; + +struct Index { + int lba = InvalidLBA; + int end = InvalidLBA; //inclusive range + + explicit operator bool() const { + return lba != InvalidLBA; + } + + auto inRange(int sector) const -> bool { + if(lba == InvalidLBA || end == InvalidLBA) return false; + return sector >= lba && sector <= end; + } +}; + +struct Track { + uint8_t control = 0b1111; //4-bit + uint8_t address = 0b1111; //4-bit + Index indices[100]; + uint8_t firstIndex = -1; + uint8_t lastIndex = -1; + + explicit operator bool() const { + return (bool)indices[1]; + } + + auto emphasis() const -> bool { + return control & 1; + } + + auto copyable() const -> bool { + return control & 2; + } + + auto channels() const -> uint { + if((control & 0b1100) == 0b0000) return 2; + if((control & 0b1100) == 0b1000) return 4; + return 0; //data track or reserved + } + + auto pregap() const -> int { + if(!indices[0] || !indices[1]) return InvalidLBA; + return indices[1].lba - indices[0].lba; + } + + auto isAudio() const -> bool { + return channels() != 0; + } + + auto isData() const -> bool { + return (control & 0b1100) == 0b0100; + } + + auto inIndex(int lba) const -> maybe { + for(uint8_t index : range(100)) { + if(indices[index].inRange(lba)) return index; + } + return {}; + } + + auto inRange(int lba) const -> bool { + if(firstIndex > 99 || lastIndex > 99) return false; + return lba >= indices[firstIndex].lba && lba <= indices[lastIndex].end; + } +}; + +struct Session { + Index leadIn; //00 + Track tracks[100]; //01-99 + Index leadOut; //aa + uint8_t firstTrack = -1; + uint8_t lastTrack = -1; + + auto inLeadIn(int lba) const -> bool { + return lba < 0; + } + + auto inTrack(int lba) const -> maybe { + for(uint8_t trackID : range(100)) { + auto& track = tracks[trackID]; + if(track && track.inRange(lba)) return trackID; + } + return {}; + } + + auto inLeadOut(int lba) const -> bool { + return lba >= leadOut.lba; + } + + auto encode(uint sectors) const -> vector { + if(sectors < abs(leadIn.lba) + leadOut.lba) return {}; //not enough sectors + + vector data; + data.resize(sectors * 96 + 96); //add one sector for P shift + + auto toP = [&](int lba) -> array_span { + //P is encoded one sector later than Q + return {&data[(lba + abs(leadIn.lba) + 1) * 96], 12}; + }; + + auto toQ = [&](int lba) -> array_span { + return {&data[(lba + abs(leadIn.lba)) * 96 + 12], 12}; + }; + + //lead-in + int lba = leadIn.lba; + while(lba < 0) { + //tracks + for(uint trackID : range(100)) { + for(uint repeat : range(3)) { + auto& track = tracks[trackID]; + if(!track) continue; + auto q = toQ(lba); + q[0] = track.control << 4 | track.address << 0; + q[1] = 0x00; + q[2] = BCD::encode(trackID); + auto msf = MSF(lba); + q[3] = BCD::encode(msf.minute); + q[4] = BCD::encode(msf.second); + q[5] = BCD::encode(msf.frame); + q[6] = 0x00; + msf = MSF(track.indices[1].lba); + q[7] = BCD::encode(msf.minute); + q[8] = BCD::encode(msf.second); + q[9] = BCD::encode(msf.frame); + auto crc16 = CRC16({q, 10}); + q[10] = crc16 >> 8; + q[11] = crc16 >> 0; + if(++lba >= 0) break; + }}if( lba >= 0) break; + + //first track + for(uint repeat : range(3)) { + auto q = toQ(lba); + q[0] = 0x01; //control value unverified; address = 1 + q[1] = 0x00; //track# = 00 (TOC) + q[2] = 0xa0; //first track + auto msf = MSF(lba); + q[3] = BCD::encode(msf.minute); + q[4] = BCD::encode(msf.second); + q[5] = BCD::encode(msf.frame); + q[6] = 0x00; + q[7] = BCD::encode(firstTrack); + q[8] = 0x00; + q[9] = 0x00; + auto crc16 = CRC16({q, 10}); + q[10] = crc16 >> 8; + q[11] = crc16 >> 0; + if(++lba >= 0) break; + } if( lba >= 0) break; + + //last track + for(uint repeat : range(3)) { + auto q = toQ(lba); + q[0] = 0x01; + q[1] = 0x00; + q[2] = 0xa1; //last track + auto msf = MSF(lba); + q[3] = BCD::encode(msf.minute); + q[4] = BCD::encode(msf.second); + q[5] = BCD::encode(msf.frame); + q[6] = 0x00; + q[7] = BCD::encode(lastTrack); + q[8] = 0x00; + q[9] = 0x00; + auto crc16 = CRC16({q, 10}); + q[10] = crc16 >> 8; + q[11] = crc16 >> 0; + if(++lba >= 0) break; + } if( lba >= 0) break; + + //lead-out point + for(uint repeat : range(3)) { + auto q = toQ(lba); + q[0] = 0x01; + q[1] = 0x00; + q[2] = 0xa2; //lead-out point + auto msf = MSF(lba); + q[3] = BCD::encode(msf.minute); + q[4] = BCD::encode(msf.second); + q[5] = BCD::encode(msf.frame); + q[6] = 0x00; + msf = MSF(leadOut.lba); + q[7] = BCD::encode(msf.minute); + q[8] = BCD::encode(msf.second); + q[9] = BCD::encode(msf.frame); + auto crc16 = CRC16({q, 10}); + q[10] = crc16 >> 8; + q[11] = crc16 >> 0; + if(++lba >= 0) break; + } if( lba >= 0) break; + } + + //tracks + int end = leadOut.lba; + for(uint8_t trackID : reverse(range(100))) { + auto& track = tracks[trackID]; + if(!track) continue; + + //indices + for(uint8_t indexID : reverse(range(100))) { + auto& index = track.indices[indexID]; + if(!index) continue; + + for(int lba = index.lba; lba < end; lba++) { + auto p = toP(lba); + uint8_t byte = indexID == 0 ? 0xff : 0x00; + for(uint index : range(12)) p[index] = byte; + + auto q = toQ(lba); + q[0] = track.control << 4 | track.address << 0; + q[1] = BCD::encode(trackID); + q[2] = BCD::encode(indexID); + auto msf = MSF(lba - track.indices[1].lba); + q[3] = BCD::encode(msf.minute); + q[4] = BCD::encode(msf.second); + q[5] = BCD::encode(msf.frame); + q[6] = 0x00; + msf = MSF(lba); + q[7] = BCD::encode(msf.minute); + q[8] = BCD::encode(msf.second); + q[9] = BCD::encode(msf.frame); + auto crc16 = CRC16({q, 10}); + q[10] = crc16 >> 8; + q[11] = crc16 >> 0; + } + + end = index.lba; + } + } + + //lead-out + for(int lba : range(sectors - abs(leadIn.lba) - leadOut.lba)) { + auto p = toP(leadOut.lba + lba); + uint8_t byte; + if(lba < 150) { + //2s start (standard specifies 2-3s start) + byte = 0x00; + } else { + //2hz duty cycle; rounded downward (standard specifies 2% tolerance) + byte = (lba - 150) / (75 >> 1) & 1 ? 0xff : 0x00; + } + for(uint index : range(12)) p[index] = byte; + + auto q = toQ(leadOut.lba + lba); + q[0] = 0x01; + q[1] = 0xaa; //lead-out track# + q[2] = 0x01; //lead-out index# + auto msf = MSF(lba); + q[3] = BCD::encode(msf.minute); + q[4] = BCD::encode(msf.second); + q[5] = BCD::encode(msf.frame); + q[6] = 0x00; + msf = MSF(leadOut.lba + lba); + q[7] = BCD::encode(msf.minute); + q[8] = BCD::encode(msf.second); + q[9] = BCD::encode(msf.frame); + auto crc16 = CRC16({q, 10}); + q[10] = crc16 >> 8; + q[11] = crc16 >> 0; + } + + data.resize(data.size() - 96); //remove padding for P shift + return data; + } + + auto decode(array_view data, uint size, uint leadOutSectors = 0) -> bool { + *this = {}; //reset session + //three data[] types supported: subcode Q only, subcode P-W only, data+subcode complete image + if(size != 12 && size != 96 && size != 2448) return false; + + //determine lead-in sector count + for(int lba : range(7500)) { //7500 max sectors scanned + uint offset = lba * size; + if(size == 96) offset += 12; + if(size == 2448) offset += 12 + 2352; + if(offset + 12 > data.size()) break; + auto q = array_view{&data[offset], 12}; + auto crc16 = CRC16({q, 10}); + if(q[10] != uint8_t(crc16 >> 8)) continue; + if(q[11] != uint8_t(crc16 >> 0)) continue; + + uint8_t control = q[0] >> 4; + uint8_t address = q[0] & 15; + uint8_t trackID = q[1]; + if(address != 1) continue; + if(trackID != 0) continue; + + auto msf = MSF::fromBCD(q[3], q[4], q[5]); + leadIn.lba = msf.toLBA() - lba; + break; + } + if(leadIn.lba == InvalidLBA || leadIn.lba >= 0) return false; + + auto toQ = [&](int lba) -> array_view { + uint offset = (lba + abs(leadIn.lba)) * size; + if(size == 96) offset += 12; + if(size == 2448) offset += 12 + 2352; + if(offset + 12 > data.size()) return {}; + return {&data[offset], 12}; + }; + + //lead-in + for(int lba = leadIn.lba; lba < 0; lba++) { + auto q = toQ(lba); + if(!q) break; + auto crc16 = CRC16({q, 10}); + if(q[10] != uint8_t(crc16 >> 8)) continue; + if(q[11] != uint8_t(crc16 >> 0)) continue; + + uint8_t control = q[0] >> 4; + uint8_t address = q[0] & 15; + uint8_t trackID = q[1]; + if(address != 1) continue; + if(trackID != 0) continue; + + trackID = BCD::decode(q[2]); + + if(trackID <= 99) { //00-99 + auto& track = tracks[trackID]; + track.control = control; + track.address = address; + track.indices[1].lba = MSF::fromBCD(q[7], q[8], q[9]).toLBA(); + } + + if(trackID == 100) { //a0 + firstTrack = BCD::decode(q[7]); + } + + if(trackID == 101) { //a1 + lastTrack = BCD::decode(q[7]); + } + + if(trackID == 102) { //a2 + leadOut.lba = MSF::fromBCD(q[7], q[8], q[9]).toLBA(); + } + } + if(leadOut.lba == InvalidLBA) return false; + + //tracks + for(int lba = 0; lba < leadOut.lba; lba++) { + auto q = toQ(lba); + if(!q) break; + auto crc16 = CRC16({q, 10}); + if(q[10] != uint8_t(crc16 >> 8)) continue; + if(q[11] != uint8_t(crc16 >> 0)) continue; + + uint8_t control = q[0] >> 4; + uint8_t address = q[0] & 15; + uint8_t trackID = BCD::decode(q[1]); + uint8_t indexID = BCD::decode(q[2]); + if(address != 1) continue; + if(trackID > 99) continue; + if(indexID > 99) continue; + + auto& track = tracks[trackID]; + if(!track) continue; //track not found? + auto& index = track.indices[indexID]; + if(index) continue; //index already decoded? + + index.lba = MSF::fromBCD(q[7], q[8], q[9]).toLBA(); + } + + synchronize(leadOutSectors); + return true; + } + + //calculates Index::end variables: + //needed for Session::isTrack() and Track::isIndex() to function. + auto synchronize(uint leadOutSectors = 0) -> void { + leadIn.end = -1; + int end = leadOut.lba - 1; + for(uint trackID : reverse(range(100))) { + auto& track = tracks[trackID]; + if(!track) continue; + + for(uint indexID : reverse(range(100))) { + auto& index = track.indices[indexID]; + if(!index) continue; + + index.end = end; + end = index.lba - 1; + } + + for(uint indexID : range(100)) { + auto& index = track.indices[indexID]; + if(index) { track.firstIndex = indexID; break; } + } + + for(uint indexID : reverse(range(100))) { + auto& index = track.indices[indexID]; + if(index) { track.lastIndex = indexID; break; } + } + } + leadOut.end = leadOut.lba + leadOutSectors - 1; + } + + //for diagnostic use only + auto serialize() const -> string { + string s; + s.append("session\n"); + s.append(" leadIn: "); + s.append(MSF(leadIn.lba).toString(), " - ", MSF(leadIn.end).toString(), "\n"); + for(uint trackID : range(100)) { + auto& track = tracks[trackID]; + if(!track) continue; + s.append(" track", pad(trackID, 2, '0')); + if(trackID == firstTrack) s.append(" first"); + if(trackID == lastTrack) s.append( " last"); + s.append("\n"); + s.append(" control: ", binary(track.control, 4, '0'), "\n"); + s.append(" address: ", binary(track.address, 4, '0'), "\n"); + for(uint indexID : range(100)) { + auto& index = track.indices[indexID]; + if(!index) continue; + s.append(" index", pad(indexID, 2, '0'), ": "); + s.append(MSF(index.lba).toString(), " - ", MSF(index.end).toString(), "\n"); + } + } + s.append(" leadout: "); + s.append(MSF(leadOut.lba).toString(), " - ", MSF(leadOut.end).toString(), "\n"); + return s; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/cd/sync.hpp b/waterbox/bsnescore/bsnes/nall/cd/sync.hpp new file mode 100644 index 0000000000..513db3557f --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/cd/sync.hpp @@ -0,0 +1,27 @@ +#pragma once + +namespace nall::CD::Sync { + +inline auto create(array_span sector) -> bool { + if(sector.size() != 12 && sector.size() != 2352) return false; + + for(uint n : range(12)) { + sector[n] = (n == 0 || n == 11) ? 0x00 : 0xff; + } + + return true; +} + +// + +inline auto verify(array_view sector) -> bool { + if(sector.size() != 12 && sector.size() != 2352) return false; + + for(uint n : range(12)) { + if(sector[n] != (n == 0 || n == 11) ? 0x00 : 0xff) return false; + } + + return true; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/chrono.hpp b/waterbox/bsnescore/bsnes/nall/chrono.hpp new file mode 100644 index 0000000000..b9c116220c --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/chrono.hpp @@ -0,0 +1,183 @@ +#pragma once + +#include +#include + +namespace nall::chrono { + +//passage of time functions (from unknown epoch) + +inline auto nanosecond() -> uint64_t { + timespec tv; + clock_gettime(CLOCK_MONOTONIC, &tv); + return tv.tv_sec * 1'000'000'000 + tv.tv_nsec; +} + +inline auto microsecond() -> uint64_t { return nanosecond() / 1'000; } +inline auto millisecond() -> uint64_t { return nanosecond() / 1'000'000; } +inline auto second() -> uint64_t { return nanosecond() / 1'000'000'000; } + +inline auto benchmark(const function& f, uint64_t times = 1) -> void { + auto start = nanosecond(); + while(times--) f(); + auto end = nanosecond(); + print("[chrono::benchmark] ", (double)(end - start) / 1'000'000'000.0, "s\n"); +} + +//exact date/time functions (from system epoch) + +struct timeinfo { + timeinfo( + uint year = 0, uint month = 0, uint day = 0, + uint hour = 0, uint minute = 0, uint second = 0, uint weekday = 0 + ) : year(year), month(month), day(day), + hour(hour), minute(minute), second(second), weekday(weekday) { + } + + inline explicit operator bool() const { return month; } + + uint year; //... + uint month; //1 - 12 + uint day; //1 - 31 + uint hour; //0 - 23 + uint minute; //0 - 59 + uint second; //0 - 60 + uint weekday; //0 - 6 +}; + +inline auto timestamp() -> uint64_t { + return ::time(nullptr); +} + +//0 = failure condition +inline auto timestamp(const string& datetime) -> uint64_t { + static const uint monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + uint64_t timestamp = 0; + if(datetime.match("??????????")) { + return datetime.natural(); + } + if(datetime.match("????*")) { + uint year = datetime.slice(0, 4).natural(); + if(year < 1970 || year > 2199) return 0; + for(uint y = 1970; y < year && y < 2999; y++) { + uint daysInYear = 365; + if(y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) daysInYear++; + timestamp += daysInYear * 24 * 60 * 60; + } + } + if(datetime.match("????-??*")) { + uint y = datetime.slice(0, 4).natural(); + uint month = datetime.slice(5, 2).natural(); + if(month < 1 || month > 12) return 0; + for(uint m = 1; m < month && m < 12; m++) { + uint daysInMonth = monthDays[m - 1]; + if(m == 2 && y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) daysInMonth++; + timestamp += daysInMonth * 24 * 60 * 60; + } + } + if(datetime.match("????-??-??*")) { + uint day = datetime.slice(8, 2).natural(); + if(day < 1 || day > 31) return 0; + timestamp += (day - 1) * 24 * 60 * 60; + } + if(datetime.match("????-??-?? ??*")) { + uint hour = datetime.slice(11, 2).natural(); + if(hour > 23) return 0; + timestamp += hour * 60 * 60; + } + if(datetime.match("????-??-?? ??:??*")) { + uint minute = datetime.slice(14, 2).natural(); + if(minute > 59) return 0; + timestamp += minute * 60; + } + if(datetime.match("????-??-?? ??:??:??*")) { + uint second = datetime.slice(17, 2).natural(); + if(second > 59) return 0; + timestamp += second; + } + return timestamp; +} + +namespace utc { + inline auto timeinfo(uint64_t time = 0) -> chrono::timeinfo { + auto stamp = time ? (time_t)time : (time_t)timestamp(); + auto info = gmtime(&stamp); + return { + (uint)info->tm_year + 1900, + (uint)info->tm_mon + 1, + (uint)info->tm_mday, + (uint)info->tm_hour, + (uint)info->tm_min, + (uint)info->tm_sec, + (uint)info->tm_wday + }; + } + + inline auto year(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).year, 4, '0'); } + inline auto month(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).month, 2, '0'); } + inline auto day(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).day, 2, '0'); } + inline auto hour(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).hour, 2, '0'); } + inline auto minute(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).minute, 2, '0'); } + inline auto second(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).second, 2, '0'); } + + inline auto date(uint64_t timestamp = 0) -> string { + auto t = timeinfo(timestamp); + return {pad(t.year, 4, '0'), "-", pad(t.month, 2, '0'), "-", pad(t.day, 2, '0')}; + } + + inline auto time(uint64_t timestamp = 0) -> string { + auto t = timeinfo(timestamp); + return {pad(t.hour, 2, '0'), ":", pad(t.minute, 2, '0'), ":", pad(t.second, 2, '0')}; + } + + inline auto datetime(uint64_t timestamp = 0) -> string { + auto t = timeinfo(timestamp); + return { + pad(t.year, 4, '0'), "-", pad(t.month, 2, '0'), "-", pad(t.day, 2, '0'), " ", + pad(t.hour, 2, '0'), ":", pad(t.minute, 2, '0'), ":", pad(t.second, 2, '0') + }; + } +} + +namespace local { + inline auto timeinfo(uint64_t time = 0) -> chrono::timeinfo { + auto stamp = time ? (time_t)time : (time_t)timestamp(); + auto info = localtime(&stamp); + return { + (uint)info->tm_year + 1900, + (uint)info->tm_mon + 1, + (uint)info->tm_mday, + (uint)info->tm_hour, + (uint)info->tm_min, + (uint)info->tm_sec, + (uint)info->tm_wday + }; + } + + inline auto year(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).year, 4, '0'); } + inline auto month(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).month, 2, '0'); } + inline auto day(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).day, 2, '0'); } + inline auto hour(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).hour, 2, '0'); } + inline auto minute(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).minute, 2, '0'); } + inline auto second(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).second, 2, '0'); } + + inline auto date(uint64_t timestamp = 0) -> string { + auto t = timeinfo(timestamp); + return {pad(t.year, 4, '0'), "-", pad(t.month, 2, '0'), "-", pad(t.day, 2, '0')}; + } + + inline auto time(uint64_t timestamp = 0) -> string { + auto t = timeinfo(timestamp); + return {pad(t.hour, 2, '0'), ":", pad(t.minute, 2, '0'), ":", pad(t.second, 2, '0')}; + } + + inline auto datetime(uint64_t timestamp = 0) -> string { + auto t = timeinfo(timestamp); + return { + pad(t.year, 4, '0'), "-", pad(t.month, 2, '0'), "-", pad(t.day, 2, '0'), " ", + pad(t.hour, 2, '0'), ":", pad(t.minute, 2, '0'), ":", pad(t.second, 2, '0') + }; + } +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/cipher/chacha20.hpp b/waterbox/bsnescore/bsnes/nall/cipher/chacha20.hpp new file mode 100644 index 0000000000..66b71663ad --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/cipher/chacha20.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include +#include + +namespace nall::Cipher { + +//64-bit nonce; 64-bit x 64-byte (256GB) counter +struct ChaCha20 { + ChaCha20(uint256_t key, uint64_t nonce, uint64_t counter = 0) { + static const uint128_t sigma = 0x6b20657479622d323320646e61707865_u128; //"expand 32-byte k" + + input[ 0] = sigma >> 0; + input[ 1] = sigma >> 32; + input[ 2] = sigma >> 64; + input[ 3] = sigma >> 96; + input[ 4] = key >> 0; + input[ 5] = key >> 32; + input[ 6] = key >> 64; + input[ 7] = key >> 96; + input[ 8] = key >> 128; + input[ 9] = key >> 160; + input[10] = key >> 192; + input[11] = key >> 224; + input[12] = counter >> 0; + input[13] = counter >> 32; + input[14] = nonce >> 0; + input[15] = nonce >> 32; + + offset = 0; + } + + auto encrypt(array_view input) -> vector { + vector output; + while(input) { + if(!offset) { + cipher(); + increment(); + } + auto byte = offset++; + output.append(*input++ ^ (block[byte >> 2] >> (byte & 3) * 8)); + offset &= 63; + } + return output; + } + + auto decrypt(array_view input) -> vector { + return encrypt(input); //reciprocal cipher + } + +//protected: + inline auto rol(uint32_t value, uint bits) -> uint32_t { + return value << bits | value >> 32 - bits; + } + + auto quarterRound(uint32_t x[16], uint a, uint b, uint c, uint d) -> void { + x[a] += x[b]; x[d] = rol(x[d] ^ x[a], 16); + x[c] += x[d]; x[b] = rol(x[b] ^ x[c], 12); + x[a] += x[b]; x[d] = rol(x[d] ^ x[a], 8); + x[c] += x[d]; x[b] = rol(x[b] ^ x[c], 7); + } + + auto cipher() -> void { + memory::copy(block, input, 64); + for(uint n : range(10)) { + quarterRound(block, 0, 4, 8, 12); + quarterRound(block, 1, 5, 9, 13); + quarterRound(block, 2, 6, 10, 14); + quarterRound(block, 3, 7, 11, 15); + quarterRound(block, 0, 5, 10, 15); + quarterRound(block, 1, 6, 11, 12); + quarterRound(block, 2, 7, 8, 13); + quarterRound(block, 3, 4, 9, 14); + } + } + + auto increment() -> void { + for(uint n : range(16)) { + block[n] += input[n]; + } + if(!++input[12]) ++input[13]; + } + + uint32_t input[16]; + uint32_t block[16]; + uint64_t offset; +}; + +struct HChaCha20 : protected ChaCha20 { + HChaCha20(uint256_t key, uint128_t nonce) : ChaCha20(key, nonce >> 64, nonce >> 0) { + cipher(); + } + + auto key() const -> uint256_t { + uint256_t key = 0; + for(uint n : range(4)) key |= (uint256_t)block[ 0 + n] << (n + 0) * 32; + for(uint n : range(4)) key |= (uint256_t)block[12 + n] << (n + 4) * 32; + return key; + } +}; + +//192-bit nonce; 64-bit x 64-byte (256GB) counter +struct XChaCha20 : ChaCha20 { + XChaCha20(uint256_t key, uint192_t nonce, uint64_t counter = 0): + ChaCha20(HChaCha20(key, nonce).key(), nonce >> 128, counter) { + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/counting-sort.hpp b/waterbox/bsnescore/bsnes/nall/counting-sort.hpp new file mode 100644 index 0000000000..e4f7100e4d --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/counting-sort.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace nall { + +//counting sort by powers of two: used to implement radix sort +template +auto counting_sort(T* output, const T* input, uint size) -> void { + static_assert(Bits >= 1 && Bits <= 20, "must be between 1 and 20 bits"); + enum : uint { Base = 1 << Bits, Mask = Base - 1 }; + + uint64_t count[Base] = {}, last = 0; + for(uint n : range(size)) ++count[(input[n] >> Shift) & Mask]; + for(uint n : range(Base)) last += count[n], count[n] = last - count[n]; + for(uint n : range(size)) output[count[(input[n] >> Shift) & Mask]++] = input[n]; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/database/odbc.hpp b/waterbox/bsnescore/bsnes/nall/database/odbc.hpp new file mode 100644 index 0000000000..bd3cba17e9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/database/odbc.hpp @@ -0,0 +1,299 @@ +#pragma once + +//legacy code; no longer used + +#include + +#include +#include +#include + +namespace nall::Database { + +struct ODBC { + struct Statement { + Statement(const Statement& source) = delete; + auto operator=(const Statement& source) -> Statement& = delete; + + Statement(SQLHANDLE statement) : _statement(statement) {} + Statement(Statement&& source) { operator=(move(source)); } + + auto operator=(Statement&& source) -> Statement& { + _statement = source._statement; + _output = source._output; + _values = move(source._values); + source._statement = nullptr; + source._output = 0; + return *this; + } + + auto columns() -> unsigned { + SQLSMALLINT columns = 0; + if(statement()) SQLNumResultCols(statement(), &columns); + return columns; + } + + auto integer(unsigned column) -> int64_t { + if(auto value = _values(column)) return value.get(0); + int64_t value = 0; + SQLGetData(statement(), 1 + column, SQL_C_SBIGINT, &value, 0, nullptr); + _values(column) = (int64_t)value; + return value; + } + + auto natural(unsigned column) -> uint64_t { + if(auto value = _values(column)) return value.get(0); + uint64_t value = 0; + SQLGetData(statement(), 1 + column, SQL_C_UBIGINT, &value, 0, nullptr); + _values(column) = (uint64_t)value; + return value; + } + + auto real(unsigned column) -> double { + if(auto value = _values(column)) return value.get(0.0); + double value = 0.0; + SQLGetData(statement(), 1 + column, SQL_C_DOUBLE, &value, 0, nullptr); + _values(column) = (double)value; + return value; + } + + auto text(unsigned column) -> string { + if(auto value = _values(column)) return value.get({}); + string value; + value.resize(65535); + SQLLEN size = 0; + SQLGetData(statement(), 1 + column, SQL_C_CHAR, value.get(), value.size(), &size); + value.resize(size); + _values(column) = (string)value; + return value; + } + + auto data(unsigned column) -> vector { + if(auto value = _values(column)) return value.get>({}); + vector value; + value.resize(65535); + SQLLEN size = 0; + SQLGetData(statement(), 1 + column, SQL_C_CHAR, value.data(), value.size(), &size); + value.resize(size); + _values(column) = (vector)value; + return value; + } + + auto integer() -> int64_t { return integer(_output++); } + auto natural() -> uint64_t { return natural(_output++); } + auto real() -> double { return real(_output++); } + auto text() -> string { return text(_output++); } + auto data() -> vector { return data(_output++); } + + protected: + virtual auto statement() -> SQLHANDLE { return _statement; } + + SQLHANDLE _statement = nullptr; + unsigned _output = 0; + vector _values; //some ODBC drivers (eg MS-SQL) do not allow the same column to be read more than once + }; + + struct Query : Statement { + Query(const Query& source) = delete; + auto operator=(const Query& source) -> Query& = delete; + + Query(SQLHANDLE statement) : Statement(statement) {} + Query(Query&& source) : Statement(source._statement) { operator=(move(source)); } + + ~Query() { + if(statement()) { + SQLFreeHandle(SQL_HANDLE_STMT, _statement); + _statement = nullptr; + } + } + + auto operator=(Query&& source) -> Query& { + Statement::operator=(move(source)); + _bindings = move(source._bindings); + _result = source._result; + _input = source._input; + _stepped = source._stepped; + source._result = SQL_SUCCESS; + source._input = 0; + source._stepped = false; + return *this; + } + + explicit operator bool() { + //this is likely not the best way to test if the query has returned data ... + //but I wasn't able to find an ODBC API for this seemingly simple task + return statement() && success(); + } + + //ODBC SQLBindParameter only holds pointers to data values + //if the bound paramters go out of scope before the query is executed, binding would reference dangling pointers + //so to work around this, we cache all parameters inside Query until the query is executed + + auto& bind(unsigned column, nullptr_t) { return _bindings.append({column, any{(nullptr_t)nullptr}}), *this; } + auto& bind(unsigned column, int32_t value) { return _bindings.append({column, any{(int32_t)value}}), *this; } + auto& bind(unsigned column, uint32_t value) { return _bindings.append({column, any{(uint32_t)value}}), *this; } + auto& bind(unsigned column, int64_t value) { return _bindings.append({column, any{(int64_t)value}}), *this; } + auto& bind(unsigned column, uint64_t value) { return _bindings.append({column, any{(uint64_t)value}}), *this; } + auto& bind(unsigned column, double value) { return _bindings.append({column, any{(double)value}}), *this; } + auto& bind(unsigned column, const string& value) { return _bindings.append({column, any{(string)value}}), *this; } + auto& bind(unsigned column, const vector& value) { return _bindings.append({column, any{(vector)value}}), *this; } + + auto& bind(nullptr_t) { return bind(_input++, nullptr); } + auto& bind(int32_t value) { return bind(_input++, value); } + auto& bind(uint32_t value) { return bind(_input++, value); } + auto& bind(int64_t value) { return bind(_input++, value); } + auto& bind(uint64_t value) { return bind(_input++, value); } + auto& bind(double value) { return bind(_input++, value); } + auto& bind(const string& value) { return bind(_input++, value); } + auto& bind(const vector& value) { return bind(_input++, value); } + + auto step() -> bool { + if(!_stepped) { + for(auto& binding : _bindings) { + if(binding.value.is()) { + SQLLEN length = SQL_NULL_DATA; + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_NUMERIC, SQL_NUMERIC, 0, 0, nullptr, 0, &length); + } else if(binding.value.is()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_INTEGER, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_INTEGER, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, &binding.value.get(), 0, nullptr); + } else if(binding.value.is()) { + auto& value = binding.value.get(); + SQLLEN length = SQL_NTS; + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, value.size(), 0, (SQLPOINTER)value.data(), 0, &length); + } else if(binding.value.is>()) { + auto& value = binding.value.get>(); + SQLLEN length = value.size(); + SQLBindParameter(_statement, 1 + binding.column, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARBINARY, value.size(), 0, (SQLPOINTER)value.data(), 0, &length); + } + } + + _stepped = true; + _result = SQLExecute(_statement); + if(!success()) return false; + } + + _values.reset(); //clear previous row's cached read results + _result = SQLFetch(_statement); + _output = 0; + return success(); + } + + struct Iterator { + Iterator(Query& query, bool finished) : query(query), finished(finished) {} + auto operator*() -> Statement { return query._statement; } + auto operator!=(const Iterator& source) const -> bool { return finished != source.finished; } + auto operator++() -> Iterator& { finished = !query.step(); return *this; } + + protected: + Query& query; + bool finished = false; + }; + + auto begin() -> Iterator { return Iterator(*this, !step()); } + auto end() -> Iterator { return Iterator(*this, true); } + + private: + auto success() const -> bool { + return _result == SQL_SUCCESS || _result == SQL_SUCCESS_WITH_INFO; + } + + auto statement() -> SQLHANDLE override { + if(!_stepped) step(); + return _statement; + } + + struct Binding { + unsigned column; + any value; + }; + vector _bindings; + + SQLRETURN _result = SQL_SUCCESS; + unsigned _input = 0; + bool _stepped = false; + }; + + ODBC() { + _result = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &_environment); + if(!success()) return; + + SQLSetEnvAttr(_environment, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + } + + ODBC(const string& database, const string& username, const string& password) : ODBC() { + open(database, username, password); + } + + ~ODBC() { + if(_environment) { + close(); + SQLFreeHandle(SQL_HANDLE_ENV, _environment); + _environment = nullptr; + } + } + + explicit operator bool() const { return _connection; } + + auto open(const string& database, const string& username, const string& password) -> bool { + if(!_environment) return false; + close(); + + _result = SQLAllocHandle(SQL_HANDLE_DBC, _environment, &_connection); + if(!success()) return false; + + SQLSetConnectAttr(_connection, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0); + _result = SQLConnectA(_connection, + (SQLCHAR*)database.data(), SQL_NTS, + (SQLCHAR*)username.data(), SQL_NTS, + (SQLCHAR*)password.data(), SQL_NTS + ); + if(!success()) return close(), false; + + return true; + } + + auto close() -> void { + if(_connection) { + SQLDisconnect(_connection); + SQLFreeHandle(SQL_HANDLE_DBC, _connection); + _connection = nullptr; + } + } + + template auto execute(const string& statement, P&&... p) -> Query { + if(!_connection) return {nullptr}; + + SQLHANDLE _statement = nullptr; + _result = SQLAllocHandle(SQL_HANDLE_STMT, _connection, &_statement); + if(!success()) return {nullptr}; + + Query query{_statement}; + _result = SQLPrepareA(_statement, (SQLCHAR*)statement.data(), SQL_NTS); + if(!success()) return {nullptr}; + + bind(query, forward

(p)...); + return query; + } + +private: + auto success() const -> bool { return _result == SQL_SUCCESS || _result == SQL_SUCCESS_WITH_INFO; } + + auto bind(Query&) -> void {} + template auto bind(Query& query, const T& value, P&&... p) -> void { + query.bind(value); + bind(query, forward

(p)...); + } + + SQLHANDLE _environment = nullptr; + SQLHANDLE _connection = nullptr; + SQLRETURN _result = SQL_SUCCESS; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/database/sqlite3.hpp b/waterbox/bsnescore/bsnes/nall/database/sqlite3.hpp new file mode 100644 index 0000000000..10ab87299b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/database/sqlite3.hpp @@ -0,0 +1,218 @@ +#pragma once + +//SQLite3 C++ RAII wrapper for nall +//note: it is safe (no-op) to call sqlite3_* functions on null sqlite3 objects + +#include + +#include +#include + +namespace nall::Database { + +struct SQLite3 { + struct Statement { + Statement(const Statement& source) = delete; + auto operator=(const Statement& source) -> Statement& = delete; + + Statement(sqlite3_stmt* statement) : _statement(statement) {} + Statement(Statement&& source) { operator=(move(source)); } + + auto operator=(Statement&& source) -> Statement& { + _statement = source._statement; + _response = source._response; + _output = source._output; + source._statement = nullptr; + source._response = SQLITE_OK; + source._output = 0; + return *this; + } + + explicit operator bool() { + return sqlite3_data_count(statement()); + } + + auto columns() -> uint { + return sqlite3_column_count(statement()); + } + + auto boolean(uint column) -> bool { + return sqlite3_column_int64(statement(), column) != 0; + } + + auto integer(uint column) -> int64_t { + return sqlite3_column_int64(statement(), column); + } + + auto natural(uint column) -> uint64_t { + return sqlite3_column_int64(statement(), column); + } + + auto real(uint column) -> double { + return sqlite3_column_double(statement(), column); + } + + auto string(uint column) -> nall::string { + nall::string result; + if(auto text = sqlite3_column_text(statement(), column)) { + result.resize(sqlite3_column_bytes(statement(), column)); + memory::copy(result.get(), text, result.size()); + } + return result; + } + + auto data(uint column) -> vector { + vector result; + if(auto data = sqlite3_column_blob(statement(), column)) { + result.resize(sqlite3_column_bytes(statement(), column)); + memory::copy(result.data(), data, result.size()); + } + return result; + } + + auto boolean() -> bool { return boolean(_output++); } + auto integer() -> int64_t { return integer(_output++); } + auto natural() -> uint64_t { return natural(_output++); } + auto real() -> double { return real(_output++); } + auto string() -> nall::string { return string(_output++); } + auto data() -> vector { return data(_output++); } + + protected: + virtual auto statement() -> sqlite3_stmt* { return _statement; } + + sqlite3_stmt* _statement = nullptr; + int _response = SQLITE_OK; + uint _output = 0; + }; + + struct Query : Statement { + Query(const Query& source) = delete; + auto operator=(const Query& source) -> Query& = delete; + + Query(sqlite3_stmt* statement) : Statement(statement) {} + Query(Query&& source) : Statement(source._statement) { operator=(move(source)); } + + ~Query() { + sqlite3_finalize(statement()); + _statement = nullptr; + } + + auto operator=(Query&& source) -> Query& { + _statement = source._statement; + _input = source._input; + source._statement = nullptr; + source._input = 0; + return *this; + } + + auto& bind(uint column, nullptr_t) { sqlite3_bind_null(_statement, 1 + column); return *this; } + auto& bind(uint column, bool value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; } + auto& bind(uint column, int32_t value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; } + auto& bind(uint column, uint32_t value) { sqlite3_bind_int(_statement, 1 + column, value); return *this; } + auto& bind(uint column, int64_t value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; } + auto& bind(uint column, uint64_t value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; } + auto& bind(uint column, intmax value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; } + auto& bind(uint column, uintmax value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; } + auto& bind(uint column, nall::boolean value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; } + auto& bind(uint column, nall::integer value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; } + auto& bind(uint column, nall::natural value) { sqlite3_bind_int64(_statement, 1 + column, value); return *this; } + auto& bind(uint column, double value) { sqlite3_bind_double(_statement, 1 + column, value); return *this; } + auto& bind(uint column, const nall::string& value) { sqlite3_bind_text(_statement, 1 + column, value.data(), value.size(), SQLITE_TRANSIENT); return *this; } + auto& bind(uint column, const vector& value) { sqlite3_bind_blob(_statement, 1 + column, value.data(), value.size(), SQLITE_TRANSIENT); return *this; } + + auto& bind(nullptr_t) { return bind(_input++, nullptr); } + auto& bind(bool value) { return bind(_input++, value); } + auto& bind(int32_t value) { return bind(_input++, value); } + auto& bind(uint32_t value) { return bind(_input++, value); } + auto& bind(int64_t value) { return bind(_input++, value); } + auto& bind(uint64_t value) { return bind(_input++, value); } + auto& bind(intmax value) { return bind(_input++, value); } + auto& bind(uintmax value) { return bind(_input++, value); } + auto& bind(nall::boolean value) { return bind(_input++, value); } + auto& bind(nall::integer value) { return bind(_input++, value); } + auto& bind(nall::natural value) { return bind(_input++, value); } + auto& bind(double value) { return bind(_input++, value); } + auto& bind(const nall::string& value) { return bind(_input++, value); } + auto& bind(const vector& value) { return bind(_input++, value); } + + auto step() -> bool { + _stepped = true; + return sqlite3_step(_statement) == SQLITE_ROW; + } + + struct Iterator { + Iterator(Query& query, bool finished) : query(query), finished(finished) {} + auto operator*() -> Statement { return query._statement; } + auto operator!=(const Iterator& source) const -> bool { return finished != source.finished; } + auto operator++() -> Iterator& { finished = !query.step(); return *this; } + + protected: + Query& query; + bool finished = false; + }; + + auto begin() -> Iterator { return Iterator(*this, !step()); } + auto end() -> Iterator { return Iterator(*this, true); } + + private: + auto statement() -> sqlite3_stmt* override { + if(!_stepped) step(); + return _statement; + } + + uint _input = 0; + bool _stepped = false; + }; + + SQLite3() = default; + SQLite3(const string& filename) { open(filename); } + ~SQLite3() { close(); } + + explicit operator bool() const { return _database; } + + auto open(const string& filename) -> bool { + close(); + sqlite3_open(filename, &_database); + return _database; + } + + auto close() -> void { + sqlite3_close(_database); + _database = nullptr; + } + + template auto execute(const string& statement, P&&... p) -> Query { + if(!_database) return {nullptr}; + + sqlite3_stmt* _statement = nullptr; + sqlite3_prepare_v2(_database, statement.data(), statement.size(), &_statement, nullptr); + if(!_statement) { + if(_debug) print("[sqlite3_prepare_v2] ", sqlite3_errmsg(_database), "\n"); + return {nullptr}; + } + + Query query{_statement}; + bind(query, forward

(p)...); + return query; + } + + auto lastInsertID() const -> uint64_t { + return _database ? sqlite3_last_insert_rowid(_database) : 0; + } + + auto setDebug(bool debug = true) -> void { + _debug = debug; + } + +protected: + auto bind(Query&) -> void {} + template auto bind(Query& query, const T& value, P&&... p) -> void { + query.bind(value); + bind(query, forward

(p)...); + } + + bool _debug = false; + sqlite3* _database = nullptr; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/base.hpp b/waterbox/bsnescore/bsnes/nall/decode/base.hpp new file mode 100644 index 0000000000..d20f1a5e4f --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/base.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace nall::Decode { + +template inline auto Base(const string& value) -> T { + static const string format = + Bits == 2 ? "01" + : Bits == 8 ? "01234567" + : Bits == 10 ? "0123456789" + : Bits == 16 ? "0123456789abcdef" + : Bits == 32 ? "0123456789abcdefghijklmnopqrstuv" + : Bits == 34 ? "023456789abcdefghijkmnopqrstuvwxyz" //1l + : Bits == 36 ? "0123456789abcdefghijklmnopqrstuvwxyz" + : Bits == 57 ? "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" //01IOl + : Bits == 62 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + : Bits == 64 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}" + : Bits == 85 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%()+,-.:;=@[]^_`{|}~" //\ "&'*/<>? + : ""; + static bool initialized = false; + static uint8_t lookup[256] = {0}; + if(!initialized) { + initialized = true; + for(uint n : range(format.size())) { + lookup[format[n]] = n; + } + } + + T result = 0; + for(auto byte : value) { + result = result * Bits + lookup[byte]; + } + return result; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/base64.hpp b/waterbox/bsnescore/bsnes/nall/decode/base64.hpp new file mode 100644 index 0000000000..a613d346ca --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/base64.hpp @@ -0,0 +1,47 @@ +#pragma once + +namespace nall::Decode { + +inline auto Base64(const string& text) -> vector { + static bool initialized = false; + static uint8_t lookup[256] = {0}; + if(!initialized) { + initialized = true; + for(uint n : range(26)) lookup['A' + n] = n; + for(uint n : range(26)) lookup['a' + n] = n + 26; + for(uint n : range(10)) lookup['0' + n] = n + 52; + lookup['+'] = lookup['-'] = 62; + lookup['/'] = lookup['_'] = 63; + } + + vector result; + uint8_t buffer, output; + for(uint n : range(text.size())) { + uint8_t buffer = lookup[text[n]]; + + switch(n & 3) { + case 0: + output = buffer << 2; + break; + + case 1: + result.append(output | buffer >> 4); + output = (buffer & 15) << 4; + break; + + case 2: + result.append(output | buffer >> 2); + output = (buffer & 3) << 6; + break; + + case 3: + result.append(output | buffer); + break; + } + } + + if(text.size() & 3) result.append(output | buffer); + return result; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/bmp.hpp b/waterbox/bsnescore/bsnes/nall/decode/bmp.hpp new file mode 100644 index 0000000000..8aa725a3f0 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/bmp.hpp @@ -0,0 +1,76 @@ +#pragma once + +namespace nall::Decode { + +struct BMP { + BMP() = default; + BMP(const string& filename) { load(filename); } + BMP(const uint8_t* data, uint size) { load(data, size); } + + explicit operator bool() const { return _data; } + + auto reset() -> void { + if(_data) { delete[] _data; _data = nullptr; } + } + + auto data() -> uint32_t* { return _data; } + auto data() const -> const uint32_t* { return _data; } + auto width() const -> uint { return _width; } + auto height() const -> uint { return _height; } + + auto load(const string& filename) -> bool { + auto buffer = file::read(filename); + return load(buffer.data(), buffer.size()); + } + + auto load(const uint8_t* data, uint size) -> bool { + if(size < 0x36) return false; + const uint8_t* p = data; + if(read(p, 2) != 0x4d42) return false; //signature + read(p, 8); + uint offset = read(p, 4); + if(read(p, 4) != 40) return false; //DIB size + int width = read(p, 4); + if(width < 0) return false; + int height = read(p, 4); + bool flip = height < 0; + if(flip) height = -height; + read(p, 2); + uint bitsPerPixel = read(p, 2); + if(bitsPerPixel != 24 && bitsPerPixel != 32) return false; + if(read(p, 4) != 0) return false; //compression type + + _width = width; + _height = height; + _data = new uint32_t[width * height]; + + uint bytesPerPixel = bitsPerPixel / 8; + uint alignedWidth = width * bytesPerPixel; + uint paddingLength = 0; + while(alignedWidth % 4) alignedWidth++, paddingLength++; + + p = data + offset; + for(auto y : range(height)) { + uint32_t* output = flip ? _data + (height - 1 - y) * width : _data + y * width; + for(auto x : range(width)) { + *output++ = read(p, bytesPerPixel) | (bitsPerPixel == 24 ? 255u << 24 : 0); + } + if(paddingLength) read(p, paddingLength); + } + + return true; + } + +private: + uint32_t* _data = nullptr; + uint _width = 0; + uint _height = 0; + + auto read(const uint8_t*& buffer, uint length) -> uintmax { + uintmax result = 0; + for(auto n : range(length)) result |= (uintmax)*buffer++ << (n << 3); + return result; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/bwt.hpp b/waterbox/bsnescore/bsnes/nall/decode/bwt.hpp new file mode 100644 index 0000000000..5aeb1f8cd2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/bwt.hpp @@ -0,0 +1,47 @@ +#pragma once + +//burrows-wheeler transform + +#include + +namespace nall::Decode { + +inline auto BWT(array_view input) -> vector { + vector output; + + uint size = 0; + for(uint byte : range(8)) size |= *input++ << byte * 8; + output.resize(size); + + uint I = 0; + for(uint byte : range(8)) I |= *input++ << byte * 8; + + auto suffixes = SuffixArray(input); + + auto L = input; + auto F = new uint8_t[size]; + for(uint offset : range(size)) F[offset] = L[suffixes[offset + 1]]; + + uint64_t K[256] = {}; + auto C = new int[size]; + for(uint i : range(size)) { + C[i] = K[L[i]]; + K[L[i]]++; + } + + int M[256]; + memory::fill(M, 256, -1); + for(uint i : range(size)) { + if(M[F[i]] == -1) M[F[i]] = i; + } + + uint i = I; + for(uint j : reverse(range(size))) { + output[j] = L[i]; + i = C[i] + M[L[i]]; + } + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/gzip.hpp b/waterbox/bsnescore/bsnes/nall/decode/gzip.hpp new file mode 100644 index 0000000000..20f754cd76 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/gzip.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +namespace nall::Decode { + +struct GZIP { + inline ~GZIP(); + + inline auto decompress(const string& filename) -> bool; + inline auto decompress(const uint8_t* data, uint size) -> bool; + + string filename; + uint8_t* data = nullptr; + uint size = 0; +}; + +GZIP::~GZIP() { + if(data) delete[] data; +} + +auto GZIP::decompress(const string& filename) -> bool { + if(auto memory = file::read(filename)) { + return decompress(memory.data(), memory.size()); + } + return false; +} + +auto GZIP::decompress(const uint8_t* data, uint size) -> bool { + if(size < 18) return false; + if(data[0] != 0x1f) return false; + if(data[1] != 0x8b) return false; + uint cm = data[2]; + uint flg = data[3]; + uint mtime = data[4]; + mtime |= data[5] << 8; + mtime |= data[6] << 16; + mtime |= data[7] << 24; + uint xfl = data[8]; + uint os = data[9]; + uint p = 10; + uint isize = data[size - 4]; + isize |= data[size - 3] << 8; + isize |= data[size - 2] << 16; + isize |= data[size - 1] << 24; + filename = ""; + + if(flg & 0x04) { //FEXTRA + uint xlen = data[p + 0]; + xlen |= data[p + 1] << 8; + p += 2 + xlen; + } + + if(flg & 0x08) { //FNAME + char buffer[PATH_MAX]; + for(uint n = 0; n < PATH_MAX; n++, p++) { + buffer[n] = data[p]; + if(data[p] == 0) break; + } + if(data[p++]) return false; + filename = buffer; + } + + if(flg & 0x10) { //FCOMMENT + while(data[p++]); + } + + if(flg & 0x02) { //FHCRC + p += 2; + } + + this->size = isize; + this->data = new uint8_t[this->size]; + return inflate(this->data, this->size, data + p, size - p - 8); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/html.hpp b/waterbox/bsnescore/bsnes/nall/decode/html.hpp new file mode 100644 index 0000000000..ccec3e8dea --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/html.hpp @@ -0,0 +1,40 @@ +#pragma once + +namespace nall::Decode { + +inline auto HTML(const string& input) -> string { + string output; + for(uint n = 0; n < input.size();) { + if(input[n] == '&') { + if(input(n + 1) == 'a' && input(n + 2) == 'm' && input(n + 3) == 'p' && input(n + 4) == ';') { + output.append('&'); + n += 5; + continue; + } + if(input(n + 1) == 'l' && input(n + 2) == 't' && input(n + 3) == ';') { + output.append('<'); + n += 4; + continue; + } + if(input(n + 1) == 'g' && input(n + 2) == 't' && input(n + 3) == ';') { + output.append('>'); + n += 4; + continue; + } + if(input(n + 1) == 'q' && input(n + 2) == 'u' && input(n + 3) == 'o' && input(n + 4) == 't' && input(n + 5) == ';') { + output.append('"'); + n += 6; + continue; + } + if(input(n + 1) == 'a' && input(n + 2) == 'p' && input(n + 3) == 'o' && input(n + 4) == 's' && input(n + 5) == ';') { + output.append('\''); + n += 6; + continue; + } + } + output.append(input[n++]); + } + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/huffman.hpp b/waterbox/bsnescore/bsnes/nall/decode/huffman.hpp new file mode 100644 index 0000000000..cb8c3929d6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/huffman.hpp @@ -0,0 +1,36 @@ +#pragma once + +namespace nall::Decode { + +inline auto Huffman(array_view input) -> vector { + vector output; + + uint size = 0; + for(uint byte : range(8)) size |= *input++ << byte * 8; + output.reserve(size); + + uint byte = 0, bits = 0; + auto read = [&]() -> bool { + if(bits == 0) bits = 8, byte = *input++; + return byte >> --bits & 1; + }; + + uint nodes[256][2] = {}; + for(uint offset : range(256)) { + for(uint index : range(9)) nodes[offset][0] = nodes[offset][0] << 1 | read(); + for(uint index : range(9)) nodes[offset][1] = nodes[offset][1] << 1 | read(); + } + + uint node = 511; + while(output.size() < size) { + node = nodes[node - 256][read()]; + if(node < 256) { + output.append(node); + node = 511; + } + } + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/inflate.hpp b/waterbox/bsnescore/bsnes/nall/decode/inflate.hpp new file mode 100644 index 0000000000..0f659b709a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/inflate.hpp @@ -0,0 +1,349 @@ +#pragma once + +//a bad implementation of inflate from zlib/minizip +//todo: replace with Talarubi's version + +#include + +namespace nall::Decode { + +namespace puff { + inline auto puff( + unsigned char* dest, unsigned long* destlen, + unsigned char* source, unsigned long* sourcelen + ) -> int; +} + +inline auto inflate( + uint8_t* target, uint targetLength, + const uint8_t* source, uint sourceLength +) -> bool { + unsigned long tl = targetLength, sl = sourceLength; + int result = puff::puff((unsigned char*)target, &tl, (unsigned char*)source, &sl); + return result == 0; +} + +namespace puff { + +enum : uint { + MAXBITS = 15, + MAXLCODES = 286, + MAXDCODES = 30, + FIXLCODES = 288, + MAXCODES = MAXLCODES + MAXDCODES, +}; + +struct state { + unsigned char* out; + unsigned long outlen; + unsigned long outcnt; + + unsigned char* in; + unsigned long inlen; + unsigned long incnt; + int bitbuf; + int bitcnt; + + jmp_buf env; +}; + +struct huffman { + short* count; + short* symbol; +}; + +inline auto bits(state* s, int need) -> int { + long val; + + val = s->bitbuf; + while(s->bitcnt < need) { + if(s->incnt == s->inlen) longjmp(s->env, 1); + val |= (long)(s->in[s->incnt++]) << s->bitcnt; + s->bitcnt += 8; + } + + s->bitbuf = (int)(val >> need); + s->bitcnt -= need; + + return (int)(val & ((1L << need) - 1)); +} + +inline auto stored(state* s) -> int { + uint len; + + s->bitbuf = 0; + s->bitcnt = 0; + + if(s->incnt + 4 > s->inlen) return 2; + len = s->in[s->incnt++]; + len |= s->in[s->incnt++] << 8; + if(s->in[s->incnt++] != (~len & 0xff) || + s->in[s->incnt++] != ((~len >> 8) & 0xff) + ) return 2; + + if(s->incnt + len > s->inlen) return 2; + if(s->out != nullptr) { + if(s->outcnt + len > s->outlen) return 1; + while(len--) s->out[s->outcnt++] = s->in[s->incnt++]; + } else { + s->outcnt += len; + s->incnt += len; + } + + return 0; +} + +inline auto decode(state* s, huffman* h) -> int { + int len, code, first, count, index, bitbuf, left; + short* next; + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while(true) { + while(left--) { + code |= bitbuf & 1; + bitbuf >>= 1; + count = *next++; + if(code - count < first) { + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS + 1) - len; + if(left == 0) break; + if(s->incnt == s->inlen) longjmp(s->env, 1); + bitbuf = s->in[s->incnt++]; + if(left > 8) left = 8; + } + + return -10; +} + +inline auto construct(huffman* h, short* length, int n) -> int { + int symbol, len, left; + short offs[MAXBITS + 1]; + + for(len = 0; len <= MAXBITS; len++) h->count[len] = 0; + for(symbol = 0; symbol < n; symbol++) h->count[length[symbol]]++; + if(h->count[0] == n) return 0; + + left = 1; + for(len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= h->count[len]; + if(left < 0) return left; + } + + offs[1] = 0; + for(len = 1; len < MAXBITS; len++) offs[len + 1] = offs[len] + h->count[len]; + + for(symbol = 0; symbol < n; symbol++) { + if(length[symbol] != 0) h->symbol[offs[length[symbol]]++] = symbol; + } + + return left; +} + +inline auto codes(state* s, huffman* lencode, huffman* distcode) -> int { + int symbol, len; + uint dist; + static const short lens[29] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 + }; + static const short lext[29] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + }; + static const short dists[30] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + static const short dext[30] = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13 + }; + + do { + symbol = decode(s, lencode); + if(symbol < 0) return symbol; + if(symbol < 256) { + if(s->out != nullptr) { + if(s->outcnt == s->outlen) return 1; + s->out[s->outcnt] = symbol; + } + s->outcnt++; + } else if(symbol > 256) { + symbol -= 257; + if(symbol >= 29) return -10; + len = lens[symbol] + bits(s, lext[symbol]); + + symbol = decode(s, distcode); + if(symbol < 0) return symbol; + dist = dists[symbol] + bits(s, dext[symbol]); + #ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR + if(dist > s->outcnt) return -11; + #endif + + if(s->out != nullptr) { + if(s->outcnt + len > s->outlen) return 1; + while(len--) { + s->out[s->outcnt] = + #ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR + dist > s->outcnt ? 0 : + #endif + s->out[s->outcnt - dist]; + s->outcnt++; + } + } else { + s->outcnt += len; + } + } + } while(symbol != 256); + + return 0; +} + +inline auto fixed(state* s) -> int { + static int virgin = 1; + static short lencnt[MAXBITS + 1], lensym[FIXLCODES]; + static short distcnt[MAXBITS + 1], distsym[MAXDCODES]; + static huffman lencode, distcode; + + if(virgin) { + int symbol = 0; + short lengths[FIXLCODES]; + + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + for(; symbol < 144; symbol++) lengths[symbol] = 8; + for(; symbol < 256; symbol++) lengths[symbol] = 9; + for(; symbol < 280; symbol++) lengths[symbol] = 7; + for(; symbol < FIXLCODES; symbol++) lengths[symbol] = 8; + construct(&lencode, lengths, FIXLCODES); + + for(symbol = 0; symbol < MAXDCODES; symbol++) lengths[symbol] = 5; + construct(&distcode, lengths, MAXDCODES); + + virgin = 0; + } + + return codes(s, &lencode, &distcode); +} + +inline auto dynamic(state* s) -> int { + int nlen, ndist, ncode, index, err; + short lengths[MAXCODES]; + short lencnt[MAXBITS + 1], lensym[MAXLCODES]; + short distcnt[MAXBITS + 1], distsym[MAXDCODES]; + huffman lencode, distcode; + static const short order[19] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + nlen = bits(s, 5) + 257; + ndist = bits(s, 5) + 1; + ncode = bits(s, 4) + 4; + if(nlen > MAXLCODES || ndist > MAXDCODES) return -3; + + for(index = 0; index < ncode; index++) lengths[order[index]] = bits(s, 3); + for(; index < 19; index++) lengths[order[index]] = 0; + + err = construct(&lencode, lengths, 19); + if(err != 0) return -4; + + index = 0; + while(index < nlen + ndist) { + int symbol, len; + + symbol = decode(s, &lencode); + if(symbol < 16) { + lengths[index++] = symbol; + } else { + len = 0; + if(symbol == 16) { + if(index == 0) return -5; + len = lengths[index - 1]; + symbol = 3 + bits(s, 2); + } else if(symbol == 17) { + symbol = 3 + bits(s, 3); + } else { + symbol = 11 + bits(s, 7); + } + if(index + symbol > nlen + ndist) return -6; + while(symbol--) lengths[index++] = len; + } + } + + if(lengths[256] == 0) return -9; + + err = construct(&lencode, lengths, nlen); + if(err < 0 || (err > 0 && nlen - lencode.count[0] != 1)) return -7; + + err = construct(&distcode, lengths + nlen, ndist); + if(err < 0 || (err > 0 && ndist - distcode.count[0] != 1)) return -8; + + return codes(s, &lencode, &distcode); +} + +inline auto puff( + unsigned char* dest, unsigned long* destlen, + unsigned char* source, unsigned long* sourcelen +) -> int { + state s; + int last, type, err; + + s.out = dest; + s.outlen = *destlen; + s.outcnt = 0; + + s.in = source; + s.inlen = *sourcelen; + s.incnt = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + if(setjmp(s.env) != 0) { + err = 2; + } else { + do { + last = bits(&s, 1); + type = bits(&s, 2); + err = type == 0 ? stored(&s) + : type == 1 ? fixed(&s) + : type == 2 ? dynamic(&s) + : -1; + if(err != 0) break; + } while(!last); + } + + if(err <= 0) { + *destlen = s.outcnt; + *sourcelen = s.incnt; + } + + return err; +} + +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/lzsa.hpp b/waterbox/bsnescore/bsnes/nall/decode/lzsa.hpp new file mode 100644 index 0000000000..a0e9c9c440 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/lzsa.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include + +namespace nall::Decode { + +inline auto LZSA(array_view input) -> vector { + vector output; + uint index = 0; + + uint size = 0; + for(uint byte : range(8)) size |= *input++ << byte * 8; + output.resize(size); + + auto load = [&]() -> vector { + uint size = 0; + for(uint byte : range(8)) size |= *input++ << byte * 8; + vector buffer; + buffer.reserve(size); + while(size--) buffer.append(*input++); + return buffer; + }; + + auto flags = Decode::Huffman(load()); + auto literals = Decode::Huffman(load()); + auto lengths = Decode::Huffman(load()); + auto offsets = Decode::Huffman(load()); + + auto flagData = flags.data(); + uint byte = 0, bits = 0; + auto flagRead = [&]() -> bool { + if(bits == 0) bits = 8, byte = *flagData++; + return byte >> --bits & 1; + }; + + auto literalData = literals.data(); + auto literalRead = [&]() -> uint8_t { + return *literalData++; + }; + + auto lengthData = lengths.data(); + auto lengthRead = [&]() -> uint64_t { + uint byte = *lengthData++, bytes = 1; + while(!(byte & 1)) byte >>= 1, bytes++; + uint length = byte >> 1, shift = 8 - bytes; + while(--bytes) length |= *lengthData++ << shift, shift += 8; + return length; + }; + + auto offsetData = offsets.data(); + auto offsetRead = [&]() -> uint { + uint offset = 0; + offset |= *offsetData++ << 0; if(index < 1 << 8) return offset; + offset |= *offsetData++ << 8; if(index < 1 << 16) return offset; + offset |= *offsetData++ << 16; if(index < 1 << 24) return offset; + offset |= *offsetData++ << 24; return offset; + }; + + while(index < size) { + if(!flagRead()) { + output[index++] = literalRead(); + } else { + uint length = lengthRead() + 6; + uint offset = index - offsetRead(); + while(length--) output[index++] = output[offset++]; + } + } + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/mtf.hpp b/waterbox/bsnescore/bsnes/nall/decode/mtf.hpp new file mode 100644 index 0000000000..d70e16834b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/mtf.hpp @@ -0,0 +1,25 @@ +#pragma once + +//move to front + +namespace nall::Decode { + +inline auto MTF(array_view input) -> vector { + vector output; + output.resize(input.size()); + + uint8_t order[256]; + for(uint n : range(256)) order[n] = n; + + for(uint offset : range(input.size())) { + uint data = input[offset]; + uint value = order[data]; + output[offset] = value; + memory::move(&order[1], &order[0], data); + order[0] = value; + } + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/png.hpp b/waterbox/bsnescore/bsnes/nall/decode/png.hpp new file mode 100644 index 0000000000..f5c2eeda24 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/png.hpp @@ -0,0 +1,332 @@ +#pragma once + +#include +#include + +namespace nall::Decode { + +struct PNG { + inline PNG(); + inline ~PNG(); + + inline auto load(const string& filename) -> bool; + inline auto load(const uint8_t* sourceData, uint sourceSize) -> bool; + inline auto readbits(const uint8_t*& data) -> uint; + + struct Info { + uint width; + uint height; + uint bitDepth; + //colorType: + //0 = L (luma) + //2 = R,G,B + //3 = P (palette) + //4 = L,A + //6 = R,G,B,A + uint colorType; + uint compressionMethod; + uint filterType; + uint interlaceMethod; + + uint bytesPerPixel; + uint pitch; + + uint8_t palette[256][3]; + } info; + + uint8_t* data = nullptr; + uint size = 0; + + uint bitpos = 0; + +protected: + enum class FourCC : uint { + IHDR = 0x49484452, + PLTE = 0x504c5445, + IDAT = 0x49444154, + IEND = 0x49454e44, + }; + + inline auto interlace(uint pass, uint index) -> uint; + inline auto inflateSize() -> uint; + inline auto deinterlace(const uint8_t*& inputData, uint pass) -> bool; + inline auto filter(uint8_t* outputData, const uint8_t* inputData, uint width, uint height) -> bool; + inline auto read(const uint8_t* data, uint length) -> uint; +}; + +PNG::PNG() { +} + +PNG::~PNG() { + if(data) delete[] data; +} + +auto PNG::load(const string& filename) -> bool { + if(auto memory = file::read(filename)) { + return load(memory.data(), memory.size()); + } + return false; +} + +auto PNG::load(const uint8_t* sourceData, uint sourceSize) -> bool { + if(sourceSize < 8) return false; + if(read(sourceData + 0, 4) != 0x89504e47) return false; + if(read(sourceData + 4, 4) != 0x0d0a1a0a) return false; + + uint8_t* compressedData = nullptr; + uint compressedSize = 0; + + uint offset = 8; + while(offset < sourceSize) { + uint length = read(sourceData + offset + 0, 4); + uint fourCC = read(sourceData + offset + 4, 4); + uint checksum = read(sourceData + offset + 8 + length, 4); + + if(fourCC == (uint)FourCC::IHDR) { + info.width = read(sourceData + offset + 8, 4); + info.height = read(sourceData + offset + 12, 4); + info.bitDepth = read(sourceData + offset + 16, 1); + info.colorType = read(sourceData + offset + 17, 1); + info.compressionMethod = read(sourceData + offset + 18, 1); + info.filterType = read(sourceData + offset + 19, 1); + info.interlaceMethod = read(sourceData + offset + 20, 1); + + if(info.bitDepth == 0 || info.bitDepth > 16) return false; + if(info.bitDepth & (info.bitDepth - 1)) return false; //not a power of two + if(info.compressionMethod != 0) return false; + if(info.filterType != 0) return false; + if(info.interlaceMethod != 0 && info.interlaceMethod != 1) return false; + + switch(info.colorType) { + case 0: info.bytesPerPixel = info.bitDepth * 1; break; //L + case 2: info.bytesPerPixel = info.bitDepth * 3; break; //R,G,B + case 3: info.bytesPerPixel = info.bitDepth * 1; break; //P + case 4: info.bytesPerPixel = info.bitDepth * 2; break; //L,A + case 6: info.bytesPerPixel = info.bitDepth * 4; break; //R,G,B,A + default: return false; + } + + if(info.colorType == 2 || info.colorType == 4 || info.colorType == 6) { + if(info.bitDepth != 8 && info.bitDepth != 16) return false; + } + if(info.colorType == 3 && info.bitDepth == 16) return false; + + info.bytesPerPixel = (info.bytesPerPixel + 7) / 8; + info.pitch = (int)info.width * info.bytesPerPixel; + } + + if(fourCC == (uint)FourCC::PLTE) { + if(length % 3) return false; + for(uint n = 0, p = offset + 8; n < length / 3; n++) { + info.palette[n][0] = sourceData[p++]; + info.palette[n][1] = sourceData[p++]; + info.palette[n][2] = sourceData[p++]; + } + } + + if(fourCC == (uint)FourCC::IDAT) { + compressedData = (uint8_t*)realloc(compressedData, compressedSize + length); + memcpy(compressedData + compressedSize, sourceData + offset + 8, length); + compressedSize += length; + } + + if(fourCC == (uint)FourCC::IEND) { + break; + } + + offset += 4 + 4 + length + 4; + } + + uint interlacedSize = inflateSize(); + auto interlacedData = new uint8_t[interlacedSize]; + + bool result = inflate(interlacedData, interlacedSize, compressedData + 2, compressedSize - 6); + free(compressedData); + + if(result == false) { + delete[] interlacedData; + return false; + } + + size = info.width * info.height * info.bytesPerPixel; + data = new uint8_t[size]; + + if(info.interlaceMethod == 0) { + if(filter(data, interlacedData, info.width, info.height) == false) { + delete[] interlacedData; + delete[] data; + data = nullptr; + return false; + } + } else { + const uint8_t* passData = interlacedData; + for(uint pass = 0; pass < 7; pass++) { + if(deinterlace(passData, pass) == false) { + delete[] interlacedData; + delete[] data; + data = nullptr; + return false; + } + } + } + + delete[] interlacedData; + return true; +} + +auto PNG::interlace(uint pass, uint index) -> uint { + static const uint data[7][4] = { + //x-distance, y-distance, x-origin, y-origin + {8, 8, 0, 0}, + {8, 8, 4, 0}, + {4, 8, 0, 4}, + {4, 4, 2, 0}, + {2, 4, 0, 2}, + {2, 2, 1, 0}, + {1, 2, 0, 1}, + }; + return data[pass][index]; +} + +auto PNG::inflateSize() -> uint { + if(info.interlaceMethod == 0) { + return info.width * info.height * info.bytesPerPixel + info.height; + } + + uint size = 0; + for(uint pass = 0; pass < 7; pass++) { + uint xd = interlace(pass, 0), yd = interlace(pass, 1); + uint xo = interlace(pass, 2), yo = interlace(pass, 3); + uint width = (info.width + (xd - xo - 1)) / xd; + uint height = (info.height + (yd - yo - 1)) / yd; + if(width == 0 || height == 0) continue; + size += width * height * info.bytesPerPixel + height; + } + return size; +} + +auto PNG::deinterlace(const uint8_t*& inputData, uint pass) -> bool { + uint xd = interlace(pass, 0), yd = interlace(pass, 1); + uint xo = interlace(pass, 2), yo = interlace(pass, 3); + uint width = (info.width + (xd - xo - 1)) / xd; + uint height = (info.height + (yd - yo - 1)) / yd; + if(width == 0 || height == 0) return true; + + uint outputSize = width * height * info.bytesPerPixel; + auto outputData = new uint8_t[outputSize]; + bool result = filter(outputData, inputData, width, height); + + const uint8_t* rd = outputData; + for(uint y = yo; y < info.height; y += yd) { + uint8_t* wr = data + y * info.pitch; + for(uint x = xo; x < info.width; x += xd) { + for(uint b = 0; b < info.bytesPerPixel; b++) { + wr[x * info.bytesPerPixel + b] = *rd++; + } + } + } + + inputData += outputSize + height; + delete[] outputData; + return result; +} + +auto PNG::filter(uint8_t* outputData, const uint8_t* inputData, uint width, uint height) -> bool { + uint8_t* wr = outputData; + const uint8_t* rd = inputData; + int bpp = info.bytesPerPixel, pitch = width * bpp; + for(int y = 0; y < height; y++) { + uint8_t filter = *rd++; + + switch(filter) { + case 0x00: //None + for(int x = 0; x < pitch; x++) { + wr[x] = rd[x]; + } + break; + + case 0x01: //Subtract + for(int x = 0; x < pitch; x++) { + wr[x] = rd[x] + (x - bpp < 0 ? 0 : wr[x - bpp]); + } + break; + + case 0x02: //Above + for(int x = 0; x < pitch; x++) { + wr[x] = rd[x] + (y - 1 < 0 ? 0 : wr[x - pitch]); + } + break; + + case 0x03: //Average + for(int x = 0; x < pitch; x++) { + short a = x - bpp < 0 ? 0 : wr[x - bpp]; + short b = y - 1 < 0 ? 0 : wr[x - pitch]; + + wr[x] = rd[x] + (uint8_t)((a + b) / 2); + } + break; + + case 0x04: //Paeth + for(int x = 0; x < pitch; x++) { + short a = x - bpp < 0 ? 0 : wr[x - bpp]; + short b = y - 1 < 0 ? 0 : wr[x - pitch]; + short c = x - bpp < 0 || y - 1 < 0 ? 0 : wr[x - pitch - bpp]; + + short p = a + b - c; + short pa = p > a ? p - a : a - p; + short pb = p > b ? p - b : b - p; + short pc = p > c ? p - c : c - p; + + auto paeth = (uint8_t)((pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c); + + wr[x] = rd[x] + paeth; + } + break; + + default: //Invalid + return false; + } + + rd += pitch; + wr += pitch; + } + + return true; +} + +auto PNG::read(const uint8_t* data, uint length) -> uint { + uint result = 0; + while(length--) result = (result << 8) | (*data++); + return result; +} + +auto PNG::readbits(const uint8_t*& data) -> uint { + uint result = 0; + switch(info.bitDepth) { + case 1: + result = (*data >> bitpos) & 1; + bitpos++; + if(bitpos == 8) { data++; bitpos = 0; } + break; + case 2: + result = (*data >> bitpos) & 3; + bitpos += 2; + if(bitpos == 8) { data++; bitpos = 0; } + break; + case 4: + result = (*data >> bitpos) & 15; + bitpos += 4; + if(bitpos == 8) { data++; bitpos = 0; } + break; + case 8: + result = *data++; + break; + case 16: + result = (data[0] << 8) | (data[1] << 0); + data += 2; + break; + } + return result; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/rle.hpp b/waterbox/bsnescore/bsnes/nall/decode/rle.hpp new file mode 100644 index 0000000000..4a6c67ca97 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/rle.hpp @@ -0,0 +1,44 @@ +#pragma once + +namespace nall::Decode { + +template //S = word size; M = match length +inline auto RLE(array_view input) -> vector { + vector output; + + auto load = [&]() -> uint8_t { + return input ? *input++ : 0; + }; + + uint base = 0; + uint64_t size = 0; + for(uint byte : range(8)) size |= load() << byte * 8; + output.resize(size); + + auto read = [&]() -> uint64_t { + uint64_t value = 0; + for(uint byte : range(S)) value |= load() << byte * 8; + return value; + }; + + auto write = [&](uint64_t value) -> void { + if(base >= size) return; + for(uint byte : range(S)) output[base++] = value >> byte * 8; + }; + + while(base < size) { + auto byte = load(); + if(byte < 128) { + byte++; + while(byte--) write(read()); + } else { + auto value = read(); + byte = (byte & 127) + M; + while(byte--) write(value); + } + } + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/url.hpp b/waterbox/bsnescore/bsnes/nall/decode/url.hpp new file mode 100644 index 0000000000..56526c928e --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/url.hpp @@ -0,0 +1,39 @@ +#pragma once + +namespace nall::Decode { + +//returns empty string on malformed content +inline auto URL(string_view input) -> string { + string output; + for(uint n = 0; n < input.size();) { + char c = input[n]; + + //unreserved characters + if(c >= 'A' && c <= 'Z') { output.append(c); n++; continue; } + if(c >= 'a' && c <= 'z') { output.append(c); n++; continue; } + if(c >= '0' && c <= '9') { output.append(c); n++; continue; } + if(c == '-' || c == '_' || c == '.' || c == '~') { output.append(c); n++; continue; } + + //special characters + if(c == '+') { output.append(' '); n++; continue; } + + //reserved characters + if(c != '%' || n + 2 >= input.size()) return ""; + char hi = input[n + 1]; + char lo = input[n + 2]; + if(hi >= '0' && hi <= '9') hi -= '0'; + else if(hi >= 'A' && hi <= 'F') hi -= 'A' - 10; + else if(hi >= 'a' && hi <= 'f') hi -= 'a' - 10; + else return ""; + if(lo >= '0' && lo <= '9') lo -= '0'; + else if(lo >= 'A' && lo <= 'F') lo -= 'A' - 10; + else if(lo >= 'a' && lo <= 'f') lo -= 'a' - 10; + else return ""; + char byte = hi * 16 + lo; + output.append(byte); + n += 3; + } + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/decode/zip.hpp b/waterbox/bsnescore/bsnes/nall/decode/zip.hpp new file mode 100644 index 0000000000..316009dd93 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/decode/zip.hpp @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include +#include + +namespace nall::Decode { + +struct ZIP { + struct File { + string name; + const uint8_t* data; + uint size; + uint csize; + uint cmode; //0 = uncompressed, 8 = deflate + uint crc32; + time_t timestamp; + }; + + ~ZIP() { + close(); + } + + auto open(const string& filename) -> bool { + close(); + if(fm.open(filename, file::mode::read) == false) return false; + if(open(fm.data(), fm.size()) == false) { + fm.close(); + return false; + } + return true; + } + + auto open(const uint8_t* data, uint size) -> bool { + if(size < 22) return false; + + filedata = data; + filesize = size; + + file.reset(); + + const uint8_t* footer = data + size - 22; + while(true) { + if(footer <= data + 22) return false; + if(read(footer, 4) == 0x06054b50) { + uint commentlength = read(footer + 20, 2); + if(footer + 22 + commentlength == data + size) break; + } + footer--; + } + const uint8_t* directory = data + read(footer + 16, 4); + + while(true) { + uint signature = read(directory + 0, 4); + if(signature != 0x02014b50) break; + + File file; + file.cmode = read(directory + 10, 2); + file.crc32 = read(directory + 16, 4); + file.csize = read(directory + 20, 4); + file.size = read(directory + 24, 4); + + uint16_t dosTime = read(directory + 12, 2); + uint16_t dosDate = read(directory + 14, 2); + tm info = {}; + info.tm_sec = (dosTime >> 0 & 31) << 1; + info.tm_min = (dosTime >> 5 & 63); + info.tm_hour = (dosTime >> 11 & 31); + info.tm_mday = (dosDate >> 0 & 31); + info.tm_mon = (dosDate >> 5 & 15) - 1; + info.tm_year = (dosDate >> 9 & 127) + 80; + info.tm_isdst = -1; + file.timestamp = mktime(&info); + + uint namelength = read(directory + 28, 2); + uint extralength = read(directory + 30, 2); + uint commentlength = read(directory + 32, 2); + + char* filename = new char[namelength + 1]; + memcpy(filename, directory + 46, namelength); + filename[namelength] = 0; + file.name = filename; + delete[] filename; + + uint offset = read(directory + 42, 4); + uint offsetNL = read(data + offset + 26, 2); + uint offsetEL = read(data + offset + 28, 2); + file.data = data + offset + 30 + offsetNL + offsetEL; + + directory += 46 + namelength + extralength + commentlength; + + this->file.append(file); + } + + return true; + } + + auto extract(File& file) -> vector { + vector buffer; + + if(file.cmode == 0) { + buffer.resize(file.size); + memcpy(buffer.data(), file.data, file.size); + } + + if(file.cmode == 8) { + buffer.resize(file.size); + if(inflate(buffer.data(), buffer.size(), file.data, file.csize) == false) { + buffer.reset(); + } + } + + return buffer; + } + + auto close() -> void { + if(fm) fm.close(); + } + +protected: + file_map fm; + const uint8_t* filedata; + uint filesize; + + auto read(const uint8_t* data, uint size) -> uint { + uint result = 0, shift = 0; + while(size--) { result |= *data++ << shift; shift += 8; } + return result; + } + +public: + vector file; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/directory.hpp b/waterbox/bsnescore/bsnes/nall/directory.hpp new file mode 100644 index 0000000000..126ec661ff --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/directory.hpp @@ -0,0 +1,348 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include +#else + #include + #include + #include +#endif + +namespace nall { + +struct directory : inode { + directory() = delete; + + static auto copy(const string& source, const string& target) -> bool; //recursive + static auto create(const string& pathname, uint permissions = 0755) -> bool; //recursive + static auto remove(const string& pathname) -> bool; //recursive + static auto exists(const string& pathname) -> bool; + + static auto folders(const string& pathname, const string& pattern = "*") -> vector { + auto folders = directory::ufolders(pathname, pattern); + folders.sort(); + for(auto& folder : folders) folder.append("/"); //must append after sorting + return folders; + } + + static auto files(const string& pathname, const string& pattern = "*") -> vector { + auto files = directory::ufiles(pathname, pattern); + files.sort(); + return files; + } + + static auto contents(const string& pathname, const string& pattern = "*") -> vector { + auto folders = directory::ufolders(pathname); //pattern search of contents should only filter files + folders.sort(); + for(auto& folder : folders) folder.append("/"); //must append after sorting + auto files = directory::ufiles(pathname, pattern); + files.sort(); + for(auto& file : files) folders.append(file); + return folders; + } + + static auto ifolders(const string& pathname, const string& pattern = "*") -> vector { + auto folders = ufolders(pathname, pattern); + folders.isort(); + for(auto& folder : folders) folder.append("/"); //must append after sorting + return folders; + } + + static auto ifiles(const string& pathname, const string& pattern = "*") -> vector { + auto files = ufiles(pathname, pattern); + files.isort(); + return files; + } + + static auto icontents(const string& pathname, const string& pattern = "*") -> vector { + auto folders = directory::ufolders(pathname); //pattern search of contents should only filter files + folders.isort(); + for(auto& folder : folders) folder.append("/"); //must append after sorting + auto files = directory::ufiles(pathname, pattern); + files.isort(); + for(auto& file : files) folders.append(file); + return folders; + } + + static auto rcontents(const string& pathname, const string& pattern = "*") -> vector { + vector contents; + function + recurse = [&](const string& basename, const string& pathname, const string& pattern) { + for(auto& folder : directory::ufolders(pathname)) { + contents.append(string{pathname, folder, "/"}.trimLeft(basename, 1L)); + recurse(basename, {pathname, folder, "/"}, pattern); + } + for(auto& file : directory::ufiles(pathname, pattern)) { + contents.append(string{pathname, file}.trimLeft(basename, 1L)); + } + }; + for(auto& folder : directory::ufolders(pathname)) { + contents.append({folder, "/"}); + recurse(pathname, {pathname, folder, "/"}, pattern); + } + for(auto& file : directory::ufiles(pathname, pattern)) { + contents.append(file); + } + contents.sort(); + return contents; + } + + static auto ircontents(const string& pathname, const string& pattern = "*") -> vector { + vector contents; + function + recurse = [&](const string& basename, const string& pathname, const string& pattern) { + for(auto& folder : directory::ufolders(pathname)) { + contents.append(string{pathname, folder, "/"}.trimLeft(basename, 1L)); + recurse(basename, {pathname, folder, "/"}, pattern); + } + for(auto& file : directory::ufiles(pathname, pattern)) { + contents.append(string{pathname, file}.trimLeft(basename, 1L)); + } + }; + for(auto& folder : directory::ufolders(pathname)) { + contents.append({folder, "/"}); + recurse(pathname, {pathname, folder, "/"}, pattern); + } + for(auto& file : directory::ufiles(pathname, pattern)) { + contents.append(file); + } + contents.isort(); + return contents; + } + + static auto rfolders(const string& pathname, const string& pattern = "*") -> vector { + vector folders; + for(auto& folder : rcontents(pathname, pattern)) { + if(directory::exists({pathname, folder})) folders.append(folder); + } + return folders; + } + + static auto irfolders(const string& pathname, const string& pattern = "*") -> vector { + vector folders; + for(auto& folder : ircontents(pathname, pattern)) { + if(directory::exists({pathname, folder})) folders.append(folder); + } + return folders; + } + + static auto rfiles(const string& pathname, const string& pattern = "*") -> vector { + vector files; + for(auto& file : rcontents(pathname, pattern)) { + if(file::exists({pathname, file})) files.append(file); + } + return files; + } + + static auto irfiles(const string& pathname, const string& pattern = "*") -> vector { + vector files; + for(auto& file : ircontents(pathname, pattern)) { + if(file::exists({pathname, file})) files.append(file); + } + return files; + } + +private: + //internal functions; these return unsorted lists + static auto ufolders(const string& pathname, const string& pattern = "*") -> vector; + static auto ufiles(const string& pathname, const string& pattern = "*") -> vector; +}; + +inline auto directory::copy(const string& source, const string& target) -> bool { + bool result = true; + if(!directory::create(target)) return result = false; + for(auto& name : directory::folders(source)) { + if(!directory::copy({source, name}, {target, name})) result = false; + } + for(auto& name : directory::files(source)) { + if(!file::copy({source, name}, {target, name})) result = false; + } + return result; +} + +#if defined(PLATFORM_WINDOWS) + inline auto directory::create(const string& pathname, uint permissions) -> bool { + string path; + auto list = string{pathname}.transform("\\", "/").trimRight("/").split("/"); + bool result = true; + for(auto& part : list) { + path.append(part, "/"); + if(directory::exists(path)) continue; + result &= (_wmkdir(utf16_t(path)) == 0); + } + return result; + } + + inline auto directory::remove(const string& pathname) -> bool { + auto list = directory::contents(pathname); + for(auto& name : list) { + if(name.endsWith("/")) directory::remove({pathname, name}); + else file::remove({pathname, name}); + } + return _wrmdir(utf16_t(pathname)) == 0; + } + + inline auto directory::exists(const string& pathname) -> bool { + string name = pathname; + name.trim("\"", "\""); + DWORD result = GetFileAttributes(utf16_t(name)); + if(result == INVALID_FILE_ATTRIBUTES) return false; + return (result & FILE_ATTRIBUTE_DIRECTORY); + } + + inline auto directory::ufolders(const string& pathname, const string& pattern) -> vector { + if(!pathname) { + //special root pseudo-folder (return list of drives) + wchar_t drives[PATH_MAX] = {0}; + GetLogicalDriveStrings(PATH_MAX, drives); + wchar_t* p = drives; + while(*p || *(p + 1)) { + if(!*p) *p = ';'; + *p++; + } + return string{(const char*)utf8_t(drives)}.replace("\\", "/").split(";"); + } + + vector list; + string path = pathname; + path.transform("/", "\\"); + if(!path.endsWith("\\")) path.append("\\"); + path.append("*"); + HANDLE handle; + WIN32_FIND_DATA data; + handle = FindFirstFile(utf16_t(path), &data); + if(handle != INVALID_HANDLE_VALUE) { + if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) { + if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + string name = (const char*)utf8_t(data.cFileName); + if(name.match(pattern)) list.append(name); + } + } + while(FindNextFile(handle, &data) != false) { + if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) { + if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + string name = (const char*)utf8_t(data.cFileName); + if(name.match(pattern)) list.append(name); + } + } + } + FindClose(handle); + } + return list; + } + + inline auto directory::ufiles(const string& pathname, const string& pattern) -> vector { + if(!pathname) return {}; + + vector list; + string path = pathname; + path.transform("/", "\\"); + if(!path.endsWith("\\")) path.append("\\"); + path.append("*"); + HANDLE handle; + WIN32_FIND_DATA data; + handle = FindFirstFile(utf16_t(path), &data); + if(handle != INVALID_HANDLE_VALUE) { + if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + string name = (const char*)utf8_t(data.cFileName); + if(name.match(pattern)) list.append(name); + } + while(FindNextFile(handle, &data) != false) { + if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + string name = (const char*)utf8_t(data.cFileName); + if(name.match(pattern)) list.append(name); + } + } + FindClose(handle); + } + return list; + } +#else + inline auto directoryIsFolder(DIR* dp, struct dirent* ep) -> bool { + if(ep->d_type == DT_DIR) return true; + if(ep->d_type == DT_LNK || ep->d_type == DT_UNKNOWN) { + //symbolic links must be resolved to determine type + struct stat sp = {0}; + fstatat(dirfd(dp), ep->d_name, &sp, 0); + return S_ISDIR(sp.st_mode); + } + return false; + } + + inline auto directory::create(const string& pathname, uint permissions) -> bool { + string path; + auto list = string{pathname}.trimRight("/").split("/"); + bool result = true; + for(auto& part : list) { + path.append(part, "/"); + if(directory::exists(path)) continue; + result &= (mkdir(path, permissions) == 0); + } + return result; + } + + inline auto directory::remove(const string& pathname) -> bool { + auto list = directory::contents(pathname); + for(auto& name : list) { + if(name.endsWith("/")) directory::remove({pathname, name}); + else file::remove({pathname, name}); + } + return rmdir(pathname) == 0; + } + + inline auto directory::exists(const string& pathname) -> bool { + struct stat data; + if(stat(pathname, &data) != 0) return false; + return S_ISDIR(data.st_mode); + } + + inline auto directory::ufolders(const string& pathname, const string& pattern) -> vector { + if(!pathname) return vector{"/"}; + + vector list; + DIR* dp; + struct dirent* ep; + dp = opendir(pathname); + if(dp) { + while(ep = readdir(dp)) { + if(!strcmp(ep->d_name, ".")) continue; + if(!strcmp(ep->d_name, "..")) continue; + if(!directoryIsFolder(dp, ep)) continue; + string name{ep->d_name}; + if(name.match(pattern)) list.append(std::move(name)); + } + closedir(dp); + } + return list; + } + + inline auto directory::ufiles(const string& pathname, const string& pattern) -> vector { + if(!pathname) return {}; + + vector list; + DIR* dp; + struct dirent* ep; + dp = opendir(pathname); + if(dp) { + while(ep = readdir(dp)) { + if(!strcmp(ep->d_name, ".")) continue; + if(!strcmp(ep->d_name, "..")) continue; + if(directoryIsFolder(dp, ep)) continue; + string name{ep->d_name}; + if(name.match(pattern)) list.append(std::move(name)); + } + closedir(dp); + } + return list; + } +#endif + +} diff --git a/waterbox/bsnescore/bsnes/nall/dl.hpp b/waterbox/bsnescore/bsnes/nall/dl.hpp new file mode 100644 index 0000000000..8116d34921 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/dl.hpp @@ -0,0 +1,126 @@ +#pragma once + +//dynamic linking support + +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include +#else + #include +#endif + +namespace nall { + +struct library { + library() = default; + ~library() { close(); } + + library& operator=(const library&) = delete; + library(const library&) = delete; + + explicit operator bool() const { return open(); } + auto open() const -> bool { return handle; } + auto open(const string&, const string& = "") -> bool; + auto openAbsolute(const string&) -> bool; + auto sym(const string&) -> void*; + auto close() -> void; + +private: + uintptr handle = 0; +}; + +#if defined(PLATFORM_LINUX) || defined(PLATFORM_BSD) +inline auto library::open(const string& name, const string& path) -> bool { + if(handle) close(); + if(path) handle = (uintptr)dlopen(string(path, "lib", name, ".so"), RTLD_LAZY); + if(!handle) handle = (uintptr)dlopen(string(Path::user(), ".local/lib/lib", name, ".so"), RTLD_LAZY); + if(!handle) handle = (uintptr)dlopen(string("/usr/local/lib/lib", name, ".so"), RTLD_LAZY); + if(!handle) handle = (uintptr)dlopen(string("lib", name, ".so"), RTLD_LAZY); + return handle; +} + +inline auto library::openAbsolute(const string& name) -> bool { + if(handle) close(); + handle = (uintptr)dlopen(name, RTLD_LAZY); + return handle; +} + +inline auto library::sym(const string& name) -> void* { + if(!handle) return nullptr; + return dlsym((void*)handle, name); +} + +inline auto library::close() -> void { + if(!handle) return; + dlclose((void*)handle); + handle = 0; +} +#elif defined(PLATFORM_MACOS) +inline auto library::open(const string& name, const string& path) -> bool { + if(handle) close(); + if(path) handle = (uintptr)dlopen(string(path, "lib", name, ".dylib"), RTLD_LAZY); + if(!handle) handle = (uintptr)dlopen(string(Path::user(), ".local/lib/lib", name, ".dylib"), RTLD_LAZY); + if(!handle) handle = (uintptr)dlopen(string("/usr/local/lib/lib", name, ".dylib"), RTLD_LAZY); + if(!handle) handle = (uintptr)dlopen(string("lib", name, ".dylib"), RTLD_LAZY); + return handle; +} + +inline auto library::openAbsolute(const string& name) -> bool { + if(handle) close(); + handle = (uintptr)dlopen(name, RTLD_LAZY); + return handle; +} + +inline auto library::sym(const string& name) -> void* { + if(!handle) return nullptr; + return dlsym((void*)handle, name); +} + +inline auto library::close() -> void { + if(!handle) return; + dlclose((void*)handle); + handle = 0; +} +#elif defined(PLATFORM_WINDOWS) +inline auto library::open(const string& name, const string& path) -> bool { + if(handle) close(); + if(path) { + string filepath = {path, name, ".dll"}; + handle = (uintptr)LoadLibraryW(utf16_t(filepath)); + } + if(!handle) { + string filepath = {name, ".dll"}; + handle = (uintptr)LoadLibraryW(utf16_t(filepath)); + } + return handle; +} + +inline auto library::openAbsolute(const string& name) -> bool { + if(handle) close(); + handle = (uintptr)LoadLibraryW(utf16_t(name)); + return handle; +} + +inline auto library::sym(const string& name) -> void* { + if(!handle) return nullptr; + return (void*)GetProcAddress((HMODULE)handle, name); +} + +inline auto library::close() -> void { + if(!handle) return; + FreeLibrary((HMODULE)handle); + handle = 0; +} +#else +inline auto library::open(const string&, const string&) -> bool { return false; } +inline auto library::openAbsolute(const string&) -> bool { return false; } +inline auto library::sym(const string&) -> void* { return nullptr; } +inline auto library::close() -> void {} +#endif + +} diff --git a/waterbox/bsnescore/bsnes/nall/dsp/dsp.hpp b/waterbox/bsnescore/bsnes/nall/dsp/dsp.hpp new file mode 100644 index 0000000000..f8bf82e2b4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/dsp/dsp.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace nall::DSP { + +} diff --git a/waterbox/bsnescore/bsnes/nall/dsp/iir/biquad.hpp b/waterbox/bsnescore/bsnes/nall/dsp/iir/biquad.hpp new file mode 100644 index 0000000000..becd2c6e36 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/dsp/iir/biquad.hpp @@ -0,0 +1,164 @@ +#pragma once + +#include + +//transposed direct form II biquadratic second-order IIR filter + +namespace nall::DSP::IIR { + +struct Biquad { + enum class Type : uint { + LowPass, + HighPass, + BandPass, + Notch, + Peak, + LowShelf, + HighShelf, + }; + + inline auto reset(Type type, double cutoffFrequency, double samplingFrequency, double quality, double gain = 0.0) -> void; + inline auto process(double in) -> double; //normalized sample (-1.0 to +1.0) + + inline static auto shelf(double gain, double slope) -> double; + inline static auto butterworth(uint order, uint phase) -> double; + +private: + Type type; + double cutoffFrequency; + double samplingFrequency; + double quality; //frequency response quality + double gain; //peak gain + double a0, a1, a2, b1, b2; //coefficients + double z1, z2; //second-order IIR +}; + +auto Biquad::reset(Type type, double cutoffFrequency, double samplingFrequency, double quality, double gain) -> void { + this->type = type; + this->cutoffFrequency = cutoffFrequency; + this->samplingFrequency = samplingFrequency; + this->quality = quality; + this->gain = gain; + + z1 = 0.0; + z2 = 0.0; + + double v = pow(10, fabs(gain) / 20.0); + double k = tan(Math::Pi * cutoffFrequency / samplingFrequency); + double q = quality; + double n = 0.0; + + switch(type) { + + case Type::LowPass: + n = 1 / (1 + k / q + k * k); + a0 = k * k * n; + a1 = 2 * a0; + a2 = a0; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - k / q + k * k) * n; + break; + + case Type::HighPass: + n = 1 / (1 + k / q + k * k); + a0 = 1 * n; + a1 = -2 * a0; + a2 = a0; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - k / q + k * k) * n; + break; + + case Type::BandPass: + n = 1 / (1 + k / q + k * k); + a0 = k / q * n; + a1 = 0; + a2 = -a0; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - k / q + k * k) * n; + break; + + case Type::Notch: + n = 1 / (1 + k / q + k * k); + a0 = (1 + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = a0; + b1 = a1; + b2 = (1 - k / q + k * k) * n; + break; + + case Type::Peak: + if(gain >= 0) { + n = 1 / (1 + 1 / q * k + k * k); + a0 = (1 + v / q * k + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = (1 - v / q * k + k * k) * n; + b1 = a1; + b2 = (1 - 1 / q * k + k * k) * n; + } else { + n = 1 / (1 + v / q * k + k * k); + a0 = (1 + 1 / q * k + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = (1 - 1 / q * k + k * k) * n; + b1 = a1; + b2 = (1 - v / q * k + k * k) * n; + } + break; + + case Type::LowShelf: + if(gain >= 0) { + n = 1 / (1 + k / q + k * k); + a0 = (1 + sqrt(v) / q * k + v * k * k) * n; + a1 = 2 * (v * k * k - 1) * n; + a2 = (1 - sqrt(v) / q * k + v * k * k) * n; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - k / q + k * k) * n; + } else { + n = 1 / (1 + sqrt(v) / q * k + v * k * k); + a0 = (1 + k / q + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = (1 - k / q + k * k) * n; + b1 = 2 * (v * k * k - 1) * n; + b2 = (1 - sqrt(v) / q * k + v * k * k) * n; + } + break; + + case Type::HighShelf: + if(gain >= 0) { + n = 1 / (1 + k / q + k * k); + a0 = (v + sqrt(v) / q * k + k * k) * n; + a1 = 2 * (k * k - v) * n; + a2 = (v - sqrt(v) / q * k + k * k) * n; + b1 = 2 * (k * k - 1) * n; + b2 = (1 - k / q + k * k) * n; + } else { + n = 1 / (v + sqrt(v) / q * k + k * k); + a0 = (1 + k / q + k * k) * n; + a1 = 2 * (k * k - 1) * n; + a2 = (1 - k / q + k * k) * n; + b1 = 2 * (k * k - v) * n; + b2 = (v - sqrt(v) / q * k + k * k) * n; + } + break; + + } +} + +auto Biquad::process(double in) -> double { + double out = in * a0 + z1; + z1 = in * a1 + z2 - b1 * out; + z2 = in * a2 - b2 * out; + return out; +} + +//compute Q values for low-shelf and high-shelf filtering +auto Biquad::shelf(double gain, double slope) -> double { + double a = pow(10, gain / 40); + return 1 / sqrt((a + 1 / a) * (1 / slope - 1) + 2); +} + +//compute Q values for Nth-order butterworth filtering +auto Biquad::butterworth(uint order, uint phase) -> double { + return -0.5 / cos(Math::Pi * (phase + order + 0.5) / order); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/dsp/iir/dc-removal.hpp b/waterbox/bsnescore/bsnes/nall/dsp/iir/dc-removal.hpp new file mode 100644 index 0000000000..3fdbf3a58b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/dsp/iir/dc-removal.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +//DC offset removal IIR filter + +namespace nall::DSP::IIR { + +struct DCRemoval { + inline auto reset() -> void; + inline auto process(double in) -> double; //normalized sample (-1.0 to +1.0) + +private: + double x; + double y; +}; + +auto DCRemoval::reset() -> void { + x = 0.0; + y = 0.0; +} + +auto DCRemoval::process(double in) -> double { + x = 0.999 * x + in - y; + y = in; + return x; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/dsp/iir/one-pole.hpp b/waterbox/bsnescore/bsnes/nall/dsp/iir/one-pole.hpp new file mode 100644 index 0000000000..a2f6d98049 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/dsp/iir/one-pole.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +//one-pole first-order IIR filter + +namespace nall::DSP::IIR { + +struct OnePole { + enum class Type : uint { + LowPass, + HighPass, + }; + + inline auto reset(Type type, double cutoffFrequency, double samplingFrequency) -> void; + inline auto process(double in) -> double; //normalized sample (-1.0 to +1.0) + +private: + Type type; + double cutoffFrequency; + double samplingFrequency; + double a0, b1; //coefficients + double z1; //first-order IIR +}; + +auto OnePole::reset(Type type, double cutoffFrequency, double samplingFrequency) -> void { + this->type = type; + this->cutoffFrequency = cutoffFrequency; + this->samplingFrequency = samplingFrequency; + + z1 = 0.0; + double x = cos(2.0 * Math::Pi * cutoffFrequency / samplingFrequency); + if(type == Type::LowPass) { + b1 = +2.0 - x - sqrt((+2.0 - x) * (+2.0 - x) - 1); + a0 = 1.0 - b1; + } else { + b1 = -2.0 - x + sqrt((-2.0 - x) * (-2.0 - x) - 1); + a0 = 1.0 + b1; + } +} + +auto OnePole::process(double in) -> double { + return z1 = in * a0 + z1 * b1; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/dsp/resampler/cubic.hpp b/waterbox/bsnescore/bsnes/nall/dsp/resampler/cubic.hpp new file mode 100644 index 0000000000..eaa4aafff6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/dsp/resampler/cubic.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +namespace nall::DSP::Resampler { + +struct Cubic { + inline auto inputFrequency() const -> double { return _inputFrequency; } + inline auto outputFrequency() const -> double { return _outputFrequency; } + + inline auto reset(double inputFrequency, double outputFrequency = 0, uint queueSize = 0) -> void; + inline auto setInputFrequency(double inputFrequency) -> void; + inline auto pending() const -> bool; + inline auto read() -> double; + inline auto write(double sample) -> void; + inline auto serialize(serializer&) -> void; + +private: + double _inputFrequency; + double _outputFrequency; + + double _ratio; + double _fraction; + double _history[4]; + queue _samples; +}; + +auto Cubic::reset(double inputFrequency, double outputFrequency, uint queueSize) -> void { + _inputFrequency = inputFrequency; + _outputFrequency = outputFrequency ? outputFrequency : _inputFrequency; + + _ratio = _inputFrequency / _outputFrequency; + _fraction = 0.0; + for(auto& sample : _history) sample = 0.0; + _samples.resize(queueSize ? queueSize : _outputFrequency * 0.02); //default to 20ms max queue size +} + +auto Cubic::setInputFrequency(double inputFrequency) -> void { + _inputFrequency = inputFrequency; + _ratio = _inputFrequency / _outputFrequency; +} + +auto Cubic::pending() const -> bool { + return _samples.pending(); +} + +auto Cubic::read() -> double { + return _samples.read(); +} + +auto Cubic::write(double sample) -> void { + auto& mu = _fraction; + auto& s = _history; + + s[0] = s[1]; + s[1] = s[2]; + s[2] = s[3]; + s[3] = sample; + + while(mu <= 1.0) { + double A = s[3] - s[2] - s[0] + s[1]; + double B = s[0] - s[1] - A; + double C = s[2] - s[0]; + double D = s[1]; + + _samples.write(A * mu * mu * mu + B * mu * mu + C * mu + D); + mu += _ratio; + } + + mu -= 1.0; +} + +auto Cubic::serialize(serializer& s) -> void { + s.real(_inputFrequency); + s.real(_outputFrequency); + s.real(_ratio); + s.real(_fraction); + s.array(_history); + _samples.serialize(s); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/elliptic-curve/curve25519.hpp b/waterbox/bsnescore/bsnes/nall/elliptic-curve/curve25519.hpp new file mode 100644 index 0000000000..194efdd19e --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/elliptic-curve/curve25519.hpp @@ -0,0 +1,57 @@ +#pragma once + +#if defined(EC_REFERENCE) + #include +#else + #include +#endif + +namespace nall::EllipticCurve { + +struct Curve25519 { + auto sharedKey(uint256_t secretKey, uint256_t basepoint = 9) const -> uint256_t { + secretKey &= (1_u256 << 254) - 8; + secretKey |= (1_u256 << 254); + basepoint &= ~0_u256 >> 1; + + point p = scalarMultiply(basepoint % P, secretKey); + field k = p.x * reciprocal(p.z); + return k(); + } + +private: + using field = Modulo25519; + struct point { field x, z; }; + const BarrettReduction<256> P = BarrettReduction<256>{EllipticCurve::P}; + + inline auto montgomeryDouble(point p) const -> point { + field a = square(p.x + p.z); + field b = square(p.x - p.z); + field c = a - b; + field d = a + c * 121665; + return {a * b, c * d}; + } + + inline auto montgomeryAdd(point p, point q, field b) const -> point { + return { + square(p.x * q.x - p.z * q.z), + square(p.x * q.z - p.z * q.x) * b + }; + } + + inline auto scalarMultiply(field b, uint256_t exponent) const -> point { + point p{1, 0}, q{b, 1}; + for(uint bit : reverse(range(255))) { + bool condition = exponent >> bit & 1; + cswap(condition, p.x, q.x); + cswap(condition, p.z, q.z); + q = montgomeryAdd(p, q, b); + p = montgomeryDouble(p); + cswap(condition, p.x, q.x); + cswap(condition, p.z, q.z); + } + return p; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/elliptic-curve/ed25519.hpp b/waterbox/bsnescore/bsnes/nall/elliptic-curve/ed25519.hpp new file mode 100644 index 0000000000..afce41b125 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/elliptic-curve/ed25519.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include +#if defined(EC_REFERENCE) + #include +#else + #include +#endif + +namespace nall::EllipticCurve { + +static const uint256_t L = (1_u256 << 252) + 27742317777372353535851937790883648493_u256; + +struct Ed25519 { + auto publicKey(uint256_t privateKey) const -> uint256_t { + return compress(scalarMultiply(B, clamp(hash(privateKey)) % L)); + } + + auto sign(array_view message, uint256_t privateKey) const -> uint512_t { + uint512_t H = hash(privateKey); + uint256_t a = clamp(H) % L; + uint256_t A = compress(scalarMultiply(B, a)); + + uint512_t r = hash(upper(H), message) % L; + uint256_t R = compress(scalarMultiply(B, r)); + + uint512_t k = hash(R, A, message) % L; + uint256_t S = (k * a + r) % L; + + return uint512_t(S) << 256 | R; + } + + auto verify(array_view message, uint512_t signature, uint256_t publicKey) const -> bool { + auto R = decompress(lower(signature)); + auto A = decompress(publicKey); + if(!R || !A) return false; + + uint256_t S = upper(signature) % L; + uint512_t r = hash(lower(signature), publicKey, message) % L; + + auto p = scalarMultiply(B, S); + auto q = edwardsAdd(R(), scalarMultiply(A(), r)); + if(!onCurve(p) || !onCurve(q)) return false; + if(p.x * q.z - q.x * p.z) return false; + if(p.y * q.z - q.y * p.z) return false; + return true; + } + +private: + using field = Modulo25519; + struct point { field x, y, z, t; }; + const field D = -field(121665) * reciprocal(field(121666)); + const point B = *decompress((field(4) * reciprocal(field(5)))()); + const BarrettReduction<256> L = BarrettReduction<256>{EllipticCurve::L}; + + inline auto input(Hash::SHA512&) const -> void {} + + template inline auto input(Hash::SHA512& hash, uint256_t value, P&&... p) const -> void { + for(uint byte : range(32)) hash.input(uint8_t(value >> byte * 8)); + input(hash, forward

(p)...); + } + + template inline auto input(Hash::SHA512& hash, array_view value, P&&... p) const -> void { + hash.input(value); + input(hash, forward

(p)...); + } + + template inline auto hash(P&&... p) const -> uint512_t { + Hash::SHA512 hash; + input(hash, forward

(p)...); + uint512_t result; + for(auto byte : reverse(hash.output())) result = result << 8 | byte; + return result; + } + + inline auto clamp(uint256_t p) const -> uint256_t { + p &= (1_u256 << 254) - 8; + p |= (1_u256 << 254); + return p; + } + + inline auto onCurve(point p) const -> bool { + if(!p.z) return false; + if(p.x * p.y - p.z * p.t) return false; + if(square(p.y) - square(p.x) - square(p.z) - square(p.t) * D) return false; + return true; + } + + inline auto decompress(uint256_t c) const -> maybe { + field y = c & ~0_u256 >> 1; + field x = squareRoot((square(y) - 1) * reciprocal(D * square(y) + 1)); + if(c >> 255) x = -x; + point p{x, y, 1, x * y}; + if(!onCurve(p)) return nothing; + return p; + } + + inline auto compress(point p) const -> uint256_t { + field r = reciprocal(p.z); + field x = p.x * r; + field y = p.y * r; + return (x & 1) << 255 | (y & ~0_u256 >> 1); + } + + inline auto edwardsDouble(point p) const -> point { + field a = square(p.x); + field b = square(p.y); + field c = square(p.z); + field d = -a; + field e = square(p.x + p.y) - a - b; + field g = d + b; + field f = g - (c + c); + field h = d - b; + return {e * f, g * h, f * g, e * h}; + } + + inline auto edwardsAdd(point p, point q) const -> point { + field a = (p.y - p.x) * (q.y - q.x); + field b = (p.y + p.x) * (q.y + q.x); + field c = (p.t + p.t) * q.t * D; + field d = (p.z + p.z) * q.z; + field e = b - a; + field f = d - c; + field g = d + c; + field h = b + a; + return {e * f, g * h, f * g, e * h}; + } + + inline auto scalarMultiply(point q, uint256_t exponent) const -> point { + point p{0, 1, 1, 0}, c; + for(uint bit : reverse(range(253))) { + p = edwardsDouble(p); + c = edwardsAdd(p, q); + bool condition = exponent >> bit & 1; + cmove(condition, p.x, c.x); + cmove(condition, p.y, c.y); + cmove(condition, p.z, c.z); + cmove(condition, p.t, c.t); + } + return p; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/elliptic-curve/modulo25519-optimized.hpp b/waterbox/bsnescore/bsnes/nall/elliptic-curve/modulo25519-optimized.hpp new file mode 100644 index 0000000000..4d2ec01095 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/elliptic-curve/modulo25519-optimized.hpp @@ -0,0 +1,218 @@ +#pragma once + +#include + +namespace nall::EllipticCurve { + +static const uint256_t P = (1_u256 << 255) - 19; + +#define Mask ((1ull << 51) - 1) + +struct Modulo25519 { + inline Modulo25519() = default; + inline Modulo25519(const Modulo25519&) = default; + inline Modulo25519(uint64_t a, uint64_t b = 0, uint64_t c = 0, uint64_t d = 0, uint64_t e = 0) : l{a, b, c, d, e} {} + inline Modulo25519(uint256_t n); + + inline explicit operator bool() const { return (bool)operator()(); } + inline auto operator[](uint index) -> uint64_t& { return l[index]; } + inline auto operator[](uint index) const -> uint64_t { return l[index]; } + inline auto operator()() const -> uint256_t; + +private: + uint64_t l[5]; //51-bits per limb; 255-bits total +}; + +inline Modulo25519::Modulo25519(uint256_t n) { + l[0] = n >> 0 & Mask; + l[1] = n >> 51 & Mask; + l[2] = n >> 102 & Mask; + l[3] = n >> 153 & Mask; + l[4] = n >> 204 & Mask; +} + +inline auto Modulo25519::operator()() const -> uint256_t { + Modulo25519 o = *this; + + o[1] += (o[0] >> 51); o[0] &= Mask; + o[2] += (o[1] >> 51); o[1] &= Mask; + o[3] += (o[2] >> 51); o[2] &= Mask; + o[4] += (o[3] >> 51); o[3] &= Mask; + o[0] += 19 * (o[4] >> 51); o[4] &= Mask; + + o[1] += (o[0] >> 51); o[0] &= Mask; + o[2] += (o[1] >> 51); o[1] &= Mask; + o[3] += (o[2] >> 51); o[2] &= Mask; + o[4] += (o[3] >> 51); o[3] &= Mask; + o[0] += 19 * (o[4] >> 51); o[4] &= Mask; + + o[0] += 19; + o[1] += (o[0] >> 51); o[0] &= Mask; + o[2] += (o[1] >> 51); o[1] &= Mask; + o[3] += (o[2] >> 51); o[2] &= Mask; + o[4] += (o[3] >> 51); o[3] &= Mask; + o[0] += 19 * (o[4] >> 51); o[4] &= Mask; + + o[0] += Mask - 18; + o[1] += Mask; + o[2] += Mask; + o[3] += Mask; + o[4] += Mask; + + o[1] += o[0] >> 51; o[0] &= Mask; + o[2] += o[1] >> 51; o[1] &= Mask; + o[3] += o[2] >> 51; o[2] &= Mask; + o[4] += o[3] >> 51; o[3] &= Mask; + o[4] &= Mask; + + return (uint256_t)o[0] << 0 | (uint256_t)o[1] << 51 | (uint256_t)o[2] << 102 | (uint256_t)o[3] << 153 | (uint256_t)o[4] << 204; +} + +inline auto cmove(bool move, Modulo25519& l, const Modulo25519& r) -> void { + uint64_t mask = -move; + l[0] ^= mask & (l[0] ^ r[0]); + l[1] ^= mask & (l[1] ^ r[1]); + l[2] ^= mask & (l[2] ^ r[2]); + l[3] ^= mask & (l[3] ^ r[3]); + l[4] ^= mask & (l[4] ^ r[4]); +} + +inline auto cswap(bool swap, Modulo25519& l, Modulo25519& r) -> void { + uint64_t mask = -swap, x; + x = mask & (l[0] ^ r[0]); l[0] ^= x; r[0] ^= x; + x = mask & (l[1] ^ r[1]); l[1] ^= x; r[1] ^= x; + x = mask & (l[2] ^ r[2]); l[2] ^= x; r[2] ^= x; + x = mask & (l[3] ^ r[3]); l[3] ^= x; r[3] ^= x; + x = mask & (l[4] ^ r[4]); l[4] ^= x; r[4] ^= x; +} + +inline auto operator-(const Modulo25519& l) -> Modulo25519 { //P - l + Modulo25519 o; + uint64_t c; + o[0] = 0xfffffffffffda - l[0]; c = o[0] >> 51; o[0] &= Mask; + o[1] = 0xffffffffffffe - l[1] + c; c = o[1] >> 51; o[1] &= Mask; + o[2] = 0xffffffffffffe - l[2] + c; c = o[2] >> 51; o[2] &= Mask; + o[3] = 0xffffffffffffe - l[3] + c; c = o[3] >> 51; o[3] &= Mask; + o[4] = 0xffffffffffffe - l[4] + c; c = o[4] >> 51; o[4] &= Mask; + o[0] += c * 19; + return o; +} + +inline auto operator+(const Modulo25519& l, const Modulo25519& r) -> Modulo25519 { + Modulo25519 o; + uint64_t c; + o[0] = l[0] + r[0]; c = o[0] >> 51; o[0] &= Mask; + o[1] = l[1] + r[1] + c; c = o[1] >> 51; o[1] &= Mask; + o[2] = l[2] + r[2] + c; c = o[2] >> 51; o[2] &= Mask; + o[3] = l[3] + r[3] + c; c = o[3] >> 51; o[3] &= Mask; + o[4] = l[4] + r[4] + c; c = o[4] >> 51; o[4] &= Mask; + o[0] += c * 19; + return o; +} + +inline auto operator-(const Modulo25519& l, const Modulo25519& r) -> Modulo25519 { + Modulo25519 o; + uint64_t c; + o[0] = l[0] + 0x1fffffffffffb4 - r[0]; c = o[0] >> 51; o[0] &= Mask; + o[1] = l[1] + 0x1ffffffffffffc - r[1] + c; c = o[1] >> 51; o[1] &= Mask; + o[2] = l[2] + 0x1ffffffffffffc - r[2] + c; c = o[2] >> 51; o[2] &= Mask; + o[3] = l[3] + 0x1ffffffffffffc - r[3] + c; c = o[3] >> 51; o[3] &= Mask; + o[4] = l[4] + 0x1ffffffffffffc - r[4] + c; c = o[4] >> 51; o[4] &= Mask; + o[0] += c * 19; + return o; +} + +inline auto operator*(const Modulo25519& l, uint64_t scalar) -> Modulo25519 { + Modulo25519 o; + uint128_t a; + a = (uint128_t)l[0] * scalar; o[0] = a & Mask; + a = (uint128_t)l[1] * scalar + (a >> 51 & Mask); o[1] = a & Mask; + a = (uint128_t)l[2] * scalar + (a >> 51 & Mask); o[2] = a & Mask; + a = (uint128_t)l[3] * scalar + (a >> 51 & Mask); o[3] = a & Mask; + a = (uint128_t)l[4] * scalar + (a >> 51 & Mask); o[4] = a & Mask; + o[0] += (a >> 51) * 19; + return o; +} + +inline auto operator*(const Modulo25519& l, Modulo25519 r) -> Modulo25519 { + uint128_t t[] = { + (uint128_t)r[0] * l[0], + (uint128_t)r[0] * l[1] + (uint128_t)r[1] * l[0], + (uint128_t)r[0] * l[2] + (uint128_t)r[1] * l[1] + (uint128_t)r[2] * l[0], + (uint128_t)r[0] * l[3] + (uint128_t)r[1] * l[2] + (uint128_t)r[2] * l[1] + (uint128_t)r[3] * l[0], + (uint128_t)r[0] * l[4] + (uint128_t)r[1] * l[3] + (uint128_t)r[2] * l[2] + (uint128_t)r[3] * l[1] + (uint128_t)r[4] * l[0] + }; + + r[1] *= 19, r[2] *= 19, r[3] *= 19, r[4] *= 19; + + t[0] += (uint128_t)r[4] * l[1] + (uint128_t)r[3] * l[2] + (uint128_t)r[2] * l[3] + (uint128_t)r[1] * l[4]; + t[1] += (uint128_t)r[4] * l[2] + (uint128_t)r[3] * l[3] + (uint128_t)r[2] * l[4]; + t[2] += (uint128_t)r[4] * l[3] + (uint128_t)r[3] * l[4]; + t[3] += (uint128_t)r[4] * l[4]; + + uint64_t c; r[0] = t[0] & Mask; c = (uint64_t)(t[0] >> 51); + t[1] += c; r[1] = t[1] & Mask; c = (uint64_t)(t[1] >> 51); + t[2] += c; r[2] = t[2] & Mask; c = (uint64_t)(t[2] >> 51); + t[3] += c; r[3] = t[3] & Mask; c = (uint64_t)(t[3] >> 51); + t[4] += c; r[4] = t[4] & Mask; c = (uint64_t)(t[4] >> 51); + + r[0] += c * 19; c = r[0] >> 51; r[0] &= Mask; + r[1] += c; c = r[1] >> 51; r[1] &= Mask; + r[2] += c; + return r; +} + +inline auto operator&(const Modulo25519& lhs, uint256_t rhs) -> uint256_t { + return lhs() & rhs; +} + +inline auto square(const Modulo25519& lhs) -> Modulo25519 { + Modulo25519 r{lhs}; + Modulo25519 d{r[0] * 2, r[1] * 2, r[2] * 2 * 19, r[4] * 19, r[4] * 19 * 2}; + + uint128_t t[5]; + t[0] = (uint128_t)r[0] * r[0] + (uint128_t)d[4] * r[1] + (uint128_t)d[2] * r[3]; + t[1] = (uint128_t)d[0] * r[1] + (uint128_t)d[4] * r[2] + (uint128_t)r[3] * r[3] * 19; + t[2] = (uint128_t)d[0] * r[2] + (uint128_t)r[1] * r[1] + (uint128_t)d[4] * r[3]; + t[3] = (uint128_t)d[0] * r[3] + (uint128_t)d[1] * r[2] + (uint128_t)r[4] * d[3]; + t[4] = (uint128_t)d[0] * r[4] + (uint128_t)d[1] * r[3] + (uint128_t)r[2] * r[2]; + + uint64_t c; r[0] = t[0] & Mask; c = (uint64_t)(t[0] >> 51); + t[1] += c; r[1] = t[1] & Mask; c = (uint64_t)(t[1] >> 51); + t[2] += c; r[2] = t[2] & Mask; c = (uint64_t)(t[2] >> 51); + t[3] += c; r[3] = t[3] & Mask; c = (uint64_t)(t[3] >> 51); + t[4] += c; r[4] = t[4] & Mask; c = (uint64_t)(t[4] >> 51); + + r[0] += c * 19; c = r[0] >> 51; r[0] &= Mask; + r[1] += c; c = r[1] >> 51; r[1] &= Mask; + r[2] += c; + return r; +} + +inline auto exponentiate(const Modulo25519& lhs, uint256_t exponent) -> Modulo25519 { + Modulo25519 x = 1, y; + for(uint bit : reverse(range(256))) { + x = square(x); + y = x * lhs; + cmove(exponent >> bit & 1, x, y); + } + return x; +} + +inline auto reciprocal(const Modulo25519& lhs) -> Modulo25519 { + return exponentiate(lhs, P - 2); +} + +inline auto squareRoot(const Modulo25519& lhs) -> Modulo25519 { + static const Modulo25519 I = exponentiate(Modulo25519(2), P - 1 >> 2); //I == sqrt(-1) + Modulo25519 x = exponentiate(lhs, P + 3 >> 3); + Modulo25519 y = x * I; + cmove(bool(square(x) - lhs), x, y); + y = -x; + cmove(x & 1, x, y); + return x; +} + +#undef Mask + +} diff --git a/waterbox/bsnescore/bsnes/nall/elliptic-curve/modulo25519-reference.hpp b/waterbox/bsnescore/bsnes/nall/elliptic-curve/modulo25519-reference.hpp new file mode 100644 index 0000000000..f9eb485a9d --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/elliptic-curve/modulo25519-reference.hpp @@ -0,0 +1,84 @@ +#pragma once + +//warning: this implementation leaks side-channel information +//use modulo25519-optimized.hpp in production + +#include + +namespace nall::EllipticCurve { + +static const uint256_t P = (1_u256 << 255) - 19; + +struct Modulo25519 { + inline Modulo25519() = default; + inline Modulo25519(const Modulo25519& source) : value(source.value) {} + template inline Modulo25519(const T& value) : value(value) {} + inline explicit operator bool() const { return (bool)value; } + inline auto operator()() const -> uint256_t { return value; } + +private: + uint256_t value; +}; + +inline auto operator-(const Modulo25519& lhs) -> Modulo25519 { + return P - lhs(); +} + +inline auto operator+(const Modulo25519& lhs, const Modulo25519& rhs) -> Modulo25519 { + uint512_t value = (uint512_t)lhs() + rhs(); + if(value >= P) value -= P; + return value; +} + +inline auto operator-(const Modulo25519& lhs, const Modulo25519& rhs) -> Modulo25519 { + uint512_t value = (uint512_t)lhs(); + if(value < rhs()) value += P; + return uint256_t(value - rhs()); +} + +inline auto operator*(const Modulo25519& lhs, const Modulo25519& rhs) -> Modulo25519 { + static const BarrettReduction<256> P{EllipticCurve::P}; + uint256_t hi, lo; + mul(lhs(), rhs(), hi, lo); + return uint512_t{hi, lo} % P; +} + +inline auto operator&(const Modulo25519& lhs, uint256_t rhs) -> uint256_t { + return lhs() & rhs; +} + +inline auto square(const Modulo25519& lhs) -> Modulo25519 { + static const BarrettReduction<256> P{EllipticCurve::P}; + uint256_t hi, lo; + square(lhs(), hi, lo); + return uint512_t{hi, lo} % P; +} + +inline auto exponentiate(const Modulo25519& lhs, uint256_t exponent) -> Modulo25519 { + if(exponent == 0) return 1; + Modulo25519 value = square(exponentiate(lhs, exponent >> 1)); + if(exponent & 1) value = value * lhs; + return value; +} + +inline auto reciprocal(const Modulo25519& lhs) -> Modulo25519 { + return exponentiate(lhs, P - 2); +} + +inline auto squareRoot(const Modulo25519& lhs) -> Modulo25519 { + static const Modulo25519 I = exponentiate(Modulo25519(2), P - 1 >> 2); //I = sqrt(-1) + Modulo25519 value = exponentiate(lhs, P + 3 >> 3); + if(square(value) - lhs) value = value * I; + if(value & 1) value = -value; + return value; +} + +inline auto cmove(bool condition, Modulo25519& lhs, const Modulo25519& rhs) -> void { + if(condition) lhs = rhs; +} + +inline auto cswap(bool condition, Modulo25519& lhs, Modulo25519& rhs) -> void { + if(condition) swap(lhs, rhs); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/emulation/21fx.hpp b/waterbox/bsnescore/bsnes/nall/emulation/21fx.hpp new file mode 100644 index 0000000000..1625e74b95 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/emulation/21fx.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +using namespace nall; + +using int8 = Integer< 8>; +using int16 = Integer<16>; +using int24 = Integer<24>; +using int32 = Integer<32>; +using int64 = Integer<64>; + +using uint8 = Natural< 8>; +using uint16 = Natural<16>; +using uint24 = Natural<24>; +using uint32 = Natural<32>; +using uint64 = Natural<64>; + +struct FX { + auto open(Arguments& arguments) -> bool; + auto close() -> void; + auto readable() -> bool; + auto read() -> uint8_t; + auto writable() -> bool; + auto write(uint8_t data) -> void; + + auto read(uint offset, uint length) -> vector; + auto write(uint offset, const void* buffer, uint length) -> void; + auto write(uint offset, const vector& buffer) -> void { write(offset, buffer.data(), buffer.size()); } + auto execute(uint offset) -> void; + + auto read(uint offset) -> uint8_t; + auto write(uint offset, uint8_t data) -> void; + + serial device; +}; + +auto FX::open(Arguments& arguments) -> bool { + //device name override support + string name; + arguments.take("--device", name); + if(!device.open(name)) { + print("[21fx] error: unable to open hardware device\n"); + return false; + } + + //flush the device (to clear floating inputs) + while(true) { + while(readable()) read(); + auto iplrom = read(0x2184, 122); + auto sha256 = Hash::SHA256(iplrom).digest(); + if(sha256 == "41b79712a4a2d16d39894ae1b38cde5c41dad22eadc560df631d39f13df1e4b9") break; + } + + return true; +} + +auto FX::close() -> void { + device.close(); +} + +auto FX::readable() -> bool { + return device.readable(); +} + +//1000ns delay avoids burning CPU core at 100%; does not slow down max transfer rate at all +auto FX::read() -> uint8_t { + while(!readable()) usleep(1000); + uint8_t buffer[1] = {0}; + device.read(buffer, 1); + return buffer[0]; +} + +auto FX::writable() -> bool { + return device.writable(); +} + +auto FX::write(uint8_t data) -> void { + while(!writable()) usleep(1000); + uint8_t buffer[1] = {data}; + device.write(buffer, 1); +} + +// + +auto FX::read(uint offset, uint length) -> vector { + write(0x21); + write(0x66); + write(0x78); + write(offset >> 16); + write(offset >> 8); + write(offset >> 0); + write(0x01); + write(length >> 8); + write(length >> 0); + write(0x00); + + vector buffer; + while(length--) buffer.append(read()); + return buffer; +} + +auto FX::write(uint offset, const void* data, uint length) -> void { + write(0x21); + write(0x66); + write(0x78); + write(offset >> 16); + write(offset >> 8); + write(offset >> 0); + write(0x01); + write(length >> 8); + write(length >> 0); + write(0x01); + + auto buffer = (uint8_t*)data; + for(auto n : range(length)) write(buffer[n]); + write(0x00); +} + +auto FX::execute(uint offset) -> void { + write(0x21); + write(0x66); + write(0x78); + write(offset >> 16); + write(offset >> 8); + write(offset >> 0); + write(0x00); +} + +// + +auto FX::read(uint offset) -> uint8_t { + auto buffer = read(offset, 1); + return buffer[0]; +} + +auto FX::write(uint offset, uint8_t data) -> void { + vector buffer = {data}; + write(offset, buffer); +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/base.hpp b/waterbox/bsnescore/bsnes/nall/encode/base.hpp new file mode 100644 index 0000000000..ac55486ee6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/base.hpp @@ -0,0 +1,38 @@ +#pragma once + +//required bytes: ceil(bits / log2(base)) +//base57 => 128=22, 256=44, 512=88 +//base62 => 128=22, 256=43, 512=86 +//base64 => 128=22, 256=43, 512=86 + +#include + +namespace nall::Encode { + +template inline auto Base(T value) -> string { + static const string format = + Bits == 2 ? "01" + : Bits == 8 ? "01234567" + : Bits == 10 ? "0123456789" + : Bits == 16 ? "0123456789abcdef" + : Bits == 32 ? "0123456789abcdefghijklmnopqrstuv" + : Bits == 34 ? "023456789abcdefghijkmnopqrstuvwxyz" //1l + : Bits == 36 ? "0123456789abcdefghijklmnopqrstuvwxyz" + : Bits == 57 ? "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" //01IOl + : Bits == 62 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + : Bits == 64 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}" + : Bits == 85 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%()+,-.:;=@[]^_`{|}~" //\ "&'*/<>? + : ""; + static const uint size = ceil(sizeof(T) * 8 / log2(Bits)); + + string result; + result.resize(size); + char* data = result.get() + size; + for(auto byte : result) { + *--data = format[value % Bits]; + value /= Bits; + } + return result; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/base64.hpp b/waterbox/bsnescore/bsnes/nall/encode/base64.hpp new file mode 100644 index 0000000000..a1d518cf99 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/base64.hpp @@ -0,0 +1,68 @@ +#pragma once + +namespace nall::Encode { + +inline auto Base64(const void* vdata, uint size, const string& format = "MIME") -> string { + static bool initialized = false; + static char lookup[65] = {0}; + if(!initialized) { + initialized = true; + for(uint n : range(26)) lookup[n + 0] = 'A' + n; + for(uint n : range(26)) lookup[n + 26] = 'a' + n; + for(uint n : range(10)) lookup[n + 52] = '0' + n; + } + + if(format == "MIME") { + lookup[62] = '+'; + lookup[63] = '/'; + lookup[64] = '='; + } else if(format == "URI") { + lookup[62] = '-'; + lookup[63] = '_'; + lookup[64] = 0; + } else return ""; + + auto data = (const uint8_t*)vdata; + uint overflow = (3 - (size % 3)) % 3; //bytes to round to nearest multiple of 3 + string result; + uint8_t buffer; + for(uint n : range(size)) { + switch(n % 3) { + case 0: + buffer = data[n] >> 2; + result.append(lookup[buffer]); + buffer = (data[n] & 3) << 4; + break; + + case 1: + buffer |= data[n] >> 4; + result.append(lookup[buffer]); + buffer = (data[n] & 15) << 2; + break; + + case 2: + buffer |= data[n] >> 6; + result.append(lookup[buffer]); + buffer = (data[n] & 63); + result.append(lookup[buffer]); + break; + } + } + + if(overflow) result.append(lookup[buffer]); + if(lookup[64]) { + while(result.size() % 4) result.append(lookup[64]); + } + + return result; +} + +inline auto Base64(const vector& buffer, const string& format = "MIME") -> string { + return Base64(buffer.data(), buffer.size(), format); +} + +inline auto Base64(const string& text, const string& format = "MIME") -> string { + return Base64(text.data(), text.size(), format); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/bmp.hpp b/waterbox/bsnescore/bsnes/nall/encode/bmp.hpp new file mode 100644 index 0000000000..1ccc930470 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/bmp.hpp @@ -0,0 +1,47 @@ +#pragma once + +namespace nall::Encode { + +struct BMP { + static auto create(const string& filename, const void* data, uint pitch, uint width, uint height, bool alpha) -> bool { + auto fp = file::open(filename, file::mode::write); + if(!fp) return false; + + uint bitsPerPixel = alpha ? 32 : 24; + uint bytesPerPixel = bitsPerPixel / 8; + uint alignedWidth = width * bytesPerPixel; + uint paddingLength = 0; + uint imageSize = alignedWidth * height; + uint fileSize = 0x36 + imageSize; + while(alignedWidth % 4) alignedWidth++, paddingLength++; + + fp.writel(0x4d42, 2); //signature + fp.writel(fileSize, 4); //file size + fp.writel(0, 2); //reserved + fp.writel(0, 2); //reserved + fp.writel(0x36, 4); //offset + + fp.writel(40, 4); //DIB size + fp.writel(width, 4); //width + fp.writel(-height, 4); //height + fp.writel(1, 2); //color planes + fp.writel(bitsPerPixel, 2); //bits per pixel + fp.writel(0, 4); //compression method (BI_RGB) + fp.writel(imageSize, 4); //image data size + fp.writel(3780, 4); //horizontal resolution + fp.writel(3780, 4); //vertical resolution + fp.writel(0, 4); //palette size + fp.writel(0, 4); //important color count + + pitch >>= 2; + for(auto y : range(height)) { + auto p = (const uint32_t*)data + y * pitch; + for(auto x : range(width)) fp.writel(*p++, bytesPerPixel); + if(paddingLength) fp.writel(0, paddingLength); + } + + return true; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/bwt.hpp b/waterbox/bsnescore/bsnes/nall/encode/bwt.hpp new file mode 100644 index 0000000000..84e00d9c49 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/bwt.hpp @@ -0,0 +1,86 @@ +#pragma once + +//burrows-wheeler transform + +#include + +namespace nall::Encode { + +/* + A standard suffix array cannot produce a proper burrows-wheeler transform, due to rotations. + + Take the input string, "nall", this gives us: + nall + alln + llna + lnal + + If we suffix sort this, we produce: + all => alln + l => lnal + ll => llna + nall => nall + + If we sort this, we produce: + alln + llna + lnal + nall + + Thus, suffix sorting gives us "nlal" as the last column instead of "nall". + This is because BWT rotates the input string, whereas suffix arrays sort the input string. + + Adding a 256th character terminator before sorting will not produce the desired result, either. + A more complicated string such as "mississippi" will sort as "ssmppissiii" with terminator=256, + and as "ipssmpissii" with terminator=0, alphabet=1..256, whereas we want "pssmipissii". + + Performing a merge sort to use a specialized comparison function that wraps suffixes is too slow at O(n log n). + + Producing a custom induced sort to handle rotations would be incredibly complicated, + owing to the recursive nature of induced sorting, among other things. + + So instead, a temporary array is produced that contains the input suffix twice. + This is then fed into the suffix array sort, and the doubled matches are filtered out. + After this point, suffixes are sorted in their mirrored form, and the correct result can be derived + + The result of this is an O(2n) algorithm, which vastly outperforms a naive O(n log n) algorithm, + but is still far from ideal. However, this will have to do until a better solution is devised. + + Although to be fair, BWT is inferior to the bijective BWT anyway, so it may not be worth the effort. +*/ + +inline auto BWT(array_view input) -> vector { + auto size = input.size(); + vector output; + output.reserve(8 + 8 + size); + for(uint byte : range(8)) output.append(size >> byte * 8); + for(uint byte : range(8)) output.append(0x00); + + vector buffer; + buffer.reserve(2 * size); + for(uint offset : range(size)) buffer.append(input[offset]); + for(uint offset : range(size)) buffer.append(input[offset]); + + auto suffixes = SuffixArray(buffer); + + vector prefixes; + prefixes.reserve(size); + + for(uint offset : range(2 * size + 1)) { + uint suffix = suffixes[offset]; + if(suffix >= size) continue; //beyond the bounds of the original input string + prefixes.append(suffix); + } + + uint64_t root = 0; + for(uint offset : range(size)) { + uint suffix = prefixes[offset]; + if(suffix == 0) root = offset, suffix = size; + output.append(input[--suffix]); + } + for(uint byte : range(8)) output[8 + byte] = root >> byte * 8; + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/html.hpp b/waterbox/bsnescore/bsnes/nall/encode/html.hpp new file mode 100644 index 0000000000..6e4fd04b1a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/html.hpp @@ -0,0 +1,18 @@ +#pragma once + +namespace nall::Encode { + +inline auto HTML(const string& input) -> string { + string output; + for(char c : input) { + if(c == '&' ) { output.append("&" ); continue; } + if(c == '<' ) { output.append("<" ); continue; } + if(c == '>' ) { output.append(">" ); continue; } + if(c == '"' ) { output.append("""); continue; } + if(c == '\'') { output.append("'"); continue; } + output.append(c); + } + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/huffman.hpp b/waterbox/bsnescore/bsnes/nall/encode/huffman.hpp new file mode 100644 index 0000000000..18aa25f186 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/huffman.hpp @@ -0,0 +1,84 @@ +#pragma once + +namespace nall::Encode { + +inline auto Huffman(array_view input) -> vector { + vector output; + for(uint byte : range(8)) output.append(input.size() >> byte * 8); + + struct Node { + uint frequency = 0; + uint parent = 0; + uint lhs = 0; + uint rhs = 0; + }; + array nodes; + for(uint offset : range(input.size())) nodes[input[offset]].frequency++; + + uint count = 0; + for(uint offset : range(511)) { + if(nodes[offset].frequency) count++; + else nodes[offset].parent = 511; + } + + auto minimum = [&] { + uint frequency = ~0, minimum = 511; + for(uint index : range(511)) { + if(!nodes[index].parent && nodes[index].frequency && nodes[index].frequency < frequency) { + frequency = nodes[index].frequency; + minimum = index; + } + } + return minimum; + }; + + //group the least two frequently used nodes until only one node remains + uint index = 256; + for(uint remaining = max(2, count); remaining >= 2; remaining--) { + uint lhs = minimum(); + nodes[lhs].parent = index; + uint rhs = minimum(); + nodes[rhs].parent = index; + if(remaining == 2) index = nodes[lhs].parent = nodes[rhs].parent = 511; + nodes[index].lhs = lhs; + nodes[index].rhs = rhs; + nodes[index].parent = 0; + nodes[index].frequency = nodes[lhs].frequency + nodes[rhs].frequency; + index++; + } + + uint byte = 0, bits = 0; + auto write = [&](bool bit) { + byte = byte << 1 | bit; + if(++bits == 8) output.append(byte), bits = 0; + }; + + //only the upper half of the table is needed for decompression + //the first 256 nodes are always treated as leaf nodes + for(uint offset : range(256)) { + for(uint index : reverse(range(9))) write(nodes[256 + offset].lhs >> index & 1); + for(uint index : reverse(range(9))) write(nodes[256 + offset].rhs >> index & 1); + } + + for(uint byte : input) { + uint node = byte, length = 0; + uint256_t sequence = 0; + //traversing the array produces the bitstream in reverse order + do { + uint parent = nodes[node].parent; + bool bit = nodes[nodes[node].parent].rhs == node; + sequence = sequence << 1 | bit; + length++; + node = parent; + } while(node != 511); + //output the generated bits in the correct order + for(uint index : range(length)) { + write(sequence >> index & 1); + } + } + while(bits) write(0); + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/lzsa.hpp b/waterbox/bsnescore/bsnes/nall/encode/lzsa.hpp new file mode 100644 index 0000000000..2ca59b6eaf --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/lzsa.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace nall::Encode { + +inline auto LZSA(array_view input) -> vector { + vector output; + for(uint byte : range(8)) output.append(input.size() >> byte * 8); + + auto suffixArray = SuffixArray(input).lpf(); + uint index = 0; + vector flags; + vector literals; + vector stringLengths; + vector stringOffsets; + + uint byte = 0, bits = 0; + auto flagWrite = [&](bool bit) { + byte = byte << 1 | bit; + if(++bits == 8) flags.append(byte), bits = 0; + }; + + auto literalWrite = [&](uint8_t literal) { + literals.append(literal); + }; + + auto lengthWrite = [&](uint64_t length) { + if(length < 1 << 7) length = length << 1 | 0b1; + else if(length < 1 << 14) length = length << 2 | 0b10; + else if(length < 1 << 21) length = length << 3 | 0b100; + else if(length < 1 << 28) length = length << 4 | 0b1000; + else /*length < 1 << 35*/length = length << 5 | 0b10000; + while(length) stringLengths.append(length), length >>= 8; + }; + + auto offsetWrite = [&](uint offset) { + stringOffsets.append(offset >> 0); if(index < 1 << 8) return; + stringOffsets.append(offset >> 8); if(index < 1 << 16) return; + stringOffsets.append(offset >> 16); if(index < 1 << 24) return; + stringOffsets.append(offset >> 24); + }; + + while(index < input.size()) { + int length, offset; + suffixArray.previous(length, offset, index); + +/* for(uint ahead = 1; ahead <= 2; ahead++) { + int aheadLength, aheadOffset; + suffixArray.previous(aheadLength, aheadOffset, index + ahead); + if(aheadLength > length && aheadOffset >= 0) { + length = 0; + break; + } + } */ + + if(length < 6 || offset < 0) { + flagWrite(0); + literalWrite(input[index++]); + } else { + flagWrite(1); + lengthWrite(length - 6); + offsetWrite(index - offset); + index += length; + } + } + while(bits) flagWrite(0); + + auto save = [&](const vector& buffer) { + for(uint byte : range(8)) output.append(buffer.size() >> byte * 8); + output.append(buffer); + }; + + save(Encode::Huffman(flags)); + save(Encode::Huffman(literals)); + save(Encode::Huffman(stringLengths)); + save(Encode::Huffman(stringOffsets)); + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/mtf.hpp b/waterbox/bsnescore/bsnes/nall/encode/mtf.hpp new file mode 100644 index 0000000000..42f24b1958 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/mtf.hpp @@ -0,0 +1,30 @@ +#pragma once + +//move to front + +namespace nall::Encode { + +inline auto MTF(array_view input) -> vector { + vector output; + output.resize(input.size()); + + uint8_t order[256]; + for(uint n : range(256)) order[n] = n; + + for(uint offset : range(input.size())) { + uint data = input[offset]; + for(uint index : range(256)) { + uint value = order[index]; + if(value == data) { + output[offset] = index; + memory::move(&order[1], &order[0], index); + order[0] = value; + break; + } + } + } + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/rle.hpp b/waterbox/bsnescore/bsnes/nall/encode/rle.hpp new file mode 100644 index 0000000000..da88fff978 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/rle.hpp @@ -0,0 +1,56 @@ +#pragma once + +namespace nall::Encode { + +template //S = word size; M = match length +inline auto RLE(array_view input) -> vector { + vector output; + for(uint byte : range(8)) output.append(input.size() >> byte * 8); + + uint base = 0; + uint skip = 0; + + auto load = [&](uint offset) -> uint8_t { + return input(offset); + }; + + auto read = [&](uint offset) -> uint64_t { + uint64_t value = 0; + for(uint byte : range(S)) value |= load(offset + byte) << byte * 8; + return value; + }; + + auto write = [&](uint64_t value) -> void { + for(uint byte : range(S)) output.append(value >> byte * 8); + }; + + auto flush = [&] { + output.append(skip - 1); + do { + write(read(base)); + base += S; + } while(--skip); + }; + + while(base + S * skip < input.size()) { + uint same = 1; + for(uint offset = base + S * (skip + 1); offset < input.size(); offset += S) { + if(read(offset) != read(base + S * skip)) break; + if(++same == 127 + M) break; + } + + if(same < M) { + if(++skip == 128) flush(); + } else { + if(skip) flush(); + output.append(128 | same - M); + write(read(base)); + base += S * same; + } + } + if(skip) flush(); + + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/url.hpp b/waterbox/bsnescore/bsnes/nall/encode/url.hpp new file mode 100644 index 0000000000..1f422e5d22 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/url.hpp @@ -0,0 +1,27 @@ +#pragma once + +namespace nall::Encode { + +inline auto URL(string_view input) -> string { + string output; + for(auto c : input) { + //unreserved characters + if(c >= 'A' && c <= 'Z') { output.append(c); continue; } + if(c >= 'a' && c <= 'z') { output.append(c); continue; } + if(c >= '0' && c <= '9') { output.append(c); continue; } + if(c == '-' || c == '_' || c == '.' || c == '~') { output.append(c); continue; } + + //special characters + if(c == ' ') { output.append('+'); continue; } + + //reserved characters + uint hi = (c >> 4) & 15; + uint lo = (c >> 0) & 15; + output.append('%'); + output.append((char)(hi < 10 ? ('0' + hi) : ('a' + hi - 10))); + output.append((char)(lo < 10 ? ('0' + lo) : ('a' + lo - 10))); + } + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/wav.hpp b/waterbox/bsnescore/bsnes/nall/encode/wav.hpp new file mode 100644 index 0000000000..1b42485576 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/wav.hpp @@ -0,0 +1,52 @@ +#pragma once + +namespace nall::Encode { + +struct WAV { + static auto stereo_16bit(const string& filename, array_view left, array_view right, uint frequency) -> bool { + if(left.size() != right.size()) return false; + static uint channels = 2; + static uint bits = 16; + static uint samples = left.size(); + + file_buffer fp; + if(!fp.open(filename, file::mode::write)) return false; + + fp.write('R'); + fp.write('I'); + fp.write('F'); + fp.write('F'); + fp.writel(4 + (8 + 16) + (8 + samples * 4), 4); + + fp.write('W'); + fp.write('A'); + fp.write('V'); + fp.write('E'); + + fp.write('f'); + fp.write('m'); + fp.write('t'); + fp.write(' '); + fp.writel(16, 4); + fp.writel(1, 2); + fp.writel(channels, 2); + fp.writel(frequency, 4); + fp.writel(frequency * channels * bits, 4); + fp.writel(channels * bits, 2); + fp.writel(bits, 2); + + fp.write('d'); + fp.write('a'); + fp.write('t'); + fp.write('a'); + fp.writel(samples * 4, 4); + for(uint sample : range(samples)) { + fp.writel(left[sample], 2); + fp.writel(right[sample], 2); + } + + return true; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/encode/zip.hpp b/waterbox/bsnescore/bsnes/nall/encode/zip.hpp new file mode 100644 index 0000000000..b98376d76c --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/encode/zip.hpp @@ -0,0 +1,101 @@ +#pragma once + +//creates uncompressed ZIP archives + +#include +#include + +namespace nall::Encode { + +struct ZIP { + ZIP(const string& filename) { + fp.open(filename, file::mode::write); + timestamp = time(nullptr); + } + + //append path: append("path/"); + //append file: append("path/file", data, size); + auto append(string filename, const uint8_t* data = nullptr, uint size = 0u, time_t timestamp = 0) -> void { + filename.transform("\\", "/"); + if(!timestamp) timestamp = this->timestamp; + uint32_t checksum = Hash::CRC32({data, size}).digest().hex(); + directory.append({filename, timestamp, checksum, size, (uint32_t)fp.offset()}); + + fp.writel(0x04034b50, 4); //signature + fp.writel(0x0014, 2); //minimum version (2.0) + fp.writel(0x0000, 2); //general purpose bit flags + fp.writel(0x0000, 2); //compression method (0 = uncompressed) + fp.writel(makeTime(timestamp), 2); + fp.writel(makeDate(timestamp), 2); + fp.writel(checksum, 4); + fp.writel(size, 4); //compressed size + fp.writel(size, 4); //uncompressed size + fp.writel(filename.length(), 2); //file name length + fp.writel(0x0000, 2); //extra field length + fp.print(filename); //file name + + fp.write({data, size}); //file data + } + + ~ZIP() { + //central directory + uint baseOffset = fp.offset(); + for(auto& entry : directory) { + fp.writel(0x02014b50, 4); //signature + fp.writel(0x0014, 2); //version made by (2.0) + fp.writel(0x0014, 2); //version needed to extract (2.0) + fp.writel(0x0000, 2); //general purpose bit flags + fp.writel(0x0000, 2); //compression method (0 = uncompressed) + fp.writel(makeTime(entry.timestamp), 2); + fp.writel(makeDate(entry.timestamp), 2); + fp.writel(entry.checksum, 4); + fp.writel(entry.size, 4); //compressed size + fp.writel(entry.size, 4); //uncompressed size + fp.writel(entry.filename.length(), 2); //file name length + fp.writel(0x0000, 2); //extra field length + fp.writel(0x0000, 2); //file comment length + fp.writel(0x0000, 2); //disk number start + fp.writel(0x0000, 2); //internal file attributes + fp.writel(0x00000000, 4); //external file attributes + fp.writel(entry.offset, 4); //relative offset of file header + fp.print(entry.filename); + } + uint finishOffset = fp.offset(); + + //end of central directory + fp.writel(0x06054b50, 4); //signature + fp.writel(0x0000, 2); //number of this disk + fp.writel(0x0000, 2); //disk where central directory starts + fp.writel(directory.size(), 2); //number of central directory records on this disk + fp.writel(directory.size(), 2); //total number of central directory records + fp.writel(finishOffset - baseOffset, 4); //size of central directory + fp.writel(baseOffset, 4); //offset of central directory + fp.writel(0x0000, 2); //comment length + + fp.close(); + } + +protected: + auto makeTime(time_t timestamp) -> uint16_t { + tm* info = localtime(×tamp); + return (info->tm_hour << 11) | (info->tm_min << 5) | (info->tm_sec >> 1); + } + + auto makeDate(time_t timestamp) -> uint16_t { + tm* info = localtime(×tamp); + return ((info->tm_year - 80) << 9) | ((1 + info->tm_mon) << 5) + (info->tm_mday); + } + + file_buffer fp; + time_t timestamp; + struct entry_t { + string filename; + time_t timestamp; + uint32_t checksum; + uint32_t size; + uint32_t offset; + }; + vector directory; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/endian.hpp b/waterbox/bsnescore/bsnes/nall/endian.hpp new file mode 100644 index 0000000000..7a01b9a1b8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/endian.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +#if defined(ENDIAN_LSB) + //little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 + #define order_lsb1(a) a + #define order_lsb2(a,b) a,b + #define order_lsb3(a,b,c) a,b,c + #define order_lsb4(a,b,c,d) a,b,c,d + #define order_lsb5(a,b,c,d,e) a,b,c,d,e + #define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h + #define order_msb1(a) a + #define order_msb2(a,b) b,a + #define order_msb3(a,b,c) c,b,a + #define order_msb4(a,b,c,d) d,c,b,a + #define order_msb5(a,b,c,d,e) e,d,c,b,a + #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a +#elif defined(ENDIAN_MSB) + //big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 + #define order_lsb1(a) a + #define order_lsb2(a,b) b,a + #define order_lsb3(a,b,c) c,b,a + #define order_lsb4(a,b,c,d) d,c,b,a + #define order_lsb5(a,b,c,d,e) e,d,c,b,a + #define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a + #define order_msb1(a) a + #define order_msb2(a,b) a,b + #define order_msb3(a,b,c) a,b,c + #define order_msb4(a,b,c,d) a,b,c,d + #define order_msb5(a,b,c,d,e) a,b,c,d,e + #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h +#else + #error "Unknown endian. Please specify in nall/intrinsics.hpp" +#endif diff --git a/waterbox/bsnescore/bsnes/nall/file-buffer.hpp b/waterbox/bsnescore/bsnes/nall/file-buffer.hpp new file mode 100644 index 0000000000..6dcf905e1a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/file-buffer.hpp @@ -0,0 +1,249 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + +//on Windows (at least for 7 and earlier), FILE* is not buffered +//thus, reading/writing one byte at a time will be dramatically slower +//on all other OSes, FILE* is buffered +//in order to ensure good performance, file_buffer implements its own buffer +//this speeds up Windows substantially, without harming performance elsewhere much + +struct file_buffer { + struct mode { enum : uint { read, write, modify, append }; }; + struct index { enum : uint { absolute, relative }; }; + + file_buffer(const file_buffer&) = delete; + auto operator=(const file_buffer&) -> file_buffer& = delete; + + file_buffer() = default; + file_buffer(const string& filename, uint mode) { open(filename, mode); } + + file_buffer(file_buffer&& source) { operator=(move(source)); } + + ~file_buffer() { close(); } + + auto operator=(file_buffer&& source) -> file_buffer& { + buffer = source.buffer; + bufferOffset = source.bufferOffset; + bufferDirty = source.bufferDirty; + fileHandle = source.fileHandle; + fileOffset = source.fileOffset; + fileSize = source.fileSize; + fileMode = source.fileMode; + + source.bufferOffset = -1; + source.bufferDirty = false; + source.fileHandle = nullptr; + source.fileOffset = 0; + source.fileSize = 0; + source.fileMode = mode::read; + + return *this; + } + + explicit operator bool() const { + return (bool)fileHandle; + } + + auto read() -> uint8_t { + if(!fileHandle) return 0; //file not open + if(fileMode == mode::write) return 0; //reads not permitted + if(fileOffset >= fileSize) return 0; //cannot read past end of file + bufferSynchronize(); + return buffer[fileOffset++ & buffer.size() - 1]; + } + + template auto readl(uint length = 1) -> T { + T data = 0; + for(uint n : range(length)) { + data |= (T)read() << n * 8; + } + return data; + } + + template auto readm(uint length = 1) -> T { + T data = 0; + while(length--) { + data <<= 8; + data |= read(); + } + return data; + } + + auto reads(uint length) -> string { + string result; + result.resize(length); + for(auto& byte : result) byte = read(); + return result; + } + + auto read(array_span memory) -> void { + for(auto& byte : memory) byte = read(); + } + + auto write(uint8_t data) -> void { + if(!fileHandle) return; //file not open + if(fileMode == mode::read) return; //writes not permitted + bufferSynchronize(); + buffer[fileOffset++ & buffer.size() - 1] = data; + bufferDirty = true; + if(fileOffset > fileSize) fileSize = fileOffset; + } + + template auto writel(T data, uint length = 1) -> void { + while(length--) { + write(uint8_t(data)); + data >>= 8; + } + } + + template auto writem(T data, uint length = 1) -> void { + for(uint n : reverse(range(length))) { + write(uint8_t(data >> n * 8)); + } + } + + auto writes(const string& s) -> void { + for(auto& byte : s) write(byte); + } + + auto write(array_view memory) -> void { + for(auto& byte : memory) write(byte); + } + + template auto print(P&&... p) -> void { + string s{forward

(p)...}; + for(auto& byte : s) write(byte); + } + + auto flush() -> void { + bufferFlush(); + fflush(fileHandle); + } + + auto seek(int64_t offset, uint index_ = index::absolute) -> void { + if(!fileHandle) return; + bufferFlush(); + + int64_t seekOffset = fileOffset; + switch(index_) { + case index::absolute: seekOffset = offset; break; + case index::relative: seekOffset += offset; break; + } + + if(seekOffset < 0) seekOffset = 0; //cannot seek before start of file + if(seekOffset > fileSize) { + if(fileMode == mode::read) { //cannot seek past end of file + seekOffset = fileSize; + } else { //pad file to requested location + fileOffset = fileSize; + while(fileSize < seekOffset) write(0); + } + } + + fileOffset = seekOffset; + } + + auto offset() const -> uint64_t { + if(!fileHandle) return 0; + return fileOffset; + } + + auto size() const -> uint64_t { + if(!fileHandle) return 0; + return fileSize; + } + + auto truncate(uint64_t size) -> bool { + if(!fileHandle) return false; + #if defined(API_POSIX) + return ftruncate(fileno(fileHandle), size) == 0; + #elif defined(API_WINDOWS) + return _chsize(fileno(fileHandle), size) == 0; + #endif + } + + auto end() const -> bool { + if(!fileHandle) return true; + return fileOffset >= fileSize; + } + + auto open(const string& filename, uint mode_) -> bool { + close(); + + switch(fileMode = mode_) { + #if defined(API_POSIX) + case mode::read: fileHandle = fopen(filename, "rb" ); break; + case mode::write: fileHandle = fopen(filename, "wb+"); break; //need read permission for buffering + case mode::modify: fileHandle = fopen(filename, "rb+"); break; + case mode::append: fileHandle = fopen(filename, "wb+"); break; + #elif defined(API_WINDOWS) + case mode::read: fileHandle = _wfopen(utf16_t(filename), L"rb" ); break; + case mode::write: fileHandle = _wfopen(utf16_t(filename), L"wb+"); break; + case mode::modify: fileHandle = _wfopen(utf16_t(filename), L"rb+"); break; + case mode::append: fileHandle = _wfopen(utf16_t(filename), L"wb+"); break; + #endif + } + if(!fileHandle) return false; + + bufferOffset = -1; + fileOffset = 0; + fseek(fileHandle, 0, SEEK_END); + fileSize = ftell(fileHandle); + fseek(fileHandle, 0, SEEK_SET); + return true; + } + + auto close() -> void { + if(!fileHandle) return; + bufferFlush(); + fclose(fileHandle); + fileHandle = nullptr; + } + +private: + array buffer; + int bufferOffset = -1; + bool bufferDirty = false; + FILE* fileHandle = nullptr; + uint64_t fileOffset = 0; + uint64_t fileSize = 0; + uint fileMode = mode::read; + + auto bufferSynchronize() -> void { + if(!fileHandle) return; + if(bufferOffset == (fileOffset & ~(buffer.size() - 1))) return; + + bufferFlush(); + bufferOffset = fileOffset & ~(buffer.size() - 1); + fseek(fileHandle, bufferOffset, SEEK_SET); + uint64_t length = bufferOffset + buffer.size() <= fileSize ? buffer.size() : fileSize & buffer.size() - 1; + if(length) (void)fread(buffer.data(), 1, length, fileHandle); + } + + auto bufferFlush() -> void { + if(!fileHandle) return; //file not open + if(fileMode == mode::read) return; //buffer cannot be written to + if(bufferOffset < 0) return; //buffer unused + if(!bufferDirty) return; //buffer unmodified since read + + fseek(fileHandle, bufferOffset, SEEK_SET); + uint64_t length = bufferOffset + buffer.size() <= fileSize ? buffer.size() : fileSize & buffer.size() - 1; + if(length) (void)fwrite(buffer.data(), 1, length, fileHandle); + bufferOffset = -1; + bufferDirty = false; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/file-map.hpp b/waterbox/bsnescore/bsnes/nall/file-map.hpp new file mode 100644 index 0000000000..647a823be6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/file-map.hpp @@ -0,0 +1,225 @@ +#pragma once + +#include +#include + +#include +#include +#if defined(PLATFORM_WINDOWS) + #include +#else + #include + #include + #include + #include + #include +#endif + +#if !defined(MAP_NORESERVE) + //not supported on FreeBSD; flag removed in 11.0 + #define MAP_NORESERVE 0 +#endif + +namespace nall { + +struct file_map { + struct mode { enum : uint { read, write, modify, append }; }; + + file_map(const file_map&) = delete; + auto operator=(const file_map&) = delete; + + file_map() = default; + file_map(file_map&& source) { operator=(move(source)); } + file_map(const string& filename, uint mode) { open(filename, mode); } + + ~file_map() { close(); } + + explicit operator bool() const { return _open; } + auto size() const -> uint64_t { return _size; } + auto data() -> uint8_t* { return _data; } + auto data() const -> const uint8_t* { return _data; } + +//auto operator=(file_map&& source) -> file_map&; +//auto open(const string& filename, uint mode) -> bool; +//auto close() -> void; + +private: + bool _open = false; //zero-byte files return _data = nullptr, _size = 0 + uint8_t* _data = nullptr; + uint64_t _size = 0; + + #if defined(API_WINDOWS) + + HANDLE _file = INVALID_HANDLE_VALUE; + HANDLE _map = INVALID_HANDLE_VALUE; + +public: + auto operator=(file_map&& source) -> file_map& { + _open = source._open; + _data = source._data; + _size = source._size; + _file = source._file; + _map = source._map; + + source._open = false; + source._data = nullptr; + source._size = 0; + source._file = INVALID_HANDLE_VALUE; + source._map = INVALID_HANDLE_VALUE; + + return *this; + } + + auto open(const string& filename, uint mode_) -> bool { + close(); + if(file::exists(filename) && file::size(filename) == 0) return _open = true; + + int desiredAccess, creationDisposition, protection, mapAccess; + + switch(mode_) { + default: return false; + case mode::read: + desiredAccess = GENERIC_READ; + creationDisposition = OPEN_EXISTING; + protection = PAGE_READONLY; + mapAccess = FILE_MAP_READ; + break; + case mode::write: + //write access requires read access + desiredAccess = GENERIC_WRITE; + creationDisposition = CREATE_ALWAYS; + protection = PAGE_READWRITE; + mapAccess = FILE_MAP_ALL_ACCESS; + break; + case mode::modify: + desiredAccess = GENERIC_READ | GENERIC_WRITE; + creationDisposition = OPEN_EXISTING; + protection = PAGE_READWRITE; + mapAccess = FILE_MAP_ALL_ACCESS; + break; + case mode::append: + desiredAccess = GENERIC_READ | GENERIC_WRITE; + creationDisposition = CREATE_NEW; + protection = PAGE_READWRITE; + mapAccess = FILE_MAP_ALL_ACCESS; + break; + } + + _file = CreateFileW(utf16_t(filename), desiredAccess, FILE_SHARE_READ, nullptr, + creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr); + if(_file == INVALID_HANDLE_VALUE) return false; + + _size = GetFileSize(_file, nullptr); + + _map = CreateFileMapping(_file, nullptr, protection, 0, _size, nullptr); + if(_map == INVALID_HANDLE_VALUE) { + CloseHandle(_file); + _file = INVALID_HANDLE_VALUE; + return false; + } + + _data = (uint8_t*)MapViewOfFile(_map, mapAccess, 0, 0, _size); + return _open = true; + } + + auto close() -> void { + if(_data) { + UnmapViewOfFile(_data); + _data = nullptr; + } + + if(_map != INVALID_HANDLE_VALUE) { + CloseHandle(_map); + _map = INVALID_HANDLE_VALUE; + } + + if(_file != INVALID_HANDLE_VALUE) { + CloseHandle(_file); + _file = INVALID_HANDLE_VALUE; + } + + _open = false; + } + + #else + + int _fd = -1; + +public: + auto operator=(file_map&& source) -> file_map& { + _open = source._open; + _data = source._data; + _size = source._size; + _fd = source._fd; + + source._open = false; + source._data = nullptr; + source._size = 0; + source._fd = -1; + + return *this; + } + + auto open(const string& filename, uint mode_) -> bool { + close(); + if(file::exists(filename) && file::size(filename) == 0) return _open = true; + + int openFlags = 0; + int mmapFlags = 0; + + switch(mode_) { + default: return false; + case mode::read: + openFlags = O_RDONLY; + mmapFlags = PROT_READ; + break; + case mode::write: + openFlags = O_RDWR | O_CREAT; //mmap() requires read access + mmapFlags = PROT_WRITE; + break; + case mode::modify: + openFlags = O_RDWR; + mmapFlags = PROT_READ | PROT_WRITE; + break; + case mode::append: + openFlags = O_RDWR | O_CREAT; + mmapFlags = PROT_READ | PROT_WRITE; + break; + } + + _fd = ::open(filename, openFlags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if(_fd < 0) return false; + + struct stat _stat; + fstat(_fd, &_stat); + _size = _stat.st_size; + + _data = (uint8_t*)mmap(nullptr, _size, mmapFlags, MAP_SHARED | MAP_NORESERVE, _fd, 0); + if(_data == MAP_FAILED) { + _data = nullptr; + ::close(_fd); + _fd = -1; + return false; + } + + return _open = true; + } + + auto close() -> void { + if(_data) { + munmap(_data, _size); + _data = nullptr; + } + + if(_fd >= 0) { + ::close(_fd); + _fd = -1; + } + + _open = false; + } + + #endif +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/file.hpp b/waterbox/bsnescore/bsnes/nall/file.hpp new file mode 100644 index 0000000000..fc4284e446 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/file.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include + +namespace nall { + +struct file : inode { + struct mode { enum : uint { read, write, modify, append }; }; + struct index { enum : uint { absolute, relative }; }; + + file() = delete; + + static auto open(const string& filename, uint mode) -> file_buffer { + return file_buffer{filename, mode}; + } + + static auto copy(const string& sourcename, const string& targetname) -> bool { + if(sourcename == targetname) return true; + if(auto reader = file::open(sourcename, mode::read)) { + if(auto writer = file::open(targetname, mode::write)) { + for(uint64_t n : range(reader.size())) writer.write(reader.read()); + return true; + } + } + return false; + } + + //attempt to rename file first + //this will fail if paths point to different file systems; fall back to copy+remove in this case + static auto move(const string& sourcename, const string& targetname) -> bool { + if(sourcename == targetname) return true; + if(rename(sourcename, targetname)) return true; + if(!writable(sourcename)) return false; + if(copy(sourcename, targetname)) return remove(sourcename), true; + return false; + } + + static auto truncate(const string& filename, uint64_t size) -> bool { + #if defined(API_POSIX) + return truncate(filename, size) == 0; + #elif defined(API_WINDOWS) + if(auto fp = _wfopen(utf16_t(filename), L"rb+")) { + bool result = _chsize(fileno(fp), size) == 0; + fclose(fp); + return result; + } + return false; + #endif + } + + //returns false if specified filename is a directory + static auto exists(const string& filename) -> bool { + #if defined(API_POSIX) + struct stat data; + if(stat(filename, &data) != 0) return false; + #elif defined(API_WINDOWS) + struct __stat64 data; + if(_wstat64(utf16_t(filename), &data) != 0) return false; + #endif + return !(data.st_mode & S_IFDIR); + } + + static auto size(const string& filename) -> uint64_t { + #if defined(API_POSIX) + struct stat data; + stat(filename, &data); + #elif defined(API_WINDOWS) + struct __stat64 data; + _wstat64(utf16_t(filename), &data); + #endif + return S_ISREG(data.st_mode) ? data.st_size : 0u; + } + + static auto read(const string& filename) -> vector { + vector memory; + if(auto fp = file::open(filename, mode::read)) { + memory.resize(fp.size()); + fp.read(memory); + } + return memory; + } + + static auto read(const string& filename, array_span memory) -> bool { + if(auto fp = file::open(filename, mode::read)) return fp.read(memory), true; + return false; + } + + static auto write(const string& filename, array_view memory) -> bool { + if(auto fp = file::open(filename, mode::write)) return fp.write(memory), true; + return false; + } + + //create an empty file (will replace existing files) + static auto create(const string& filename) -> bool { + if(auto fp = file::open(filename, mode::write)) return true; + return false; + } + + static auto sha256(const string& filename) -> string { + return Hash::SHA256(read(filename)).digest(); + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/function.hpp b/waterbox/bsnescore/bsnes/nall/function.hpp new file mode 100644 index 0000000000..3b59e05c2f --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/function.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include + +namespace nall { + +template struct function; + +template struct function R> { + using cast = auto (*)(P...) -> R; + + //value = true if auto L::operator()(P...) -> R exists + template struct is_compatible { + template static auto exists(T*) -> const typename is_same().operator()(declval

()...))>::type; + template static auto exists(...) -> const false_type; + static constexpr bool value = decltype(exists(0))::value; + }; + + function() {} + function(const function& source) { operator=(source); } + function(auto (*function)(P...) -> R) { callback = new global(function); } + template function(auto (C::*function)(P...) -> R, C* object) { callback = new member(function, object); } + template function(auto (C::*function)(P...) const -> R, C* object) { callback = new member((auto (C::*)(P...) -> R)function, object); } + template::value>> function(const L& object) { callback = new lambda(object); } + explicit function(void* function) { if(function) callback = new global((auto (*)(P...) -> R)function); } + ~function() { if(callback) delete callback; } + + explicit operator bool() const { return callback; } + auto operator()(P... p) const -> R { return (*callback)(forward

(p)...); } + auto reset() -> void { if(callback) { delete callback; callback = nullptr; } } + + auto operator=(const function& source) -> function& { + if(this != &source) { + if(callback) { delete callback; callback = nullptr; } + if(source.callback) callback = source.callback->copy(); + } + return *this; + } + + auto operator=(void* source) -> function& { + if(callback) { delete callback; callback = nullptr; } + callback = new global((auto (*)(P...) -> R)source); + return *this; + } + +private: + struct container { + virtual auto operator()(P... p) const -> R = 0; + virtual auto copy() const -> container* = 0; + virtual ~container() = default; + }; + + container* callback = nullptr; + + struct global : container { + auto (*function)(P...) -> R; + auto operator()(P... p) const -> R { return function(forward

(p)...); } + auto copy() const -> container* { return new global(function); } + global(auto (*function)(P...) -> R) : function(function) {} + }; + + template struct member : container { + auto (C::*function)(P...) -> R; + C* object; + auto operator()(P... p) const -> R { return (object->*function)(forward

(p)...); } + auto copy() const -> container* { return new member(function, object); } + member(auto (C::*function)(P...) -> R, C* object) : function(function), object(object) {} + }; + + template struct lambda : container { + mutable L object; + auto operator()(P... p) const -> R { return object(forward

(p)...); } + auto copy() const -> container* { return new lambda(object); } + lambda(const L& object) : object(object) {} + }; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/galois-field.hpp b/waterbox/bsnescore/bsnes/nall/galois-field.hpp new file mode 100644 index 0000000000..38f226979f --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/galois-field.hpp @@ -0,0 +1,70 @@ +#pragma once + +//table-driven galois field modulo 2 +//do not use with GF(2^17) or larger + +namespace nall { + +template +struct GaloisField { + using type = GaloisField; + + GaloisField(uint x = 0) : x(x) {} + operator field() const { return x; } + + auto operator^(field y) const -> type { return x ^ y; } + auto operator+(field y) const -> type { return x ^ y; } + auto operator-(field y) const -> type { return x ^ y; } + auto operator*(field y) const -> type { return x && y ? exp(log(x) + log(y)) : 0; } + auto operator/(field y) const -> type { return x && y ? exp(log(x) + Elements - log(y)) : 0; } + + auto& operator =(field y) { return x = y, *this; } + auto& operator^=(field y) { return x = operator^(y), *this; } + auto& operator+=(field y) { return x = operator^(y), *this; } + auto& operator-=(field y) { return x = operator^(y), *this; } + auto& operator*=(field y) { return x = operator*(y), *this; } + auto& operator/=(field y) { return x = operator/(y), *this; } + + auto pow(field y) const -> type { return exp(log(x) * y); } + auto inv() const -> type { return exp(Elements - log(x)); } // 1/x + + static auto log(uint x) -> uint { + enum : uint { Size = bit::round(Elements), Mask = Size - 1 }; + static array log = [] { + uint shift = 0, polynomial = Polynomial; + while(polynomial >>= 1) shift++; + shift--; + + array log; + field x = 1; + for(uint n : range(Elements)) { + log[x] = n; + x = x << 1 ^ (x >> shift ? Polynomial : 0); + } + log[0] = 0; //-inf (undefined) + return log; + }(); + return log[x & Mask]; + } + + static auto exp(uint x) -> uint { + static array exp = [] { + uint shift = 0, polynomial = Polynomial; + while(polynomial >>= 1) shift++; + shift--; + + array exp; + field x = 1; + for(uint n : range(Elements)) { + exp[n] = x; + x = x << 1 ^ (x >> shift ? Polynomial : 0); + } + return exp; + }(); + return exp[x % Elements]; + } + + field x; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hash/crc16.hpp b/waterbox/bsnescore/bsnes/nall/hash/crc16.hpp new file mode 100644 index 0000000000..8058a40bac --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hash/crc16.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +namespace nall::Hash { + +struct CRC16 : Hash { + using Hash::input; + + CRC16(array_view buffer = {}) { + reset(); + input(buffer); + } + + auto reset() -> void override { + checksum = ~0; + } + + auto input(uint8_t value) -> void override { + checksum = (checksum >> 8) ^ table(checksum ^ value); + } + + auto output() const -> vector override { + vector result; + for(auto n : reverse(range(2))) result.append(~checksum >> n * 8); + return result; + } + + auto value() const -> uint16_t { + return ~checksum; + } + +private: + static auto table(uint8_t index) -> uint16_t { + static uint16_t table[256] = {0}; + static bool initialized = false; + + if(!initialized) { + initialized = true; + for(auto index : range(256)) { + uint16_t crc = index; + for(auto bit : range(8)) { + crc = (crc >> 1) ^ (crc & 1 ? 0x8408 : 0); + } + table[index] = crc; + } + } + + return table[index]; + } + + uint16_t checksum = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hash/crc32.hpp b/waterbox/bsnescore/bsnes/nall/hash/crc32.hpp new file mode 100644 index 0000000000..39318713d6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hash/crc32.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +namespace nall::Hash { + +struct CRC32 : Hash { + using Hash::input; + + CRC32(array_view buffer = {}) { + reset(); + input(buffer); + } + + auto reset() -> void override { + checksum = ~0; + } + + auto input(uint8_t value) -> void override { + checksum = (checksum >> 8) ^ table(checksum ^ value); + } + + auto output() const -> vector { + vector result; + for(auto n : reverse(range(4))) result.append(~checksum >> n * 8); + return result; + } + + auto value() const -> uint32_t { + return ~checksum; + } + +private: + static auto table(uint8_t index) -> uint32_t { + static uint32_t table[256] = {0}; + static bool initialized = false; + + if(!initialized) { + initialized = true; + for(auto index : range(256)) { + uint32_t crc = index; + for(auto bit : range(8)) { + crc = (crc >> 1) ^ (crc & 1 ? 0xedb8'8320 : 0); + } + table[index] = crc; + } + } + + return table[index]; + } + + uint32_t checksum = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hash/crc64.hpp b/waterbox/bsnescore/bsnes/nall/hash/crc64.hpp new file mode 100644 index 0000000000..7364b0d8db --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hash/crc64.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +namespace nall::Hash { + +struct CRC64 : Hash { + using Hash::input; + + CRC64(array_view buffer = {}) { + reset(); + input(buffer); + } + + auto reset() -> void override { + checksum = ~0; + } + + auto input(uint8_t value) -> void override { + checksum = (checksum >> 8) ^ table(checksum ^ value); + } + + auto output() const -> vector { + vector result; + for(auto n : reverse(range(8))) result.append(~checksum >> n * 8); + return result; + } + + auto value() const -> uint64_t { + return ~checksum; + } + +private: + static auto table(uint8_t index) -> uint64_t { + static uint64_t table[256] = {0}; + static bool initialized = false; + + if(!initialized) { + initialized = true; + for(auto index : range(256)) { + uint64_t crc = index; + for(auto bit : range(8)) { + crc = (crc >> 1) ^ (crc & 1 ? 0xc96c'5795'd787'0f42 : 0); + } + table[index] = crc; + } + } + + return table[index]; + } + + uint64_t checksum = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hash/hash.hpp b/waterbox/bsnescore/bsnes/nall/hash/hash.hpp new file mode 100644 index 0000000000..4565fe3d53 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hash/hash.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +//cannot use constructor inheritance due to needing to call virtual reset(); +//instead, define a macro to reduce boilerplate code in every Hash subclass +#define nallHash(Name) \ + Name() { reset(); } \ + Name(const void* data, uint64_t size) : Name() { input(data, size); } \ + Name(const vector& data) : Name() { input(data); } \ + Name(const string& data) : Name() { input(data); } \ + using Hash::input; \ + +namespace nall::Hash { + +struct Hash { + virtual auto reset() -> void = 0; + virtual auto input(uint8_t data) -> void = 0; + virtual auto output() const -> vector = 0; + + auto input(array_view data) -> void { + for(auto byte : data) input(byte); + } + + auto input(const void* data, uint64_t size) -> void { + auto p = (const uint8_t*)data; + while(size--) input(*p++); + } + + auto input(const vector& data) -> void { + for(auto byte : data) input(byte); + } + + auto input(const string& data) -> void { + for(auto byte : data) input(byte); + } + + auto digest() const -> string { + string result; + for(auto n : output()) result.append(hex(n, 2L)); + return result; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hash/sha224.hpp b/waterbox/bsnescore/bsnes/nall/hash/sha224.hpp new file mode 100644 index 0000000000..6bcf7858ad --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hash/sha224.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include + +namespace nall::Hash { + +struct SHA224 : Hash { + using Hash::input; + + SHA224(array_view buffer = {}) { + reset(); + input(buffer); + } + + auto reset() -> void override { + for(auto& n : queue) n = 0; + for(auto& n : w) n = 0; + for(auto n : range(8)) h[n] = square(n); + queued = length = 0; + } + + auto input(uint8_t value) -> void override { + byte(value); + length++; + } + + auto output() const -> vector override { + SHA224 self(*this); + self.finish(); + vector result; + for(auto h : range(7)) { + for(auto n : reverse(range(4))) result.append(self.h[h] >> n * 8); + } + return result; + } + + auto value() const -> uint256_t { + uint256_t value = 0; + for(auto byte : output()) value = value << 8 | byte; + return value; + } + +private: + auto byte(uint8_t value) -> void { + uint32_t shift = (3 - (queued & 3)) * 8; + queue[queued >> 2] &= ~(0xff << shift); + queue[queued >> 2] |= (value << shift); + if(++queued == 64) block(), queued = 0; + } + + auto block() -> void { + for(auto n : range(16)) w[n] = queue[n]; + for(auto n : range(16, 64)) { + uint32_t a = ror(w[n - 15], 7) ^ ror(w[n - 15], 18) ^ (w[n - 15] >> 3); + uint32_t b = ror(w[n - 2], 17) ^ ror(w[n - 2], 19) ^ (w[n - 2] >> 10); + w[n] = w[n - 16] + w[n - 7] + a + b; + } + uint32_t t[8]; + for(auto n : range(8)) t[n] = h[n]; + for(auto n : range(64)) { + uint32_t a = ror(t[0], 2) ^ ror(t[0], 13) ^ ror(t[0], 22); + uint32_t b = ror(t[4], 6) ^ ror(t[4], 11) ^ ror(t[4], 25); + uint32_t c = (t[0] & t[1]) ^ (t[0] & t[2]) ^ (t[1] & t[2]); + uint32_t d = (t[4] & t[5]) ^ (~t[4] & t[6]); + uint32_t e = t[7] + w[n] + cube(n) + b + d; + t[7] = t[6]; t[6] = t[5]; t[5] = t[4]; t[4] = t[3] + e; + t[3] = t[2]; t[2] = t[1]; t[1] = t[0]; t[0] = a + c + e; + } + for(auto n : range(8)) h[n] += t[n]; + } + + auto finish() -> void { + byte(0x80); + while(queued != 56) byte(0x00); + for(auto n : range(8)) byte(length * 8 >> (7 - n) * 8); + } + + auto square(uint n) -> uint32_t { + static const uint32_t value[8] = { + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4, + }; + return value[n]; + } + + auto cube(uint n) -> uint32_t { + static const uint32_t value[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + return value[n]; + } + + uint32_t queue[16] = {0}; + uint32_t w[64] = {0}; + uint32_t h[8] = {0}; + uint32_t queued = 0; + uint64_t length = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hash/sha256.hpp b/waterbox/bsnescore/bsnes/nall/hash/sha256.hpp new file mode 100644 index 0000000000..27f42a0479 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hash/sha256.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include + +namespace nall::Hash { + +struct SHA256 : Hash { + using Hash::input; + + SHA256(array_view buffer = {}) { + reset(); + input(buffer); + } + + auto reset() -> void override { + for(auto& n : queue) n = 0; + for(auto& n : w) n = 0; + for(auto n : range(8)) h[n] = square(n); + queued = length = 0; + } + + auto input(uint8_t value) -> void override { + byte(value); + length++; + } + + auto output() const -> vector override { + SHA256 self(*this); + self.finish(); + vector result; + for(auto h : self.h) { + for(auto n : reverse(range(4))) result.append(h >> n * 8); + } + return result; + } + + auto value() const -> uint256_t { + uint256_t value = 0; + for(auto byte : output()) value = value << 8 | byte; + return value; + } + +private: + auto byte(uint8_t value) -> void { + uint32_t shift = (3 - (queued & 3)) * 8; + queue[queued >> 2] &= ~(0xff << shift); + queue[queued >> 2] |= (value << shift); + if(++queued == 64) block(), queued = 0; + } + + auto block() -> void { + for(auto n : range(16)) w[n] = queue[n]; + for(auto n : range(16, 64)) { + uint32_t a = ror(w[n - 15], 7) ^ ror(w[n - 15], 18) ^ (w[n - 15] >> 3); + uint32_t b = ror(w[n - 2], 17) ^ ror(w[n - 2], 19) ^ (w[n - 2] >> 10); + w[n] = w[n - 16] + w[n - 7] + a + b; + } + uint32_t t[8]; + for(auto n : range(8)) t[n] = h[n]; + for(auto n : range(64)) { + uint32_t a = ror(t[0], 2) ^ ror(t[0], 13) ^ ror(t[0], 22); + uint32_t b = ror(t[4], 6) ^ ror(t[4], 11) ^ ror(t[4], 25); + uint32_t c = (t[0] & t[1]) ^ (t[0] & t[2]) ^ (t[1] & t[2]); + uint32_t d = (t[4] & t[5]) ^ (~t[4] & t[6]); + uint32_t e = t[7] + w[n] + cube(n) + b + d; + t[7] = t[6]; t[6] = t[5]; t[5] = t[4]; t[4] = t[3] + e; + t[3] = t[2]; t[2] = t[1]; t[1] = t[0]; t[0] = a + c + e; + } + for(auto n : range(8)) h[n] += t[n]; + } + + auto finish() -> void { + byte(0x80); + while(queued != 56) byte(0x00); + for(auto n : range(8)) byte(length * 8 >> (7 - n) * 8); + } + + auto square(uint n) -> uint32_t { + static const uint32_t value[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + }; + return value[n]; + } + + auto cube(uint n) -> uint32_t { + static const uint32_t value[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + return value[n]; + } + + uint32_t queue[16] = {0}; + uint32_t w[64] = {0}; + uint32_t h[8] = {0}; + uint32_t queued = 0; + uint64_t length = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hash/sha384.hpp b/waterbox/bsnescore/bsnes/nall/hash/sha384.hpp new file mode 100644 index 0000000000..09dbbdb181 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hash/sha384.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include + +namespace nall::Hash { + +struct SHA384 : Hash { + using Hash::input; + + SHA384(array_view buffer = {}) { + reset(); + input(buffer); + } + + auto reset() -> void override { + for(auto& n : queue) n = 0; + for(auto& n : w) n = 0; + for(auto n : range(8)) h[n] = square(n); + queued = length = 0; + } + + auto input(uint8_t data) -> void override { + byte(data); + length++; + } + + auto output() const -> vector override { + SHA384 self(*this); + self.finish(); + vector result; + for(auto h : range(6)) { + for(auto n : reverse(range(8))) result.append(self.h[h] >> n * 8); + } + return result; + } + + auto value() const -> uint512_t { + uint512_t value = 0; + for(auto byte : output()) value = value << 8 | byte; + return value; + } + +private: + auto byte(uint8_t data) -> void { + uint64_t shift = (7 - (queued & 7)) * 8; + queue[queued >> 3] &=~((uint64_t)0xff << shift); + queue[queued >> 3] |= ((uint64_t)data << shift); + if(++queued == 128) block(), queued = 0; + } + + auto block() -> void { + for(auto n : range(16)) w[n] = queue[n]; + for(auto n : range(16, 80)) { + uint64_t a = ror(w[n - 15], 1) ^ ror(w[n - 15], 8) ^ (w[n - 15] >> 7); + uint64_t b = ror(w[n - 2], 19) ^ ror(w[n - 2], 61) ^ (w[n - 2] >> 6); + w[n] = w[n - 16] + w[n - 7] + a + b; + } + uint64_t t[8]; + for(auto n : range(8)) t[n] = h[n]; + for(auto n : range(80)) { + uint64_t a = ror(t[0], 28) ^ ror(t[0], 34) ^ ror(t[0], 39); + uint64_t b = ror(t[4], 14) ^ ror(t[4], 18) ^ ror(t[4], 41); + uint64_t c = (t[0] & t[1]) ^ (t[0] & t[2]) ^ (t[1] & t[2]); + uint64_t d = (t[4] & t[5]) ^ (~t[4] & t[6]); + uint64_t e = t[7] + w[n] + cube(n) + b + d; + t[7] = t[6]; t[6] = t[5]; t[5] = t[4]; t[4] = t[3] + e; + t[3] = t[2]; t[2] = t[1]; t[1] = t[0]; t[0] = a + c + e; + } + for(auto n : range(8)) h[n] += t[n]; + } + + auto finish() -> void { + byte(0x80); + while(queued != 112) byte(0x00); + for(auto n : range(16)) byte(length * 8 >> (15 - n) * 8); + } + + auto square(uint n) -> uint64_t { + static const uint64_t data[8] = { + 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, + 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4, + }; + return data[n]; + } + + auto cube(uint n) -> uint64_t { + static const uint64_t data[80] = { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70, + 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, + 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, + 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817, + }; + return data[n]; + } + + uint64_t queue[16] = {0}; + uint64_t w[80] = {0}; + uint64_t h[8] = {0}; + uint64_t queued = 0; + uint128_t length = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hash/sha512.hpp b/waterbox/bsnescore/bsnes/nall/hash/sha512.hpp new file mode 100644 index 0000000000..f76d2d52bd --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hash/sha512.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include + +namespace nall::Hash { + +struct SHA512 : Hash { + using Hash::input; + + SHA512(array_view buffer = {}) { + reset(); + input(buffer); + } + + auto reset() -> void override { + for(auto& n : queue) n = 0; + for(auto& n : w) n = 0; + for(auto n : range(8)) h[n] = square(n); + queued = length = 0; + } + + auto input(uint8_t data) -> void override { + byte(data); + length++; + } + + auto output() const -> vector override { + SHA512 self(*this); + self.finish(); + vector result; + for(auto h : self.h) { + for(auto n : reverse(range(8))) result.append(h >> n * 8); + } + return result; + } + + auto value() const -> uint512_t { + uint512_t value = 0; + for(auto byte : output()) value = value << 8 | byte; + return value; + } + +private: + auto byte(uint8_t data) -> void { + uint64_t shift = (7 - (queued & 7)) * 8; + queue[queued >> 3] &=~((uint64_t)0xff << shift); + queue[queued >> 3] |= ((uint64_t)data << shift); + if(++queued == 128) block(), queued = 0; + } + + auto block() -> void { + for(auto n : range(16)) w[n] = queue[n]; + for(auto n : range(16, 80)) { + uint64_t a = ror(w[n - 15], 1) ^ ror(w[n - 15], 8) ^ (w[n - 15] >> 7); + uint64_t b = ror(w[n - 2], 19) ^ ror(w[n - 2], 61) ^ (w[n - 2] >> 6); + w[n] = w[n - 16] + w[n - 7] + a + b; + } + uint64_t t[8]; + for(auto n : range(8)) t[n] = h[n]; + for(auto n : range(80)) { + uint64_t a = ror(t[0], 28) ^ ror(t[0], 34) ^ ror(t[0], 39); + uint64_t b = ror(t[4], 14) ^ ror(t[4], 18) ^ ror(t[4], 41); + uint64_t c = (t[0] & t[1]) ^ (t[0] & t[2]) ^ (t[1] & t[2]); + uint64_t d = (t[4] & t[5]) ^ (~t[4] & t[6]); + uint64_t e = t[7] + w[n] + cube(n) + b + d; + t[7] = t[6]; t[6] = t[5]; t[5] = t[4]; t[4] = t[3] + e; + t[3] = t[2]; t[2] = t[1]; t[1] = t[0]; t[0] = a + c + e; + } + for(auto n : range(8)) h[n] += t[n]; + } + + auto finish() -> void { + byte(0x80); + while(queued != 112) byte(0x00); + for(auto n : range(16)) byte(length * 8 >> (15 - n) * 8); + } + + auto square(uint n) -> uint64_t { + static const uint64_t data[8] = { + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, + }; + return data[n]; + } + + auto cube(uint n) -> uint64_t { + static const uint64_t data[80] = { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70, + 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, + 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, + 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817, + }; + return data[n]; + } + + uint64_t queue[16] = {0}; + uint64_t w[80] = {0}; + uint64_t h[8] = {0}; + uint64_t queued = 0; + uint128_t length = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hashset.hpp b/waterbox/bsnescore/bsnes/nall/hashset.hpp new file mode 100644 index 0000000000..6812eafc2d --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hashset.hpp @@ -0,0 +1,133 @@ +#pragma once + +//hashset +// +//search: O(1) average; O(n) worst +//insert: O(1) average; O(n) worst +//remove: O(1) average; O(n) worst +// +//requirements: +// auto T::hash() const -> uint; +// auto T::operator==(const T&) const -> bool; + +namespace nall { + +template +struct hashset { + hashset() = default; + hashset(uint length) : length(bit::round(length)) {} + hashset(const hashset& source) { operator=(source); } + hashset(hashset&& source) { operator=(move(source)); } + ~hashset() { reset(); } + + auto operator=(const hashset& source) -> hashset& { + reset(); + if(source.pool) { + for(uint n : range(source.count)) { + insert(*source.pool[n]); + } + } + return *this; + } + + auto operator=(hashset&& source) -> hashset& { + reset(); + pool = source.pool; + length = source.length; + count = source.count; + source.pool = nullptr; + source.length = 8; + source.count = 0; + return *this; + } + + explicit operator bool() const { return count; } + auto capacity() const -> uint { return length; } + auto size() const -> uint { return count; } + + auto reset() -> void { + if(pool) { + for(uint n : range(length)) { + if(pool[n]) { + delete pool[n]; + pool[n] = nullptr; + } + } + delete pool; + pool = nullptr; + } + length = 8; + count = 0; + } + + auto reserve(uint size) -> void { + //ensure all items will fit into pool (with <= 50% load) and amortize growth + size = bit::round(max(size, count << 1)); + T** copy = new T*[size](); + + if(pool) { + for(uint n : range(length)) { + if(pool[n]) { + uint hash = (*pool[n]).hash() & (size - 1); + while(copy[hash]) if(++hash >= size) hash = 0; + copy[hash] = pool[n]; + pool[n] = nullptr; + } + } + } + + delete pool; + pool = copy; + length = size; + } + + auto find(const T& value) -> maybe { + if(!pool) return nothing; + + uint hash = value.hash() & (length - 1); + while(pool[hash]) { + if(value == *pool[hash]) return *pool[hash]; + if(++hash >= length) hash = 0; + } + + return nothing; + } + + auto insert(const T& value) -> maybe { + if(!pool) pool = new T*[length](); + + //double pool size when load is >= 50% + if(count >= (length >> 1)) reserve(length << 1); + count++; + + uint hash = value.hash() & (length - 1); + while(pool[hash]) if(++hash >= length) hash = 0; + pool[hash] = new T(value); + + return *pool[hash]; + } + + auto remove(const T& value) -> bool { + if(!pool) return false; + + uint hash = value.hash() & (length - 1); + while(pool[hash]) { + if(value == *pool[hash]) { + delete pool[hash]; + pool[hash] = nullptr; + count--; + return true; + } + if(++hash >= length) hash = 0; + } + + return false; + } + +protected: + T** pool = nullptr; + uint length = 8; //length of pool + uint count = 0; //number of objects inside of the pool +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/hid.hpp b/waterbox/bsnescore/bsnes/nall/hid.hpp new file mode 100644 index 0000000000..e002c656ab --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/hid.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include +#include + +namespace nall::HID { + +struct Input { + Input(const string& name) : _name(name) {} + + auto name() const -> string { return _name; } + auto value() const -> int16_t { return _value; } + auto setValue(int16_t value) -> void { _value = value; } + +private: + string _name; + int16_t _value = 0; + friend class Group; +}; + +struct Group : vector { + Group(const string& name) : _name(name) {} + + auto name() const -> string { return _name; } + auto input(uint id) -> Input& { return operator[](id); } + auto append(const string& name) -> void { vector::append(Input{name}); } + + auto find(const string& name) const -> maybe { + for(auto id : range(size())) { + if(operator[](id)._name == name) return id; + } + return nothing; + } + +private: + string _name; + friend class Device; +}; + +struct Device : vector { + Device(const string& name) : _name(name) {} + + //id => {pathID}-{vendorID}-{productID} + auto pathID() const -> uint32_t { return (uint32_t)(_id >> 32); } //32-63 + auto vendorID() const -> uint16_t { return (uint16_t)(_id >> 16); } //16-31 + auto productID() const -> uint16_t { return (uint16_t)(_id >> 0); } // 0-15 + + auto setPathID (uint32_t pathID ) -> void { _id = (uint64_t)pathID << 32 | vendorID() << 16 | productID() << 0; } + auto setVendorID (uint16_t vendorID ) -> void { _id = (uint64_t)pathID() << 32 | vendorID << 16 | productID() << 0; } + auto setProductID(uint16_t productID) -> void { _id = (uint64_t)pathID() << 32 | vendorID() << 16 | productID << 0; } + + virtual auto isNull() const -> bool { return false; } + virtual auto isKeyboard() const -> bool { return false; } + virtual auto isMouse() const -> bool { return false; } + virtual auto isJoypad() const -> bool { return false; } + + auto name() const -> string { return _name; } + auto id() const -> uint64_t { return _id; } + auto setID(uint64_t id) -> void { _id = id; } + auto group(uint id) -> Group& { return operator[](id); } + auto append(const string& name) -> void { vector::append(Group{name}); } + + auto find(const string& name) const -> maybe { + for(auto id : range(size())) { + if(operator[](id)._name == name) return id; + } + return nothing; + } + +private: + string _name; + uint64_t _id = 0; +}; + +struct Null : Device { + enum : uint16_t { GenericVendorID = 0x0000, GenericProductID = 0x0000 }; + + Null() : Device("Null") {} + auto isNull() const -> bool { return true; } +}; + +struct Keyboard : Device { + enum : uint16_t { GenericVendorID = 0x0000, GenericProductID = 0x0001 }; + enum GroupID : uint { Button }; + + Keyboard() : Device("Keyboard") { append("Button"); } + auto isKeyboard() const -> bool { return true; } + auto buttons() -> Group& { return group(GroupID::Button); } +}; + +struct Mouse : Device { + enum : uint16_t { GenericVendorID = 0x0000, GenericProductID = 0x0002 }; + enum GroupID : uint { Axis, Button }; + + Mouse() : Device("Mouse") { append("Axis"), append("Button"); } + auto isMouse() const -> bool { return true; } + auto axes() -> Group& { return group(GroupID::Axis); } + auto buttons() -> Group& { return group(GroupID::Button); } +}; + +struct Joypad : Device { + enum : uint16_t { GenericVendorID = 0x0000, GenericProductID = 0x0003 }; + enum GroupID : uint { Axis, Hat, Trigger, Button }; + + Joypad() : Device("Joypad") { append("Axis"), append("Hat"), append("Trigger"), append("Button"); } + auto isJoypad() const -> bool { return true; } + auto axes() -> Group& { return group(GroupID::Axis); } + auto hats() -> Group& { return group(GroupID::Hat); } + auto triggers() -> Group& { return group(GroupID::Trigger); } + auto buttons() -> Group& { return group(GroupID::Button); } + + auto rumble() const -> bool { return _rumble; } + auto setRumble(bool rumble) -> void { _rumble = rumble; } + +private: + bool _rumble = false; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/http/client.hpp b/waterbox/bsnescore/bsnes/nall/http/client.hpp new file mode 100644 index 0000000000..17a6a43f80 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/http/client.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include + +namespace nall::HTTP { + +struct Client : Role { + inline auto open(const string& hostname, uint port = 80) -> bool; + inline auto upload(const Request& request) -> bool; + inline auto download(const Request& request) -> Response; + inline auto close() -> void; + ~Client() { close(); } + +private: + int fd = -1; + addrinfo* info = nullptr; +}; + +auto Client::open(const string& hostname, uint port) -> bool { + addrinfo hint = {0}; + hint.ai_family = AF_UNSPEC; + hint.ai_socktype = SOCK_STREAM; + hint.ai_flags = AI_ADDRCONFIG; + + if(getaddrinfo(hostname, string{port}, &hint, &info) != 0) return close(), false; + + fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol); + if(fd < 0) return close(), false; + + if(connect(fd, info->ai_addr, info->ai_addrlen) < 0) return close(), false; + return true; +} + +auto Client::upload(const Request& request) -> bool { + return Role::upload(fd, request); +} + +auto Client::download(const Request& request) -> Response { + Response response(request); + Role::download(fd, response); + return response; +} + +auto Client::close() -> void { + if(fd) { + ::close(fd); + fd = -1; + } + + if(info) { + freeaddrinfo(info); + info = nullptr; + } +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/http/message.hpp b/waterbox/bsnescore/bsnes/nall/http/message.hpp new file mode 100644 index 0000000000..e4c543d10a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/http/message.hpp @@ -0,0 +1,104 @@ +#pragma once + +//httpMessage: base class for httpRequest and httpResponse +//provides shared functionality + +namespace nall::HTTP { + +struct Variable { + string name; + string value; +}; + +struct SharedVariable { + SharedVariable(const nall::string& name = "", const nall::string& value = "") : shared(new Variable{name, value}) {} + + explicit operator bool() const { return (bool)shared->name; } + auto operator()() const { return shared->value; } + auto& operator=(const nall::string& value) { shared->value = value; return *this; } + + auto name() const { return shared->name; } + auto value() const { return shared->value; } + auto string() const { return nall::string{shared->value}.strip().replace("\r", ""); } + auto boolean() const { return string() == "true"; } + auto integer() const { return string().integer(); } + auto natural() const { return string().natural(); } + auto real() const { return string().real(); } + + auto& setName(const nall::string& name) { shared->name = name; return *this; } + auto& setValue(const nall::string& value = "") { shared->value = value; return *this; } + + shared_pointer shared; +}; + +struct Variables { + auto operator[](const string& name) const -> SharedVariable { + for(auto& variable : variables) { + if(variable.shared->name.iequals(name)) return variable; + } + return {}; + } + + auto operator()(const string& name) -> SharedVariable { + for(auto& variable : variables) { + if(variable.shared->name.iequals(name)) return variable; + } + return append(name); + } + + auto find(const string& name) const -> vector { + vector result; + for(auto& variable : variables) { + if(variable.shared->name.iequals(name)) result.append(variable); + } + return result; + } + + auto assign(const string& name, const string& value = "") -> SharedVariable { + for(auto& variable : variables) { + if(variable.shared->name.iequals(name)) { + variable.shared->value = value; + return variable; + } + } + return append(name, value); + } + + auto append(const string& name, const string& value = "") -> SharedVariable { + SharedVariable variable{name, value}; + variables.append(variable); + return variable; + } + + auto remove(const string& name) -> void { + for(auto n : reverse(range(variables.size()))) { + if(variables[n].shared->name.iequals(name)) variables.remove(n); + } + } + + auto size() const { return variables.size(); } + auto begin() const { return variables.begin(); } + auto end() const { return variables.end(); } + auto begin() { return variables.begin(); } + auto end() { return variables.end(); } + + vector variables; +}; + +struct Message { + using type = Message; + + virtual auto head(const function& callback) const -> bool = 0; + virtual auto setHead() -> bool = 0; + + virtual auto body(const function& callback) const -> bool = 0; + virtual auto setBody() -> bool = 0; + + Variables header; + +//private: + string _head; + string _body; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/http/request.hpp b/waterbox/bsnescore/bsnes/nall/http/request.hpp new file mode 100644 index 0000000000..081c1c7404 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/http/request.hpp @@ -0,0 +1,184 @@ +#pragma once + +#include +#include +#include + +namespace nall::HTTP { + +struct Request : Message { + using type = Request; + + enum class RequestType : uint { None, Head, Get, Post }; + + explicit operator bool() const { return requestType() != RequestType::None; } + + inline auto head(const function& callback) const -> bool override; + inline auto setHead() -> bool override; + + inline auto body(const function& callback) const -> bool override; + inline auto setBody() -> bool override; + + auto ipv4() const -> bool { return _ipv6 == false; } + auto ipv6() const -> bool { return _ipv6 == true; } + auto ip() const -> string { return _ip; } + + auto requestType() const -> RequestType { return _requestType; } + auto setRequestType(RequestType value) -> void { _requestType = value; } + + auto path() const -> string { return _path; } + auto setPath(const string& value) -> void { _path = value; } + + Variables cookie; + Variables get; + Variables post; + +//private: + bool _ipv6 = false; + string _ip; + RequestType _requestType = RequestType::None; + string _path; +}; + +auto Request::head(const function& callback) const -> bool { + if(!callback) return false; + string output; + + string request = path(); + if(get.size()) { + request.append("?"); + for(auto& variable : get) { + request.append(Encode::URL(variable.name()), "=", Encode::URL(variable.value()), "&"); + } + request.trimRight("&", 1L); + } + + switch(requestType()) { + case RequestType::Head: output.append("HEAD ", request, " HTTP/1.1\r\n"); break; + case RequestType::Get : output.append("GET ", request, " HTTP/1.1\r\n"); break; + case RequestType::Post: output.append("POST ", request, " HTTP/1.1\r\n"); break; + default: return false; + } + + for(auto& variable : header) { + output.append(variable.name(), ": ", variable.value(), "\r\n"); + } + output.append("\r\n"); + + return callback(output.data(), output.size()); +} + +auto Request::setHead() -> bool { + auto headers = _head.split("\n"); + string request = headers.takeLeft().trimRight("\r", 1L); + string requestHost; + + if(request.iendsWith(" HTTP/1.0")) request.itrimRight(" HTTP/1.0", 1L); + else if(request.iendsWith(" HTTP/1.1")) request.itrimRight(" HTTP/1.1", 1L); + else return false; + + if(request.ibeginsWith("HEAD ")) request.itrimLeft("HEAD ", 1L), setRequestType(RequestType::Head); + else if(request.ibeginsWith("GET " )) request.itrimLeft("GET ", 1L), setRequestType(RequestType::Get ); + else if(request.ibeginsWith("POST ")) request.itrimLeft("POST ", 1L), setRequestType(RequestType::Post); + else return false; + + //decode absolute URIs + request.strip().itrimLeft("http://", 1L); + if(!request.beginsWith("/")) { + auto components = request.split("/", 1L); + requestHost = components(0); + request = {"/", components(1)}; + } + + auto components = request.split("?", 1L); + setPath(components(0)); + + if(auto queryString = components(1)) { + for(auto& block : queryString.split("&")) { + auto p = block.split("=", 1L); + auto name = Decode::URL(p(0)); + auto value = Decode::URL(p(1)); + if(name) get.append(name, value); + } + } + + for(auto& header : headers) { + if(header.beginsWith(" ") || header.beginsWith("\t")) continue; + auto part = header.split(":", 1L).strip(); + if(!part[0] || part.size() != 2) continue; + this->header.append(part[0], part[1]); + + if(part[0].iequals("Cookie")) { + for(auto& block : part[1].split(";")) { + auto p = block.split("=", 1L).strip(); + auto name = p(0); + auto value = p(1).trim("\"", "\"", 1L); + if(name) cookie.append(name, value); + } + } + } + + if(requestHost) header.assign("Host", requestHost); //request URI overrides host header + return true; +} + +auto Request::body(const function& callback) const -> bool { + if(!callback) return false; + + if(_body) { + return callback(_body.data(), _body.size()); + } + + return true; +} + +auto Request::setBody() -> bool { + if(requestType() == RequestType::Post) { + auto contentType = header["Content-Type"].value(); + if(contentType.iequals("application/x-www-form-urlencoded")) { + for(auto& block : _body.split("&")) { + auto p = block.trimRight("\r").split("=", 1L); + auto name = Decode::URL(p(0)); + auto value = Decode::URL(p(1)); + if(name) post.append(name, value); + } + } else if(contentType.imatch("multipart/form-data; boundary=?*")) { + auto boundary = contentType.itrimLeft("multipart/form-data; boundary=", 1L).trim("\"", "\"", 1L); + auto blocks = _body.split({"--", boundary}, 1024L); //limit blocks to prevent memory exhaustion + for(auto& block : blocks) block.trim("\r\n", "\r\n", 1L); + if(blocks.size() < 2 || (blocks.takeLeft(), !blocks.takeRight().beginsWith("--"))) return false; + for(auto& block : blocks) { + string name; + string filename; + string contentType; + + auto segments = block.split("\r\n\r\n", 1L); + for(auto& segment : segments(0).split("\r\n")) { + auto statement = segment.split(":", 1L); + if(statement(0).ibeginsWith("Content-Disposition")) { + for(auto& component : statement(1).split(";")) { + auto part = component.split("=", 1L).strip(); + if(part(0).iequals("name")) { + name = part(1).trim("\"", "\"", 1L); + } else if(part(0).iequals("filename")) { + filename = part(1).trim("\"", "\"", 1L); + } + } + } else if(statement(0).ibeginsWith("Content-Type")) { + contentType = statement(1).strip(); + } + } + + if(name) { + post.append(name, segments(1)); + post.append({name, ".filename"}, filename); + post.append({name, ".content-type"}, contentType); + } + } + } + } + + return true; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/http/response.hpp b/waterbox/bsnescore/bsnes/nall/http/response.hpp new file mode 100644 index 0000000000..a55d8701a2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/http/response.hpp @@ -0,0 +1,273 @@ +#pragma once + +#include + +namespace nall::HTTP { + +struct Response : Message { + using type = Response; + + Response() = default; + Response(const Request& request) { setRequest(request); } + + explicit operator bool() const { return responseType() != 0; } + auto operator()(uint responseType) -> type& { return setResponseType(responseType); } + + inline auto head(const function& callback) const -> bool override; + inline auto setHead() -> bool override; + + inline auto body(const function& callback) const -> bool override; + inline auto setBody() -> bool override; + + auto request() const -> const Request* { return _request; } + auto setRequest(const Request& value) -> type& { _request = &value; return *this; } + + auto responseType() const -> uint { return _responseType; } + auto setResponseType(uint value) -> type& { _responseType = value; return *this; } + + auto hasData() const -> bool { return (bool)_data; } + auto data() const -> const vector& { return _data; } + inline auto setData(const vector& value) -> type&; + + auto hasFile() const -> bool { return (bool)_file; } + auto file() const -> const string& { return _file; } + inline auto setFile(const string& value) -> type&; + + auto hasText() const -> bool { return (bool)_text; } + auto text() const -> const string& { return _text; } + inline auto setText(const string& value) -> type&; + + inline auto hasBody() const -> bool; + inline auto findContentLength() const -> uint; + inline auto findContentType() const -> string; + inline auto findContentType(const string& suffix) const -> string; + inline auto findResponseType() const -> string; + inline auto setFileETag() -> void; + + const Request* _request = nullptr; + uint _responseType = 0; + vector _data; + string _file; + string _text; +}; + +auto Response::head(const function& callback) const -> bool { + if(!callback) return false; + string output; + + if(auto request = this->request()) { + if(auto eTag = header["ETag"]) { + if(eTag.value() == request->header["If-None-Match"].value()) { + output.append("HTTP/1.1 304 Not Modified\r\n"); + output.append("Connection: close\r\n"); + output.append("\r\n"); + return callback(output.data(), output.size()); + } + } + } + + output.append("HTTP/1.1 ", findResponseType(), "\r\n"); + for(auto& variable : header) { + output.append(variable.name(), ": ", variable.value(), "\r\n"); + } + if(hasBody()) { + if(!header["Content-Length"] && !header["Transfer-Encoding"].value().iequals("chunked")) { + output.append("Content-Length: ", findContentLength(), "\r\n"); + } + if(!header["Content-Type"]) { + output.append("Content-Type: ", findContentType(), "\r\n"); + } + } + if(!header["Connection"]) { + output.append("Connection: close\r\n"); + } + output.append("\r\n"); + + return callback(output.data(), output.size()); +} + +auto Response::setHead() -> bool { + auto headers = _head.split("\n"); + string response = headers.takeLeft().trimRight("\r"); + + if(response.ibeginsWith("HTTP/1.0 ")) response.itrimLeft("HTTP/1.0 ", 1L); + else if(response.ibeginsWith("HTTP/1.1 ")) response.itrimLeft("HTTP/1.1 ", 1L); + else return false; + + setResponseType(response.natural()); + + for(auto& header : headers) { + if(header.beginsWith(" ") || header.beginsWith("\t")) continue; + auto variable = header.split(":", 1L).strip(); + if(variable.size() != 2) continue; + this->header.append(variable[0], variable[1]); + } + + return true; +} + +auto Response::body(const function& callback) const -> bool { + if(!callback) return false; + if(!hasBody()) return true; + bool chunked = header["Transfer-Encoding"].value() == "chunked"; + + if(chunked) { + string prefix = {hex(findContentLength()), "\r\n"}; + if(!callback(prefix.data(), prefix.size())) return false; + } + + if(_body) { + if(!callback(_body.data(), _body.size())) return false; + } else if(hasData()) { + if(!callback(data().data(), data().size())) return false; + } else if(hasFile()) { + file_map map(file(), file_map::mode::read); + if(!callback(map.data(), map.size())) return false; + } else if(hasText()) { + if(!callback(text().data(), text().size())) return false; + } else { + string response = findResponseType(); + if(!callback(response.data(), response.size())) return false; + } + + if(chunked) { + string suffix = {"\r\n0\r\n\r\n"}; + if(!callback(suffix.data(), suffix.size())) return false; + } + + return true; +} + +auto Response::setBody() -> bool { + return true; +} + +auto Response::hasBody() const -> bool { + if(auto request = this->request()) { + if(request->requestType() == Request::RequestType::Head) return false; + } + if(responseType() == 301) return false; + if(responseType() == 302) return false; + if(responseType() == 303) return false; + if(responseType() == 304) return false; + if(responseType() == 307) return false; + return true; +} + +auto Response::findContentLength() const -> uint { + if(auto contentLength = header["Content-Length"]) return contentLength.value().natural(); + if(_body) return _body.size(); + if(hasData()) return data().size(); + if(hasFile()) return file::size(file()); + if(hasText()) return text().size(); + return findResponseType().size(); +} + +auto Response::findContentType() const -> string { + if(auto contentType = header["Content-Type"]) return contentType.value(); + if(hasData()) return "application/octet-stream"; + if(hasFile()) return findContentType(Location::suffix(file())); + return "text/html; charset=utf-8"; +} + +auto Response::findContentType(const string& s) const -> string { + if(s == ".7z" ) return "application/x-7z-compressed"; + if(s == ".avi" ) return "video/avi"; + if(s == ".bml" ) return "text/plain; charset=utf-8"; + if(s == ".bz2" ) return "application/x-bzip2"; + if(s == ".css" ) return "text/css; charset=utf-8"; + if(s == ".gif" ) return "image/gif"; + if(s == ".gz" ) return "application/gzip"; + if(s == ".htm" ) return "text/html; charset=utf-8"; + if(s == ".html") return "text/html; charset=utf-8"; + if(s == ".ico" ) return "image/x-icon"; + if(s == ".jpg" ) return "image/jpeg"; + if(s == ".jpeg") return "image/jpeg"; + if(s == ".js" ) return "application/javascript"; + if(s == ".mka" ) return "audio/x-matroska"; + if(s == ".mkv" ) return "video/x-matroska"; + if(s == ".mp3" ) return "audio/mpeg"; + if(s == ".mp4" ) return "video/mp4"; + if(s == ".mpeg") return "video/mpeg"; + if(s == ".mpg" ) return "video/mpeg"; + if(s == ".ogg" ) return "audio/ogg"; + if(s == ".pdf" ) return "application/pdf"; + if(s == ".png" ) return "image/png"; + if(s == ".rar" ) return "application/x-rar-compressed"; + if(s == ".svg" ) return "image/svg+xml"; + if(s == ".tar" ) return "application/x-tar"; + if(s == ".txt" ) return "text/plain; charset=utf-8"; + if(s == ".wav" ) return "audio/vnd.wave"; + if(s == ".webm") return "video/webm"; + if(s == ".xml" ) return "text/xml; charset=utf-8"; + if(s == ".xz" ) return "application/x-xz"; + if(s == ".zip" ) return "application/zip"; + return "application/octet-stream"; //binary +} + +auto Response::findResponseType() const -> string { + switch(responseType()) { + case 200: return "200 OK"; + case 301: return "301 Moved Permanently"; + case 302: return "302 Found"; + case 303: return "303 See Other"; + case 304: return "304 Not Modified"; + case 307: return "307 Temporary Redirect"; + case 400: return "400 Bad Request"; + case 403: return "403 Forbidden"; + case 404: return "404 Not Found"; + case 500: return "500 Internal Server Error"; + case 501: return "501 Not Implemented"; + case 503: return "503 Service Unavailable"; + } + return "501 Not Implemented"; +} + +auto Response::setData(const vector& value) -> type& { + _data = value; + header.assign("Content-Length", value.size()); + return *this; +} + +auto Response::setFile(const string& value) -> type& { + //block path escalation exploits ("../" and "..\" in the file location) + bool valid = true; + for(uint n : range(value.size())) { + if(value(n + 0, '\0') != '.') continue; + if(value(n + 1, '\0') != '.') continue; + if(value(n + 2, '\0') != '/' && value(n + 2, '\0') != '\\') continue; + valid = false; + break; + } + if(!valid) return *this; + + //cache images for seven days + auto suffix = Location::suffix(value); + uint maxAge = 0; + if(suffix == ".svg" + || suffix == ".ico" + || suffix == ".png" + || suffix == ".gif" + || suffix == ".jpg" + || suffix == ".jpeg") { + maxAge = 7 * 24 * 60 * 60; + } + + _file = value; + header.assign("Content-Length", file::size(value)); + header.assign("ETag", {"\"", chrono::utc::datetime(file::timestamp(value, file::time::modify)), "\""}); + if(maxAge == 0) { + header.assign("Cache-Control", {"public"}); + } else { + header.assign("Cache-Control", {"public, max-age=", maxAge}); + } + return *this; +} + +auto Response::setText(const string& value) -> type& { + _text = value; + header.assign("Content-Length", value.size()); + return *this; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/http/role.hpp b/waterbox/bsnescore/bsnes/nall/http/role.hpp new file mode 100644 index 0000000000..5a9abcf6d6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/http/role.hpp @@ -0,0 +1,158 @@ +#pragma once + +//Role: base class for Client and Server +//provides shared functionality + +#include +#include + +namespace nall::HTTP { + +struct Role { + struct Settings { + int connectionLimit = 1 * 1024; //server + int headSizeLimit = 16 * 1024; //client, server + int bodySizeLimit = 65536 * 1024; //client, server + int chunkSize = 32 * 1024; //client, server + int threadStackSize = 128 * 1024; //server + int timeoutReceive = 15 * 1000; //server + int timeoutSend = 15 * 1000; //server + } settings; + + inline auto configure(const string& parameters) -> bool; + inline auto download(int fd, Message& message) -> bool; + inline auto upload(int fd, const Message& message) -> bool; +}; + +auto Role::configure(const string& parameters) -> bool { + auto document = BML::unserialize(parameters); + for(auto parameter : document) { + auto name = parameter.name(); + auto value = parameter.integer(); + + if(0); + else if(name == "connectionLimit") settings.connectionLimit = value; + else if(name == "headSizeLimit") settings.headSizeLimit = value; + else if(name == "bodySizeLimit") settings.bodySizeLimit = value; + else if(name == "chunkSize") settings.chunkSize = value; + else if(name == "threadStackSize") settings.threadStackSize = value; + else if(name == "timeoutReceive") settings.timeoutReceive = value; + else if(name == "timeoutSend") settings.timeoutSend = value; + } + return true; +} + +auto Role::download(int fd, Message& message) -> bool { + auto& head = message._head; + auto& body = message._body; + string chunk; + uint8_t packet[settings.chunkSize], *p = nullptr; + + head.reset(), head.reserve(4095); + body.reset(), body.reserve(4095); + + bool headReceived = false; + bool chunked = false; + bool chunkReceived = false; + bool chunkFooterReceived = true; + int length = 0; + int chunkLength = 0; + int contentLength = 0; + + while(true) { + if(auto limit = settings.headSizeLimit) if(head.size() >= limit) return false; + if(auto limit = settings.bodySizeLimit) if(body.size() >= limit) return false; + + if(headReceived && !chunked && body.size() >= contentLength) { + body.resize(contentLength); + break; + } + + if(length == 0) { + length = recv(fd, packet, settings.chunkSize, MSG_NOSIGNAL); + if(length <= 0) return false; + p = packet; + } + + if(!headReceived) { + head.append((char)*p++); + --length; + + if(head.endsWith("\r\n\r\n") || head.endsWith("\n\n")) { + headReceived = true; + if(!message.setHead()) return false; + chunked = message.header["Transfer-Encoding"].value().iequals("chunked"); + contentLength = message.header["Content-Length"].value().natural(); + } + + continue; + } + + if(chunked && !chunkReceived) { + char n = *p++; + --length; + + if(!chunkFooterReceived) { + if(n == '\n') chunkFooterReceived = true; + continue; + } + + chunk.append(n); + + if(chunk.endsWith("\r\n") || chunk.endsWith("\n")) { + chunkReceived = true; + chunkLength = chunk.hex(); + if(chunkLength == 0) break; + chunk.reset(); + } + + continue; + } + + if(!chunked) { + body.resize(body.size() + length); + memory::copy(body.get() + body.size() - length, p, length); + + p += length; + length = 0; + } else { + int transferLength = min(length, chunkLength); + body.resize(body.size() + transferLength); + memory::copy(body.get() + body.size() - transferLength, p, transferLength); + + p += transferLength; + length -= transferLength; + chunkLength -= transferLength; + + if(chunkLength == 0) { + chunkReceived = false; + chunkFooterReceived = false; + } + } + } + + if(!message.setBody()) return false; + return true; +} + +auto Role::upload(int fd, const Message& message) -> bool { + auto transfer = [&](const uint8_t* data, uint size) -> bool { + while(size) { + int length = send(fd, data, min(size, settings.chunkSize), MSG_NOSIGNAL); + if(length < 0) return false; + data += length; + size -= length; + } + return true; + }; + + if(message.head([&](const uint8_t* data, uint size) -> bool { return transfer(data, size); })) { + if(message.body([&](const uint8_t* data, uint size) -> bool { return transfer(data, size); })) { + return true; + } + } + + return false; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/http/server.hpp b/waterbox/bsnescore/bsnes/nall/http/server.hpp new file mode 100644 index 0000000000..3454ccf366 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/http/server.hpp @@ -0,0 +1,226 @@ +#pragma once + +#include +#include + +namespace nall::HTTP { + +struct Server : Role, service { + inline auto open(uint port = 8080, const string& serviceName = "", const string& command = "") -> bool; + inline auto main(const function& function = {}) -> void; + inline auto scan() -> string; + inline auto close() -> void; + ~Server() { close(); } + +private: + function callback; + std::atomic connections{0}; + + int fd4 = -1; + int fd6 = -1; + struct sockaddr_in addrin4 = {0}; + struct sockaddr_in6 addrin6 = {0}; + + auto ipv4() const -> bool { return fd4 >= 0; } + auto ipv6() const -> bool { return fd6 >= 0; } + + auto ipv4_close() -> void { if(fd4 >= 0) ::close(fd4); fd4 = -1; } + auto ipv6_close() -> void { if(fd6 >= 0) ::close(fd6); fd6 = -1; } + + auto ipv4_scan() -> bool; + auto ipv6_scan() -> bool; +}; + +auto Server::open(uint port, const string& serviceName, const string& command) -> bool { + if(serviceName) { + if(!service::command(serviceName, command)) return false; + } + + fd4 = socket(AF_INET, SOCK_STREAM, 0); + fd6 = socket(AF_INET6, SOCK_STREAM, 0); + if(!ipv4() && !ipv6()) return false; + + { + #if defined(SO_RCVTIMEO) + if(settings.timeoutReceive) { + struct timeval rcvtimeo; + rcvtimeo.tv_sec = settings.timeoutReceive / 1000; + rcvtimeo.tv_usec = settings.timeoutReceive % 1000 * 1000; + if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_RCVTIMEO, &rcvtimeo, sizeof(struct timeval)); + if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_RCVTIMEO, &rcvtimeo, sizeof(struct timeval)); + } + #endif + + #if defined(SO_SNDTIMEO) + if(settings.timeoutSend) { + struct timeval sndtimeo; + sndtimeo.tv_sec = settings.timeoutSend / 1000; + sndtimeo.tv_usec = settings.timeoutSend % 1000 * 1000; + if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_SNDTIMEO, &sndtimeo, sizeof(struct timeval)); + if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_SNDTIMEO, &sndtimeo, sizeof(struct timeval)); + } + #endif + + #if defined(SO_NOSIGPIPE) //BSD, OSX + int nosigpipe = 1; + if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(int)); + if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(int)); + #endif + + #if defined(SO_REUSEADDR) //BSD, Linux, OSX + int reuseaddr = 1; + if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)); + if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)); + #endif + + #if defined(SO_REUSEPORT) //BSD, OSX + int reuseport = 1; + if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_REUSEPORT, &reuseport, sizeof(int)); + if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_REUSEPORT, &reuseport, sizeof(int)); + #endif + } + + addrin4.sin_family = AF_INET; + addrin4.sin_addr.s_addr = htonl(INADDR_ANY); + addrin4.sin_port = htons(port); + + addrin6.sin6_family = AF_INET6; + addrin6.sin6_addr = in6addr_any; + addrin6.sin6_port = htons(port); + + if(bind(fd4, (struct sockaddr*)&addrin4, sizeof(addrin4)) < 0 || listen(fd4, SOMAXCONN) < 0) ipv4_close(); + if(bind(fd6, (struct sockaddr*)&addrin6, sizeof(addrin6)) < 0 || listen(fd6, SOMAXCONN) < 0) ipv6_close(); + return ipv4() || ipv6(); +} + +auto Server::main(const function& function) -> void { + callback = function; +} + +auto Server::scan() -> string { + if(auto command = service::receive()) return command; + if(connections >= settings.connectionLimit) return "busy"; + if(ipv4() && ipv4_scan()) return "ok"; + if(ipv6() && ipv6_scan()) return "ok"; + return "idle"; +} + +auto Server::ipv4_scan() -> bool { + struct pollfd query = {0}; + query.fd = fd4; + query.events = POLLIN; + poll(&query, 1, 0); + + if(query.fd == fd4 && query.revents & POLLIN) { + ++connections; + + thread::create([&](uintptr) { + thread::detach(); + + int clientfd = -1; + struct sockaddr_in settings = {0}; + socklen_t socklen = sizeof(sockaddr_in); + + clientfd = accept(fd4, (struct sockaddr*)&settings, &socklen); + if(clientfd < 0) return; + + uint32_t ip = ntohl(settings.sin_addr.s_addr); + + Request request; + request._ipv6 = false; + request._ip = { + (uint8_t)(ip >> 24), ".", + (uint8_t)(ip >> 16), ".", + (uint8_t)(ip >> 8), ".", + (uint8_t)(ip >> 0) + }; + + if(download(clientfd, request) && callback) { + auto response = callback(request); + upload(clientfd, response); + } else { + upload(clientfd, Response()); //"501 Not Implemented" + } + + ::close(clientfd); + --connections; + }, 0, settings.threadStackSize); + + return true; + } + + return false; +} + +auto Server::ipv6_scan() -> bool { + struct pollfd query = {0}; + query.fd = fd6; + query.events = POLLIN; + poll(&query, 1, 0); + + if(query.fd == fd6 && query.revents & POLLIN) { + ++connections; + + thread::create([&](uintptr) { + thread::detach(); + + int clientfd = -1; + struct sockaddr_in6 settings = {0}; + socklen_t socklen = sizeof(sockaddr_in6); + + clientfd = accept(fd6, (struct sockaddr*)&settings, &socklen); + if(clientfd < 0) return; + + uint8_t* ip = settings.sin6_addr.s6_addr; + uint16_t ipSegment[8]; + for(auto n : range(8)) ipSegment[n] = ip[n * 2 + 0] * 256 + ip[n * 2 + 1]; + + Request request; + request._ipv6 = true; + //RFC5952 IPv6 encoding: the first longest 2+ consecutive zero-sequence is compressed to "::" + int zeroOffset = -1; + int zeroLength = 0; + int zeroCounter = 0; + for(auto n : range(8)) { + uint16_t value = ipSegment[n]; + if(value == 0) zeroCounter++; + if(zeroCounter > zeroLength) { + zeroLength = zeroCounter; + zeroOffset = 1 + n - zeroLength; + } + if(value != 0) zeroCounter = 0; + } + if(zeroLength == 1) zeroOffset = -1; + for(uint n = 0; n < 8;) { + if(n == zeroOffset) { + request._ip.append(n == 0 ? "::" : ":"); + n += zeroLength; + } else { + uint16_t value = ipSegment[n]; + request._ip.append(hex(value), n++ != 7 ? ":" : ""); + } + } + + if(download(clientfd, request) && callback) { + auto response = callback(request); + upload(clientfd, response); + } else { + upload(clientfd, Response()); //"501 Not Implemented" + } + + ::close(clientfd); + --connections; + }, 0, settings.threadStackSize); + + return true; + } + + return false; +} + +auto Server::close() -> void { + ipv4_close(); + ipv6_close(); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/image.hpp b/waterbox/bsnescore/bsnes/nall/image.hpp new file mode 100644 index 0000000000..3124e73e3b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/image.hpp @@ -0,0 +1,169 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace nall { + +struct image { + enum class blend : uint { + add, + sourceAlpha, //color = sourceColor * sourceAlpha + targetColor * (1 - sourceAlpha) + sourceColor, //color = sourceColor + targetAlpha, //color = targetColor * targetAlpha + sourceColor * (1 - targetAlpha) + targetColor, //color = targetColor + }; + + struct channel { + channel(uint64_t mask, uint depth, uint shift) : _mask(mask), _depth(depth), _shift(shift) { + } + + auto operator==(const channel& source) const -> bool { + return _mask == source._mask && _depth == source._depth && _shift == source._shift; + } + + auto operator!=(const channel& source) const -> bool { + return !operator==(source); + } + + alwaysinline auto mask() const { return _mask; } + alwaysinline auto depth() const { return _depth; } + alwaysinline auto shift() const { return _shift; } + + private: + uint64_t _mask; + uint _depth; + uint _shift; + }; + + //core.hpp + inline image(const image& source); + inline image(image&& source); + inline image(bool endian, uint depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline image(const string& filename); + inline image(const void* data, uint size); + inline image(const vector& buffer); + template inline image(const uint8_t (&Name)[Size]); + inline image(); + inline ~image(); + + inline auto operator=(const image& source) -> image&; + inline auto operator=(image&& source) -> image&; + + inline explicit operator bool() const; + inline auto operator==(const image& source) const -> bool; + inline auto operator!=(const image& source) const -> bool; + + inline auto read(const uint8_t* data) const -> uint64_t; + inline auto write(uint8_t* data, uint64_t value) const -> void; + + inline auto free() -> void; + inline auto load(const string& filename) -> bool; + inline auto copy(const void* data, uint pitch, uint width, uint height) -> void; + inline auto allocate(uint width, uint height) -> void; + + //fill.hpp + inline auto fill(uint64_t color = 0) -> void; + inline auto gradient(uint64_t a, uint64_t b, uint64_t c, uint64_t d) -> void; + inline auto gradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY, function callback) -> void; + inline auto crossGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto diamondGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto horizontalGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto radialGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto sphericalGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto squareGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto verticalGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + + //scale.hpp + inline auto scale(uint width, uint height, bool linear = true) -> void; + + //blend.hpp + inline auto impose(blend mode, uint targetX, uint targetY, image source, uint x, uint y, uint width, uint height) -> void; + + //utility.hpp + inline auto shrink(uint64_t transparentColor = 0) -> void; + inline auto crop(uint x, uint y, uint width, uint height) -> bool; + inline auto alphaBlend(uint64_t alphaColor) -> void; + inline auto alphaMultiply() -> void; + inline auto transform(const image& source = {}) -> void; + inline auto transform(bool endian, uint depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) -> void; + + //static.hpp + static inline auto bitDepth(uint64_t color) -> uint; + static inline auto bitShift(uint64_t color) -> uint; + static inline auto normalize(uint64_t color, uint sourceDepth, uint targetDepth) -> uint64_t; + + //access + alwaysinline auto data() { return _data; } + alwaysinline auto data() const { return _data; } + alwaysinline auto width() const { return _width; } + alwaysinline auto height() const { return _height; } + + alwaysinline auto endian() const { return _endian; } + alwaysinline auto depth() const { return _depth; } + alwaysinline auto stride() const { return (_depth + 7) >> 3; } + + alwaysinline auto pitch() const { return _width * stride(); } + alwaysinline auto size() const { return _height * pitch(); } + + alwaysinline auto alpha() const { return _alpha; } + alwaysinline auto red() const { return _red; } + alwaysinline auto green() const { return _green; } + alwaysinline auto blue() const { return _blue; } + +private: + //core.hpp + inline auto allocate(uint width, uint height, uint stride) -> uint8_t*; + + //scale.hpp + inline auto scaleLinearWidth(uint width) -> void; + inline auto scaleLinearHeight(uint height) -> void; + inline auto scaleLinear(uint width, uint height) -> void; + inline auto scaleNearest(uint width, uint height) -> void; + + //load.hpp + inline auto loadBMP(const string& filename) -> bool; + inline auto loadBMP(const uint8_t* data, uint size) -> bool; + inline auto loadPNG(const string& filename) -> bool; + inline auto loadPNG(const uint8_t* data, uint size) -> bool; + + //interpolation.hpp + alwaysinline auto isplit(uint64_t* component, uint64_t color) -> void; + alwaysinline auto imerge(const uint64_t* component) -> uint64_t; + alwaysinline auto interpolate1f(uint64_t a, uint64_t b, double x) -> uint64_t; + alwaysinline auto interpolate1f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t; + alwaysinline auto interpolate1i(int64_t a, int64_t b, uint32_t x) -> uint64_t; + alwaysinline auto interpolate1i(int64_t a, int64_t b, int64_t c, int64_t d, uint32_t x, uint32_t y) -> uint64_t; + inline auto interpolate4f(uint64_t a, uint64_t b, double x) -> uint64_t; + inline auto interpolate4f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t; + inline auto interpolate4i(uint64_t a, uint64_t b, uint32_t x) -> uint64_t; + inline auto interpolate4i(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint32_t x, uint32_t y) -> uint64_t; + + uint8_t* _data = nullptr; + uint _width = 0; + uint _height = 0; + + bool _endian = 0; //0 = lsb, 1 = msb + uint _depth = 32; + + channel _alpha{255u << 24, 8, 24}; + channel _red {255u << 16, 8, 16}; + channel _green{255u << 8, 8, 8}; + channel _blue {255u << 0, 8, 0}; +}; + +} + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/waterbox/bsnescore/bsnes/nall/image/blend.hpp b/waterbox/bsnescore/bsnes/nall/image/blend.hpp new file mode 100644 index 0000000000..298d6e173f --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/image/blend.hpp @@ -0,0 +1,71 @@ +#pragma once + +namespace nall { + +auto image::impose(blend mode, unsigned targetX, unsigned targetY, image source, unsigned sourceX, unsigned sourceY, unsigned sourceWidth, unsigned sourceHeight) -> void { + source.transform(_endian, _depth, _alpha.mask(), _red.mask(), _green.mask(), _blue.mask()); + + for(unsigned y = 0; y < sourceHeight; y++) { + const uint8_t* sp = source._data + source.pitch() * (sourceY + y) + source.stride() * sourceX; + uint8_t* dp = _data + pitch() * (targetY + y) + stride() * targetX; + for(unsigned x = 0; x < sourceWidth; x++) { + uint64_t sourceColor = source.read(sp); + uint64_t targetColor = read(dp); + + int64_t sa = (sourceColor & _alpha.mask()) >> _alpha.shift(); + int64_t sr = (sourceColor & _red.mask() ) >> _red.shift(); + int64_t sg = (sourceColor & _green.mask()) >> _green.shift(); + int64_t sb = (sourceColor & _blue.mask() ) >> _blue.shift(); + + int64_t da = (targetColor & _alpha.mask()) >> _alpha.shift(); + int64_t dr = (targetColor & _red.mask() ) >> _red.shift(); + int64_t dg = (targetColor & _green.mask()) >> _green.shift(); + int64_t db = (targetColor & _blue.mask() ) >> _blue.shift(); + + uint64_t a, r, g, b; + + switch(mode) { + case blend::add: + a = max(sa, da); + r = min(_red.mask() >> _red.shift(), ((sr * sa) >> _alpha.depth()) + ((dr * da) >> _alpha.depth())); + g = min(_green.mask() >> _green.shift(), ((sg * sa) >> _alpha.depth()) + ((dg * da) >> _alpha.depth())); + b = min(_blue.mask() >> _blue.shift(), ((sb * sa) >> _alpha.depth()) + ((db * da) >> _alpha.depth())); + break; + + case blend::sourceAlpha: + a = max(sa, da); + r = dr + (((sr - dr) * sa) >> _alpha.depth()); + g = dg + (((sg - dg) * sa) >> _alpha.depth()); + b = db + (((sb - db) * sa) >> _alpha.depth()); + break; + + case blend::sourceColor: + a = sa; + r = sr; + g = sg; + b = sb; + break; + + case blend::targetAlpha: + a = max(sa, da); + r = sr + (((dr - sr) * da) >> _alpha.depth()); + g = sg + (((dg - sg) * da) >> _alpha.depth()); + b = sb + (((db - sb) * da) >> _alpha.depth()); + break; + + case blend::targetColor: + a = da; + r = dr; + g = dg; + b = db; + break; + } + + write(dp, (a << _alpha.shift()) | (r << _red.shift()) | (g << _green.shift()) | (b << _blue.shift())); + sp += source.stride(); + dp += stride(); + } + } +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/image/core.hpp b/waterbox/bsnescore/bsnes/nall/image/core.hpp new file mode 100644 index 0000000000..c2fae6b984 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/image/core.hpp @@ -0,0 +1,173 @@ +#pragma once + +namespace nall { + +image::image(const image& source) { + operator=(source); +} + +image::image(image&& source) { + operator=(forward(source)); +} + +image::image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) { + _endian = endian; + _depth = depth; + + _alpha = {alphaMask, bitDepth(alphaMask), bitShift(alphaMask)}; + _red = {redMask, bitDepth(redMask), bitShift(redMask )}; + _green = {greenMask, bitDepth(greenMask), bitShift(greenMask)}; + _blue = {blueMask, bitDepth(blueMask), bitShift(blueMask )}; +} + +image::image(const string& filename) { + load(filename); +} + +image::image(const void* data_, uint size) { + auto data = (const uint8_t*)data_; + if(size < 4); + else if(data[0] == 'B' && data[1] == 'M') loadBMP(data, size); + else if(data[1] == 'P' && data[2] == 'N' && data[3] == 'G') loadPNG(data, size); +} + +image::image(const vector& buffer) : image(buffer.data(), buffer.size()) { +} + +template image::image(const uint8_t (&Name)[Size]) : image(Name, Size) { +} + +image::image() { +} + +image::~image() { + free(); +} + +auto image::operator=(const image& source) -> image& { + if(this == &source) return *this; + free(); + + _width = source._width; + _height = source._height; + + _endian = source._endian; + _depth = source._depth; + + _alpha = source._alpha; + _red = source._red; + _green = source._green; + _blue = source._blue; + + _data = allocate(_width, _height, stride()); + memory::copy(_data, source._data, source.size()); + return *this; +} + +auto image::operator=(image&& source) -> image& { + if(this == &source) return *this; + free(); + + _width = source._width; + _height = source._height; + + _endian = source._endian; + _depth = source._depth; + + _alpha = source._alpha; + _red = source._red; + _green = source._green; + _blue = source._blue; + + _data = source._data; + source._data = nullptr; + return *this; +} + +image::operator bool() const { + return _data && _width && _height; +} + +auto image::operator==(const image& source) const -> bool { + if(_width != source._width) return false; + if(_height != source._height) return false; + + if(_endian != source._endian) return false; + if(_depth != source._depth) return false; + + if(_alpha != source._alpha) return false; + if(_red != source._red) return false; + if(_green != source._green) return false; + if(_blue != source._blue) return false; + + return memory::compare(_data, source._data, size()) == 0; +} + +auto image::operator!=(const image& source) const -> bool { + return !operator==(source); +} + +auto image::read(const uint8_t* data) const -> uint64_t { + uint64_t result = 0; + if(_endian == 0) { + for(signed n = stride() - 1; n >= 0; n--) result = (result << 8) | data[n]; + } else { + for(signed n = 0; n < stride(); n++) result = (result << 8) | data[n]; + } + return result; +} + +auto image::write(uint8_t* data, uint64_t value) const -> void { + if(_endian == 0) { + for(signed n = 0; n < stride(); n++) { + data[n] = value; + value >>= 8; + } + } else { + for(signed n = stride() - 1; n >= 0; n--) { + data[n] = value; + value >>= 8; + } + } +} + +auto image::free() -> void { + if(_data) delete[] _data; + _data = nullptr; +} + +auto image::load(const string& filename) -> bool { + if(loadBMP(filename) == true) return true; + if(loadPNG(filename) == true) return true; + return false; +} + +//assumes image and data are in the same format; pitch is adapted to image +auto image::copy(const void* data, uint pitch, uint width, uint height) -> void { + allocate(width, height); + for(uint y : range(height)) { + auto input = (const uint8_t*)data + y * pitch; + auto output = (uint8_t*)_data + y * this->pitch(); + memory::copy(output, input, width * stride()); + } +} + +auto image::allocate(unsigned width, unsigned height) -> void { + if(_data && _width == width && _height == height) return; + free(); + _width = width; + _height = height; + _data = allocate(_width, _height, stride()); +} + +//private +auto image::allocate(unsigned width, unsigned height, unsigned stride) -> uint8_t* { + //allocate 1x1 larger than requested; so that linear interpolation does not require bounds-checking + unsigned size = width * height * stride; + unsigned padding = width * stride + stride; + auto data = new uint8_t[size + padding]; + memory::fill(data + size, padding); + return data; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/image/fill.hpp b/waterbox/bsnescore/bsnes/nall/image/fill.hpp new file mode 100644 index 0000000000..8ff1f88613 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/image/fill.hpp @@ -0,0 +1,84 @@ +#pragma once + +namespace nall { + +auto image::fill(uint64_t color) -> void { + for(unsigned y = 0; y < _height; y++) { + uint8_t* dp = _data + pitch() * y; + for(unsigned x = 0; x < _width; x++) { + write(dp, color); + dp += stride(); + } + } +} + +auto image::gradient(uint64_t a, uint64_t b, uint64_t c, uint64_t d) -> void { + for(unsigned y = 0; y < _height; y++) { + uint8_t* dp = _data + pitch() * y; + double muY = (double)y / (double)_height; + for(unsigned x = 0; x < _width; x++) { + double muX = (double)x / (double)_width; + write(dp, interpolate4f(a, b, c, d, muX, muY)); + dp += stride(); + } + } +} + +auto image::gradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY, function callback) -> void { + for(signed y = 0; y < _height; y++) { + uint8_t* dp = _data + pitch() * y; + double py = max(-radiusY, min(+radiusY, y - centerY)) * 1.0 / radiusY; + for(signed x = 0; x < _width; x++) { + double px = max(-radiusX, min(+radiusX, x - centerX)) * 1.0 / radiusX; + double mu = max(0.0, min(1.0, callback(px, py))); + if(mu != mu) mu = 1.0; //NaN + write(dp, interpolate4f(a, b, mu)); + dp += stride(); + } + } +} + +auto image::crossGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + x = fabs(x), y = fabs(y); + return min(x, y) * min(x, y); + }); +} + +auto image::diamondGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return fabs(x) + fabs(y); + }); +} + +auto image::horizontalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return fabs(x); + }); +} + +auto image::radialGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return sqrt(x * x + y * y); + }); +} + +auto image::sphericalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return x * x + y * y; + }); +} + +auto image::squareGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return max(fabs(x), fabs(y)); + }); +} + +auto image::verticalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return fabs(y); + }); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/image/interpolation.hpp b/waterbox/bsnescore/bsnes/nall/image/interpolation.hpp new file mode 100644 index 0000000000..c839f6cbe4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/image/interpolation.hpp @@ -0,0 +1,62 @@ +#pragma once + +namespace nall { + +auto image::isplit(uint64_t* c, uint64_t color) -> void { + c[0] = (color & _alpha.mask()) >> _alpha.shift(); + c[1] = (color & _red.mask() ) >> _red.shift(); + c[2] = (color & _green.mask()) >> _green.shift(); + c[3] = (color & _blue.mask() ) >> _blue.shift(); +} + +auto image::imerge(const uint64_t* c) -> uint64_t { + return c[0] << _alpha.shift() | c[1] << _red.shift() | c[2] << _green.shift() | c[3] << _blue.shift(); +} + +auto image::interpolate1f(uint64_t a, uint64_t b, double x) -> uint64_t { + return a * (1.0 - x) + b * x; +} + +auto image::interpolate1f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t { + return a * (1.0 - x) * (1.0 - y) + b * x * (1.0 - y) + c * (1.0 - x) * y + d * x * y; +} + +auto image::interpolate1i(int64_t a, int64_t b, uint32_t x) -> uint64_t { + return a + (((b - a) * x) >> 32); //a + (b - a) * x +} + +auto image::interpolate1i(int64_t a, int64_t b, int64_t c, int64_t d, uint32_t x, uint32_t y) -> uint64_t { + a = a + (((b - a) * x) >> 32); //a + (b - a) * x + c = c + (((d - c) * x) >> 32); //c + (d - c) * x + return a + (((c - a) * y) >> 32); //a + (c - a) * y +} + +auto image::interpolate4f(uint64_t a, uint64_t b, double x) -> uint64_t { + uint64_t o[4], pa[4], pb[4]; + isplit(pa, a), isplit(pb, b); + for(unsigned n = 0; n < 4; n++) o[n] = interpolate1f(pa[n], pb[n], x); + return imerge(o); +} + +auto image::interpolate4f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t { + uint64_t o[4], pa[4], pb[4], pc[4], pd[4]; + isplit(pa, a), isplit(pb, b), isplit(pc, c), isplit(pd, d); + for(unsigned n = 0; n < 4; n++) o[n] = interpolate1f(pa[n], pb[n], pc[n], pd[n], x, y); + return imerge(o); +} + +auto image::interpolate4i(uint64_t a, uint64_t b, uint32_t x) -> uint64_t { + uint64_t o[4], pa[4], pb[4]; + isplit(pa, a), isplit(pb, b); + for(unsigned n = 0; n < 4; n++) o[n] = interpolate1i(pa[n], pb[n], x); + return imerge(o); +} + +auto image::interpolate4i(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint32_t x, uint32_t y) -> uint64_t { + uint64_t o[4], pa[4], pb[4], pc[4], pd[4]; + isplit(pa, a), isplit(pb, b), isplit(pc, c), isplit(pd, d); + for(unsigned n = 0; n < 4; n++) o[n] = interpolate1i(pa[n], pb[n], pc[n], pd[n], x, y); + return imerge(o); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/image/load.hpp b/waterbox/bsnescore/bsnes/nall/image/load.hpp new file mode 100644 index 0000000000..cf40f5ecb3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/image/load.hpp @@ -0,0 +1,99 @@ +#pragma once + +namespace nall { + +auto image::loadBMP(const string& filename) -> bool { + if(!file::exists(filename)) return false; + auto buffer = file::read(filename); + return loadBMP(buffer.data(), buffer.size()); +} + +auto image::loadBMP(const uint8_t* bmpData, unsigned bmpSize) -> bool { + Decode::BMP source; + if(!source.load(bmpData, bmpSize)) return false; + + allocate(source.width(), source.height()); + const uint32_t* sp = source.data(); + uint8_t* dp = _data; + + for(unsigned y = 0; y < _height; y++) { + for(unsigned x = 0; x < _width; x++) { + uint32_t color = *sp++; + uint64_t a = normalize((uint8_t)(color >> 24), 8, _alpha.depth()); + uint64_t r = normalize((uint8_t)(color >> 16), 8, _red.depth()); + uint64_t g = normalize((uint8_t)(color >> 8), 8, _green.depth()); + uint64_t b = normalize((uint8_t)(color >> 0), 8, _blue.depth()); + write(dp, (a << _alpha.shift()) | (r << _red.shift()) | (g << _green.shift()) | (b << _blue.shift())); + dp += stride(); + } + } + + return true; +} + +auto image::loadPNG(const string& filename) -> bool { + if(!file::exists(filename)) return false; + auto buffer = file::read(filename); + return loadPNG(buffer.data(), buffer.size()); +} + +auto image::loadPNG(const uint8_t* pngData, unsigned pngSize) -> bool { + Decode::PNG source; + if(!source.load(pngData, pngSize)) return false; + + allocate(source.info.width, source.info.height); + const uint8_t* sp = source.data; + uint8_t* dp = _data; + + auto decode = [&]() -> uint64_t { + uint64_t p, r, g, b, a; + + switch(source.info.colorType) { + case 0: //L + r = g = b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 2: //R,G,B + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 3: //P + p = source.readbits(sp); + r = source.info.palette[p][0]; + g = source.info.palette[p][1]; + b = source.info.palette[p][2]; + a = (1 << source.info.bitDepth) - 1; + break; + case 4: //L,A + r = g = b = source.readbits(sp); + a = source.readbits(sp); + break; + case 6: //R,G,B,A + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = source.readbits(sp); + break; + } + + a = normalize(a, source.info.bitDepth, _alpha.depth()); + r = normalize(r, source.info.bitDepth, _red.depth()); + g = normalize(g, source.info.bitDepth, _green.depth()); + b = normalize(b, source.info.bitDepth, _blue.depth()); + + return (a << _alpha.shift()) | (r << _red.shift()) | (g << _green.shift()) | (b << _blue.shift()); + }; + + for(unsigned y = 0; y < _height; y++) { + for(unsigned x = 0; x < _width; x++) { + write(dp, decode()); + dp += stride(); + } + } + + return true; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/image/scale.hpp b/waterbox/bsnescore/bsnes/nall/image/scale.hpp new file mode 100644 index 0000000000..3ea63d1fe0 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/image/scale.hpp @@ -0,0 +1,177 @@ +#pragma once + +namespace nall { + +auto image::scale(unsigned outputWidth, unsigned outputHeight, bool linear) -> void { + if(!_data) return; + if(_width == outputWidth && _height == outputHeight) return; //no scaling necessary + if(linear == false) return scaleNearest(outputWidth, outputHeight); + + if(_width == outputWidth ) return scaleLinearHeight(outputHeight); + if(_height == outputHeight) return scaleLinearWidth(outputWidth); + + //find fastest scaling method, based on number of interpolation operations required + //magnification usually benefits from two-pass linear interpolation + //minification usually benefits from one-pass bilinear interpolation + unsigned d1wh = ((_width * outputWidth ) + (outputWidth * outputHeight)) * 1; + unsigned d1hw = ((_height * outputHeight) + (outputWidth * outputHeight)) * 1; + unsigned d2wh = (outputWidth * outputHeight) * 3; + + if(d1wh <= d1hw && d1wh <= d2wh) return scaleLinearWidth(outputWidth), scaleLinearHeight(outputHeight); + if(d1hw <= d2wh) return scaleLinearHeight(outputHeight), scaleLinearWidth(outputWidth); + return scaleLinear(outputWidth, outputHeight); +} + +auto image::scaleLinearWidth(unsigned outputWidth) -> void { + uint8_t* outputData = allocate(outputWidth, _height, stride()); + unsigned outputPitch = outputWidth * stride(); + uint64_t xstride = ((uint64_t)(_width - 1) << 32) / max(1u, outputWidth - 1); + + for(unsigned y = 0; y < _height; y++) { + uint64_t xfraction = 0; + + const uint8_t* sp = _data + pitch() * y; + uint8_t* dp = outputData + outputPitch * y; + + uint64_t a = read(sp); + uint64_t b = read(sp + stride()); + sp += stride(); + + unsigned x = 0; + while(true) { + while(xfraction < 0x100000000 && x++ < outputWidth) { + write(dp, interpolate4i(a, b, xfraction)); + dp += stride(); + xfraction += xstride; + } + if(x >= outputWidth) break; + + sp += stride(); + a = b; + b = read(sp); + xfraction -= 0x100000000; + } + } + + free(); + _data = outputData; + _width = outputWidth; +} + +auto image::scaleLinearHeight(unsigned outputHeight) -> void { + uint8_t* outputData = allocate(_width, outputHeight, stride()); + uint64_t ystride = ((uint64_t)(_height - 1) << 32) / max(1u, outputHeight - 1); + + for(unsigned x = 0; x < _width; x++) { + uint64_t yfraction = 0; + + const uint8_t* sp = _data + stride() * x; + uint8_t* dp = outputData + stride() * x; + + uint64_t a = read(sp); + uint64_t b = read(sp + pitch()); + sp += pitch(); + + unsigned y = 0; + while(true) { + while(yfraction < 0x100000000 && y++ < outputHeight) { + write(dp, interpolate4i(a, b, yfraction)); + dp += pitch(); + yfraction += ystride; + } + if(y >= outputHeight) break; + + sp += pitch(); + a = b; + b = read(sp); + yfraction -= 0x100000000; + } + } + + free(); + _data = outputData; + _height = outputHeight; +} + +auto image::scaleLinear(unsigned outputWidth, unsigned outputHeight) -> void { + uint8_t* outputData = allocate(outputWidth, outputHeight, stride()); + unsigned outputPitch = outputWidth * stride(); + + uint64_t xstride = ((uint64_t)(_width - 1) << 32) / max(1u, outputWidth - 1); + uint64_t ystride = ((uint64_t)(_height - 1) << 32) / max(1u, outputHeight - 1); + + for(unsigned y = 0; y < outputHeight; y++) { + uint64_t yfraction = ystride * y; + uint64_t xfraction = 0; + + const uint8_t* sp = _data + pitch() * (yfraction >> 32); + uint8_t* dp = outputData + outputPitch * y; + + uint64_t a = read(sp); + uint64_t b = read(sp + stride()); + uint64_t c = read(sp + pitch()); + uint64_t d = read(sp + pitch() + stride()); + sp += stride(); + + unsigned x = 0; + while(true) { + while(xfraction < 0x100000000 && x++ < outputWidth) { + write(dp, interpolate4i(a, b, c, d, xfraction, yfraction)); + dp += stride(); + xfraction += xstride; + } + if(x >= outputWidth) break; + + sp += stride(); + a = b; + c = d; + b = read(sp); + d = read(sp + pitch()); + xfraction -= 0x100000000; + } + } + + free(); + _data = outputData; + _width = outputWidth; + _height = outputHeight; +} + +auto image::scaleNearest(unsigned outputWidth, unsigned outputHeight) -> void { + uint8_t* outputData = allocate(outputWidth, outputHeight, stride()); + unsigned outputPitch = outputWidth * stride(); + + uint64_t xstride = ((uint64_t)_width << 32) / outputWidth; + uint64_t ystride = ((uint64_t)_height << 32) / outputHeight; + + for(unsigned y = 0; y < outputHeight; y++) { + uint64_t yfraction = ystride * y; + uint64_t xfraction = 0; + + const uint8_t* sp = _data + pitch() * (yfraction >> 32); + uint8_t* dp = outputData + outputPitch * y; + + uint64_t a = read(sp); + + unsigned x = 0; + while(true) { + while(xfraction < 0x100000000 && x++ < outputWidth) { + write(dp, a); + dp += stride(); + xfraction += xstride; + } + if(x >= outputWidth) break; + + sp += stride(); + a = read(sp); + xfraction -= 0x100000000; + } + } + + free(); + _data = outputData; + _width = outputWidth; + _height = outputHeight; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/image/static.hpp b/waterbox/bsnescore/bsnes/nall/image/static.hpp new file mode 100644 index 0000000000..7dc0d8429f --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/image/static.hpp @@ -0,0 +1,28 @@ +#pragma once + +namespace nall { + +auto image::bitDepth(uint64_t color) -> unsigned { + unsigned depth = 0; + if(color) while((color & 1) == 0) color >>= 1; + while((color & 1) == 1) { color >>= 1; depth++; } + return depth; +} + +auto image::bitShift(uint64_t color) -> unsigned { + unsigned shift = 0; + if(color) while((color & 1) == 0) { color >>= 1; shift++; } + return shift; +} + +auto image::normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth) -> uint64_t { + if(sourceDepth == 0 || targetDepth == 0) return 0; + while(sourceDepth < targetDepth) { + color = (color << sourceDepth) | color; + sourceDepth += sourceDepth; + } + if(targetDepth < sourceDepth) color >>= (sourceDepth - targetDepth); + return color; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/image/utility.hpp b/waterbox/bsnescore/bsnes/nall/image/utility.hpp new file mode 100644 index 0000000000..7c0ddd45f2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/image/utility.hpp @@ -0,0 +1,179 @@ +#pragma once + +namespace nall { + +//scan all four sides of the image for fully transparent pixels, and then crop them +//imagine an icon centered on a transparent background: this function removes the bordering +//this certainly won't win any speed awards, but nall::image is meant to be correct and simple, not fast +auto image::shrink(uint64_t transparentColor) -> void { + //top + { uint padding = 0; + for(uint y : range(_height)) { + const uint8_t* sp = _data + pitch() * y; + bool found = false; + for(uint x : range(_width)) { + if(read(sp) != transparentColor) { found = true; break; } + sp += stride(); + } + if(found) break; + padding++; + } + crop(0, padding, _width, _height - padding); + } + + //bottom + { uint padding = 0; + for(uint y : reverse(range(_height))) { + const uint8_t* sp = _data + pitch() * y; + bool found = false; + for(uint x : range(_width)) { + if(read(sp) != transparentColor) { found = true; break; } + sp += stride(); + } + if(found) break; + padding++; + } + crop(0, 0, _width, _height - padding); + } + + //left + { uint padding = 0; + for(uint x : range(_width)) { + const uint8_t* sp = _data + stride() * x; + bool found = false; + for(uint y : range(_height)) { + if(read(sp) != transparentColor) { found = true; break; } + sp += pitch(); + } + if(found) break; + padding++; + } + crop(padding, 0, _width - padding, _height); + } + + //right + { uint padding = 0; + for(uint x : reverse(range(_width))) { + const uint8_t* sp = _data + stride() * x; + bool found = false; + for(uint y : range(_height)) { + if(read(sp) != transparentColor) { found = true; break; } + sp += pitch(); + } + if(found) break; + padding++; + } + crop(0, 0, _width - padding, _height); + } +} + +auto image::crop(unsigned outputX, unsigned outputY, unsigned outputWidth, unsigned outputHeight) -> bool { + if(outputX + outputWidth > _width) return false; + if(outputY + outputHeight > _height) return false; + + uint8_t* outputData = allocate(outputWidth, outputHeight, stride()); + unsigned outputPitch = outputWidth * stride(); + + for(unsigned y = 0; y < outputHeight; y++) { + const uint8_t* sp = _data + pitch() * (outputY + y) + stride() * outputX; + uint8_t* dp = outputData + outputPitch * y; + for(unsigned x = 0; x < outputWidth; x++) { + write(dp, read(sp)); + sp += stride(); + dp += stride(); + } + } + + delete[] _data; + _data = outputData; + _width = outputWidth; + _height = outputHeight; + return true; +} + +auto image::alphaBlend(uint64_t alphaColor) -> void { + uint64_t alphaR = (alphaColor & _red.mask() ) >> _red.shift(); + uint64_t alphaG = (alphaColor & _green.mask()) >> _green.shift(); + uint64_t alphaB = (alphaColor & _blue.mask() ) >> _blue.shift(); + + for(unsigned y = 0; y < _height; y++) { + uint8_t* dp = _data + pitch() * y; + for(unsigned x = 0; x < _width; x++) { + uint64_t color = read(dp); + + uint64_t colorA = (color & _alpha.mask()) >> _alpha.shift(); + uint64_t colorR = (color & _red.mask() ) >> _red.shift(); + uint64_t colorG = (color & _green.mask()) >> _green.shift(); + uint64_t colorB = (color & _blue.mask() ) >> _blue.shift(); + double alphaScale = (double)colorA / (double)((1 << _alpha.depth()) - 1); + + colorA = (1 << _alpha.depth()) - 1; + colorR = (colorR * alphaScale) + (alphaR * (1.0 - alphaScale)); + colorG = (colorG * alphaScale) + (alphaG * (1.0 - alphaScale)); + colorB = (colorB * alphaScale) + (alphaB * (1.0 - alphaScale)); + + write(dp, (colorA << _alpha.shift()) | (colorR << _red.shift()) | (colorG << _green.shift()) | (colorB << _blue.shift())); + dp += stride(); + } + } +} + +auto image::alphaMultiply() -> void { + unsigned divisor = (1 << _alpha.depth()) - 1; + + for(unsigned y = 0; y < _height; y++) { + uint8_t* dp = _data + pitch() * y; + for(unsigned x = 0; x < _width; x++) { + uint64_t color = read(dp); + + uint64_t colorA = (color & _alpha.mask()) >> _alpha.shift(); + uint64_t colorR = (color & _red.mask() ) >> _red.shift(); + uint64_t colorG = (color & _green.mask()) >> _green.shift(); + uint64_t colorB = (color & _blue.mask() ) >> _blue.shift(); + + colorR = (colorR * colorA) / divisor; + colorG = (colorG * colorA) / divisor; + colorB = (colorB * colorA) / divisor; + + write(dp, (colorA << _alpha.shift()) | (colorR << _red.shift()) | (colorG << _green.shift()) | (colorB << _blue.shift())); + dp += stride(); + } + } +} + +auto image::transform(const image& source) -> void { + return transform(source._endian, source._depth, source._alpha.mask(), source._red.mask(), source._green.mask(), source._blue.mask()); +} + +auto image::transform(bool outputEndian, unsigned outputDepth, uint64_t outputAlphaMask, uint64_t outputRedMask, uint64_t outputGreenMask, uint64_t outputBlueMask) -> void { + if(_endian == outputEndian && _depth == outputDepth && _alpha.mask() == outputAlphaMask && _red.mask() == outputRedMask && _green.mask() == outputGreenMask && _blue.mask() == outputBlueMask) return; + + image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask); + output.allocate(_width, _height); + + for(unsigned y = 0; y < _height; y++) { + const uint8_t* sp = _data + pitch() * y; + uint8_t* dp = output._data + output.pitch() * y; + for(unsigned x = 0; x < _width; x++) { + uint64_t color = read(sp); + sp += stride(); + + uint64_t a = (color & _alpha.mask()) >> _alpha.shift(); + uint64_t r = (color & _red.mask() ) >> _red.shift(); + uint64_t g = (color & _green.mask()) >> _green.shift(); + uint64_t b = (color & _blue.mask() ) >> _blue.shift(); + + a = normalize(a, _alpha.depth(), output._alpha.depth()); + r = normalize(r, _red.depth(), output._red.depth()); + g = normalize(g, _green.depth(), output._green.depth()); + b = normalize(b, _blue.depth(), output._blue.depth()); + + output.write(dp, (a << output._alpha.shift()) | (r << output._red.shift()) | (g << output._green.shift()) | (b << output._blue.shift())); + dp += output.stride(); + } + } + + operator=(move(output)); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/induced-sort.hpp b/waterbox/bsnescore/bsnes/nall/induced-sort.hpp new file mode 100644 index 0000000000..c544a89353 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/induced-sort.hpp @@ -0,0 +1,176 @@ +#pragma once + +//suffix array construction via induced sorting +//many thanks to Screwtape for the thorough explanation of this algorithm +//this implementation would not be possible without his help + +namespace nall { + +//note that induced_sort will return an array of size+1 characters, +//where the first character is the empty suffix, equal to size + +template +inline auto induced_sort(array_view data, const uint characters = 256) -> vector { + const uint size = data.size(); + if(size == 0) return vector{0}; //required to avoid out-of-bounds accesses + if(size == 1) return vector{1, 0}; //not strictly necessary; but more performant + + vector types; //0 = S-suffix (sort before next suffix), 1 = L-suffix (sort after next suffix) + types.resize(size + 1); + + types[size - 0] = 0; //empty suffix is always S-suffix + types[size - 1] = 1; //last suffix is always L-suffix compared to empty suffix + for(uint n : reverse(range(size - 1))) { + if(data[n] < data[n + 1]) { + types[n] = 0; //this suffix is smaller than the one after it + } else if(data[n] > data[n + 1]) { + types[n] = 1; //this suffix is larger than the one after it + } else { + types[n] = types[n + 1]; //this suffix will be the same as the one after it + } + } + + //left-most S-suffix + auto isLMS = [&](int n) -> bool { + if(n == 0) return 0; //no character to the left of the first suffix + return !types[n] && types[n - 1]; //true if this is the start of a new S-suffix + }; + + //test if two LMS-substrings are equal + auto isEqual = [&](int lhs, int rhs) -> bool { + if(lhs == size || rhs == size) return false; //no other suffix can be equal to the empty suffix + + for(uint n = 0;; n++) { + bool lhsLMS = isLMS(lhs + n); + bool rhsLMS = isLMS(rhs + n); + if(n && lhsLMS && rhsLMS) return true; //substrings are identical + if(lhsLMS != rhsLMS) return false; //length mismatch: substrings cannot be identical + if(data[lhs + n] != data[rhs + n]) return false; //character mismatch: substrings are different + } + }; + + //determine the sizes of each bucket: one bucket per character + vector counts; + counts.resize(characters); + for(uint n : range(size)) counts[data[n]]++; + + //bucket sorting start offsets + vector heads; + heads.resize(characters); + + uint headOffset; + auto getHeads = [&] { + headOffset = 1; + for(uint n : range(characters)) { + heads[n] = headOffset; + headOffset += counts[n]; + } + }; + + //bucket sorting end offsets + vector tails; + tails.resize(characters); + + uint tailOffset; + auto getTails = [&] { + tailOffset = 1; + for(uint n : range(characters)) { + tailOffset += counts[n]; + tails[n] = tailOffset - 1; + } + }; + + //inaccurate LMS bucket sort + vector suffixes; + suffixes.resize(size + 1, (int)-1); + + getTails(); + for(uint n : range(size)) { + if(!isLMS(n)) continue; //skip non-LMS-suffixes + suffixes[tails[data[n]]--] = n; //advance from the tail of the bucket + } + + suffixes[0] = size; //the empty suffix is always an LMS-suffix, and is the first suffix + + //sort all L-suffixes to the left of LMS-suffixes + auto sortL = [&] { + getHeads(); + for(uint n : range(size + 1)) { + if(suffixes[n] == -1) continue; //offsets may not be known yet here ... + auto l = suffixes[n] - 1; + if(l < 0 || !types[l]) continue; //skip S-suffixes + suffixes[heads[data[l]]++] = l; //advance from the head of the bucket + } + }; + + auto sortS = [&] { + getTails(); + for(uint n : reverse(range(size + 1))) { + auto l = suffixes[n] - 1; + if(l < 0 || types[l]) continue; //skip L-suffixes + suffixes[tails[data[l]]--] = l; //advance from the tail of the bucket + } + }; + + sortL(); + sortS(); + + //analyze data for the summary suffix array + vector names; + names.resize(size + 1, (int)-1); + + uint currentName = 0; //keep a count to tag each unique LMS-substring with unique IDs + auto lastLMSOffset = suffixes[0]; //location in the original data of the last checked LMS suffix + names[lastLMSOffset] = currentName; //the first LMS-substring is always the empty suffix entry, at position 0 + + for(uint n : range(1, size + 1)) { + auto offset = suffixes[n]; + if(!isLMS(offset)) continue; //only LMS suffixes are important + + //if this LMS suffix starts with a different LMS substring than the last suffix observed ... + if(!isEqual(lastLMSOffset, offset)) currentName++; //then it gets a new name + lastLMSOffset = offset; //keep track of the new most-recent LMS suffix + names[lastLMSOffset] = currentName; //store the LMS suffix name where the suffix appears at in the original data + } + + vector summaryOffsets; + vector summaryData; + for(uint n : range(size + 1)) { + if(names[n] == -1) continue; + summaryOffsets.append(n); + summaryData.append(names[n]); + } + uint summaryCharacters = currentName + 1; //zero-indexed, so the total unique characters is currentName + 1 + + //make the summary suffix array + vector summaries; + if(summaryData.size() == summaryCharacters) { + //simple bucket sort when every character in summaryData appears only once + summaries.resize(summaryData.size() + 1, (int)-1); + summaries[0] = summaryData.size(); //always include the empty suffix at the beginning + for(int x : range(summaryData.size())) { + int y = summaryData[x]; + summaries[y + 1] = x; + } + } else { + //recurse until every character in summaryData is unique ... + summaries = induced_sort({summaryData.data(), summaryData.size()}, summaryCharacters); + } + + suffixes.fill(-1); //reuse existing buffer for accurate sort + + //accurate LMS sort + getTails(); + for(uint n : reverse(range(2, summaries.size()))) { + auto index = summaryOffsets[summaries[n]]; + suffixes[tails[data[index]]--] = index; //advance from the tail of the bucket + } + suffixes[0] = size; //always include the empty suffix at the beginning + + sortL(); + sortS(); + + return suffixes; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/inline-if.hpp b/waterbox/bsnescore/bsnes/nall/inline-if.hpp new file mode 100644 index 0000000000..b7b7a86590 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/inline-if.hpp @@ -0,0 +1,11 @@ +#pragma once +#warning "these defines break if statements with multiple parameters to templates" + +#define if1(statement) if(statement) +#define if2(condition, false) ([&](auto&& value) -> decltype(condition) { \ + return (bool)value ? value : (decltype(condition))false; \ +})(condition) +#define if3(condition, true, false) ((condition) ? (true) : (decltype(true))(false)) +#define if4(type, condition, true, false) ((condition) ? (type)(true) : (type)(false)) +#define if_(_1, _2, _3, _4, name, ...) name +#define if(...) if_(__VA_ARGS__, if4, if3, if2, if1)(__VA_ARGS__) diff --git a/waterbox/bsnescore/bsnes/nall/inode.hpp b/waterbox/bsnescore/bsnes/nall/inode.hpp new file mode 100644 index 0000000000..89bebb249a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/inode.hpp @@ -0,0 +1,163 @@ +#pragma once + +//generic abstraction layer for common storage operations against both files and directories +//these functions are not recursive; use directory::create() and directory::remove() for recursion + +#include +#include + +namespace nall { + +struct inode { + enum class time : uint { create, modify, access }; + + inode() = delete; + inode(const inode&) = delete; + auto operator=(const inode&) -> inode& = delete; + + static auto exists(const string& name) -> bool { + return access(name, F_OK) == 0; + } + + static auto readable(const string& name) -> bool { + return access(name, R_OK) == 0; + } + + static auto writable(const string& name) -> bool { + return access(name, W_OK) == 0; + } + + static auto executable(const string& name) -> bool { + return access(name, X_OK) == 0; + } + + static auto hidden(const string& name) -> bool { + #if defined(PLATFORM_WINDOWS) + auto attributes = GetFileAttributes(utf16_t(name)); + return attributes & FILE_ATTRIBUTE_HIDDEN; + #else + //todo: is this really the best way to do this? stat doesn't have S_ISHIDDEN ... + return name.split("/").last().beginsWith("."); + #endif + } + + static auto mode(const string& name) -> uint { + struct stat data{}; + stat(name, &data); + return data.st_mode; + } + + static auto uid(const string& name) -> uint { + struct stat data{}; + stat(name, &data); + return data.st_uid; + } + + static auto gid(const string& name) -> uint { + struct stat data{}; + stat(name, &data); + return data.st_gid; + } + + static auto owner(const string& name) -> string { + #if !defined(PLATFORM_WINDOWS) + struct passwd* pw = getpwuid(uid(name)); + if(pw && pw->pw_name) return pw->pw_name; + #endif + return {}; + } + + static auto group(const string& name) -> string { + #if !defined(PLATFORM_WINDOWS) + struct group* gr = getgrgid(gid(name)); + if(gr && gr->gr_name) return gr->gr_name; + #endif + return {}; + } + + static auto timestamp(const string& name, time mode = time::modify) -> uint64_t { + struct stat data{}; + stat(name, &data); + switch(mode) { + #if defined(PLATFORM_WINDOWS) + //on Windows, the last status change time (ctime) holds the file creation time instead + case time::create: return data.st_ctime; + #elif defined(PLATFORM_BSD) || defined(PLATFORM_MACOS) + //st_birthtime may return -1 or st_atime if it is not supported by the file system + //the best that can be done in this case is to return st_mtime if it's older + case time::create: return min((uint)data.st_birthtime, (uint)data.st_mtime); + #else + //Linux simply doesn't support file creation time at all + //this is also our fallback case for unsupported operating systems + case time::create: return data.st_mtime; + #endif + case time::modify: return data.st_mtime; + //for performance reasons, last access time is usually not enabled on various filesystems + //ensure that the last access time is not older than the last modify time (eg for NTFS) + case time::access: return max((uint)data.st_atime, data.st_mtime); + } + return 0; + } + + static auto setMode(const string& name, uint mode) -> bool { + #if !defined(PLATFORM_WINDOWS) + return chmod(name, mode) == 0; + #else + return _wchmod(utf16_t(name), (mode & 0400 ? _S_IREAD : 0) | (mode & 0200 ? _S_IWRITE : 0)) == 0; + #endif + } + + static auto setOwner(const string& name, const string& owner) -> bool { + #if !defined(PLATFORM_WINDOWS) + struct passwd* pwd = getpwnam(owner); + if(!pwd) return false; + return chown(name, pwd->pw_uid, inode::gid(name)) == 0; + #else + return true; + #endif + } + + static auto setGroup(const string& name, const string& group) -> bool { + #if !defined(PLATFORM_WINDOWS) + struct group* grp = getgrnam(group); + if(!grp) return false; + return chown(name, inode::uid(name), grp->gr_gid) == 0; + #else + return true; + #endif + } + + static auto setTimestamp(const string& name, uint64_t value, time mode = time::modify) -> bool { + struct utimbuf timeBuffer; + timeBuffer.modtime = mode == time::modify ? value : inode::timestamp(name, time::modify); + timeBuffer.actime = mode == time::access ? value : inode::timestamp(name, time::access); + return utime(name, &timeBuffer) == 0; + } + + //returns true if 'name' already exists + static auto create(const string& name, uint permissions = 0755) -> bool { + if(exists(name)) return true; + if(name.endsWith("/")) return mkdir(name, permissions) == 0; + int fd = open(name, O_CREAT | O_EXCL, permissions); + if(fd < 0) return false; + return close(fd), true; + } + + //returns false if 'name' and 'targetname' are on different file systems (requires copy) + static auto rename(const string& name, const string& targetname) -> bool { + return ::rename(name, targetname) == 0; + } + + //returns false if 'name' is a directory that is not empty + static auto remove(const string& name) -> bool { + #if defined(PLATFORM_WINDOWS) + if(name.endsWith("/")) return _wrmdir(utf16_t(name)) == 0; + return _wunlink(utf16_t(name)) == 0; + #else + if(name.endsWith("/")) return rmdir(name) == 0; + return unlink(name) == 0; + #endif + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/instance.hpp b/waterbox/bsnescore/bsnes/nall/instance.hpp new file mode 100644 index 0000000000..a3cbcedc21 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/instance.hpp @@ -0,0 +1,39 @@ +#pragma once + +namespace nall { + +template +struct Instance { + ~Instance() { + destruct(); + } + + auto operator()() -> T& { + return instance.object; + } + + template + auto construct(P&&... p) { + if(constructed) return; + constructed = true; + new((void*)(&instance.object)) T(forward

(p)...); + } + + auto destruct() -> void { + if(!constructed) return; + constructed = false; + instance.object.~T(); + } + +private: + bool constructed = false; + union Union { + Union() {} + ~Union() {} + + T object; + char storage[sizeof(T)]; + } instance; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/interpolation.hpp b/waterbox/bsnescore/bsnes/nall/interpolation.hpp new file mode 100644 index 0000000000..007f2ec206 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/interpolation.hpp @@ -0,0 +1,56 @@ +#pragma once + +namespace nall { + +struct Interpolation { + static inline auto Nearest(double mu, double a, double b, double c, double d) -> double { + return (mu <= 0.5 ? b : c); + } + + static inline auto Sublinear(double mu, double a, double b, double c, double d) -> double { + mu = ((mu - 0.5) * 2.0) + 0.5; + if(mu < 0) mu = 0; + if(mu > 1) mu = 1; + return b * (1.0 - mu) + c * mu; + } + + static inline auto Linear(double mu, double a, double b, double c, double d) -> double { + return b * (1.0 - mu) + c * mu; + } + + static inline auto Cosine(double mu, double a, double b, double c, double d) -> double { + mu = (1.0 - cos(mu * Math::Pi)) / 2.0; + return b * (1.0 - mu) + c * mu; + } + + static inline auto Cubic(double mu, double a, double b, double c, double d) -> double { + double A = d - c - a + b; + double B = a - b - A; + double C = c - a; + double D = b; + return A * (mu * mu * mu) + B * (mu * mu) + C * mu + D; + } + + static inline auto Hermite(double mu1, double a, double b, double c, double d) -> double { + const double tension = 0.0; //-1 = low, 0 = normal, +1 = high + const double bias = 0.0; //-1 = left, 0 = even, +1 = right + double mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu2 = mu1 * mu1; + mu3 = mu2 * mu1; + + m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0; + m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0; + m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0; + m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0; + + a0 = +2 * mu3 - 3 * mu2 + 1; + a1 = mu3 - 2 * mu2 + mu1; + a2 = mu3 - mu2; + a3 = -2 * mu3 + 3 * mu2; + + return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/intrinsics.hpp b/waterbox/bsnescore/bsnes/nall/intrinsics.hpp new file mode 100644 index 0000000000..5a71704fa5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/intrinsics.hpp @@ -0,0 +1,228 @@ +#pragma once + +namespace nall { + using uint = unsigned; + + enum class Compiler : uint { Clang, GCC, Microsoft, Unknown }; + enum class Platform : uint { Windows, MacOS, Linux, BSD, Android, Unknown }; + enum class API : uint { Windows, Posix, Unknown }; + enum class DisplayServer : uint { Windows, Quartz, Xorg, Unknown }; + enum class Architecture : uint { x86, amd64, ARM32, ARM64, PPC32, PPC64, Unknown }; + enum class Endian : uint { LSB, MSB, Unknown }; + enum class Build : uint { Debug, Stable, Size, Release, Performance }; + + static inline constexpr auto compiler() -> Compiler; + static inline constexpr auto platform() -> Platform; + static inline constexpr auto api() -> API; + static inline constexpr auto display() -> DisplayServer; + static inline constexpr auto architecture() -> Architecture; + static inline constexpr auto endian() -> Endian; + static inline constexpr auto build() -> Build; +} + +/* Compiler detection */ + +namespace nall { + +#if defined(__clang__) + #define COMPILER_CLANG + constexpr auto compiler() -> Compiler { return Compiler::Clang; } + + #pragma clang diagnostic warning "-Wreturn-type" + #pragma clang diagnostic ignored "-Wunused-result" + #pragma clang diagnostic ignored "-Wunknown-pragmas" + #pragma clang diagnostic ignored "-Wempty-body" + #pragma clang diagnostic ignored "-Wparentheses" + #pragma clang diagnostic ignored "-Wswitch" + #pragma clang diagnostic ignored "-Wswitch-bool" + #pragma clang diagnostic ignored "-Wtautological-compare" + #pragma clang diagnostic ignored "-Wabsolute-value" + #pragma clang diagnostic ignored "-Wshift-count-overflow" + #pragma clang diagnostic ignored "-Wtrigraphs" + + //temporary + #pragma clang diagnostic ignored "-Winconsistent-missing-override" +//#pragma clang diagnostic error "-Wdeprecated-declarations" +#elif defined(__GNUC__) + #define COMPILER_GCC + constexpr auto compiler() -> Compiler { return Compiler::GCC; } + + #pragma GCC diagnostic warning "-Wreturn-type" + #pragma GCC diagnostic ignored "-Wunused-result" + #pragma GCC diagnostic ignored "-Wunknown-pragmas" + #pragma GCC diagnostic ignored "-Wpragmas" + #pragma GCC diagnostic ignored "-Wswitch-bool" + #pragma GCC diagnostic ignored "-Wtrigraphs" +#elif defined(_MSC_VER) + #define COMPILER_MICROSOFT + constexpr auto compiler() -> Compiler { return Compiler::Microsoft; } + + #pragma warning(disable:4996) //libc "deprecation" warnings +#else + #warning "unable to detect compiler" + #define COMPILER_UNKNOWN + constexpr auto compiler() -> Compiler { return Compiler::Unknown; } +#endif + +} + +/* Platform detection */ + +namespace nall { + +#if defined(_WIN32) + #define PLATFORM_WINDOWS + #define API_WINDOWS + #define DISPLAY_WINDOWS + constexpr auto platform() -> Platform { return Platform::Windows; } + constexpr auto api() -> API { return API::Windows; } + constexpr auto display() -> DisplayServer { return DisplayServer::Windows; } +#elif defined(__APPLE__) + #define PLATFORM_MACOS + #define API_POSIX + #define DISPLAY_QUARTZ + constexpr auto platform() -> Platform { return Platform::MacOS; } + constexpr auto api() -> API { return API::Posix; } + constexpr auto display() -> DisplayServer { return DisplayServer::Quartz; } +#elif defined(__ANDROID__) + #define PLATFORM_ANDROID + #define API_POSIX + #define DISPLAY_UNKNOWN + constexpr auto platform() -> Platform { return Platform::Android; } + constexpr auto api() -> API { return API::Posix; } + constexpr auto display() -> DisplayServer { return DisplayServer::Unknown; } +#elif defined(linux) || defined(__linux__) + #define PLATFORM_LINUX + #define API_POSIX + #define DISPLAY_XORG + constexpr auto platform() -> Platform { return Platform::Linux; } + constexpr auto api() -> API { return API::Posix; } + constexpr auto display() -> DisplayServer { return DisplayServer::Xorg; } +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PLATFORM_BSD + #define API_POSIX + #define DISPLAY_XORG + constexpr auto platform() -> Platform { return Platform::BSD; } + constexpr auto api() -> API { return API::Posix; } + constexpr auto display() -> DisplayServer { return DisplayServer::Xorg; } +#else + #warning "unable to detect platform" + #define PLATFORM_UNKNOWN + #define API_UNKNOWN + #define DISPLAY_UNKNOWN + constexpr auto platform() -> Platform { return Platform::Unknown; } + constexpr auto api() -> API { return API::Unknown; } + constexpr auto display() -> DisplayServer { return DisplayServer::Unknown; } +#endif + +} + +/* Architecture detection */ + +namespace nall { + +#if defined(__i386__) || defined(_M_IX86) + #define ARCHITECTURE_X86 + constexpr auto architecture() -> Architecture { return Architecture::x86; } +#elif defined(__amd64__) || defined(_M_AMD64) + #define ARCHITECTURE_AMD64 + constexpr auto architecture() -> Architecture { return Architecture::amd64; } +#elif defined(__aarch64__) + #define ARCHITECTURE_ARM64 + constexpr auto architecture() -> Architecture { return Architecture::ARM64; } +#elif defined(__arm__) + #define ARCHITECTURE_ARM32 + constexpr auto architecture() -> Architecture { return Architecture::ARM32; } +#elif defined(__ppc64__) || defined(_ARCH_PPC64) + #define ARCHITECTURE_PPC64 + constexpr auto architecture() -> Architecture { return Architecture::PPC64; } +#elif defined(__ppc__) || defined(_ARCH_PPC) || defined(_M_PPC) + #define ARCHITECTURE_PPC32 + constexpr auto architecture() -> Architecture { return Architecture::PPC32; } +#else + #warning "unable to detect architecture" + #define ARCHITECTURE_UNKNOWN + constexpr auto architecture() -> Architecture { return Architecture::Unknown; } +#endif + +} + +/* Endian detection */ + +#if defined(PLATFORM_MACOS) + #include +#elif defined(PLATFORM_LINUX) + #include +#elif defined(PLATFORM_BSD) + #include +#endif + +namespace nall { + +// A note on endian constants: Traditional UNIX provides a header that defines +// constants LITTLE_ENDIAN, BIG_ENDIAN, and BYTE_ORDER (set to LITTLE_ENDIAN or +// BIG_ENDIAN as appropriate). However, C89 says that the compiler/libc should +// not introduce any names unless they start with an underscore, so when you're +// compiling in standards-compilant mode, those constants are named +// __LITTLE_ENDIAN, or sometimes _LITTLE_ENDIAN, or sometimes even LITTLE_ENDIAN +// on platforms that care more about tradition than standards. The platforms +// that rename the constants usually provide some other name you can #define to +// say, "forget C89, yes I really want traditional constant names", but *that* +// name also differs from platform to platform, and it affects more than just +// the endian header. +// +// Rather than wade into that mess, let's just test for all the constants we +// know about. + +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) \ + || (defined( _BYTE_ORDER) && defined( _LITTLE_ENDIAN) && _BYTE_ORDER == _LITTLE_ENDIAN) \ + || (defined( BYTE_ORDER) && defined( LITTLE_ENDIAN) && BYTE_ORDER == LITTLE_ENDIAN) \ + || defined(__LITTLE_ENDIAN__) \ + || defined(__i386__) || defined(__amd64__) \ + || defined(_M_IX86) || defined(_M_AMD64) + #define ENDIAN_LSB + constexpr auto endian() -> Endian { return Endian::LSB; } +#elif(defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) \ + || (defined( _BYTE_ORDER) && defined( _BIG_ENDIAN) && _BYTE_ORDER == _BIG_ENDIAN) \ + || (defined( BYTE_ORDER) && defined( BIG_ENDIAN) && BYTE_ORDER == BIG_ENDIAN) \ + || defined(__BIG_ENDIAN__) \ + || defined(__powerpc__) || defined(_M_PPC) + #define ENDIAN_MSB + constexpr auto endian() -> Endian { return Endian::MSB; } +#else + #warning "unable to detect endian" + #define ENDIAN_UNKNOWN + constexpr auto endian() -> Endian { return Endian::Unknown; } +#endif + +} + +/* Build optimization level detection */ + +#undef DEBUG +#undef NDEBUG + +namespace nall { + +#if defined(BUILD_DEBUG) + #define DEBUG + constexpr auto build() -> Build { return Build::Debug; } +#elif defined(BUILD_STABLE) + #define DEBUG + constexpr auto build() -> Build { return Build::Stable; } +#elif defined(BUILD_SIZE) + #define NDEBUG + constexpr auto build() -> Build { return Build::Size; } +#elif defined(BUILD_RELEASE) + #define NDEBUG + constexpr auto build() -> Build { return Build::Release; } +#elif defined(BUILD_PERFORMANCE) + #define NDEBUG + constexpr auto build() -> Build { return Build::Performance; } +#else + //default to debug mode + #define DEBUG + constexpr auto build() -> Build { return Build::Debug; } +#endif + +} diff --git a/waterbox/bsnescore/bsnes/nall/iterator.hpp b/waterbox/bsnescore/bsnes/nall/iterator.hpp new file mode 100644 index 0000000000..6c2625e9e2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/iterator.hpp @@ -0,0 +1,79 @@ +#pragma once + +namespace nall { + +template struct iterator { + iterator(T* self, uint64_t offset) : _self(self), _offset(offset) {} + auto operator*() -> T& { return _self[_offset]; } + auto operator!=(const iterator& source) const -> bool { return _offset != source._offset; } + auto operator++() -> iterator& { return _offset++, *this; } + auto offset() const -> uint64_t { return _offset; } + +private: + T* _self; + uint64_t _offset; +}; + +template struct iterator_const { + iterator_const(const T* self, uint64_t offset) : _self(self), _offset(offset) {} + auto operator*() -> const T& { return _self[_offset]; } + auto operator!=(const iterator_const& source) const -> bool { return _offset != source._offset; } + auto operator++() -> iterator_const& { return _offset++, *this; } + auto offset() const -> uint64_t { return _offset; } + +private: + const T* _self; + uint64_t _offset; +}; + +template struct reverse_iterator { + reverse_iterator(T* self, uint64_t offset) : _self(self), _offset(offset) {} + auto operator*() -> T& { return _self[_offset]; } + auto operator!=(const reverse_iterator& source) const -> bool { return _offset != source._offset; } + auto operator++() -> reverse_iterator& { return _offset--, *this; } + auto offset() const -> uint64_t { return _offset; } + +private: + T* _self; + uint64_t _offset; +}; + +template struct reverse_iterator_const { + reverse_iterator_const(const T* self, uint64_t offset) : _self(self), _offset(offset) {} + auto operator*() -> const T& { return _self[_offset]; } + auto operator!=(const reverse_iterator_const& source) const -> bool { return _offset != source._offset; } + auto operator++() -> reverse_iterator_const& { return _offset--, *this; } + auto offset() const -> uint64_t { return _offset; } + +private: + const T* _self; + uint64_t _offset; +}; + +//std::rbegin(), std::rend() is missing from GCC 4.9; which I still target + +template auto rbegin(T (&array)[Size]) { return reverse_iterator{array, Size - 1}; } +template auto rend(T (&array)[Size]) { return reverse_iterator{array, (uint64_t)-1}; } + +template auto rbegin(T& self) { return self.rbegin(); } +template auto rend(T& self) { return self.rend(); } + +template struct reverse_wrapper { + auto begin() { return rbegin(_self); } + auto end() { return rend(_self); } + + auto begin() const { return rbegin(_self); } + auto end() const { return rend(_self); } + + T _self; +}; + +template auto reverse(T& object) -> reverse_wrapper { + return {object}; +} + +template auto reverse(T&& object) -> reverse_wrapper { + return {object}; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/literals.hpp b/waterbox/bsnescore/bsnes/nall/literals.hpp new file mode 100644 index 0000000000..304823be82 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/literals.hpp @@ -0,0 +1,20 @@ +#pragma once + +namespace nall { + +inline constexpr auto operator"" _Kibit(unsigned long long value) { return value * 1024 / 8; } +inline constexpr auto operator"" _Mibit(unsigned long long value) { return value * 1024 * 1024 / 8; } +inline constexpr auto operator"" _Gibit(unsigned long long value) { return value * 1024 * 1024 * 1024 / 8; } +inline constexpr auto operator"" _Tibit(unsigned long long value) { return value * 1024 * 1024 * 1024 * 1024 / 8; } + +inline constexpr auto operator"" _KiB(unsigned long long value) { return value * 1024; } +inline constexpr auto operator"" _MiB(unsigned long long value) { return value * 1024 * 1024; } +inline constexpr auto operator"" _GiB(unsigned long long value) { return value * 1024 * 1024 * 1024; } +inline constexpr auto operator"" _TiB(unsigned long long value) { return value * 1024 * 1024 * 1024 * 1024; } + +inline constexpr auto operator"" _KHz(unsigned long long value) { return value * 1000; } +inline constexpr auto operator"" _MHz(unsigned long long value) { return value * 1000 * 1000; } +inline constexpr auto operator"" _GHz(unsigned long long value) { return value * 1000 * 1000 * 1000; } +inline constexpr auto operator"" _THz(unsigned long long value) { return value * 1000 * 1000 * 1000 * 1000; } + +} diff --git a/waterbox/bsnescore/bsnes/nall/locale.hpp b/waterbox/bsnescore/bsnes/nall/locale.hpp new file mode 100644 index 0000000000..6fb9f173e8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/locale.hpp @@ -0,0 +1,87 @@ +#pragma once + +namespace nall { + +struct Locale { + struct Dictionary { + string location; + string language; + Markup::Node document; + }; + + auto scan(string pathname) -> void { + dictionaries.reset(); + selected.reset(); + for(auto filename : directory::icontents(pathname, "*.bml")) { + Dictionary dictionary; + dictionary.location = {pathname, filename}; + dictionary.document = BML::unserialize(string::read(dictionary.location)); + dictionary.language = dictionary.document["locale/language"].text(); + dictionaries.append(dictionary); + } + } + + auto available() const -> vector { + vector result; + for(auto& dictionary : dictionaries) { + result.append(dictionary.language); + } + return result; + } + + auto select(string option) -> bool { + selected.reset(); + for(auto& dictionary : dictionaries) { + if(option == Location::prefix(dictionary.location) || option == dictionary.language) { + selected = dictionary; + return true; + } + } + return false; + } + + template + auto operator()(string ns, string input, P&&... p) const -> string { + vector arguments{forward

(p)...}; + if(selected) { + for(auto node : selected().document) { + if(node.name() == "namespace" && node.text() == ns) { + for(auto map : node) { + if(map.name() == "map" && map["input"].text() == input) { + input = map["value"].text(); + break; + } + } + } + } + } + for(uint index : range(arguments.size())) { + input.replace({"{", index, "}"}, arguments[index]); + } + return input; + } + + struct Namespace { + Namespace(Locale& _locale, string _namespace) : _locale(_locale), _namespace(_namespace) {} + + template + auto operator()(string input, P&&... p) const -> string { + return _locale(_namespace, input, forward

(p)...); + } + + template + auto tr(string input, P&&... p) const -> string { + return _locale(_namespace, input, forward

(p)...); + } + + private: + Locale& _locale; + string _namespace; + }; + +private: + vector dictionaries; + maybe selected; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/location.hpp b/waterbox/bsnescore/bsnes/nall/location.hpp new file mode 100644 index 0000000000..7aeb8b07db --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/location.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include + +namespace nall::Location { + +// (/parent/child.type/) +// (/parent/child.type/)name.type +inline auto path(string_view self) -> string { + const char* p = self.data() + self.size() - 1; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/') return slice(self, 0, offset + 1); + } + return ""; //no path found +} + +// /parent/child.type/() +// /parent/child.type/(name.type) +inline auto file(string_view self) -> string { + const char* p = self.data() + self.size() - 1; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/') return slice(self, offset + 1); + } + return self; //no path found +} + +// (/parent/)child.type/ +// (/parent/child.type/)name.type +inline auto dir(string_view self) -> string { + const char* p = self.data() + self.size() - 1, *last = p; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/' && p == last) continue; + if(*p == '/') return slice(self, 0, offset + 1); + } + return ""; //no path found +} + +// /parent/(child.type/) +// /parent/child.type/(name.type) +inline auto base(string_view self) -> string { + const char* p = self.data() + self.size() - 1, *last = p; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/' && p == last) continue; + if(*p == '/') return slice(self, offset + 1); + } + return self; //no path found +} + +// /parent/(child).type/ +// /parent/child.type/(name).type +inline auto prefix(string_view self) -> string { + const char* p = self.data() + self.size() - 1, *last = p; + for(int offset = self.size() - 1, suffix = -1; offset >= 0; offset--, p--) { + if(*p == '/' && p == last) continue; + if(*p == '/') return slice(self, offset + 1, (suffix >= 0 ? suffix : self.size()) - offset - 1).trimRight("/"); + if(*p == '.' && suffix == -1) { suffix = offset; continue; } + if(offset == 0) return slice(self, offset, suffix).trimRight("/"); + } + return ""; //no prefix found +} + +// /parent/child(.type)/ +// /parent/child.type/name(.type) +inline auto suffix(string_view self) -> string { + const char* p = self.data() + self.size() - 1, *last = p; + for(int offset = self.size() - 1; offset >= 0; offset--, p--) { + if(*p == '/' && p == last) continue; + if(*p == '/') break; + if(*p == '.') return slice(self, offset).trimRight("/"); + } + return ""; //no suffix found +} + +inline auto notsuffix(string_view self) -> string { + return {path(self), prefix(self)}; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/mac/poly1305.hpp b/waterbox/bsnescore/bsnes/nall/mac/poly1305.hpp new file mode 100644 index 0000000000..befbf46e7c --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/mac/poly1305.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include + +namespace nall::MAC { + +struct Poly1305 { + auto authenticate(array_view memory, uint256_t nonce) -> uint128_t { + initialize(nonce); + process(memory.data(), memory.size()); + return finish(); + } + + auto initialize(uint256_t key) -> void { + uint64_t t0 = key >> 0; + uint64_t t1 = key >> 64; + pad[0] = key >> 128; + pad[1] = key >> 192; + + r[0] = (t0 ) & 0xffc0fffffff; + r[1] = (t0 >> 44 | t1 << 20) & 0xfffffc0ffff; + r[2] = ( t1 >> 24) & 0x00ffffffc0f; + + h[0] = 0, h[1] = 0, h[2] = 0; + offset = 0; + } + + auto process(const uint8_t* data, uint64_t size) -> void { + while(size--) { + buffer[offset++] = *data++; + if(offset >= 16) { + block(); + offset = 0; + } + } + } + + auto finish() -> uint128_t { + if(offset) { + buffer[offset++] = 1; + while(offset < 16) buffer[offset++] = 0; + block(true); + } + + uint64_t h0 = h[0], h1 = h[1], h2 = h[2]; + + uint64_t c = h1 >> 44; h1 &= 0xfffffffffff; + h2 += c; c = h2 >> 42; h2 &= 0x3ffffffffff; + h0 += c * 5; c = h0 >> 44; h0 &= 0xfffffffffff; + h1 += c; c = h1 >> 44; h1 &= 0xfffffffffff; + h2 += c; c = h2 >> 42; h2 &= 0x3ffffffffff; + h0 += c * 5; c = h0 >> 44; h0 &= 0xfffffffffff; + h1 += c; + + uint64_t g0 = h0 + 5; c = g0 >> 44; g0 &= 0xfffffffffff; + uint64_t g1 = h1 + c; c = g1 >> 44; g1 &= 0xfffffffffff; + uint64_t g2 = h2 + c - (1ull << 42); + + c = (g2 >> 63) - 1; + g0 &= c, g1 &= c, g2 &= c; + c = ~c; + h0 = (h0 & c) | g0; + h1 = (h1 & c) | g1; + h2 = (h2 & c) | g2; + + uint64_t t0 = pad[0], t1 = pad[1]; + + h0 += ((t0 ) & 0xfffffffffff) ; c = h0 >> 44; h0 &= 0xfffffffffff; + h1 += ((t0 >> 44 | t1 << 20) & 0xfffffffffff) + c; c = h1 >> 44; h1 &= 0xfffffffffff; + h2 += (( t1 >> 24) & 0x3ffffffffff) + c; h2 &= 0x3ffffffffff; + + h0 = (h0 >> 0 | h1 << 44); + h1 = (h1 >> 20 | h2 << 24); + + r[0] = 0, r[1] = 0, r[2] = 0; + h[0] = 0, h[1] = 0, h[2] = 0; + pad[0] = 0, pad[1] = 0; + memory::fill(buffer, sizeof(buffer)); + offset = 0; + + return uint128_t(h1) << 64 | h0; + } + +private: + auto block(bool last = false) -> void { + uint64_t r0 = r[0], r1 = r[1], r2 = r[2]; + uint64_t h0 = h[0], h1 = h[1], h2 = h[2]; + + uint64_t s1 = r1 * 20; + uint64_t s2 = r2 * 20; + + uint64_t t0 = memory::readl<8>(buffer + 0); + uint64_t t1 = memory::readl<8>(buffer + 8); + + h0 += ((t0 ) & 0xfffffffffff); + h1 += ((t0 >> 44 | t1 << 20) & 0xfffffffffff); + h2 += (( t1 >> 24) & 0x3ffffffffff) | (last ? 0 : 1ull << 40); + + uint128_t d, d0, d1, d2; + d0 = (uint128_t)h0 * r0; d = (uint128_t)h1 * s2; d0 += d; d = (uint128_t)h2 * s1; d0 += d; + d1 = (uint128_t)h0 * r1; d = (uint128_t)h1 * r0; d1 += d; d = (uint128_t)h2 * s2; d1 += d; + d2 = (uint128_t)h0 * r2; d = (uint128_t)h1 * r1; d2 += d; d = (uint128_t)h2 * r0; d2 += d; + + uint64_t c = (uint64_t)(d0 >> 44); h0 = (uint64_t)d0 & 0xfffffffffff; + d1 += c; c = (uint64_t)(d1 >> 44); h1 = (uint64_t)d1 & 0xfffffffffff; + d2 += c; c = (uint64_t)(d2 >> 42); h2 = (uint64_t)d2 & 0x3ffffffffff; + + h0 += c * 5; c = h0 >> 44; h0 &= 0xfffffffffff; + h1 += c; + + h[0] = h0, h[1] = h1, h[2] = h2; + } + + uint64_t r[3]; + uint64_t h[3]; + uint64_t pad[2]; + + uint8_t buffer[16]; + uint offset; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/macos/guard.hpp b/waterbox/bsnescore/bsnes/nall/macos/guard.hpp new file mode 100644 index 0000000000..96cd4d6904 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/macos/guard.hpp @@ -0,0 +1,15 @@ +#ifndef NALL_MACOS_GUARD_HPP +#define NALL_MACOS_GUARD_HPP + +#define Boolean CocoaBoolean +#define decimal CocoaDecimal +#define DEBUG CocoaDebug + +#else +#undef NALL_MACOS_GUARD_HPP + +#undef Boolean +#undef decimal +#undef DEBUG + +#endif diff --git a/waterbox/bsnescore/bsnes/nall/main.hpp b/waterbox/bsnescore/bsnes/nall/main.hpp new file mode 100644 index 0000000000..24642b61c5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/main.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +namespace nall { + auto main(Arguments arguments) -> void; + + auto main(int argc, char** argv) -> int { + #if defined(PLATFORM_WINDOWS) + CoInitialize(0); + WSAData wsaData{0}; + WSAStartup(MAKEWORD(2, 2), &wsaData); + _setmode(_fileno(stdin ), O_BINARY); + _setmode(_fileno(stdout), O_BINARY); + _setmode(_fileno(stderr), O_BINARY); + #endif + + main(move(Arguments{argc, argv})); + + //when a program is running, input on the terminal queues in stdin + //when terminating the program, the shell proceeds to try and execute all stdin data + //this is annoying behavior: this code tries to minimize the impact as much as it can + //we can flush all of stdin up to the last line feed, preventing spurious commands from executing + //however, even with setvbuf(_IONBF), we can't stop the last line from echoing to the terminal + #if !defined(PLATFORM_WINDOWS) + auto flags = fcntl(fileno(stdin), F_GETFL, 0); + fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK); //don't allow read() to block when empty + char buffer[4096], data = false; + while(read(fileno(stdin), buffer, sizeof(buffer)) > 0) data = true; + fcntl(fileno(stdin), F_SETFL, flags); //restore original flags for the terminal + if(data) putchar('\r'); //ensures PS1 is printed at the start of the line + #endif + + return EXIT_SUCCESS; + } +} + +auto main(int argc, char** argv) -> int { + return nall::main(argc, argv); +} diff --git a/waterbox/bsnescore/bsnes/nall/map.hpp b/waterbox/bsnescore/bsnes/nall/map.hpp new file mode 100644 index 0000000000..58b1e11b86 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/map.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +namespace nall { + +template struct map { + struct node_t { + T key; + U value; + node_t() = default; + node_t(const T& key) : key(key) {} + node_t(const T& key, const U& value) : key(key), value(value) {} + auto operator< (const node_t& source) const -> bool { return key < source.key; } + auto operator==(const node_t& source) const -> bool { return key == source.key; } + }; + + auto find(const T& key) const -> maybe { + if(auto node = root.find({key})) return node().value; + return nothing; + } + + auto insert(const T& key, const U& value) -> void { root.insert({key, value}); } + auto remove(const T& key) -> void { root.remove({key}); } + auto size() const -> unsigned { return root.size(); } + auto reset() -> void { root.reset(); } + + auto begin() -> typename set::iterator { return root.begin(); } + auto end() -> typename set::iterator { return root.end(); } + + auto begin() const -> const typename set::iterator { return root.begin(); } + auto end() const -> const typename set::iterator { return root.end(); } + +protected: + set root; +}; + +template struct bimap { + auto find(const T& key) const -> maybe { return tmap.find(key); } + auto find(const U& key) const -> maybe { return umap.find(key); } + auto insert(const T& key, const U& value) -> void { tmap.insert(key, value); umap.insert(value, key); } + auto remove(const T& key) -> void { if(auto p = tmap.find(key)) { umap.remove(p().value); tmap.remove(key); } } + auto remove(const U& key) -> void { if(auto p = umap.find(key)) { tmap.remove(p().value); umap.remove(key); } } + auto size() const -> unsigned { return tmap.size(); } + auto reset() -> void { tmap.reset(); umap.reset(); } + + auto begin() -> typename set::node_t>::iterator { return tmap.begin(); } + auto end() -> typename set::node_t>::iterator { return tmap.end(); } + + auto begin() const -> const typename set::node_t>::iterator { return tmap.begin(); } + auto end() const -> const typename set::node_t>::iterator { return tmap.end(); } + +protected: + map tmap; + map umap; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/matrix-multiply.hpp b/waterbox/bsnescore/bsnes/nall/matrix-multiply.hpp new file mode 100644 index 0000000000..a82d8ea2cc --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/matrix-multiply.hpp @@ -0,0 +1,36 @@ +#pragma once + +//matrix multiplication primitives +//used in: ruby/opengl/quark + +namespace nall { + +template inline auto MatrixMultiply( +T* output, +const T* xdata, uint xrows, uint xcols, +const T* ydata, uint yrows, uint ycols +) -> void { + if(xcols != yrows) return; + + for(uint y : range(xrows)) { + for(uint x : range(ycols)) { + T sum = 0; + for(uint z : range(xcols)) { + sum += xdata[y * xcols + z] * ydata[z * ycols + x]; + } + *output++ = sum; + } + } +} + +template inline auto MatrixMultiply( +const T* xdata, uint xrows, uint xcols, +const T* ydata, uint yrows, uint ycols +) -> vector { + vector output; + output.resize(xrows * ycols); + MatrixMultiply(output.data(), xdata, xrows, xcols, ydata, yrows, ycols); + return output; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/matrix.hpp b/waterbox/bsnescore/bsnes/nall/matrix.hpp new file mode 100644 index 0000000000..e266df6a63 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/matrix.hpp @@ -0,0 +1,213 @@ +#pragma once + +namespace nall { + +template +struct Matrix { + static_assert(Rows > 0 && Cols > 0); + + Matrix() = default; + Matrix(const Matrix&) = default; + Matrix(const initializer_list& source) { + uint index = 0; + for(auto& value : source) { + if(index >= Rows * Cols) break; + values[index / Cols][index % Cols] = value; + } + } + + operator array_span() { return {values, Rows * Cols}; } + operator array_view() const { return {values, Rows * Cols}; } + + //1D matrices (for polynomials, etc) + auto operator[](uint row) -> T& { return values[row][0]; } + auto operator[](uint row) const -> T { return values[row][0]; } + + //2D matrices + auto operator()(uint row, uint col) -> T& { return values[row][col]; } + auto operator()(uint row, uint col) const -> T { return values[row][col]; } + + //operators + auto operator+() const -> Matrix { + Matrix result; + for(uint row : range(Rows)) { + for(uint col : range(Cols)) { + result(row, col) = +target(row, col); + } + } + return result; + } + + auto operator-() const -> Matrix { + Matrix result; + for(uint row : range(Rows)) { + for(uint col : range(Cols)) { + result(row, col) = -target(row, col); + } + } + return result; + } + + auto operator+(const Matrix& source) const -> Matrix { + Matrix result; + for(uint row : range(Rows)) { + for(uint col : range(Cols)) { + result(row, col) = target(row, col) + source(row, col); + } + } + return result; + } + + auto operator-(const Matrix& source) const -> Matrix { + Matrix result; + for(uint row : range(Rows)) { + for(uint col : range(Cols)) { + result(row, col) = target(row, col) - source(row, col); + } + } + return result; + } + + auto operator*(T source) const -> Matrix { + Matrix result; + for(uint row : range(Rows)) { + for(uint col : range(Cols)) { + result(row, col) = target(row, col) * source; + } + } + return result; + } + + auto operator/(T source) const -> Matrix { + Matrix result; + for(uint row : range(Rows)) { + for(uint col : range(Cols)) { + result(row, col) = target(row, col) / source; + } + } + return result; + } + + //warning: matrix multiplication is not commutative! + template + auto operator*(const Matrix& source) const -> Matrix { + static_assert(Cols == SourceRows); + Matrix result; + for(uint y : range(Rows)) { + for(uint x : range(SourceCols)) { + T sum{}; + for(uint z : range(Cols)) { + sum += target(y, z) * source(z, x); + } + result(y, x) = sum; + } + } + return result; + } + + template + auto operator/(const Matrix& source) const -> maybe> { + static_assert(Cols == SourceRows && SourceRows == SourceCols); + if(auto inverted = source.invert()) return operator*(inverted()); + return {}; + } + + auto& operator+=(const Matrix& source) { return *this = operator+(source); } + auto& operator-=(const Matrix& source) { return *this = operator-(source); } + auto& operator*=(T source) { return *this = operator*(source); } + auto& operator/=(T source) { return *this = operator/(source); } + template + auto& operator*=(const Matrix& source) { return *this = operator*(source); } + //matrix division is not always possible (when matrix cannot be inverted), so operator/= is not provided + + //algorithm: Gauss-Jordan + auto invert() const -> maybe { + static_assert(Rows == Cols); + Matrix source = *this; + Matrix result = identity(); + + const auto add = [&](uint targetRow, uint sourceRow, T factor = 1) { + for(uint col : range(Cols)) { + result(targetRow, col) += result(sourceRow, col) * factor; + source(targetRow, col) += source(sourceRow, col) * factor; + } + }; + + const auto sub = [&](uint targetRow, uint sourceRow, T factor = 1) { + for(uint col : range(Cols)) { + result(targetRow, col) -= result(sourceRow, col) * factor; + source(targetRow, col) -= source(sourceRow, col) * factor; + } + }; + + const auto mul = [&](uint row, T factor) { + for(uint col : range(Cols)) { + result(row, col) *= factor; + source(row, col) *= factor; + } + }; + + for(uint i : range(Cols)) { + if(source(i, i) == 0) { + for(uint row : range(Rows)) { + if(source(row, i) != 0) { + add(i, row); + break; + } + } + //matrix is not invertible: + if(source(i, i) == 0) return {}; + } + + mul(i, T{1} / source(i, i)); + for(uint row : range(Rows)) { + if(row == i) continue; + sub(row, i, source(row, i)); + } + } + + return result; + } + + auto transpose() const -> Matrix { + Matrix result; + for(uint row : range(Rows)) { + for(uint col : range(Cols)) { + result(col, row) = target(row, col); + } + } + return result; + } + + static auto identity() -> Matrix { + static_assert(Rows == Cols); + Matrix result; + for(uint row : range(Rows)) { + for(uint col : range(Cols)) { + result(row, col) = row == col; + } + } + return result; + } + + //debugging function: do not use in production code + template + auto _print() const -> void { + for(uint row : range(Rows)) { + nall::print("[ "); + for(uint col : range(Cols)) { + nall::print(pad(target(row, col), Pad, ' '), " "); + } + nall::print("]\n"); + } + } + +protected: + //same as operator(), but with easier to read syntax inside Matrix class + auto target(uint row, uint col) -> T& { return values[row][col]; } + auto target(uint row, uint col) const -> T { return values[row][col]; } + + T values[Rows][Cols]{}; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/maybe.hpp b/waterbox/bsnescore/bsnes/nall/maybe.hpp new file mode 100644 index 0000000000..0c3ae522bb --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/maybe.hpp @@ -0,0 +1,91 @@ +#pragma once + +namespace nall { + +struct nothing_t {}; +static nothing_t nothing; +struct else_t {}; + +template +struct maybe { + inline maybe() {} + inline maybe(nothing_t) {} + inline maybe(const T& source) { operator=(source); } + inline maybe(T&& source) { operator=(move(source)); } + inline maybe(const maybe& source) { operator=(source); } + inline maybe(maybe&& source) { operator=(move(source)); } + inline ~maybe() { reset(); } + + inline auto operator=(nothing_t) -> maybe& { reset(); return *this; } + inline auto operator=(const T& source) -> maybe& { reset(); _valid = true; new(&_value.t) T(source); return *this; } + inline auto operator=(T&& source) -> maybe& { reset(); _valid = true; new(&_value.t) T(move(source)); return *this; } + + inline auto operator=(const maybe& source) -> maybe& { + if(this == &source) return *this; + reset(); + if(_valid = source._valid) new(&_value.t) T(source.get()); + return *this; + } + + inline auto operator=(maybe&& source) -> maybe& { + if(this == &source) return *this; + reset(); + if(_valid = source._valid) new(&_value.t) T(move(source.get())); + return *this; + } + + inline explicit operator bool() const { return _valid; } + inline auto reset() -> void { if(_valid) { _value.t.~T(); _valid = false; } } + inline auto data() -> T* { return _valid ? &_value.t : nullptr; } + inline auto get() -> T& { assert(_valid); return _value.t; } + + inline auto data() const -> const T* { return ((maybe*)this)->data(); } + inline auto get() const -> const T& { return ((maybe*)this)->get(); } + inline auto operator->() -> T* { return data(); } + inline auto operator->() const -> const T* { return data(); } + inline auto operator*() -> T& { return get(); } + inline auto operator*() const -> const T& { return get(); } + inline auto operator()() -> T& { return get(); } + inline auto operator()() const -> const T& { return get(); } + inline auto operator()(const T& invalid) const -> const T& { return _valid ? get() : invalid; } + +private: + union U { + T t; + U() {} + ~U() {} + } _value; + bool _valid = false; +}; + +template +struct maybe { + inline maybe() : _value(nullptr) {} + inline maybe(nothing_t) : _value(nullptr) {} + inline maybe(const T& source) : _value((T*)&source) {} + inline maybe(const maybe& source) : _value(source._value) {} + + inline auto operator=(nothing_t) -> maybe& { _value = nullptr; return *this; } + inline auto operator=(const T& source) -> maybe& { _value = (T*)&source; return *this; } + inline auto operator=(const maybe& source) -> maybe& { _value = source._value; return *this; } + + inline explicit operator bool() const { return _value; } + inline auto reset() -> void { _value = nullptr; } + inline auto data() -> T* { return _value; } + inline auto get() -> T& { assert(_value); return *_value; } + + inline auto data() const -> const T* { return ((maybe*)this)->data(); } + inline auto get() const -> const T& { return ((maybe*)this)->get(); } + inline auto operator->() -> T* { return data(); } + inline auto operator->() const -> const T* { return data(); } + inline auto operator*() -> T& { return get(); } + inline auto operator*() const -> const T& { return get(); } + inline auto operator()() -> T& { return get(); } + inline auto operator()() const -> const T& { return get(); } + inline auto operator()(const T& invalid) const -> const T& { return _value ? get() : invalid; } + +private: + T* _value; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/memory.hpp b/waterbox/bsnescore/bsnes/nall/memory.hpp new file mode 100644 index 0000000000..9eba671ad5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/memory.hpp @@ -0,0 +1,163 @@ +#pragma once + +#include +#include + +namespace nall::memory { + template inline auto allocate(uint size) -> T*; + template inline auto allocate(uint size, const T& value) -> T*; + + template inline auto resize(void* target, uint size) -> T*; + + inline auto free(void* target) -> void; + + template inline auto compare(const void* target, uint capacity, const void* source, uint size) -> int; + template inline auto compare(const void* target, const void* source, uint size) -> int; + + template inline auto icompare(const void* target, uint capacity, const void* source, uint size) -> int; + template inline auto icompare(const void* target, const void* source, uint size) -> int; + + template inline auto copy(void* target, uint capacity, const void* source, uint size) -> T*; + template inline auto copy(void* target, const void* source, uint size) -> T*; + + template inline auto move(void* target, uint capacity, const void* source, uint size) -> T*; + template inline auto move(void* target, const void* source, uint size) -> T*; + + template inline auto fill(void* target, uint capacity, const T& value = {}) -> T*; + + template inline auto assign(T* target) -> void {} + template inline auto assign(T* target, const U& value, P&&... p) -> void; + + template inline auto readl(const void* source) -> T; + template inline auto readm(const void* source) -> T; + + template inline auto writel(void* target, T data) -> void; + template inline auto writem(void* target, T data) -> void; +} + +namespace nall::memory { + +//implementation notes: +//memcmp, memcpy, memmove have terrible performance on small block sizes (FreeBSD 10.0-amd64) +//as this library is used extensively by nall/string, and most strings tend to be small, +//this library hand-codes these functions instead. surprisingly, it's a substantial speedup + +template auto allocate(uint size) -> T* { + return (T*)malloc(size * sizeof(T)); +} + +template auto allocate(uint size, const T& value) -> T* { + auto result = allocate(size); + if(result) fill(result, size, value); + return result; +} + +template auto resize(void* target, uint size) -> T* { + return (T*)realloc(target, size * sizeof(T)); +} + +auto free(void* target) -> void { + ::free(target); +} + +template auto compare(const void* target, uint capacity, const void* source, uint size) -> int { + auto t = (uint8_t*)target; + auto s = (uint8_t*)source; + auto l = min(capacity, size) * sizeof(T); + while(l--) { + auto x = *t++; + auto y = *s++; + if(x != y) return x - y; + } + if(capacity == size) return 0; + return -(capacity < size); +} + +template auto compare(const void* target, const void* source, uint size) -> int { + return compare(target, size, source, size); +} + +template auto icompare(const void* target, uint capacity, const void* source, uint size) -> int { + auto t = (uint8_t*)target; + auto s = (uint8_t*)source; + auto l = min(capacity, size) * sizeof(T); + while(l--) { + auto x = *t++; + auto y = *s++; + if(x - 'A' < 26) x += 32; + if(y - 'A' < 26) y += 32; + if(x != y) return x - y; + } + return -(capacity < size); +} + +template auto icompare(const void* target, const void* source, uint size) -> int { + return icompare(target, size, source, size); +} + +template auto copy(void* target, uint capacity, const void* source, uint size) -> T* { + auto t = (uint8_t*)target; + auto s = (uint8_t*)source; + auto l = min(capacity, size) * sizeof(T); + while(l--) *t++ = *s++; + return (T*)target; +} + +template auto copy(void* target, const void* source, uint size) -> T* { + return copy(target, size, source, size); +} + +template auto move(void* target, uint capacity, const void* source, uint size) -> T* { + auto t = (uint8_t*)target; + auto s = (uint8_t*)source; + auto l = min(capacity, size) * sizeof(T); + if(t < s) { + while(l--) *t++ = *s++; + } else { + t += l; + s += l; + while(l--) *--t = *--s; + } + return (T*)target; +} + +template auto move(void* target, const void* source, uint size) -> T* { + return move(target, size, source, size); +} + +template auto fill(void* target, uint capacity, const T& value) -> T* { + auto t = (T*)target; + while(capacity--) *t++ = value; + return (T*)target; +} + +template auto assign(T* target, const U& value, P&&... p) -> void { + *target++ = value; + assign(target, forward

(p)...); +} + +template auto readl(const void* source) -> T { + auto p = (const uint8_t*)source; + T data = 0; + for(uint n = 0; n < size; n++) data |= T(*p++) << n * 8; + return data; +} + +template auto readm(const void* source) -> T { + auto p = (const uint8_t*)source; + T data = 0; + for(int n = size - 1; n >= 0; n--) data |= T(*p++) << n * 8; + return data; +} + +template auto writel(void* target, T data) -> void { + auto p = (uint8_t*)target; + for(uint n = 0; n < size; n++) *p++ = data >> n * 8; +} + +template auto writem(void* target, T data) -> void { + auto p = (uint8_t*)target; + for(int n = size - 1; n >= 0; n--) *p++ = data >> n * 8; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/merge-sort.hpp b/waterbox/bsnescore/bsnes/nall/merge-sort.hpp new file mode 100644 index 0000000000..5c1175b8d7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/merge-sort.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +//class: merge sort +//average: O(n log n) +//worst: O(n log n) +//memory: O(n) +//stack: O(log n) +//stable?: yes + +//note: merge sort was chosen over quick sort, because: +//* it is a stable sort +//* it lacks O(n^2) worst-case overhead +//* it usually runs faster than quick sort anyway + +//note: insertion sort is generally more performant than selection sort +#define NALL_MERGE_SORT_INSERTION +//#define NALL_MERGE_SORT_SELECTION + +namespace nall { + +template auto sort(T list[], uint size, const Comparator& lessthan) -> void { + if(size <= 1) return; //nothing to sort + + //sort smaller blocks using an O(n^2) algorithm (which for small sizes, increases performance) + if(size < 64) { + //insertion sort requires a copy (via move construction) + #if defined(NALL_MERGE_SORT_INSERTION) + for(int i = 1, j; i < size; i++) { + T copy(move(list[i])); + for(j = i - 1; j >= 0; j--) { + if(!lessthan(copy, list[j])) break; + list[j + 1] = move(list[j]); + } + list[j + 1] = move(copy); + } + //selection sort requires a swap + #elif defined(NALL_MERGE_SORT_SELECTION) + for(uint i = 0; i < size; i++) { + uint min = i; + for(uint j = i + 1; j < size; j++) { + if(lessthan(list[j], list[min])) min = j; + } + if(min != i) swap(list[i], list[min]); + } + #endif + return; + } + + //split list in half and recursively sort both + uint middle = size / 2; + sort(list, middle, lessthan); + sort(list + middle, size - middle, lessthan); + + //left and right are sorted here; perform merge sort + //use placement new to avoid needing T to be default-constructable + auto buffer = memory::allocate(size); + uint offset = 0, left = 0, right = middle; + while(left < middle && right < size) { + if(!lessthan(list[right], list[left])) { + new(buffer + offset++) T(move(list[left++])); + } else { + new(buffer + offset++) T(move(list[right++])); + } + } + while(left < middle) new(buffer + offset++) T(move(list[left++])); + while(right < size ) new(buffer + offset++) T(move(list[right++])); + + for(uint i = 0; i < size; i++) { + list[i] = move(buffer[i]); + buffer[i].~T(); + } + memory::free(buffer); +} + +template auto sort(T list[], uint size) -> void { + return sort(list, size, [](const T& l, const T& r) { return l < r; }); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/nall.hpp b/waterbox/bsnescore/bsnes/nall/nall.hpp new file mode 100644 index 0000000000..ec04866ed8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/nall.hpp @@ -0,0 +1,101 @@ +#pragma once + +/* nall + * author: byuu + * license: ISC + * + * nall is a header library that provides both fundamental and useful classes + * its goals are portability, consistency, minimalism and reusability + */ + +//include the most common nall headers with one statement +//does not include the most obscure components with high cost and low usage + +#include + +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //todo: compilation errors when included earlier +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include + #include +#endif + +#if defined(API_POSIX) + #include +#endif diff --git a/waterbox/bsnescore/bsnes/nall/path.hpp b/waterbox/bsnescore/bsnes/nall/path.hpp new file mode 100644 index 0000000000..3353817b43 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/path.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include + +namespace nall::Path { + +inline auto active() -> string { + char path[PATH_MAX] = ""; + (void)getcwd(path, PATH_MAX); + string result = path; + if(!result) result = "."; + result.transform("\\", "/"); + if(!result.endsWith("/")) result.append("/"); + return result; +} + +inline auto real(string_view name) -> string { + string result; + char path[PATH_MAX] = ""; + if(::realpath(name, path)) result = Location::path(string{path}.transform("\\", "/")); + if(!result) return active(); + result.transform("\\", "/"); + if(!result.endsWith("/")) result.append("/"); + return result; +} + +inline auto program() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + GetModuleFileName(nullptr, path, PATH_MAX); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + return Path::real(result); + #else + Dl_info info; + dladdr((void*)&program, &info); + return Path::real(info.dli_fname); + #endif +} + +// / +// c:/ +inline auto root() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_WINDOWS | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + return slice(result, 0, 3); + #else + return "/"; + #endif +} + +// /home/username/ +// c:/users/username/ +inline auto user() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_PROFILE | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #else + struct passwd* userinfo = getpwuid(getuid()); + string result = userinfo->pw_dir; + #endif + if(!result) result = "."; + if(!result.endsWith("/")) result.append("/"); + return result; +} + +// /home/username/Desktop/ +// c:/users/username/Desktop/ +inline auto desktop(string_view name = {}) -> string { + return {user(), "Desktop/", name}; +} + +//todo: MacOS uses the same location for userData() and userSettings() +//... is there a better option here? + +// /home/username/.config/ +// ~/Library/Application Support/ +// c:/users/username/appdata/roaming/ +inline auto userSettings() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(PLATFORM_MACOS) + string result = {Path::user(), "Library/Application Support/"}; + #else + string result; + if(const char *env = getenv("XDG_CONFIG_HOME")) { + result = string(env); + } else { + result = {Path::user(), ".config/"}; + } + #endif + if(!result) result = "."; + if(!result.endsWith("/")) result.append("/"); + return result; +} + +// /home/username/.local/share/ +// ~/Library/Application Support/ +// c:/users/username/appdata/local/ +inline auto userData() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(PLATFORM_MACOS) + string result = {Path::user(), "Library/Application Support/"}; + #else + string result; + if(const char *env = getenv("XDG_DATA_HOME")) { + result = string(env); + } else { + result = {Path::user(), ".local/share/"}; + } + #endif + if(!result) result = "."; + if(!result.endsWith("/")) result.append("/"); + return result; +} + +// /usr/share +// /Library/Application Support/ +// c:/ProgramData/ +inline auto sharedData() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(PLATFORM_MACOS) + string result = "/Library/Application Support/"; + #else + string result = "/usr/share/"; + #endif + if(!result) result = "."; + if(!result.endsWith("/")) result.append("/"); + return result; +} + +// /tmp +// c:/users/username/AppData/Local/Temp/ +inline auto temporary() -> string { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + GetTempPathW(PATH_MAX, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(P_tmpdir) + string result = P_tmpdir; + #else + string result = "/tmp/"; + #endif + if(!result.endsWith("/")) result.append("/"); + return result; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/platform.hpp b/waterbox/bsnescore/bsnes/nall/platform.hpp new file mode 100644 index 0000000000..bf8711c683 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/platform.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include + +namespace Math { + static const long double e = 2.71828182845904523536; + static const long double Pi = 3.14159265358979323846; +} + +#if defined(PLATFORM_WINDOWS) + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if !defined(PLATFORM_WINDOWS) + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#if defined(COMPILER_MICROSOFT) + #define va_copy(dest, src) ((dest) = (src)) +#endif + +#if defined(PLATFORM_WINDOWS) + #undef IN + #undef OUT + #undef interface + #define dllexport __declspec(dllexport) + #define MSG_NOSIGNAL 0 + + extern "C" { + using pollfd = WSAPOLLFD; + } + + inline auto access(const char* path, int amode) -> int { return _waccess(nall::utf16_t(path), amode); } + inline auto getcwd(char* buf, size_t size) -> char* { wchar_t wpath[PATH_MAX] = L""; if(!_wgetcwd(wpath, size)) return nullptr; strcpy(buf, nall::utf8_t(wpath)); return buf; } + inline auto mkdir(const char* path, int mode) -> int { return _wmkdir(nall::utf16_t(path)); } + inline auto poll(struct pollfd fds[], unsigned long nfds, int timeout) -> int { return WSAPoll(fds, nfds, timeout); } + inline auto putenv(const char* value) -> int { return _wputenv(nall::utf16_t(value)); } + inline auto realpath(const char* file_name, char* resolved_name) -> char* { wchar_t wfile_name[PATH_MAX] = L""; if(!_wfullpath(wfile_name, nall::utf16_t(file_name), PATH_MAX)) return nullptr; strcpy(resolved_name, nall::utf8_t(wfile_name)); return resolved_name; } + inline auto rename(const char* oldname, const char* newname) -> int { return _wrename(nall::utf16_t(oldname), nall::utf16_t(newname)); } + + namespace nall { + //network functions take void*, not char*. this allows them to be used without casting + + inline auto recv(int socket, void* buffer, size_t length, int flags) -> ssize_t { + return ::recv(socket, (char*)buffer, length, flags); + } + + inline auto send(int socket, const void* buffer, size_t length, int flags) -> ssize_t { + return ::send(socket, (const char*)buffer, length, flags); + } + + inline auto setsockopt(int socket, int level, int option_name, const void* option_value, socklen_t option_len) -> int { + return ::setsockopt(socket, level, option_name, (const char*)option_value, option_len); + } + } +#else + #define dllexport +#endif + +#if defined(PLATFORM_MACOS) + #define MSG_NOSIGNAL 0 +#endif + +#if defined(COMPILER_CLANG) || defined(COMPILER_GCC) + #define noinline __attribute__((noinline)) + #define alwaysinline inline __attribute__((always_inline)) +#elif defined(COMPILER_MICROSOFT) + #define noinline __declspec(noinline) + #define alwaysinline inline __forceinline +#else + #define noinline + #define alwaysinline inline +#endif + +//P0627: [[unreachable]] -- impossible to simulate with identical syntax, must omit brackets ... +#if defined(COMPILER_CLANG) || defined(COMPILER_GCC) + #define unreachable __builtin_unreachable() +#else + #define unreachable throw +#endif + +#define export $export +#define register $register diff --git a/waterbox/bsnescore/bsnes/nall/pointer.hpp b/waterbox/bsnescore/bsnes/nall/pointer.hpp new file mode 100644 index 0000000000..0e96ddb09a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/pointer.hpp @@ -0,0 +1,34 @@ +#pragma once + +namespace nall { + +template +struct pointer { + explicit operator bool() const { return value; } + + pointer() = default; + pointer(T* source) { value = source; } + pointer(const pointer& source) { value = source.value; } + + auto& operator=(T* source) { value = source; return *this; } + auto& operator=(const pointer& source) { value = source.value; return *this; } + + auto operator()() -> T* { return value; } + auto operator()() const -> const T* { return value; } + + auto operator->() -> T* { return value; } + auto operator->() const -> const T* { return value; } + + auto operator*() -> T& { return *value; } + auto operator*() const -> const T& { return *value; } + + auto reset() -> void { value = nullptr; } + + auto data() -> T* { return value; } + auto data() const -> const T* { return value; } + +private: + T* value = nullptr; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/posix/service.hpp b/waterbox/bsnescore/bsnes/nall/posix/service.hpp new file mode 100644 index 0000000000..623682c083 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/posix/service.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include + +namespace nall { + +struct service { + inline explicit operator bool() const; + inline auto command(const string& name, const string& command) -> bool; + inline auto receive() -> string; + inline auto name() const -> string; + inline auto stop() const -> bool; + +private: + shared_memory shared; + string _name; + bool _stop = false; +}; + +service::operator bool() const { + return (bool)shared; +} + +//returns true on new service process creation (false is not necessarily an error) +auto service::command(const string& name, const string& command) -> bool { + if(!name) return false; + if(!command) return print("[{0}] usage: {service} command\n" + "commands:\n" + " status : query whether service is running\n" + " start : start service if it is not running\n" + " stop : stop service if it is running\n" + " remove : remove semaphore lock if service crashed\n" + " {value} : send custom command to service\n" + "", string_format{name}), false; + + if(shared.open(name, 4096)) { + if(command == "start") { + print("[{0}] already started\n", string_format{name}); + } else if(command == "status") { + print("[{0}] running\n", string_format{name}); + } + if(auto data = shared.acquire()) { + if(command == "stop") print("[{0}] stopped\n", string_format{name}); + memory::copy(data, 4096, command.data(), command.size()); + shared.release(); + } + if(command == "remove") { + shared.remove(); + print("[{0}] removed\n", string_format{name}); + } + return false; + } + + if(command == "start") { + if(shared.create(name, 4096)) { + print("[{0}] started\n", string_format{name}); + auto pid = fork(); + if(pid == 0) { + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + _name = name; + return true; + } + shared.close(); + } else { + print("[{0}] start failed ({1})\n", string_format{name, strerror(errno)}); + } + return false; + } + + if(command == "status") { + print("[{0}] stopped\n", string_format{name}); + return false; + } + + return false; +} + +auto service::receive() -> string { + string command; + if(shared) { + if(auto data = shared.acquire()) { + if(*data) { + command.resize(4095); + memory::copy(command.get(), data, 4095); + memory::fill(data, 4096); + } + shared.release(); + if(command == "remove") { + _stop = true; + return ""; + } else if(command == "start") { + return ""; + } else if(command == "status") { + return ""; + } else if(command == "stop") { + _stop = true; + shared.remove(); + return ""; + } + } + } + return command; +} + +auto service::name() const -> string { + return _name; +} + +auto service::stop() const -> bool { + return _stop; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/posix/shared-memory.hpp b/waterbox/bsnescore/bsnes/nall/posix/shared-memory.hpp new file mode 100644 index 0000000000..af66563ce5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/posix/shared-memory.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include +#include + +namespace nall { + +struct shared_memory { + shared_memory() = default; + shared_memory(const shared_memory&) = delete; + auto operator=(const shared_memory&) -> shared_memory& = delete; + + ~shared_memory() { + reset(); + } + + explicit operator bool() const { + return _mode != mode::inactive; + } + + auto size() const -> unsigned { + return _size; + } + + auto acquired() const -> bool { + return _acquired; + } + + auto acquire() -> uint8_t* { + if(!acquired()) { + sem_wait(_semaphore); + _acquired = true; + } + return _data; + } + + auto release() -> void { + if(acquired()) { + sem_post(_semaphore); + _acquired = false; + } + } + + auto reset() -> void { + release(); + if(_mode == mode::server) return remove(); + if(_mode == mode::client) return close(); + } + + auto create(const string& name, unsigned size) -> bool { + reset(); + + _name = {"/nall-", string{name}.transform("/:", "--")}; + _size = size; + + //O_CREAT | O_EXCL seems to throw ENOENT even when semaphore does not exist ... + _semaphore = sem_open(_name, O_CREAT, 0644, 1); + if(_semaphore == SEM_FAILED) return remove(), false; + + _descriptor = shm_open(_name, O_CREAT | O_TRUNC | O_RDWR, 0644); + if(_descriptor < 0) return remove(), false; + + if(ftruncate(_descriptor, _size) != 0) return remove(), false; + + _data = (uint8_t*)mmap(nullptr, _size, PROT_READ | PROT_WRITE, MAP_SHARED, _descriptor, 0); + if(_data == MAP_FAILED) return remove(), false; + + memory::fill(_data, _size); + + _mode = mode::server; + return true; + } + + auto remove() -> void { + if(_data) { + munmap(_data, _size); + _data = nullptr; + } + + if(_descriptor) { + ::close(_descriptor); + shm_unlink(_name); + _descriptor = -1; + } + + if(_semaphore) { + sem_close(_semaphore); + sem_unlink(_name); + _semaphore = nullptr; + } + + _mode = mode::inactive; + _name = ""; + _size = 0; + } + + auto open(const string& name, unsigned size) -> bool { + reset(); + + _name = {"/nall-", string{name}.transform("/:", "--")}; + _size = size; + + _semaphore = sem_open(_name, 0, 0644); + if(_semaphore == SEM_FAILED) return close(), false; + + _descriptor = shm_open(_name, O_RDWR, 0644); + if(_descriptor < 0) return close(), false; + + _data = (uint8_t*)mmap(nullptr, _size, PROT_READ | PROT_WRITE, MAP_SHARED, _descriptor, 0); + if(_data == MAP_FAILED) return close(), false; + + _mode = mode::client; + return true; + } + + auto close() -> void { + if(_data) { + munmap(_data, _size); + _data = nullptr; + } + + if(_descriptor) { + ::close(_descriptor); + _descriptor = -1; + } + + if(_semaphore) { + sem_close(_semaphore); + _semaphore = nullptr; + } + + _mode = mode::inactive; + _name = ""; + _size = 0; + } + +private: + enum class mode : unsigned { server, client, inactive } _mode = mode::inactive; + string _name; + sem_t* _semaphore = nullptr; + signed _descriptor = -1; + uint8_t* _data = nullptr; + unsigned _size = 0; + bool _acquired = false; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/primitives.hpp b/waterbox/bsnescore/bsnes/nall/primitives.hpp new file mode 100644 index 0000000000..fd81129eba --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/primitives.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +namespace nall { + struct Boolean; + template struct Natural; + template struct Integer; + template struct Real; +} + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + template auto Natural::integer() const -> Integer { return Integer(*this); } + template auto Integer::natural() const -> Natural { return Natural(*this); } +} diff --git a/waterbox/bsnescore/bsnes/nall/primitives/bit-field.hpp b/waterbox/bsnescore/bsnes/nall/primitives/bit-field.hpp new file mode 100644 index 0000000000..0ee52fff5b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/primitives/bit-field.hpp @@ -0,0 +1,124 @@ +#pragma once + +namespace nall { + +template struct BitField; + +/* static BitField */ + +template struct BitField { + static_assert(Precision >= 1 && Precision <= 64); + enum : uint { bits = Precision }; + using type = + conditional_t>>>; + enum : uint { shift = Index < 0 ? Precision + Index : Index }; + enum : type { mask = 1ull << shift }; + + BitField(const BitField&) = delete; + + inline auto& operator=(const BitField& source) { + target = target & ~mask | (bool)source << shift; + return *this; + } + + template inline BitField(T* source) : target((type&)*source) { + static_assert(sizeof(T) == sizeof(type)); + } + + inline auto bit() const { + return shift; + } + + inline operator bool() const { + return target & mask; + } + + inline auto& operator=(bool source) { + target = target & ~mask | source << shift; + return *this; + } + + inline auto& operator&=(bool source) { + target = target & (~mask | source << shift); + return *this; + } + + inline auto& operator^=(bool source) { + target = target ^ source << shift; + return *this; + } + + inline auto& operator|=(bool source) { + target = target | source << shift; + return *this; + } + +private: + type& target; +}; + +/* dynamic BitField */ + +template struct BitField { + static_assert(Precision >= 1 && Precision <= 64); + enum : uint { bits = Precision }; + using type = + conditional_t>>>; + + BitField(const BitField&) = delete; + + inline auto& operator=(const BitField& source) { + target = target & ~mask | (bool)source << shift; + return *this; + } + + template inline BitField(T* source, int index) : target((type&)*source) { + static_assert(sizeof(T) == sizeof(type)); + if(index < 0) index = Precision + index; + mask = 1ull << index; + shift = index; + } + + inline auto bit() const { + return shift; + } + + inline operator bool() const { + return target & mask; + } + + inline auto& operator=(bool source) { + target = target & ~mask | source << shift; + return *this; + } + + inline auto& operator&=(bool source) { + target = target & (~mask | source << shift); + return *this; + } + + inline auto& operator^=(bool source) { + target = target ^ source << shift; + return *this; + } + + inline auto& operator|=(bool source) { + target = target | source << shift; + return *this; + } + +private: + type& target; + type mask; + uint shift; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/primitives/bit-range.hpp b/waterbox/bsnescore/bsnes/nall/primitives/bit-range.hpp new file mode 100644 index 0000000000..7d8112e2d7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/primitives/bit-range.hpp @@ -0,0 +1,263 @@ +#pragma once + +namespace nall { + +template struct BitRange; + +/* static BitRange */ + +template struct BitRange { + static_assert(Precision >= 1 && Precision <= 64); + enum : uint { bits = Precision }; + using type = + conditional_t>>>; + enum : uint { lo = Lo < 0 ? Precision + Lo : Lo }; + enum : uint { hi = Hi < 0 ? Precision + Hi : Hi }; + enum : type { mask = ~0ull >> 64 - (hi - lo + 1) << lo }; + enum : uint { shift = lo }; + + BitRange(const BitRange& source) = delete; + + inline auto& operator=(const BitRange& source) { + target = target & ~mask | ((source.target & source.mask) >> source.shift) << shift & mask; + return *this; + } + + template inline BitRange(T* source) : target((type&)*source) { + static_assert(sizeof(T) == sizeof(type)); + } + + inline operator type() const { + return (target & mask) >> shift; + } + + inline auto operator++(int) { + auto value = (target & mask) >> shift; + target = target & ~mask | target + (1 << shift) & mask; + return value; + } + + inline auto operator--(int) { + auto value = (target & mask) >> shift; + target = target & ~mask | target - (1 << shift) & mask; + return value; + } + + inline auto& operator++() { + target = target & ~mask | target + (1 << shift) & mask; + return *this; + } + + inline auto& operator--() { + target = target & ~mask | target - (1 << shift) & mask; + return *this; + } + + template inline auto& operator=(const T& source) { + type value = source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator*=(const T& source) { + auto value = ((target & mask) >> shift) * source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator/=(const T& source) { + auto value = ((target & mask) >> shift) / source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator%=(const T& source) { + auto value = ((target & mask) >> shift) % source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator+=(const T& source) { + auto value = ((target & mask) >> shift) + source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator-=(const T& source) { + auto value = ((target & mask) >> shift) - source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator<<=(const T& source) { + auto value = ((target & mask) >> shift) << source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator>>=(const T& source) { + auto value = ((target & mask) >> shift) >> source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator&=(const T& source) { + type value = source; + target = target & (~mask | value << shift & mask); + return *this; + } + + template inline auto& operator^=(const T& source) { + type value = source; + target = target ^ value << shift & mask; + return *this; + } + + template inline auto& operator|=(const T& source) { + type value = source; + target = target | value << shift & mask; + return *this; + } + +private: + type& target; +}; + +/* dynamic BitRange */ + +template struct BitRange { + static_assert(Precision >= 1 && Precision <= 64); + enum : uint { bits = Precision }; + using type = + conditional_t>>>; + + BitRange(const BitRange& source) = delete; + + inline auto& operator=(const BitRange& source) { + target = target & ~mask | ((source.target & source.mask) >> source.shift) << shift & mask; + return *this; + } + + template inline BitRange(T* source, int index) : target((type&)*source) { + static_assert(sizeof(T) == sizeof(type)); + if(index < 0) index = Precision + index; + mask = 1ull << index; + shift = index; + } + + template inline BitRange(T* source, int lo, int hi) : target((type&)*source) { + static_assert(sizeof(T) == sizeof(type)); + if(lo < 0) lo = Precision + lo; + if(hi < 0) hi = Precision + hi; + if(lo > hi) swap(lo, hi); + mask = ~0ull >> 64 - (hi - lo + 1) << lo; + shift = lo; + } + + inline operator type() const { + return (target & mask) >> shift; + } + + inline auto operator++(int) { + auto value = (target & mask) >> shift; + target = target & ~mask | target + (1 << shift) & mask; + return value; + } + + inline auto operator--(int) { + auto value = (target & mask) >> shift; + target = target & ~mask | target - (1 << shift) & mask; + return value; + } + + inline auto& operator++() { + target = target & ~mask | target + (1 << shift) & mask; + return *this; + } + + inline auto& operator--() { + target = target & ~mask | target - (1 << shift) & mask; + return *this; + } + + template inline auto& operator=(const T& source) { + type value = source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator*=(const T& source) { + auto value = ((target & mask) >> shift) * source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator/=(const T& source) { + auto value = ((target & mask) >> shift) / source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator%=(const T& source) { + auto value = ((target & mask) >> shift) % source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator+=(const T& source) { + auto value = ((target & mask) >> shift) + source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator-=(const T& source) { + auto value = ((target & mask) >> shift) - source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator<<=(const T& source) { + auto value = ((target & mask) >> shift) << source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator>>=(const T& source) { + auto value = ((target & mask) >> shift) >> source; + target = target & ~mask | value << shift & mask; + return *this; + } + + template inline auto& operator&=(const T& source) { + type value = source; + target = target & (~mask | value << shift & mask); + return *this; + } + + template inline auto& operator^=(const T& source) { + type value = source; + target = target ^ value << shift & mask; + return *this; + } + + template inline auto& operator|=(const T& source) { + type value = source; + target = target | value << shift & mask; + return *this; + } + +private: + type& target; + type mask; + uint shift; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/primitives/boolean.hpp b/waterbox/bsnescore/bsnes/nall/primitives/boolean.hpp new file mode 100644 index 0000000000..0426a3700c --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/primitives/boolean.hpp @@ -0,0 +1,30 @@ +#pragma once + +namespace nall { + +struct Boolean { + static inline constexpr auto bits() -> uint { return 1; } + using btype = bool; + + inline Boolean() : data(false) {} + template inline Boolean(const T& value) : data(value) {} + explicit inline Boolean(const char* value) { data = !strcmp(value, "true"); } + + inline operator bool() const { return data; } + template inline auto& operator=(const T& value) { data = value; return *this; } + + inline auto flip() { return data ^= 1; } + inline auto raise() { return data == 0 ? data = 1, true : false; } + inline auto lower() { return data == 1 ? data = 0, true : false; } + + inline auto flip(bool value) { return data != value ? (data = value, true) : false; } + inline auto raise(bool value) { return !data && value ? (data = value, true) : (data = value, false); } + inline auto lower(bool value) { return data && !value ? (data = value, true) : (data = value, false); } + + inline auto serialize(serializer& s) { s(data); } + +private: + btype data; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/primitives/integer.hpp b/waterbox/bsnescore/bsnes/nall/primitives/integer.hpp new file mode 100644 index 0000000000..92e6e89e48 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/primitives/integer.hpp @@ -0,0 +1,86 @@ +#pragma once + +namespace nall { + +template struct Integer { + static_assert(Precision >= 1 && Precision <= 64); + static inline constexpr auto bits() -> uint { return Precision; } + using stype = + conditional_t>>>; + using utype = typename Natural::utype; + static inline constexpr auto mask() -> utype { return ~0ull >> 64 - Precision; } + static inline constexpr auto sign() -> utype { return 1ull << Precision - 1; } + + inline Integer() : data(0) {} + template inline Integer(Integer value) { data = cast(value); } + template inline Integer(const T& value) { data = cast(value); } + explicit inline Integer(const char* value) { data = cast(toInteger(value)); } + + inline operator stype() const { return data; } + + inline auto operator++(int) { auto value = *this; data = cast(data + 1); return value; } + inline auto operator--(int) { auto value = *this; data = cast(data - 1); return value; } + + inline auto& operator++() { data = cast(data + 1); return *this; } + inline auto& operator--() { data = cast(data - 1); return *this; } + + template inline auto& operator =(const T& value) { data = cast( value); return *this; } + template inline auto& operator *=(const T& value) { data = cast(data * value); return *this; } + template inline auto& operator /=(const T& value) { data = cast(data / value); return *this; } + template inline auto& operator %=(const T& value) { data = cast(data % value); return *this; } + template inline auto& operator +=(const T& value) { data = cast(data + value); return *this; } + template inline auto& operator -=(const T& value) { data = cast(data - value); return *this; } + template inline auto& operator<<=(const T& value) { data = cast(data << value); return *this; } + template inline auto& operator>>=(const T& value) { data = cast(data >> value); return *this; } + template inline auto& operator &=(const T& value) { data = cast(data & value); return *this; } + template inline auto& operator ^=(const T& value) { data = cast(data ^ value); return *this; } + template inline auto& operator |=(const T& value) { data = cast(data | value); return *this; } + + inline auto bit(int index) -> BitRange { return {&data, index}; } + inline auto bit(int index) const -> const BitRange { return {&data, index}; } + + inline auto bit(int lo, int hi) -> BitRange { return {&data, lo, hi}; } + inline auto bit(int lo, int hi) const -> const BitRange { return {&data, lo, hi}; } + + inline auto byte(int index) -> BitRange { return {&data, index * 8 + 0, index * 8 + 7}; } + inline auto byte(int index) const -> const BitRange { return {&data, index * 8 + 0, index * 8 + 7}; } + + inline auto mask(int index) const -> utype { + return data & 1 << index; + } + + inline auto mask(int lo, int hi) const -> utype { + return data & (~0ull >> 64 - (hi - lo + 1) << lo); + } + + inline auto slice(int index) const { return Natural<>{bit(index)}; } + inline auto slice(int lo, int hi) const { return Natural<>{bit(lo, hi)}; } + + inline auto clamp(uint bits) -> stype { + const int64_t b = 1ull << bits - 1; + const int64_t m = b - 1; + return data > m ? m : data < -b ? -b : data; + } + + inline auto clip(uint bits) -> stype { + const uint64_t b = 1ull << bits - 1; + const uint64_t m = b * 2 - 1; + return (data & m ^ b) - b; + } + + inline auto serialize(serializer& s) { s(data); } + inline auto natural() const -> Natural; + +private: + inline auto cast(stype value) const -> stype { + return (value & mask() ^ sign()) - sign(); + } + + stype data; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/primitives/literals.hpp b/waterbox/bsnescore/bsnes/nall/primitives/literals.hpp new file mode 100644 index 0000000000..0e85233876 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/primitives/literals.hpp @@ -0,0 +1,143 @@ +#pragma once + +namespace nall { + +inline auto operator"" _b(unsigned long long value) { return boolean{value}; } +inline auto operator"" _n(unsigned long long value) { return natural{value}; } +inline auto operator"" _i(unsigned long long value) { return integer{value}; } +inline auto operator"" _r(long double value) { return real{value}; } + +inline auto operator"" _n1(unsigned long long value) { return natural1{value}; } +inline auto operator"" _n2(unsigned long long value) { return natural2{value}; } +inline auto operator"" _n3(unsigned long long value) { return natural3{value}; } +inline auto operator"" _n4(unsigned long long value) { return natural4{value}; } +inline auto operator"" _n5(unsigned long long value) { return natural5{value}; } +inline auto operator"" _n6(unsigned long long value) { return natural6{value}; } +inline auto operator"" _n7(unsigned long long value) { return natural7{value}; } +inline auto operator"" _n8(unsigned long long value) { return natural8{value}; } +inline auto operator"" _n9(unsigned long long value) { return natural9{value}; } +inline auto operator"" _n10(unsigned long long value) { return natural10{value}; } +inline auto operator"" _n11(unsigned long long value) { return natural11{value}; } +inline auto operator"" _n12(unsigned long long value) { return natural12{value}; } +inline auto operator"" _n13(unsigned long long value) { return natural13{value}; } +inline auto operator"" _n14(unsigned long long value) { return natural14{value}; } +inline auto operator"" _n15(unsigned long long value) { return natural15{value}; } +inline auto operator"" _n16(unsigned long long value) { return natural16{value}; } +inline auto operator"" _n17(unsigned long long value) { return natural17{value}; } +inline auto operator"" _n18(unsigned long long value) { return natural18{value}; } +inline auto operator"" _n19(unsigned long long value) { return natural19{value}; } +inline auto operator"" _n20(unsigned long long value) { return natural20{value}; } +inline auto operator"" _n21(unsigned long long value) { return natural21{value}; } +inline auto operator"" _n22(unsigned long long value) { return natural22{value}; } +inline auto operator"" _n23(unsigned long long value) { return natural23{value}; } +inline auto operator"" _n24(unsigned long long value) { return natural24{value}; } +inline auto operator"" _n25(unsigned long long value) { return natural25{value}; } +inline auto operator"" _n26(unsigned long long value) { return natural26{value}; } +inline auto operator"" _n27(unsigned long long value) { return natural27{value}; } +inline auto operator"" _n28(unsigned long long value) { return natural28{value}; } +inline auto operator"" _n29(unsigned long long value) { return natural29{value}; } +inline auto operator"" _n30(unsigned long long value) { return natural30{value}; } +inline auto operator"" _n31(unsigned long long value) { return natural31{value}; } +inline auto operator"" _n32(unsigned long long value) { return natural32{value}; } +inline auto operator"" _n33(unsigned long long value) { return natural33{value}; } +inline auto operator"" _n34(unsigned long long value) { return natural34{value}; } +inline auto operator"" _n35(unsigned long long value) { return natural35{value}; } +inline auto operator"" _n36(unsigned long long value) { return natural36{value}; } +inline auto operator"" _n37(unsigned long long value) { return natural37{value}; } +inline auto operator"" _n38(unsigned long long value) { return natural38{value}; } +inline auto operator"" _n39(unsigned long long value) { return natural39{value}; } +inline auto operator"" _n40(unsigned long long value) { return natural40{value}; } +inline auto operator"" _n41(unsigned long long value) { return natural41{value}; } +inline auto operator"" _n42(unsigned long long value) { return natural42{value}; } +inline auto operator"" _n43(unsigned long long value) { return natural43{value}; } +inline auto operator"" _n44(unsigned long long value) { return natural44{value}; } +inline auto operator"" _n45(unsigned long long value) { return natural45{value}; } +inline auto operator"" _n46(unsigned long long value) { return natural46{value}; } +inline auto operator"" _n47(unsigned long long value) { return natural47{value}; } +inline auto operator"" _n48(unsigned long long value) { return natural48{value}; } +inline auto operator"" _n49(unsigned long long value) { return natural49{value}; } +inline auto operator"" _n50(unsigned long long value) { return natural50{value}; } +inline auto operator"" _n51(unsigned long long value) { return natural51{value}; } +inline auto operator"" _n52(unsigned long long value) { return natural52{value}; } +inline auto operator"" _n53(unsigned long long value) { return natural53{value}; } +inline auto operator"" _n54(unsigned long long value) { return natural54{value}; } +inline auto operator"" _n55(unsigned long long value) { return natural55{value}; } +inline auto operator"" _n56(unsigned long long value) { return natural56{value}; } +inline auto operator"" _n57(unsigned long long value) { return natural57{value}; } +inline auto operator"" _n58(unsigned long long value) { return natural58{value}; } +inline auto operator"" _n59(unsigned long long value) { return natural59{value}; } +inline auto operator"" _n60(unsigned long long value) { return natural60{value}; } +inline auto operator"" _n61(unsigned long long value) { return natural61{value}; } +inline auto operator"" _n62(unsigned long long value) { return natural62{value}; } +inline auto operator"" _n63(unsigned long long value) { return natural63{value}; } +inline auto operator"" _n64(unsigned long long value) { return natural64{value}; } + +inline auto operator"" _i1(unsigned long long value) { return integer1{value}; } +inline auto operator"" _i2(unsigned long long value) { return integer2{value}; } +inline auto operator"" _i3(unsigned long long value) { return integer3{value}; } +inline auto operator"" _i4(unsigned long long value) { return integer4{value}; } +inline auto operator"" _i5(unsigned long long value) { return integer5{value}; } +inline auto operator"" _i6(unsigned long long value) { return integer6{value}; } +inline auto operator"" _i7(unsigned long long value) { return integer7{value}; } +inline auto operator"" _i8(unsigned long long value) { return integer8{value}; } +inline auto operator"" _i9(unsigned long long value) { return integer9{value}; } +inline auto operator"" _i10(unsigned long long value) { return integer10{value}; } +inline auto operator"" _i11(unsigned long long value) { return integer11{value}; } +inline auto operator"" _i12(unsigned long long value) { return integer12{value}; } +inline auto operator"" _i13(unsigned long long value) { return integer13{value}; } +inline auto operator"" _i14(unsigned long long value) { return integer14{value}; } +inline auto operator"" _i15(unsigned long long value) { return integer15{value}; } +inline auto operator"" _i16(unsigned long long value) { return integer16{value}; } +inline auto operator"" _i17(unsigned long long value) { return integer17{value}; } +inline auto operator"" _i18(unsigned long long value) { return integer18{value}; } +inline auto operator"" _i19(unsigned long long value) { return integer19{value}; } +inline auto operator"" _i20(unsigned long long value) { return integer20{value}; } +inline auto operator"" _i21(unsigned long long value) { return integer21{value}; } +inline auto operator"" _i22(unsigned long long value) { return integer22{value}; } +inline auto operator"" _i23(unsigned long long value) { return integer23{value}; } +inline auto operator"" _i24(unsigned long long value) { return integer24{value}; } +inline auto operator"" _i25(unsigned long long value) { return integer25{value}; } +inline auto operator"" _i26(unsigned long long value) { return integer26{value}; } +inline auto operator"" _i27(unsigned long long value) { return integer27{value}; } +inline auto operator"" _i28(unsigned long long value) { return integer28{value}; } +inline auto operator"" _i29(unsigned long long value) { return integer29{value}; } +inline auto operator"" _i30(unsigned long long value) { return integer30{value}; } +inline auto operator"" _i31(unsigned long long value) { return integer31{value}; } +inline auto operator"" _i32(unsigned long long value) { return integer32{value}; } +inline auto operator"" _i33(unsigned long long value) { return integer33{value}; } +inline auto operator"" _i34(unsigned long long value) { return integer34{value}; } +inline auto operator"" _i35(unsigned long long value) { return integer35{value}; } +inline auto operator"" _i36(unsigned long long value) { return integer36{value}; } +inline auto operator"" _i37(unsigned long long value) { return integer37{value}; } +inline auto operator"" _i38(unsigned long long value) { return integer38{value}; } +inline auto operator"" _i39(unsigned long long value) { return integer39{value}; } +inline auto operator"" _i40(unsigned long long value) { return integer40{value}; } +inline auto operator"" _i41(unsigned long long value) { return integer41{value}; } +inline auto operator"" _i42(unsigned long long value) { return integer42{value}; } +inline auto operator"" _i43(unsigned long long value) { return integer43{value}; } +inline auto operator"" _i44(unsigned long long value) { return integer44{value}; } +inline auto operator"" _i45(unsigned long long value) { return integer45{value}; } +inline auto operator"" _i46(unsigned long long value) { return integer46{value}; } +inline auto operator"" _i47(unsigned long long value) { return integer47{value}; } +inline auto operator"" _i48(unsigned long long value) { return integer48{value}; } +inline auto operator"" _i49(unsigned long long value) { return integer49{value}; } +inline auto operator"" _i50(unsigned long long value) { return integer50{value}; } +inline auto operator"" _i51(unsigned long long value) { return integer51{value}; } +inline auto operator"" _i52(unsigned long long value) { return integer52{value}; } +inline auto operator"" _i53(unsigned long long value) { return integer53{value}; } +inline auto operator"" _i54(unsigned long long value) { return integer54{value}; } +inline auto operator"" _i55(unsigned long long value) { return integer55{value}; } +inline auto operator"" _i56(unsigned long long value) { return integer56{value}; } +inline auto operator"" _i57(unsigned long long value) { return integer57{value}; } +inline auto operator"" _i58(unsigned long long value) { return integer58{value}; } +inline auto operator"" _i59(unsigned long long value) { return integer59{value}; } +inline auto operator"" _i60(unsigned long long value) { return integer60{value}; } +inline auto operator"" _i61(unsigned long long value) { return integer61{value}; } +inline auto operator"" _i62(unsigned long long value) { return integer62{value}; } +inline auto operator"" _i63(unsigned long long value) { return integer63{value}; } +inline auto operator"" _i64(unsigned long long value) { return integer64{value}; } + +inline auto operator"" _r32(long double value) { return real32{value}; } +inline auto operator"" _r64(long double value) { return real32{value}; } + +} diff --git a/waterbox/bsnescore/bsnes/nall/primitives/natural.hpp b/waterbox/bsnescore/bsnes/nall/primitives/natural.hpp new file mode 100644 index 0000000000..7bf3237d38 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/primitives/natural.hpp @@ -0,0 +1,84 @@ +#pragma once + +namespace nall { + +template struct Natural { + static_assert(Precision >= 1 && Precision <= 64); + static inline constexpr auto bits() -> uint { return Precision; } + using utype = + conditional_t>>>; + static inline constexpr auto mask() -> utype { return ~0ull >> 64 - Precision; } + + inline Natural() : data(0) {} + template inline Natural(Natural value) { data = cast(value); } + template inline Natural(const T& value) { data = cast(value); } + explicit inline Natural(const char* value) { data = cast(toNatural(value)); } + + inline operator utype() const { return data; } + + inline auto operator++(int) { auto value = *this; data = cast(data + 1); return value; } + inline auto operator--(int) { auto value = *this; data = cast(data - 1); return value; } + + inline auto& operator++() { data = cast(data + 1); return *this; } + inline auto& operator--() { data = cast(data - 1); return *this; } + + template inline auto& operator =(const T& value) { data = cast( value); return *this; } + template inline auto& operator *=(const T& value) { data = cast(data * value); return *this; } + template inline auto& operator /=(const T& value) { data = cast(data / value); return *this; } + template inline auto& operator %=(const T& value) { data = cast(data % value); return *this; } + template inline auto& operator +=(const T& value) { data = cast(data + value); return *this; } + template inline auto& operator -=(const T& value) { data = cast(data - value); return *this; } + template inline auto& operator<<=(const T& value) { data = cast(data << value); return *this; } + template inline auto& operator>>=(const T& value) { data = cast(data >> value); return *this; } + template inline auto& operator &=(const T& value) { data = cast(data & value); return *this; } + template inline auto& operator ^=(const T& value) { data = cast(data ^ value); return *this; } + template inline auto& operator |=(const T& value) { data = cast(data | value); return *this; } + + inline auto bit(int index) -> BitRange { return {&data, index}; } + inline auto bit(int index) const -> const BitRange { return {&data, index}; } + + inline auto bit(int lo, int hi) -> BitRange { return {&data, lo, hi}; } + inline auto bit(int lo, int hi) const -> const BitRange { return {&data, lo, hi}; } + + inline auto byte(int index) -> BitRange { return {&data, index * 8 + 0, index * 8 + 7}; } + inline auto byte(int index) const -> const BitRange { return {&data, index * 8 + 0, index * 8 + 7}; } + + inline auto mask(int index) const -> utype { + return data & 1 << index; + } + + inline auto mask(int lo, int hi) const -> utype { + return data & (~0ull >> 64 - (hi - lo + 1) << lo); + } + + inline auto slice(int index) const { return Natural<>{bit(index)}; } + inline auto slice(int lo, int hi) const { return Natural<>{bit(lo, hi)}; } + + inline auto clamp(uint bits) -> utype { + const uint64_t b = 1ull << bits - 1; + const uint64_t m = b * 2 - 1; + return data < m ? data : m; + } + + inline auto clip(uint bits) -> utype { + const uint64_t b = 1ull << bits - 1; + const uint64_t m = b * 2 - 1; + return data & m; + } + + inline auto serialize(serializer& s) { s(data); } + inline auto integer() const -> Integer; + +private: + inline auto cast(utype value) const -> utype { + return value & mask(); + } + + utype data; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/primitives/real.hpp b/waterbox/bsnescore/bsnes/nall/primitives/real.hpp new file mode 100644 index 0000000000..0145363aa2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/primitives/real.hpp @@ -0,0 +1,39 @@ +#pragma once + +namespace nall { + +template struct Real { + static_assert(Precision == 32 || Precision == 64); + static inline constexpr auto bits() -> uint { return Precision; } + using ftype = + conditional_t>; + + inline Real() : data(0.0) {} + template inline Real(Real value) : data((ftype)value) {} + template inline Real(const T& value) : data((ftype)value) {} + explicit inline Real(const char* value) : data((ftype)toReal(value)) {} + + inline operator ftype() const { return data; } + + inline auto operator++(int) { auto value = *this; ++data; return value; } + inline auto operator--(int) { auto value = *this; --data; return value; } + + inline auto& operator++() { data++; return *this; } + inline auto& operator--() { data--; return *this; } + + template inline auto& operator =(const T& value) { data = value; return *this; } + template inline auto& operator*=(const T& value) { data = data * value; return *this; } + template inline auto& operator/=(const T& value) { data = data / value; return *this; } + template inline auto& operator%=(const T& value) { data = data % value; return *this; } + template inline auto& operator+=(const T& value) { data = data + value; return *this; } + template inline auto& operator-=(const T& value) { data = data - value; return *this; } + + inline auto serialize(serializer& s) { s(data); } + +private: + ftype data; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/primitives/types.hpp b/waterbox/bsnescore/bsnes/nall/primitives/types.hpp new file mode 100644 index 0000000000..74ca6a6ab3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/primitives/types.hpp @@ -0,0 +1,45 @@ +#pragma once + +namespace nall { + using boolean = Boolean; + using natural = Natural<>; + using integer = Integer<>; + using real = Real<>; + + using natural1 = Natural< 1>; using natural2 = Natural< 2>; using natural3 = Natural< 3>; using natural4 = Natural< 4>; + using natural5 = Natural< 5>; using natural6 = Natural< 6>; using natural7 = Natural< 7>; using natural8 = Natural< 8>; + using natural9 = Natural< 9>; using natural10 = Natural<10>; using natural11 = Natural<11>; using natural12 = Natural<12>; + using natural13 = Natural<13>; using natural14 = Natural<14>; using natural15 = Natural<15>; using natural16 = Natural<16>; + using natural17 = Natural<17>; using natural18 = Natural<18>; using natural19 = Natural<19>; using natural20 = Natural<20>; + using natural21 = Natural<21>; using natural22 = Natural<22>; using natural23 = Natural<23>; using natural24 = Natural<24>; + using natural25 = Natural<25>; using natural26 = Natural<26>; using natural27 = Natural<27>; using natural28 = Natural<28>; + using natural29 = Natural<29>; using natural30 = Natural<30>; using natural31 = Natural<31>; using natural32 = Natural<32>; + using natural33 = Natural<33>; using natural34 = Natural<34>; using natural35 = Natural<35>; using natural36 = Natural<36>; + using natural37 = Natural<37>; using natural38 = Natural<38>; using natural39 = Natural<39>; using natural40 = Natural<40>; + using natural41 = Natural<41>; using natural42 = Natural<42>; using natural43 = Natural<43>; using natural44 = Natural<44>; + using natural45 = Natural<45>; using natural46 = Natural<46>; using natural47 = Natural<47>; using natural48 = Natural<48>; + using natural49 = Natural<49>; using natural50 = Natural<50>; using natural51 = Natural<51>; using natural52 = Natural<52>; + using natural53 = Natural<53>; using natural54 = Natural<54>; using natural55 = Natural<55>; using natural56 = Natural<56>; + using natural57 = Natural<57>; using natural58 = Natural<58>; using natural59 = Natural<59>; using natural60 = Natural<60>; + using natural61 = Natural<61>; using natural62 = Natural<62>; using natural63 = Natural<63>; using natural64 = Natural<64>; + + using integer1 = Integer< 1>; using integer2 = Integer< 2>; using integer3 = Integer< 3>; using integer4 = Integer< 4>; + using integer5 = Integer< 5>; using integer6 = Integer< 6>; using integer7 = Integer< 7>; using integer8 = Integer< 8>; + using integer9 = Integer< 9>; using integer10 = Integer<10>; using integer11 = Integer<11>; using integer12 = Integer<12>; + using integer13 = Integer<13>; using integer14 = Integer<14>; using integer15 = Integer<15>; using integer16 = Integer<16>; + using integer17 = Integer<17>; using integer18 = Integer<18>; using integer19 = Integer<19>; using integer20 = Integer<20>; + using integer21 = Integer<21>; using integer22 = Integer<22>; using integer23 = Integer<23>; using integer24 = Integer<24>; + using integer25 = Integer<25>; using integer26 = Integer<26>; using integer27 = Integer<27>; using integer28 = Integer<28>; + using integer29 = Integer<29>; using integer30 = Integer<30>; using integer31 = Integer<31>; using integer32 = Integer<32>; + using integer33 = Integer<33>; using integer34 = Integer<34>; using integer35 = Integer<35>; using integer36 = Integer<36>; + using integer37 = Integer<37>; using integer38 = Integer<38>; using integer39 = Integer<39>; using integer40 = Integer<40>; + using integer41 = Integer<41>; using integer42 = Integer<42>; using integer43 = Integer<43>; using integer44 = Integer<44>; + using integer45 = Integer<45>; using integer46 = Integer<46>; using integer47 = Integer<47>; using integer48 = Integer<48>; + using integer49 = Integer<49>; using integer50 = Integer<50>; using integer51 = Integer<51>; using integer52 = Integer<52>; + using integer53 = Integer<53>; using integer54 = Integer<54>; using integer55 = Integer<55>; using integer56 = Integer<56>; + using integer57 = Integer<57>; using integer58 = Integer<58>; using integer59 = Integer<59>; using integer60 = Integer<60>; + using integer61 = Integer<61>; using integer62 = Integer<62>; using integer63 = Integer<63>; using integer64 = Integer<64>; + + using real32 = Real<32>; + using real64 = Real<64>; +} diff --git a/waterbox/bsnescore/bsnes/nall/property.hpp b/waterbox/bsnescore/bsnes/nall/property.hpp new file mode 100644 index 0000000000..00dd7c7f17 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/property.hpp @@ -0,0 +1,13 @@ +#if !defined(property) + #define property1(declaration) public: declaration + #define property2(declaration, getter) public: __declspec(property(get=getter)) declaration; protected: declaration##_ + #define property3(declaration, getter, setter) public: __declspec(property(get=getter, put=setter)) declaration; protected: declaration##_ + #define property_(_1, _2, _3, name, ...) name + #define property(...) property_(__VA_ARGS__, property3, property2, property1)(__VA_ARGS__) +#else + #undef property1 + #undef property2 + #undef property3 + #undef property_ + #undef property +#endif diff --git a/waterbox/bsnescore/bsnes/nall/queue.hpp b/waterbox/bsnescore/bsnes/nall/queue.hpp new file mode 100644 index 0000000000..062eda77b5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/queue.hpp @@ -0,0 +1,113 @@ +#pragma once + +//simple circular ring buffer + +#include +#include + +namespace nall { + +template +struct queue { + queue() = default; + queue(const queue& source) { operator=(source); } + queue(queue&& source) { operator=(move(source)); } + ~queue() { reset(); } + + auto operator=(const queue& source) -> queue& { + if(this == &source) return *this; + delete[] _data; + _data = new T[source._capacity]; + _capacity = source._capacity; + _size = source._size; + _read = source._read; + _write = source._write; + for(uint n : range(_capacity)) _data[n] = source._data[n]; + return *this; + } + + auto operator=(queue&& source) -> queue& { + if(this == &source) return *this; + _data = source._data; + _capacity = source._capacity; + _size = source._size; + _read = source._read; + _write = source._write; + source._data = nullptr; + source.reset(); + return *this; + } + + template auto capacity() const -> uint { return _capacity * sizeof(T) / sizeof(U); } + template auto size() const -> uint { return _size * sizeof(T) / sizeof(U); } + auto empty() const -> bool { return _size == 0; } + auto pending() const -> bool { return _size > 0; } + auto full() const -> bool { return _size >= (int)_capacity; } + auto underflow() const -> bool { return _size < 0; } + auto overflow() const -> bool { return _size > (int)_capacity; } + + auto data() -> T* { return _data; } + auto data() const -> const T* { return _data; } + + auto reset() { + delete[] _data; + _data = nullptr; + _capacity = 0; + _size = 0; + _read = 0; + _write = 0; + } + + auto resize(uint capacity, const T& value = {}) -> void { + delete[] _data; + _data = new T[capacity]; + _capacity = capacity; + _size = 0; + _read = 0; + _write = 0; + for(uint n : range(_capacity)) _data[n] = value; + } + + auto flush() -> void { + _size = 0; + _read = 0; + _write = 0; + } + + auto fill(const T& value = {}) -> void { + _size = 0; + _read = 0; + _write = 0; + for(uint n : range(_capacity)) _data[n] = value; + } + + auto read() -> T { + T value = _data[_read++]; + if(_read >= _capacity) _read = 0; + _size--; + return value; + } + + auto write(const T& value) -> void { + _data[_write++] = value; + if(_write >= _capacity) _write = 0; + _size++; + } + + auto serialize(serializer& s) -> void { + s.array(_data, _capacity); + s.integer(_capacity); + s.integer(_size); + s.integer(_read); + s.integer(_write); + } + +private: + T* _data = nullptr; + uint _capacity = 0; + int _size = 0; + uint _read = 0; + uint _write = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/random.hpp b/waterbox/bsnescore/bsnes/nall/random.hpp new file mode 100644 index 0000000000..5b7b3c2c1e --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/random.hpp @@ -0,0 +1,168 @@ +#pragma once + +#include +#include +#include +#include +#include +#if !defined(PLATFORM_ANDROID) +#include +#endif + +#if defined(PLATFORM_LINUX) && __has_include() + #include +#elif defined(PLATFORM_ANDROID) && __has_include() + #include +#elif defined(PLATFORM_WINDOWS) && __has_include() + #include +#else + #include +#endif + +namespace nall { + +template struct RNG { + template auto random() -> T { + T value = 0; + for(uint n : range((sizeof(T) + 3) / 4)) { + value = value << 32 | (uint32_t)static_cast(this)->read(); + } + return value; + } + + template auto bound(T range) -> T { + T threshold = -range % range; + while(true) { + T value = random(); + if(value >= threshold) return value % range; + } + } + +protected: + auto randomSeed() -> uint256_t { + uint256_t seed = 0; + #if defined(PLATFORM_BSD) || defined(PLATFORM_MACOS) + for(uint n : range(8)) seed = seed << 32 | (uint32_t)arc4random(); + #elif defined(PLATFORM_LINUX) && __has_include() + getrandom(&seed, 32, GRND_NONBLOCK); + #elif defined(PLATFORM_ANDROID) && __has_include() + syscall(__NR_getrandom, &seed, 32, 0x0001); //GRND_NONBLOCK + #elif defined(PLATFORM_WINDOWS) && __has_include() + HCRYPTPROV provider; + if(CryptAcquireContext(&provider, nullptr, MS_STRONG_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + CryptGenRandom(provider, 32, (BYTE*)&seed); + CryptReleaseContext(provider, 0); + } + #else + srand(time(nullptr)); + for(uint n : range(32)) seed = seed << 8 | (uint8_t)rand(); + if(auto fp = fopen("/dev/urandom", "rb")) { + fread(&seed, 32, 1, fp); + fclose(fp); + } + #endif + return seed; + } +}; + +namespace PRNG { + +//Galois linear feedback shift register using CRC64 polynomials +struct LFSR : RNG { + LFSR() { seed(); } + + auto seed(maybe seed = {}) -> void { + lfsr = seed ? seed() : (uint64_t)randomSeed(); + for(uint n : range(8)) read(); //hide the CRC64 polynomial from initial output + } + + auto serialize(serializer& s) -> void { + s.integer(lfsr); + } + +private: + auto read() -> uint64_t { + return lfsr = (lfsr >> 1) ^ (-(lfsr & 1) & crc64); + } + + static const uint64_t crc64 = 0xc96c'5795'd787'0f42; + uint64_t lfsr = crc64; + + friend class RNG; +}; + +struct PCG : RNG { + PCG() { seed(); } + + auto seed(maybe seed = {}, maybe sequence = {}) -> void { + if(!seed) seed = (uint32_t)randomSeed(); + if(!sequence) sequence = 0; + + state = 0; + increment = sequence() << 1 | 1; + read(); + state += seed(); + read(); + } + + auto serialize(serializer& s) -> void { + s.integer(state); + s.integer(increment); + } + +private: + auto read() -> uint32_t { + uint64_t state = this->state; + this->state = state * 6'364'136'223'846'793'005ull + increment; + uint32_t xorshift = (state >> 18 ^ state) >> 27; + uint32_t rotate = state >> 59; + return xorshift >> rotate | xorshift << (-rotate & 31); + } + + uint64_t state = 0; + uint64_t increment = 0; + + friend class RNG; +}; + +} + +#if !defined(PLATFORM_ANDROID) +namespace CSPRNG { + +//XChaCha20 cryptographically secure pseudo-random number generator +struct XChaCha20 : RNG { + XChaCha20() { seed(); } + + auto seed(maybe key = {}, maybe nonce = {}) -> void { + //the randomness comes from the key; the nonce just adds a bit of added entropy + if(!key) key = randomSeed(); + if(!nonce) nonce = (uint192_t)clock() << 64 | chrono::nanosecond(); + context = {key(), nonce()}; + } + +private: + auto read() -> uint32_t { + if(!counter) { context.cipher(); context.increment(); } + uint32_t value = context.block[counter++]; + if(counter == 16) counter = 0; //64-bytes per block; 4 bytes per read + return value; + } + + Cipher::XChaCha20 context{0, 0}; + uint counter = 0; + + friend class RNG; +}; + +} +#endif + +// + +template inline auto random() -> T { + static PRNG::PCG pcg; //note: unseeded + return pcg.random(); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/range.hpp b/waterbox/bsnescore/bsnes/nall/range.hpp new file mode 100644 index 0000000000..891f5ecad1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/range.hpp @@ -0,0 +1,51 @@ +#pragma once + +namespace nall { + +struct range_t { + struct iterator { + iterator(int64_t position, int64_t step = 0) : position(position), step(step) {} + auto operator*() const -> int64_t { return position; } + auto operator!=(const iterator& source) const -> bool { return step > 0 ? position < source.position : position > source.position; } + auto operator++() -> iterator& { position += step; return *this; } + + private: + int64_t position; + const int64_t step; + }; + + struct reverse_iterator { + reverse_iterator(int64_t position, int64_t step = 0) : position(position), step(step) {} + auto operator*() const -> int64_t { return position; } + auto operator!=(const reverse_iterator& source) const -> bool { return step > 0 ? position > source.position : position < source.position; } + auto operator++() -> reverse_iterator& { position -= step; return *this; } + + private: + int64_t position; + const int64_t step; + }; + + auto begin() const -> iterator { return {origin, stride}; } + auto end() const -> iterator { return {target}; } + + auto rbegin() const -> reverse_iterator { return {target - stride, stride}; } + auto rend() const -> reverse_iterator { return {origin - stride}; } + + int64_t origin; + int64_t target; + int64_t stride; +}; + +inline auto range(int64_t size) { + return range_t{0, size, 1}; +} + +inline auto range(int64_t offset, int64_t size) { + return range_t{offset, size, 1}; +} + +inline auto range(int64_t offset, int64_t size, int64_t step) { + return range_t{offset, size, step}; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/reed-solomon.hpp b/waterbox/bsnescore/bsnes/nall/reed-solomon.hpp new file mode 100644 index 0000000000..d04bb66123 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/reed-solomon.hpp @@ -0,0 +1,218 @@ +#pragma once + +namespace nall { + +//RS(n,k) = ReedSolomon +template +struct ReedSolomon { + enum : uint { Parity = Length - Inputs }; + static_assert(Length <= 255 && Length > 0); + static_assert(Parity <= 32 && Parity > 0); + + using Field = GaloisField; + template using Polynomial = Matrix; + + template + static auto shift(Polynomial polynomial) -> Polynomial { + for(int n = Size - 1; n > 0; n--) polynomial[n] = polynomial[n - 1]; + polynomial[0] = 0; + return polynomial; + } + + template + static auto degree(const Polynomial& polynomial) -> uint { + for(int n = Size; n > 0; n--) { + if(polynomial[n - 1] != 0) return n - 1; + } + return 0; + } + + template + static auto evaluate(const Polynomial& polynomial, Field field) -> Field { + Field sum = 0; + for(uint n : range(Size)) sum += polynomial[n] * field.pow(n); + return sum; + } + + Polynomial message; + Polynomial syndromes; + Polynomial locators; + + ReedSolomon() = default; + ReedSolomon(const ReedSolomon&) = default; + + ReedSolomon(const initializer_list& source) { + uint index = 0; + for(auto& value : source) { + if(index >= Length) break; + message[index++] = value; + } + } + + auto operator[](uint index) -> Field& { return message[index]; } + auto operator[](uint index) const -> Field { return message[index]; } + + auto calculateSyndromes() -> void { + static const Polynomial bases = [] { + Polynomial bases; + for(uint n : range(Parity)) { + bases[n] = Field::exp(n); + } + return bases; + }(); + + syndromes = {}; + for(uint m : range(Length)) { + for(uint p : range(Parity)) { + syndromes[p] *= bases[p]; + syndromes[p] += message[m]; + } + } + } + + auto generateParity() -> void { + static const Polynomial matrix = [] { + Polynomial matrix{}; + for(uint row : range(Parity)) { + for(uint col : range(Parity)) { + matrix(row, col) = Field::exp(row * col); + } + } + if(auto result = matrix.invert()) return *result; + throw; //should never occur + }(); + + for(uint p : range(Parity)) message[Inputs + p] = 0; + calculateSyndromes(); + auto parity = matrix * syndromes; + for(uint p : range(Parity)) message[Inputs + p] = parity[Parity - (p + 1)]; + } + + auto syndromesAreZero() -> bool { + for(uint p : range(Parity)) { + if(syndromes[p]) return false; + } + return true; + } + + //algorithm: Berlekamp-Massey + auto calculateLocators() -> void { + Polynomial history{1}; + locators = history; + uint errors = 0; + + for(uint n : range(Parity)) { + Field discrepancy = 0; + for(uint l : range(errors + 1)) { + discrepancy += locators[l] * syndromes[n - l]; + } + + history = shift(history); + if(discrepancy) { + auto located = locators - history * discrepancy; + if(errors * 2 <= n) { + errors = (n + 1) - errors; + history = locators * discrepancy.inv(); + } + locators = located; + } + } + } + + //algorithm: brute force + //todo: implement Chien search here + auto calculateErrors() -> vector { + calculateSyndromes(); + if(syndromesAreZero()) return {}; //no errors detected + calculateLocators(); + vector errors; + for(uint n : range(Length)) { + if(evaluate(locators, Field{2}.pow(255 - n))) continue; + errors.append(Length - (n + 1)); + } + return errors; + } + + template + static auto calculateErasures(array_view errors) -> maybe> { + Polynomial matrix{}; + for(uint row : range(Size)) { + for(uint col : range(Size)) { + uint index = Length - (errors[col] + 1); + matrix(row, col) = Field::exp(row * index); + } + } + return matrix.invert(); + } + + template + auto correctErasures(array_view errors) -> int { + calculateSyndromes(); + if(syndromesAreZero()) return 0; //no errors detected + if(auto matrix = calculateErasures(errors)) { + Polynomial factors; + for(uint n : range(Size)) factors[n] = syndromes[n]; + auto errata = matrix() * factors; + for(uint m : range(Size)) { + message[errors[m]] += errata[m]; + } + calculateSyndromes(); + if(syndromesAreZero()) return Size; //corrected Size errors + return -Size; //failed to correct Size errors + } + return -Size; //should never occur, but might ... + } + + //note: the erasure matrix is generated as a Polynomial, where N is the number of errors to correct. + //because this is a template parameter, and the actual number of errors may very, this function is needed. + //the alternative would be to convert Matrix to a dynamically sized Matrix(Rows, Cols) type, + //but this would require heap memory allocations and would be a massive performance penalty. + auto correctErrata(array_view errors) -> int { + if(errors.size() >= Parity) return -errors.size(); //too many errors to be correctable + + switch(errors.size()) { + case 0: return 0; + case 1: return correctErasures< 1>(errors); + case 2: return correctErasures< 2>(errors); + case 3: return correctErasures< 3>(errors); + case 4: return correctErasures< 4>(errors); + case 5: return correctErasures< 5>(errors); + case 6: return correctErasures< 6>(errors); + case 7: return correctErasures< 7>(errors); + case 8: return correctErasures< 8>(errors); + case 9: return correctErasures< 9>(errors); + case 10: return correctErasures<10>(errors); + case 11: return correctErasures<11>(errors); + case 12: return correctErasures<12>(errors); + case 13: return correctErasures<13>(errors); + case 14: return correctErasures<14>(errors); + case 15: return correctErasures<15>(errors); + case 16: return correctErasures<16>(errors); + case 17: return correctErasures<17>(errors); + case 18: return correctErasures<18>(errors); + case 19: return correctErasures<19>(errors); + case 20: return correctErasures<20>(errors); + case 21: return correctErasures<21>(errors); + case 22: return correctErasures<22>(errors); + case 23: return correctErasures<23>(errors); + case 24: return correctErasures<24>(errors); + case 25: return correctErasures<25>(errors); + case 26: return correctErasures<26>(errors); + case 27: return correctErasures<27>(errors); + case 28: return correctErasures<28>(errors); + case 29: return correctErasures<29>(errors); + case 30: return correctErasures<30>(errors); + case 31: return correctErasures<31>(errors); + case 32: return correctErasures<32>(errors); + } + return -errors.size(); //it's possible to correct more errors if the above switch were extended ... + } + + //convenience function for when erasures aren't needed + auto correctErrors() -> int { + auto errors = calculateErrors(); + return correctErrata(errors); + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/run.hpp b/waterbox/bsnescore/bsnes/nall/run.hpp new file mode 100644 index 0000000000..14f92ec490 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/run.hpp @@ -0,0 +1,214 @@ +#pragma once + +//auto execute(const string& name, const string& args...) -> string; +//[[synchronous]] +//executes program, waits for completion, and returns data written to stdout + +//auto invoke(const string& name, const string& args...) -> void; +//[[asynchronous]] +//if a program is specified, it is executed with the arguments provided +//if a file is specified, the file is opened using the program associated with said file type +//if a folder is specified, the folder is opened using the associated file explorer +//if a URL is specified, the default web browser is opened and pointed at the URL requested + +#include +#include + +namespace nall { + +struct execute_result_t { + explicit operator bool() const { return code == EXIT_SUCCESS; } + + int code = EXIT_FAILURE; + string output; + string error; +}; + +#if defined(PLATFORM_MACOS) || defined(PLATFORM_LINUX) || defined(PLATFORM_BSD) + +template inline auto execute(const string& name, P&&... p) -> execute_result_t { + int fdout[2]; + int fderr[2]; + if(pipe(fdout) == -1) return {}; + if(pipe(fderr) == -1) return {}; + + pid_t pid = fork(); + if(pid == 0) { + const char* argv[1 + sizeof...(p) + 1]; + const char** argp = argv; + vector argl(forward

(p)...); + *argp++ = (const char*)name; + for(auto& arg : argl) *argp++ = (const char*)arg; + *argp++ = nullptr; + + dup2(fdout[1], STDOUT_FILENO); + dup2(fderr[1], STDERR_FILENO); + close(fdout[0]); + close(fderr[0]); + close(fdout[1]); + close(fderr[1]); + execvp(name, (char* const*)argv); + //this is called only if execvp fails: + //use _exit instead of exit, to avoid destroying key shared file descriptors + _exit(EXIT_FAILURE); + } else { + close(fdout[1]); + close(fderr[1]); + + char buffer[256]; + execute_result_t result; + + while(true) { + auto size = read(fdout[0], buffer, sizeof(buffer)); + if(size <= 0) break; + + auto offset = result.output.size(); + result.output.resize(offset + size); + memory::copy(result.output.get() + offset, buffer, size); + } + + while(true) { + auto size = read(fderr[0], buffer, sizeof(buffer)); + if(size <= 0) break; + + auto offset = result.error.size(); + result.error.resize(offset + size); + memory::copy(result.error.get() + offset, buffer, size); + } + + close(fdout[0]); + close(fderr[0]); + + int status = 0; + waitpid(pid, &status, 0); + if(!WIFEXITED(status)) return {}; + result.code = WEXITSTATUS(status); + return result; + } +} + +template inline auto invoke(const string& name, P&&... p) -> void { + pid_t pid = fork(); + if(pid == 0) { + const char* argv[1 + sizeof...(p) + 1]; + const char** argp = argv; + vector argl(forward

(p)...); + *argp++ = (const char*)name; + for(auto& arg : argl) *argp++ = (const char*)arg; + *argp++ = nullptr; + + if(execvp(name, (char* const*)argv) < 0) { + #if defined(PLATFORM_MACOS) + execlp("open", "open", (const char*)name, nullptr); + #else + execlp("xdg-open", "xdg-open", (const char*)name, nullptr); + #endif + } + exit(0); + } +} + +#elif defined(PLATFORM_WINDOWS) + +template inline auto execute(const string& name, P&&... p) -> execute_result_t { + vector argl(name, forward

(p)...); + for(auto& arg : argl) if(arg.find(" ")) arg = {"\"", arg, "\""}; + string arguments = argl.merge(" "); + + SECURITY_ATTRIBUTES sa; + ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = nullptr; + + HANDLE stdoutRead; + HANDLE stdoutWrite; + if(!CreatePipe(&stdoutRead, &stdoutWrite, &sa, 0)) return {}; + if(!SetHandleInformation(stdoutRead, HANDLE_FLAG_INHERIT, 0)) return {}; + + HANDLE stderrRead; + HANDLE stderrWrite; + if(!CreatePipe(&stderrRead, &stderrWrite, &sa, 0)) return {}; + if(!SetHandleInformation(stderrRead, HANDLE_FLAG_INHERIT, 0)) return {}; + + HANDLE stdinRead; + HANDLE stdinWrite; + if(!CreatePipe(&stdinRead, &stdinWrite, &sa, 0)) return {}; + if(!SetHandleInformation(stdinWrite, HANDLE_FLAG_INHERIT, 0)) return {}; + + STARTUPINFO si; + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.hStdOutput = stdoutWrite; + si.hStdError = stderrWrite; + si.hStdInput = stdinRead; + si.dwFlags = STARTF_USESTDHANDLES; + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + + if(!CreateProcess( + nullptr, utf16_t(arguments), + nullptr, nullptr, true, CREATE_NO_WINDOW, + nullptr, nullptr, &si, &pi + )) return {}; + + DWORD exitCode = EXIT_FAILURE; + if(WaitForSingleObject(pi.hProcess, INFINITE)) return {}; + if(!GetExitCodeProcess(pi.hProcess, &exitCode)) return {}; + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + char buffer[256]; + execute_result_t result; + result.code = exitCode; + + while(true) { + DWORD read, available, remaining; + if(!PeekNamedPipe(stdoutRead, nullptr, sizeof(buffer), &read, &available, &remaining)) break; + if(read == 0) break; + + if(!ReadFile(stdoutRead, buffer, sizeof(buffer), &read, nullptr)) break; + if(read == 0) break; + + auto offset = result.output.size(); + result.output.resize(offset + read); + memory::copy(result.output.get() + offset, buffer, read); + } + + while(true) { + DWORD read, available, remaining; + if(!PeekNamedPipe(stderrRead, nullptr, sizeof(buffer), &read, &available, &remaining)) break; + if(read == 0) break; + + if(!ReadFile(stderrRead, buffer, sizeof(buffer), &read, nullptr)) break; + if(read == 0) break; + + auto offset = result.error.size(); + result.error.resize(offset + read); + memory::copy(result.error.get() + offset, buffer, read); + } + + return result; +} + +template inline auto invoke(const string& name, P&&... p) -> void { + vector argl(forward

(p)...); + for(auto& arg : argl) if(arg.find(" ")) arg = {"\"", arg, "\""}; + string arguments = argl.merge(" "); + string directory = Path::program().replace("/", "\\"); + ShellExecute(nullptr, nullptr, utf16_t(name), utf16_t(arguments), utf16_t(directory), SW_SHOWNORMAL); +} + +#else + +template inline auto execute(const string& name, P&&... p) -> string { + return ""; +} + +template inline auto invoke(const string& name, P&&... p) -> void { +} + +#endif + +} diff --git a/waterbox/bsnescore/bsnes/nall/serial.hpp b/waterbox/bsnescore/bsnes/nall/serial.hpp new file mode 100644 index 0000000000..671e8ffe6a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/serial.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +#if !defined(API_POSIX) + #error "nall/serial: unsupported system" +#endif + +#include +#include +#include +#include + +namespace nall { + +struct serial { + ~serial() { + close(); + } + + auto readable() -> bool { + if(!opened) return false; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(port, &fdset); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + int result = select(FD_SETSIZE, &fdset, nullptr, nullptr, &timeout); + if(result < 1) return false; + return FD_ISSET(port, &fdset); + } + + //-1 on error, otherwise return bytes read + auto read(uint8_t* data, uint length) -> int { + if(!opened) return -1; + return ::read(port, (void*)data, length); + } + + auto writable() -> bool { + if(!opened) return false; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(port, &fdset); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + int result = select(FD_SETSIZE, nullptr, &fdset, nullptr, &timeout); + if(result < 1) return false; + return FD_ISSET(port, &fdset); + } + + //-1 on error, otherwise return bytes written + auto write(const uint8_t* data, uint length) -> int { + if(!opened) return -1; + return ::write(port, (void*)data, length); + } + + //rate==0: use flow control (synchronous mode) + //rate!=0: baud-rate (asynchronous mode) + auto open(string device, uint rate = 0) -> bool { + close(); + + if(!device) device = "/dev/ttyU0"; //note: default device name is for FreeBSD 10+ + port = ::open(device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if(port == -1) return false; + + if(ioctl(port, TIOCEXCL) == -1) { close(); return false; } + if(fcntl(port, F_SETFL, 0) == -1) { close(); return false; } + if(tcgetattr(port, &original_attr) == -1) { close(); return false; } + + termios attr = original_attr; + cfmakeraw(&attr); + cfsetspeed(&attr, rate ? rate : 57600); //rate value has no effect in synchronous mode + + attr.c_lflag &=~ (ECHO | ECHONL | ISIG | ICANON | IEXTEN); + attr.c_iflag &=~ (BRKINT | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); + attr.c_iflag |= (IGNBRK | IGNPAR); + attr.c_oflag &=~ (OPOST); + attr.c_cflag &=~ (CSIZE | CSTOPB | PARENB | CLOCAL); + attr.c_cflag |= (CS8 | CREAD); + if(rate) { + attr.c_cflag &= ~CRTSCTS; + } else { + attr.c_cflag |= CRTSCTS; + } + attr.c_cc[VTIME] = attr.c_cc[VMIN] = 0; + + if(tcsetattr(port, TCSANOW, &attr) == -1) { close(); return false; } + return opened = true; + } + + auto close() -> void { + if(port != -1) { + tcdrain(port); + if(opened) { + tcsetattr(port, TCSANOW, &original_attr); + opened = false; + } + ::close(port); + port = -1; + } + } + +private: + int port = -1; + bool opened = false; + termios original_attr; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/serializer.hpp b/waterbox/bsnescore/bsnes/nall/serializer.hpp new file mode 100644 index 0000000000..0910529beb --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/serializer.hpp @@ -0,0 +1,201 @@ +#pragma once + +//serializer: a class designed to save and restore the state of classes. +// +//benefits: +//- data() will be portable in size (it is not necessary to specify type sizes.) +//- data() will be portable in endianness (always stored internally as little-endian.) +//- one serialize function can both save and restore class states. +// +//caveats: +//- only plain-old-data can be stored. complex classes must provide serialize(serializer&); +//- floating-point usage is not portable across different implementations + +#include +#include +#include +#include +#include + +namespace nall { + +struct serializer; + +template +struct has_serialize { + template static auto test(decltype(std::declval().serialize(std::declval()))*) -> char; + template static auto test(...) -> long; + static const bool value = sizeof(test(0)) == sizeof(char); +}; + +struct serializer { + enum Mode : uint { Load, Save, Size }; + + explicit operator bool() const { + return _size; + } + + auto setMode(Mode mode) -> void { + _mode = mode; + _size = 0; + } + + auto mode() const -> Mode { + return _mode; + } + + auto data() const -> const uint8_t* { + return _data; + } + + auto size() const -> uint { + return _size; + } + + auto capacity() const -> uint { + return _capacity; + } + + template auto real(T& value) -> serializer& { + enum : uint { size = sizeof(T) }; + //this is rather dangerous, and not cross-platform safe; + //but there is no standardized way to export FP-values + auto p = (uint8_t*)&value; + if(_mode == Save) { + for(uint n : range(size)) _data[_size++] = p[n]; + } else if(_mode == Load) { + for(uint n : range(size)) p[n] = _data[_size++]; + } else { + _size += size; + } + return *this; + } + + template auto boolean(T& value) -> serializer& { + if(_mode == Save) { + _data[_size++] = (bool)value; + } else if(_mode == Load) { + value = (bool)_data[_size++]; + } else if(_mode == Size) { + _size += 1; + } + return *this; + } + + template auto integer(T& value) -> serializer& { + enum : uint { size = std::is_same::value ? 1 : sizeof(T) }; + if(_mode == Save) { + T copy = value; + for(uint n : range(size)) _data[_size++] = copy, copy >>= 8; + } else if(_mode == Load) { + value = 0; + for(uint n : range(size)) value |= (T)_data[_size++] << (n << 3); + } else if(_mode == Size) { + _size += size; + } + return *this; + } + + template auto array(T (&array)[N]) -> serializer& { + for(uint n : range(N)) operator()(array[n]); + return *this; + } + + template auto array(T array, uint size) -> serializer& { + for(uint n : range(size)) operator()(array[n]); + return *this; + } + + template auto array(nall::array& array) -> serializer& { + for(auto& value : array) operator()(value); + return *this; + } + + //optimized specializations + + auto array(uint8_t* data, uint size) -> serializer& { + if(_mode == Save) { + memory::copy(_data + _size, data, size); + } else if(_mode == Load) { + memory::copy(data, _data + _size, size); + } else { + } + _size += size; + return *this; + } + + template auto array(uint8_t (&data)[N]) -> serializer& { + return array(data, N); + } + + //nall/serializer saves data in little-endian ordering + #if defined(ENDIAN_LSB) + auto array(uint16_t* data, uint size) -> serializer& { return array((uint8_t*)data, size * sizeof(uint16_t)); } + auto array(uint32_t* data, uint size) -> serializer& { return array((uint8_t*)data, size * sizeof(uint32_t)); } + auto array(uint64_t* data, uint size) -> serializer& { return array((uint8_t*)data, size * sizeof(uint64_t)); } + template auto array(uint16_t (&data)[N]) -> serializer& { return array(data, N); } + template auto array(uint32_t (&data)[N]) -> serializer& { return array(data, N); } + template auto array(uint64_t (&data)[N]) -> serializer& { return array(data, N); } + #endif + + template auto operator()(T& value, typename std::enable_if::value>::type* = 0) -> serializer& { value.serialize(*this); return *this; } + template auto operator()(T& value, typename std::enable_if::value>::type* = 0) -> serializer& { return integer(value); } + template auto operator()(T& value, typename std::enable_if::value>::type* = 0) -> serializer& { return real(value); } + template auto operator()(T& value, typename std::enable_if::value>::type* = 0) -> serializer& { return array(value); } + template auto operator()(T& value, uint size, typename std::enable_if::value>::type* = 0) -> serializer& { return array(value, size); } + + auto operator=(const serializer& s) -> serializer& { + if(_data) delete[] _data; + + _mode = s._mode; + _data = new uint8_t[s._capacity]; + _size = s._size; + _capacity = s._capacity; + + memcpy(_data, s._data, s._capacity); + return *this; + } + + auto operator=(serializer&& s) -> serializer& { + if(_data) delete[] _data; + + _mode = s._mode; + _data = s._data; + _size = s._size; + _capacity = s._capacity; + + s._data = nullptr; + return *this; + } + + serializer() = default; + serializer(const serializer& s) { operator=(s); } + serializer(serializer&& s) { operator=(move(s)); } + + serializer(uint capacity) { + _mode = Save; + _data = new uint8_t[capacity](); + _size = 0; + _capacity = capacity; + } + + serializer(const uint8_t* data, uint capacity) { + _mode = Load; + _data = new uint8_t[capacity]; + _size = 0; + _capacity = capacity; + memcpy(_data, data, capacity); + } + + ~serializer() { + if(_data) delete[] _data; + } + +private: + Mode _mode = Size; + uint8_t* _data = nullptr; + uint _size = 0; + uint _capacity = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/service.hpp b/waterbox/bsnescore/bsnes/nall/service.hpp new file mode 100644 index 0000000000..3d3c902499 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/service.hpp @@ -0,0 +1,13 @@ +#pragma once + +//service model template built on top of shared-memory + +#include + +#if defined(API_POSIX) + #include +#endif + +#if defined(API_WINDOWS) + #include +#endif diff --git a/waterbox/bsnescore/bsnes/nall/set.hpp b/waterbox/bsnescore/bsnes/nall/set.hpp new file mode 100644 index 0000000000..733bf84348 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/set.hpp @@ -0,0 +1,266 @@ +#pragma once + +//set +//implementation: red-black tree +// +//search: O(log n) average; O(log n) worst +//insert: O(log n) average; O(log n) worst +//remove: O(log n) average; O(log n) worst +// +//requirements: +// bool T::operator==(const T&) const; +// bool T::operator< (const T&) const; + +#include +#include + +namespace nall { + +template struct set { + struct node_t { + T value; + bool red = 1; + node_t* link[2] = {nullptr, nullptr}; + node_t() = default; + node_t(const T& value) : value(value) {} + }; + + node_t* root = nullptr; + uint nodes = 0; + + set() = default; + set(const set& source) { operator=(source); } + set(set&& source) { operator=(move(source)); } + set(std::initializer_list list) { for(auto& value : list) insert(value); } + ~set() { reset(); } + + auto operator=(const set& source) -> set& { + if(this == &source) return *this; + reset(); + copy(root, source.root); + nodes = source.nodes; + return *this; + } + + auto operator=(set&& source) -> set& { + if(this == &source) return *this; + root = source.root; + nodes = source.nodes; + source.root = nullptr; + source.nodes = 0; + return *this; + } + + explicit operator bool() const { return nodes; } + auto size() const -> uint { return nodes; } + + auto reset() -> void { + reset(root); + nodes = 0; + } + + auto find(const T& value) -> maybe { + if(node_t* node = find(root, value)) return node->value; + return nothing; + } + + auto find(const T& value) const -> maybe { + if(node_t* node = find(root, value)) return node->value; + return nothing; + } + + auto insert(const T& value) -> maybe { + uint count = size(); + node_t* v = insert(root, value); + root->red = 0; + if(size() == count) return nothing; + return v->value; + } + + template auto insert(const T& value, P&&... p) -> bool { + bool result = insert(value); + insert(forward

(p)...) | result; + return result; + } + + auto remove(const T& value) -> bool { + uint count = size(); + bool done = 0; + remove(root, &value, done); + if(root) root->red = 0; + return size() < count; + } + + template auto remove(const T& value, P&&... p) -> bool { + bool result = remove(value); + return remove(forward

(p)...) | result; + } + + struct base_iterator { + auto operator!=(const base_iterator& source) const -> bool { return position != source.position; } + + auto operator++() -> base_iterator& { + if(++position >= source.size()) { position = source.size(); return *this; } + + if(stack.right()->link[1]) { + stack.append(stack.right()->link[1]); + while(stack.right()->link[0]) stack.append(stack.right()->link[0]); + } else { + node_t* child; + do child = stack.takeRight(); + while(child == stack.right()->link[1]); + } + + return *this; + } + + base_iterator(const set& source, uint position) : source(source), position(position) { + node_t* node = source.root; + while(node) { + stack.append(node); + node = node->link[0]; + } + } + + protected: + const set& source; + uint position; + vector stack; + }; + + struct iterator : base_iterator { + iterator(const set& source, uint position) : base_iterator(source, position) {} + auto operator*() const -> T& { return base_iterator::stack.right()->value; } + }; + + auto begin() -> iterator { return iterator(*this, 0); } + auto end() -> iterator { return iterator(*this, size()); } + + struct const_iterator : base_iterator { + const_iterator(const set& source, uint position) : base_iterator(source, position) {} + auto operator*() const -> const T& { return base_iterator::stack.right()->value; } + }; + + auto begin() const -> const const_iterator { return const_iterator(*this, 0); } + auto end() const -> const const_iterator { return const_iterator(*this, size()); } + +private: + auto reset(node_t*& node) -> void { + if(!node) return; + if(node->link[0]) reset(node->link[0]); + if(node->link[1]) reset(node->link[1]); + delete node; + node = nullptr; + } + + auto copy(node_t*& target, const node_t* source) -> void { + if(!source) return; + target = new node_t(source->value); + target->red = source->red; + copy(target->link[0], source->link[0]); + copy(target->link[1], source->link[1]); + } + + auto find(node_t* node, const T& value) const -> node_t* { + if(node == nullptr) return nullptr; + if(node->value == value) return node; + return find(node->link[node->value < value], value); + } + + auto red(node_t* node) const -> bool { return node && node->red; } + auto black(node_t* node) const -> bool { return !red(node); } + + auto rotate(node_t*& a, bool dir) -> void { + node_t*& b = a->link[!dir]; + node_t*& c = b->link[dir]; + a->red = 1, b->red = 0; + std::swap(a, b); + std::swap(b, c); + } + + auto rotateTwice(node_t*& node, bool dir) -> void { + rotate(node->link[!dir], !dir); + rotate(node, dir); + } + + auto insert(node_t*& node, const T& value) -> node_t* { + if(!node) { nodes++; node = new node_t(value); return node; } + if(node->value == value) { node->value = value; return node; } //prevent duplicate entries + + bool dir = node->value < value; + node_t* v = insert(node->link[dir], value); + if(black(node->link[dir])) return v; + + if(red(node->link[!dir])) { + node->red = 1; + node->link[0]->red = 0; + node->link[1]->red = 0; + } else if(red(node->link[dir]->link[dir])) { + rotate(node, !dir); + } else if(red(node->link[dir]->link[!dir])) { + rotateTwice(node, !dir); + } + + return v; + } + + auto balance(node_t*& node, bool dir, bool& done) -> void { + node_t* p = node; + node_t* s = node->link[!dir]; + if(!s) return; + + if(red(s)) { + rotate(node, dir); + s = p->link[!dir]; + } + + if(black(s->link[0]) && black(s->link[1])) { + if(red(p)) done = 1; + p->red = 0, s->red = 1; + } else { + bool save = p->red; + bool head = node == p; + + if(red(s->link[!dir])) rotate(p, dir); + else rotateTwice(p, dir); + + p->red = save; + p->link[0]->red = 0; + p->link[1]->red = 0; + + if(head) node = p; + else node->link[dir] = p; + + done = 1; + } + } + + auto remove(node_t*& node, const T* value, bool& done) -> void { + if(!node) { done = 1; return; } + + if(node->value == *value) { + if(!node->link[0] || !node->link[1]) { + node_t* save = node->link[!node->link[0]]; + + if(red(node)) done = 1; + else if(red(save)) save->red = 0, done = 1; + + nodes--; + delete node; + node = save; + return; + } else { + node_t* heir = node->link[0]; + while(heir->link[1]) heir = heir->link[1]; + node->value = heir->value; + value = &heir->value; + } + } + + bool dir = node->value < *value; + remove(node->link[dir], value, done); + if(!done) balance(node, dir, done); + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/shared-memory.hpp b/waterbox/bsnescore/bsnes/nall/shared-memory.hpp new file mode 100644 index 0000000000..9d40bca6a6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/shared-memory.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#if defined(API_POSIX) + #include +#endif + +#if defined(API_WINDOWS) + #include +#endif diff --git a/waterbox/bsnescore/bsnes/nall/shared-pointer.hpp b/waterbox/bsnescore/bsnes/nall/shared-pointer.hpp new file mode 100644 index 0000000000..e5755c13ab --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/shared-pointer.hpp @@ -0,0 +1,291 @@ +#pragma once + +#include +#include +#include +#include + +namespace nall { + +template struct shared_pointer; + +struct shared_pointer_manager { + void* pointer = nullptr; + function deleter; + uint strong = 0; + uint weak = 0; + + shared_pointer_manager(void* pointer) : pointer(pointer) { + } +}; + +template struct shared_pointer; +template struct shared_pointer_weak; +template struct shared_pointer_this; +struct shared_pointer_this_base{}; + +template +struct shared_pointer { + template static auto create(P&&... p) { + return shared_pointer{new T{forward

(p)...}}; + } + + using type = T; + shared_pointer_manager* manager = nullptr; + + template + struct is_compatible { + static constexpr bool value = is_base_of::value || is_base_of::value; + }; + + shared_pointer() { + } + + shared_pointer(T* source) { + operator=(source); + } + + shared_pointer(T* source, const function& deleter) { + operator=(source); + manager->deleter = function([=](void* p) { + deleter((T*)p); + }); + } + + shared_pointer(const shared_pointer& source) { + operator=(source); + } + + shared_pointer(shared_pointer&& source) { + operator=(move(source)); + } + + template::value>> + shared_pointer(const shared_pointer& source) { + operator=(source); + } + + template::value>> + shared_pointer(shared_pointer&& source) { + operator=(move(source)); + } + + template::value>> + shared_pointer(const shared_pointer_weak& source) { + operator=(source); + } + + template::value>> + shared_pointer(const shared_pointer& source, T* pointer) { + if((bool)source && (T*)source.manager->pointer == pointer) { + manager = source.manager; + manager->strong++; + } + } + + ~shared_pointer() { + reset(); + } + + auto operator=(T* source) -> shared_pointer& { + reset(); + if(source) { + manager = new shared_pointer_manager((void*)source); + manager->strong++; + if constexpr(is_base_of_v) { + source->weak = *this; + } + } + return *this; + } + + auto operator=(const shared_pointer& source) -> shared_pointer& { + if(this != &source) { + reset(); + if((bool)source) { + manager = source.manager; + manager->strong++; + } + } + return *this; + } + + auto operator=(shared_pointer&& source) -> shared_pointer& { + if(this != &source) { + reset(); + manager = source.manager; + source.manager = nullptr; + } + return *this; + } + + template::value>> + auto operator=(const shared_pointer& source) -> shared_pointer& { + if((uintptr)this != (uintptr)&source) { + reset(); + if((bool)source) { + manager = source.manager; + manager->strong++; + } + } + return *this; + } + + template::value>> + auto operator=(shared_pointer&& source) -> shared_pointer& { + if((uintptr)this != (uintptr)&source) { + reset(); + manager = source.manager; + source.manager = nullptr; + } + return *this; + } + + template::value>> + auto operator=(const shared_pointer_weak& source) -> shared_pointer& { + reset(); + if((bool)source) { + manager = source.manager; + manager->strong++; + } + return *this; + } + + auto data() -> T* { + if(manager) return (T*)manager->pointer; + return nullptr; + } + + auto data() const -> const T* { + if(manager) return (T*)manager->pointer; + return nullptr; + } + + auto operator->() -> T* { return data(); } + auto operator->() const -> const T* { return data(); } + + auto operator*() -> T& { return *data(); } + auto operator*() const -> const T& { return *data(); } + + auto operator()() -> T& { return *data(); } + auto operator()() const -> const T& { return *data(); } + + template + auto operator==(const shared_pointer& source) const -> bool { + return manager == source.manager; + } + + template + auto operator!=(const shared_pointer& source) const -> bool { + return manager != source.manager; + } + + explicit operator bool() const { + return manager && manager->strong; + } + + auto unique() const -> bool { + return manager && manager->strong == 1; + } + + auto references() const -> uint { + return manager ? manager->strong : 0; + } + + auto reset() -> void { + if(manager && manager->strong) { + //pointer may contain weak references; if strong==0 it may destroy manager + //as such, we must destroy strong before decrementing it to zero + if(manager->strong == 1) { + if(manager->deleter) { + manager->deleter(manager->pointer); + } else { + delete (T*)manager->pointer; + } + manager->pointer = nullptr; + } + if(--manager->strong == 0) { + if(manager->weak == 0) { + delete manager; + } + } + } + manager = nullptr; + } + + template + auto cast() -> shared_pointer { + if(auto pointer = dynamic_cast(data())) { + return {*this, pointer}; + } + return {}; + } +}; + +template +struct shared_pointer_weak { + using type = T; + shared_pointer_manager* manager = nullptr; + + shared_pointer_weak() { + } + + shared_pointer_weak(const shared_pointer& source) { + operator=(source); + } + + auto operator=(const shared_pointer& source) -> shared_pointer_weak& { + reset(); + if(manager = source.manager) manager->weak++; + return *this; + } + + ~shared_pointer_weak() { + reset(); + } + + auto operator==(const shared_pointer_weak& source) const -> bool { + return manager == source.manager; + } + + auto operator!=(const shared_pointer_weak& source) const -> bool { + return manager != source.manager; + } + + explicit operator bool() const { + return manager && manager->strong; + } + + auto acquire() const -> shared_pointer { + return shared_pointer(*this); + } + + auto reset() -> void { + if(manager && --manager->weak == 0) { + if(manager->strong == 0) { + delete manager; + } + } + manager = nullptr; + } +}; + +template +struct shared_pointer_this : shared_pointer_this_base { + shared_pointer_weak weak; + auto shared() -> shared_pointer { return weak; } + auto shared() const -> shared_pointer { return weak; } +}; + +template +auto shared_pointer_make(P&&... p) -> shared_pointer { + return shared_pointer{new T{forward

(p)...}}; +} + +template +struct shared_pointer_new : shared_pointer { + shared_pointer_new(const shared_pointer& source) : shared_pointer(source) {} + template shared_pointer_new(P&&... p) : shared_pointer(new T(forward

(p)...)) {} +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/simd.hpp b/waterbox/bsnescore/bsnes/nall/simd.hpp new file mode 100644 index 0000000000..b413ee80b8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/simd.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace nall { + +#if defined(__AVX2__) + #define SIMD 256 + #define SIMD_AVX2 + #include +#endif + +} diff --git a/waterbox/bsnescore/bsnes/nall/smtp.hpp b/waterbox/bsnescore/bsnes/nall/smtp.hpp new file mode 100644 index 0000000000..6bad4135c3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/smtp.hpp @@ -0,0 +1,314 @@ +#pragma once + +#include +#include +#include + +#if !defined(PLATFORM_WINDOWS) + #include + #include + #include + #include +#else + #include + #include +#endif + +namespace nall { + +struct SMTP { + enum class Format : uint { Plain, HTML }; + + inline auto server(string server, uint16_t port = 25) -> void; + inline auto from(string mail, string name = "") -> void; + inline auto to(string mail, string name = "") -> void; + inline auto cc(string mail, string name = "") -> void; + inline auto bcc(string mail, string name = "") -> void; + inline auto attachment(const uint8_t* data, uint size, string name) -> void; + inline auto attachment(string filename, string name = "") -> bool; + inline auto subject(string subject) -> void; + inline auto body(string body, Format format = Format::Plain) -> void; + + inline auto send() -> bool; + inline auto message() -> string; + inline auto response() -> string; + + #if defined(API_WINDOWS) + inline auto close(int) -> int; + inline SMTP(); + #endif + +private: + struct Information { + string server; + uint16_t port; + struct Contact { + string mail; + string name; + }; + Contact from; + vector to; + vector cc; + vector bcc; + struct Attachment { + vector buffer; + string name; + }; + string subject; + string body; + Format format = Format::Plain; + vector attachments; + + string message; + string response; + } info; + + inline auto send(int sock, const string& text) -> bool; + inline auto recv(int sock) -> string; + inline auto boundary() -> string; + inline auto filename(const string& filename) -> string; + inline auto contact(const Information::Contact& contact) -> string; + inline auto contacts(const vector& contacts) -> string; + inline auto split(const string& text) -> string; +}; + +auto SMTP::server(string server, uint16_t port) -> void { + info.server = server; + info.port = port; +} + +auto SMTP::from(string mail, string name) -> void { + info.from = {mail, name}; +} + +auto SMTP::to(string mail, string name) -> void { + info.to.append({mail, name}); +} + +auto SMTP::cc(string mail, string name) -> void { + info.cc.append({mail, name}); +} + +auto SMTP::bcc(string mail, string name) -> void { + info.bcc.append({mail, name}); +} + +auto SMTP::attachment(const uint8_t* data, uint size, string name) -> void { + vector buffer; + buffer.resize(size); + memcpy(buffer.data(), data, size); + info.attachments.append({std::move(buffer), name}); +} + +auto SMTP::attachment(string filename, string name) -> bool { + if(!file::exists(filename)) return false; + if(name == "") name = notdir(filename); + auto buffer = file::read(filename); + info.attachments.append({std::move(buffer), name}); + return true; +} + +auto SMTP::subject(string subject) -> void { + info.subject = subject; +} + +auto SMTP::body(string body, Format format) -> void { + info.body = body; + info.format = format; +} + +auto SMTP::send() -> bool { + info.message.append("From: =?UTF-8?B?", Base64::encode(contact(info.from)), "?=\r\n"); + info.message.append("To: =?UTF-8?B?", Base64::encode(contacts(info.to)), "?=\r\n"); + info.message.append("Cc: =?UTF-8?B?", Base64::encode(contacts(info.cc)), "?=\r\n"); + info.message.append("Subject: =?UTF-8?B?", Base64::encode(info.subject), "?=\r\n"); + + string uniqueID = boundary(); + + info.message.append("MIME-Version: 1.0\r\n"); + info.message.append("Content-Type: multipart/mixed; boundary=", uniqueID, "\r\n"); + info.message.append("\r\n"); + + string format = (info.format == Format::Plain ? "text/plain" : "text/html"); + + info.message.append("--", uniqueID, "\r\n"); + info.message.append("Content-Type: ", format, "; charset=UTF-8\r\n"); + info.message.append("Content-Transfer-Encoding: base64\r\n"); + info.message.append("\r\n"); + info.message.append(split(Base64::encode(info.body)), "\r\n"); + info.message.append("\r\n"); + + for(auto& attachment : info.attachments) { + info.message.append("--", uniqueID, "\r\n"); + info.message.append("Content-Type: application/octet-stream\r\n"); + info.message.append("Content-Transfer-Encoding: base64\r\n"); + info.message.append("Content-Disposition: attachment; size=", attachment.buffer.size(), "; filename*=UTF-8''", filename(attachment.name), "\r\n"); + info.message.append("\r\n"); + info.message.append(split(Base64::encode(attachment.buffer)), "\r\n"); + info.message.append("\r\n"); + } + + info.message.append("--", uniqueID, "--\r\n"); + + addrinfo hints; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + addrinfo* serverinfo; + int status = getaddrinfo(info.server, string(info.port), &hints, &serverinfo); + if(status != 0) return false; + + int sock = socket(serverinfo->ai_family, serverinfo->ai_socktype, serverinfo->ai_protocol); + if(sock == -1) return false; + + int result = connect(sock, serverinfo->ai_addr, serverinfo->ai_addrlen); + if(result == -1) return false; + + string response; + info.response.append(response = recv(sock)); + if(!response.beginswith("220 ")) { close(sock); return false; } + + send(sock, {"HELO ", info.server, "\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + + send(sock, {"MAIL FROM: <", info.from.mail, ">\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + + for(auto& contact : info.to) { + send(sock, {"RCPT TO: <", contact.mail, ">\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + } + + for(auto& contact : info.cc) { + send(sock, {"RCPT TO: <", contact.mail, ">\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + } + + for(auto& contact : info.bcc) { + send(sock, {"RCPT TO: <", contact.mail, ">\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + } + + send(sock, {"DATA\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("354 ")) { close(sock); return false; } + + send(sock, {info.message, "\r\n", ".\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + + send(sock, {"QUIT\r\n"}); + info.response.append(response = recv(sock)); +//if(!response.beginswith("221 ")) { close(sock); return false; } + + close(sock); + return true; +} + +auto SMTP::message() -> string { + return info.message; +} + +auto SMTP::response() -> string { + return info.response; +} + +auto SMTP::send(int sock, const string& text) -> bool { + const char* data = text.data(); + uint size = text.size(); + while(size) { + int length = ::send(sock, (const char*)data, size, 0); + if(length == -1) return false; + data += length; + size -= length; + } + return true; +} + +auto SMTP::recv(int sock) -> string { + vector buffer; + while(true) { + char c; + if(::recv(sock, &c, sizeof(char), 0) < 1) break; + buffer.append(c); + if(c == '\n') break; + } + buffer.append(0); + return buffer; +} + +auto SMTP::boundary() -> string { + random_lfsr random; + random.seed(time(0)); + string boundary; + for(uint n = 0; n < 16; n++) boundary.append(hex<2>(random())); + return boundary; +} + +auto SMTP::filename(const string& filename) -> string { + string result; + for(auto& n : filename) { + if(n <= 32 || n >= 127) result.append("%", hex<2>(n)); + else result.append(n); + } + return result; +} + +auto SMTP::contact(const Information::Contact& contact) -> string { + if(!contact.name) return contact.mail; + return {"\"", contact.name, "\" <", contact.mail, ">"}; +} + +auto SMTP::contacts(const vector& contacts) -> string { + string result; + for(auto& contact : contacts) { + result.append(this->contact(contact), "; "); + } + result.trimRight("; ", 1L); + return result; +} + +auto SMTP::split(const string& text) -> string { + string result; + + uint offset = 0; + while(offset < text.size()) { + uint length = min(76, text.size() - offset); + if(length < 76) { + result.append(text.slice(offset)); + } else { + result.append(text.slice(offset, 76), "\r\n"); + } + offset += length; + } + + return result; +} + +#if defined(API_WINDOWS) +auto SMTP::close(int sock) -> int { + return closesocket(sock); +} + +SMTP::SMTP() { + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sock == INVALID_SOCKET && WSAGetLastError() == WSANOTINITIALISED) { + WSADATA wsaData; + if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + WSACleanup(); + return; + } + } else { + close(sock); + } +} +#endif + +} diff --git a/waterbox/bsnescore/bsnes/nall/stdint.hpp b/waterbox/bsnescore/bsnes/nall/stdint.hpp new file mode 100644 index 0000000000..9b36a6cc7c --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/stdint.hpp @@ -0,0 +1,65 @@ +#pragma once + +#if defined(_MSC_VER) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef signed long long int64_t; + typedef int64_t intmax_t; + #if defined(_WIN64) + typedef int64_t intptr_t; + #else + typedef int32_t intptr_t; + #endif + + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef unsigned long long uint64_t; + typedef uint64_t uintmax_t; + #if defined(_WIN64) + typedef uint64_t uintptr_t; + #else + typedef uint32_t uintptr_t; + #endif +#else + #include +#endif + +//note: (u)intmax actually mean it: use as many bits as is possible +#if defined(__SIZEOF_INT128__) + using int128_t = signed __int128; + using uint128_t = unsigned __int128; + + #define INTMAX_BITS 128 + using intmax = int128_t; + using uintmax = uint128_t; +#else + #define INTMAX_BITS 64 + using intmax = intmax_t; + using uintmax = uintmax_t; +#endif + +using intptr = intptr_t; +using uintptr = uintptr_t; + +using float32_t = float; +using float64_t = double; +//note: long double size is not reliable across platforms +//using float80_t = long double; + +static_assert(sizeof(int8_t) == 1, "int8_t is not of the correct size" ); +static_assert(sizeof(int16_t) == 2, "int16_t is not of the correct size"); +static_assert(sizeof(int32_t) == 4, "int32_t is not of the correct size"); +static_assert(sizeof(int64_t) == 8, "int64_t is not of the correct size"); + +static_assert(sizeof(uint8_t) == 1, "int8_t is not of the correct size" ); +static_assert(sizeof(uint16_t) == 2, "int16_t is not of the correct size"); +static_assert(sizeof(uint32_t) == 4, "int32_t is not of the correct size"); +static_assert(sizeof(uint64_t) == 8, "int64_t is not of the correct size"); + +static_assert(sizeof(float) >= 4, "float32_t is not of the correct size"); +static_assert(sizeof(double) >= 8, "float64_t is not of the correct size"); +//static_assert(sizeof(long double) >= 10, "float80_t is not of the correct size"); + +using uint = unsigned int; diff --git a/waterbox/bsnescore/bsnes/nall/string.hpp b/waterbox/bsnescore/bsnes/nall/string.hpp new file mode 100644 index 0000000000..8a83798792 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string.hpp @@ -0,0 +1,366 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + +struct string; +struct string_format; + +struct string_view { + using type = string_view; + + //view.hpp + inline string_view(); + inline string_view(const string_view& source); + inline string_view(string_view&& source); + inline string_view(const char* data); + inline string_view(const char* data, uint size); + inline string_view(const string& source); + template inline string_view(P&&... p); + inline ~string_view(); + + inline auto operator=(const string_view& source) -> type&; + inline auto operator=(string_view&& source) -> type&; + + inline explicit operator bool() const; + inline operator const char*() const; + inline auto data() const -> const char*; + inline auto size() const -> uint; + + inline auto begin() const { return &_data[0]; } + inline auto end() const { return &_data[size()]; } + +protected: + string* _string; + const char* _data; + mutable int _size; +}; + +//adaptive (SSO + COW) is by far the best choice, the others exist solely to: +//1) demonstrate the performance benefit of combining SSO + COW +//2) rule out allocator bugs by trying different allocators when needed +#define NALL_STRING_ALLOCATOR_ADAPTIVE +//#define NALL_STRING_ALLOCATOR_COPY_ON_WRITE +//#define NALL_STRING_ALLOCATOR_SMALL_STRING_OPTIMIZATION +//#define NALL_STRING_ALLOCATOR_VECTOR + +//cast.hpp +template struct stringify; + +//format.hpp +template inline auto print(P&&...) -> void; +template inline auto print(FILE*, P&&...) -> void; +template inline auto pad(const T& value, long precision = 0, char padchar = ' ') -> string; +inline auto hex(uintmax value, long precision = 0, char padchar = '0') -> string; +inline auto octal(uintmax value, long precision = 0, char padchar = '0') -> string; +inline auto binary(uintmax value, long precision = 0, char padchar = '0') -> string; + +//match.hpp +inline auto tokenize(const char* s, const char* p) -> bool; +inline auto tokenize(vector& list, const char* s, const char* p) -> bool; + +//utf8.hpp +inline auto characters(string_view self, int offset = 0, int length = -1) -> uint; + +//utility.hpp +inline auto slice(string_view self, int offset = 0, int length = -1) -> string; +template inline auto fromInteger(char* result, T value) -> char*; +template inline auto fromNatural(char* result, T value) -> char*; +template inline auto fromReal(char* str, T value) -> uint; + +struct string { + using type = string; + +protected: + #if defined(NALL_STRING_ALLOCATOR_ADAPTIVE) + enum : uint { SSO = 24 }; + union { + struct { //copy-on-write + char* _data; + uint* _refs; + }; + struct { //small-string-optimization + char _text[SSO]; + }; + }; + inline auto _allocate() -> void; + inline auto _copy() -> void; + inline auto _resize() -> void; + #endif + + #if defined(NALL_STRING_ALLOCATOR_COPY_ON_WRITE) + char* _data; + mutable uint* _refs; + inline auto _allocate() -> char*; + inline auto _copy() -> char*; + #endif + + #if defined(NALL_STRING_ALLOCATOR_SMALL_STRING_OPTIMIZATION) + enum : uint { SSO = 24 }; + union { + char* _data; + char _text[SSO]; + }; + #endif + + #if defined(NALL_STRING_ALLOCATOR_VECTOR) + char* _data; + #endif + + uint _capacity; + uint _size; + +public: + inline string(); + inline string(string& source) : string() { operator=(source); } + inline string(const string& source) : string() { operator=(source); } + inline string(string&& source) : string() { operator=(move(source)); } + template inline auto get() -> T*; + template inline auto data() const -> const T*; + template auto size() const -> uint { return _size / sizeof(T); } + template auto capacity() const -> uint { return _capacity / sizeof(T); } + inline auto reset() -> type&; + inline auto reserve(uint) -> type&; + inline auto resize(uint) -> type&; + inline auto operator=(const string&) -> type&; + inline auto operator=(string&&) -> type&; + + template string(T&& s, P&&... p) : string() { + append(forward(s), forward

(p)...); + } + ~string() { reset(); } + + explicit operator bool() const { return _size; } + operator const char*() const { return (const char*)data(); } + operator array_span() { return {(char*)get(), size()}; } + operator array_view() const { return {(const char*)data(), size()}; } + operator array_span() { return {(uint8_t*)get(), size()}; } + operator array_view() const { return {(const uint8_t*)data(), size()}; } + + auto operator==(const string& source) const -> bool { + return size() == source.size() && memory::compare(data(), source.data(), size()) == 0; + } + auto operator!=(const string& source) const -> bool { + return size() != source.size() || memory::compare(data(), source.data(), size()) != 0; + } + + auto operator==(const char* source) const -> bool { return strcmp(data(), source) == 0; } + auto operator!=(const char* source) const -> bool { return strcmp(data(), source) != 0; } + + auto operator==(string_view source) const -> bool { return compare(source) == 0; } + auto operator!=(string_view source) const -> bool { return compare(source) != 0; } + auto operator< (string_view source) const -> bool { return compare(source) < 0; } + auto operator<=(string_view source) const -> bool { return compare(source) <= 0; } + auto operator> (string_view source) const -> bool { return compare(source) > 0; } + auto operator>=(string_view source) const -> bool { return compare(source) >= 0; } + + auto begin() -> char* { return &get()[0]; } + auto end() -> char* { return &get()[size()]; } + auto begin() const -> const char* { return &data()[0]; } + auto end() const -> const char* { return &data()[size()]; } + + //atoi.hpp + inline auto boolean() const -> bool; + inline auto integer() const -> intmax; + inline auto natural() const -> uintmax; + inline auto hex() const -> uintmax; + inline auto real() const -> double; + + //core.hpp + inline auto operator[](uint) const -> const char&; + inline auto operator()(uint, char = 0) const -> char; + template inline auto assign(P&&...) -> type&; + template inline auto prepend(const T&, P&&...) -> type&; + template inline auto prepend(const nall::string_format&, P&&...) -> type&; + template inline auto _prepend(const stringify&) -> type&; + template inline auto append(const T&, P&&...) -> type&; + template inline auto append(const nall::string_format&, P&&...) -> type&; + template inline auto _append(const stringify&) -> type&; + inline auto length() const -> uint; + + //find.hpp + inline auto contains(string_view characters) const -> maybe; + + template inline auto _find(int, string_view) const -> maybe; + + inline auto find(string_view source) const -> maybe; + inline auto ifind(string_view source) const -> maybe; + inline auto qfind(string_view source) const -> maybe; + inline auto iqfind(string_view source) const -> maybe; + + inline auto findFrom(int offset, string_view source) const -> maybe; + inline auto ifindFrom(int offset, string_view source) const -> maybe; + + inline auto findNext(int offset, string_view source) const -> maybe; + inline auto ifindNext(int offset, string_view source) const -> maybe; + + inline auto findPrevious(int offset, string_view source) const -> maybe; + inline auto ifindPrevious(int offset, string_view source) const -> maybe; + + //format.hpp + inline auto format(const nall::string_format& params) -> type&; + + //compare.hpp + template inline static auto _compare(const char*, uint, const char*, uint) -> int; + + inline static auto compare(string_view, string_view) -> int; + inline static auto icompare(string_view, string_view) -> int; + + inline auto compare(string_view source) const -> int; + inline auto icompare(string_view source) const -> int; + + inline auto equals(string_view source) const -> bool; + inline auto iequals(string_view source) const -> bool; + + inline auto beginsWith(string_view source) const -> bool; + inline auto ibeginsWith(string_view source) const -> bool; + + inline auto endsWith(string_view source) const -> bool; + inline auto iendsWith(string_view source) const -> bool; + + //convert.hpp + inline auto downcase() -> type&; + inline auto upcase() -> type&; + + inline auto qdowncase() -> type&; + inline auto qupcase() -> type&; + + inline auto transform(string_view from, string_view to) -> type&; + + //match.hpp + inline auto match(string_view source) const -> bool; + inline auto imatch(string_view source) const -> bool; + + //replace.hpp + template inline auto _replace(string_view, string_view, long) -> type&; + inline auto replace(string_view from, string_view to, long limit = LONG_MAX) -> type&; + inline auto ireplace(string_view from, string_view to, long limit = LONG_MAX) -> type&; + inline auto qreplace(string_view from, string_view to, long limit = LONG_MAX) -> type&; + inline auto iqreplace(string_view from, string_view to, long limit = LONG_MAX) -> type&; + + //split.hpp + inline auto split(string_view key, long limit = LONG_MAX) const -> vector; + inline auto isplit(string_view key, long limit = LONG_MAX) const -> vector; + inline auto qsplit(string_view key, long limit = LONG_MAX) const -> vector; + inline auto iqsplit(string_view key, long limit = LONG_MAX) const -> vector; + + //trim.hpp + inline auto trim(string_view lhs, string_view rhs, long limit = LONG_MAX) -> type&; + inline auto trimLeft(string_view lhs, long limit = LONG_MAX) -> type&; + inline auto trimRight(string_view rhs, long limit = LONG_MAX) -> type&; + + inline auto itrim(string_view lhs, string_view rhs, long limit = LONG_MAX) -> type&; + inline auto itrimLeft(string_view lhs, long limit = LONG_MAX) -> type&; + inline auto itrimRight(string_view rhs, long limit = LONG_MAX) -> type&; + + inline auto strip() -> type&; + inline auto stripLeft() -> type&; + inline auto stripRight() -> type&; + + //utf8.hpp + inline auto characters(int offset = 0, int length = -1) const -> uint; + + //utility.hpp + inline static auto read(string_view filename) -> string; + inline static auto repeat(string_view pattern, uint times) -> string; + inline auto fill(char fill = ' ') -> type&; + inline auto hash() const -> uint; + inline auto remove(uint offset, uint length) -> type&; + inline auto reverse() -> type&; + inline auto size(int length, char fill = ' ') -> type&; + inline auto slice(int offset = 0, int length = -1) const -> string; +}; + +template<> struct vector : vector_base { + using type = vector; + using vector_base::vector_base; + + vector(const vector& source) { vector_base::operator=(source); } + vector(vector& source) { vector_base::operator=(source); } + vector(vector&& source) { vector_base::operator=(move(source)); } + template vector(P&&... p) { append(forward

(p)...); } + + inline auto operator=(const vector& source) -> type& { return vector_base::operator=(source), *this; } + inline auto operator=(vector& source) -> type& { return vector_base::operator=(source), *this; } + inline auto operator=(vector&& source) -> type& { return vector_base::operator=(move(source)), *this; } + + //vector.hpp + template inline auto append(const string&, P&&...) -> type&; + inline auto append() -> type&; + + inline auto isort() -> type&; + inline auto find(string_view source) const -> maybe; + inline auto ifind(string_view source) const -> maybe; + inline auto match(string_view pattern) const -> vector; + inline auto merge(string_view separator) const -> string; + inline auto strip() -> type&; + + //split.hpp + template inline auto _split(string_view, string_view, long) -> type&; +}; + +struct string_format : vector { + using type = string_format; + + template string_format(P&&... p) { reserve(sizeof...(p)); append(forward

(p)...); } + template inline auto append(const T&, P&&... p) -> type&; + inline auto append() -> type&; +}; + +inline auto operator"" _s(const char* value, std::size_t) -> string { return {value}; } + +} + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include diff --git a/waterbox/bsnescore/bsnes/nall/string/allocator/adaptive.hpp b/waterbox/bsnescore/bsnes/nall/string/allocator/adaptive.hpp new file mode 100644 index 0000000000..174a72d96a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/allocator/adaptive.hpp @@ -0,0 +1,123 @@ +#pragma once + +/***** + adaptive allocator + sizeof(string) == SSO + 8 + + aggressively tries to avoid heap allocations + small strings are stored on the stack + large strings are shared via copy-on-write + + SSO alone is very slow on large strings due to copying + SSO alone is very slightly faster than this allocator on small strings + + COW alone is very slow on small strings due to heap allocations + COW alone is very slightly faster than this allocator on large strings + + adaptive is thus very fast for all string sizes +*****/ + +namespace nall { + +string::string() : _data(nullptr), _capacity(SSO - 1), _size(0) { +} + +template +auto string::get() -> T* { + if(_capacity < SSO) return (T*)_text; + if(*_refs > 1) _copy(); + return (T*)_data; +} + +template +auto string::data() const -> const T* { + if(_capacity < SSO) return (const T*)_text; + return (const T*)_data; +} + +auto string::reset() -> type& { + if(_capacity >= SSO && !--*_refs) memory::free(_data); + _data = nullptr; + _capacity = SSO - 1; + _size = 0; + return *this; +} + +auto string::reserve(uint capacity) -> type& { + if(capacity <= _capacity) return *this; + capacity = bit::round(capacity + 1) - 1; + if(_capacity < SSO) { + _capacity = capacity; + _allocate(); + } else if(*_refs > 1) { + _capacity = capacity; + _copy(); + } else { + _capacity = capacity; + _resize(); + } + return *this; +} + +auto string::resize(uint size) -> type& { + reserve(size); + get()[_size = size] = 0; + return *this; +} + +auto string::operator=(const string& source) -> type& { + if(&source == this) return *this; + reset(); + if(source._capacity >= SSO) { + _data = source._data; + _refs = source._refs; + _capacity = source._capacity; + _size = source._size; + ++*_refs; + } else { + memory::copy(_text, source._text, SSO); + _capacity = source._capacity; + _size = source._size; + } + return *this; +} + +auto string::operator=(string&& source) -> type& { + if(&source == this) return *this; + reset(); + memory::copy(this, &source, sizeof(string)); + source._data = nullptr; + source._capacity = SSO - 1; + source._size = 0; + return *this; +} + +//SSO -> COW +auto string::_allocate() -> void { + char _temp[SSO]; + memory::copy(_temp, _text, SSO); + _data = memory::allocate(_capacity + 1 + sizeof(uint)); + memory::copy(_data, _temp, SSO); + _refs = (uint*)(_data + _capacity + 1); //always aligned by 32 via reserve() + *_refs = 1; +} + +//COW -> Unique +auto string::_copy() -> void { + auto _temp = memory::allocate(_capacity + 1 + sizeof(uint)); + memory::copy(_temp, _data, _size = min(_capacity, _size)); + _temp[_size] = 0; + --*_refs; + _data = _temp; + _refs = (uint*)(_data + _capacity + 1); + *_refs = 1; +} + +//COW -> Resize +auto string::_resize() -> void { + _data = memory::resize(_data, _capacity + 1 + sizeof(uint)); + _refs = (uint*)(_data + _capacity + 1); + *_refs = 1; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/allocator/copy-on-write.hpp b/waterbox/bsnescore/bsnes/nall/string/allocator/copy-on-write.hpp new file mode 100644 index 0000000000..c1a19ae5e9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/allocator/copy-on-write.hpp @@ -0,0 +1,92 @@ +#pragma once + +namespace nall { + +string::string() : _data(nullptr), _refs(nullptr), _capacity(0), _size(0) { +} + +template +auto string::get() -> T* { + static char _null[] = ""; + if(!_data) return (T*)_null; + if(*_refs > 1) _data = _copy(); //make unique for write operations + return (T*)_data; +} + +template +auto string::data() const -> const T* { + static const char _null[] = ""; + if(!_data) return (const T*)_null; + return (const T*)_data; +} + +auto string::reset() -> type& { + if(_data && !--*_refs) { + memory::free(_data); + _data = nullptr; //_refs = nullptr; is unnecessary + } + _capacity = 0; + _size = 0; + return *this; +} + +auto string::reserve(uint capacity) -> type& { + if(capacity > _capacity) { + _capacity = bit::round(max(31u, capacity) + 1) - 1; + _data = _data ? _copy() : _allocate(); + } + return *this; +} + +auto string::resize(uint size) -> type& { + reserve(size); + get()[_size = size] = 0; + return *this; +} + +auto string::operator=(const string& source) -> string& { + if(&source == this) return *this; + reset(); + if(source._data) { + _data = source._data; + _refs = source._refs; + _capacity = source._capacity; + _size = source._size; + ++*_refs; + } + return *this; +} + +auto string::operator=(string&& source) -> string& { + if(&source == this) return *this; + reset(); + _data = source._data; + _refs = source._refs; + _capacity = source._capacity; + _size = source._size; + source._data = nullptr; + source._refs = nullptr; + source._capacity = 0; + source._size = 0; + return *this; +} + +auto string::_allocate() -> char* { + auto _temp = memory::allocate(_capacity + 1 + sizeof(uint)); + *_temp = 0; + _refs = (uint*)(_temp + _capacity + 1); //this will always be aligned by 32 via reserve() + *_refs = 1; + return _temp; +} + +auto string::_copy() -> char* { + auto _temp = memory::allocate(_capacity + 1 + sizeof(uint)); + memory::copy(_temp, _data, _size = min(_capacity, _size)); + _temp[_size] = 0; + --*_refs; + _refs = (uint*)(_temp + _capacity + 1); + *_refs = 1; + return _temp; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/allocator/small-string-optimization.hpp b/waterbox/bsnescore/bsnes/nall/string/allocator/small-string-optimization.hpp new file mode 100644 index 0000000000..39aad4e762 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/allocator/small-string-optimization.hpp @@ -0,0 +1,95 @@ +#pragma once + +/* +small string optimization (SSO) allocator +sizeof(string) == 8 + string::SSO + +utilizes a union to store small strings directly into text pointer +bypasses the need to allocate heap memory for small strings +requires extra computations, which can be slower for large strings + +pros: +* potential for in-place resize +* no heap allocation when (capacity < SSO) + +cons: +* added overhead to fetch data() +* pass-by-value requires heap allocation when (capacity >= SSO) + +*/ + +namespace nall { + +string::string() { + _data = nullptr; + _capacity = SSO - 1; + _size = 0; +} + +template +auto string::get() -> T* { + if(_capacity < SSO) return (T*)_text; + return (T*)_data; +} + +template +auto string::data() const -> const T* { + if(_capacity < SSO) return (const T*)_text; + return (const T*)_data; +} + +auto string::reset() -> type& { + if(_capacity >= SSO) memory::free(_data); + _data = nullptr; + _capacity = SSO - 1; + _size = 0; + return *this; +} + +auto string::reserve(uint capacity) -> type& { + if(capacity <= _capacity) return *this; + capacity = bit::round(capacity + 1) - 1; + if(_capacity < SSO) { + char _temp[SSO]; + memory::copy(_temp, _text, SSO); + _data = memory::allocate(_capacity = capacity + 1); + memory::copy(_data, _temp, SSO); + } else { + _data = memory::resize(_data, _capacity = capacity + 1); + } + return *this; +} + +auto string::resize(uint size) -> type& { + reserve(size); + get()[_size = size] = 0; + return *this; +} + +auto string::operator=(const string& source) -> type& { + if(&source == this) return *this; + reset(); + if(source._capacity >= SSO) { + _data = memory::allocate(source._capacity + 1); + _capacity = source._capacity; + _size = source._size; + memory::copy(_data, source._data, source._size + 1); + } else { + memory::copy(_text, source._text, SSO); + _capacity = SSO - 1; + _size = source._size; + } + return *this; +} + +auto string::operator=(string&& source) -> type& { + if(&source == this) return *this; + reset(); + memory::copy(this, &source, sizeof(string)); + source._data = nullptr; + source._capacity = SSO - 1; + source._size = 0; + return *this; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/allocator/vector.hpp b/waterbox/bsnescore/bsnes/nall/string/allocator/vector.hpp new file mode 100644 index 0000000000..ad11316260 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/allocator/vector.hpp @@ -0,0 +1,84 @@ +#pragma once + +/* +vector allocator +sizeof(string) == 16 (amd64) + +utilizes a raw string pointer +always allocates memory onto the heap when string is not empty + +pros: +* potential for in-place resize +* simplicity + +cons: +* always allocates heap memory on (capacity > 0) +* pass-by-value requires heap allocation + +*/ + +namespace nall { + +template +auto string::get() -> T* { + if(_capacity == 0) reserve(1); + return (T*)_data; +} + +template +auto string::data() const -> const T* { + if(_capacity == 0) return (const T*)""; + return (const T*)_data; +} + +auto string::reset() -> type& { + if(_data) { memory::free(_data); _data = nullptr; } + _capacity = 0; + _size = 0; + return *this; +} + +auto string::reserve(uint capacity) -> type& { + if(capacity > _capacity) { + _capacity = bit::round(capacity + 1) - 1; + _data = memory::resize(_data, _capacity + 1); + _data[_capacity] = 0; + } + return *this; +} + +auto string::resize(uint size) -> type& { + reserve(size); + get()[_size = size] = 0; + return *this; +} + +auto string::operator=(const string& source) -> type& { + if(&source == this) return *this; + reset(); + _data = memory::allocate(source._size + 1); + _capacity = source._size; + _size = source._size; + memory::copy(_data, source.data(), source.size() + 1); + return *this; +} + +auto string::operator=(string&& source) -> type& { + if(&source == this) return *this; + reset(); + _data = source._data; + _capacity = source._capacity; + _size = source._size; + source._data = nullptr; + source._capacity = 0; + source._size = 0; + return *this; +} + +string::string() { + _data = nullptr; + _capacity = 0; + _size = 0; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/atoi.hpp b/waterbox/bsnescore/bsnes/nall/string/atoi.hpp new file mode 100644 index 0000000000..11dafbc9e5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/atoi.hpp @@ -0,0 +1,25 @@ +#pragma once + +namespace nall { + +auto string::boolean() const -> bool { + return equals("true"); +} + +auto string::integer() const -> intmax { + return toInteger(data()); +} + +auto string::natural() const -> uintmax { + return toNatural(data()); +} + +auto string::hex() const -> uintmax { + return toHex(data()); +} + +auto string::real() const -> double { + return toReal(data()); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/cast.hpp b/waterbox/bsnescore/bsnes/nall/string/cast.hpp new file mode 100644 index 0000000000..da915eeda6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/cast.hpp @@ -0,0 +1,288 @@ +#pragma once + +//convert any (supported) type to a const char* without constructing a new nall::string +//this is used inside string{...} to build nall::string values + +namespace nall { + +//booleans + +template<> struct stringify { + stringify(bool value) : _value(value) {} + auto data() const -> const char* { return _value ? "true" : "false"; } + auto size() const -> uint { return _value ? 4 : 5; } + bool _value; +}; + +template<> struct stringify { + stringify(bool value) : _value(value) {} + auto data() const -> const char* { return _value ? "true" : "false"; } + auto size() const -> uint { return _value ? 4 : 5; } + bool _value; +}; + +//characters + +template<> struct stringify { + stringify(char source) { _data[0] = source; _data[1] = 0; } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return 1; } + char _data[2]; +}; + +//signed integers + +template<> struct stringify { + stringify(signed char source) { fromInteger(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[2 + sizeof(signed char) * 3]; +}; + +template<> struct stringify { + stringify(signed short source) { fromInteger(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[2 + sizeof(signed short) * 3]; +}; + +template<> struct stringify { + stringify(signed int source) { fromInteger(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[2 + sizeof(signed int) * 3]; +}; + +template<> struct stringify { + stringify(signed long source) { fromInteger(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[2 + sizeof(signed long) * 3]; +}; + +template<> struct stringify { + stringify(signed long long source) { fromInteger(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[2 + sizeof(signed long long) * 3]; +}; + +#if defined(__SIZEOF_INT128__) +template<> struct stringify { + stringify(int128_t source) { fromInteger(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[2 + sizeof(int128_t) * 3]; +}; +#endif + +template struct stringify> { + stringify(Integer source) { fromInteger(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[2 + sizeof(int64_t) * 3]; +}; + +//unsigned integers + +template<> struct stringify { + stringify(unsigned char source) { fromNatural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[1 + sizeof(unsigned char) * 3]; +}; + +template<> struct stringify { + stringify(unsigned short source) { fromNatural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[1 + sizeof(unsigned short) * 3]; +}; + +template<> struct stringify { + stringify(unsigned int source) { fromNatural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[1 + sizeof(unsigned int) * 3]; +}; + +template<> struct stringify { + stringify(unsigned long source) { fromNatural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[1 + sizeof(unsigned long) * 3]; +}; + +template<> struct stringify { + stringify(unsigned long long source) { fromNatural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[1 + sizeof(unsigned long long) * 3]; +}; + +#if defined(__SIZEOF_INT128__) +template<> struct stringify { + stringify(uint128_t source) { fromNatural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[1 + sizeof(uint128_t) * 3]; +}; +#endif + +template struct stringify> { + stringify(Natural source) { fromNatural(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[1 + sizeof(uint64_t) * 3]; +}; + +//floating-point + +template<> struct stringify { + stringify(float source) { fromReal(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[256]; +}; + +template<> struct stringify { + stringify(double source) { fromReal(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[256]; +}; + +template<> struct stringify { + stringify(long double source) { fromReal(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[256]; +}; + +template struct stringify> { + stringify(Real source) { fromReal(_data, source); } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[256]; +}; + +//arrays + +template<> struct stringify> { + stringify(vector source) { + _text.resize(source.size()); + memory::copy(_text.data(), source.data(), source.size()); + } + auto data() const -> const char* { return _text.data(); } + auto size() const -> uint { return _text.size(); } + vector _text; +}; + +template<> struct stringify&> { + stringify(const vector& source) { + _text.resize(source.size()); + memory::copy(_text.data(), source.data(), source.size()); + } + auto data() const -> const char* { return _text.data(); } + auto size() const -> uint { return _text.size(); } + vector _text; +}; + +//char arrays + +template<> struct stringify { + stringify(char* source) : _data(source ? source : "") {} + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + const char* _data; +}; + +template<> struct stringify { + stringify(const char* source) : _data(source ? source : "") {} + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + const char* _data; +}; + +//strings + +template<> struct stringify { + stringify(const string& source) : _text(source) {} + auto data() const -> const char* { return _text.data(); } + auto size() const -> uint { return _text.size(); } + const string& _text; +}; + +template<> struct stringify { + stringify(const string& source) : _text(source) {} + auto data() const -> const char* { return _text.data(); } + auto size() const -> uint { return _text.size(); } + const string& _text; +}; + +template<> struct stringify { + stringify(const string_view& source) : _view(source) {} + auto data() const -> const char* { return _view.data(); } + auto size() const -> uint { return _view.size(); } + const string_view& _view; +}; + +template<> struct stringify { + stringify(const string_view& source) : _view(source) {} + auto data() const -> const char* { return _view.data(); } + auto size() const -> uint { return _view.size(); } + const string_view& _view; +}; + +template<> struct stringify> { + stringify(const array_view& source) : _view(source) {} + auto data() const -> const char* { return _view.data(); } + auto size() const -> uint { return _view.size(); } + const array_view& _view; +}; + +template<> struct stringify&> { + stringify(const array_view& source) : _view(source) {} + auto data() const -> const char* { return _view.data(); } + auto size() const -> uint { return _view.size(); } + const array_view& _view; +}; + +template<> struct stringify { + stringify(const string_pascal& source) : _text(source) {} + auto data() const -> const char* { return _text.data(); } + auto size() const -> uint { return _text.size(); } + const string_pascal& _text; +}; + +template<> struct stringify { + stringify(const string_pascal& source) : _text(source) {} + auto data() const -> const char* { return _text.data(); } + auto size() const -> uint { return _text.size(); } + const string_pascal& _text; +}; + +//pointers + +//note: T = char* is matched by stringify +template struct stringify { + stringify(const T* source) { + if(!source) { + memory::copy(_data, "(nullptr)", 10); + } else { + memory::copy(_data, "0x", 2); + fromNatural(_data + 2, (uintptr)source); + } + } + auto data() const -> const char* { return _data; } + auto size() const -> uint { return strlen(_data); } + char _data[256]; +}; + +// + +template auto make_string(T value) -> stringify { + return stringify(forward(value)); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/compare.hpp b/waterbox/bsnescore/bsnes/nall/string/compare.hpp new file mode 100644 index 0000000000..08479ba263 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/compare.hpp @@ -0,0 +1,58 @@ +#pragma once + +namespace nall { + +template +auto string::_compare(const char* target, uint capacity, const char* source, uint size) -> int { + if(Insensitive) return memory::icompare(target, capacity, source, size); + return memory::compare(target, capacity, source, size); +} + +//size() + 1 includes null-terminator; required to properly compare strings of differing lengths +auto string::compare(string_view x, string_view y) -> int { + return memory::compare(x.data(), x.size() + 1, y.data(), y.size() + 1); +} + +auto string::icompare(string_view x, string_view y) -> int { + return memory::icompare(x.data(), x.size() + 1, y.data(), y.size() + 1); +} + +auto string::compare(string_view source) const -> int { + return memory::compare(data(), size() + 1, source.data(), source.size() + 1); +} + +auto string::icompare(string_view source) const -> int { + return memory::icompare(data(), size() + 1, source.data(), source.size() + 1); +} + +auto string::equals(string_view source) const -> bool { + if(size() != source.size()) return false; + return memory::compare(data(), source.data(), source.size()) == 0; +} + +auto string::iequals(string_view source) const -> bool { + if(size() != source.size()) return false; + return memory::icompare(data(), source.data(), source.size()) == 0; +} + +auto string::beginsWith(string_view source) const -> bool { + if(source.size() > size()) return false; + return memory::compare(data(), source.data(), source.size()) == 0; +} + +auto string::ibeginsWith(string_view source) const -> bool { + if(source.size() > size()) return false; + return memory::icompare(data(), source.data(), source.size()) == 0; +} + +auto string::endsWith(string_view source) const -> bool { + if(source.size() > size()) return false; + return memory::compare(data() + size() - source.size(), source.data(), source.size()) == 0; +} + +auto string::iendsWith(string_view source) const -> bool { + if(source.size() > size()) return false; + return memory::icompare(data() + size() - source.size(), source.data(), source.size()) == 0; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/convert.hpp b/waterbox/bsnescore/bsnes/nall/string/convert.hpp new file mode 100644 index 0000000000..4c7d4abf18 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/convert.hpp @@ -0,0 +1,53 @@ +#pragma once + +namespace nall { + +auto string::downcase() -> string& { + char* p = get(); + for(uint n = 0; n < size(); n++) { + if(p[n] >= 'A' && p[n] <= 'Z') p[n] += 0x20; + } + return *this; +} + +auto string::qdowncase() -> string& { + char* p = get(); + for(uint n = 0, quoted = 0; n < size(); n++) { + if(p[n] == '\"') quoted ^= 1; + if(!quoted && p[n] >= 'A' && p[n] <= 'Z') p[n] += 0x20; + } + return *this; +} + +auto string::upcase() -> string& { + char* p = get(); + for(uint n = 0; n < size(); n++) { + if(p[n] >= 'a' && p[n] <= 'z') p[n] -= 0x20; + } + return *this; +} + +auto string::qupcase() -> string& { + char* p = get(); + for(uint n = 0, quoted = 0; n < size(); n++) { + if(p[n] == '\"') quoted ^= 1; + if(!quoted && p[n] >= 'a' && p[n] <= 'z') p[n] -= 0x20; + } + return *this; +} + +auto string::transform(string_view from, string_view to) -> string& { + if(from.size() != to.size() || from.size() == 0) return *this; //patterns must be the same length + char* p = get(); + for(uint n = 0; n < size(); n++) { + for(uint s = 0; s < from.size(); s++) { + if(p[n] == from[s]) { + p[n] = to[s]; + break; + } + } + } + return *this; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/core.hpp b/waterbox/bsnescore/bsnes/nall/string/core.hpp new file mode 100644 index 0000000000..9c5074d984 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/core.hpp @@ -0,0 +1,75 @@ +#pragma once + +//only allocators may access _data or modify _size and _capacity +//all other functions must use data(), size(), capacity() + +#if defined(NALL_STRING_ALLOCATOR_ADAPTIVE) + #include +#elif defined(NALL_STRING_ALLOCATOR_COPY_ON_WRITE) + #include +#elif defined(NALL_STRING_ALLOCATOR_SMALL_STRING_OPTIMIZATION) + #include +#elif defined(NALL_STRING_ALLOCATOR_VECTOR) + #include +#endif + +namespace nall { + +auto string::operator[](uint position) const -> const char& { + #ifdef DEBUG + struct out_of_bounds {}; + if(position >= size() + 1) throw out_of_bounds{}; + #endif + return data()[position]; +} + +auto string::operator()(uint position, char fallback) const -> char { + if(position >= size() + 1) return fallback; + return data()[position]; +} + +template auto string::assign(P&&... p) -> string& { + resize(0); + return append(forward

(p)...); +} + +template auto string::prepend(const T& value, P&&... p) -> string& { + if constexpr(sizeof...(p)) prepend(forward

(p)...); + return _prepend(make_string(value)); +} + +template auto string::prepend(const nall::string_format& value, P&&... p) -> string& { + if constexpr(sizeof...(p)) prepend(forward

(p)...); + return format(value); +} + +template auto string::_prepend(const stringify& source) -> string& { + resize(source.size() + size()); + memory::move(get() + source.size(), get(), size() - source.size()); + memory::copy(get(), source.data(), source.size()); + return *this; +} + +template auto string::append(const T& value, P&&... p) -> string& { + _append(make_string(value)); + if constexpr(sizeof...(p) > 0) append(forward

(p)...); + return *this; +} + +template auto string::append(const nall::string_format& value, P&&... p) -> string& { + format(value); + if constexpr(sizeof...(p)) append(forward

(p)...); + return *this; +} + +template auto string::_append(const stringify& source) -> string& { + resize(size() + source.size()); + memory::copy(get() + size() - source.size(), source.data(), source.size()); + return *this; +} + +auto string::length() const -> uint { + return strlen(data()); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/eval/evaluator.hpp b/waterbox/bsnescore/bsnes/nall/string/eval/evaluator.hpp new file mode 100644 index 0000000000..cecffd4048 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/eval/evaluator.hpp @@ -0,0 +1,146 @@ +#pragma once + +namespace nall::Eval { + +inline auto evaluateExpression(Node* node) -> string { + #define p(n) evaluateExpression(node->link[n]) + switch(node->type) { + case Node::Type::Null: return "Null"; + case Node::Type::Literal: return {"Literal:", node->literal}; + case Node::Type::Function: return {"Function(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Subscript: return {"Subscript(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Member: return {"Member(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::SuffixIncrement: return {"SuffixIncrement(0:", p(0), ")"}; + case Node::Type::SuffixDecrement: return {"SuffixDecrement(0:", p(0), ")"}; + case Node::Type::Reference: return {"Reference(0:", p(0), ")"}; + case Node::Type::Dereference: return {"Dereference(0:", p(0), ")"}; + case Node::Type::BitwiseNot: return {"Complement(0:", p(0), ")"}; + case Node::Type::PrefixIncrement: return {"PrefixIncrement(0:", p(0), ")"}; + case Node::Type::PrefixDecrement: return {"PrefixDecrement(0:", p(0), ")"}; + case Node::Type::Add: return {"Add(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Multiply: return {"Multiply(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Concatenate: return {"Concatenate(0:", p(0), ", ", p(1), ")"}; + case Node::Type::Coalesce: return {"Coalesce(0:", p(0), ", ", p(1), ")"}; + case Node::Type::Condition: return {"Condition(0:", p(0), ", ", p(1), ", ", p(2), ")"}; + case Node::Type::Assign: return {"Assign(0:", p(0), ", ", p(1), ")"}; + case Node::Type::Separator: { + string result = "Separator("; + for(auto& link : node->link) { + result.append(evaluateExpression(link), ", "); + } + return result.trimRight(", ", 1L).append(")"); + } + } + #undef p + + throw "invalid operator"; +} + +inline auto evaluateInteger(Node* node) -> int64_t { + if(node->type == Node::Type::Literal) return toInteger(node->literal); + + #define p(n) evaluateInteger(node->link[n]) + switch(node->type) { + case Node::Type::SuffixIncrement: return p(0); + case Node::Type::SuffixDecrement: return p(0); + case Node::Type::LogicalNot: return !p(0); + case Node::Type::BitwiseNot: return ~p(0); + case Node::Type::Positive: return +p(0); + case Node::Type::Negative: return -p(0); + case Node::Type::PrefixIncrement: return p(0) + 1; + case Node::Type::PrefixDecrement: return p(0) - 1; + case Node::Type::Multiply: return p(0) * p(1); + case Node::Type::Divide: return p(0) / p(1); + case Node::Type::Modulo: return p(0) % p(1); + case Node::Type::Add: return p(0) + p(1); + case Node::Type::Subtract: return p(0) - p(1); + case Node::Type::ShiftLeft: return p(0) << p(1); + case Node::Type::ShiftRight: return p(0) >> p(1); + case Node::Type::BitwiseAnd: return p(0) & p(1); + case Node::Type::BitwiseOr: return p(0) | p(1); + case Node::Type::BitwiseXor: return p(0) ^ p(1); + case Node::Type::Equal: return p(0) == p(1); + case Node::Type::NotEqual: return p(0) != p(1); + case Node::Type::LessThanEqual: return p(0) <= p(1); + case Node::Type::GreaterThanEqual: return p(0) >= p(1); + case Node::Type::LessThan: return p(0) < p(1); + case Node::Type::GreaterThan: return p(0) > p(1); + case Node::Type::LogicalAnd: return p(0) && p(1); + case Node::Type::LogicalOr: return p(0) || p(1); + case Node::Type::Condition: return p(0) ? p(1) : p(2); + case Node::Type::Assign: return p(1); + case Node::Type::AssignMultiply: return p(0) * p(1); + case Node::Type::AssignDivide: return p(0) / p(1); + case Node::Type::AssignModulo: return p(0) % p(1); + case Node::Type::AssignAdd: return p(0) + p(1); + case Node::Type::AssignSubtract: return p(0) - p(1); + case Node::Type::AssignShiftLeft: return p(0) << p(1); + case Node::Type::AssignShiftRight: return p(0) >> p(1); + case Node::Type::AssignBitwiseAnd: return p(0) & p(1); + case Node::Type::AssignBitwiseOr: return p(0) | p(1); + case Node::Type::AssignBitwiseXor: return p(0) ^ p(1); + } + #undef p + + throw "invalid operator"; +} + +inline auto integer(const string& expression) -> maybe { + try { + auto tree = new Node; + const char* p = expression; + parse(tree, p, 0); + auto result = evaluateInteger(tree); + delete tree; + return result; + } catch(const char*) { + return nothing; + } +} + +inline auto evaluateReal(Node* node) -> long double { + if(node->type == Node::Type::Literal) return toReal(node->literal); + + #define p(n) evaluateReal(node->link[n]) + switch(node->type) { + case Node::Type::LogicalNot: return !p(0); + case Node::Type::Positive: return +p(0); + case Node::Type::Negative: return -p(0); + case Node::Type::Multiply: return p(0) * p(1); + case Node::Type::Divide: return p(0) / p(1); + case Node::Type::Add: return p(0) + p(1); + case Node::Type::Subtract: return p(0) - p(1); + case Node::Type::Equal: return p(0) == p(1); + case Node::Type::NotEqual: return p(0) != p(1); + case Node::Type::LessThanEqual: return p(0) <= p(1); + case Node::Type::GreaterThanEqual: return p(0) >= p(1); + case Node::Type::LessThan: return p(0) < p(1); + case Node::Type::GreaterThan: return p(0) > p(1); + case Node::Type::LogicalAnd: return p(0) && p(1); + case Node::Type::LogicalOr: return p(0) || p(1); + case Node::Type::Condition: return p(0) ? p(1) : p(2); + case Node::Type::Assign: return p(1); + case Node::Type::AssignMultiply: return p(0) * p(1); + case Node::Type::AssignDivide: return p(0) / p(1); + case Node::Type::AssignAdd: return p(0) + p(1); + case Node::Type::AssignSubtract: return p(0) - p(1); + } + #undef p + + throw "invalid operator"; +} + +inline auto real(const string& expression) -> maybe { + try { + auto tree = new Node; + const char* p = expression; + parse(tree, p, 0); + auto result = evaluateReal(tree); + delete tree; + return result; + } catch(const char*) { + return nothing; + } +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/eval/literal.hpp b/waterbox/bsnescore/bsnes/nall/string/eval/literal.hpp new file mode 100644 index 0000000000..4c106fcf70 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/eval/literal.hpp @@ -0,0 +1,99 @@ +#pragma once + +namespace nall::Eval { + +inline auto isLiteral(const char*& s) -> bool { + char n = s[0]; + return (n >= 'A' && n <= 'Z') + || (n >= 'a' && n <= 'z') + || (n >= '0' && n <= '9') + || (n == '%' || n == '$' || n == '_' || n == '.') + || (n == '\'' || n == '\"'); +} + +inline auto literalNumber(const char*& s) -> string { + const char* p = s; + + //binary + if(p[0] == '%' || (p[0] == '0' && p[1] == 'b')) { + uint prefix = 1 + (p[0] == '0'); + p += prefix; + while(p[0] == '\'' || p[0] == '0' || p[0] == '1') p++; + if(p - s <= prefix) throw "invalid binary literal"; + string result = slice(s, 0, p - s); + s = p; + return result; + } + + //octal + if(p[0] == '0' && p[1] == 'o') { + uint prefix = 1 + (p[0] == '0'); + p += prefix; + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '7')) p++; + if(p - s <= prefix) throw "invalid octal literal"; + string result = slice(s, 0, p - s); + s = p; + return result; + } + + //hex + if(p[0] == '$' || (p[0] == '0' && p[1] == 'x')) { + uint prefix = 1 + (p[0] == '0'); + p += prefix; + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '9') || (p[0] >= 'A' && p[0] <= 'F') || (p[0] >= 'a' && p[0] <= 'f')) p++; + if(p - s <= prefix) throw "invalid hex literal"; + string result = slice(s, 0, p - s); + s = p; + return result; + } + + //decimal + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '9')) p++; + if(p[0] != '.') { + string result = slice(s, 0, p - s); + s = p; + return result; + } + + //floating-point + p++; + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '9')) p++; + string result = slice(s, 0, p - s); + s = p; + return result; +} + +inline auto literalString(const char*& s) -> string { + const char* p = s; + char escape = *p++; + + while(p[0] && p[0] != escape) p++; + if(*p++ != escape) throw "unclosed string literal"; + + string result = slice(s, 0, p - s); + s = p; + return result; +} + +inline auto literalVariable(const char*& s) -> string { + const char* p = s; + + while(p[0] == '_' || p[0] == '.' || (p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z') || (p[0] >= '0' && p[0] <= '9')) p++; + + string result = slice(s, 0, p - s); + s = p; + return result; +} + +inline auto literal(const char*& s) -> string { + const char* p = s; + + if(p[0] >= '0' && p[0] <= '9') return literalNumber(s); + if(p[0] == '%' || p[0] == '$') return literalNumber(s); + if(p[0] == '\'' || p[0] == '\"') return literalString(s); + if(p[0] == '_' || p[0] == '.' || (p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z')) return literalVariable(s); + + throw "invalid literal"; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/eval/node.hpp b/waterbox/bsnescore/bsnes/nall/string/eval/node.hpp new file mode 100644 index 0000000000..d02a928e9e --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/eval/node.hpp @@ -0,0 +1,37 @@ +#pragma once + +namespace nall::Eval { + +struct Node { + enum class Type : uint { + Null, + Literal, + Function, Subscript, Member, SuffixIncrement, SuffixDecrement, + Reference, Dereference, LogicalNot, BitwiseNot, Positive, Negative, PrefixIncrement, PrefixDecrement, + Multiply, Divide, Modulo, + Add, Subtract, + RotateLeft, RotateRight, ShiftLeft, ShiftRight, + BitwiseAnd, BitwiseOr, BitwiseXor, + Concatenate, + Equal, NotEqual, LessThanEqual, GreaterThanEqual, LessThan, GreaterThan, + LogicalAnd, LogicalOr, + Coalesce, Condition, + Assign, Create, //all assignment operators have the same precedence + AssignMultiply, AssignDivide, AssignModulo, + AssignAdd, AssignSubtract, + AssignRotateLeft, AssignRotateRight, AssignShiftLeft, AssignShiftRight, + AssignBitwiseAnd, AssignBitwiseOr, AssignBitwiseXor, + AssignConcatenate, + Separator, + }; + + Type type; + string literal; + vector link; + + Node() : type(Type::Null) {} + Node(Type type) : type(type) {} + ~Node() { for(auto& node : link) delete node; } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/eval/parser.hpp b/waterbox/bsnescore/bsnes/nall/string/eval/parser.hpp new file mode 100644 index 0000000000..f736d228b9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/eval/parser.hpp @@ -0,0 +1,164 @@ +#pragma once + +namespace nall::Eval { + +inline auto whitespace(char n) -> bool { + return n == ' ' || n == '\t' || n == '\r' || n == '\n'; +} + +inline auto parse(Node*& node, const char*& s, uint depth) -> void { + auto unaryPrefix = [&](Node::Type type, uint seek, uint depth) { + auto parent = new Node(type); + parse(parent->link(0) = new Node, s += seek, depth); + node = parent; + }; + + auto unarySuffix = [&](Node::Type type, uint seek, uint depth) { + auto parent = new Node(type); + parent->link(0) = node; + parse(parent, s += seek, depth); + node = parent; + }; + + auto binary = [&](Node::Type type, uint seek, uint depth) { + auto parent = new Node(type); + parent->link(0) = node; + parse(parent->link(1) = new Node, s += seek, depth); + node = parent; + }; + + auto ternary = [&](Node::Type type, uint seek, uint depth) { + auto parent = new Node(type); + parent->link(0) = node; + parse(parent->link(1) = new Node, s += seek, depth); + if(s[0] != ':') throw "mismatched ternary"; + parse(parent->link(2) = new Node, s += seek, depth); + node = parent; + }; + + auto separator = [&](Node::Type type, uint seek, uint depth) { + if(node->type != Node::Type::Separator) return binary(type, seek, depth); + uint n = node->link.size(); + parse(node->link(n) = new Node, s += seek, depth); + }; + + while(whitespace(s[0])) s++; + if(!s[0]) return; + + if(s[0] == '(' && !node->link) { + parse(node, s += 1, 1); + if(*s++ != ')') throw "mismatched group"; + } + + if(isLiteral(s)) { + node->type = Node::Type::Literal; + node->literal = literal(s); + } + + #define p() (!node->literal && !node->link) + while(true) { + while(whitespace(s[0])) s++; + if(!s[0]) return; + + if(depth >= 13) break; + if(s[0] == '(' && !p()) { + binary(Node::Type::Function, 1, 1); + if(*s++ != ')') throw "mismatched function"; + continue; + } + if(s[0] == '[') { + binary(Node::Type::Subscript, 1, 1); + if(*s++ != ']') throw "mismatched subscript"; + continue; + } + if(s[0] == '.') { binary(Node::Type::Member, 1, 13); continue; } + if(s[0] == '+' && s[1] == '+' && !p()) { unarySuffix(Node::Type::SuffixIncrement, 2, 13); continue; } + if(s[0] == '-' && s[1] == '-' && !p()) { unarySuffix(Node::Type::SuffixDecrement, 2, 13); continue; } + + if(s[0] == '&' && p()) { unaryPrefix(Node::Type::Reference, 1, 12); continue; } + if(s[0] == '*' && p()) { unaryPrefix(Node::Type::Dereference, 1, 12); continue; } + if(s[0] == '!' && p()) { unaryPrefix(Node::Type::LogicalNot, 1, 12); continue; } + if(s[0] == '~' && p()) { unaryPrefix(Node::Type::BitwiseNot, 1, 12); continue; } + if(s[0] == '+' && s[1] != '+' && p()) { unaryPrefix(Node::Type::Positive, 1, 12); continue; } + if(s[0] == '-' && s[1] != '-' && p()) { unaryPrefix(Node::Type::Negative, 1, 12); continue; } + if(s[0] == '+' && s[1] == '+' && p()) { unaryPrefix(Node::Type::PrefixIncrement, 2, 12); continue; } + if(s[0] == '-' && s[1] == '-' && p()) { unaryPrefix(Node::Type::PrefixDecrement, 2, 12); continue; } + if(depth >= 12) break; + + if(depth >= 11) break; + if(s[0] == '*' && s[1] != '=') { binary(Node::Type::Multiply, 1, 11); continue; } + if(s[0] == '/' && s[1] != '=') { binary(Node::Type::Divide, 1, 11); continue; } + if(s[0] == '%' && s[1] != '=') { binary(Node::Type::Modulo, 1, 11); continue; } + + if(depth >= 10) break; + if(s[0] == '+' && s[1] != '=') { binary(Node::Type::Add, 1, 10); continue; } + if(s[0] == '-' && s[1] != '=') { binary(Node::Type::Subtract, 1, 10); continue; } + + if(depth >= 9) break; + if(s[0] == '<' && s[1] == '<' && s[2] == '<' && s[3] != '=') { binary(Node::Type::RotateLeft, 3, 9); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] == '>' && s[3] != '=') { binary(Node::Type::RotateRight, 3, 9); continue; } + if(s[0] == '<' && s[1] == '<' && s[2] != '=') { binary(Node::Type::ShiftLeft, 2, 9); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] != '=') { binary(Node::Type::ShiftRight, 2, 9); continue; } + + if(depth >= 8) break; + if(s[0] == '&' && s[1] != '&' && s[1] != '=') { binary(Node::Type::BitwiseAnd, 1, 8); continue; } + if(s[0] == '|' && s[1] != '|' && s[1] != '=') { binary(Node::Type::BitwiseOr, 1, 8); continue; } + if(s[0] == '^' && s[1] != '^' && s[1] != '=') { binary(Node::Type::BitwiseXor, 1, 8); continue; } + + if(depth >= 7) break; + if(s[0] == '~' && s[1] != '=') { binary(Node::Type::Concatenate, 1, 7); continue; } + + if(depth >= 6) break; + if(s[0] == '=' && s[1] == '=') { binary(Node::Type::Equal, 2, 6); continue; } + if(s[0] == '!' && s[1] == '=') { binary(Node::Type::NotEqual, 2, 6); continue; } + if(s[0] == '<' && s[1] == '=') { binary(Node::Type::LessThanEqual, 2, 6); continue; } + if(s[0] == '>' && s[1] == '=') { binary(Node::Type::GreaterThanEqual, 2, 6); continue; } + if(s[0] == '<') { binary(Node::Type::LessThan, 1, 6); continue; } + if(s[0] == '>') { binary(Node::Type::GreaterThan, 1, 6); continue; } + + if(depth >= 5) break; + if(s[0] == '&' && s[1] == '&') { binary(Node::Type::LogicalAnd, 2, 5); continue; } + if(s[0] == '|' && s[1] == '|') { binary(Node::Type::LogicalOr, 2, 5); continue; } + + if(s[0] == '?' && s[1] == '?') { binary(Node::Type::Coalesce, 2, 4); continue; } + if(s[0] == '?' && s[1] != '?') { ternary(Node::Type::Condition, 1, 4); continue; } + if(depth >= 4) break; + + if(s[0] == '=') { binary(Node::Type::Assign, 1, 3); continue; } + if(s[0] == ':' && s[1] == '=') { binary(Node::Type::Create, 2, 3); continue; } + if(s[0] == '*' && s[1] == '=') { binary(Node::Type::AssignMultiply, 2, 3); continue; } + if(s[0] == '/' && s[1] == '=') { binary(Node::Type::AssignDivide, 2, 3); continue; } + if(s[0] == '%' && s[1] == '=') { binary(Node::Type::AssignModulo, 2, 3); continue; } + if(s[0] == '+' && s[1] == '=') { binary(Node::Type::AssignAdd, 2, 3); continue; } + if(s[0] == '-' && s[1] == '=') { binary(Node::Type::AssignSubtract, 2, 3); continue; } + if(s[0] == '<' && s[1] == '<' && s[2] == '<' && s[3] == '=') { binary(Node::Type::AssignRotateLeft, 4, 3); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] == '>' && s[3] == '=') { binary(Node::Type::AssignRotateRight, 4, 3); continue; } + if(s[0] == '<' && s[1] == '<' && s[2] == '=') { binary(Node::Type::AssignShiftLeft, 3, 3); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] == '=') { binary(Node::Type::AssignShiftRight, 3, 3); continue; } + if(s[0] == '&' && s[1] == '=') { binary(Node::Type::AssignBitwiseAnd, 2, 3); continue; } + if(s[0] == '|' && s[1] == '=') { binary(Node::Type::AssignBitwiseOr, 2, 3); continue; } + if(s[0] == '^' && s[1] == '=') { binary(Node::Type::AssignBitwiseXor, 2, 3); continue; } + if(s[0] == '~' && s[1] == '=') { binary(Node::Type::AssignConcatenate, 2, 3); continue; } + if(depth >= 3) break; + + if(depth >= 2) break; + if(s[0] == ',') { separator(Node::Type::Separator, 1, 2); continue; } + + if(depth >= 1 && (s[0] == ')' || s[0] == ']')) break; + + while(whitespace(s[0])) s++; + if(!s[0]) break; + + throw "unrecognized terminal"; + } + #undef p +} + +inline auto parse(const string& expression) -> Node* { + auto result = new Node; + const char* p = expression; + parse(result, p, 0); + return result; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/find.hpp b/waterbox/bsnescore/bsnes/nall/string/find.hpp new file mode 100644 index 0000000000..c9c4442179 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/find.hpp @@ -0,0 +1,65 @@ +#pragma once + +namespace nall { + +auto string::contains(string_view characters) const -> maybe { + for(uint x : range(size())) { + for(char y : characters) { + if(operator[](x) == y) return x; + } + } + return nothing; +} + +template auto string::_find(int offset, string_view source) const -> maybe { + if(source.size() == 0) return nothing; + auto p = data(); + for(uint n = offset, quoted = 0; n < size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(_compare(p + n, size() - n, source.data(), source.size())) { n++; continue; } + return n - offset; + } + return nothing; +} + +auto string::find(string_view source) const -> maybe { return _find<0, 0>(0, source); } +auto string::ifind(string_view source) const -> maybe { return _find<1, 0>(0, source); } +auto string::qfind(string_view source) const -> maybe { return _find<0, 1>(0, source); } +auto string::iqfind(string_view source) const -> maybe { return _find<1, 1>(0, source); } + +auto string::findFrom(int offset, string_view source) const -> maybe { return _find<0, 0>(offset, source); } +auto string::ifindFrom(int offset, string_view source) const -> maybe { return _find<1, 0>(offset, source); } + +auto string::findNext(int offset, string_view source) const -> maybe { + if(source.size() == 0) return nothing; + for(int n = offset + 1; n < size(); n++) { + if(memory::compare(data() + n, size() - n, source.data(), source.size()) == 0) return n; + } + return nothing; +} + +auto string::ifindNext(int offset, string_view source) const -> maybe { + if(source.size() == 0) return nothing; + for(int n = offset + 1; n < size(); n++) { + if(memory::icompare(data() + n, size() - n, source.data(), source.size()) == 0) return n; + } + return nothing; +} + +auto string::findPrevious(int offset, string_view source) const -> maybe { + if(source.size() == 0) return nothing; + for(int n = offset - 1; n >= 0; n--) { + if(memory::compare(data() + n, size() - n, source.data(), source.size()) == 0) return n; + } + return nothing; +} + +auto string::ifindPrevious(int offset, string_view source) const -> maybe { + if(source.size() == 0) return nothing; + for(int n = offset - 1; n >= 0; n--) { + if(memory::icompare(data() + n, size() - n, source.data(), source.size()) == 0) return n; + } + return nothing; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/format.hpp b/waterbox/bsnescore/bsnes/nall/string/format.hpp new file mode 100644 index 0000000000..7af01ed01f --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/format.hpp @@ -0,0 +1,138 @@ +#pragma once + +namespace nall { + +//nall::format is a vector of parameters that can be applied to a string +//each {#} token will be replaced with its appropriate format parameter + +auto string::format(const nall::string_format& params) -> type& { + auto size = (int)this->size(); + auto data = memory::allocate(size); + memory::copy(data, this->data(), size); + + int x = 0; + while(x < size - 2) { //2 = minimum tag length + if(data[x] != '{') { x++; continue; } + + int y = x + 1; + while(y < size - 1) { //-1 avoids going out of bounds on test after this loop + if(data[y] != '}') { y++; continue; } + break; + } + + if(data[y++] != '}') { x++; continue; } + + static auto isNumeric = [](char* s, char* e) -> bool { + if(s == e) return false; //ignore empty tags: {} + while(s < e) { + if(*s >= '0' && *s <= '9') { s++; continue; } + return false; + } + return true; + }; + if(!isNumeric(&data[x + 1], &data[y - 1])) { x++; continue; } + + uint index = toNatural(&data[x + 1]); + if(index >= params.size()) { x++; continue; } + + uint sourceSize = y - x; + uint targetSize = params[index].size(); + uint remaining = size - x; + + if(sourceSize > targetSize) { + uint difference = sourceSize - targetSize; + memory::move(&data[x], &data[x + difference], remaining - difference); + size -= difference; + } else if(targetSize > sourceSize) { + uint difference = targetSize - sourceSize; + data = (char*)realloc(data, size + difference); + size += difference; + memory::move(&data[x + difference], &data[x], remaining); + } + memory::copy(&data[x], params[index].data(), targetSize); + x += targetSize; + } + + resize(size); + memory::copy(get(), data, size); + memory::free(data); + return *this; +} + +template auto string_format::append(const T& value, P&&... p) -> string_format& { + vector::append(value); + return append(forward

(p)...); +} + +auto string_format::append() -> string_format& { + return *this; +} + +template auto print(P&&... p) -> void { + string s{forward

(p)...}; + fwrite(s.data(), 1, s.size(), stdout); + fflush(stdout); +} + +template auto print(FILE* fp, P&&... p) -> void { + string s{forward

(p)...}; + fwrite(s.data(), 1, s.size(), fp); + if(fp == stdout || fp == stderr) fflush(fp); +} + +template auto pad(const T& value, long precision, char padchar) -> string { + string buffer{value}; + if(precision) buffer.size(precision, padchar); + return buffer; +} + +auto hex(uintmax value, long precision, char padchar) -> string { + string buffer; + buffer.resize(sizeof(uintmax) * 2); + char* p = buffer.get(); + + uint size = 0; + do { + uint n = value & 15; + p[size++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + buffer.resize(size); + buffer.reverse(); + if(precision) buffer.size(precision, padchar); + return buffer; +} + +auto octal(uintmax value, long precision, char padchar) -> string { + string buffer; + buffer.resize(sizeof(uintmax) * 3); + char* p = buffer.get(); + + uint size = 0; + do { + p[size++] = '0' + (value & 7); + value >>= 3; + } while(value); + buffer.resize(size); + buffer.reverse(); + if(precision) buffer.size(precision, padchar); + return buffer; +} + +auto binary(uintmax value, long precision, char padchar) -> string { + string buffer; + buffer.resize(sizeof(uintmax) * 8); + char* p = buffer.get(); + + uint size = 0; + do { + p[size++] = '0' + (value & 1); + value >>= 1; + } while(value); + buffer.resize(size); + buffer.reverse(); + if(precision) buffer.size(precision, padchar); + return buffer; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/markup/bml.hpp b/waterbox/bsnescore/bsnes/nall/string/markup/bml.hpp new file mode 100644 index 0000000000..3bf05ddf68 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/markup/bml.hpp @@ -0,0 +1,189 @@ +#pragma once + +//BML v1.0 parser +//revision 0.04 + +namespace nall::BML { + +//metadata is used to store nesting level + +struct ManagedNode; +using SharedNode = shared_pointer; + +struct ManagedNode : Markup::ManagedNode { +protected: + //test to verify if a valid character for a node name + auto valid(char p) const -> bool { //A-Z, a-z, 0-9, -. + return p - 'A' < 26u || p - 'a' < 26u || p - '0' < 10u || p - '-' < 2u; + } + + //determine indentation level, without incrementing pointer + auto readDepth(const char* p) -> uint { + uint depth = 0; + while(p[depth] == '\t' || p[depth] == ' ') depth++; + return depth; + } + + //determine indentation level + auto parseDepth(const char*& p) -> uint { + uint depth = readDepth(p); + p += depth; + return depth; + } + + //read name + auto parseName(const char*& p) -> void { + uint length = 0; + while(valid(p[length])) length++; + if(length == 0) throw "Invalid node name"; + _name = slice(p, 0, length); + p += length; + } + + auto parseData(const char*& p, string_view spacing) -> void { + if(*p == '=' && *(p + 1) == '\"') { + uint length = 2; + while(p[length] && p[length] != '\n' && p[length] != '\"') length++; + if(p[length] != '\"') throw "Unescaped value"; + _value = {slice(p, 2, length - 2), "\n"}; + p += length + 1; + } else if(*p == '=') { + uint length = 1; + while(p[length] && p[length] != '\n' && p[length] != '\"' && p[length] != ' ') length++; + if(p[length] == '\"') throw "Illegal character in value"; + _value = {slice(p, 1, length - 1), "\n"}; + p += length; + } else if(*p == ':') { + uint length = 1; + while(p[length] && p[length] != '\n') length++; + _value = {slice(p, 1, length - 1).trimLeft(spacing, 1L), "\n"}; + p += length; + } + } + + //read all attributes for a node + auto parseAttributes(const char*& p, string_view spacing) -> void { + while(*p && *p != '\n') { + if(*p != ' ') throw "Invalid node name"; + while(*p == ' ') p++; //skip excess spaces + if(*(p + 0) == '/' && *(p + 1) == '/') break; //skip comments + + SharedNode node(new ManagedNode); + uint length = 0; + while(valid(p[length])) length++; + if(length == 0) throw "Invalid attribute name"; + node->_name = slice(p, 0, length); + node->parseData(p += length, spacing); + node->_value.trimRight("\n", 1L); + _children.append(node); + } + } + + //read a node and all of its child nodes + auto parseNode(const vector& text, uint& y, string_view spacing) -> void { + const char* p = text[y++]; + _metadata = parseDepth(p); + parseName(p); + parseData(p, spacing); + parseAttributes(p, spacing); + + while(y < text.size()) { + uint depth = readDepth(text[y]); + if(depth <= _metadata) break; + + if(text[y][depth] == ':') { + _value.append(slice(text[y++], depth + 1).trimLeft(spacing, 1L), "\n"); + continue; + } + + SharedNode node(new ManagedNode); + node->parseNode(text, y, spacing); + _children.append(node); + } + + _value.trimRight("\n", 1L); + } + + //read top-level nodes + auto parse(string document, string_view spacing) -> void { + //in order to simplify the parsing logic; we do an initial pass to normalize the data + //the below code will turn '\r\n' into '\n'; skip empty lines; and skip comment lines + char* p = document.get(), *output = p; + while(*p) { + char* origin = p; + bool empty = true; + while(*p) { + //scan for first non-whitespace character. if it's a line feed or comment; skip the line + if(p[0] == ' ' || p[0] == '\t') { p++; continue; } + empty = p[0] == '\r' || p[0] == '\n' || (p[0] == '/' && p[1] == '/'); + break; + } + while(*p) { + if(p[0] == '\r') p[0] = '\n'; //turns '\r\n' into '\n\n' (second '\n' will be skipped) + if(*p++ == '\n') break; //include '\n' in the output to be copied + } + if(empty) continue; + + memory::move(output, origin, p - origin); + output += p - origin; + } + document.resize(document.size() - (p - output)).trimRight("\n"); + if(document.size() == 0) return; //empty document + + auto text = document.split("\n"); + uint y = 0; + while(y < text.size()) { + SharedNode node(new ManagedNode); + node->parseNode(text, y, spacing); + if(node->_metadata > 0) throw "Root nodes cannot be indented"; + _children.append(node); + } + } + + friend auto unserialize(const string&, string_view) -> Markup::Node; +}; + +inline auto unserialize(const string& markup, string_view spacing = {}) -> Markup::Node { + SharedNode node(new ManagedNode); + try { + node->parse(markup, spacing); + } catch(const char* error) { + node.reset(); + } + return (Markup::SharedNode&)node; +} + +inline auto serialize(const Markup::Node& node, string_view spacing = {}, uint depth = 0) -> string { + if(!node.name()) { + string result; + for(auto leaf : node) { + result.append(serialize(leaf, spacing, depth)); + } + return result; + } + + string padding; + padding.resize(depth * 2); + padding.fill(' '); + + vector lines; + if(auto value = node.value()) lines = value.split("\n"); + + string result; + result.append(padding); + result.append(node.name()); + if(lines.size() == 1) result.append(":", spacing, lines[0]); + result.append("\n"); + if(lines.size() > 1) { + padding.append(" "); + for(auto& line : lines) { + result.append(padding, ":", spacing, line, "\n"); + } + } + for(auto leaf : node) { + result.append(serialize(leaf, spacing, depth + 1)); + } + return result; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/markup/find.hpp b/waterbox/bsnescore/bsnes/nall/string/markup/find.hpp new file mode 100644 index 0000000000..0901b0d19e --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/markup/find.hpp @@ -0,0 +1,137 @@ +#pragma once + +namespace nall::Markup { + +auto ManagedNode::_evaluate(string query) const -> bool { + if(!query) return true; + + for(auto& rule : query.split(",")) { + enum class Comparator : uint { ID, EQ, NE, LT, LE, GT, GE }; + auto comparator = Comparator::ID; + if(rule.match("*!=*")) comparator = Comparator::NE; + else if(rule.match("*<=*")) comparator = Comparator::LE; + else if(rule.match("*>=*")) comparator = Comparator::GE; + else if(rule.match ("*=*")) comparator = Comparator::EQ; + else if(rule.match ("*<*")) comparator = Comparator::LT; + else if(rule.match ("*>*")) comparator = Comparator::GT; + + if(comparator == Comparator::ID) { + if(_find(rule).size()) continue; + return false; + } + + vector side; + switch(comparator) { + case Comparator::EQ: side = rule.split ("=", 1L); break; + case Comparator::NE: side = rule.split("!=", 1L); break; + case Comparator::LT: side = rule.split ("<", 1L); break; + case Comparator::LE: side = rule.split("<=", 1L); break; + case Comparator::GT: side = rule.split (">", 1L); break; + case Comparator::GE: side = rule.split(">=", 1L); break; + } + + string data = string{_value}.strip(); + if(side(0)) { + auto result = _find(side(0)); + if(result.size() == 0) return false; + data = result[0].text(); //strips whitespace so rules can match without requiring it + } + + switch(comparator) { + case Comparator::EQ: if(data.match(side(1)) == true) continue; break; + case Comparator::NE: if(data.match(side(1)) == false) continue; break; + case Comparator::LT: if(data.natural() < side(1).natural()) continue; break; + case Comparator::LE: if(data.natural() <= side(1).natural()) continue; break; + case Comparator::GT: if(data.natural() > side(1).natural()) continue; break; + case Comparator::GE: if(data.natural() >= side(1).natural()) continue; break; + } + + return false; + } + + return true; +} + +auto ManagedNode::_find(const string& query) const -> vector { + vector result; + + auto path = query.split("/"); + string name = path.take(0), rule; + uint lo = 0u, hi = ~0u; + + if(name.match("*[*]")) { + auto p = name.trimRight("]", 1L).split("[", 1L); + name = p(0); + if(p(1).find("-")) { + p = p(1).split("-", 1L); + lo = !p(0) ? 0u : p(0).natural(); + hi = !p(1) ? ~0u : p(1).natural(); + } else { + lo = hi = p(1).natural(); + } + } + + if(name.match("*(*)")) { + auto p = name.trimRight(")", 1L).split("(", 1L); + name = p(0); + rule = p(1); + } + + uint position = 0; + for(auto& node : _children) { + if(!node->_name.match(name)) continue; + if(!node->_evaluate(rule)) continue; + + bool inrange = position >= lo && position <= hi; + position++; + if(!inrange) continue; + + if(path.size() == 0) { + result.append(node); + } else for(auto& item : node->_find(path.merge("/"))) { + result.append(item); + } + } + + return result; +} + +//operator[](string) +auto ManagedNode::_lookup(const string& path) const -> Node { + auto result = _find(path); + return result ? result[0] : Node{}; + +/*//faster, but cannot search + if(auto position = path.find("/")) { + auto name = slice(path, 0, *position); + for(auto& node : _children) { + if(name == node->_name) { + return node->_lookup(slice(path, *position + 1)); + } + } + } else for(auto& node : _children) { + if(path == node->_name) return node; + } + return {}; +*/ +} + +auto ManagedNode::_create(const string& path) -> Node { + if(auto position = path.find("/")) { + auto name = slice(path, 0, *position); + for(auto& node : _children) { + if(name == node->_name) { + return node->_create(slice(path, *position + 1)); + } + } + _children.append(new ManagedNode(name)); + return _children.right()->_create(slice(path, *position + 1)); + } + for(auto& node : _children) { + if(path == node->_name) return node; + } + _children.append(new ManagedNode(path)); + return _children.right(); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/markup/node.hpp b/waterbox/bsnescore/bsnes/nall/string/markup/node.hpp new file mode 100644 index 0000000000..528d0a0389 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/markup/node.hpp @@ -0,0 +1,147 @@ +#pragma once + +namespace nall::Markup { + +struct Node; +struct ManagedNode; +using SharedNode = shared_pointer; + +struct ManagedNode { + ManagedNode() = default; + ManagedNode(const string& name) : _name(name) {} + ManagedNode(const string& name, const string& value) : _name(name), _value(value) {} + + auto clone() const -> SharedNode { + SharedNode clone{new ManagedNode(_name, _value)}; + for(auto& child : _children) { + clone->_children.append(child->clone()); + } + return clone; + } + + auto copy(SharedNode source) -> void { + _name = source->_name; + _value = source->_value; + _metadata = source->_metadata; + _children.reset(); + for(auto child : source->_children) { + _children.append(child->clone()); + } + } + +protected: + string _name; + string _value; + uintptr _metadata = 0; + vector _children; + + inline auto _evaluate(string query) const -> bool; + inline auto _find(const string& query) const -> vector; + inline auto _lookup(const string& path) const -> Node; + inline auto _create(const string& path) -> Node; + + friend class Node; +}; + +struct Node { + Node() : shared(new ManagedNode) {} + Node(const SharedNode& source) : shared(source ? source : new ManagedNode) {} + Node(const nall::string& name) : shared(new ManagedNode(name)) {} + Node(const nall::string& name, const nall::string& value) : shared(new ManagedNode(name, value)) {} + + auto unique() const -> bool { return shared.unique(); } + auto clone() const -> Node { return shared->clone(); } + auto copy(Node source) -> void { return shared->copy(source.shared); } + + explicit operator bool() const { return shared->_name || shared->_children; } + auto name() const -> nall::string { return shared->_name; } + auto value() const -> nall::string { return shared->_value; } + + auto value(nall::string& target) const -> bool { if(shared) target = string(); return (bool)shared; } + auto value(bool& target) const -> bool { if(shared) target = boolean(); return (bool)shared; } + auto value(int& target) const -> bool { if(shared) target = integer(); return (bool)shared; } + auto value(uint& target) const -> bool { if(shared) target = natural(); return (bool)shared; } + auto value(double& target) const -> bool { if(shared) target = real(); return (bool)shared; } + + auto text() const -> nall::string { return value().strip(); } + auto string() const -> nall::string { return value().strip(); } + auto boolean() const -> bool { return text() == "true"; } + auto integer() const -> int64_t { return text().integer(); } + auto natural() const -> uint64_t { return text().natural(); } + auto real() const -> double { return text().real(); } + + auto text(const nall::string& fallback) const -> nall::string { return bool(*this) ? text() : fallback; } + auto string(const nall::string& fallback) const -> nall::string { return bool(*this) ? string() : fallback; } + auto boolean(bool fallback) const -> bool { return bool(*this) ? boolean() : fallback; } + auto integer(int64_t fallback) const -> int64_t { return bool(*this) ? integer() : fallback; } + auto natural(uint64_t fallback) const -> uint64_t { return bool(*this) ? natural() : fallback; } + auto real(double fallback) const -> double { return bool(*this) ? real() : fallback; } + + auto setName(const nall::string& name = "") -> Node& { shared->_name = name; return *this; } + auto setValue(const nall::string& value = "") -> Node& { shared->_value = value; return *this; } + + auto reset() -> void { shared->_children.reset(); } + auto size() const -> uint { return shared->_children.size(); } + + auto prepend(const Node& node) -> void { shared->_children.prepend(node.shared); } + auto append(const Node& node) -> void { shared->_children.append(node.shared); } + auto remove(const Node& node) -> bool { + for(auto n : range(size())) { + if(node.shared == shared->_children[n]) { + return shared->_children.remove(n), true; + } + } + return false; + } + + auto insert(uint position, const Node& node) -> bool { + if(position > size()) return false; //used > instead of >= to allow indexed-equivalent of append() + return shared->_children.insert(position, node.shared), true; + } + + auto remove(uint position) -> bool { + if(position >= size()) return false; + return shared->_children.remove(position), true; + } + + auto swap(uint x, uint y) -> bool { + if(x >= size() || y >= size()) return false; + return std::swap(shared->_children[x], shared->_children[y]), true; + } + + auto sort(function comparator = [](auto x, auto y) { + return nall::string::compare(x.shared->_name, y.shared->_name) < 0; + }) -> void { + nall::sort(shared->_children.data(), shared->_children.size(), [&](auto x, auto y) { + return comparator(x, y); //this call converts SharedNode objects to Node objects + }); + } + + auto operator[](int position) -> Node { + if(position >= size()) return {}; + return shared->_children[position]; + } + + auto operator[](const nall::string& path) const -> Node { return shared->_lookup(path); } + auto operator()(const nall::string& path) -> Node { return shared->_create(path); } + auto find(const nall::string& query) const -> vector { return shared->_find(query); } + + struct iterator { + auto operator*() -> Node { return {source.shared->_children[position]}; } + auto operator!=(const iterator& source) const -> bool { return position != source.position; } + auto operator++() -> iterator& { return position++, *this; } + iterator(const Node& source, uint position) : source(source), position(position) {} + + private: + const Node& source; + uint position; + }; + + auto begin() const -> iterator { return iterator(*this, 0); } + auto end() const -> iterator { return iterator(*this, size()); } + +protected: + SharedNode shared; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/markup/xml.hpp b/waterbox/bsnescore/bsnes/nall/string/markup/xml.hpp new file mode 100644 index 0000000000..27109101df --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/markup/xml.hpp @@ -0,0 +1,217 @@ +#pragma once + +//XML v1.0 subset parser +//revision 0.04 + +namespace nall::XML { + +//metadata: +// 0 = element +// 1 = attribute + +struct ManagedNode; +using SharedNode = shared_pointer; + +struct ManagedNode : Markup::ManagedNode { +protected: + inline string escape() const { + string result = _value; + result.replace("&", "&"); + result.replace("<", "<"); + result.replace(">", ">"); + if(_metadata == 1) { + result.replace("\'", "'"); + result.replace("\"", """); + } + return result; + } + + inline bool isName(char c) const { + if(c >= 'A' && c <= 'Z') return true; + if(c >= 'a' && c <= 'z') return true; + if(c >= '0' && c <= '9') return true; + if(c == '.' || c == '_') return true; + if(c == '?') return true; + return false; + } + + inline bool isWhitespace(char c) const { + if(c == ' ' || c == '\t') return true; + if(c == '\r' || c == '\n') return true; + return false; + } + + //copy part of string from source document into target string; decode markup while copying + inline void copy(string& target, const char* source, uint length) { + target.reserve(length + 1); + + #if defined(NALL_XML_LITERAL) + memory::copy(target.pointer(), source, length); + target[length] = 0; + return; + #endif + + char* output = target.get(); + while(length) { + if(*source == '&') { + if(!memory::compare(source, "<", 4)) { *output++ = '<'; source += 4; length -= 4; continue; } + if(!memory::compare(source, ">", 4)) { *output++ = '>'; source += 4; length -= 4; continue; } + if(!memory::compare(source, "&", 5)) { *output++ = '&'; source += 5; length -= 5; continue; } + if(!memory::compare(source, "'", 6)) { *output++ = '\''; source += 6; length -= 6; continue; } + if(!memory::compare(source, """, 6)) { *output++ = '\"'; source += 6; length -= 6; continue; } + } + + if(_metadata == 0 && source[0] == '<' && source[1] == '!') { + //comment + if(!memory::compare(source, "", 3)) source++, length--; + source += 3, length -= 3; + continue; + } + + //CDATA + if(!memory::compare(source, "", 3)) *output++ = *source++, length--; + source += 3, length -= 3; + continue; + } + } + + *output++ = *source++, length--; + } + *output = 0; + } + + inline bool parseExpression(const char*& p) { + if(*(p + 1) != '!') return false; + + //comment + if(!memory::compare(p, "", 3)) p++; + if(!*p) throw "unclosed comment"; + p += 3; + return true; + } + + //CDATA + if(!memory::compare(p, "", 3)) p++; + if(!*p) throw "unclosed CDATA"; + p += 3; + return true; + } + + //DOCTYPE + if(!memory::compare(p, "') counter--; + } while(counter); + return true; + } + + return false; + } + + //returns true if tag closes itself (); false if not () + inline bool parseHead(const char*& p) { + //parse name + const char* nameStart = ++p; //skip '<' + while(isName(*p)) p++; + const char* nameEnd = p; + copy(_name, nameStart, nameEnd - nameStart); + if(!_name) throw "missing element name"; + + //parse attributes + while(*p) { + while(isWhitespace(*p)) p++; + if(!*p) throw "unclosed attribute"; + if(*p == '?' || *p == '/' || *p == '>') break; + + //parse attribute name + SharedNode attribute(new ManagedNode); + attribute->_metadata = 1; + + const char* nameStart = p; + while(isName(*p)) p++; + const char* nameEnd = p; + copy(attribute->_name, nameStart, nameEnd - nameStart); + if(!attribute->_name) throw "missing attribute name"; + + //parse attribute data + if(*p++ != '=') throw "missing attribute value"; + char terminal = *p++; + if(terminal != '\'' && terminal != '\"') throw "attribute value not quoted"; + const char* dataStart = p; + while(*p && *p != terminal) p++; + if(!*p) throw "missing attribute data terminal"; + const char* dataEnd = p++; //skip closing terminal + + copy(attribute->_value, dataStart, dataEnd - dataStart); + _children.append(attribute); + } + + //parse closure + if(*p == '?' && *(p + 1) == '>') { p += 2; return true; } + if(*p == '/' && *(p + 1) == '>') { p += 2; return true; } + if(*p == '>') { p += 1; return false; } + throw "invalid element tag"; + } + + //parse element and all of its child elements + inline void parseElement(const char*& p) { + SharedNode node(new ManagedNode); + if(node->parseHead(p) == false) node->parse(p); + _children.append(node); + } + + //return true if matches this node's name + inline bool parseClosureElement(const char*& p) { + if(p[0] != '<' || p[1] != '/') return false; + p += 2; + const char* nameStart = p; + while(*p && *p != '>') p++; + if(*p != '>') throw "unclosed closure element"; + const char* nameEnd = p++; + if(memory::compare(_name.data(), nameStart, nameEnd - nameStart)) throw "closure element name mismatch"; + return true; + } + + //parse contents of an element + inline void parse(const char*& p) { + const char* dataStart = p; + const char* dataEnd = p; + + while(*p) { + while(*p && *p != '<') p++; + if(!*p) break; + dataEnd = p; + if(parseClosureElement(p) == true) break; + if(parseExpression(p) == true) continue; + parseElement(p); + } + + copy(_value, dataStart, dataEnd - dataStart); + } + + friend auto unserialize(const string&) -> Markup::SharedNode; +}; + +inline auto unserialize(const string& markup) -> Markup::SharedNode { + auto node = new ManagedNode; + try { + const char* p = markup; + node->parse(p); + } catch(const char* error) { + delete node; + node = nullptr; + } + return node; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/match.hpp b/waterbox/bsnescore/bsnes/nall/string/match.hpp new file mode 100644 index 0000000000..c1f79695a5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/match.hpp @@ -0,0 +1,90 @@ +#pragma once + +namespace nall { + +//todo: these functions are not binary-safe + +auto string::match(string_view source) const -> bool { + const char* s = data(); + const char* p = source.data(); + + const char* cp = nullptr; + const char* mp = nullptr; + while(*s && *p != '*') { + if(*p != '?' && *s != *p) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || *p == *s) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +auto string::imatch(string_view source) const -> bool { + static auto chrlower = [](char c) -> char { + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; + }; + + const char* s = data(); + const char* p = source.data(); + + const char* cp = nullptr; + const char* mp = nullptr; + while(*s && *p != '*') { + if(*p != '?' && chrlower(*s) != chrlower(*p)) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || chrlower(*p) == chrlower(*s)) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +auto tokenize(const char* s, const char* p) -> bool { + while(*s) { + if(*p == '*') { + while(*s) if(tokenize(s++, p + 1)) return true; + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') p++; + return !*p; +} + +auto tokenize(vector& list, const char* s, const char* p) -> bool { + while(*s) { + if(*p == '*') { + const char* b = s; + while(*s) { + if(tokenize(list, s++, p + 1)) { + list.prepend(slice(b, 0, --s - b)); + return true; + } + } + list.prepend(b); + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') { list.prepend(s); p++; } + return !*p; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/pascal.hpp b/waterbox/bsnescore/bsnes/nall/string/pascal.hpp new file mode 100644 index 0000000000..cadfe76782 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/pascal.hpp @@ -0,0 +1,79 @@ +#pragma once + +namespace nall { + +struct string_pascal { + using type = string_pascal; + + string_pascal(const char* text = nullptr) { + if(text && *text) { + uint size = strlen(text); + _data = memory::allocate(sizeof(uint) + size + 1); + ((uint*)_data)[0] = size; + memory::copy(_data + sizeof(uint), text, size); + _data[sizeof(uint) + size] = 0; + } + } + + string_pascal(const string& text) { + if(text.size()) { + _data = memory::allocate(sizeof(uint) + text.size() + 1); + ((uint*)_data)[0] = text.size(); + memory::copy(_data + sizeof(uint), text.data(), text.size()); + _data[sizeof(uint) + text.size()] = 0; + } + } + + string_pascal(const string_pascal& source) { operator=(source); } + string_pascal(string_pascal&& source) { operator=(move(source)); } + + ~string_pascal() { + if(_data) memory::free(_data); + } + + explicit operator bool() const { return _data; } + operator const char*() const { return _data ? _data + sizeof(uint) : nullptr; } + operator string() const { return _data ? string{_data + sizeof(uint)} : ""; } + + auto operator=(const string_pascal& source) -> type& { + if(this == &source) return *this; + if(_data) { memory::free(_data); _data = nullptr; } + if(source._data) { + uint size = source.size(); + _data = memory::allocate(sizeof(uint) + size); + memory::copy(_data, source._data, sizeof(uint) + size); + } + return *this; + } + + auto operator=(string_pascal&& source) -> type& { + if(this == &source) return *this; + if(_data) memory::free(_data); + _data = source._data; + source._data = nullptr; + return *this; + } + + auto operator==(string_view source) const -> bool { + return size() == source.size() && memory::compare(data(), source.data(), size()) == 0; + } + + auto operator!=(string_view source) const -> bool { + return size() != source.size() || memory::compare(data(), source.data(), size()) != 0; + } + + auto data() const -> char* { + if(!_data) return nullptr; + return _data + sizeof(uint); + } + + auto size() const -> uint { + if(!_data) return 0; + return ((uint*)_data)[0]; + } + +protected: + char* _data = nullptr; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/replace.hpp b/waterbox/bsnescore/bsnes/nall/string/replace.hpp new file mode 100644 index 0000000000..d8143a15fc --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/replace.hpp @@ -0,0 +1,94 @@ +#pragma once + +namespace nall { + +template +auto string::_replace(string_view from, string_view to, long limit) -> string& { + if(limit <= 0 || from.size() == 0) return *this; + + int size = this->size(); + int matches = 0; + int quoted = 0; + + //count matches first, so that we only need to reallocate memory once + //(recording matches would also require memory allocation, so this is not done) + { const char* p = data(); + for(int n = 0; n <= size - (int)from.size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(_compare(p + n, size - n, from.data(), from.size())) { n++; continue; } + + if(++matches >= limit) break; + n += from.size(); + } + } + if(matches == 0) return *this; + + //in-place overwrite + if(to.size() == from.size()) { + char* p = get(); + + for(int n = 0, remaining = matches, quoted = 0; n <= size - (int)from.size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(_compare(p + n, size - n, from.data(), from.size())) { n++; continue; } + + memory::copy(p + n, to.data(), to.size()); + + if(!--remaining) break; + n += from.size(); + } + } + + //left-to-right shrink + else if(to.size() < from.size()) { + char* p = get(); + int offset = 0; + int base = 0; + + for(int n = 0, remaining = matches, quoted = 0; n <= size - (int)from.size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(_compare(p + n, size - n, from.data(), from.size())) { n++; continue; } + + if(offset) memory::move(p + offset, p + base, n - base); + memory::copy(p + offset + (n - base), to.data(), to.size()); + offset += (n - base) + to.size(); + + n += from.size(); + base = n; + if(!--remaining) break; + } + + memory::move(p + offset, p + base, size - base); + resize(size - matches * (from.size() - to.size())); + } + + //right-to-left expand + else if(to.size() > from.size()) { + resize(size + matches * (to.size() - from.size())); + char* p = get(); + + int offset = this->size(); + int base = size; + + for(int n = size, remaining = matches; n >= (int)from.size();) { //quoted reused from parent scope since we are iterating backward + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n--; continue; } if(quoted) { n--; continue; } } + if(_compare(p + n - from.size(), size - n + from.size(), from.data(), from.size())) { n--; continue; } + + memory::move(p + offset - (base - n), p + base - (base - n), base - n); + memory::copy(p + offset - (base - n) - to.size(), to.data(), to.size()); + offset -= (base - n) + to.size(); + + if(!--remaining) break; + n -= from.size(); + base = n; + } + } + + return *this; +} + +auto string::replace(string_view from, string_view to, long limit) -> string& { return _replace<0, 0>(from, to, limit); } +auto string::ireplace(string_view from, string_view to, long limit) -> string& { return _replace<1, 0>(from, to, limit); } +auto string::qreplace(string_view from, string_view to, long limit) -> string& { return _replace<0, 1>(from, to, limit); } +auto string::iqreplace(string_view from, string_view to, long limit) -> string& { return _replace<1, 1>(from, to, limit); } + +}; diff --git a/waterbox/bsnescore/bsnes/nall/string/split.hpp b/waterbox/bsnescore/bsnes/nall/string/split.hpp new file mode 100644 index 0000000000..0ceba68b9a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/split.hpp @@ -0,0 +1,41 @@ +#pragma once + +namespace nall { + +template +auto vector::_split(string_view source, string_view find, long limit) -> type& { + reset(); + if(limit <= 0 || find.size() == 0) return *this; + + const char* p = source.data(); + int size = source.size(); + int base = 0; + int matches = 0; + + for(int n = 0, quoted = 0; n <= size - (int)find.size();) { + if(Quoted) { if(p[n] == '\"') { quoted ^= 1; n++; continue; } if(quoted) { n++; continue; } } + if(string::_compare(p + n, size - n, find.data(), find.size())) { n++; continue; } + if(matches >= limit) break; + + string& s = operator()(matches); + s.resize(n - base); + memory::copy(s.get(), p + base, n - base); + + n += find.size(); + base = n; + matches++; + } + + string& s = operator()(matches); + s.resize(size - base); + memory::copy(s.get(), p + base, size - base); + + return *this; +} + +auto string::split(string_view on, long limit) const -> vector { return vector()._split<0, 0>(*this, on, limit); } +auto string::isplit(string_view on, long limit) const -> vector { return vector()._split<1, 0>(*this, on, limit); } +auto string::qsplit(string_view on, long limit) const -> vector { return vector()._split<0, 1>(*this, on, limit); } +auto string::iqsplit(string_view on, long limit) const -> vector { return vector()._split<1, 1>(*this, on, limit); } + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/transform/cml.hpp b/waterbox/bsnescore/bsnes/nall/string/transform/cml.hpp new file mode 100644 index 0000000000..1e3cf803c8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/transform/cml.hpp @@ -0,0 +1,120 @@ +#pragma once + +/* CSS Markup Language (CML) v1.0 parser + * revision 0.02 + */ + +#include + +namespace nall { + +struct CML { + auto& setPath(const string& pathname) { settings.path = pathname; return *this; } + auto& setReader(const function& reader) { settings.reader = reader; return *this; } + + auto parse(const string& filename) -> string; + auto parse(const string& filedata, const string& pathname) -> string; + +private: + struct Settings { + string path; + function reader; + } settings; + + struct State { + string output; + } state; + + struct Variable { + string name; + string value; + }; + vector variables; + bool inMedia = false; + bool inMediaNode = false; + + auto parseDocument(const string& filedata, const string& pathname, uint depth) -> bool; +}; + +inline auto CML::parse(const string& filename) -> string { + if(!settings.path) settings.path = Location::path(filename); + string document = settings.reader ? settings.reader(filename) : string::read(filename); + parseDocument(document, settings.path, 0); + return state.output; +} + +inline auto CML::parse(const string& filedata, const string& pathname) -> string { + settings.path = pathname; + parseDocument(filedata, settings.path, 0); + return state.output; +} + +inline auto CML::parseDocument(const string& filedata, const string& pathname, uint depth) -> bool { + if(depth >= 100) return false; //prevent infinite recursion + + auto vendorAppend = [&](const string& name, const string& value) { + state.output.append(" -moz-", name, ": ", value, ";\n"); + state.output.append(" -webkit-", name, ": ", value, ";\n"); + }; + + for(auto& block : filedata.split("\n\n")) { + auto lines = block.stripRight().split("\n"); + auto name = lines.takeFirst(); + + if(name.beginsWith("include ")) { + name.trimLeft("include ", 1L); + string filename{pathname, name}; + string document = settings.reader ? settings.reader(filename) : string::read(filename); + parseDocument(document, Location::path(filename), depth + 1); + continue; + } + + if(name == "variables") { + for(auto& line : lines) { + auto data = line.split(":", 1L).strip(); + variables.append({data(0), data(1)}); + } + continue; + } + + state.output.append(name, " {\n"); + inMedia = name.beginsWith("@media"); + + for(auto& line : lines) { + if(inMedia && !line.find(": ")) { + if(inMediaNode) state.output.append(" }\n"); + state.output.append(line, " {\n"); + inMediaNode = true; + continue; + } + + auto data = line.split(":", 1L).strip(); + auto name = data(0), value = data(1); + while(auto offset = value.find("var(")) { + bool found = false; + if(auto length = value.findFrom(*offset, ")")) { + string name = slice(value, *offset + 4, *length - 4); + for(auto& variable : variables) { + if(variable.name == name) { + value = {slice(value, 0, *offset), variable.value, slice(value, *offset + *length + 1)}; + found = true; + break; + } + } + } + if(!found) break; + } + state.output.append(inMedia ? " " : " ", name, ": ", value, ";\n"); + if(name == "box-sizing") vendorAppend(name, value); + } + if(inMediaNode) { + state.output.append(" }\n"); + inMediaNode = false; + } + state.output.append("}\n\n"); + } + + return true; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/transform/dml.hpp b/waterbox/bsnescore/bsnes/nall/string/transform/dml.hpp new file mode 100644 index 0000000000..d5061355a8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/transform/dml.hpp @@ -0,0 +1,346 @@ +#pragma once + +/* Document Markup Language (DML) v1.0 parser + * revision 0.05 + */ + +#include + +namespace nall { + +struct DML { + auto content() const -> string { return state.output; } + + auto& setAllowHTML(bool allowHTML) { settings.allowHTML = allowHTML; return *this; } + auto& setHost(const string& hostname) { settings.host = hostname; return *this; } + auto& setPath(const string& pathname) { settings.path = pathname; return *this; } + auto& setReader(const function& reader) { settings.reader = reader; return *this; } + + auto parse(const string& filedata, const string& pathname) -> string; + auto parse(const string& filename) -> string; + + auto attribute(const string& name) const -> string; + +private: + struct Settings { + bool allowHTML = true; + string host = "localhost"; + string path; + function reader; + } settings; + + struct State { + string output; + } state; + + struct Attribute { + string name; + string value; + }; + vector attributes; + + auto parseDocument(const string& filedata, const string& pathname, uint depth) -> bool; + auto parseBlock(string& block, const string& pathname, uint depth) -> bool; + auto count(const string& text, char value) -> uint; + + auto address(string text) -> string; + auto escape(const string& text) -> string; + auto markup(const string& text) -> string; +}; + +inline auto DML::attribute(const string& name) const -> string { + for(auto& attribute : attributes) { + if(attribute.name == name) return attribute.value; + } + return {}; +} + +inline auto DML::parse(const string& filedata, const string& pathname) -> string { + state = {}; + settings.path = pathname; + parseDocument(filedata, settings.path, 0); + return state.output; +} + +inline auto DML::parse(const string& filename) -> string { + state = {}; + if(!settings.path) settings.path = Location::path(filename); + string document = settings.reader ? settings.reader(filename) : string::read(filename); + parseDocument(document, settings.path, 0); + return state.output; +} + +inline auto DML::parseDocument(const string& filedata, const string& pathname, uint depth) -> bool { + if(depth >= 100) return false; //attempt to prevent infinite recursion with reasonable limit + + auto blocks = filedata.split("\n\n"); + for(auto& block : blocks) parseBlock(block, pathname, depth); + return true; +} + +inline auto DML::parseBlock(string& block, const string& pathname, uint depth) -> bool { + if(!block.stripRight()) return true; + auto lines = block.split("\n"); + + //include + if(block.beginsWith("")) { + string filename{pathname, block.trim("", 1L).strip()}; + string document = settings.reader ? settings.reader(filename) : string::read(filename); + parseDocument(document, Location::path(filename), depth + 1); + } + + //attribute + else if(block.beginsWith("? ")) { + for(auto n : range(lines.size())) { + if(!lines[n].beginsWith("? ")) continue; + auto part = lines[n].trimLeft("? ", 1L).split(":", 1L); + if(part.size() != 2) continue; + auto name = part[0].strip(); + auto value = part[1].strip(); + attributes.append({name, value}); + } + } + + //html + else if(block.beginsWith("\n") && settings.allowHTML) { + for(auto n : range(lines.size())) { + if(n == 0 || !lines[n].beginsWith(" ")) continue; + state.output.append(lines[n].trimLeft(" ", 1L), "\n"); + } + } + + //header + else if(auto depth = count(block, '#')) { + auto content = slice(lines.takeLeft(), depth + 1).split("::", 1L).strip(); + auto data = markup(content[0]); + auto name = escape(content(1, data.hash())); + if(depth <= 5) { + state.output.append("", data); + for(auto& line : lines) { + if(count(line, '#') != depth) continue; + state.output.append("", slice(line, depth + 1), ""); + } + state.output.append("\n"); + } + } + + //navigation + else if(count(block, '-')) { + state.output.append("

\n"); + } + + //list + else if(count(block, '*')) { + uint level = 0; + for(auto& line : lines) { + if(auto depth = count(line, '*')) { + while(level < depth) level++, state.output.append("
    \n"); + while(level > depth) level--, state.output.append("
\n"); + auto data = markup(slice(line, depth + 1)); + state.output.append("
  • ", data, "
  • \n"); + } + } + while(level--) state.output.append("\n"); + } + + //quote + else if(count(block, '>')) { + uint level = 0; + for(auto& line : lines) { + if(auto depth = count(line, '>')) { + while(level < depth) level++, state.output.append("
    \n"); + while(level > depth) level--, state.output.append("
    \n"); + auto data = markup(slice(line, depth + 1)); + state.output.append(data, "\n"); + } + } + while(level--) state.output.append("\n"); + } + + //code + else if(block.beginsWith(" ")) { + state.output.append("
    ");
    +    for(auto& line : lines) {
    +      if(!line.beginsWith("  ")) continue;
    +      state.output.append(escape(line.trimLeft("  ", 1L)), "\n");
    +    }
    +    state.output.trimRight("\n", 1L).append("
    \n"); + } + + //divider + else if(block.equals("---")) { + state.output.append("
    \n"); + } + + //paragraph + else { + auto content = markup(block); + if(content.beginsWith("")) { + state.output.append(content, "\n"); + } else { + state.output.append("

    ", content, "

    \n"); + } + } + + return true; +} + +inline auto DML::count(const string& text, char value) -> uint { + for(uint n = 0; n < text.size(); n++) { + if(text[n] != value) { + if(text[n] == ' ') return n; + break; + } + } + return 0; +} + +// . => domain +// ./* => domain/* +// ../subdomain => subdomain.domain +// ../subdomain/* => subdomain.domain/* +inline auto DML::address(string s) -> string { + if(s.beginsWith("../")) { + s.trimLeft("../", 1L); + if(auto p = s.find("/")) { + return {"//", s.slice(0, *p), ".", settings.host, s.slice(*p)}; + } else { + return {"//", s, ".", settings.host}; + } + } + if(s.beginsWith("./")) { + s.trimLeft(".", 1L); + return {"//", settings.host, s}; + } + if(s == ".") { + return {"//", settings.host}; + } + return s; +} + +inline auto DML::escape(const string& text) -> string { + string output; + for(auto c : text) { + if(c == '&') { output.append("&"); continue; } + if(c == '<') { output.append("<"); continue; } + if(c == '>') { output.append(">"); continue; } + if(c == '"') { output.append("""); continue; } + output.append(c); + } + return output; +} + +inline auto DML::markup(const string& s) -> string { + string t; + + boolean strong; + boolean emphasis; + boolean insertion; + boolean deletion; + boolean code; + + maybe link; + maybe image; + + for(uint n = 0; n < s.size();) { + char a = s[n]; + char b = s[n + 1]; + + if(!link && !image) { + if(a == '*' && b == '*') { t.append(strong.flip() ? "" : ""); n += 2; continue; } + if(a == '/' && b == '/') { t.append(emphasis.flip() ? "" : ""); n += 2; continue; } + if(a == '_' && b == '_') { t.append(insertion.flip() ? "" : ""); n += 2; continue; } + if(a == '~' && b == '~') { t.append(deletion.flip() ? "" : ""); n += 2; continue; } + if(a == '|' && b == '|') { t.append(code.flip() ? "" : ""); n += 2; continue; } + if(a =='\\' && b =='\\') { t.append("
    "); n += 2; continue; } + + if(a == '[' && b == '[') { n += 2; link = n; continue; } + if(a == '{' && b == '{') { n += 2; image = n; continue; } + } + + if(link && !image && a == ']' && b == ']') { + auto list = slice(s, link(), n - link()).split("::", 1L); + string uri = address(list.last()); + string name = list.size() == 2 ? list.first() : uri.split("//", 1L).last(); + + t.append("", escape(name), ""); + + n += 2; + link = nothing; + continue; + } + + if(image && !link && a == '}' && b == '}') { + auto side = slice(s, image(), n - image()).split("}{", 1L); + auto list = side(0).split("::", 1L); + string uri = address(list.last()); + string name = list.size() == 2 ? list.first() : uri.split("//", 1L).last(); + list = side(1).split("; "); + boolean link, title, caption; + string width, height; + for(auto p : list) { + if(p == "link") { link = true; continue; } + if(p == "title") { title = true; continue; } + if(p == "caption") { caption = true; continue; } + if(p.beginsWith("width:")) { p.trimLeft("width:", 1L); width = p.strip(); continue; } + if(p.beginsWith("height:")) { p.trimLeft("height:", 1L); height = p.strip(); continue; } + } + + if(caption) { + t.append("
    \n"); + if(link) t.append(""); + t.append("\"",\n"); + if(link) t.append("\n"); + t.append("
    ", escape(name), "
    \n"); + t.append("
    "); + } else { + if(link) t.append(""); + t.append("\"","); + if(link) t.append(""); + } + + n += 2; + image = nothing; + continue; + } + + if(link || image) { n++; continue; } + if(a =='\\') { t.append(b); n += 2; continue; } + if(a == '&') { t.append("&"); n++; continue; } + if(a == '<') { t.append("<"); n++; continue; } + if(a == '>') { t.append(">"); n++; continue; } + if(a == '"') { t.append("""); n++; continue; } + t.append(a); n++; continue; + } + + if(strong) t.append(""); + if(emphasis) t.append(""); + if(insertion) t.append(""); + if(deletion) t.append(""); + if(code) t.append("
    "); + + return t; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/trim.hpp b/waterbox/bsnescore/bsnes/nall/string/trim.hpp new file mode 100644 index 0000000000..17823a7da7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/trim.hpp @@ -0,0 +1,102 @@ +#pragma once + +namespace nall { + +auto string::trim(string_view lhs, string_view rhs, long limit) -> string& { + trimRight(rhs, limit); + trimLeft(lhs, limit); + return *this; +} + +auto string::trimLeft(string_view lhs, long limit) -> string& { + if(lhs.size() == 0) return *this; + long matches = 0; + while(matches < limit) { + int offset = lhs.size() * matches; + int length = (int)size() - offset; + if(length < (int)lhs.size()) break; + if(memory::compare(data() + offset, lhs.data(), lhs.size()) != 0) break; + matches++; + } + if(matches) remove(0, lhs.size() * matches); + return *this; +} + +auto string::trimRight(string_view rhs, long limit) -> string& { + if(rhs.size() == 0) return *this; + long matches = 0; + while(matches < limit) { + int offset = (int)size() - rhs.size() * (matches + 1); + int length = (int)size() - offset; + if(offset < 0 || length < (int)rhs.size()) break; + if(memory::compare(data() + offset, rhs.data(), rhs.size()) != 0) break; + matches++; + } + if(matches) resize(size() - rhs.size() * matches); + return *this; +} + +auto string::itrim(string_view lhs, string_view rhs, long limit) -> string& { + itrimRight(rhs, limit); + itrimLeft(lhs, limit); + return *this; +} + +auto string::itrimLeft(string_view lhs, long limit) -> string& { + if(lhs.size() == 0) return *this; + long matches = 0; + while(matches < limit) { + int offset = lhs.size() * matches; + int length = (int)size() - offset; + if(length < (int)lhs.size()) break; + if(memory::icompare(data() + offset, lhs.data(), lhs.size()) != 0) break; + matches++; + } + if(matches) remove(0, lhs.size() * matches); + return *this; +} + +auto string::itrimRight(string_view rhs, long limit) -> string& { + if(rhs.size() == 0) return *this; + long matches = 0; + while(matches < limit) { + int offset = (int)size() - rhs.size() * (matches + 1); + int length = (int)size() - offset; + if(offset < 0 || length < (int)rhs.size()) break; + if(memory::icompare(data() + offset, rhs.data(), rhs.size()) != 0) break; + matches++; + } + if(matches) resize(size() - rhs.size() * matches); + return *this; +} + +auto string::strip() -> string& { + stripRight(); + stripLeft(); + return *this; +} + +auto string::stripLeft() -> string& { + uint length = 0; + while(length < size()) { + char input = operator[](length); + if(input != ' ' && input != '\t' && input != '\r' && input != '\n') break; + length++; + } + if(length) remove(0, length); + return *this; +} + +auto string::stripRight() -> string& { + uint length = 0; + while(length < size()) { + bool matched = false; + char input = operator[](size() - length - 1); + if(input != ' ' && input != '\t' && input != '\r' && input != '\n') break; + length++; + } + if(length) resize(size() - length); + return *this; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/utf8.hpp b/waterbox/bsnescore/bsnes/nall/string/utf8.hpp new file mode 100644 index 0000000000..5b04bec678 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/utf8.hpp @@ -0,0 +1,32 @@ +#pragma once + +namespace nall { + +//note: this function assumes the string contains valid UTF-8 characters +//invalid characters will result in an incorrect result from this function +//invalid case 1: byte 1 == 0b'01xxxxxx +//invalid case 2: bytes 2-4 != 0b'10xxxxxx +//invalid case 3: end of string without bytes 2-4 present +auto characters(string_view self, int offset, int length) -> uint { + uint characters = 0; + if(offset < 0) offset = self.size() - abs(offset); + if(offset >= 0 && offset < self.size()) { + if(length < 0) length = self.size() - offset; + if(length >= 0) { + for(int index = offset; index < offset + length;) { + auto byte = self.data()[index++]; + if((byte & 0b111'00000) == 0b110'00000) index += 1; + if((byte & 0b1111'0000) == 0b1110'0000) index += 2; + if((byte & 0b11111'000) == 0b11110'000) index += 3; + characters++; + } + } + } + return characters; +} + +auto string::characters(int offset, int length) const -> uint { + return nall::characters(*this, offset, length); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/utility.hpp b/waterbox/bsnescore/bsnes/nall/string/utility.hpp new file mode 100644 index 0000000000..4f11fd5451 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/utility.hpp @@ -0,0 +1,165 @@ +#pragma once + +namespace nall { + +auto string::read(string_view filename) -> string { + #if !defined(_WIN32) + FILE* fp = fopen(filename, "rb"); + #else + FILE* fp = _wfopen(utf16_t(filename), L"rb"); + #endif + + string result; + if(!fp) return result; + + fseek(fp, 0, SEEK_END); + int filesize = ftell(fp); + if(filesize < 0) return fclose(fp), result; + + rewind(fp); + result.resize(filesize); + (void)fread(result.get(), 1, filesize, fp); + return fclose(fp), result; +} + +auto string::repeat(string_view pattern, uint times) -> string { + string result; + while(times--) result.append(pattern.data()); + return result; +} + +auto string::fill(char fill) -> string& { + memory::fill(get(), size(), fill); + return *this; +} + +auto string::hash() const -> uint { + const char* p = data(); + uint length = size(); + uint result = 5381; + while(length--) result = (result << 5) + result + *p++; + return result; +} + +auto string::remove(uint offset, uint length) -> string& { + char* p = get(); + length = min(length, size()); + memory::move(p + offset, p + offset + length, size() - length); + return resize(size() - length); +} + +auto string::reverse() -> string& { + char* p = get(); + uint length = size(); + uint pivot = length >> 1; + for(int x = 0, y = length - 1; x < pivot && y >= 0; x++, y--) std::swap(p[x], p[y]); + return *this; +} + +//+length => insert/delete from start (right justify) +//-length => insert/delete from end (left justify) +auto string::size(int length, char fill) -> string& { + uint size = this->size(); + if(size == length) return *this; + + bool right = length >= 0; + length = abs(length); + + if(size < length) { //expand + resize(length); + char* p = get(); + uint displacement = length - size; + if(right) memory::move(p + displacement, p, size); + else p += size; + while(displacement--) *p++ = fill; + } else { //shrink + char* p = get(); + uint displacement = size - length; + if(right) memory::move(p, p + displacement, length); + resize(length); + } + + return *this; +} + +auto slice(string_view self, int offset, int length) -> string { + string result; + if(offset < 0) offset = self.size() - abs(offset); + if(offset >= 0 && offset < self.size()) { + if(length < 0) length = self.size() - offset; + if(length >= 0) { + result.resize(length); + memory::copy(result.get(), self.data() + offset, length); + } + } + return result; +} + +auto string::slice(int offset, int length) const -> string { + return nall::slice(*this, offset, length); +} + +template auto fromInteger(char* result, T value) -> char* { + bool negative = value < 0; + if(!negative) value = -value; //negate positive integers to support eg INT_MIN + + char buffer[1 + sizeof(T) * 3]; + uint size = 0; + + do { + int n = value % 10; //-0 to -9 + buffer[size++] = '0' - n; //'0' to '9' + value /= 10; + } while(value); + if(negative) buffer[size++] = '-'; + + for(int x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +template auto fromNatural(char* result, T value) -> char* { + char buffer[1 + sizeof(T) * 3]; + uint size = 0; + + do { + uint n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + + for(int x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +//using sprintf is certainly not the most ideal method to convert +//a double to a string ... but attempting to parse a double by +//hand, digit-by-digit, results in subtle rounding errors. +template auto fromReal(char* result, T value) -> uint { + char buffer[256]; + #ifdef _WIN32 + //Windows C-runtime does not support long double via sprintf() + sprintf(buffer, "%f", (double)value); + #else + sprintf(buffer, "%Lf", (long double)value); + #endif + + //remove excess 0's in fraction (2.500000 -> 2.5) + for(char* p = buffer; *p; p++) { + if(*p == '.') { + char* p = buffer + strlen(buffer) - 1; + while(*p == '0') { + if(*(p - 1) != '.') *p = 0; //... but not for eg 1.0 -> 1. + p--; + } + break; + } + } + + uint length = strlen(buffer); + if(result) strcpy(result, buffer); + return length + 1; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/vector.hpp b/waterbox/bsnescore/bsnes/nall/string/vector.hpp new file mode 100644 index 0000000000..31f206359e --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/vector.hpp @@ -0,0 +1,60 @@ +#pragma once + +namespace nall { + +template auto vector::append(const string& data, P&&... p) -> type& { + vector_base::append(data); + append(forward

    (p)...); + return *this; +} + +auto vector::append() -> type& { + return *this; +} + +auto vector::isort() -> type& { + sort([](const string& x, const string& y) { + return memory::icompare(x.data(), x.size(), y.data(), y.size()) < 0; + }); + return *this; +} + +auto vector::find(string_view source) const -> maybe { + for(uint n = 0; n < size(); n++) { + if(operator[](n).equals(source)) return n; + } + return {}; +} + +auto vector::ifind(string_view source) const -> maybe { + for(uint n = 0; n < size(); n++) { + if(operator[](n).iequals(source)) return n; + } + return {}; +} + +auto vector::match(string_view pattern) const -> vector { + vector result; + for(uint n = 0; n < size(); n++) { + if(operator[](n).match(pattern)) result.append(operator[](n)); + } + return result; +} + +auto vector::merge(string_view separator) const -> string { + string output; + for(uint n = 0; n < size(); n++) { + output.append(operator[](n)); + if(n < size() - 1) output.append(separator.data()); + } + return output; +} + +auto vector::strip() -> type& { + for(uint n = 0; n < size(); n++) { + operator[](n).strip(); + } + return *this; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/string/view.hpp b/waterbox/bsnescore/bsnes/nall/string/view.hpp new file mode 100644 index 0000000000..fe56b9a9a4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/string/view.hpp @@ -0,0 +1,89 @@ +#pragma once + +namespace nall { + +string_view::string_view() { + _string = nullptr; + _data = ""; + _size = 0; +} + +string_view::string_view(const string_view& source) { + if(this == &source) return; + _string = nullptr; + _data = source._data; + _size = source._size; +} + +string_view::string_view(string_view&& source) { + if(this == &source) return; + _string = source._string; + _data = source._data; + _size = source._size; + source._string = nullptr; +} + +string_view::string_view(const char* data) { + _string = nullptr; + _data = data; + _size = -1; //defer length calculation, as it is often unnecessary +} + +string_view::string_view(const char* data, uint size) { + _string = nullptr; + _data = data; + _size = size; +} + +string_view::string_view(const string& source) { + _string = nullptr; + _data = source.data(); + _size = source.size(); +} + +template +string_view::string_view(P&&... p) { + _string = new string{forward

    (p)...}; + _data = _string->data(); + _size = _string->size(); +} + +string_view::~string_view() { + if(_string) delete _string; +} + +auto string_view::operator=(const string_view& source) -> type& { + if(this == &source) return *this; + _string = nullptr; + _data = source._data; + _size = source._size; + return *this; +}; + +auto string_view::operator=(string_view&& source) -> type& { + if(this == &source) return *this; + _string = source._string; + _data = source._data; + _size = source._size; + source._string = nullptr; + return *this; +}; + +string_view::operator bool() const { + return _size > 0; +} + +string_view::operator const char*() const { + return _data; +} + +auto string_view::data() const -> const char* { + return _data; +} + +auto string_view::size() const -> uint { + if(_size < 0) _size = strlen(_data); + return _size; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/suffix-array.hpp b/waterbox/bsnescore/bsnes/nall/suffix-array.hpp new file mode 100644 index 0000000000..4cc33ea42a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/suffix-array.hpp @@ -0,0 +1,386 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace nall { + +/* + +input: + data = "acaacatat" + 0 "acaacatat" + 1 "caacatat" + 2 "aacatat" + 3 "acatat" + 4 "catat" + 5 "atat" + 6 "tat" + 7 "at" + 8 "t" + 9 "" + +suffix_array: + suffixes = [9,2,0,3,7,5,1,4,8,6] => input + suffixes: + 9 "" + 2 "aacatat" + 0 "acaacatat" + 3 "acatat" + 7 "at" + 5 "atat" + 1 "caacatat" + 4 "catat" + 8 "t" + 6 "tat" + +[auxiliary data structures to represent information lost from suffix trees] + +suffix_array_invert: + inverted = [2,6,1,3,7,5,9,4,8,0] => input + suffixes[inverted]: + 2 "acaacatat" + 6 "caacatat" + 1 "aacatat" + 3 "acatat" + 7 "catat" + 5 "atat" + 9 "tat" + 4 "at" + 8 "t" + 0 "" + +suffix_array_phi: + phi = [2,5,9,0,1,7,8,3,4,0] + +suffix_array_lcp: + prefixes = [0,0,1,3,1,2,0,2,0,1] => lcp[n] == lcp(n, n-1) + "" 0 + "aacatat" 0 + "acaacatat" 1 "a" + "acatat" 3 "aca" + "at" 1 "a" + "atat" 2 "at" + "caacatat" 0 + "catat" 2 "ca" + "t" 0 + "tat" 1 "t" + +suffix_array_plcp: + plcp = [1,0,0,3,2,2,1,1,0,0] + +suffix_array_lrcp: + llcp = [0,0,0,3,0,2,0,2,0,0] => llcp[m] == lcp(l, m) + rlcp = [0,1,1,1,0,0,0,0,0,0] => rlcp[m] == lcp(m, r) + +suffix_array_lpf: + lengths = [0,0,1,3,2,1,0,2,1,0] + offsets = [0,0,0,0,1,3,4,5,6,2] + "acaacatat" (0,-) + "caacatat" (0,-) + "aacatat" (1,0) at 0, match "a" + "acatat" (3,0) at 0, match "aca" + "catat" (2,1) at 1, match "ca" + "atat" (1,3) at 3, match "a" + "tat" (0,-) + "at" (2,5) at 5, match "at" + "t" (1,6) at 6, match "t" + "" (0,-) + +*/ + +// suffix array via induced sorting +// O(n) +inline auto suffix_array(array_view input) -> vector { + return induced_sort(input); +} + +// inverse +// O(n) +inline auto suffix_array_invert(array_view sa) -> vector { + vector isa; + isa.reallocate(sa.size()); + for(int i : range(sa.size())) isa[sa[i]] = i; + return isa; +} + +// auxiliary data structure for plcp and lpf computation +// O(n) +inline auto suffix_array_phi(array_view sa) -> vector { + vector phi; + phi.reallocate(sa.size()); + phi[sa[0]] = 0; + for(int i : range(1, sa.size())) phi[sa[i]] = sa[i - 1]; + return phi; +} + +// longest common prefix: lcp(l, r) +// O(n) +inline auto suffix_array_lcp(int l, int r, array_view sa, array_view input) -> int { + int i = sa[l], j = sa[r], k = 0, size = input.size(); + while(i + k < size && j + k < size && input[i + k] == input[j + k]) k++; + return k; +} + +// longest common prefix: lcp(i, j, k) +// O(n) +inline auto suffix_array_lcp(int i, int j, int k, array_view input) -> int { + int size = input.size(); + while(i + k < size && j + k < size && input[i + k] == input[j + k]) k++; + return k; +} + +// longest common prefix: lcp[n] == lcp(n, n-1) +// O(n) +inline auto suffix_array_lcp(array_view sa, array_view isa, array_view input) -> vector { + int k = 0, size = input.size(); + vector lcp; + lcp.reallocate(size + 1); + for(int i : range(size)) { + if(isa[i] == size) { k = 0; continue; } //the next substring is empty; ignore it + int j = sa[isa[i] + 1]; + while(i + k < size && j + k < size && input[i + k] == input[j + k]) k++; + lcp[1 + isa[i]] = k; + if(k) k--; + } + lcp[0] = 0; + return lcp; +} + +// longest common prefix (from permuted longest common prefix) +// O(n) +inline auto suffix_array_lcp(array_view plcp, array_view sa) -> vector { + vector lcp; + lcp.reallocate(plcp.size()); + for(int i : range(plcp.size())) lcp[i] = plcp[sa[i]]; + return lcp; +} + +// permuted longest common prefix +// O(n) +inline auto suffix_array_plcp(array_view phi, array_view input) -> vector { + vector plcp; + plcp.reallocate(phi.size()); + int k = 0, size = input.size(); + for(int i : range(size)) { + int j = phi[i]; + while(i + k < size && j + k < size && input[i + k] == input[j + k]) k++; + plcp[i] = k; + if(k) k--; + } + return plcp; +} + +// permuted longest common prefix (from longest common prefix) +// O(n) +inline auto suffix_array_plcp(array_view lcp, array_view sa) -> vector { + vector plcp; + plcp.reallocate(lcp.size()); + for(int i : range(lcp.size())) plcp[sa[i]] = lcp[i]; + return plcp; +} + +// longest common prefixes - left + right +// llcp[m] == lcp(l, m) +// rlcp[m] == lcp(m, r) +// O(n) +// requires: lcp -or- plcp+sa +inline auto suffix_array_lrcp(vector& llcp, vector& rlcp, array_view lcp, array_view plcp, array_view sa, array_view input) -> void { + int size = input.size(); + llcp.reset(), llcp.reallocate(size + 1); + rlcp.reset(), rlcp.reallocate(size + 1); + + function recurse = [&](int l, int r) -> int { + if(l >= r - 1) { + if(l >= size) return 0; + if(lcp) return lcp[l]; + return plcp[sa[l]]; + } + int m = l + r >> 1; + llcp[m - 1] = recurse(l, m); + rlcp[m - 1] = recurse(m, r); + return min(llcp[m - 1], rlcp[m - 1]); + }; + recurse(1, size + 1); + + llcp[0] = 0; + rlcp[0] = 0; +} + +// longest previous factor +// O(n) +// optional: plcp +inline auto suffix_array_lpf(vector& lengths, vector& offsets, array_view phi, array_view plcp, array_view input) -> void { + int k = 0, size = input.size(); + lengths.reset(), lengths.resize(size + 1, -1); + offsets.reset(), offsets.resize(size + 1, -1); + + function recurse = [&](int i, int j, int k) -> void { + if(lengths[i] < 0) { + lengths[i] = k; + offsets[i] = j; + } else if(lengths[i] < k) { + if(offsets[i] > j) { + recurse(offsets[i], j, lengths[i]); + } else { + recurse(j, offsets[i], lengths[i]); + } + lengths[i] = k; + offsets[i] = j; + } else { + if(offsets[i] > j) { + recurse(offsets[i], j, k); + } else { + recurse(j, offsets[i], k); + } + } + }; + + for(int i : range(size)) { + int j = phi[i]; + if(plcp) k = plcp[i]; + else while(i + k < size && j + k < size && input[i + k] == input[j + k]) k++; + if(i > j) { + recurse(i, j, k); + } else { + recurse(j, i, k); + } + if(k) k--; + } + + lengths[0] = 0; + offsets[0] = 0; +} + +// O(n log m) +inline auto suffix_array_find(int& length, int& offset, array_view sa, array_view input, array_view match) -> bool { + length = 0, offset = 0; + int l = 0, r = input.size(); + + while(l < r - 1) { + int m = l + r >> 1; + int s = sa[m]; + + int k = 0; + while(k < match.size() && s + k < input.size()) { + if(match[k] != input[s + k]) break; + k++; + } + + if(k > length) { + length = k; + offset = s; + if(k == match.size()) return true; + } + + if(k == match.size() || s + k == input.size()) k--; + + if(match[k] < input[s + k]) { + r = m; + } else { + l = m; + } + } + + return false; +} + +// O(n + log m) +inline auto suffix_array_find(int& length, int& offset, array_view llcp, array_view rlcp, array_view sa, array_view input, array_view match) -> bool { + length = 0, offset = 0; + int l = 0, r = input.size(), k = 0; + + while(l < r - 1) { + int m = l + r >> 1; + int s = sa[m]; + + while(k < match.size() && s + k < input.size()) { + if(match[k] != input[s + k]) break; + k++; + } + + if(k > length) { + length = k; + offset = s; + if(k == match.size()) return true; + } + + if(k == match.size() || s + k == input.size()) k--; + + if(match[k] < input[s + k]) { + r = m; + k = min(k, llcp[m]); + } else { + l = m; + k = min(k, rlcp[m]); + } + } + + return false; +} + +// + +//there are multiple strategies for building the required auxiliary structures for suffix arrays + +struct SuffixArray { + using type = SuffixArray; + + //O(n) + inline SuffixArray(array_view input) : input(input) { + sa = suffix_array(input); + } + + //O(n) + inline auto lrcp() -> type& { + //if(!isa) isa = suffix_array_invert(sa); + //if(!lcp) lcp = suffix_array_lcp(sa, isa, input); + if(!phi) phi = suffix_array_phi(sa); + if(!plcp) plcp = suffix_array_plcp(phi, input); + //if(!lcp) lcp = suffix_array_lcp(plcp, sa); + if(!llcp || !rlcp) suffix_array_lrcp(llcp, rlcp, lcp, plcp, sa, input); + return *this; + } + + //O(n) + inline auto lpf() -> type& { + if(!phi) phi = suffix_array_phi(sa); + //if(!plcp) plcp = suffix_array_plcp(phi, input); + if(!lengths || !offsets) suffix_array_lpf(lengths, offsets, phi, plcp, input); + return *this; + } + + inline auto operator[](int offset) const -> int { + return sa[offset]; + } + + //O(n log m) + //O(n + log m) with lrcp() + inline auto find(int& length, int& offset, array_view match) -> bool { + if(!llcp || !rlcp) return suffix_array_find(length, offset, sa, input, match); //O(n log m) + return suffix_array_find(length, offset, llcp, rlcp, sa, input, match); //O(n + log m) + } + + //O(n) with lpf() + inline auto previous(int& length, int& offset, int address) -> void { + length = lengths[address]; + offset = offsets[address]; + } + + //non-owning reference: SuffixArray is invalidated if memory is freed + array_view input; + + //suffix array and auxiliary data structures + vector sa; //suffix array + vector isa; //inverted suffix array + vector phi; //phi + vector plcp; //permuted longest common prefixes + vector lcp; //longest common prefixes + vector llcp; //longest common prefixes - left + vector rlcp; //longest common prefixes - right + vector lengths; //longest previous factors + vector offsets; //longest previous factors +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/terminal.hpp b/waterbox/bsnescore/bsnes/nall/terminal.hpp new file mode 100644 index 0000000000..9004163914 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/terminal.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +namespace nall::terminal { + +inline auto escapable() -> bool { + #if defined(PLATFORM_WINDOWS) + //todo: colors are supported by Windows 10+ and with alternate terminals (eg msys) + //disabled for now for compatibility with Windows 7 and 8.1's cmd.exe + return false; + #endif + return true; +} + +namespace color { + +template inline auto black(P&&... p) -> string { + if(!escapable()) return string{forward

    (p)...}; + return {"\e[30m", string{forward

    (p)...}, "\e[0m"}; +} + +template inline auto blue(P&&... p) -> string { + if(!escapable()) return string{forward

    (p)...}; + return {"\e[94m", string{forward

    (p)...}, "\e[0m"}; +} + +template inline auto green(P&&... p) -> string { + if(!escapable()) return string{forward

    (p)...}; + return {"\e[92m", string{forward

    (p)...}, "\e[0m"}; +} + +template inline auto cyan(P&&... p) -> string { + if(!escapable()) return string{forward

    (p)...}; + return {"\e[96m", string{forward

    (p)...}, "\e[0m"}; +} + +template inline auto red(P&&... p) -> string { + if(!escapable()) return string{forward

    (p)...}; + return {"\e[91m", string{forward

    (p)...}, "\e[0m"}; +} + +template inline auto magenta(P&&... p) -> string { + if(!escapable()) return string{forward

    (p)...}; + return {"\e[95m", string{forward

    (p)...}, "\e[0m"}; +} + +template inline auto yellow(P&&... p) -> string { + if(!escapable()) return string{forward

    (p)...}; + return {"\e[93m", string{forward

    (p)...}, "\e[0m"}; +} + +template inline auto white(P&&... p) -> string { + if(!escapable()) return string{forward

    (p)...}; + return {"\e[97m", string{forward

    (p)...}, "\e[0m"}; +} + +template inline auto gray(P&&... p) -> string { + if(!escapable()) return string{forward

    (p)...}; + return {"\e[37m", string{forward

    (p)...}, "\e[0m"}; +} + +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/thread.hpp b/waterbox/bsnescore/bsnes/nall/thread.hpp new file mode 100644 index 0000000000..fd33e2d5be --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/thread.hpp @@ -0,0 +1,137 @@ +#pragma once + +//simple thread library +//primary rationale is that std::thread does not support custom stack sizes +//this is highly critical in certain applications such as threaded web servers +//an added bonus is that it avoids licensing issues on Windows +//win32-pthreads (needed for std::thread) is licensed under the GPL only + +#include +#include +#include + +#if defined(API_POSIX) + +#include + +namespace nall { + +struct thread { + inline auto join() -> void; + + static inline auto create(const function& callback, uintptr parameter = 0, uint stacksize = 0) -> thread; + static inline auto detach() -> void; + static inline auto exit() -> void; + + struct context { + function void> callback; + uintptr parameter = 0; + }; + +private: + pthread_t handle; +}; + +inline auto _threadCallback(void* parameter) -> void* { + auto context = (thread::context*)parameter; + context->callback(context->parameter); + delete context; + return nullptr; +} + +auto thread::join() -> void { + pthread_join(handle, nullptr); +} + +auto thread::create(const function& callback, uintptr parameter, uint stacksize) -> thread { + thread instance; + + auto context = new thread::context; + context->callback = callback; + context->parameter = parameter; + + pthread_attr_t attr; + pthread_attr_init(&attr); + if(stacksize) pthread_attr_setstacksize(&attr, max(PTHREAD_STACK_MIN, stacksize)); + + pthread_create(&instance.handle, &attr, _threadCallback, (void*)context); + return instance; +} + +auto thread::detach() -> void { + pthread_detach(pthread_self()); +} + +auto thread::exit() -> void { + pthread_exit(nullptr); +} + +} + +#elif defined(API_WINDOWS) + +namespace nall { + +struct thread { + inline ~thread(); + inline auto join() -> void; + + static inline auto create(const function& callback, uintptr parameter = 0, uint stacksize = 0) -> thread; + static inline auto detach() -> void; + static inline auto exit() -> void; + + struct context { + function void> callback; + uintptr parameter = 0; + }; + +private: + HANDLE handle = 0; +}; + +inline auto WINAPI _threadCallback(void* parameter) -> DWORD { + auto context = (thread::context*)parameter; + context->callback(context->parameter); + delete context; + return 0; +} + +thread::~thread() { + if(handle) { + CloseHandle(handle); + handle = 0; + } +} + +auto thread::join() -> void { + if(handle) { + WaitForSingleObject(handle, INFINITE); + CloseHandle(handle); + handle = 0; + } +} + +auto thread::create(const function& callback, uintptr parameter, uint stacksize) -> thread { + thread instance; + + auto context = new thread::context; + context->callback = callback; + context->parameter = parameter; + + instance.handle = CreateThread(nullptr, stacksize, _threadCallback, (void*)context, 0, nullptr); + return instance; +} + +auto thread::detach() -> void { + //Windows threads do not use this concept: + //~thread() frees resources via CloseHandle() + //thread continues to run even after handle is closed +} + +auto thread::exit() -> void { + ExitThread(0); +} + +} + +#endif diff --git a/waterbox/bsnescore/bsnes/nall/traits.hpp b/waterbox/bsnescore/bsnes/nall/traits.hpp new file mode 100644 index 0000000000..e2607687d9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/traits.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +//pull all type traits used by nall from std namespace into nall namespace +//this removes the requirement to prefix type traits with std:: within nall + +namespace nall { + using std::add_const; + using std::conditional; + using std::conditional_t; + using std::decay; + using std::declval; + using std::enable_if; + using std::enable_if_t; + using std::false_type; + using std::is_floating_point; + using std::is_floating_point_v; + using std::forward; + using std::initializer_list; + using std::is_array; + using std::is_base_of; + using std::is_base_of_v; + using std::is_function; + using std::is_integral; + using std::is_integral_v; + using std::is_same; + using std::is_same_v; + using std::is_signed; + using std::is_signed_v; + using std::is_unsigned; + using std::is_unsigned_v; + using std::move; + using std::nullptr_t; + using std::remove_extent; + using std::remove_reference; + using std::swap; + using std::true_type; +} + +namespace std { + #if INTMAX_BITS >= 128 + template<> struct is_signed : true_type {}; + template<> struct is_unsigned : true_type {}; + #endif +} diff --git a/waterbox/bsnescore/bsnes/nall/unique-pointer.hpp b/waterbox/bsnescore/bsnes/nall/unique-pointer.hpp new file mode 100644 index 0000000000..bd91843cd9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/unique-pointer.hpp @@ -0,0 +1,106 @@ +#pragma once + +namespace nall { + +template +struct unique_pointer { + template static auto create(P&&... p) { + return unique_pointer{new T{forward

    (p)...}}; + } + + using type = T; + T* pointer = nullptr; + function deleter; + + unique_pointer(const unique_pointer&) = delete; + auto operator=(const unique_pointer&) -> unique_pointer& = delete; + + unique_pointer(T* pointer = nullptr, const function& deleter = {}) : pointer(pointer), deleter(deleter) {} + ~unique_pointer() { reset(); } + + auto operator=(T* source) -> unique_pointer& { + reset(); + pointer = source; + return *this; + } + + explicit operator bool() const { return pointer; } + + auto operator->() -> T* { return pointer; } + auto operator->() const -> const T* { return pointer; } + + auto operator*() -> T& { return *pointer; } + auto operator*() const -> const T& { return *pointer; } + + auto operator()() -> T& { return *pointer; } + auto operator()() const -> const T& { return *pointer; } + + auto data() -> T* { return pointer; } + auto data() const -> const T* { return pointer; } + + auto release() -> T* { + auto result = pointer; + pointer = nullptr; + return result; + } + + auto reset() -> void { + if(pointer) { + if(deleter) { + deleter(pointer); + } else { + delete pointer; + } + pointer = nullptr; + } + } +}; + +template +struct unique_pointer { + using type = T; + T* pointer = nullptr; + function void> deleter; + + unique_pointer(const unique_pointer&) = delete; + auto operator=(const unique_pointer&) -> unique_pointer& = delete; + + unique_pointer(T* pointer = nullptr, const function& deleter = {}) : pointer(pointer), deleter(deleter) {} + ~unique_pointer() { reset(); } + + auto operator=(T* source) -> unique_pointer& { + reset(); + pointer = source; + return *this; + } + + explicit operator bool() const { return pointer; } + + auto operator()() -> T* { return pointer; } + auto operator()() const -> T* { return pointer; } + + alwaysinline auto operator[](uint offset) -> T& { return pointer[offset]; } + alwaysinline auto operator[](uint offset) const -> const T& { return pointer[offset]; } + + auto data() -> T* { return pointer; } + auto data() const -> const T* { return pointer; } + + auto release() -> T* { + auto result = pointer; + pointer = nullptr; + return result; + } + + auto reset() -> void { + if(pointer) { + if(deleter) { + deleter(pointer); + } else { + delete[] pointer; + } + pointer = nullptr; + } + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/utility.hpp b/waterbox/bsnescore/bsnes/nall/utility.hpp new file mode 100644 index 0000000000..717da4e347 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/utility.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace nall { + +using std::tuple; + +template struct base_from_member { + base_from_member(T value) : value(value) {} + T value; +}; + +template struct castable { + operator To&() { return (To&)value; } + operator const To&() const { return (const To&)value; } + operator With&() { return value; } + operator const With&() const { return value; } + auto& operator=(const With& value) { return this->value = value; } + With value; +}; + +template inline auto allocate(uint size, const T& value) -> T* { + T* array = new T[size]; + for(uint i = 0; i < size; i++) array[i] = value; + return array; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/variant.hpp b/waterbox/bsnescore/bsnes/nall/variant.hpp new file mode 100644 index 0000000000..b08e3359de --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/variant.hpp @@ -0,0 +1,148 @@ +#pragma once + +namespace nall { + +template struct variant_size { + static constexpr uint size = max(sizeof(T), variant_size::size); +}; + +template struct variant_size { + static constexpr uint size = sizeof(T); +}; + +template struct variant_index { + static constexpr uint index = is_same_v ? Index : variant_index::index; +}; + +template struct variant_index { + static constexpr uint index = is_same_v ? Index : 0; +}; + +template struct variant_copy { + constexpr variant_copy(uint index, uint assigned, void* target, void* source) { + if(index == assigned) new(target) T(*((T*)source)); + else variant_copy(index + 1, assigned, target, source); + } +}; + +template struct variant_copy { + constexpr variant_copy(uint index, uint assigned, void* target, void* source) { + if(index == assigned) new(target) T(*((T*)source)); + } +}; + +template struct variant_move { + constexpr variant_move(uint index, uint assigned, void* target, void* source) { + if(index == assigned) new(target) T(move(*((T*)source))); + else variant_move(index + 1, assigned, target, source); + } +}; + +template struct variant_move { + constexpr variant_move(uint index, uint assigned, void* target, void* source) { + if(index == assigned) new(target) T(move(*((T*)source))); + } +}; + +template struct variant_destruct { + constexpr variant_destruct(uint index, uint assigned, void* data) { + if(index == assigned) ((T*)data)->~T(); + else variant_destruct(index + 1, assigned, data); + } +}; + +template struct variant_destruct { + constexpr variant_destruct(uint index, uint assigned, void* data) { + if(index == assigned) ((T*)data)->~T(); + } +}; + +template struct variant_equals { + constexpr auto operator()(uint index, uint assigned) const -> bool { + if(index == assigned) return is_same_v; + return variant_equals()(index + 1, assigned); + } +}; + +template struct variant_equals { + constexpr auto operator()(uint index, uint assigned) const -> bool { + if(index == assigned) return is_same_v; + return false; + } +}; + +template struct variant final { //final as destructor is not virtual + variant() : assigned(0) {} + variant(const variant& source) { operator=(source); } + variant(variant&& source) { operator=(move(source)); } + template variant(const T& value) { operator=(value); } + template variant(T&& value) { operator=(move(value)); } + ~variant() { reset(); } + + explicit operator bool() const { return assigned; } + template explicit constexpr operator T&() { return get(); } + template explicit constexpr operator const T&() const { return get(); } + + template constexpr auto is() const -> bool { + return variant_equals()(1, assigned); + } + + template constexpr auto get() -> T& { + static_assert(variant_index<1, T, P...>::index, "type not in variant"); + struct variant_bad_cast{}; + if(!is()) throw variant_bad_cast{}; + return *((T*)data); + } + + template constexpr auto get() const -> const T& { + static_assert(variant_index<1, T, P...>::index, "type not in variant"); + struct variant_bad_cast{}; + if(!is()) throw variant_bad_cast{}; + return *((const T*)data); + } + + template constexpr auto get(const T& fallback) const -> const T& { + if(!is()) return fallback; + return *((const T*)data); + } + + auto reset() -> void { + if(assigned) variant_destruct(1, assigned, (void*)data); + assigned = 0; + } + + auto& operator=(const variant& source) { + reset(); + if(assigned = source.assigned) variant_copy(1, source.assigned, (void*)data, (void*)source.data); + return *this; + } + + auto& operator=(variant&& source) { + reset(); + if(assigned = source.assigned) variant_move(1, source.assigned, (void*)data, (void*)source.data); + source.assigned = 0; + return *this; + } + + template auto& operator=(const T& value) { + static_assert(variant_index<1, T, P...>::index, "type not in variant"); + reset(); + new((void*)&data) T(value); + assigned = variant_index<1, T, P...>::index; + return *this; + } + + template auto& operator=(T&& value) { + static_assert(variant_index<1, T, P...>::index, "type not in variant"); + reset(); + new((void*)&data) T(move(value)); + assigned = variant_index<1, T, P...>::index; + return *this; + } + +private: + alignas(P...) char data[variant_size::size]; + uint assigned; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/varint.hpp b/waterbox/bsnescore/bsnes/nall/varint.hpp new file mode 100644 index 0000000000..ee18a03678 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/varint.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include + +namespace nall { + +struct varint { + virtual auto read() -> uint8_t = 0; + virtual auto write(uint8_t) -> void = 0; + + auto readvu() -> uintmax { + uintmax data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + } + + auto readvs() -> intmax { + uintmax data = readvu(); + bool negate = data & 1; + data >>= 1; + if(negate) data = ~data; + return data; + } + + auto writevu(uintmax data) -> void { + while(true) { + uint8_t x = data & 0x7f; + data >>= 7; + if(data == 0) return write(0x80 | x); + write(x); + data--; + } + } + + auto writevs(intmax data) -> void { + bool negate = data < 0; + if(negate) data = ~data; + data = (data << 1) | negate; + writevu(data); + } +}; + +struct VariadicNatural { + inline VariadicNatural() : mask(~0ull) { assign(0); } + template inline VariadicNatural(const T& value) : mask(~0ull) { assign(value); } + + inline operator uint64_t() const { return data; } + template inline auto& operator=(const T& value) { return assign(value); } + + inline auto operator++(int) { auto value = data; assign(data + 1); return value; } + inline auto operator--(int) { auto value = data; assign(data - 1); return value; } + + inline auto& operator++() { return assign(data + 1); } + inline auto& operator--() { return assign(data - 1); } + + inline auto& operator &=(const uint64_t value) { return assign(data & value); } + inline auto& operator |=(const uint64_t value) { return assign(data | value); } + inline auto& operator ^=(const uint64_t value) { return assign(data ^ value); } + inline auto& operator<<=(const uint64_t value) { return assign(data << value); } + inline auto& operator>>=(const uint64_t value) { return assign(data >> value); } + inline auto& operator +=(const uint64_t value) { return assign(data + value); } + inline auto& operator -=(const uint64_t value) { return assign(data - value); } + inline auto& operator *=(const uint64_t value) { return assign(data * value); } + inline auto& operator /=(const uint64_t value) { return assign(data / value); } + inline auto& operator %=(const uint64_t value) { return assign(data % value); } + + inline auto resize(uint bits) { + assert(bits <= 64); + mask = ~0ull >> (64 - bits); + data &= mask; + } + + inline auto serialize(serializer& s) { + s(data); + s(mask); + } + + struct Reference { + inline Reference(VariadicNatural& self, uint lo, uint hi) : self(self), Lo(lo), Hi(hi) {} + + inline operator uint64_t() const { + const uint64_t RangeBits = Hi - Lo + 1; + const uint64_t RangeMask = (((1ull << RangeBits) - 1) << Lo) & self.mask; + return (self & RangeMask) >> Lo; + } + + inline auto& operator=(const uint64_t value) { + const uint64_t RangeBits = Hi - Lo + 1; + const uint64_t RangeMask = (((1ull << RangeBits) - 1) << Lo) & self.mask; + self.data = (self.data & ~RangeMask) | ((value << Lo) & RangeMask); + return *this; + } + + private: + VariadicNatural& self; + const uint Lo; + const uint Hi; + }; + + inline auto bits(uint lo, uint hi) -> Reference { return {*this, lo < hi ? lo : hi, hi > lo ? hi : lo}; } + inline auto bit(uint index) -> Reference { return {*this, index, index}; } + inline auto byte(uint index) -> Reference { return {*this, index * 8 + 0, index * 8 + 7}; } + +private: + auto assign(uint64_t value) -> VariadicNatural& { + data = value & mask; + return *this; + } + + uint64_t data; + uint64_t mask; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/vector.hpp b/waterbox/bsnescore/bsnes/nall/vector.hpp new file mode 100644 index 0000000000..3662a76873 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector.hpp @@ -0,0 +1,156 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + +template +struct vector_base { + using type = vector_base; + + //core.hpp + vector_base() = default; + vector_base(const initializer_list& values); + vector_base(const type& source); + vector_base(type&& source); + ~vector_base(); + + explicit operator bool() const; + operator array_span(); + operator array_view() const; + template auto capacity() const -> uint64_t; + template auto size() const -> uint64_t; + template auto data() -> Cast*; + template auto data() const -> const Cast*; + + //assign.hpp + auto operator=(const type& source) -> type&; + auto operator=(type&& source) -> type&; + + //compare.hpp + auto operator==(const type& source) const -> bool; + auto operator!=(const type& source) const -> bool; + + //memory.hpp + auto reset() -> void; + auto acquire(T* data, uint64_t size, uint64_t capacity = 0) -> void; + auto release() -> T*; + + auto reserveLeft(uint64_t capacity) -> bool; + auto reserveRight(uint64_t capacity) -> bool; + auto reserve(uint64_t capacity) -> bool { return reserveRight(capacity); } + + auto reallocateLeft(uint64_t size) -> bool; + auto reallocateRight(uint64_t size) -> bool; + auto reallocate(uint64_t size) -> bool { return reallocateRight(size); } + + auto resizeLeft(uint64_t size, const T& value = T()) -> bool; + auto resizeRight(uint64_t size, const T& value = T()) -> bool; + auto resize(uint64_t size, const T& value = T()) -> bool { return resizeRight(size, value); } + + //access.hpp + alwaysinline auto operator[](uint64_t offset) -> T&; + alwaysinline auto operator[](uint64_t offset) const -> const T&; + + alwaysinline auto operator()(uint64_t offset) -> T&; + alwaysinline auto operator()(uint64_t offset, const T& value) const -> const T&; + + alwaysinline auto left() -> T&; + alwaysinline auto first() -> T& { return left(); } + alwaysinline auto left() const -> const T&; + alwaysinline auto first() const -> const T& { return left(); } + + alwaysinline auto right() -> T&; + alwaysinline auto last() -> T& { return right(); } + alwaysinline auto right() const -> const T&; + alwaysinline auto last() const -> const T& { return right(); } + + //modify.hpp + auto prepend(const T& value) -> void; + auto prepend(T&& value) -> void; + auto prepend(const type& values) -> void; + auto prepend(type&& values) -> void; + + auto append(const T& value) -> void; + auto append(T&& value) -> void; + auto append(const type& values) -> void; + auto append(type&& values) -> void; + + auto insert(uint64_t offset, const T& value) -> void; + + auto removeLeft(uint64_t length = 1) -> void; + auto removeFirst(uint64_t length = 1) -> void { return removeLeft(length); } + auto removeRight(uint64_t length = 1) -> void; + auto removeLast(uint64_t length = 1) -> void { return removeRight(length); } + auto remove(uint64_t offset, uint64_t length = 1) -> void; + auto removeByIndex(uint64_t offset) -> bool; + auto removeByValue(const T& value) -> bool; + + auto takeLeft() -> T; + auto takeFirst() -> T { return move(takeLeft()); } + auto takeRight() -> T; + auto takeLast() -> T { return move(takeRight()); } + auto take(uint64_t offset) -> T; + + //iterator.hpp + auto begin() -> iterator { return {data(), 0}; } + auto end() -> iterator { return {data(), size()}; } + + auto begin() const -> iterator_const { return {data(), 0}; } + auto end() const -> iterator_const { return {data(), size()}; } + + auto rbegin() -> reverse_iterator { return {data(), size() - 1}; } + auto rend() -> reverse_iterator { return {data(), (uint64_t)-1}; } + + auto rbegin() const -> reverse_iterator_const { return {data(), size() - 1}; } + auto rend() const -> reverse_iterator_const { return {data(), (uint64_t)-1}; } + + //utility.hpp + auto fill(const T& value = {}) -> void; + auto sort(const function& comparator = [](auto& lhs, auto& rhs) { return lhs < rhs; }) -> void; + auto reverse() -> void; + auto find(const function& comparator) -> maybe; + auto find(const T& value) const -> maybe; + auto findSorted(const T& value) const -> maybe; + auto foreach(const function& callback) -> void; + auto foreach(const function& callback) -> void; + +protected: + T* _pool = nullptr; //pointer to first initialized element in pool + uint64_t _size = 0; //number of initialized elements in pool + uint64_t _left = 0; //number of allocated elements free on the left of pool + uint64_t _right = 0; //number of allocated elements free on the right of pool +}; + +} + +#define vector vector_base +#include +#include +#include +#include +#include +#include +#include +#include +#undef vector + +namespace nall { + template struct vector : vector_base { + using vector_base::vector_base; + }; +} + +#include diff --git a/waterbox/bsnescore/bsnes/nall/vector/access.hpp b/waterbox/bsnescore/bsnes/nall/vector/access.hpp new file mode 100644 index 0000000000..62f44fb0df --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector/access.hpp @@ -0,0 +1,47 @@ +#pragma once + +namespace nall { + +template auto vector::operator[](uint64_t offset) -> T& { + #ifdef DEBUG + struct out_of_bounds {}; + if(offset >= size()) throw out_of_bounds{}; + #endif + return _pool[offset]; +} + +template auto vector::operator[](uint64_t offset) const -> const T& { + #ifdef DEBUG + struct out_of_bounds {}; + if(offset >= size()) throw out_of_bounds{}; + #endif + return _pool[offset]; +} + +template auto vector::operator()(uint64_t offset) -> T& { + while(offset >= size()) append(T()); + return _pool[offset]; +} + +template auto vector::operator()(uint64_t offset, const T& value) const -> const T& { + if(offset >= size()) return value; + return _pool[offset]; +} + +template auto vector::left() -> T& { + return _pool[0]; +} + +template auto vector::left() const -> const T& { + return _pool[0]; +} + +template auto vector::right() -> T& { + return _pool[_size - 1]; +} + +template auto vector::right() const -> const T& { + return _pool[_size - 1]; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/vector/assign.hpp b/waterbox/bsnescore/bsnes/nall/vector/assign.hpp new file mode 100644 index 0000000000..2d66149c27 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector/assign.hpp @@ -0,0 +1,28 @@ +#pragma once + +namespace nall { + +template auto vector::operator=(const vector& source) -> vector& { + if(this == &source) return *this; + _pool = memory::allocate(source._size); + _size = source._size; + _left = 0; + _right = 0; + for(uint64_t n : range(_size)) new(_pool + n) T(source._pool[n]); + return *this; +} + +template auto vector::operator=(vector&& source) -> vector& { + if(this == &source) return *this; + _pool = source._pool; + _size = source._size; + _left = source._left; + _right = source._right; + source._pool = nullptr; + source._size = 0; + source._left = 0; + source._right = 0; + return *this; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/vector/compare.hpp b/waterbox/bsnescore/bsnes/nall/vector/compare.hpp new file mode 100644 index 0000000000..e2ef4b7b2f --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector/compare.hpp @@ -0,0 +1,18 @@ +#pragma once + +namespace nall { + +template auto vector::operator==(const vector& source) const -> bool { + if(this == &source) return true; + if(size() != source.size()) return false; + for(uint64_t n = 0; n < size(); n++) { + if(operator[](n) != source[n]) return false; + } + return true; +} + +template auto vector::operator!=(const vector& source) const -> bool { + return !operator==(source); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/vector/core.hpp b/waterbox/bsnescore/bsnes/nall/vector/core.hpp new file mode 100644 index 0000000000..a332928ea6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector/core.hpp @@ -0,0 +1,50 @@ +#pragma once + +namespace nall { + +template vector::vector(const initializer_list& values) { + reserveRight(values.size()); + for(auto& value : values) append(value); +} + +template vector::vector(const vector& source) { + operator=(source); +} + +template vector::vector(vector&& source) { + operator=(move(source)); +} + +template vector::~vector() { + reset(); +} + +template vector::operator bool() const { + return _size; +} + +template vector::operator array_span() { + return {data(), size()}; +} + +template vector::operator array_view() const { + return {data(), size()}; +} + +template template auto vector::capacity() const -> uint64_t { + return (_left + _size + _right) * sizeof(T) / sizeof(Cast); +} + +template template auto vector::size() const -> uint64_t { + return _size * sizeof(T) / sizeof(Cast); +} + +template template auto vector::data() -> Cast* { + return (Cast*)_pool; +} + +template template auto vector::data() const -> const Cast* { + return (const Cast*)_pool; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/vector/iterator.hpp b/waterbox/bsnescore/bsnes/nall/vector/iterator.hpp new file mode 100644 index 0000000000..67afb01a76 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector/iterator.hpp @@ -0,0 +1,57 @@ +#pragma once + +namespace nall { + +template +struct vector_iterator { + vector_iterator(vector& self, uint64_t offset) : self(self), offset(offset) {} + auto operator*() -> T& { return self.operator[](offset); } + auto operator->() -> T* { return self.operator[](offset); } + auto operator!=(const vector_iterator& source) const -> bool { return offset != source.offset; } + auto operator++() -> vector_iterator& { return offset++, *this; } + +private: + vector& self; + uint64_t offset; +}; + +template +struct vector_iterator_const { + vector_iterator_const(const vector& self, uint64_t offset) : self(self), offset(offset) {} + auto operator*() -> const T& { return self.operator[](offset); } + auto operator->() -> T* { return self.operator[](offset); } + auto operator!=(const vector_iterator_const& source) const -> bool { return offset != source.offset; } + auto operator++() -> vector_iterator_const& { return offset++, *this; } + +private: + const vector& self; + uint64_t offset; +}; + +template +struct vector_reverse_iterator { + vector_reverse_iterator(vector& self, uint64_t offset) : self(self), offset(offset) {} + auto operator*() -> T& { return self.operator[](offset); } + auto operator->() -> T* { return self.operator[](offset); } + auto operator!=(const vector_reverse_iterator& source) const -> bool { return offset != source.offset; } + auto operator++() -> vector_reverse_iterator& { return offset--, *this; } + +private: + vector& self; + uint64_t offset; +}; + +template +struct vector_reverse_iterator_const { + vector_reverse_iterator_const(const vector& self, uint64_t offset) : self(self), offset(offset) {} + auto operator*() -> const T& { return self.operator[](offset); } + auto operator->() -> T* { return self.operator[](offset); } + auto operator!=(const vector_reverse_iterator_const& source) const -> bool { return offset != source.offset; } + auto operator++() -> vector_reverse_iterator_const& { return offset--, *this; } + +private: + const vector& self; + uint64_t offset; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/vector/memory.hpp b/waterbox/bsnescore/bsnes/nall/vector/memory.hpp new file mode 100644 index 0000000000..863d406c09 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector/memory.hpp @@ -0,0 +1,147 @@ +#pragma once + +namespace nall { + +//nall::vector acts internally as a deque (double-ended queue) +//it does this because it's essentially free to do so, only costing an extra integer in sizeof(vector) + +template auto vector::reset() -> void { + if(!_pool) return; + + for(uint64_t n : range(_size)) _pool[n].~T(); + memory::free(_pool - _left); + + _pool = nullptr; + _size = 0; + _left = 0; + _right = 0; +} + +//acquire ownership of allocated memory + +template auto vector::acquire(T* data, uint64_t size, uint64_t capacity) -> void { + reset(); + _pool = data; + _size = size; + _left = 0; + _right = capacity ? capacity : size; +} + +//release ownership of allocated memory + +template auto vector::release() -> T* { + auto pool = _pool; + _pool = nullptr; + _size = 0; + _left = 0; + _right = 0; + return pool; +} + +//reserve allocates memory for objects, but does not initialize them +//when the vector desired size is known, this can be used to avoid growing the capacity dynamically +//reserve will not actually shrink the capacity, only expand it +//shrinking the capacity would destroy objects, and break amortized growth with reallocate and resize + +template auto vector::reserveLeft(uint64_t capacity) -> bool { + if(_size + _left >= capacity) return false; + + uint64_t left = bit::round(capacity); + auto pool = memory::allocate(left + _right) + (left - _size); + for(uint64_t n : range(_size)) new(pool + n) T(move(_pool[n])); + memory::free(_pool - _left); + + _pool = pool; + _left = left - _size; + + return true; +} + +template auto vector::reserveRight(uint64_t capacity) -> bool { + if(_size + _right >= capacity) return false; + + uint64_t right = bit::round(capacity); + auto pool = memory::allocate(_left + right) + _left; + for(uint64_t n : range(_size)) new(pool + n) T(move(_pool[n])); + memory::free(_pool - _left); + + _pool = pool; + _right = right - _size; + + return true; +} + +//reallocation is meant for POD types, to avoid the overhead of initialization +//do not use with non-POD types, or they will not be properly constructed or destructed + +template auto vector::reallocateLeft(uint64_t size) -> bool { + if(size < _size) { //shrink + _pool += _size - size; + _left += _size - size; + _size = size; + return true; + } + if(size > _size) { //grow + reserveLeft(size); + _pool -= size - _size; + _left -= size - _size; + _size = size; + return true; + } + return false; +} + +template auto vector::reallocateRight(uint64_t size) -> bool { + if(size < _size) { //shrink + _right += _size - size; + _size = size; + return true; + } + if(size > _size) { //grow + reserveRight(size); + _right -= size - _size; + _size = size; + return true; + } + return false; +} + +//resize is meant for non-POD types, and will properly construct objects + +template auto vector::resizeLeft(uint64_t size, const T& value) -> bool { + if(size < _size) { //shrink + for(uint64_t n : range(_size - size)) _pool[n].~T(); + _pool += _size - size; + _left += _size - size; + _size = size; + return true; + } + if(size > _size) { //grow + reserveLeft(size); + _pool -= size - _size; + for(uint64_t n : nall::reverse(range(size - _size))) new(_pool + n) T(value); + _left -= size - _size; + _size = size; + return true; + } + return false; +} + +template auto vector::resizeRight(uint64_t size, const T& value) -> bool { + if(size < _size) { //shrink + for(uint64_t n : range(size, _size)) _pool[n].~T(); + _right += _size - size; + _size = size; + return true; + } + if(size > _size) { //grow + reserveRight(size); + for(uint64_t n : range(_size, size)) new(_pool + n) T(value); + _right -= size - _size; + _size = size; + return true; + } + return false; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/vector/modify.hpp b/waterbox/bsnescore/bsnes/nall/vector/modify.hpp new file mode 100644 index 0000000000..3386125ef6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector/modify.hpp @@ -0,0 +1,137 @@ +#pragma once + +namespace nall { + +template auto vector::prepend(const T& value) -> void { + reserveLeft(size() + 1); + new(--_pool) T(value); + _left--; + _size++; +} + +template auto vector::prepend(T&& value) -> void { + reserveLeft(size() + 1); + new(--_pool) T(move(value)); + _left--; + _size++; +} + +template auto vector::prepend(const vector& values) -> void { + reserveLeft(size() + values.size()); + _pool -= values.size(); + for(uint64_t n : range(values)) new(_pool + n) T(values[n]); + _left -= values.size(); + _size += values.size(); +} + +template auto vector::prepend(vector&& values) -> void { + reserveLeft(size() + values.size()); + _pool -= values.size(); + for(uint64_t n : range(values)) new(_pool + n) T(move(values[n])); + _left -= values.size(); + _size += values.size(); +} + +// + +template auto vector::append(const T& value) -> void { + reserveRight(size() + 1); + new(_pool + _size) T(value); + _right--; + _size++; +} + +template auto vector::append(T&& value) -> void { + reserveRight(size() + 1); + new(_pool + _size) T(move(value)); + _right--; + _size++; +} + +template auto vector::append(const vector& values) -> void { + reserveRight(size() + values.size()); + for(uint64_t n : range(values.size())) new(_pool + _size + n) T(values[n]); + _right -= values.size(); + _size += values.size(); +} + +template auto vector::append(vector&& values) -> void { + reserveRight(size() + values.size()); + for(uint64_t n : range(values.size())) new(_pool + _size + n) T(move(values[n])); + _right -= values.size(); + _size += values.size(); +} + +// + +template auto vector::insert(uint64_t offset, const T& value) -> void { + if(offset == 0) return prepend(value); + if(offset == size() - 1) return append(value); + reserveRight(size() + 1); + _size++; + for(int64_t n = size() - 1; n > offset; n--) { + _pool[n] = move(_pool[n - 1]); + } + new(_pool + offset) T(value); +} + +// + +template auto vector::removeLeft(uint64_t length) -> void { + if(length > size()) length = size(); + resizeLeft(size() - length); +} + +template auto vector::removeRight(uint64_t length) -> void { + if(length > size()) length = size(); + resizeRight(size() - length); +} + +template auto vector::remove(uint64_t offset, uint64_t length) -> void { + if(offset == 0) return removeLeft(length); + if(offset == size() - 1) return removeRight(length); + + for(uint64_t n = offset; n < size(); n++) { + if(n + length < size()) { + _pool[n] = move(_pool[n + length]); + } else { + _pool[n].~T(); + } + } + _size -= length; +} + +template auto vector::removeByIndex(uint64_t index) -> bool { + if(index < size()) return remove(index), true; + return false; +} + +template auto vector::removeByValue(const T& value) -> bool { + if(auto index = find(value)) return remove(*index), true; + return false; +} + +// + +template auto vector::takeLeft() -> T { + T value = move(_pool[0]); + removeLeft(); + return value; +} + +template auto vector::takeRight() -> T { + T value = move(_pool[size() - 1]); + removeRight(); + return value; +} + +template auto vector::take(uint64_t offset) -> T { + if(offset == 0) return takeLeft(); + if(offset == size() - 1) return takeRight(); + + T value = move(_pool[offset]); + remove(offset); + return value; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/vector/specialization/uint8_t.hpp b/waterbox/bsnescore/bsnes/nall/vector/specialization/uint8_t.hpp new file mode 100644 index 0000000000..8ec5cadb8b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector/specialization/uint8_t.hpp @@ -0,0 +1,38 @@ +#pragma once + +namespace nall { + +template<> struct vector : vector_base { + using type = vector; + using vector_base::vector_base; + + template auto appendl(U value, uint size) -> void { + for(uint byte : range(size)) append(uint8_t(value >> byte * 8)); + } + + template auto appendm(U value, uint size) -> void { + for(uint byte : nall::reverse(range(size))) append(uint8_t(value >> byte * 8)); + } + + //note: string_view is not declared here yet ... + auto appends(array_view memory) -> void { + for(uint8_t byte : memory) append(byte); + } + + template auto readl(int offset, uint size) -> U { + if(offset < 0) offset = this->size() - abs(offset); + U value = 0; + for(uint byte : range(size)) value |= (U)operator[](offset + byte) << byte * 8; + return value; + } + + auto view(uint offset, uint length) -> array_view { + #ifdef DEBUG + struct out_of_bounds {}; + if(offset + length >= size()) throw out_of_bounds{}; + #endif + return {data() + offset, length}; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/vector/utility.hpp b/waterbox/bsnescore/bsnes/nall/vector/utility.hpp new file mode 100644 index 0000000000..635f52ee35 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vector/utility.hpp @@ -0,0 +1,47 @@ +#pragma once + +namespace nall { + +template auto vector::fill(const T& value) -> void { + for(uint64_t n : range(size())) _pool[n] = value; +} + +template auto vector::sort(const function& comparator) -> void { + nall::sort(_pool, _size, comparator); +} + +template auto vector::reverse() -> void { + vector reversed; + for(uint64_t n : range(size())) reversed.prepend(_pool[n]); + operator=(move(reversed)); +} + +template auto vector::find(const function& comparator) -> maybe { + for(uint64_t n : range(size())) if(comparator(_pool[n])) return n; + return nothing; +} + +template auto vector::find(const T& value) const -> maybe { + for(uint64_t n : range(size())) if(_pool[n] == value) return n; + return nothing; +} + +template auto vector::findSorted(const T& value) const -> maybe { + int64_t l = 0, r = size() - 1; + while(l <= r) { + int64_t m = l + (r - l >> 1); + if(value == _pool[m]) return m; + value < _pool[m] ? r = m - 1 : l = m + 1; + } + return nothing; +} + +template auto vector::foreach(const function& callback) -> void { + for(uint64_t n : range(size())) callback(_pool[n]); +} + +template auto vector::foreach(const function& callback) -> void { + for(uint64_t n : range(size())) callback(n, _pool[n]); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/vfs.hpp b/waterbox/bsnescore/bsnes/nall/vfs.hpp new file mode 100644 index 0000000000..80396785dd --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vfs.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/waterbox/bsnescore/bsnes/nall/vfs/fs/file.hpp b/waterbox/bsnescore/bsnes/nall/vfs/fs/file.hpp new file mode 100644 index 0000000000..bcac705c0b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vfs/fs/file.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +namespace nall::vfs::fs { + +struct file : vfs::file { + static auto open(string location_, mode mode_) -> shared_pointer { + auto instance = shared_pointer{new file}; + if(!instance->_open(location_, mode_)) return {}; + return instance; + } + + auto size() const -> uintmax override { + return _fp.size(); + } + + auto offset() const -> uintmax override { + return _fp.offset(); + } + + auto seek(intmax offset_, index index_) -> void override { + _fp.seek(offset_, (uint)index_); + } + + auto read() -> uint8_t override { + return _fp.read(); + } + + auto write(uint8_t data_) -> void override { + _fp.write(data_); + } + + auto flush() -> void override { + _fp.flush(); + } + +private: + file() = default; + file(const file&) = delete; + auto operator=(const file&) -> file& = delete; + + auto _open(string location_, mode mode_) -> bool { + if(!_fp.open(location_, (uint)mode_)) return false; + return true; + } + + file_buffer _fp; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/vfs/memory/file.hpp b/waterbox/bsnescore/bsnes/nall/vfs/memory/file.hpp new file mode 100644 index 0000000000..e5af580563 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vfs/memory/file.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +namespace nall::vfs::memory { + +struct file : vfs::file { + ~file() { delete[] _data; } + + static auto open(const void* data, uintmax size) -> shared_pointer { + auto instance = shared_pointer{new file}; + instance->_open((const uint8_t*)data, size); + return instance; + } + + static auto open(string location, bool decompress = false) -> shared_pointer { + auto instance = shared_pointer{new file}; + if(decompress && location.iendsWith(".zip")) { + Decode::ZIP archive; + if(archive.open(location) && archive.file.size() == 1) { + auto memory = archive.extract(archive.file.first()); + instance->_open(memory.data(), memory.size()); + return instance; + } + } + auto memory = nall::file::read(location); + instance->_open(memory.data(), memory.size()); + return instance; + } + + auto data() const -> const uint8_t* { return _data; } + auto size() const -> uintmax override { return _size; } + auto offset() const -> uintmax override { return _offset; } + + auto seek(intmax offset, index mode) -> void override { + if(mode == index::absolute) _offset = (uintmax)offset; + if(mode == index::relative) _offset += (intmax)offset; + } + + auto read() -> uint8_t override { + if(_offset >= _size) return 0x00; + return _data[_offset++]; + } + + auto write(uint8_t data) -> void override { + if(_offset >= _size) return; + _data[_offset++] = data; + } + +private: + file() = default; + file(const file&) = delete; + auto operator=(const file&) -> file& = delete; + + auto _open(const uint8_t* data, uintmax size) -> void { + _size = size; + _data = new uint8_t[size]; + nall::memory::copy(_data, data, size); + } + + uint8_t* _data = nullptr; + uintmax _size = 0; + uintmax _offset = 0; +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/vfs/vfs.hpp b/waterbox/bsnescore/bsnes/nall/vfs/vfs.hpp new file mode 100644 index 0000000000..56488be0d7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vfs/vfs.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +namespace nall::vfs { + +struct file { + enum class mode : uint { read, write, modify, create }; + enum class index : uint { absolute, relative }; + + virtual ~file() = default; + + virtual auto size() const -> uintmax = 0; + virtual auto offset() const -> uintmax = 0; + + virtual auto seek(intmax offset, index = index::absolute) -> void = 0; + virtual auto read() -> uint8_t = 0; + virtual auto write(uint8_t data) -> void = 0; + virtual auto flush() -> void {} + + auto end() const -> bool { + return offset() >= size(); + } + + auto read(void* vdata, uintmax bytes) -> void { + auto data = (uint8_t*)vdata; + while(bytes--) *data++ = read(); + } + + auto readl(uint bytes) -> uintmax { + uintmax data = 0; + for(auto n : range(bytes)) data |= (uintmax)read() << n * 8; + return data; + } + + auto readm(uint bytes) -> uintmax { + uintmax data = 0; + for(auto n : range(bytes)) data = data << 8 | read(); + return data; + } + + auto reads() -> string { + string s; + s.resize(size()); + read(s.get(), s.size()); + return s; + } + + auto write(const void* vdata, uintmax bytes) -> void { + auto data = (const uint8_t*)vdata; + while(bytes--) write(*data++); + } + + auto writel(uintmax data, uint bytes) -> void { + for(auto n : range(bytes)) write(data), data >>= 8; + } + + auto writem(uintmax data, uint bytes) -> void { + for(auto n : reverse(range(bytes))) write(data >> n * 8); + } + + auto writes(const string& s) -> void { + write(s.data(), s.size()); + } +}; + +} + +#include +#include diff --git a/waterbox/bsnescore/bsnes/nall/view.hpp b/waterbox/bsnescore/bsnes/nall/view.hpp new file mode 100644 index 0000000000..ce874ce49b --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/view.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace nall { + +template struct view; + +} diff --git a/waterbox/bsnescore/bsnes/nall/windows/detour.hpp b/waterbox/bsnescore/bsnes/nall/windows/detour.hpp new file mode 100644 index 0000000000..d4f304cd0a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/windows/detour.hpp @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace nall { + +#define Copy 0 +#define RelNear 1 + +struct detour { + static auto insert(const string& moduleName, const string& functionName, void*& source, void* target) -> bool; + static auto remove(const string& moduleName, const string& functionName, void*& source) -> bool; + +protected: + static auto length(const uint8* function) -> uint; + static auto mirror(uint8* target, const uint8* source) -> uint; + + struct opcode { + uint16 prefix; + uint length; + uint mode; + uint16 modify; + }; + static opcode opcodes[]; +}; + +//TODO: +//* fs:, gs: should force another opcode copy +//* conditional branches within +5-byte range should fail +detour::opcode detour::opcodes[] = { + {0x50, 1}, //push eax + {0x51, 1}, //push ecx + {0x52, 1}, //push edx + {0x53, 1}, //push ebx + {0x54, 1}, //push esp + {0x55, 1}, //push ebp + {0x56, 1}, //push esi + {0x57, 1}, //push edi + {0x58, 1}, //pop eax + {0x59, 1}, //pop ecx + {0x5a, 1}, //pop edx + {0x5b, 1}, //pop ebx + {0x5c, 1}, //pop esp + {0x5d, 1}, //pop ebp + {0x5e, 1}, //pop esi + {0x5f, 1}, //pop edi + {0x64, 1}, //fs: + {0x65, 1}, //gs: + {0x68, 5}, //push dword + {0x6a, 2}, //push byte + {0x74, 2, RelNear, 0x0f84}, //je near -> je far + {0x75, 2, RelNear, 0x0f85}, //jne near -> jne far + {0x89, 2}, //mov reg,reg + {0x8b, 2}, //mov reg,reg + {0x90, 1}, //nop + {0xa1, 5}, //mov eax,[dword] + {0xeb, 2, RelNear, 0xe9}, //jmp near -> jmp far +}; + +auto detour::insert(const string& moduleName, const string& functionName, void*& source, void* target) -> bool { + HMODULE module = GetModuleHandleW(utf16_t(moduleName)); + if(!module) return false; + + uint8* sourceData = (uint8_t*)GetProcAddress(module, functionName); + if(!sourceData) return false; + + uint sourceLength = detour::length(sourceData); + if(sourceLength < 5) { + //unable to clone enough bytes to insert hook + #if 1 + string output = {"detour::insert(", moduleName, "::", functionName, ") failed: "}; + for(uint n = 0; n < 16; n++) output.append(hex<2>(sourceData[n]), " "); + output.trimRight(" ", 1L); + MessageBoxA(0, output, "nall::detour", MB_OK); + #endif + return false; + } + + auto mirrorData = new uint8[512](); + detour::mirror(mirrorData, sourceData); + + DWORD privileges; + VirtualProtect((void*)mirrorData, 512, PAGE_EXECUTE_READWRITE, &privileges); + VirtualProtect((void*)sourceData, 256, PAGE_EXECUTE_READWRITE, &privileges); + uint64_t address = (uint64_t)target - ((uint64_t)sourceData + 5); + sourceData[0] = 0xe9; //jmp target + sourceData[1] = address >> 0; + sourceData[2] = address >> 8; + sourceData[3] = address >> 16; + sourceData[4] = address >> 24; + VirtualProtect((void*)sourceData, 256, privileges, &privileges); + + source = (void*)mirrorData; + return true; +} + +auto detour::remove(const string& moduleName, const string& functionName, void*& source) -> bool { + HMODULE module = GetModuleHandleW(utf16_t(moduleName)); + if(!module) return false; + + auto sourceData = (uint8*)GetProcAddress(module, functionName); + if(!sourceData) return false; + + auto mirrorData = (uint8*)source; + if(mirrorData == sourceData) return false; //hook was never installed + + uint length = detour::length(256 + mirrorData); + if(length < 5) return false; + + DWORD privileges; + VirtualProtect((void*)sourceData, 256, PAGE_EXECUTE_READWRITE, &privileges); + for(uint n = 0; n < length; n++) sourceData[n] = mirrorData[256 + n]; + VirtualProtect((void*)sourceData, 256, privileges, &privileges); + + source = (void*)sourceData; + delete[] mirrorData; + return true; +} + +auto detour::length(const uint8* function) -> uint { + uint length = 0; + while(length < 5) { + detour::opcode *opcode = 0; + foreach(op, detour::opcodes) { + if(function[length] == op.prefix) { + opcode = &op; + break; + } + } + if(opcode == 0) break; + length += opcode->length; + } + return length; +} + +auto detour::mirror(uint8* target, const uint8* source) -> uint { + const uint8* entryPoint = source; + for(uint n = 0; n < 256; n++) target[256 + n] = source[n]; + + uint size = detour::length(source); + while(size) { + detour::opcode* opcode = nullptr; + foreach(op, detour::opcodes) { + if(*source == op.prefix) { + opcode = &op; + break; + } + } + + switch(opcode->mode) { + case Copy: + for(uint n = 0; n < opcode->length; n++) *target++ = *source++; + break; + case RelNear: { + source++; + uint64_t sourceAddress = (uint64_t)source + 1 + (int8)*source; + *target++ = opcode->modify; + if(opcode->modify >> 8) *target++ = opcode->modify >> 8; + uint64_t targetAddress = (uint64_t)target + 4; + uint64_t address = sourceAddress - targetAddress; + *target++ = address >> 0; + *target++ = address >> 8; + *target++ = address >> 16; + *target++ = address >> 24; + source += 2; + } break; + } + + size -= opcode->length; + } + + uint64_t address = (entryPoint + detour::length(entryPoint)) - (target + 5); + *target++ = 0xe9; //jmp entryPoint + *target++ = address >> 0; + *target++ = address >> 8; + *target++ = address >> 16; + *target++ = address >> 24; + + return source - entryPoint; +} + +#undef Implied +#undef RelNear + +} diff --git a/waterbox/bsnescore/bsnes/nall/windows/guard.hpp b/waterbox/bsnescore/bsnes/nall/windows/guard.hpp new file mode 100644 index 0000000000..353874cb65 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/windows/guard.hpp @@ -0,0 +1,34 @@ +#ifndef NALL_WINDOWS_GUARD_HPP +#define NALL_WINDOWS_GUARD_HPP + +#define boolean WindowsBoolean +#define interface WindowsInterface + +#undef UNICODE +#undef WINVER +#undef WIN32_LEAN_AND_LEAN +#undef _WIN32_WINNT +#undef _WIN32_IE +#undef __MSVCRT_VERSION__ +#undef NOMINMAX +#undef PATH_MAX + +#define UNICODE +#define WINVER 0x0601 +#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT WINVER +#define _WIN32_IE WINVER +#define __MSVCRT_VERSION__ WINVER +#define NOMINMAX +#define PATH_MAX 260 + +#else +#undef NALL_WINDOWS_GUARD_HPP + +#undef boolean +#undef interface + +#undef far +#undef near + +#endif diff --git a/waterbox/bsnescore/bsnes/nall/windows/guid.hpp b/waterbox/bsnescore/bsnes/nall/windows/guid.hpp new file mode 100644 index 0000000000..7c3ae2a4e1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/windows/guid.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace nall { + +inline auto guid() -> string { + GUID guidInstance; + CoCreateGuid(&guidInstance); + + wchar_t guidString[39]; + StringFromGUID2(guidInstance, guidString, 39); + + return (char*)utf8_t(guidString); +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/windows/launcher.hpp b/waterbox/bsnescore/bsnes/nall/windows/launcher.hpp new file mode 100644 index 0000000000..1ae13c3dd8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/windows/launcher.hpp @@ -0,0 +1,91 @@ +#pragma once + +namespace nall { + +//launch a new process and inject specified DLL into it + +auto launch(const char* applicationName, const char* libraryName, uint32 entryPoint) -> bool { + //if a launcher does not send at least one message, a wait cursor will appear + PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0); + MSG msg; + GetMessage(&msg, 0, 0, 0); + + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + memset(&si, 0, sizeof(STARTUPINFOW)); + BOOL result = CreateProcessW( + utf16_t(applicationName), GetCommandLineW(), NULL, NULL, TRUE, + DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS, //do not break if application creates its own processes + NULL, NULL, &si, &pi + ); + if(result == false) return false; + + uint8 entryData[1024], entryHook[1024] = { + 0x68, 0x00, 0x00, 0x00, 0x00, //push libraryName + 0xb8, 0x00, 0x00, 0x00, 0x00, //mov eax,LoadLibraryW + 0xff, 0xd0, //call eax + 0xcd, 0x03, //int 3 + }; + + entryHook[1] = (uint8)((entryPoint + 14) >> 0); + entryHook[2] = (uint8)((entryPoint + 14) >> 8); + entryHook[3] = (uint8)((entryPoint + 14) >> 16); + entryHook[4] = (uint8)((entryPoint + 14) >> 24); + + auto pLoadLibraryW = (uint32)GetProcAddress(GetModuleHandleW(L"kernel32"), "LoadLibraryW"); + entryHook[6] = pLoadLibraryW >> 0; + entryHook[7] = pLoadLibraryW >> 8; + entryHook[8] = pLoadLibraryW >> 16; + entryHook[9] = pLoadLibraryW >> 24; + + utf16_t buffer = utf16_t(libraryName); + memcpy(entryHook + 14, buffer, 2 * wcslen(buffer) + 2); + + while(true) { + DEBUG_EVENT event; + WaitForDebugEvent(&event, INFINITE); + + if(event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) break; + + if(event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { + if(event.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) { + if(event.u.Exception.ExceptionRecord.ExceptionAddress == (void*)(entryPoint + 14 - 1)) { + HANDLE hProcess = OpenProcess(0, FALSE, event.dwProcessId); + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, event.dwThreadId); + + CONTEXT context; + context.ContextFlags = CONTEXT_FULL; + GetThreadContext(hThread, &context); + + WriteProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryData, sizeof entryData, NULL); + context.Eip = entryPoint; + SetThreadContext(hThread, &context); + + CloseHandle(hThread); + CloseHandle(hProcess); + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + continue; + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); + continue; + } + + if(event.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { + ReadProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryData, sizeof entryData, NULL); + WriteProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryHook, sizeof entryHook, NULL); + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + continue; + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + } + + return true; +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/windows/registry.hpp b/waterbox/bsnescore/bsnes/nall/windows/registry.hpp new file mode 100644 index 0000000000..96bbb32869 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/windows/registry.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include +#include + +#include +#undef interface +#ifndef KEY_WOW64_64KEY + #define KEY_WOW64_64KEY 0x0100 +#endif +#ifndef KEY_WOW64_32KEY + #define KEY_WOW64_32KEY 0x0200 +#endif + +#ifndef NWR_FLAGS + #define NWR_FLAGS KEY_WOW64_64KEY +#endif + +#ifndef NWR_SIZE + #define NWR_SIZE 4096 +#endif + +namespace nall { + +struct registry { + static auto exists(const string& name) -> bool { + auto part = name.split("\\"); + HKEY handle, rootKey = root(part.takeLeft()); + string node = part.takeRight(); + string path = part.merge("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + wchar_t data[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + LONG result = RegQueryValueExW(handle, utf16_t(node), nullptr, nullptr, (LPBYTE)&data, (LPDWORD)&size); + RegCloseKey(handle); + if(result == ERROR_SUCCESS) return true; + } + return false; + } + + static auto read(const string& name) -> string { + auto part = name.split("\\"); + HKEY handle, rootKey = root(part.takeLeft()); + string node = part.takeRight(); + string path = part.merge("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + wchar_t data[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + LONG result = RegQueryValueExW(handle, utf16_t(node), nullptr, nullptr, (LPBYTE)&data, (LPDWORD)&size); + RegCloseKey(handle); + if(result == ERROR_SUCCESS) return (const char*)utf8_t(data); + } + return ""; + } + + static auto write(const string& name, const string& data = "") -> void { + auto part = name.split("\\"); + HKEY handle, rootKey = root(part.takeLeft()); + string node = part.takeRight(), path; + DWORD disposition; + for(uint n = 0; n < part.size(); n++) { + path.append(part[n]); + if(RegCreateKeyExW(rootKey, utf16_t(path), 0, nullptr, 0, NWR_FLAGS | KEY_ALL_ACCESS, nullptr, &handle, &disposition) == ERROR_SUCCESS) { + if(n == part.size() - 1) { + RegSetValueExW(handle, utf16_t(node), 0, REG_SZ, (BYTE*)(wchar_t*)utf16_t(data), (data.length() + 1) * sizeof(wchar_t)); + } + RegCloseKey(handle); + } + path.append("\\"); + } + } + + static auto remove(const string& name) -> bool { + auto part = name.split("\\"); + HKEY rootKey = root(part.takeLeft()); + string node = part.takeRight(); + string path = part.merge("\\"); + if(!node) return SHDeleteKeyW(rootKey, utf16_t(path)) == ERROR_SUCCESS; + return SHDeleteValueW(rootKey, utf16_t(path), utf16_t(node)) == ERROR_SUCCESS; + } + + static auto contents(const string& name) -> vector { + vector result; + auto part = name.split("\\"); + HKEY handle, rootKey = root(part.takeLeft()); + part.removeRight(); + string path = part.merge("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + DWORD folders, nodes; + RegQueryInfoKey(handle, nullptr, nullptr, nullptr, &folders, nullptr, nullptr, &nodes, nullptr, nullptr, nullptr, nullptr); + for(uint n = 0; n < folders; n++) { + wchar_t name[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + RegEnumKeyEx(handle, n, (wchar_t*)&name, &size, nullptr, nullptr, nullptr, nullptr); + result.append(string{(const char*)utf8_t(name), "\\"}); + } + for(uint n = 0; n < nodes; n++) { + wchar_t name[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + RegEnumValueW(handle, n, (wchar_t*)&name, &size, nullptr, nullptr, nullptr, nullptr); + result.append((const char*)utf8_t(name)); + } + RegCloseKey(handle); + } + return result; + } + +private: + static auto root(const string& name) -> HKEY { + if(name == "HKCR") return HKEY_CLASSES_ROOT; + if(name == "HKCC") return HKEY_CURRENT_CONFIG; + if(name == "HKCU") return HKEY_CURRENT_USER; + if(name == "HKLM") return HKEY_LOCAL_MACHINE; + if(name == "HKU" ) return HKEY_USERS; + return nullptr; + } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/windows/service.hpp b/waterbox/bsnescore/bsnes/nall/windows/service.hpp new file mode 100644 index 0000000000..fa5d87f9f5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/windows/service.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace nall { + +struct service { + explicit operator bool() const { return false; } + auto command(const string& name, const string& command) -> bool { return false; } + auto receive() -> string { return ""; } + auto name() const -> string { return ""; } + auto stop() const -> bool { return false; } +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/windows/shared-memory.hpp b/waterbox/bsnescore/bsnes/nall/windows/shared-memory.hpp new file mode 100644 index 0000000000..aa74a70ab8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/windows/shared-memory.hpp @@ -0,0 +1,27 @@ +#pragma once + +namespace nall { + +struct shared_memory { + shared_memory() = default; + shared_memory(const shared_memory&) = delete; + auto operator=(const shared_memory&) -> shared_memory& = delete; + + ~shared_memory() { + reset(); + } + + explicit operator bool() const { return false; } + auto empty() const -> bool { return true; } + auto size() const -> uint { return 0; } + auto acquired() const -> bool { return false; } + auto acquire() -> uint8_t* { return nullptr; } + auto release() -> void {} + auto reset() -> void {} + auto create(const string& name, uint size) -> bool { return false; } + auto remove() -> void {} + auto open(const string& name, uint size) -> bool { return false; } + auto close() -> void {} +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/windows/utf8.hpp b/waterbox/bsnescore/bsnes/nall/windows/utf8.hpp new file mode 100644 index 0000000000..98d98fefea --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/windows/utf8.hpp @@ -0,0 +1,86 @@ +#pragma once + +using uint = unsigned; + +namespace nall { + //UTF-8 to UTF-16 + struct utf16_t { + utf16_t(const char* s = "") { operator=(s); } + ~utf16_t() { reset(); } + + utf16_t(const utf16_t&) = delete; + auto operator=(const utf16_t&) -> utf16_t& = delete; + + auto operator=(const char* s) -> utf16_t& { + reset(); + if(!s) s = ""; + length = MultiByteToWideChar(CP_UTF8, 0, s, -1, nullptr, 0); + buffer = new wchar_t[length + 1]; + MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); + buffer[length] = 0; + return *this; + } + + operator wchar_t*() { return buffer; } + operator const wchar_t*() const { return buffer; } + + auto reset() -> void { + delete[] buffer; + length = 0; + } + + auto data() -> wchar_t* { return buffer; } + auto data() const -> const wchar_t* { return buffer; } + + auto size() const -> uint { return length; } + + private: + wchar_t* buffer = nullptr; + uint length = 0; + }; + + //UTF-16 to UTF-8 + struct utf8_t { + utf8_t(const wchar_t* s = L"") { operator=(s); } + ~utf8_t() { reset(); } + + utf8_t(const utf8_t&) = delete; + auto operator=(const utf8_t&) -> utf8_t& = delete; + + auto operator=(const wchar_t* s) -> utf8_t& { + reset(); + if(!s) s = L""; + length = WideCharToMultiByte(CP_UTF8, 0, s, -1, nullptr, 0, nullptr, nullptr); + buffer = new char[length + 1]; + WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, nullptr, nullptr); + buffer[length] = 0; + return *this; + } + + auto reset() -> void { + delete[] buffer; + length = 0; + } + + operator char*() { return buffer; } + operator const char*() const { return buffer; } + + auto data() -> char* { return buffer; } + auto data() const -> const char* { return buffer; } + + auto size() const -> uint { return length; } + + private: + char* buffer = nullptr; + uint length = 0; + }; + + inline auto utf8_arguments(int& argc, char**& argv) -> void { + wchar_t** wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + argv = new char*[argc + 1](); + for(uint i = 0; i < argc; i++) { + argv[i] = new char[PATH_MAX]; + strcpy(argv[i], nall::utf8_t(wargv[i])); + } + } +} diff --git a/waterbox/bsnescore/bsnes/nall/xorg/clipboard.hpp b/waterbox/bsnescore/bsnes/nall/xorg/clipboard.hpp new file mode 100644 index 0000000000..09f06fd98a --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/xorg/clipboard.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace nall::Clipboard { + +auto clear() -> void { + XDisplay display; + if(auto atom = XInternAtom(display, "CLIPBOARD", XlibTrue)) { + XSetSelectionOwner(display, atom, XlibNone, XlibCurrentTime); + } +} + +} diff --git a/waterbox/bsnescore/bsnes/nall/xorg/guard.hpp b/waterbox/bsnescore/bsnes/nall/xorg/guard.hpp new file mode 100644 index 0000000000..2ff49b7501 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/xorg/guard.hpp @@ -0,0 +1,51 @@ +#ifndef NALL_XORG_GUARD_HPP +#define NALL_XORG_GUARD_HPP + +#define Atom XlibAtom +#define Display XlibDisplay +#define Font XlibFont +#define Screen XlibScreen +#define Window XlibWindow + +#else +#undef NALL_XORG_GUARD_HPP + +#undef Atom +#undef Display +#undef Font +#undef Screen +#undef Window + +#undef Above +#undef Below +#undef Bool + +#ifndef NALL_XORG_GUARD_CONSTANTS +#define NALL_XORG_GUARD_CONSTANTS +enum XlibConstants : int { + XlibButton1 = Button1, + XlibButton2 = Button2, + XlibButton3 = Button3, + XlibButton4 = Button4, + XlibButton5 = Button5, + XlibCurrentTime = CurrentTime, + XlibFalse = False, + XlibNone = None, + XlibTrue = True, +}; +#endif + +#undef Button1 +#undef Button2 +#undef Button3 +#undef Button4 +#undef Button5 +#undef CurrentTime +#undef False +#undef None +#undef True + +#undef MAX +#undef MIN + +#endif diff --git a/waterbox/bsnescore/bsnes/nall/xorg/xorg.hpp b/waterbox/bsnescore/bsnes/nall/xorg/xorg.hpp new file mode 100644 index 0000000000..b93eef0561 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/xorg/xorg.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +struct XDisplay { + XDisplay() { _display = XOpenDisplay(nullptr); } + ~XDisplay() { XCloseDisplay(_display); } + operator XlibDisplay*() const { return _display; } + +private: + XlibDisplay* _display; +}; diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/algorithms.cpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/algorithms.cpp new file mode 100644 index 0000000000..d646e17ec1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/algorithms.cpp @@ -0,0 +1,96 @@ +auto ARM7TDMI::ADD(uint32 source, uint32 modify, bool carry) -> uint32 { + uint32 result = source + modify + carry; + if(cpsr().t || (opcode & 1 << 20)) { + uint32 overflow = ~(source ^ modify) & (source ^ result); + cpsr().v = 1 << 31 & (overflow); + cpsr().c = 1 << 31 & (overflow ^ source ^ modify ^ result); + cpsr().z = result == 0; + cpsr().n = result >> 31; + } + return result; +} + +auto ARM7TDMI::ASR(uint32 source, uint8 shift) -> uint32 { + carry = cpsr().c; + if(shift == 0) return source; + carry = shift > 32 ? source & 1 << 31 : source & 1 << shift - 1; + source = shift > 31 ? (int32)source >> 31 : (int32)source >> shift; + return source; +} + +auto ARM7TDMI::BIT(uint32 result) -> uint32 { + if(cpsr().t || (opcode & 1 << 20)) { + cpsr().c = carry; + cpsr().z = result == 0; + cpsr().n = result >> 31; + } + return result; +} + +auto ARM7TDMI::LSL(uint32 source, uint8 shift) -> uint32 { + carry = cpsr().c; + if(shift == 0) return source; + carry = shift > 32 ? 0 : source & 1 << 32 - shift; + source = shift > 31 ? 0 : source << shift; + return source; +} + +auto ARM7TDMI::LSR(uint32 source, uint8 shift) -> uint32 { + carry = cpsr().c; + if(shift == 0) return source; + carry = shift > 32 ? 0 : source & 1 << shift - 1; + source = shift > 31 ? 0 : source >> shift; + return source; +} + +auto ARM7TDMI::MUL(uint32 product, uint32 multiplicand, uint32 multiplier) -> uint32 { + idle(); + if(multiplier >> 8 && multiplier >> 8 != 0xffffff) idle(); + if(multiplier >> 16 && multiplier >> 16 != 0xffff) idle(); + if(multiplier >> 24 && multiplier >> 24 != 0xff) idle(); + product += multiplicand * multiplier; + if(cpsr().t || (opcode & 1 << 20)) { + cpsr().z = product == 0; + cpsr().n = product >> 31; + } + return product; +} + +auto ARM7TDMI::ROR(uint32 source, uint8 shift) -> uint32 { + carry = cpsr().c; + if(shift == 0) return source; + if(shift &= 31) source = source << 32 - shift | source >> shift; + carry = source & 1 << 31; + return source; +} + +auto ARM7TDMI::RRX(uint32 source) -> uint32 { + carry = source & 1; + return cpsr().c << 31 | source >> 1; +} + +auto ARM7TDMI::SUB(uint32 source, uint32 modify, bool carry) -> uint32 { + return ADD(source, ~modify, carry); +} + +auto ARM7TDMI::TST(uint4 mode) -> bool { + switch(mode) { + case 0: return cpsr().z == 1; //EQ (equal) + case 1: return cpsr().z == 0; //NE (not equal) + case 2: return cpsr().c == 1; //CS (carry set) + case 3: return cpsr().c == 0; //CC (carry clear) + case 4: return cpsr().n == 1; //MI (negative) + case 5: return cpsr().n == 0; //PL (positive) + case 6: return cpsr().v == 1; //VS (overflow) + case 7: return cpsr().v == 0; //VC (no overflow) + case 8: return cpsr().c == 1 && cpsr().z == 0; //HI (unsigned higher) + case 9: return cpsr().c == 0 || cpsr().z == 1; //LS (unsigned lower or same) + case 10: return cpsr().n == cpsr().v; //GE (signed greater than or equal) + case 11: return cpsr().n != cpsr().v; //LT (signed less than) + case 12: return cpsr().z == 0 && cpsr().n == cpsr().v; //GT (signed greater than) + case 13: return cpsr().z == 1 || cpsr().n != cpsr().v; //LE (signed less than or equal) + case 14: return true; //AL (always) + case 15: return false; //NV (never) + } + unreachable; +} diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/arm7tdmi.cpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/arm7tdmi.cpp new file mode 100644 index 0000000000..e3dc0d2827 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/arm7tdmi.cpp @@ -0,0 +1,30 @@ +#include +#include "arm7tdmi.hpp" + +namespace Processor { + +#include "registers.cpp" +#include "memory.cpp" +#include "algorithms.cpp" +#include "instruction.cpp" +#include "instructions-arm.cpp" +#include "instructions-thumb.cpp" +#include "serialization.cpp" +#include "disassembler.cpp" + +ARM7TDMI::ARM7TDMI() { + armInitialize(); + thumbInitialize(); +} + +auto ARM7TDMI::power() -> void { + processor = {}; + processor.r15.modify = [&] { pipeline.reload = true; }; + pipeline = {}; + carry = 0; + irq = 0; + cpsr().f = 1; + exception(PSR::SVC, 0x00); +} + +} diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/arm7tdmi.hpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/arm7tdmi.hpp new file mode 100644 index 0000000000..314077549e --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/arm7tdmi.hpp @@ -0,0 +1,285 @@ +//ARMv4 (ARM7TDMI) + +#pragma once + +namespace Processor { + +struct ARM7TDMI { + enum : uint { + Nonsequential = 1 << 0, //N cycle + Sequential = 1 << 1, //S cycle + Prefetch = 1 << 2, //instruction fetch + Byte = 1 << 3, // 8-bit access + Half = 1 << 4, //16-bit access + Word = 1 << 5, //32-bit access + Load = 1 << 6, //load operation + Store = 1 << 7, //store operation + Signed = 1 << 8, //sign-extend + }; + + virtual auto step(uint clocks) -> void = 0; + virtual auto sleep() -> void = 0; + virtual auto get(uint mode, uint32 address) -> uint32 = 0; + virtual auto set(uint mode, uint32 address, uint32 word) -> void = 0; + + //arm7tdmi.cpp + ARM7TDMI(); + auto power() -> void; + + //registers.cpp + struct GPR; + struct PSR; + inline auto r(uint4) -> GPR&; + inline auto cpsr() -> PSR&; + inline auto spsr() -> PSR&; + inline auto privileged() const -> bool; + inline auto exception() const -> bool; + + //memory.cpp + auto idle() -> void; + auto read(uint mode, uint32 address) -> uint32; + auto load(uint mode, uint32 address) -> uint32; + auto write(uint mode, uint32 address, uint32 word) -> void; + auto store(uint mode, uint32 address, uint32 word) -> void; + + //algorithms.cpp + auto ADD(uint32, uint32, bool) -> uint32; + auto ASR(uint32, uint8) -> uint32; + auto BIT(uint32) -> uint32; + auto LSL(uint32, uint8) -> uint32; + auto LSR(uint32, uint8) -> uint32; + auto MUL(uint32, uint32, uint32) -> uint32; + auto ROR(uint32, uint8) -> uint32; + auto RRX(uint32) -> uint32; + auto SUB(uint32, uint32, bool) -> uint32; + auto TST(uint4) -> bool; + + //instruction.cpp + auto fetch() -> void; + auto instruction() -> void; + auto exception(uint mode, uint32 address) -> void; + auto armInitialize() -> void; + auto thumbInitialize() -> void; + + //instructions-arm.cpp + auto armALU(uint4 mode, uint4 target, uint4 source, uint32 data) -> void; + auto armMoveToStatus(uint4 field, uint1 source, uint32 data) -> void; + + auto armInstructionBranch(int24, uint1) -> void; + auto armInstructionBranchExchangeRegister(uint4) -> void; + auto armInstructionDataImmediate(uint8, uint4, uint4, uint4, uint1, uint4) -> void; + auto armInstructionDataImmediateShift(uint4, uint2, uint5, uint4, uint4, uint1, uint4) -> void; + auto armInstructionDataRegisterShift(uint4, uint2, uint4, uint4, uint4, uint1, uint4) -> void; + auto armInstructionLoadImmediate(uint8, uint1, uint4, uint4, uint1, uint1, uint1) -> void; + auto armInstructionLoadRegister(uint4, uint1, uint4, uint4, uint1, uint1, uint1) -> void; + auto armInstructionMemorySwap(uint4, uint4, uint4, uint1) -> void; + auto armInstructionMoveHalfImmediate(uint8, uint4, uint4, uint1, uint1, uint1, uint1) -> void; + auto armInstructionMoveHalfRegister(uint4, uint4, uint4, uint1, uint1, uint1, uint1) -> void; + auto armInstructionMoveImmediateOffset(uint12, uint4, uint4, uint1, uint1, uint1, uint1, uint1) -> void; + auto armInstructionMoveMultiple(uint16, uint4, uint1, uint1, uint1, uint1, uint1) -> void; + auto armInstructionMoveRegisterOffset(uint4, uint2, uint5, uint4, uint4, uint1, uint1, uint1, uint1, uint1) -> void; + auto armInstructionMoveToRegisterFromStatus(uint4, uint1) -> void; + auto armInstructionMoveToStatusFromImmediate(uint8, uint4, uint4, uint1) -> void; + auto armInstructionMoveToStatusFromRegister(uint4, uint4, uint1) -> void; + auto armInstructionMultiply(uint4, uint4, uint4, uint4, uint1, uint1) -> void; + auto armInstructionMultiplyLong(uint4, uint4, uint4, uint4, uint1, uint1, uint1) -> void; + auto armInstructionSoftwareInterrupt(uint24 immediate) -> void; + auto armInstructionUndefined() -> void; + + //instructions-thumb.cpp + auto thumbInstructionALU(uint3, uint3, uint4) -> void; + auto thumbInstructionALUExtended(uint4, uint4, uint2) -> void; + auto thumbInstructionAddRegister(uint8, uint3, uint1) -> void; + auto thumbInstructionAdjustImmediate(uint3, uint3, uint3, uint1) -> void; + auto thumbInstructionAdjustRegister(uint3, uint3, uint3, uint1) -> void; + auto thumbInstructionAdjustStack(uint7, uint1) -> void; + auto thumbInstructionBranchExchange(uint4) -> void; + auto thumbInstructionBranchFarPrefix(int11) -> void; + auto thumbInstructionBranchFarSuffix(uint11) -> void; + auto thumbInstructionBranchNear(int11) -> void; + auto thumbInstructionBranchTest(int8, uint4) -> void; + auto thumbInstructionImmediate(uint8, uint3, uint2) -> void; + auto thumbInstructionLoadLiteral(uint8, uint3) -> void; + auto thumbInstructionMoveByteImmediate(uint3, uint3, uint5, uint1) -> void; + auto thumbInstructionMoveHalfImmediate(uint3, uint3, uint5, uint1) -> void; + auto thumbInstructionMoveMultiple(uint8, uint3, uint1) -> void; + auto thumbInstructionMoveRegisterOffset(uint3, uint3, uint3, uint3) -> void; + auto thumbInstructionMoveStack(uint8, uint3, uint1) -> void; + auto thumbInstructionMoveWordImmediate(uint3, uint3, uint5, uint1) -> void; + auto thumbInstructionShiftImmediate(uint3, uint3, uint5, uint2) -> void; + auto thumbInstructionSoftwareInterrupt(uint8) -> void; + auto thumbInstructionStackMultiple(uint8, uint1, uint1) -> void; + auto thumbInstructionUndefined() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + //disassembler.cpp + auto disassemble(maybe pc = nothing, maybe thumb = nothing) -> string; + auto disassembleRegisters() -> string; + + struct GPR { + inline operator uint32_t() const { return data; } + inline auto operator=(const GPR& value) -> GPR& { return operator=(value.data); } + + inline auto operator=(uint32 value) -> GPR& { + data = value; + if(modify) modify(); + return *this; + } + + uint32 data; + function void> modify; + }; + + struct PSR { + enum : uint { + USR = 0x10, //user + FIQ = 0x11, //fast interrupt + IRQ = 0x12, //interrupt + SVC = 0x13, //service + ABT = 0x17, //abort + UND = 0x1b, //undefined + SYS = 0x1f, //system + }; + + inline operator uint32_t() const { + return m << 0 | t << 5 | f << 6 | i << 7 | v << 28 | c << 29 | z << 30 | n << 31; + } + + inline auto operator=(uint32 data) -> PSR& { + m = data >> 0 & 31; + t = data >> 5 & 1; + f = data >> 6 & 1; + i = data >> 7 & 1; + v = data >> 28 & 1; + c = data >> 29 & 1; + z = data >> 30 & 1; + n = data >> 31 & 1; + return *this; + } + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint5 m; //mode + boolean t; //thumb + boolean f; //fiq + boolean i; //irq + boolean v; //overflow + boolean c; //carry + boolean z; //zero + boolean n; //negative + }; + + struct Processor { + //serialization.cpp + auto serialize(serializer&) -> void; + + GPR r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15; + PSR cpsr; + + struct FIQ { + GPR r8, r9, r10, r11, r12, r13, r14; + PSR spsr; + } fiq; + + struct IRQ { + GPR r13, r14; + PSR spsr; + } irq; + + struct SVC { + GPR r13, r14; + PSR spsr; + } svc; + + struct ABT { + GPR r13, r14; + PSR spsr; + } abt; + + struct UND { + GPR r13, r14; + PSR spsr; + } und; + } processor; + + struct Pipeline { + //serialization.cpp + auto serialize(serializer&) -> void; + + struct Instruction { + uint32 address; + uint32 instruction; + boolean thumb; //not used by fetch stage + }; + + uint1 reload = 1; + uint1 nonsequential = 1; + Instruction fetch; + Instruction decode; + Instruction execute; + } pipeline; + + uint32 opcode; + boolean carry; + boolean irq; + + function void> armInstruction[4096]; + function void> thumbInstruction[65536]; + + //disassembler.cpp + auto armDisassembleBranch(int24, uint1) -> string; + auto armDisassembleBranchExchangeRegister(uint4) -> string; + auto armDisassembleDataImmediate(uint8, uint4, uint4, uint4, uint1, uint4) -> string; + auto armDisassembleDataImmediateShift(uint4, uint2, uint5, uint4, uint4, uint1, uint4) -> string; + auto armDisassembleDataRegisterShift(uint4, uint2, uint4, uint4, uint4, uint1, uint4) -> string; + auto armDisassembleLoadImmediate(uint8, uint1, uint4, uint4, uint1, uint1, uint1) -> string; + auto armDisassembleLoadRegister(uint4, uint1, uint4, uint4, uint1, uint1, uint1) -> string; + auto armDisassembleMemorySwap(uint4, uint4, uint4, uint1) -> string; + auto armDisassembleMoveHalfImmediate(uint8, uint4, uint4, uint1, uint1, uint1, uint1) -> string; + auto armDisassembleMoveHalfRegister(uint4, uint4, uint4, uint1, uint1, uint1, uint1) -> string; + auto armDisassembleMoveImmediateOffset(uint12, uint4, uint4, uint1, uint1, uint1, uint1, uint1) -> string; + auto armDisassembleMoveMultiple(uint16, uint4, uint1, uint1, uint1, uint1, uint1) -> string; + auto armDisassembleMoveRegisterOffset(uint4, uint2, uint5, uint4, uint4, uint1, uint1, uint1, uint1, uint1) -> string; + auto armDisassembleMoveToRegisterFromStatus(uint4, uint1) -> string; + auto armDisassembleMoveToStatusFromImmediate(uint8, uint4, uint4, uint1) -> string; + auto armDisassembleMoveToStatusFromRegister(uint4, uint4, uint1) -> string; + auto armDisassembleMultiply(uint4, uint4, uint4, uint4, uint1, uint1) -> string; + auto armDisassembleMultiplyLong(uint4, uint4, uint4, uint4, uint1, uint1, uint1) -> string; + auto armDisassembleSoftwareInterrupt(uint24) -> string; + auto armDisassembleUndefined() -> string; + + auto thumbDisassembleALU(uint3, uint3, uint4) -> string; + auto thumbDisassembleALUExtended(uint4, uint4, uint2) -> string; + auto thumbDisassembleAddRegister(uint8, uint3, uint1) -> string; + auto thumbDisassembleAdjustImmediate(uint3, uint3, uint3, uint1) -> string; + auto thumbDisassembleAdjustRegister(uint3, uint3, uint3, uint1) -> string; + auto thumbDisassembleAdjustStack(uint7, uint1) -> string; + auto thumbDisassembleBranchExchange(uint4) -> string; + auto thumbDisassembleBranchFarPrefix(int11) -> string; + auto thumbDisassembleBranchFarSuffix(uint11) -> string; + auto thumbDisassembleBranchNear(int11) -> string; + auto thumbDisassembleBranchTest(int8, uint4) -> string; + auto thumbDisassembleImmediate(uint8, uint3, uint2) -> string; + auto thumbDisassembleLoadLiteral(uint8, uint3) -> string; + auto thumbDisassembleMoveByteImmediate(uint3, uint3, uint5, uint1) -> string; + auto thumbDisassembleMoveHalfImmediate(uint3, uint3, uint5, uint1) -> string; + auto thumbDisassembleMoveMultiple(uint8, uint3, uint1) -> string; + auto thumbDisassembleMoveRegisterOffset(uint3, uint3, uint3, uint3) -> string; + auto thumbDisassembleMoveStack(uint8, uint3, uint1) -> string; + auto thumbDisassembleMoveWordImmediate(uint3, uint3, uint5, uint1) -> string; + auto thumbDisassembleShiftImmediate(uint3, uint3, uint5, uint2) -> string; + auto thumbDisassembleSoftwareInterrupt(uint8) -> string; + auto thumbDisassembleStackMultiple(uint8, uint1, uint1) -> string; + auto thumbDisassembleUndefined() -> string; + + function string> armDisassemble[4096]; + function string> thumbDisassemble[65536]; + + uint32 _pc; + string _c; +}; + +} diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/disassembler.cpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/disassembler.cpp new file mode 100644 index 0000000000..6f70097d73 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/disassembler.cpp @@ -0,0 +1,413 @@ +static const string _r[] = { + "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", + "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc" +}; +static const string _conditions[] = { + "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", + "hi", "ls", "ge", "lt", "gt", "le", "", "nv", +}; +#define _s save ? "s" : "" +#define _move(mode) (mode == 13 || mode == 15) +#define _comp(mode) (mode >= 8 && mode <= 11) +#define _math(mode) (mode <= 7 || mode == 12 || mode == 14) + +auto ARM7TDMI::disassemble(maybe pc, maybe thumb) -> string { + if(!pc) pc = pipeline.execute.address; + if(!thumb) thumb = cpsr().t; + + _pc = pc(); + if(!thumb()) { + uint32 opcode = read(Word | Nonsequential, _pc & ~3); + uint12 index = (opcode & 0x0ff00000) >> 16 | (opcode & 0x000000f0) >> 4; + _c = _conditions[opcode >> 28]; + return {hex(_pc, 8L), " ", armDisassemble[index](opcode)}; + } else { + uint16 opcode = read(Half | Nonsequential, _pc & ~1); + return {hex(_pc, 8L), " ", thumbDisassemble[opcode]()}; + } +} + +auto ARM7TDMI::disassembleRegisters() -> string { + string output; + for(uint n : range(16)) { + output.append(_r[n], ":", hex(r(n), 8L), " "); + } + + output.append("cpsr:"); + output.append(cpsr().n ? "N" : "n"); + output.append(cpsr().z ? "Z" : "z"); + output.append(cpsr().c ? "C" : "c"); + output.append(cpsr().v ? "V" : "v", "/"); + output.append(cpsr().i ? "I" : "i"); + output.append(cpsr().f ? "F" : "f"); + output.append(cpsr().t ? "T" : "t", "/"); + output.append(hex(cpsr().m, 2L)); + if(cpsr().m == PSR::USR || cpsr().m == PSR::SYS) return output; + + output.append(" spsr:"); + output.append(spsr().n ? "N" : "n"); + output.append(spsr().z ? "Z" : "z"); + output.append(spsr().c ? "C" : "c"); + output.append(spsr().v ? "V" : "v", "/"); + output.append(spsr().i ? "I" : "i"); + output.append(spsr().f ? "F" : "f"); + output.append(spsr().t ? "T" : "t", "/"); + output.append(hex(spsr().m, 2L)); + return output; +} + +// + +auto ARM7TDMI::armDisassembleBranch +(int24 displacement, uint1 link) -> string { + return {"b", link ? "l" : "", _c, " 0x", hex(_pc + 8 + displacement * 4, 8L)}; +} + +auto ARM7TDMI::armDisassembleBranchExchangeRegister +(uint4 m) -> string { + return {"bx", _c, " ", _r[m]}; +} + +auto ARM7TDMI::armDisassembleDataImmediate +(uint8 immediate, uint4 shift, uint4 d, uint4 n, uint1 save, uint4 mode) -> string { + static const string opcode[] = { + "and", "eor", "sub", "rsb", "add", "adc", "sbc", "rsc", + "tst", "teq", "cmp", "cmn", "orr", "mov", "bic", "mvn", + }; + uint32 data = immediate >> (shift << 1) | immediate << 32 - (shift << 1); + return {opcode[mode], _c, + _move(mode) ? string{_s, " ", _r[d]} : string{}, + _comp(mode) ? string{" ", _r[n]} : string{}, + _math(mode) ? string{_s, " ", _r[d], ",", _r[n]} : string{}, + ",#0x", hex(data, 8L)}; +} + +auto ARM7TDMI::armDisassembleDataImmediateShift +(uint4 m, uint2 type, uint5 shift, uint4 d, uint4 n, uint1 save, uint4 mode) -> string { + static const string opcode[] = { + "and", "eor", "sub", "rsb", "add", "adc", "sbc", "rsc", + "tst", "teq", "cmp", "cmn", "orr", "mov", "bic", "mvn", + }; + return {opcode[mode], _c, + _move(mode) ? string{_s, " ", _r[d]} : string{}, + _comp(mode) ? string{" ", _r[n]} : string{}, + _math(mode) ? string{_s, " ", _r[d], ",", _r[n]} : string{}, + ",", _r[m], + type == 0 && shift ? string{" lsl #", shift} : string{}, + type == 1 ? string{" lsr #", shift ? (uint)shift : 32} : string{}, + type == 2 ? string{" asr #", shift ? (uint)shift : 32} : string{}, + type == 3 && shift ? string{" ror #", shift} : string{}, + type == 3 && !shift ? " rrx" : ""}; +} + +auto ARM7TDMI::armDisassembleDataRegisterShift +(uint4 m, uint2 type, uint4 s, uint4 d, uint4 n, uint1 save, uint4 mode) -> string { + static const string opcode[] = { + "and", "eor", "sub", "rsb", "add", "adc", "sbc", "rsc", + "tst", "teq", "cmp", "cmn", "orr", "mov", "bic", "mvn", + }; + return {opcode[mode], _c, + _move(mode) ? string{_s, " ", _r[d]} : string{}, + _comp(mode) ? string{" ", _r[n]} : string{}, + _math(mode) ? string{_s, " ", _r[d], ",", _r[n]} : string{}, + ",", _r[m], " ", + type == 0 ? "lsl" : "", + type == 1 ? "lsr" : "", + type == 2 ? "asr" : "", + type == 3 ? "ror" : "", + " ", _r[s]}; +} + +auto ARM7TDMI::armDisassembleLoadImmediate +(uint8 immediate, uint1 half, uint4 d, uint4 n, uint1 writeback, uint1 up, uint1 pre) -> string { + string data; + if(n == 15) data = {" =0x", hex(read((half ? Half : Byte) | Nonsequential, + _pc + 8 + (up ? +immediate : -immediate)), half ? 4L : 2L)}; + + return {"ldr", _c, half ? "sh" : "sb", " ", + _r[d], ",[", _r[n], + pre == 0 ? "]" : "", + immediate ? string{",", up ? "+" : "-", "0x", hex(immediate, 2L)} : string{}, + pre == 1 ? "]" : "", + pre == 0 || writeback ? "!" : "", data}; +} + +auto ARM7TDMI::armDisassembleLoadRegister +(uint4 m, uint1 half, uint4 d, uint4 n, uint1 writeback, uint1 up, uint1 pre) -> string { + return {"ldr", _c, half ? "sh" : "sb", " ", + _r[d], ",[", _r[n], + pre == 0 ? "]" : "", + ",", up ? "+" : "-", _r[m], + pre == 1 ? "]" : "", + pre == 0 || writeback ? "!" : ""}; +} + +auto ARM7TDMI::armDisassembleMemorySwap +(uint4 m, uint4 d, uint4 n, uint1 byte) -> string { + return {"swp", _c, byte ? "b" : "", " ", _r[d], ",", _r[m], ",[", _r[n], "]"}; +} + +auto ARM7TDMI::armDisassembleMoveHalfImmediate +(uint8 immediate, uint4 d, uint4 n, uint1 mode, uint1 writeback, uint1 up, uint1 pre) -> string { + string data; + if(n == 15) data = {" =0x", hex(read(Half | Nonsequential, _pc + (up ? +immediate : -immediate)), 4L)}; + + return {mode ? "ldr" : "str", _c, "h ", + _r[d], ",[", _r[n], + pre == 0 ? "]" : "", + immediate ? string{",", up ? "+" : "-", "0x", hex(immediate, 2L)} : string{}, + pre == 1 ? "]" : "", + pre == 0 || writeback ? "!" : "", data}; +} + +auto ARM7TDMI::armDisassembleMoveHalfRegister +(uint4 m, uint4 d, uint4 n, uint1 mode, uint1 writeback, uint1 up, uint1 pre) -> string { + return {mode ? "ldr" : "str", _c, "h ", + _r[d], ",[", _r[n], + pre == 0 ? "]" : "", + ",", up ? "+" : "-", _r[m], + pre == 1 ? "]" : "", + pre == 0 || writeback ? "!" : ""}; +} + +auto ARM7TDMI::armDisassembleMoveImmediateOffset +(uint12 immediate, uint4 d, uint4 n, uint1 mode, uint1 writeback, uint1 byte, uint1 up, uint1 pre) -> string { + string data; + if(n == 15) data = {" =0x", hex(read((byte ? Byte : Word) | Nonsequential, + _pc + 8 + (up ? +immediate : -immediate)), byte ? 2L : 4L)}; + return {mode ? "ldr" : "str", _c, byte ? "b" : "", " ", _r[d], ",[", _r[n], + pre == 0 ? "]" : "", + immediate ? string{",", up ? "+" : "-", "0x", hex(immediate, 3L)} : string{}, + pre == 1 ? "]" : "", + pre == 0 || writeback ? "!" : "", data}; +} + +auto ARM7TDMI::armDisassembleMoveMultiple +(uint16 list, uint4 n, uint1 mode, uint1 writeback, uint1 type, uint1 up, uint1 pre) -> string { + string registers; + for(auto index : range(16)) { + if((list & 1 << index)) registers.append(_r[index], ","); + } + registers.trimRight(",", 1L); + return {mode ? "ldm" : "stm", _c, + up == 0 && pre == 0 ? "da" : "", + up == 0 && pre == 1 ? "db" : "", + up == 1 && pre == 0 ? "ia" : "", + up == 1 && pre == 1 ? "ib" : "", + " ", _r[n], writeback ? "!" : "", + ",{", registers, "}", type ? "^" : ""}; +} + +auto ARM7TDMI::armDisassembleMoveRegisterOffset +(uint4 m, uint2 type, uint5 shift, uint4 d, uint4 n, uint1 mode, uint1 writeback, uint1 byte, uint1 up, uint1 pre) -> string { + return {mode ? "ldr" : "str", _c, byte ? "b" : "", " ", _r[d], ",[", _r[n], + pre == 0 ? "]" : "", + ",", up ? "+" : "-", _r[m], + type == 0 && shift ? string{" lsl #", shift} : string{}, + type == 1 ? string{" lsr #", shift ? (uint)shift : 32} : string{}, + type == 2 ? string{" asr #", shift ? (uint)shift : 32} : string{}, + type == 3 && shift ? string{" ror #", shift} : string{}, + type == 3 && !shift ? " rrx" : "", + pre == 1 ? "]" : "", + pre == 0 || writeback ? "!" : ""}; +} + +auto ARM7TDMI::armDisassembleMoveToRegisterFromStatus +(uint4 d, uint1 mode) -> string { + return {"mrs", _c, " ", _r[d], ",", mode ? "spsr" : "cpsr"}; +} + +auto ARM7TDMI::armDisassembleMoveToStatusFromImmediate +(uint8 immediate, uint4 rotate, uint4 field, uint1 mode) -> string { + uint32 data = immediate >> (rotate << 1) | immediate << 32 - (rotate << 1); + return {"msr", _c, " ", mode ? "spsr:" : "cpsr:", + field.bit(0) ? "c" : "", + field.bit(1) ? "x" : "", + field.bit(2) ? "s" : "", + field.bit(3) ? "f" : "", + ",#0x", hex(data, 8L)}; +} + +auto ARM7TDMI::armDisassembleMoveToStatusFromRegister +(uint4 m, uint4 field, uint1 mode) -> string { + return {"msr", _c, " ", mode ? "spsr:" : "cpsr:", + field.bit(0) ? "c" : "", + field.bit(1) ? "x" : "", + field.bit(2) ? "s" : "", + field.bit(3) ? "f" : "", + ",", _r[m]}; +} + +auto ARM7TDMI::armDisassembleMultiply +(uint4 m, uint4 s, uint4 n, uint4 d, uint1 save, uint1 accumulate) -> string { + if(accumulate) { + return {"mla", _c, _s, " ", _r[d], ",", _r[m], ",", _r[s], ",", _r[n]}; + } else { + return {"mul", _c, _s, " ", _r[d], ",", _r[m], ",", _r[s]}; + } +} + +auto ARM7TDMI::armDisassembleMultiplyLong +(uint4 m, uint4 s, uint4 l, uint4 h, uint1 save, uint1 accumulate, uint1 sign) -> string { + return {sign ? "s" : "u", accumulate ? "mlal" : "mull", _c, _s, " ", + _r[l], ",", _r[h], ",", _r[m], ",", _r[s]}; +} + +auto ARM7TDMI::armDisassembleSoftwareInterrupt +(uint24 immediate) -> string { + return {"swi #0x", hex(immediate, 6L)}; +} + +auto ARM7TDMI::armDisassembleUndefined +() -> string { + return {"undefined"}; +} + +// + +auto ARM7TDMI::thumbDisassembleALU +(uint3 d, uint3 m, uint4 mode) -> string { + static const string opcode[] = { + "and", "eor", "lsl", "lsr", "asr", "adc", "sbc", "ror", + "tst", "neg", "cmp", "cmn", "orr", "mul", "bic", "mvn", + }; + return {opcode[mode], " ", _r[d], ",", _r[m]}; +} + +auto ARM7TDMI::thumbDisassembleALUExtended +(uint4 d, uint4 m, uint2 mode) -> string { + static const string opcode[] = {"add", "sub", "mov"}; + if(d == 8 && m == 8 && mode == 2) return {"nop"}; + return {opcode[mode], " ", _r[d], ",", _r[m]}; +} + +auto ARM7TDMI::thumbDisassembleAddRegister +(uint8 immediate, uint3 d, uint1 mode) -> string { + return {"add ", _r[d], ",", mode ? "sp" : "pc", ",#0x", hex(immediate, 2L)}; +} + +auto ARM7TDMI::thumbDisassembleAdjustImmediate +(uint3 d, uint3 n, uint3 immediate, uint1 mode) -> string { + return {!mode ? "add" : "sub", " ", _r[d], ",", _r[n], ",#", immediate}; +} + +auto ARM7TDMI::thumbDisassembleAdjustRegister +(uint3 d, uint3 n, uint3 m, uint1 mode) -> string { + return {!mode ? "add" : "sub", " ", _r[d], ",", _r[n], ",", _r[m]}; +} + +auto ARM7TDMI::thumbDisassembleAdjustStack +(uint7 immediate, uint1 mode) -> string { + return {!mode ? "add" : "sub", " sp,#0x", hex(immediate * 4, 3L)}; +} + +auto ARM7TDMI::thumbDisassembleBranchExchange +(uint4 m) -> string { + return {"bx ", _r[m]}; +} + +auto ARM7TDMI::thumbDisassembleBranchFarPrefix +(int11 displacementHi) -> string { + uint11 displacementLo = read(Half | Nonsequential, (_pc & ~1) + 2); + int22 displacement = displacementHi << 11 | displacementLo << 0; + uint32 address = _pc + 4 + displacement * 2; + return {"bl 0x", hex(address, 8L)}; +} + +auto ARM7TDMI::thumbDisassembleBranchFarSuffix +(uint11 displacement) -> string { + return {"bl (suffix)"}; +} + +auto ARM7TDMI::thumbDisassembleBranchNear +(int11 displacement) -> string { + uint32 address = _pc + 4 + displacement * 2; + return {"b 0x", hex(address, 8L)}; +} + +auto ARM7TDMI::thumbDisassembleBranchTest +(int8 displacement, uint4 condition) -> string { + uint32 address = _pc + 4 + displacement * 2; + return {"b", _conditions[condition], " 0x", hex(address, 8L)}; +} + +auto ARM7TDMI::thumbDisassembleImmediate +(uint8 immediate, uint3 d, uint2 mode) -> string { + static const string opcode[] = {"mov", "cmp", "add", "sub"}; + return {opcode[mode], " ", _r[d], ",#0x", hex(immediate, 2L)}; +} + +auto ARM7TDMI::thumbDisassembleLoadLiteral +(uint8 displacement, uint3 d) -> string { + uint32 address = ((_pc + 4) & ~3) + (displacement << 2); + uint32 data = read(Word | Nonsequential, address); + return {"ldr ", _r[d], ",[pc,#0x", hex(address, 8L), "] =0x", hex(data, 8L)}; +} + +auto ARM7TDMI::thumbDisassembleMoveByteImmediate +(uint3 d, uint3 n, uint5 offset, uint1 mode) -> string { + return {mode ? "ldrb" : "strb", " ", _r[d], ",[", _r[n], ",#0x", hex(offset, 2L), "]"}; +} + +auto ARM7TDMI::thumbDisassembleMoveHalfImmediate +(uint3 d, uint3 n, uint5 offset, uint1 mode) -> string { + return {mode ? "ldrh" : "strh", " ", _r[d], ",[", _r[n], ",#0x", hex(offset * 2, 2L), "]"}; +} + +auto ARM7TDMI::thumbDisassembleMoveMultiple +(uint8 list, uint3 n, uint1 mode) -> string { + string registers; + for(uint m : range(8)) { + if((list & 1 << m)) registers.append(_r[m], ","); + } + registers.trimRight(",", 1L); + return {mode ? "ldmia" : "stmia", " ", _r[n], "!,{", registers, "}"}; +} + +auto ARM7TDMI::thumbDisassembleMoveRegisterOffset +(uint3 d, uint3 n, uint3 m, uint3 mode) -> string { + static const string opcode[] = {"str", "strh", "strb", "ldsb", "ldr", "ldrh", "ldrb", "ldsh"}; + return {opcode[mode], " ", _r[d], ",[", _r[n], ",", _r[m], "]"}; +} + +auto ARM7TDMI::thumbDisassembleMoveStack +(uint8 immediate, uint3 d, uint1 mode) -> string { + return {mode ? "ldr" : "str", " ", _r[d], ",[sp,#0x", hex(immediate * 4, 3L), "]"}; +} + +auto ARM7TDMI::thumbDisassembleMoveWordImmediate +(uint3 d, uint3 n, uint5 offset, uint1 mode) -> string { + return {mode ? "ldr" : "str", " ", _r[d], ",[", _r[n], ",#0x", hex(offset * 4, 2L), "]"}; +} + +auto ARM7TDMI::thumbDisassembleShiftImmediate +(uint3 d, uint3 m, uint5 immediate, uint2 mode) -> string { + static const string opcode[] = {"lsl", "lsr", "asr"}; + return {opcode[mode], " ", _r[d], ",", _r[m], ",#", immediate}; +} + +auto ARM7TDMI::thumbDisassembleSoftwareInterrupt +(uint8 immediate) -> string { + return {"swi #0x", hex(immediate, 2L)}; +} + +auto ARM7TDMI::thumbDisassembleStackMultiple +(uint8 list, uint1 lrpc, uint1 mode) -> string { + string registers; + for(uint m : range(8)) { + if((list & 1 << m)) registers.append(_r[m], ","); + } + if(lrpc) registers.append(!mode ? "lr," : "pc,"); + registers.trimRight(",", 1L); + return {!mode ? "push" : "pop", " {", registers, "}"}; +} + +auto ARM7TDMI::thumbDisassembleUndefined +() -> string { + return {"undefined"}; +} + +#undef _s +#undef _move +#undef _comp +#undef _save diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/instruction.cpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/instruction.cpp new file mode 100644 index 0000000000..f622b0e181 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/instruction.cpp @@ -0,0 +1,544 @@ +auto ARM7TDMI::fetch() -> void { + pipeline.execute = pipeline.decode; + pipeline.decode = pipeline.fetch; + pipeline.decode.thumb = cpsr().t; + + uint sequential = Sequential; + if(pipeline.nonsequential) { + pipeline.nonsequential = false; + sequential = Nonsequential; + } + + uint mask = !cpsr().t ? 3 : 1; + uint size = !cpsr().t ? Word : Half; + + r(15).data += size >> 3; + pipeline.fetch.address = r(15) & ~mask; + pipeline.fetch.instruction = read(Prefetch | size | sequential, pipeline.fetch.address); +} + +auto ARM7TDMI::instruction() -> void { + uint mask = !cpsr().t ? 3 : 1; + uint size = !cpsr().t ? Word : Half; + + if(pipeline.reload) { + pipeline.reload = false; + r(15).data &= ~mask; + pipeline.fetch.address = r(15) & ~mask; + pipeline.fetch.instruction = read(Prefetch | size | Nonsequential, pipeline.fetch.address); + fetch(); + } + fetch(); + + if(irq && !cpsr().i) { + exception(PSR::IRQ, 0x18); + if(pipeline.execute.thumb) r(14).data += 2; + return; + } + + opcode = pipeline.execute.instruction; + if(!pipeline.execute.thumb) { + if(!TST(opcode >> 28)) return; + uint12 index = (opcode & 0x0ff00000) >> 16 | (opcode & 0x000000f0) >> 4; + armInstruction[index](opcode); + } else { + thumbInstruction[(uint16)opcode](); + } +} + +auto ARM7TDMI::exception(uint mode, uint32 address) -> void { + auto psr = cpsr(); + cpsr().m = mode; + spsr() = psr; + cpsr().t = 0; + if(cpsr().m == PSR::FIQ) cpsr().f = 1; + cpsr().i = 1; + r(14) = pipeline.decode.address; + r(15) = address; +} + +auto ARM7TDMI::armInitialize() -> void { + #define bind(id, name, ...) { \ + uint index = (id & 0x0ff00000) >> 16 | (id & 0x000000f0) >> 4; \ + assert(!armInstruction[index]); \ + armInstruction[index] = [&](uint32 opcode) { return armInstruction##name(arguments); }; \ + armDisassemble[index] = [&](uint32 opcode) { return armDisassemble##name(arguments); }; \ + } + + #define pattern(s) \ + std::integral_constant::value + + #define bit1(value, index) (value >> index & 1) + #define bits(value, lo, hi) (value >> lo & (1ull << (hi - lo + 1)) - 1) + + #define arguments \ + bits(opcode, 0,23), /* displacement */ \ + bit1(opcode,24) /* link */ + for(uint4 displacementLo : range(16)) + for(uint4 displacementHi : range(16)) + for(uint1 link : range(2)) { + auto opcode = pattern(".... 101? ???? ???? ???? ???? ???? ????") + | displacementLo << 4 | displacementHi << 20 | link << 24; + bind(opcode, Branch); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3) /* m */ + { + auto opcode = pattern(".... 0001 0010 ---- ---- ---- 0001 ????"); + bind(opcode, BranchExchangeRegister); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 7), /* immediate */ \ + bits(opcode, 8,11), /* shift */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,20), /* save */ \ + bits(opcode,21,24) /* mode */ + for(uint4 shiftHi : range(16)) + for(uint1 save : range(2)) + for(uint4 mode : range(16)) { + if(mode >= 8 && mode <= 11 && !save) continue; //TST, TEQ, CMP, CMN + auto opcode = pattern(".... 001? ???? ???? ???? ???? ???? ????") | shiftHi << 4 | save << 20 | mode << 21; + bind(opcode, DataImmediate); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3), /* m */ \ + bits(opcode, 5, 6), /* type */ \ + bits(opcode, 7,11), /* shift */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,20), /* save */ \ + bits(opcode,21,24) /* mode */ + for(uint2 type : range(4)) + for(uint1 shiftLo : range(2)) + for(uint1 save : range(2)) + for(uint4 mode : range(16)) { + if(mode >= 8 && mode <= 11 && !save) continue; //TST, TEQ, CMP, CMN + auto opcode = pattern(".... 000? ???? ???? ???? ???? ???0 ????") | type << 5 | shiftLo << 7 | save << 20 | mode << 21; + bind(opcode, DataImmediateShift); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3), /* m */ \ + bits(opcode, 5, 6), /* type */ \ + bits(opcode, 8,11), /* s */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,20), /* save */ \ + bits(opcode,21,24) /* mode */ + for(uint2 type : range(4)) + for(uint1 save : range(2)) + for(uint4 mode : range(16)) { + if(mode >= 8 && mode <= 11 && !save) continue; //TST, TEQ, CMP, CMN + auto opcode = pattern(".... 000? ???? ???? ???? ???? 0??1 ????") | type << 5 | save << 20 | mode << 21; + bind(opcode, DataRegisterShift); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3) << 0 | bits(opcode, 8,11) << 4, /* immediate */ \ + bit1(opcode, 5), /* half */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,21), /* writeback */ \ + bit1(opcode,23), /* up */ \ + bit1(opcode,24) /* pre */ + for(uint1 half : range(2)) + for(uint1 writeback : range(2)) + for(uint1 up : range(2)) + for(uint1 pre : range(2)) { + auto opcode = pattern(".... 000? ?1?1 ???? ???? ???? 11?1 ????") | half << 5 | writeback << 21 | up << 23 | pre << 24; + bind(opcode, LoadImmediate); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3), /* m */ \ + bit1(opcode, 5), /* half */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,21), /* writeback */ \ + bit1(opcode,23), /* up */ \ + bit1(opcode,24) /* pre */ + for(uint1 half : range(2)) + for(uint1 writeback : range(2)) + for(uint1 up : range(2)) + for(uint1 pre : range(2)) { + auto opcode = pattern(".... 000? ?0?1 ???? ???? ---- 11?1 ????") | half << 5 | writeback << 21 | up << 23 | pre << 24; + bind(opcode, LoadRegister); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3), /* m */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,22) /* byte */ + for(uint1 byte : range(2)) { + auto opcode = pattern(".... 0001 0?00 ???? ???? ---- 1001 ????") | byte << 22; + bind(opcode, MemorySwap); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3) << 0 | bits(opcode, 8,11) << 4, /* immediate */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,20), /* mode */ \ + bit1(opcode,21), /* writeback */ \ + bit1(opcode,23), /* up */ \ + bit1(opcode,24) /* pre */ + for(uint1 mode : range(2)) + for(uint1 writeback : range(2)) + for(uint1 up : range(2)) + for(uint1 pre : range(2)) { + auto opcode = pattern(".... 000? ?1?? ???? ???? ???? 1011 ????") | mode << 20 | writeback << 21 | up << 23 | pre << 24; + bind(opcode, MoveHalfImmediate); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3), /* m */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,20), /* mode */ \ + bit1(opcode,21), /* writeback */ \ + bit1(opcode,23), /* up */ \ + bit1(opcode,24) /* pre */ + for(uint1 mode : range(2)) + for(uint1 writeback : range(2)) + for(uint1 up : range(2)) + for(uint1 pre : range(2)) { + auto opcode = pattern(".... 000? ?0?? ???? ???? ---- 1011 ????") | mode << 20 | writeback << 21 | up << 23 | pre << 24; + bind(opcode, MoveHalfRegister); + } + #undef arguments + + #define arguments \ + bits(opcode, 0,11), /* immediate */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,20), /* mode */ \ + bit1(opcode,21), /* writeback */ \ + bit1(opcode,22), /* byte */ \ + bit1(opcode,23), /* up */ \ + bit1(opcode,24) /* pre */ + for(uint4 immediatePart : range(16)) + for(uint1 mode : range(2)) + for(uint1 writeback : range(2)) + for(uint1 byte : range(2)) + for(uint1 up : range(2)) + for(uint1 pre : range(2)) { + auto opcode = pattern(".... 010? ???? ???? ???? ???? ???? ????") + | immediatePart << 4 | mode << 20 | writeback << 21 | byte << 22 | up << 23 | pre << 24; + bind(opcode, MoveImmediateOffset); + } + #undef arguments + + #define arguments \ + bits(opcode, 0,15), /* list */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,20), /* mode */ \ + bit1(opcode,21), /* writeback */ \ + bit1(opcode,22), /* type */ \ + bit1(opcode,23), /* up */ \ + bit1(opcode,24) /* pre */ + for(uint4 listPart : range(16)) + for(uint1 mode : range(2)) + for(uint1 writeback : range(2)) + for(uint1 type : range(2)) + for(uint1 up : range(2)) + for(uint1 pre : range(2)) { + auto opcode = pattern(".... 100? ???? ???? ???? ???? ???? ????") + | listPart << 4 | mode << 20 | writeback << 21 | type << 22 | up << 23 | pre << 24; + bind(opcode, MoveMultiple); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3), /* m */ \ + bits(opcode, 5, 6), /* type */ \ + bits(opcode, 7,11), /* shift */ \ + bits(opcode,12,15), /* d */ \ + bits(opcode,16,19), /* n */ \ + bit1(opcode,20), /* mode */ \ + bit1(opcode,21), /* writeback */ \ + bit1(opcode,22), /* byte */ \ + bit1(opcode,23), /* up */ \ + bit1(opcode,24) /* pre */ + for(uint2 type : range(4)) + for(uint1 shiftLo : range(2)) + for(uint1 mode : range(2)) + for(uint1 writeback : range(2)) + for(uint1 byte : range(2)) + for(uint1 up : range(2)) + for(uint1 pre : range(2)) { + auto opcode = pattern(".... 011? ???? ???? ???? ???? ???0 ????") + | type << 5 | shiftLo << 7 | mode << 20 | writeback << 21 | byte << 22 | up << 23 | pre << 24; + bind(opcode, MoveRegisterOffset); + } + #undef arguments + + #define arguments \ + bits(opcode,12,15), /* d */ \ + bit1(opcode,22) /* mode */ + for(uint1 mode : range(2)) { + auto opcode = pattern(".... 0001 0?00 ---- ???? ---- 0000 ----") | mode << 22; + bind(opcode, MoveToRegisterFromStatus); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 7), /* immediate */ \ + bits(opcode, 8,11), /* rotate */ \ + bits(opcode,16,19), /* field */ \ + bit1(opcode,22) /* mode */ + for(uint4 immediateHi : range(16)) + for(uint1 mode : range(2)) { + auto opcode = pattern(".... 0011 0?10 ???? ---- ???? ???? ????") | immediateHi << 4 | mode << 22; + bind(opcode, MoveToStatusFromImmediate); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3), /* m */ \ + bits(opcode,16,19), /* field */ \ + bit1(opcode,22) /* mode */ + for(uint1 mode : range(2)) { + auto opcode = pattern(".... 0001 0?10 ???? ---- ---- 0000 ????") | mode << 22; + bind(opcode, MoveToStatusFromRegister); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3), /* m */ \ + bits(opcode, 8,11), /* s */ \ + bits(opcode,12,15), /* n */ \ + bits(opcode,16,19), /* d */ \ + bit1(opcode,20), /* save */ \ + bit1(opcode,21) /* accumulate */ + for(uint1 save : range(2)) + for(uint1 accumulate : range(2)) { + auto opcode = pattern(".... 0000 00?? ???? ???? ???? 1001 ????") | save << 20 | accumulate << 21; + bind(opcode, Multiply); + } + #undef arguments + + #define arguments \ + bits(opcode, 0, 3), /* m */ \ + bits(opcode, 8,11), /* s */ \ + bits(opcode,12,15), /* l */ \ + bits(opcode,16,19), /* h */ \ + bit1(opcode,20), /* save */ \ + bit1(opcode,21), /* accumulate */ \ + bit1(opcode,22) /* sign */ + for(uint1 save : range(2)) + for(uint1 accumulate : range(2)) + for(uint1 sign : range(2)) { + auto opcode = pattern(".... 0000 1??? ???? ???? ???? 1001 ????") | save << 20 | accumulate << 21 | sign << 22; + bind(opcode, MultiplyLong); + } + #undef arguments + + #define arguments \ + bits(opcode, 0,23) /* immediate */ + for(uint4 immediateLo : range(16)) + for(uint4 immediateHi : range(16)) { + auto opcode = pattern(".... 1111 ???? ???? ???? ???? ???? ????") | immediateLo << 4 | immediateHi << 20; + bind(opcode, SoftwareInterrupt); + } + #undef arguments + + #define arguments + for(uint12 id : range(4096)) { + if(armInstruction[id]) continue; + auto opcode = pattern(".... ???? ???? ---- ---- ---- ???? ----") | bits(id,0,3) << 4 | bits(id,4,11) << 20; + bind(opcode, Undefined); + } + #undef arguments + + #undef bind + #undef pattern +} + +auto ARM7TDMI::thumbInitialize() -> void { + #define bind(id, name, ...) { \ + assert(!thumbInstruction[id]); \ + thumbInstruction[id] = [=] { return thumbInstruction##name(__VA_ARGS__); }; \ + thumbDisassemble[id] = [=] { return thumbDisassemble##name(__VA_ARGS__); }; \ + } + + #define pattern(s) \ + std::integral_constant::value + + for(uint3 d : range(8)) + for(uint3 m : range(8)) + for(uint4 mode : range(16)) { + auto opcode = pattern("0100 00?? ???? ????") | d << 0 | m << 3 | mode << 6; + bind(opcode, ALU, d, m, mode); + } + + for(uint4 d : range(16)) + for(uint4 m : range(16)) + for(uint2 mode : range(4)) { + if(mode == 3) continue; + auto opcode = pattern("0100 01?? ???? ????") | bits(d,0,2) << 0 | m << 3 | bit1(d,3) << 7 | mode << 8; + bind(opcode, ALUExtended, d, m, mode); + } + + for(uint8 immediate : range(256)) + for(uint3 d : range(8)) + for(uint1 mode : range(2)) { + auto opcode = pattern("1010 ???? ???? ????") | immediate << 0 | d << 8 | mode << 11; + bind(opcode, AddRegister, immediate, d, mode); + } + + for(uint3 d : range(8)) + for(uint3 n : range(8)) + for(uint3 immediate : range(8)) + for(uint1 mode : range(2)) { + auto opcode = pattern("0001 11?? ???? ????") | d << 0 | n << 3 | immediate << 6 | mode << 9; + bind(opcode, AdjustImmediate, d, n, immediate, mode); + } + + for(uint3 d : range(8)) + for(uint3 n : range(8)) + for(uint3 m : range(8)) + for(uint1 mode : range(2)) { + auto opcode = pattern("0001 10?? ???? ????") | d << 0 | n << 3 | m << 6 | mode << 9; + bind(opcode, AdjustRegister, d, n, m, mode); + } + + for(uint7 immediate : range(128)) + for(uint1 mode : range(2)) { + auto opcode = pattern("1011 0000 ???? ????") | immediate << 0 | mode << 7; + bind(opcode, AdjustStack, immediate, mode); + } + + for(uint3 _ : range(8)) + for(uint4 m : range(16)) { + auto opcode = pattern("0100 0111 0??? ?---") | _ << 0 | m << 3; + bind(opcode, BranchExchange, m); + } + + for(uint11 displacement : range(2048)) { + auto opcode = pattern("1111 0??? ???? ????") | displacement << 0; + bind(opcode, BranchFarPrefix, displacement); + } + + for(uint11 displacement : range(2048)) { + auto opcode = pattern("1111 1??? ???? ????") | displacement << 0; + bind(opcode, BranchFarSuffix, displacement); + } + + for(uint11 displacement : range(2048)) { + auto opcode = pattern("1110 0??? ???? ????") | displacement << 0; + bind(opcode, BranchNear, displacement); + } + + for(uint8 displacement : range(256)) + for(uint4 condition : range(16)) { + if(condition == 15) continue; //BNV + auto opcode = pattern("1101 ???? ???? ????") | displacement << 0 | condition << 8; + bind(opcode, BranchTest, displacement, condition); + } + + for(uint8 immediate : range(256)) + for(uint3 d : range(8)) + for(uint2 mode : range(4)) { + auto opcode = pattern("001? ???? ???? ????") | immediate << 0 | d << 8 | mode << 11; + bind(opcode, Immediate, immediate, d, mode); + } + + for(uint8 displacement : range(256)) + for(uint3 d : range(8)) { + auto opcode = pattern("0100 1??? ???? ????") | displacement << 0 | d << 8; + bind(opcode, LoadLiteral, displacement, d); + } + + for(uint3 d : range(8)) + for(uint3 n : range(8)) + for(uint5 immediate : range(32)) + for(uint1 mode : range(2)) { + auto opcode = pattern("0111 ???? ???? ????") | d << 0 | n << 3 | immediate << 6 | mode << 11; + bind(opcode, MoveByteImmediate, d, n, immediate, mode); + } + + for(uint3 d : range(8)) + for(uint3 n : range(8)) + for(uint5 immediate : range(32)) + for(uint1 mode : range(2)) { + auto opcode = pattern("1000 ???? ???? ????") | d << 0 | n << 3 | immediate << 6 | mode << 11; + bind(opcode, MoveHalfImmediate, d, n, immediate, mode); + } + + for(uint8 list : range(256)) + for(uint3 n : range(8)) + for(uint1 mode : range(2)) { + auto opcode = pattern("1100 ???? ???? ????") | list << 0 | n << 8 | mode << 11; + bind(opcode, MoveMultiple, list, n, mode); + } + + for(uint3 d : range(8)) + for(uint3 n : range(8)) + for(uint3 m : range(8)) + for(uint3 mode : range(8)) { + auto opcode = pattern("0101 ???? ???? ????") | d << 0 | n << 3 | m << 6 | mode << 9; + bind(opcode, MoveRegisterOffset, d, n, m, mode); + } + + for(uint8 immediate : range(256)) + for(uint3 d : range(8)) + for(uint1 mode : range(2)) { + auto opcode = pattern("1001 ???? ???? ????") | immediate << 0 | d << 8 | mode << 11; + bind(opcode, MoveStack, immediate, d, mode); + } + + for(uint3 d : range(8)) + for(uint3 n : range(8)) + for(uint5 offset : range(32)) + for(uint1 mode : range(2)) { + auto opcode = pattern("0110 ???? ???? ????") | d << 0 | n << 3 | offset << 6 | mode << 11; + bind(opcode, MoveWordImmediate, d, n, offset, mode); + } + + for(uint3 d : range(8)) + for(uint3 m : range(8)) + for(uint5 immediate : range(32)) + for(uint2 mode : range(4)) { + if(mode == 3) continue; + auto opcode = pattern("000? ???? ???? ????") | d << 0 | m << 3 | immediate << 6 | mode << 11; + bind(opcode, ShiftImmediate, d, m, immediate, mode); + } + + for(uint8 immediate : range(256)) { + auto opcode = pattern("1101 1111 ???? ????") | immediate << 0; + bind(opcode, SoftwareInterrupt, immediate); + } + + for(uint8 list : range(256)) + for(uint1 lrpc : range(2)) + for(uint1 mode : range(2)) { + auto opcode = pattern("1011 ?10? ???? ????") | list << 0 | lrpc << 8 | mode << 11; + bind(opcode, StackMultiple, list, lrpc, mode); + } + + for(uint16 id : range(65536)) { + if(thumbInstruction[id]) continue; + auto opcode = pattern("???? ???? ???? ????") | id << 0; + bind(opcode, Undefined); + } + + #undef bit1 + #undef bits + + #undef bind + #undef pattern +} diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/instructions-arm.cpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/instructions-arm.cpp new file mode 100644 index 0000000000..bdafc5e1c9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/instructions-arm.cpp @@ -0,0 +1,314 @@ +auto ARM7TDMI::armALU(uint4 mode, uint4 d, uint4 n, uint32 rm) -> void { + uint32 rn = r(n); + + switch(mode) { + case 0: r(d) = BIT(rn & rm); break; //AND + case 1: r(d) = BIT(rn ^ rm); break; //EOR + case 2: r(d) = SUB(rn, rm, 1); break; //SUB + case 3: r(d) = SUB(rm, rn, 1); break; //RSB + case 4: r(d) = ADD(rn, rm, 0); break; //ADD + case 5: r(d) = ADD(rn, rm, cpsr().c); break; //ADC + case 6: r(d) = SUB(rn, rm, cpsr().c); break; //SBC + case 7: r(d) = SUB(rm, rn, cpsr().c); break; //RSC + case 8: BIT(rn & rm); break; //TST + case 9: BIT(rn ^ rm); break; //TEQ + case 10: SUB(rn, rm, 1); break; //CMP + case 11: ADD(rn, rm, 0); break; //CMN + case 12: r(d) = BIT(rn | rm); break; //ORR + case 13: r(d) = BIT(rm); break; //MOV + case 14: r(d) = BIT(rn & ~rm); break; //BIC + case 15: r(d) = BIT(~rm); break; //MVN + } + + if(exception() && d == 15 && (opcode & 1 << 20)) { + cpsr() = spsr(); + } +} + +auto ARM7TDMI::armMoveToStatus(uint4 field, uint1 mode, uint32 data) -> void { + if(mode && (cpsr().m == PSR::USR || cpsr().m == PSR::SYS)) return; + PSR& psr = mode ? spsr() : cpsr(); + + if(field & 1) { + if(mode || privileged()) { + psr.m = data >> 0 & 31; + psr.t = data >> 5 & 1; + psr.f = data >> 6 & 1; + psr.i = data >> 7 & 1; + if(!mode && psr.t) r(15).data += 2; + } + } + + if(field & 8) { + psr.v = data >> 28 & 1; + psr.c = data >> 29 & 1; + psr.z = data >> 30 & 1; + psr.n = data >> 31 & 1; + } +} + +// + +auto ARM7TDMI::armInstructionBranch +(int24 displacement, uint1 link) -> void { + if(link) r(14) = r(15) - 4; + r(15) = r(15) + displacement * 4; +} + +auto ARM7TDMI::armInstructionBranchExchangeRegister +(uint4 m) -> void { + uint32 address = r(m); + cpsr().t = address & 1; + r(15) = address; +} + +auto ARM7TDMI::armInstructionDataImmediate +(uint8 immediate, uint4 shift, uint4 d, uint4 n, uint1 save, uint4 mode) -> void { + uint32 data = immediate; + carry = cpsr().c; + if(shift) data = ROR(data, shift << 1); + armALU(mode, d, n, data); +} + +auto ARM7TDMI::armInstructionDataImmediateShift +(uint4 m, uint2 type, uint5 shift, uint4 d, uint4 n, uint1 save, uint4 mode) -> void { + uint32 rm = r(m); + carry = cpsr().c; + + switch(type) { + case 0: rm = LSL(rm, shift); break; + case 1: rm = LSR(rm, shift ? (uint)shift : 32); break; + case 2: rm = ASR(rm, shift ? (uint)shift : 32); break; + case 3: rm = shift ? ROR(rm, shift) : RRX(rm); break; + } + + armALU(mode, d, n, rm); +} + +auto ARM7TDMI::armInstructionDataRegisterShift +(uint4 m, uint2 type, uint4 s, uint4 d, uint4 n, uint1 save, uint4 mode) -> void { + uint8 rs = r(s) + (s == 15 ? 4 : 0); + uint32 rm = r(m) + (m == 15 ? 4 : 0); + carry = cpsr().c; + + switch(type) { + case 0: rm = LSL(rm, rs < 33 ? rs : (uint8)33); break; + case 1: rm = LSR(rm, rs < 33 ? rs : (uint8)33); break; + case 2: rm = ASR(rm, rs < 32 ? rs : (uint8)32); break; + case 3: if(rs) rm = ROR(rm, rs & 31 ? uint(rs & 31) : 32); break; + } + + armALU(mode, d, n, rm); +} + +auto ARM7TDMI::armInstructionLoadImmediate +(uint8 immediate, uint1 half, uint4 d, uint4 n, uint1 writeback, uint1 up, uint1 pre) -> void { + uint32 rn = r(n); + uint32 rd = r(d); + + if(pre == 1) rn = up ? rn + immediate : rn - immediate; + rd = load((half ? Half : Byte) | Nonsequential | Signed, rn); + if(pre == 0) rn = up ? rn + immediate : rn - immediate; + + if(pre == 0 || writeback) r(n) = rn; + r(d) = rd; +} + +auto ARM7TDMI::armInstructionLoadRegister +(uint4 m, uint1 half, uint4 d, uint4 n, uint1 writeback, uint1 up, uint1 pre) -> void { + uint32 rn = r(n); + uint32 rm = r(m); + uint32 rd = r(d); + + if(pre == 1) rn = up ? rn + rm : rn - rm; + rd = load((half ? Half : Byte) | Nonsequential | Signed, rn); + if(pre == 0) rn = up ? rn + rm : rn - rm; + + if(pre == 0 || writeback) r(n) = rn; + r(d) = rd; +} + +auto ARM7TDMI::armInstructionMemorySwap +(uint4 m, uint4 d, uint4 n, uint1 byte) -> void { + uint32 word = load((byte ? Byte : Word) | Nonsequential, r(n)); + store((byte ? Byte : Word) | Nonsequential, r(n), r(m)); + r(d) = word; +} + +auto ARM7TDMI::armInstructionMoveHalfImmediate +(uint8 immediate, uint4 d, uint4 n, uint1 mode, uint1 writeback, uint1 up, uint1 pre) -> void { + uint32 rn = r(n); + uint32 rd = r(d); + + if(pre == 1) rn = up ? rn + immediate : rn - immediate; + if(mode == 1) rd = load(Half | Nonsequential, rn); + if(mode == 0) store(Half | Nonsequential, rn, rd); + if(pre == 0) rn = up ? rn + immediate : rn - immediate; + + if(pre == 0 || writeback) r(n) = rn; + if(mode == 1) r(d) = rd; +} + +auto ARM7TDMI::armInstructionMoveHalfRegister +(uint4 m, uint4 d, uint4 n, uint1 mode, uint1 writeback, uint1 up, uint1 pre) -> void { + uint32 rn = r(n); + uint32 rm = r(m); + uint32 rd = r(d); + + if(pre == 1) rn = up ? rn + rm : rn - rm; + if(mode == 1) rd = load(Half | Nonsequential, rn); + if(mode == 0) store(Half | Nonsequential, rn, rd); + if(pre == 0) rn = up ? rn + rm : rn - rm; + + if(pre == 0 || writeback) r(n) = rn; + if(mode == 1) r(d) = rd; +} + +auto ARM7TDMI::armInstructionMoveImmediateOffset +(uint12 immediate, uint4 d, uint4 n, uint1 mode, uint1 writeback, uint1 byte, uint1 up, uint1 pre) -> void { + uint32 rn = r(n); + uint32 rd = r(d); + + if(pre == 1) rn = up ? rn + immediate : rn - immediate; + if(mode == 1) rd = load((byte ? Byte : Word) | Nonsequential, rn); + if(mode == 0) store((byte ? Byte : Word) | Nonsequential, rn, rd); + if(pre == 0) rn = up ? rn + immediate : rn - immediate; + + if(pre == 0 || writeback) r(n) = rn; + if(mode == 1) r(d) = rd; +} + +auto ARM7TDMI::armInstructionMoveMultiple +(uint16 list, uint4 n, uint1 mode, uint1 writeback, uint1 type, uint1 up, uint1 pre) -> void { + uint32 rn = r(n); + if(pre == 0 && up == 1) rn = rn + 0; //IA + if(pre == 1 && up == 1) rn = rn + 4; //IB + if(pre == 1 && up == 0) rn = rn - bit::count(list) * 4 + 0; //DB + if(pre == 0 && up == 0) rn = rn - bit::count(list) * 4 + 4; //DA + + if(writeback && mode == 1) { + if(up == 1) r(n) = r(n) + bit::count(list) * 4; //IA,IB + if(up == 0) r(n) = r(n) - bit::count(list) * 4; //DA,DB + } + + auto cpsrMode = cpsr().m; + bool usr = false; + if(type && mode == 1 && !(list & 0x8000)) usr = true; + if(type && mode == 0) usr = true; + if(usr) cpsr().m = PSR::USR; + + uint sequential = Nonsequential; + for(uint m : range(16)) { + if(!(list & 1 << m)) continue; + if(mode == 1) r(m) = read(Word | sequential, rn); + if(mode == 0) write(Word | sequential, rn, r(m)); + rn += 4; + sequential = Sequential; + } + + if(usr) cpsr().m = cpsrMode; + + if(mode) { + idle(); + if(type && (list & 0x8000) && cpsr().m != PSR::USR && cpsr().m != PSR::SYS) { + cpsr() = spsr(); + } + } else { + pipeline.nonsequential = true; + } + + if(writeback && mode == 0) { + if(up == 1) r(n) = r(n) + bit::count(list) * 4; //IA,IB + if(up == 0) r(n) = r(n) - bit::count(list) * 4; //DA,DB + } +} + +auto ARM7TDMI::armInstructionMoveRegisterOffset +(uint4 m, uint2 type, uint5 shift, uint4 d, uint4 n, uint1 mode, uint1 writeback, uint1 byte, uint1 up, uint1 pre) -> void { + uint32 rm = r(m); + uint32 rd = r(d); + uint32 rn = r(n); + carry = cpsr().c; + + switch(type) { + case 0: rm = LSL(rm, shift); break; + case 1: rm = LSR(rm, shift ? (uint)shift : 32); break; + case 2: rm = ASR(rm, shift ? (uint)shift : 32); break; + case 3: rm = shift ? ROR(rm, shift) : RRX(rm); break; + } + + if(pre == 1) rn = up ? rn + rm : rn - rm; + if(mode == 1) rd = load((byte ? Byte : Word) | Nonsequential, rn); + if(mode == 0) store((byte ? Byte : Word) | Nonsequential, rn, rd); + if(pre == 0) rn = up ? rn + rm : rn - rm; + + if(pre == 0 || writeback) r(n) = rn; + if(mode == 1) r(d) = rd; +} + +auto ARM7TDMI::armInstructionMoveToRegisterFromStatus +(uint4 d, uint1 mode) -> void { + if(mode && (cpsr().m == PSR::USR || cpsr().m == PSR::SYS)) return; + r(d) = mode ? spsr() : cpsr(); +} + +auto ARM7TDMI::armInstructionMoveToStatusFromImmediate +(uint8 immediate, uint4 rotate, uint4 field, uint1 mode) -> void { + uint32 data = immediate; + if(rotate) data = ROR(data, rotate << 1); + armMoveToStatus(field, mode, data); +} + +auto ARM7TDMI::armInstructionMoveToStatusFromRegister +(uint4 m, uint4 field, uint1 mode) -> void { + armMoveToStatus(field, mode, r(m)); +} + +auto ARM7TDMI::armInstructionMultiply +(uint4 m, uint4 s, uint4 n, uint4 d, uint1 save, uint1 accumulate) -> void { + if(accumulate) idle(); + r(d) = MUL(accumulate ? r(n) : 0, r(m), r(s)); +} + +auto ARM7TDMI::armInstructionMultiplyLong +(uint4 m, uint4 s, uint4 l, uint4 h, uint1 save, uint1 accumulate, uint1 sign) -> void { + uint64 rm = r(m); + uint64 rs = r(s); + + idle(); + idle(); + if(accumulate) idle(); + + if(sign) { + if(rs >> 8 && rs >> 8 != 0xffffff) idle(); + if(rs >> 16 && rs >> 16 != 0xffff) idle(); + if(rs >> 24 && rs >> 24 != 0xff) idle(); + rm = (int32)rm; + rs = (int32)rs; + } else { + if(rs >> 8) idle(); + if(rs >> 16) idle(); + if(rs >> 24) idle(); + } + + uint64 rd = rm * rs; + if(accumulate) rd += (uint64)r(h) << 32 | (uint64)r(l) << 0; + + r(h) = rd >> 32; + r(l) = rd >> 0; + + if(save) { + cpsr().z = rd == 0; + cpsr().n = rd >> 63 & 1; + } +} + +auto ARM7TDMI::armInstructionSoftwareInterrupt +(uint24 immediate) -> void { + exception(PSR::SVC, 0x08); +} + +auto ARM7TDMI::armInstructionUndefined +() -> void { + exception(PSR::UND, 0x04); +} diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/instructions-thumb.cpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/instructions-thumb.cpp new file mode 100644 index 0000000000..1dc1f5ef80 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/instructions-thumb.cpp @@ -0,0 +1,225 @@ +auto ARM7TDMI::thumbInstructionALU +(uint3 d, uint3 m, uint4 mode) -> void { + switch(mode) { + case 0: r(d) = BIT(r(d) & r(m)); break; //AND + case 1: r(d) = BIT(r(d) ^ r(m)); break; //EOR + case 2: r(d) = BIT(LSL(r(d), r(m))); break; //LSL + case 3: r(d) = BIT(LSR(r(d), r(m))); break; //LSR + case 4: r(d) = BIT(ASR(r(d), r(m))); break; //ASR + case 5: r(d) = ADD(r(d), r(m), cpsr().c); break; //ADC + case 6: r(d) = SUB(r(d), r(m), cpsr().c); break; //SBC + case 7: r(d) = BIT(ROR(r(d), r(m))); break; //ROR + case 8: BIT(r(d) & r(m)); break; //TST + case 9: r(d) = SUB(0, r(m), 1); break; //NEG + case 10: SUB(r(d), r(m), 1); break; //CMP + case 11: ADD(r(d), r(m), 0); break; //CMN + case 12: r(d) = BIT(r(d) | r(m)); break; //ORR + case 13: r(d) = MUL(0, r(m), r(d)); break; //MUL + case 14: r(d) = BIT(r(d) & ~r(m)); break; //BIC + case 15: r(d) = BIT(~r(m)); break; //MVN + } +} + +auto ARM7TDMI::thumbInstructionALUExtended +(uint4 d, uint4 m, uint2 mode) -> void { + switch(mode) { + case 0: r(d) = r(d) + r(m); break; //ADD + case 1: SUB(r(d), r(m), 1); break; //SUBS + case 2: r(d) = r(m); break; //MOV + } +} + +auto ARM7TDMI::thumbInstructionAddRegister +(uint8 immediate, uint3 d, uint1 mode) -> void { + switch(mode) { + case 0: r(d) = (r(15) & ~3) + immediate * 4; break; //ADD pc + case 1: r(d) = r(13) + immediate * 4; break; //ADD sp + } +} + +auto ARM7TDMI::thumbInstructionAdjustImmediate +(uint3 d, uint3 n, uint3 immediate, uint1 mode) -> void { + switch(mode) { + case 0: r(d) = ADD(r(n), immediate, 0); break; //ADD + case 1: r(d) = SUB(r(n), immediate, 1); break; //SUB + } +} + +auto ARM7TDMI::thumbInstructionAdjustRegister +(uint3 d, uint3 n, uint3 m, uint1 mode) -> void { + switch(mode) { + case 0: r(d) = ADD(r(n), r(m), 0); break; //ADD + case 1: r(d) = SUB(r(n), r(m), 1); break; //SUB + } +} + +auto ARM7TDMI::thumbInstructionAdjustStack +(uint7 immediate, uint1 mode) -> void { + switch(mode) { + case 0: r(13) = r(13) + immediate * 4; break; //ADD + case 1: r(13) = r(13) - immediate * 4; break; //SUB + } +} + +auto ARM7TDMI::thumbInstructionBranchExchange +(uint4 m) -> void { + uint32 address = r(m); + cpsr().t = address & 1; + r(15) = address; +} + +auto ARM7TDMI::thumbInstructionBranchFarPrefix +(int11 displacement) -> void { + r(14) = r(15) + (displacement * 2 << 11); +} + +auto ARM7TDMI::thumbInstructionBranchFarSuffix +(uint11 displacement) -> void { + r(15) = r(14) + (displacement * 2); + r(14) = pipeline.decode.address | 1; +} + +auto ARM7TDMI::thumbInstructionBranchNear +(int11 displacement) -> void { + r(15) = r(15) + displacement * 2; +} + +auto ARM7TDMI::thumbInstructionBranchTest +(int8 displacement, uint4 condition) -> void { + if(!TST(condition)) return; + r(15) = r(15) + displacement * 2; +} + +auto ARM7TDMI::thumbInstructionImmediate +(uint8 immediate, uint3 d, uint2 mode) -> void { + switch(mode) { + case 0: r(d) = BIT(immediate); break; //MOV + case 1: SUB(r(d), immediate, 1); break; //CMP + case 2: r(d) = ADD(r(d), immediate, 0); break; //ADD + case 3: r(d) = SUB(r(d), immediate, 1); break; //SUB + } +} + +auto ARM7TDMI::thumbInstructionLoadLiteral +(uint8 displacement, uint3 d) -> void { + uint32 address = (r(15) & ~3) + (displacement << 2); + r(d) = load(Word | Nonsequential, address); +} + +auto ARM7TDMI::thumbInstructionMoveByteImmediate +(uint3 d, uint3 n, uint5 offset, uint1 mode) -> void { + switch(mode) { + case 0: store(Byte | Nonsequential, r(n) + offset, r(d)); break; //STRB + case 1: r(d) = load(Byte | Nonsequential, r(n) + offset); break; //LDRB + } +} + +auto ARM7TDMI::thumbInstructionMoveHalfImmediate +(uint3 d, uint3 n, uint5 offset, uint1 mode) -> void { + switch(mode) { + case 0: store(Half | Nonsequential, r(n) + offset * 2, r(d)); break; //STRH + case 1: r(d) = load(Half | Nonsequential, r(n) + offset * 2); break; //LDRH + } +} + +auto ARM7TDMI::thumbInstructionMoveMultiple +(uint8 list, uint3 n, uint1 mode) -> void { + uint32 rn = r(n); + + for(uint m : range(8)) { + if(!(list & 1 << m)) continue; + switch(mode) { + case 0: write(Word | Nonsequential, rn, r(m)); break; //STMIA + case 1: r(m) = read(Word | Nonsequential, rn); break; //LDMIA + } + rn += 4; + } + + if(mode == 0 || !(list & 1 << n)) r(n) = rn; + if(mode == 1) idle(); +} + +auto ARM7TDMI::thumbInstructionMoveRegisterOffset +(uint3 d, uint3 n, uint3 m, uint3 mode) -> void { + switch(mode) { + case 0: store(Word | Nonsequential, r(n) + r(m), r(d)); break; //STR + case 1: store(Half | Nonsequential, r(n) + r(m), r(d)); break; //STRH + case 2: store(Byte | Nonsequential, r(n) + r(m), r(d)); break; //STRB + case 3: r(d) = load(Byte | Nonsequential | Signed, r(n) + r(m)); break; //LDSB + case 4: r(d) = load(Word | Nonsequential, r(n) + r(m)); break; //LDR + case 5: r(d) = load(Half | Nonsequential, r(n) + r(m)); break; //LDRH + case 6: r(d) = load(Byte | Nonsequential, r(n) + r(m)); break; //LDRB + case 7: r(d) = load(Half | Nonsequential | Signed, r(n) + r(m)); break; //LDSH + } +} + +auto ARM7TDMI::thumbInstructionMoveStack +(uint8 immediate, uint3 d, uint1 mode) -> void { + switch(mode) { + case 0: store(Word | Nonsequential, r(13) + immediate * 4, r(d)); break; //STR + case 1: r(d) = load(Word | Nonsequential, r(13) + immediate * 4); break; //LDR + } +} + +auto ARM7TDMI::thumbInstructionMoveWordImmediate +(uint3 d, uint3 n, uint5 offset, uint1 mode) -> void { + switch(mode) { + case 0: store(Word | Nonsequential, r(n) + offset * 4, r(d)); break; //STR + case 1: r(d) = load(Word | Nonsequential, r(n) + offset * 4); break; //LDR + } +} + +auto ARM7TDMI::thumbInstructionShiftImmediate +(uint3 d, uint3 m, uint5 immediate, uint2 mode) -> void { + switch(mode) { + case 0: r(d) = BIT(LSL(r(m), immediate)); break; //LSL + case 1: r(d) = BIT(LSR(r(m), immediate ? (uint)immediate : 32)); break; //LSR + case 2: r(d) = BIT(ASR(r(m), immediate ? (uint)immediate : 32)); break; //ASR + } +} + +auto ARM7TDMI::thumbInstructionSoftwareInterrupt +(uint8 immediate) -> void { + exception(PSR::SVC, 0x08); +} + +auto ARM7TDMI::thumbInstructionStackMultiple +(uint8 list, uint1 lrpc, uint1 mode) -> void { + uint32 sp; + switch(mode) { + case 0: sp = r(13) - (bit::count(list) + lrpc) * 4; break; //PUSH + case 1: sp = r(13); //POP + } + + uint sequential = Nonsequential; + for(uint m : range(8)) { + if(!(list & 1 << m)) continue; + switch(mode) { + case 0: write(Word | sequential, sp, r(m)); break; //PUSH + case 1: r(m) = read(Word | sequential, sp); break; //POP + } + sp += 4; + sequential = Sequential; + } + + if(lrpc) { + switch(mode) { + case 0: write(Word | sequential, sp, r(14)); break; //PUSH + case 1: r(15) = read(Word | sequential, sp); break; //POP + } + sp += 4; + } + + if(mode == 1) { + idle(); + r(13) = r(13) + (bit::count(list) + lrpc) * 4; //POP + } else { + pipeline.nonsequential = true; + r(13) = r(13) - (bit::count(list) + lrpc) * 4; //PUSH + } +} + +auto ARM7TDMI::thumbInstructionUndefined +() -> void { + exception(PSR::UND, 0x04); +} diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/memory.cpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/memory.cpp new file mode 100644 index 0000000000..aaeb73b010 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/memory.cpp @@ -0,0 +1,40 @@ +auto ARM7TDMI::idle() -> void { + pipeline.nonsequential = true; + sleep(); +} + +auto ARM7TDMI::read(uint mode, uint32 address) -> uint32 { + return get(mode, address); +} + +auto ARM7TDMI::load(uint mode, uint32 address) -> uint32 { + pipeline.nonsequential = true; + auto word = get(Load | mode, address); + if(mode & Half) { + address &= 1; + word = mode & Signed ? (uint32)(int16)word : (uint32)(uint16)word; + } + if(mode & Byte) { + address &= 0; + word = mode & Signed ? (uint32)(int8)word : (uint32)(uint8)word; + } + if(mode & Signed) { + word = ASR(word, (address & 3) << 3); + } else { + word = ROR(word, (address & 3) << 3); + } + idle(); + return word; +} + +auto ARM7TDMI::write(uint mode, uint32 address, uint32 word) -> void { + pipeline.nonsequential = true; + return set(mode, address, word); +} + +auto ARM7TDMI::store(uint mode, uint32 address, uint32 word) -> void { + pipeline.nonsequential = true; + if(mode & Half) { word &= 0xffff; word |= word << 16; } + if(mode & Byte) { word &= 0xff; word |= word << 8; word |= word << 16; } + return set(Store | mode, address, word); +} diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/registers.cpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/registers.cpp new file mode 100644 index 0000000000..848f088359 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/registers.cpp @@ -0,0 +1,58 @@ +auto ARM7TDMI::r(uint4 index) -> GPR& { + switch(index) { + case 0: return processor.r0; + case 1: return processor.r1; + case 2: return processor.r2; + case 3: return processor.r3; + case 4: return processor.r4; + case 5: return processor.r5; + case 6: return processor.r6; + case 7: return processor.r7; + case 8: return processor.cpsr.m == PSR::FIQ ? processor.fiq.r8 : processor.r8; + case 9: return processor.cpsr.m == PSR::FIQ ? processor.fiq.r9 : processor.r9; + case 10: return processor.cpsr.m == PSR::FIQ ? processor.fiq.r10 : processor.r10; + case 11: return processor.cpsr.m == PSR::FIQ ? processor.fiq.r11 : processor.r11; + case 12: return processor.cpsr.m == PSR::FIQ ? processor.fiq.r12 : processor.r12; + case 13: switch(processor.cpsr.m) { + case PSR::FIQ: return processor.fiq.r13; + case PSR::IRQ: return processor.irq.r13; + case PSR::SVC: return processor.svc.r13; + case PSR::ABT: return processor.abt.r13; + case PSR::UND: return processor.und.r13; + default: return processor.r13; + } + case 14: switch(processor.cpsr.m) { + case PSR::FIQ: return processor.fiq.r14; + case PSR::IRQ: return processor.irq.r14; + case PSR::SVC: return processor.svc.r14; + case PSR::ABT: return processor.abt.r14; + case PSR::UND: return processor.und.r14; + default: return processor.r14; + } + case 15: return processor.r15; + } + unreachable; +} + +auto ARM7TDMI::cpsr() -> PSR& { + return processor.cpsr; +} + +auto ARM7TDMI::spsr() -> PSR& { + switch(processor.cpsr.m) { + case PSR::FIQ: return processor.fiq.spsr; + case PSR::IRQ: return processor.irq.spsr; + case PSR::SVC: return processor.svc.spsr; + case PSR::ABT: return processor.abt.spsr; + case PSR::UND: return processor.und.spsr; + } + throw; +} + +auto ARM7TDMI::privileged() const -> bool { + return processor.cpsr.m != PSR::USR; +} + +auto ARM7TDMI::exception() const -> bool { + return privileged() && processor.cpsr.m != PSR::SYS; +} diff --git a/waterbox/bsnescore/bsnes/processor/arm7tdmi/serialization.cpp b/waterbox/bsnescore/bsnes/processor/arm7tdmi/serialization.cpp new file mode 100644 index 0000000000..b80a2e8ec4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/arm7tdmi/serialization.cpp @@ -0,0 +1,71 @@ +auto ARM7TDMI::serialize(serializer& s) -> void { + processor.serialize(s); + pipeline.serialize(s); + s.boolean(carry); + s.boolean(irq); +} + +auto ARM7TDMI::Processor::serialize(serializer& s) -> void { + s.integer(r0.data); + s.integer(r1.data); + s.integer(r2.data); + s.integer(r3.data); + s.integer(r4.data); + s.integer(r5.data); + s.integer(r6.data); + s.integer(r7.data); + s.integer(r8.data); + s.integer(r9.data); + s.integer(r10.data); + s.integer(r11.data); + s.integer(r12.data); + s.integer(r13.data); + s.integer(r14.data); + s.integer(r15.data); + cpsr.serialize(s); + s.integer(fiq.r8.data); + s.integer(fiq.r9.data); + s.integer(fiq.r10.data); + s.integer(fiq.r11.data); + s.integer(fiq.r12.data); + s.integer(fiq.r13.data); + s.integer(fiq.r14.data); + fiq.spsr.serialize(s); + s.integer(irq.r13.data); + s.integer(irq.r14.data); + irq.spsr.serialize(s); + s.integer(svc.r13.data); + s.integer(svc.r14.data); + svc.spsr.serialize(s); + s.integer(abt.r13.data); + s.integer(abt.r14.data); + abt.spsr.serialize(s); + s.integer(und.r13.data); + s.integer(und.r14.data); + und.spsr.serialize(s); +} + +auto ARM7TDMI::PSR::serialize(serializer& s) -> void { + s.integer(m); + s.boolean(t); + s.boolean(f); + s.boolean(i); + s.boolean(v); + s.boolean(c); + s.boolean(z); + s.boolean(n); +} + +auto ARM7TDMI::Pipeline::serialize(serializer& s) -> void { + s.integer(reload); + s.integer(nonsequential); + s.integer(fetch.address); + s.integer(fetch.instruction); + s.boolean(fetch.thumb); + s.integer(decode.address); + s.integer(decode.instruction); + s.boolean(decode.thumb); + s.integer(execute.address); + s.integer(execute.instruction); + s.boolean(execute.thumb); +} diff --git a/waterbox/bsnescore/bsnes/processor/gsu/disassembler.cpp b/waterbox/bsnescore/bsnes/processor/gsu/disassembler.cpp new file mode 100644 index 0000000000..75254b4f51 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/gsu/disassembler.cpp @@ -0,0 +1,268 @@ +auto GSU::disassembleOpcode(char* output) -> void { + *output = 0; + + switch(regs.sfr.alt2 << 1 | regs.sfr.alt1 << 0) { + case 0: disassembleALT0(output); break; + case 1: disassembleALT1(output); break; + case 2: disassembleALT2(output); break; + case 3: disassembleALT3(output); break; + } + + uint length = strlen(output); + while(length++ < 20) strcat(output, " "); +} + +#define case4(id) \ + case id+ 0: case id+ 1: case id+ 2: case id+ 3 +#define case6(id) \ + case id+ 0: case id+ 1: case id+ 2: case id+ 3: case id+ 4: case id+ 5 +#define case12(id) \ + case id+ 0: case id+ 1: case id+ 2: case id+ 3: case id+ 4: case id+ 5: case id+ 6: case id+ 7: \ + case id+ 8: case id+ 9: case id+10: case id+11 +#define case15(id) \ + case id+ 0: case id+ 1: case id+ 2: case id+ 3: case id+ 4: case id+ 5: case id+ 6: case id+ 7: \ + case id+ 8: case id+ 9: case id+10: case id+11: case id+12: case id+13: case id+14 +#define case16(id) \ + case id+ 0: case id+ 1: case id+ 2: case id+ 3: case id+ 4: case id+ 5: case id+ 6: case id+ 7: \ + case id+ 8: case id+ 9: case id+10: case id+11: case id+12: case id+13: case id+14: case id+15 + +#define op0 regs.pipeline +#define op1 read((regs.pbr << 16) + regs.r[15] + 0) +#define op2 read((regs.pbr << 16) + regs.r[15] + 1) + +auto GSU::disassembleALT0(char* output) -> void { + char t[256] = ""; + switch(op0) { + case (0x00): sprintf(t, "stop"); break; + case (0x01): sprintf(t, "nop"); break; + case (0x02): sprintf(t, "cache"); break; + case (0x03): sprintf(t, "lsr"); break; + case (0x04): sprintf(t, "rol"); break; + case (0x05): sprintf(t, "bra %+d", (int8_t)op1); break; + case (0x06): sprintf(t, "blt %+d", (int8_t)op1); break; + case (0x07): sprintf(t, "bge %+d", (int8_t)op1); break; + case (0x08): sprintf(t, "bne %+d", (int8_t)op1); break; + case (0x09): sprintf(t, "beq %+d", (int8_t)op1); break; + case (0x0a): sprintf(t, "bpl %+d", (int8_t)op1); break; + case (0x0b): sprintf(t, "bmi %+d", (int8_t)op1); break; + case (0x0c): sprintf(t, "bcc %+d", (int8_t)op1); break; + case (0x0d): sprintf(t, "bcs %+d", (int8_t)op1); break; + case (0x0e): sprintf(t, "bvc %+d", (int8_t)op1); break; + case (0x0f): sprintf(t, "bvs %+d", (int8_t)op1); break; + case16(0x10): sprintf(t, "to r%u", op0 & 15); break; + case16(0x20): sprintf(t, "with r%u", op0 & 15); break; + case12(0x30): sprintf(t, "stw (r%u)", op0 & 15); break; + case (0x3c): sprintf(t, "loop"); break; + case (0x3d): sprintf(t, "alt1"); break; + case (0x3e): sprintf(t, "alt2"); break; + case (0x3f): sprintf(t, "alt3"); break; + case12(0x40): sprintf(t, "ldw (r%u)", op0 & 15); break; + case (0x4c): sprintf(t, "plot"); break; + case (0x4d): sprintf(t, "swap"); break; + case (0x4e): sprintf(t, "color"); break; + case (0x4f): sprintf(t, "not"); break; + case16(0x50): sprintf(t, "add r%u", op0 & 15); break; + case16(0x60): sprintf(t, "sub r%u", op0 & 15); break; + case (0x70): sprintf(t, "merge"); break; + case15(0x71): sprintf(t, "and r%u", op0 & 15); break; + case16(0x80): sprintf(t, "mult r%u", op0 & 15); break; + case (0x90): sprintf(t, "sbk"); break; + case4 (0x91): sprintf(t, "link #%u", op0 & 15); break; + case (0x95): sprintf(t, "sex"); break; + case (0x96): sprintf(t, "asr"); break; + case (0x97): sprintf(t, "ror"); break; + case6 (0x98): sprintf(t, "jmp r%u", op0 & 15); break; + case (0x9e): sprintf(t, "lob"); break; + case (0x9f): sprintf(t, "fmult"); break; + case16(0xa0): sprintf(t, "ibt r%u,#$%.2x", op0 & 15, op1); break; + case16(0xb0): sprintf(t, "from r%u", op0 & 15); break; + case (0xc0): sprintf(t, "hib"); break; + case15(0xc1): sprintf(t, "or r%u", op0 & 15); break; + case15(0xd0): sprintf(t, "inc r%u", op0 & 15); break; + case (0xdf): sprintf(t, "getc"); break; + case15(0xe0): sprintf(t, "dec r%u", op0 & 15); break; + case (0xef): sprintf(t, "getb"); break; + case16(0xf0): sprintf(t, "iwt r%u,#$%.2x%.2x", op0 & 15, op2, op1); break; + } + strcat(output, t); +} + +auto GSU::disassembleALT1(char* output) -> void { + char t[256] = ""; + switch(op0) { + case (0x00): sprintf(t, "stop"); break; + case (0x01): sprintf(t, "nop"); break; + case (0x02): sprintf(t, "cache"); break; + case (0x03): sprintf(t, "lsr"); break; + case (0x04): sprintf(t, "rol"); break; + case (0x05): sprintf(t, "bra %+d", (int8_t)op1); break; + case (0x06): sprintf(t, "blt %+d", (int8_t)op1); break; + case (0x07): sprintf(t, "bge %+d", (int8_t)op1); break; + case (0x08): sprintf(t, "bne %+d", (int8_t)op1); break; + case (0x09): sprintf(t, "beq %+d", (int8_t)op1); break; + case (0x0a): sprintf(t, "bpl %+d", (int8_t)op1); break; + case (0x0b): sprintf(t, "bmi %+d", (int8_t)op1); break; + case (0x0c): sprintf(t, "bcc %+d", (int8_t)op1); break; + case (0x0d): sprintf(t, "bcs %+d", (int8_t)op1); break; + case (0x0e): sprintf(t, "bvc %+d", (int8_t)op1); break; + case (0x0f): sprintf(t, "bvs %+d", (int8_t)op1); break; + case16(0x10): sprintf(t, "to r%u", op0 & 15); break; + case16(0x20): sprintf(t, "with r%u", op0 & 15); break; + case12(0x30): sprintf(t, "stb (r%u)", op0 & 15); break; + case (0x3c): sprintf(t, "loop"); break; + case (0x3d): sprintf(t, "alt1"); break; + case (0x3e): sprintf(t, "alt2"); break; + case (0x3f): sprintf(t, "alt3"); break; + case12(0x40): sprintf(t, "ldb (r%u)", op0 & 15); break; + case (0x4c): sprintf(t, "rpix"); break; + case (0x4d): sprintf(t, "swap"); break; + case (0x4e): sprintf(t, "cmode"); break; + case (0x4f): sprintf(t, "not"); break; + case16(0x50): sprintf(t, "adc r%u", op0 & 15); break; + case16(0x60): sprintf(t, "sbc r%u", op0 & 15); break; + case (0x70): sprintf(t, "merge"); break; + case15(0x71): sprintf(t, "bic r%u", op0 & 15); break; + case16(0x80): sprintf(t, "umult r%u", op0 & 15); break; + case (0x90): sprintf(t, "sbk"); break; + case4 (0x91): sprintf(t, "link #%u", op0 & 15); break; + case (0x95): sprintf(t, "sex"); break; + case (0x96): sprintf(t, "div2"); break; + case (0x97): sprintf(t, "ror"); break; + case6 (0x98): sprintf(t, "ljmp r%u", op0 & 15); break; + case (0x9e): sprintf(t, "lob"); break; + case (0x9f): sprintf(t, "lmult"); break; + case16(0xa0): sprintf(t, "lms r%u,(#$%.4x)", op0 & 15, op1 << 1); break; + case16(0xb0): sprintf(t, "from r%u", op0 & 15); break; + case (0xc0): sprintf(t, "hib"); break; + case15(0xc1): sprintf(t, "xor r%u", op0 & 15); break; + case15(0xd0): sprintf(t, "inc r%u", op0 & 15); break; + case (0xdf): sprintf(t, "getc"); break; + case15(0xe0): sprintf(t, "dec r%u", op0 & 15); break; + case (0xef): sprintf(t, "getbh"); break; + case16(0xf0): sprintf(t, "lm r%u", op0 & 15); break; + } + strcat(output, t); +} + +auto GSU::disassembleALT2(char* output) -> void { + char t[256] = ""; + switch(op0) { + case (0x00): sprintf(t, "stop"); break; + case (0x01): sprintf(t, "nop"); break; + case (0x02): sprintf(t, "cache"); break; + case (0x03): sprintf(t, "lsr"); break; + case (0x04): sprintf(t, "rol"); break; + case (0x05): sprintf(t, "bra %+d", (int8_t)op1); break; + case (0x06): sprintf(t, "blt %+d", (int8_t)op1); break; + case (0x07): sprintf(t, "bge %+d", (int8_t)op1); break; + case (0x08): sprintf(t, "bne %+d", (int8_t)op1); break; + case (0x09): sprintf(t, "beq %+d", (int8_t)op1); break; + case (0x0a): sprintf(t, "bpl %+d", (int8_t)op1); break; + case (0x0b): sprintf(t, "bmi %+d", (int8_t)op1); break; + case (0x0c): sprintf(t, "bcc %+d", (int8_t)op1); break; + case (0x0d): sprintf(t, "bcs %+d", (int8_t)op1); break; + case (0x0e): sprintf(t, "bvc %+d", (int8_t)op1); break; + case (0x0f): sprintf(t, "bvs %+d", (int8_t)op1); break; + case16(0x10): sprintf(t, "to r%u", op0 & 15); break; + case16(0x20): sprintf(t, "with r%u", op0 & 15); break; + case12(0x30): sprintf(t, "stw (r%u)", op0 & 15); break; + case (0x3c): sprintf(t, "loop"); break; + case (0x3d): sprintf(t, "alt1"); break; + case (0x3e): sprintf(t, "alt2"); break; + case (0x3f): sprintf(t, "alt3"); break; + case12(0x40): sprintf(t, "ldw (r%u)", op0 & 15); break; + case (0x4c): sprintf(t, "plot"); break; + case (0x4d): sprintf(t, "swap"); break; + case (0x4e): sprintf(t, "color"); break; + case (0x4f): sprintf(t, "not"); break; + case16(0x50): sprintf(t, "add #%u", op0 & 15); break; + case16(0x60): sprintf(t, "sub #%u", op0 & 15); break; + case (0x70): sprintf(t, "merge"); break; + case15(0x71): sprintf(t, "and #%u", op0 & 15); break; + case16(0x80): sprintf(t, "mult #%u", op0 & 15); break; + case (0x90): sprintf(t, "sbk"); break; + case4 (0x91): sprintf(t, "link #%u", op0 & 15); break; + case (0x95): sprintf(t, "sex"); break; + case (0x96): sprintf(t, "asr"); break; + case (0x97): sprintf(t, "ror"); break; + case6 (0x98): sprintf(t, "jmp r%u", op0 & 15); break; + case (0x9e): sprintf(t, "lob"); break; + case (0x9f): sprintf(t, "fmult"); break; + case16(0xa0): sprintf(t, "sms r%u,(#$%.4x)", op0 & 15, op1 << 1); break; + case16(0xb0): sprintf(t, "from r%u", op0 & 15); break; + case (0xc0): sprintf(t, "hib"); break; + case15(0xc1): sprintf(t, "or #%u", op0 & 15); break; + case15(0xd0): sprintf(t, "inc r%u", op0 & 15); break; + case (0xdf): sprintf(t, "ramb"); break; + case15(0xe0): sprintf(t, "dec r%u", op0 & 15); break; + case (0xef): sprintf(t, "getbl"); break; + case16(0xf0): sprintf(t, "sm r%u", op0 & 15); break; + } + strcat(output, t); +} + +auto GSU::disassembleALT3(char* output) -> void { + char t[256] = ""; + switch(op0) { + case (0x00): sprintf(t, "stop"); break; + case (0x01): sprintf(t, "nop"); break; + case (0x02): sprintf(t, "cache"); break; + case (0x03): sprintf(t, "lsr"); break; + case (0x04): sprintf(t, "rol"); break; + case (0x05): sprintf(t, "bra %+d", (int8_t)op1); break; + case (0x06): sprintf(t, "blt %+d", (int8_t)op1); break; + case (0x07): sprintf(t, "bge %+d", (int8_t)op1); break; + case (0x08): sprintf(t, "bne %+d", (int8_t)op1); break; + case (0x09): sprintf(t, "beq %+d", (int8_t)op1); break; + case (0x0a): sprintf(t, "bpl %+d", (int8_t)op1); break; + case (0x0b): sprintf(t, "bmi %+d", (int8_t)op1); break; + case (0x0c): sprintf(t, "bcc %+d", (int8_t)op1); break; + case (0x0d): sprintf(t, "bcs %+d", (int8_t)op1); break; + case (0x0e): sprintf(t, "bvc %+d", (int8_t)op1); break; + case (0x0f): sprintf(t, "bvs %+d", (int8_t)op1); break; + case16(0x10): sprintf(t, "to r%u", op0 & 15); break; + case16(0x20): sprintf(t, "with r%u", op0 & 15); break; + case12(0x30): sprintf(t, "stb (r%u)", op0 & 15); break; + case (0x3c): sprintf(t, "loop"); break; + case (0x3d): sprintf(t, "alt1"); break; + case (0x3e): sprintf(t, "alt2"); break; + case (0x3f): sprintf(t, "alt3"); break; + case12(0x40): sprintf(t, "ldb (r%u)", op0 & 15); break; + case (0x4c): sprintf(t, "rpix"); break; + case (0x4d): sprintf(t, "swap"); break; + case (0x4e): sprintf(t, "cmode"); break; + case (0x4f): sprintf(t, "not"); break; + case16(0x50): sprintf(t, "adc #%u", op0 & 15); break; + case16(0x60): sprintf(t, "cmp r%u", op0 & 15); break; + case (0x70): sprintf(t, "merge"); break; + case15(0x71): sprintf(t, "bic #%u", op0 & 15); break; + case16(0x80): sprintf(t, "umult #%u", op0 & 15); break; + case (0x90): sprintf(t, "sbk"); break; + case4 (0x91): sprintf(t, "link #%u", op0 & 15); break; + case (0x95): sprintf(t, "sex"); break; + case (0x96): sprintf(t, "div2"); break; + case (0x97): sprintf(t, "ror"); break; + case6 (0x98): sprintf(t, "ljmp r%u", op0 & 15); break; + case (0x9e): sprintf(t, "lob"); break; + case (0x9f): sprintf(t, "lmult"); break; + case16(0xa0): sprintf(t, "lms r%u", op0 & 15); break; + case16(0xb0): sprintf(t, "from r%u", op0 & 15); break; + case (0xc0): sprintf(t, "hib"); break; + case15(0xc1): sprintf(t, "xor #%u", op0 & 15); break; + case15(0xd0): sprintf(t, "inc r%u", op0 & 15); break; + case (0xdf): sprintf(t, "romb"); break; + case15(0xe0): sprintf(t, "dec r%u", op0 & 15); break; + case (0xef): sprintf(t, "getbs"); break; + case16(0xf0): sprintf(t, "lm r%u", op0 & 15); break; + } + strcat(output, t); +} + +#undef case4 +#undef case6 +#undef case12 +#undef case15 +#undef case16 +#undef op0 +#undef op1 +#undef op2 diff --git a/waterbox/bsnescore/bsnes/processor/gsu/gsu.cpp b/waterbox/bsnescore/bsnes/processor/gsu/gsu.cpp new file mode 100644 index 0000000000..78a255c6cc --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/gsu/gsu.cpp @@ -0,0 +1,39 @@ +#include +#include "gsu.hpp" + +//note: multiplication results *may* sometimes be invalid when both CLSR and MS0 are set +//the product of multiplication in this mode (21mhz + fast-multiply) has not been analyzed; +//however, the timing of this mode has been confirmed to work as specified below + +namespace Processor { + +#include "instruction.cpp" +#include "instructions.cpp" +#include "serialization.cpp" +#include "disassembler.cpp" + +auto GSU::power() -> void { + for(auto& r : regs.r) { + r.data = 0x0000; + r.modified = false; + } + + regs.sfr = 0x0000; + regs.pbr = 0x00; + regs.rombr = 0x00; + regs.rambr = 0; + regs.cbr = 0x0000; + regs.scbr = 0x00; + regs.scmr = 0x00; + regs.colr = 0x00; + regs.por = 0x00; + regs.bramr = 0; + regs.vcr = 0x04; + regs.cfgr = 0x00; + regs.clsr = 0; + regs.pipeline = 0x01; //nop + regs.ramaddr = 0x0000; + regs.reset(); +} + +} diff --git a/waterbox/bsnescore/bsnes/processor/gsu/gsu.hpp b/waterbox/bsnescore/bsnes/processor/gsu/gsu.hpp new file mode 100644 index 0000000000..61fbe4c367 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/gsu/gsu.hpp @@ -0,0 +1,85 @@ +#pragma once + +namespace Processor { + +struct GSU { + #include "registers.hpp" + + virtual auto step(uint clocks) -> void = 0; + + virtual auto stop() -> void = 0; + virtual auto color(uint8 source) -> uint8 = 0; + virtual auto plot(uint8 x, uint8 y) -> void = 0; + virtual auto rpix(uint8 x, uint8 y) -> uint8 = 0; + + virtual auto pipe() -> uint8 = 0; + virtual auto syncROMBuffer() -> void = 0; + virtual auto readROMBuffer() -> uint8 = 0; + virtual auto syncRAMBuffer() -> void = 0; + virtual auto readRAMBuffer(uint16 addr) -> uint8 = 0; + virtual auto writeRAMBuffer(uint16 addr, uint8 data) -> void = 0; + virtual auto flushCache() -> void = 0; + + virtual auto read(uint addr, uint8 data = 0x00) -> uint8 = 0; + virtual auto write(uint addr, uint8 data) -> void = 0; + + //gsu.cpp + auto power() -> void; + + //instructions.cpp + auto instructionADD_ADC(uint n) -> void; + auto instructionALT1() -> void; + auto instructionALT2() -> void; + auto instructionALT3() -> void; + auto instructionAND_BIC(uint n) -> void; + auto instructionASR_DIV2() -> void; + auto instructionBranch(bool c) -> void; + auto instructionCACHE() -> void; + auto instructionCOLOR_CMODE() -> void; + auto instructionDEC(uint n) -> void; + auto instructionFMULT_LMULT() -> void; + auto instructionFROM_MOVES(uint n) -> void; + auto instructionGETB() -> void; + auto instructionGETC_RAMB_ROMB() -> void; + auto instructionHIB() -> void; + auto instructionIBT_LMS_SMS(uint n) -> void; + auto instructionINC(uint n) -> void; + auto instructionIWT_LM_SM(uint n) -> void; + auto instructionJMP_LJMP(uint n) -> void; + auto instructionLINK(uint n) -> void; + auto instructionLoad(uint n) -> void; + auto instructionLOB() -> void; + auto instructionLOOP() -> void; + auto instructionLSR() -> void; + auto instructionMERGE() -> void; + auto instructionMULT_UMULT(uint n) -> void; + auto instructionNOP() -> void; + auto instructionNOT() -> void; + auto instructionOR_XOR(uint n) -> void; + auto instructionPLOT_RPIX() -> void; + auto instructionROL() -> void; + auto instructionROR() -> void; + auto instructionSBK() -> void; + auto instructionSEX() -> void; + auto instructionStore(uint n) -> void; + auto instructionSTOP() -> void; + auto instructionSUB_SBC_CMP(uint n) -> void; + auto instructionSWAP() -> void; + auto instructionTO_MOVE(uint n) -> void; + auto instructionWITH(uint n) -> void; + + //switch.cpp + auto instruction(uint8 opcode) -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + //disassembler.cpp + auto disassembleOpcode(char* output) -> void; + auto disassembleALT0(char* output) -> void; + auto disassembleALT1(char* output) -> void; + auto disassembleALT2(char* output) -> void; + auto disassembleALT3(char* output) -> void; +}; + +} diff --git a/waterbox/bsnescore/bsnes/processor/gsu/instruction.cpp b/waterbox/bsnescore/bsnes/processor/gsu/instruction.cpp new file mode 100644 index 0000000000..969e611823 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/gsu/instruction.cpp @@ -0,0 +1,94 @@ +auto GSU::instruction(uint8 opcode) -> void { + #define op(id, name, ...) \ + case id: return instruction##name(__VA_ARGS__); \ + + #define op4(id, name) \ + case id+ 0: return instruction##name((uint4)opcode); \ + case id+ 1: return instruction##name((uint4)opcode); \ + case id+ 2: return instruction##name((uint4)opcode); \ + case id+ 3: return instruction##name((uint4)opcode); \ + + #define op6(id, name) \ + op4(id, name) \ + case id+ 4: return instruction##name((uint4)opcode); \ + case id+ 5: return instruction##name((uint4)opcode); \ + + #define op12(id, name) \ + op6(id, name) \ + case id+ 6: return instruction##name((uint4)opcode); \ + case id+ 7: return instruction##name((uint4)opcode); \ + case id+ 8: return instruction##name((uint4)opcode); \ + case id+ 9: return instruction##name((uint4)opcode); \ + case id+10: return instruction##name((uint4)opcode); \ + case id+11: return instruction##name((uint4)opcode); \ + + #define op15(id, name) \ + op12(id, name) \ + case id+12: return instruction##name((uint4)opcode); \ + case id+13: return instruction##name((uint4)opcode); \ + case id+14: return instruction##name((uint4)opcode); \ + + #define op16(id, name) \ + op15(id, name) \ + case id+15: return instruction##name((uint4)opcode); \ + + switch(opcode) { + op (0x00, STOP) + op (0x01, NOP) + op (0x02, CACHE) + op (0x03, LSR) + op (0x04, ROL) + op (0x05, Branch, 1) //bra + op (0x06, Branch, (regs.sfr.s ^ regs.sfr.ov) == 0) //blt + op (0x07, Branch, (regs.sfr.s ^ regs.sfr.ov) == 1) //bge + op (0x08, Branch, regs.sfr.z == 0) //bne + op (0x09, Branch, regs.sfr.z == 1) //beq + op (0x0a, Branch, regs.sfr.s == 0) //bpl + op (0x0b, Branch, regs.sfr.s == 1) //bmi + op (0x0c, Branch, regs.sfr.cy == 0) //bcc + op (0x0d, Branch, regs.sfr.cy == 1) //bcs + op (0x0e, Branch, regs.sfr.ov == 0) //bvc + op (0x0f, Branch, regs.sfr.ov == 1) //bvs + op16(0x10, TO_MOVE) + op16(0x20, WITH) + op12(0x30, Store) + op (0x3c, LOOP) + op (0x3d, ALT1) + op (0x3e, ALT2) + op (0x3f, ALT3) + op12(0x40, Load) + op (0x4c, PLOT_RPIX) + op (0x4d, SWAP) + op (0x4e, COLOR_CMODE) + op (0x4f, NOT) + op16(0x50, ADD_ADC) + op16(0x60, SUB_SBC_CMP) + op (0x70, MERGE) + op15(0x71, AND_BIC) + op16(0x80, MULT_UMULT) + op (0x90, SBK) + op4 (0x91, LINK) + op (0x95, SEX) + op (0x96, ASR_DIV2) + op (0x97, ROR) + op6 (0x98, JMP_LJMP) + op (0x9e, LOB) + op (0x9f, FMULT_LMULT) + op16(0xa0, IBT_LMS_SMS) + op16(0xb0, FROM_MOVES) + op (0xc0, HIB) + op15(0xc1, OR_XOR) + op15(0xd0, INC) + op (0xdf, GETC_RAMB_ROMB) + op15(0xe0, DEC) + op (0xef, GETB) + op16(0xf0, IWT_LM_SM) + } + + #undef op + #undef op4 + #undef op6 + #undef op12 + #undef op15 + #undef op16 +} diff --git a/waterbox/bsnescore/bsnes/processor/gsu/instructions.cpp b/waterbox/bsnescore/bsnes/processor/gsu/instructions.cpp new file mode 100644 index 0000000000..56e89d411f --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/gsu/instructions.cpp @@ -0,0 +1,424 @@ +//$00 stop +auto GSU::instructionSTOP() -> void { + if(regs.cfgr.irq == 0) { + regs.sfr.irq = 1; + stop(); + } + regs.sfr.g = 0; + regs.pipeline = 0x01; //nop + regs.reset(); +} + +//$01 nop +auto GSU::instructionNOP() -> void { + regs.reset(); +} + +//$02 cache +auto GSU::instructionCACHE() -> void { + if(regs.cbr != (regs.r[15] & 0xfff0)) { + regs.cbr = regs.r[15] & 0xfff0; + flushCache(); + } + regs.reset(); +} + +//$03 lsr +auto GSU::instructionLSR() -> void { + regs.sfr.cy = (regs.sr() & 1); + regs.dr() = regs.sr() >> 1; + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$04 rol +auto GSU::instructionROL() -> void { + bool carry = (regs.sr() & 0x8000); + regs.dr() = (regs.sr() << 1) | regs.sfr.cy; + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.cy = carry; + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$05 bra e +//$06 blt e +//$07 bge e +//$08 bne e +//$09 beq e +//$0a bpl e +//$0b bmi e +//$0c bcc e +//$0d bcs e +//$0e bvc e +//$0f bvs e +auto GSU::instructionBranch(bool take) -> void { + auto displacement = (int8)pipe(); + if(take) regs.r[15] += displacement; +} + +//$10-1f(b0) to rN +//$10-1f(b1) move rN +auto GSU::instructionTO_MOVE(uint n) -> void { + if(!regs.sfr.b) { + regs.dreg = n; + } else { + regs.r[n] = regs.sr(); + regs.reset(); + } +} + +//$20-2f with rN +auto GSU::instructionWITH(uint n) -> void { + regs.sreg = n; + regs.dreg = n; + regs.sfr.b = 1; +} + +//$30-3b(alt0) stw (rN) +//$30-3b(alt1) stb (rN) +auto GSU::instructionStore(uint n) -> void { + regs.ramaddr = regs.r[n]; + writeRAMBuffer(regs.ramaddr, regs.sr()); + if(!regs.sfr.alt1) writeRAMBuffer(regs.ramaddr ^ 1, regs.sr() >> 8); + regs.reset(); +} + +//$3c loop +auto GSU::instructionLOOP() -> void { + regs.r[12]--; + regs.sfr.s = (regs.r[12] & 0x8000); + regs.sfr.z = (regs.r[12] == 0); + if(!regs.sfr.z) regs.r[15] = regs.r[13]; + regs.reset(); +} + +//$3d alt1 +auto GSU::instructionALT1() -> void { + regs.sfr.b = 0; + regs.sfr.alt1 = 1; +} + +//$3e alt2 +auto GSU::instructionALT2() -> void { + regs.sfr.b = 0; + regs.sfr.alt2 = 1; +} + +//$3f alt3 +auto GSU::instructionALT3() -> void { + regs.sfr.b = 0; + regs.sfr.alt1 = 1; + regs.sfr.alt2 = 1; +} + +//$40-4b(alt0) ldw (rN) +//$40-4b(alt1) ldb (rN) +auto GSU::instructionLoad(uint n) -> void { + regs.ramaddr = regs.r[n]; + regs.dr() = readRAMBuffer(regs.ramaddr); + if(!regs.sfr.alt1) regs.dr() |= readRAMBuffer(regs.ramaddr ^ 1) << 8; + regs.reset(); +} + +//$4c(alt0) plot +//$4c(alt1) rpix +auto GSU::instructionPLOT_RPIX() -> void { + if(!regs.sfr.alt1) { + plot(regs.r[1], regs.r[2]); + regs.r[1]++; + } else { + regs.dr() = rpix(regs.r[1], regs.r[2]); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + } + regs.reset(); +} + +//$4d swap +auto GSU::instructionSWAP() -> void { + regs.dr() = regs.sr() >> 8 | regs.sr() << 8; + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$4e(alt0) color +//$4e(alt1) cmode +auto GSU::instructionCOLOR_CMODE() -> void { + if(!regs.sfr.alt1) { + regs.colr = color(regs.sr()); + } else { + regs.por = regs.sr(); + } + regs.reset(); +} + +//$4f not +auto GSU::instructionNOT() -> void { + regs.dr() = ~regs.sr(); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$50-5f(alt0) add rN +//$50-5f(alt1) adc rN +//$50-5f(alt2) add #N +//$50-5f(alt3) adc #N +auto GSU::instructionADD_ADC(uint n) -> void { + if(!regs.sfr.alt2) n = regs.r[n]; + int r = regs.sr() + n + (regs.sfr.alt1 ? regs.sfr.cy : 0); + regs.sfr.ov = ~(regs.sr() ^ n) & (n ^ r) & 0x8000; + regs.sfr.s = (r & 0x8000); + regs.sfr.cy = (r >= 0x10000); + regs.sfr.z = ((uint16)r == 0); + regs.dr() = r; + regs.reset(); +} + +//$60-6f(alt0) sub rN +//$60-6f(alt1) sbc rN +//$60-6f(alt2) sub #N +//$60-6f(alt3) cmp rN +auto GSU::instructionSUB_SBC_CMP(uint n) -> void { + if(!regs.sfr.alt2 || regs.sfr.alt1) n = regs.r[n]; + int r = regs.sr() - n - (!regs.sfr.alt2 && regs.sfr.alt1 ? !regs.sfr.cy : 0); + regs.sfr.ov = (regs.sr() ^ n) & (regs.sr() ^ r) & 0x8000; + regs.sfr.s = (r & 0x8000); + regs.sfr.cy = (r >= 0); + regs.sfr.z = ((uint16)r == 0); + if(!regs.sfr.alt2 || !regs.sfr.alt1) regs.dr() = r; + regs.reset(); +} + +//$70 merge +auto GSU::instructionMERGE() -> void { + regs.dr() = (regs.r[7] & 0xff00) | (regs.r[8] >> 8); + regs.sfr.ov = (regs.dr() & 0xc0c0); + regs.sfr.s = (regs.dr() & 0x8080); + regs.sfr.cy = (regs.dr() & 0xe0e0); + regs.sfr.z = (regs.dr() & 0xf0f0); + regs.reset(); +} + +//$71-7f(alt0) and rN +//$71-7f(alt1) bic rN +//$71-7f(alt2) and #N +//$71-7f(alt3) bic #N +auto GSU::instructionAND_BIC(uint n) -> void { + if(!regs.sfr.alt2) n = regs.r[n]; + regs.dr() = regs.sr() & (regs.sfr.alt1 ? ~n : n); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$80-8f(alt0) mult rN +//$80-8f(alt1) umult rN +//$80-8f(alt2) mult #N +//$80-8f(alt3) umult #N +auto GSU::instructionMULT_UMULT(uint n) -> void { + if(!regs.sfr.alt2) n = regs.r[n]; + regs.dr() = (!regs.sfr.alt1 ? uint16((int8)regs.sr() * (int8)n) : uint16((uint8)regs.sr() * (uint8)n)); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); + if(!regs.cfgr.ms0) step(regs.clsr ? 1 : 2); +} + +//$90 sbk +auto GSU::instructionSBK() -> void { + writeRAMBuffer(regs.ramaddr ^ 0, regs.sr() >> 0); + writeRAMBuffer(regs.ramaddr ^ 1, regs.sr() >> 8); + regs.reset(); +} + +//$91-94 link #N +auto GSU::instructionLINK(uint n) -> void { + regs.r[11] = regs.r[15] + n; + regs.reset(); +} + +//$95 sex +auto GSU::instructionSEX() -> void { + regs.dr() = (int8)regs.sr(); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$96(alt0) asr +//$96(alt1) div2 +auto GSU::instructionASR_DIV2() -> void { + regs.sfr.cy = (regs.sr() & 1); + regs.dr() = ((int16)regs.sr() >> 1) + (regs.sfr.alt1 ? ((regs.sr() + 1) >> 16) : 0); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$97 ror +auto GSU::instructionROR() -> void { + bool carry = (regs.sr() & 1); + regs.dr() = (regs.sfr.cy << 15) | (regs.sr() >> 1); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.cy = carry; + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$98-9d(alt0) jmp rN +//$98-9d(alt1) ljmp rN +auto GSU::instructionJMP_LJMP(uint n) -> void { + if(!regs.sfr.alt1) { + regs.r[15] = regs.r[n]; + } else { + regs.pbr = regs.r[n] & 0x7f; + regs.r[15] = regs.sr(); + regs.cbr = regs.r[15] & 0xfff0; + flushCache(); + } + regs.reset(); +} + +//$9e lob +auto GSU::instructionLOB() -> void { + regs.dr() = regs.sr() & 0xff; + regs.sfr.s = (regs.dr() & 0x80); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$9f(alt0) fmult +//$9f(alt1) lmult +auto GSU::instructionFMULT_LMULT() -> void { + uint32 result = (int16)regs.sr() * (int16)regs.r[6]; + if(regs.sfr.alt1) regs.r[4] = result; + regs.dr() = result >> 16; + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.cy = (result & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); + step((regs.cfgr.ms0 ? 3 : 7) * (regs.clsr ? 1 : 2)); +} + +//$a0-af(alt0) ibt rN,#pp +//$a0-af(alt1) lms rN,(yy) +//$a0-af(alt2) sms (yy),rN +auto GSU::instructionIBT_LMS_SMS(uint n) -> void { + if(regs.sfr.alt1) { + regs.ramaddr = pipe() << 1; + uint8 lo = readRAMBuffer(regs.ramaddr ^ 0) << 0; + regs.r[n] = readRAMBuffer(regs.ramaddr ^ 1) << 8 | lo; + } else if(regs.sfr.alt2) { + regs.ramaddr = pipe() << 1; + writeRAMBuffer(regs.ramaddr ^ 0, regs.r[n] >> 0); + writeRAMBuffer(regs.ramaddr ^ 1, regs.r[n] >> 8); + } else { + regs.r[n] = (int8)pipe(); + } + regs.reset(); +} + +//$b0-bf(b0) from rN +//$b0-bf(b1) moves rN +auto GSU::instructionFROM_MOVES(uint n) -> void { + if(!regs.sfr.b) { + regs.sreg = n; + } else { + regs.dr() = regs.r[n]; + regs.sfr.ov = (regs.dr() & 0x80); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); + } +} + +//$c0 hib +auto GSU::instructionHIB() -> void { + regs.dr() = regs.sr() >> 8; + regs.sfr.s = (regs.dr() & 0x80); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$c1-cf(alt0) or rN +//$c1-cf(alt1) xor rN +//$c1-cf(alt2) or #N +//$c1-cf(alt3) xor #N +auto GSU::instructionOR_XOR(uint n) -> void { + if(!regs.sfr.alt2) n = regs.r[n]; + regs.dr() = (!regs.sfr.alt1 ? (regs.sr() | n) : (regs.sr() ^ n)); + regs.sfr.s = (regs.dr() & 0x8000); + regs.sfr.z = (regs.dr() == 0); + regs.reset(); +} + +//$d0-de inc rN +auto GSU::instructionINC(uint n) -> void { + regs.r[n]++; + regs.sfr.s = (regs.r[n] & 0x8000); + regs.sfr.z = (regs.r[n] == 0); + regs.reset(); +} + +//$df(alt0) getc +//$df(alt2) ramb +//$df(alt3) romb +auto GSU::instructionGETC_RAMB_ROMB() -> void { + if(!regs.sfr.alt2) { + regs.colr = color(readROMBuffer()); + } else if(!regs.sfr.alt1) { + syncRAMBuffer(); + regs.rambr = regs.sr() & 0x01; + } else { + syncROMBuffer(); + regs.rombr = regs.sr() & 0x7f; + } + regs.reset(); +} + +//$e0-ee dec rN +auto GSU::instructionDEC(uint n) -> void { + regs.r[n]--; + regs.sfr.s = (regs.r[n] & 0x8000); + regs.sfr.z = (regs.r[n] == 0); + regs.reset(); +} + +//$ef(alt0) getb +//$ef(alt1) getbh +//$ef(alt2) getbl +//$ef(alt3) getbs +auto GSU::instructionGETB() -> void { + switch(regs.sfr.alt2 << 1 | regs.sfr.alt1 << 0) { + case 0: regs.dr() = readROMBuffer(); break; + case 1: regs.dr() = readROMBuffer() << 8 | (uint8)regs.sr(); break; + case 2: regs.dr() = (regs.sr() & 0xff00) | readROMBuffer(); break; + case 3: regs.dr() = (int8)readROMBuffer(); break; + } + regs.reset(); +} + +//$f0-ff(alt0) iwt rN,#xx +//$f0-ff(alt1) lm rN,(xx) +//$f0-ff(alt2) sm (xx),rN +auto GSU::instructionIWT_LM_SM(uint n) -> void { + if(regs.sfr.alt1) { + regs.ramaddr = pipe() << 0; + regs.ramaddr |= pipe() << 8; + uint8 lo = readRAMBuffer(regs.ramaddr ^ 0) << 0; + regs.r[n] = readRAMBuffer(regs.ramaddr ^ 1) << 8 | lo; + } else if(regs.sfr.alt2) { + regs.ramaddr = pipe() << 0; + regs.ramaddr |= pipe() << 8; + writeRAMBuffer(regs.ramaddr ^ 0, regs.r[n] >> 0); + writeRAMBuffer(regs.ramaddr ^ 1, regs.r[n] >> 8); + } else { + uint8 lo = pipe(); + regs.r[n] = pipe() << 8 | lo; + } + regs.reset(); +} diff --git a/waterbox/bsnescore/bsnes/processor/gsu/registers.hpp b/waterbox/bsnescore/bsnes/processor/gsu/registers.hpp new file mode 100644 index 0000000000..60492c90a8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/gsu/registers.hpp @@ -0,0 +1,164 @@ +struct Register { + uint16 data = 0; + bool modified = false; + + inline operator uint() const { + return data; + } + + inline auto assign(uint value) -> uint16 { + modified = true; + return data = value; + } + + inline auto operator++() { return assign(data + 1); } + inline auto operator--() { return assign(data - 1); } + inline auto operator++(int) { uint r = data; assign(data + 1); return r; } + inline auto operator--(int) { uint r = data; assign(data - 1); return r; } + inline auto operator = (uint i) { return assign(i); } + inline auto operator |= (uint i) { return assign(data | i); } + inline auto operator ^= (uint i) { return assign(data ^ i); } + inline auto operator &= (uint i) { return assign(data & i); } + inline auto operator <<= (uint i) { return assign(data << i); } + inline auto operator >>= (uint i) { return assign(data >> i); } + inline auto operator += (uint i) { return assign(data + i); } + inline auto operator -= (uint i) { return assign(data - i); } + inline auto operator *= (uint i) { return assign(data * i); } + inline auto operator /= (uint i) { return assign(data / i); } + inline auto operator %= (uint i) { return assign(data % i); } + + inline auto operator = (const Register& value) { return assign(value); } + + Register() = default; + Register(const Register&) = delete; +}; + +struct SFR { + uint16_t data = 0; + + BitField<16, 1> z {&data}; //zero flag + BitField<16, 2> cy {&data}; //carry flag + BitField<16, 3> s {&data}; //sign flag + BitField<16, 4> ov {&data}; //overflow flag + BitField<16, 5> g {&data}; //go flag + BitField<16, 6> r {&data}; //ROM r14 flag + BitField<16, 8> alt1{&data}; //alt1 instruction mode + BitField<16, 9> alt2{&data}; //alt2 instruction mode + BitField<16,10> il {&data}; //immediate lower 8-bit flag + BitField<16,11> ih {&data}; //immediate upper 8-bit flag + BitField<16,12> b {&data}; //with flag + BitField<16,15> irq {&data}; //interrupt flag + + BitRange<16,8,9> alt{&data}; //composite instruction mode + + inline operator uint() const { return data & 0x9f7e; } + inline auto& operator=(const uint value) { return data = value, *this; } +}; + +struct SCMR { + uint ht; + bool ron; + bool ran; + uint md; + + operator uint() const { + return ((ht >> 1) << 5) | (ron << 4) | (ran << 3) | ((ht & 1) << 2) | (md); + } + + auto& operator=(uint data) { + ht = (bool)(data & 0x20) << 1; + ht |= (bool)(data & 0x04) << 0; + ron = data & 0x10; + ran = data & 0x08; + md = data & 0x03; + return *this; + } +}; + +struct POR { + bool obj; + bool freezehigh; + bool highnibble; + bool dither; + bool transparent; + + operator uint() const { + return (obj << 4) | (freezehigh << 3) | (highnibble << 2) | (dither << 1) | (transparent); + } + + auto& operator=(uint data) { + obj = data & 0x10; + freezehigh = data & 0x08; + highnibble = data & 0x04; + dither = data & 0x02; + transparent = data & 0x01; + return *this; + } +}; + +struct CFGR { + bool irq; + bool ms0; + + operator uint() const { + return (irq << 7) | (ms0 << 5); + } + + auto& operator=(uint data) { + irq = data & 0x80; + ms0 = data & 0x20; + return *this; + } +}; + +struct Registers { + uint8 pipeline; + uint16 ramaddr; + + Register r[16]; //general purpose registers + SFR sfr; //status flag register + uint8 pbr; //program bank register + uint8 rombr; //game pack ROM bank register + bool rambr; //game pack RAM bank register + uint16 cbr; //cache base register + uint8 scbr; //screen base register + SCMR scmr; //screen mode register + uint8 colr; //color register + POR por; //plot option register + bool bramr; //back-up RAM register + uint8 vcr; //version code register + CFGR cfgr; //config register + bool clsr; //clock select register + + uint romcl; //clock ticks until romdr is valid + uint8 romdr; //ROM buffer data register + + uint ramcl; //clock ticks until ramdr is valid + uint16 ramar; //RAM buffer address register + uint8 ramdr; //RAM buffer data register + + uint sreg; + uint dreg; + auto& sr() { return r[sreg]; } //source register (from) + auto& dr() { return r[dreg]; } //destination register (to) + + auto reset() -> void { + sfr.b = 0; + sfr.alt1 = 0; + sfr.alt2 = 0; + + sreg = 0; + dreg = 0; + } +} regs; + +struct Cache { + uint8 buffer[512]; + bool valid[32]; +} cache; + +struct PixelCache { + uint16 offset; + uint8 bitpend; + uint8 data[8]; +} pixelcache[2]; diff --git a/waterbox/bsnescore/bsnes/processor/gsu/serialization.cpp b/waterbox/bsnescore/bsnes/processor/gsu/serialization.cpp new file mode 100644 index 0000000000..816b22bbf9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/gsu/serialization.cpp @@ -0,0 +1,56 @@ +auto GSU::serialize(serializer& s) -> void { + s.integer(regs.pipeline); + s.integer(regs.ramaddr); + + for(auto n : range(16)) { + s.integer(regs.r[n].data); + s.integer(regs.r[n].modified); + } + + s.integer(regs.sfr.data); + s.integer(regs.pbr); + s.integer(regs.rombr); + s.integer(regs.rambr); + s.integer(regs.cbr); + s.integer(regs.scbr); + + s.integer(regs.scmr.ht); + s.integer(regs.scmr.ron); + s.integer(regs.scmr.ran); + s.integer(regs.scmr.md); + + s.integer(regs.colr); + + s.integer(regs.por.obj); + s.integer(regs.por.freezehigh); + s.integer(regs.por.highnibble); + s.integer(regs.por.dither); + s.integer(regs.por.transparent); + + s.integer(regs.bramr); + s.integer(regs.vcr); + + s.integer(regs.cfgr.irq); + s.integer(regs.cfgr.ms0); + + s.integer(regs.clsr); + + s.integer(regs.romcl); + s.integer(regs.romdr); + + s.integer(regs.ramcl); + s.integer(regs.ramar); + s.integer(regs.ramdr); + + s.integer(regs.sreg); + s.integer(regs.dreg); + + s.array(cache.buffer); + s.array(cache.valid); + + for(uint n : range(2)) { + s.integer(pixelcache[n].offset); + s.integer(pixelcache[n].bitpend); + s.array(pixelcache[n].data); + } +} diff --git a/waterbox/bsnescore/bsnes/processor/hg51b/hg51b.cpp b/waterbox/bsnescore/bsnes/processor/hg51b/hg51b.cpp new file mode 100644 index 0000000000..499f1878f2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/hg51b/hg51b.cpp @@ -0,0 +1,125 @@ +#include +#include "hg51b.hpp" + +namespace Processor { + +#include "registers.cpp" +#include "instruction.cpp" +#include "instructions.cpp" +#include "serialization.cpp" + +auto HG51B::lock() -> void { + io.lock = 1; +} + +auto HG51B::halt() -> void { + io.halt = 1; +} + +auto HG51B::wait(uint24 address) -> uint { + if(isROM(address)) return 1 + io.wait.rom; + if(isRAM(address)) return 1 + io.wait.ram; + return 1; +} + +auto HG51B::main() -> void { + if(io.lock) return step(1); + if(io.suspend.enable) return suspend(); + if(io.cache.enable) return cache(), void(); + if(io.dma.enable) return dma(); + if(io.halt) return step(1); + return execute(); +} + +auto HG51B::step(uint clocks) -> void { + if(io.bus.enable) { + if(io.bus.pending > clocks) { + io.bus.pending -= clocks; + } else { + io.bus.enable = 0; + io.bus.pending = 0; + if(io.bus.reading) io.bus.reading = 0, r.mdr = read(io.bus.address); + if(io.bus.writing) io.bus.writing = 0, write(io.bus.address, r.mdr); + } + } +} + +auto HG51B::execute() -> void { + if(!cache()) return halt(); + auto opcode = programRAM[io.cache.page][r.pc]; + advance(); + step(1); + instructionTable[opcode](); +} + +auto HG51B::advance() -> void { + if(++r.pc == 0) { + if(io.cache.page == 1) return halt(); + io.cache.page = 1; + if(io.cache.lock[io.cache.page]) return halt(); + r.pb = r.p; + if(!cache()) return halt(); + } +} + +auto HG51B::suspend() -> void { + if(!io.suspend.duration) return step(1); //indefinite + step(io.suspend.duration); + io.suspend.duration = 0; + io.suspend.enable = 0; +} + +auto HG51B::cache() -> bool { + uint24 address = io.cache.base + r.pb * 512; + + //try to use the current page ... + if(io.cache.address[io.cache.page] == address) return io.cache.enable = 0, true; + //if it's not valid, try to use the other page ... + io.cache.page ^= 1; + if(io.cache.address[io.cache.page] == address) return io.cache.enable = 0, true; + //if it's not valid, try to load into the other page ... + if(io.cache.lock[io.cache.page]) io.cache.page ^= 1; + //if it's locked, try to load into the first page ... + if(io.cache.lock[io.cache.page]) return io.cache.enable = 0, false; + + io.cache.address[io.cache.page] = address; + for(uint offset : range(256)) { + step(wait(address)); + programRAM[io.cache.page][offset] = read(address++) << 0; + programRAM[io.cache.page][offset] |= read(address++) << 8; + } + return io.cache.enable = 0, true; +} + +auto HG51B::dma() -> void { + for(uint offset : range(io.dma.length)) { + uint24 source = io.dma.source + offset; + uint24 target = io.dma.target + offset; + + if(isROM(source) && isROM(target)) return lock(); + if(isRAM(source) && isRAM(target)) return lock(); + + step(wait(source)); + auto data = read(source); + + step(wait(target)); + write(target, data); + } + + io.dma.enable = 0; +} + +auto HG51B::running() const -> bool { + return io.cache.enable || io.dma.enable || io.bus.pending || !io.halt; +} + +auto HG51B::busy() const -> bool { + return io.cache.enable || io.dma.enable || io.bus.pending; +} + +auto HG51B::power() -> void { + r = {}; + io = {}; +} + +} diff --git a/waterbox/bsnescore/bsnes/processor/hg51b/hg51b.hpp b/waterbox/bsnescore/bsnes/processor/hg51b/hg51b.hpp new file mode 100644 index 0000000000..daae9e87fc --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/hg51b/hg51b.hpp @@ -0,0 +1,182 @@ +#pragma once + +//Hitachi HG51B S169 + +namespace Processor { + +struct HG51B { + //instruction.cpp + HG51B(); + + //hg51b.cpp + virtual auto step(uint clocks) -> void; + virtual auto isROM(uint address) -> bool = 0; + virtual auto isRAM(uint address) -> bool = 0; + virtual auto read(uint address) -> uint8 = 0; + virtual auto write(uint address, uint8 data) -> void = 0; + virtual auto lock() -> void; + virtual auto halt() -> void; + auto wait(uint24 address) -> uint; + auto main() -> void; + auto execute() -> void; + auto advance() -> void; + auto suspend() -> void; + auto cache() -> bool; + auto dma() -> void; + auto running() const -> bool; + auto busy() const -> bool; + + auto power() -> void; + + //instructions.cpp + auto push() -> void; + auto pull() -> void; + + auto algorithmADD(uint24 x, uint24 y) -> uint24; + auto algorithmAND(uint24 x, uint24 y) -> uint24; + auto algorithmASR(uint24 a, uint5 s) -> uint24; + auto algorithmMUL(int24 x, int24 y) -> uint48; + auto algorithmOR(uint24 x, uint24 y) -> uint24; + auto algorithmROR(uint24 a, uint5 s) -> uint24; + auto algorithmSHL(uint24 a, uint5 s) -> uint24; + auto algorithmSHR(uint24 a, uint5 s) -> uint24; + auto algorithmSUB(uint24 x, uint24 y) -> uint24; + auto algorithmSX(uint24 x) -> uint24; + auto algorithmXNOR(uint24 x, uint24 y) -> uint24; + auto algorithmXOR(uint24 x, uint24 y) -> uint24; + + auto instructionADD(uint7 reg, uint5 shift) -> void; + auto instructionADD(uint8 imm, uint5 shift) -> void; + auto instructionAND(uint7 reg, uint5 shift) -> void; + auto instructionAND(uint8 imm, uint5 shift) -> void; + auto instructionASR(uint7 reg) -> void; + auto instructionASR(uint5 imm) -> void; + auto instructionCLEAR() -> void; + auto instructionCMP(uint7 reg, uint5 shift) -> void; + auto instructionCMP(uint8 imm, uint5 shift) -> void; + auto instructionCMPR(uint7 reg, uint5 shift) -> void; + auto instructionCMPR(uint8 imm, uint5 shift) -> void; + auto instructionHALT() -> void; + auto instructionINC(uint24& reg) -> void; + auto instructionJMP(uint8 data, uint1 far, const uint1& take) -> void; + auto instructionJSR(uint8 data, uint1 far, const uint1& take) -> void; + auto instructionLD(uint24& out, uint7 reg) -> void; + auto instructionLD(uint15& out, uint4 reg) -> void; + auto instructionLD(uint24& out, uint8 imm) -> void; + auto instructionLD(uint15& out, uint8 imm) -> void; + auto instructionLDL(uint15& out, uint8 imm) -> void; + auto instructionLDH(uint15& out, uint7 imm) -> void; + auto instructionMUL(uint7 reg) -> void; + auto instructionMUL(uint8 imm) -> void; + auto instructionNOP() -> void; + auto instructionOR(uint7 reg, uint5 shift) -> void; + auto instructionOR(uint8 imm, uint5 shift) -> void; + auto instructionRDRAM(uint2 byte, uint24& a) -> void; + auto instructionRDRAM(uint2 byte, uint8 imm) -> void; + auto instructionRDROM(uint24& reg) -> void; + auto instructionRDROM(uint10 imm) -> void; + auto instructionROR(uint7 reg) -> void; + auto instructionROR(uint5 imm) -> void; + auto instructionRTS() -> void; + auto instructionSHL(uint7 reg) -> void; + auto instructionSHL(uint5 imm) -> void; + auto instructionSHR(uint7 reg) -> void; + auto instructionSHR(uint5 imm) -> void; + auto instructionSKIP(uint1 take, const uint1& flag) -> void; + auto instructionST(uint7 reg, uint24& in) -> void; + auto instructionSUB(uint7 reg, uint5 shift) -> void; + auto instructionSUB(uint8 imm, uint5 shift) -> void; + auto instructionSUBR(uint7 reg, uint5 shift) -> void; + auto instructionSUBR(uint8 imm, uint5 shift) -> void; + auto instructionSWAP(uint24& a, uint4 reg) -> void; + auto instructionSXB() -> void; + auto instructionSXW() -> void; + auto instructionWAIT() -> void; + auto instructionWRRAM(uint2 byte, uint24& a) -> void; + auto instructionWRRAM(uint2 byte, uint8 imm) -> void; + auto instructionXNOR(uint7 reg, uint5 shift) -> void; + auto instructionXNOR(uint8 imm, uint5 shift) -> void; + auto instructionXOR(uint7 reg, uint5 shift) -> void; + auto instructionXOR(uint8 imm, uint5 shift) -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint16 programRAM[2][256]; //instruction cache + uint24 dataROM[1024]; + uint8 dataRAM[3072]; + + //registers.cpp + auto readRegister(uint7 address) -> uint24; + auto writeRegister(uint7 address, uint24 data) -> void; + +protected: + struct Registers { + uint15 pb; //program bank + uint8 pc; //program counter + + boolean n; //negative + boolean z; //zero + boolean c; //carry + boolean v; //overflow + boolean i; //interrupt + + uint24 a; //accumulator + uint15 p; //page register + uint48 mul; //multiplier + uint24 mdr; //bus memory data register + uint24 rom; //data ROM data buffer + uint24 ram; //data RAM data buffer + uint24 mar; //bus memory address register + uint24 dpr; //data RAM address pointer + uint24 gpr[16]; //general purpose registers + } r; + + struct IO { + uint1 lock; + uint1 halt = 1; + uint1 irq; //0 = enable, 1 = disable + uint1 rom = 1; //0 = 2 ROMs, 1 = 1 ROM + uint8 vector[32]; + + struct Wait { + uint3 rom = 3; + uint3 ram = 3; + } wait; + + struct Suspend { + uint1 enable; + uint8 duration; + } suspend; + + struct Cache { + uint1 enable; + uint1 page; + uint1 lock[2]; + uint24 address[2]; //cache address is in bytes; so 24-bit + uint24 base; //base address is also in bytes + uint15 pb; + uint8 pc; + } cache; + + struct DMA { + uint1 enable; + uint24 source; + uint24 target; + uint16 length; + } dma; + + struct Bus { + uint1 enable; + uint1 reading; + uint1 writing; + uint4 pending; + uint24 address; + } bus; + } io; + + uint23 stack[8]; + function instructionTable[65536]; +}; + +} diff --git a/waterbox/bsnescore/bsnes/processor/hg51b/instruction.cpp b/waterbox/bsnescore/bsnes/processor/hg51b/instruction.cpp new file mode 100644 index 0000000000..f3e51d0232 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/hg51b/instruction.cpp @@ -0,0 +1,639 @@ +HG51B::HG51B() { + #define bind(id, name, ...) { \ + if(instructionTable[id]) throw; \ + instructionTable[id] = [=] { return instruction##name(__VA_ARGS__); }; \ + } + + #define pattern(s) \ + std::integral_constant::value + + static const uint5 shifts[] = {0, 1, 8, 16}; + + //NOP + for(uint10 null : range(1024)) { + auto opcode = pattern("0000 00.. .... ...."); + bind(opcode | null << 0, NOP); + } + + //??? + for(uint10 null : range(1024)) { + auto opcode = pattern("0000 01.. .... ...."); + bind(opcode | null << 0, NOP); + } + + //JMP imm + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0000 10f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JMP, data, far, 1); + } + + //JMP EQ,imm + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0000 11f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JMP, data, far, r.z); + } + + //JMP GE,imm + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0001 00f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JMP, data, far, r.c); + } + + //JMP MI,imm + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0001 01f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JMP, data, far, r.n); + } + + //JMP VS,imm + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0001 10f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JMP, data, far, r.v); + } + + //WAIT + for(uint10 null : range(1024)) { + auto opcode = pattern("0001 11.. .... ...."); + bind(opcode | null << 0, WAIT); + } + + //??? + for(uint10 null : range(1024)) { + auto opcode = pattern("0010 00.. .... ...."); + bind(opcode | null << 0, NOP); + } + + //SKIP V + for(uint1 take : range( 2)) + for(uint7 null : range(128)) { + auto opcode = pattern("0010 0100 .... ...t"); + bind(opcode | take << 0 | null << 1, SKIP, take, r.v); + } + + //SKIP C + for(uint1 take : range( 2)) + for(uint7 null : range(128)) { + auto opcode = pattern("0010 0101 .... ...t"); + bind(opcode | take << 0 | null << 1, SKIP, take, r.c); + } + + //SKIP Z + for(uint1 take : range( 2)) + for(uint7 null : range(128)) { + auto opcode = pattern("0010 0110 .... ...t"); + bind(opcode | take << 0 | null << 1, SKIP, take, r.z); + } + + //SKIP N + for(uint1 take : range( 2)) + for(uint7 null : range(128)) { + auto opcode = pattern("0010 0111 .... ...t"); + bind(opcode | take << 0 | null << 1, SKIP, take, r.n); + } + + //JSR + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0010 10f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JSR, data, far, 1); + } + + //JSR EQ,imm + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0010 11f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JSR, data, far, r.z); + } + + //JSR GE,imm + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0011 00f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JSR, data, far, r.c); + } + + //JSR MI,imm + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0011 01f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JSR, data, far, r.n); + } + + //JSR VS,imm + for(uint8 data : range(256)) + for(uint1 null : range( 2)) + for(uint1 far : range( 2)) { + auto opcode = pattern("0011 10f. dddd dddd"); + bind(opcode | data << 0 | null << 8 | far << 9, JSR, data, far, r.v); + } + + //RTS + for(uint10 null : range(1024)) { + auto opcode = pattern("0011 11.. .... ...."); + bind(opcode | null << 0, RTS); + } + + //INC MAR + for(uint10 null : range(1024)) { + auto opcode = pattern("0100 00.. .... ...."); + bind(opcode | null << 0, INC, r.mar); + } + + //??? + for(uint10 null : range(1024)) { + auto opcode = pattern("0100 01.. .... ...."); + bind(opcode | null << 0, NOP); + } + + //CMPR A< void { + stack[7] = stack[6]; + stack[6] = stack[5]; + stack[5] = stack[4]; + stack[4] = stack[3]; + stack[3] = stack[2]; + stack[2] = stack[1]; + stack[1] = stack[0]; + stack[0] = r.pb << 8 | r.pc << 0; +} + +auto HG51B::pull() -> void { + auto pc = stack[0]; + stack[0] = stack[1]; + stack[1] = stack[2]; + stack[2] = stack[3]; + stack[3] = stack[4]; + stack[4] = stack[5]; + stack[5] = stack[6]; + stack[6] = stack[7]; + stack[7] = 0x0000; + + r.pb = pc >> 8; + r.pc = pc >> 0; +} + +// + +auto HG51B::algorithmADD(uint24 x, uint24 y) -> uint24 { + int z = x + y; + r.n = z & 0x800000; + r.z = (uint24)z == 0; + r.c = z > 0xffffff; + r.v = ~(x ^ y) & (x ^ z) & 0x800000; + return z; +} + +auto HG51B::algorithmAND(uint24 x, uint24 y) -> uint24 { + x = x & y; + r.n = x & 0x800000; + r.z = x == 0; + return x; +} + +auto HG51B::algorithmASR(uint24 a, uint5 s) -> uint24 { + if(s > 24) s = 0; + a = (int24)a >> s; + r.n = a & 0x800000; + r.z = a == 0; + return a; +} + +auto HG51B::algorithmMUL(int24 x, int24 y) -> uint48 { + return (int48)x * (int48)y; +} + +auto HG51B::algorithmOR(uint24 x, uint24 y) -> uint24 { + x = x | y; + r.n = x & 0x800000; + r.z = x == 0; + return x; +} + +auto HG51B::algorithmROR(uint24 a, uint5 s) -> uint24 { + if(s > 24) s = 0; + a = (a >> s) | (a << 24 - s); + r.n = a & 0x800000; + r.z = a == 0; + return a; +} + +auto HG51B::algorithmSHL(uint24 a, uint5 s) -> uint24 { + if(s > 24) s = 0; + a = a << s; + r.n = a & 0x800000; + r.z = a == 0; + return a; +} + +auto HG51B::algorithmSHR(uint24 a, uint5 s) -> uint24 { + if(s > 24) s = 0; + a = a >> s; + r.n = a & 0x800000; + r.z = a == 0; + return a; +} + +auto HG51B::algorithmSUB(uint24 x, uint24 y) -> uint24 { + int z = x - y; + r.n = z & 0x800000; + r.z = (uint24)z == 0; + r.c = z >= 0; + r.v = ~(x ^ y) & (x ^ z) & 0x800000; + return z; +} + +auto HG51B::algorithmSX(uint24 x) -> uint24 { + r.n = x & 0x800000; + r.z = x == 0; + return x; +} + +auto HG51B::algorithmXNOR(uint24 x, uint24 y) -> uint24 { + x = ~x ^ y; + r.n = x & 0x800000; + r.z = x == 0; + return x; +} + +auto HG51B::algorithmXOR(uint24 x, uint24 y) -> uint24 { + x = x ^ y; + r.n = x & 0x800000; + r.z = x == 0; + return x; +} + +// + +auto HG51B::instructionADD(uint7 reg, uint5 shift) -> void { + r.a = algorithmADD(r.a << shift, readRegister(reg)); +} + +auto HG51B::instructionADD(uint8 imm, uint5 shift) -> void { + r.a = algorithmADD(r.a << shift, imm); +} + +auto HG51B::instructionAND(uint7 reg, uint5 shift) -> void { + r.a = algorithmAND(r.a << shift, readRegister(reg)); +} + +auto HG51B::instructionAND(uint8 imm, uint5 shift) -> void { + r.a = algorithmAND(r.a << shift, imm); +} + +auto HG51B::instructionASR(uint7 reg) -> void { + r.a = algorithmASR(r.a, readRegister(reg)); +} + +auto HG51B::instructionASR(uint5 imm) -> void { + r.a = algorithmASR(r.a, imm); +} + +auto HG51B::instructionCLEAR() -> void { + r.a = 0; + r.p = 0; + r.ram = 0; + r.dpr = 0; +} + +auto HG51B::instructionCMP(uint7 reg, uint5 shift) -> void { + algorithmSUB(r.a << shift, readRegister(reg)); +} + +auto HG51B::instructionCMP(uint8 imm, uint5 shift) -> void { + algorithmSUB(r.a << shift, imm); +} + +auto HG51B::instructionCMPR(uint7 reg, uint5 shift) -> void { + algorithmSUB(readRegister(reg), r.a << shift); +} + +auto HG51B::instructionCMPR(uint8 imm, uint5 shift) -> void { + algorithmSUB(imm, r.a << shift); +} + +auto HG51B::instructionHALT() -> void { + halt(); +} + +auto HG51B::instructionINC(uint24& reg) -> void { + reg++; +} + +auto HG51B::instructionJMP(uint8 data, uint1 far, const uint1& take) -> void { + if(!take) return; + if(far) r.pb = r.p; + r.pc = data; + step(2); +} + +auto HG51B::instructionJSR(uint8 data, uint1 far, const uint1& take) -> void { + if(!take) return; + push(); + if(far) r.pb = r.p; + r.pc = data; + step(2); +} + +auto HG51B::instructionLD(uint24& out, uint7 reg) -> void { + out = readRegister(reg); +} + +auto HG51B::instructionLD(uint15& out, uint4 reg) -> void { + out = r.gpr[reg]; +} + +auto HG51B::instructionLD(uint24& out, uint8 imm) -> void { + out = imm; +} + +auto HG51B::instructionLD(uint15& out, uint8 imm) -> void { + out = imm; +} + +auto HG51B::instructionLDL(uint15& out, uint8 imm) -> void { + out = out & 0x7f00 | imm << 0; +} + +auto HG51B::instructionLDH(uint15& out, uint7 imm) -> void { + out = out & 0x00ff | (imm & 0x7f) << 8; +} + +auto HG51B::instructionMUL(uint7 reg) -> void { + r.mul = algorithmMUL(r.a, readRegister(reg)); +} + +auto HG51B::instructionMUL(uint8 imm) -> void { + r.mul = algorithmMUL(r.a, imm); +} + +auto HG51B::instructionNOP() -> void { +} + +auto HG51B::instructionOR(uint7 reg, uint5 shift) -> void { + r.a = algorithmOR(r.a << shift, readRegister(reg)); +} + +auto HG51B::instructionOR(uint8 imm, uint5 shift) -> void { + r.a = algorithmOR(r.a << shift, imm); +} + +auto HG51B::instructionRDRAM(uint2 byte, uint24& a) -> void { + uint12 address = a; + if(address >= 0xc00) address -= 0x400; + r.ram.byte(byte) = dataRAM[address]; +} + +auto HG51B::instructionRDRAM(uint2 byte, uint8 imm) -> void { + uint12 address = r.dpr + imm; + if(address >= 0xc00) address -= 0x400; + r.ram.byte(byte) = dataRAM[address]; +} + +auto HG51B::instructionRDROM(uint24& reg) -> void { + r.rom = dataROM[(uint10)reg]; +} + +auto HG51B::instructionRDROM(uint10 imm) -> void { + r.rom = dataROM[imm]; +} + +auto HG51B::instructionROR(uint7 reg) -> void { + r.a = algorithmROR(r.a, readRegister(reg)); +} + +auto HG51B::instructionROR(uint5 imm) -> void { + r.a = algorithmROR(r.a, imm); +} + +auto HG51B::instructionRTS() -> void { + pull(); + step(2); +} + +auto HG51B::instructionSKIP(uint1 take, const uint1& flag) -> void { + if(flag != take) return; + advance(); + step(1); +} + +auto HG51B::instructionSHL(uint7 reg) -> void { + r.a = algorithmSHL(r.a, readRegister(reg)); +} + +auto HG51B::instructionSHL(uint5 imm) -> void { + r.a = algorithmSHL(r.a, imm); +} + +auto HG51B::instructionSHR(uint7 reg) -> void { + r.a = algorithmSHR(r.a, readRegister(reg)); +} + +auto HG51B::instructionSHR(uint5 imm) -> void { + r.a = algorithmSHR(r.a, imm); +} + +auto HG51B::instructionST(uint7 reg, uint24& in) -> void { + writeRegister(reg, in); +} + +auto HG51B::instructionSUB(uint7 reg, uint5 shift) -> void { + r.a = algorithmSUB(r.a << shift, readRegister(reg)); +} + +auto HG51B::instructionSUB(uint8 imm, uint5 shift) -> void { + r.a = algorithmSUB(r.a << shift, imm); +} + +auto HG51B::instructionSUBR(uint7 reg, uint5 shift) -> void { + r.a = algorithmSUB(readRegister(reg), r.a << shift); +} + +auto HG51B::instructionSUBR(uint8 imm, uint5 shift) -> void { + r.a = algorithmSUB(imm, r.a << shift); +} + +auto HG51B::instructionSWAP(uint24& a, uint4 reg) -> void { + swap(a, r.gpr[reg]); +} + +auto HG51B::instructionSXB() -> void { + r.a = algorithmSX((int8)r.a); +} + +auto HG51B::instructionSXW() -> void { + r.a = algorithmSX((int16)r.a); +} + +auto HG51B::instructionWAIT() -> void { + if(!io.bus.enable) return; + return step(io.bus.pending); +} + +auto HG51B::instructionWRRAM(uint2 byte, uint24& a) -> void { + uint12 address = a; + if(address >= 0xc00) address -= 0x400; + dataRAM[address] = r.ram.byte(byte); +} + +auto HG51B::instructionWRRAM(uint2 byte, uint8 imm) -> void { + uint12 address = r.dpr + imm; + if(address >= 0xc00) address -= 0x400; + dataRAM[address] = r.ram.byte(byte); +} + +auto HG51B::instructionXNOR(uint7 reg, uint5 shift) -> void { + r.a = algorithmXNOR(r.a << shift, readRegister(reg)); +} + +auto HG51B::instructionXNOR(uint8 imm, uint5 shift) -> void { + r.a = algorithmXNOR(r.a << shift, imm); +} + +auto HG51B::instructionXOR(uint7 reg, uint5 shift) -> void { + r.a = algorithmXOR(r.a << shift, readRegister(reg)); +} + +auto HG51B::instructionXOR(uint8 imm, uint5 shift) -> void { + r.a = algorithmXOR(r.a << shift, imm); +} diff --git a/waterbox/bsnescore/bsnes/processor/hg51b/registers.cpp b/waterbox/bsnescore/bsnes/processor/hg51b/registers.cpp new file mode 100644 index 0000000000..65bb42bbf4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/hg51b/registers.cpp @@ -0,0 +1,106 @@ +auto HG51B::readRegister(uint7 address) -> uint24 { + switch(address) { + case 0x01: return r.mul >> 24 & 0xffffff; + case 0x02: return r.mul >> 0 & 0xffffff; + case 0x03: return r.mdr; + case 0x08: return r.rom; + case 0x0c: return r.ram; + case 0x13: return r.mar; + case 0x1c: return r.dpr; + case 0x20: return r.pc; + case 0x28: return r.p; + case 0x2e: + io.bus.enable = 1; + io.bus.reading = 1; + io.bus.pending = 1 + io.wait.rom; + io.bus.address = r.mar; + return 0x000000; + case 0x2f: + io.bus.enable = 1; + io.bus.reading = 1; + io.bus.pending = 1 + io.wait.ram; + io.bus.address = r.mar; + return 0x000000; + + //constant registers + case 0x50: return 0x000000; + case 0x51: return 0xffffff; + case 0x52: return 0x00ff00; + case 0x53: return 0xff0000; + case 0x54: return 0x00ffff; + case 0x55: return 0xffff00; + case 0x56: return 0x800000; + case 0x57: return 0x7fffff; + case 0x58: return 0x008000; + case 0x59: return 0x007fff; + case 0x5a: return 0xff7fff; + case 0x5b: return 0xffff7f; + case 0x5c: return 0x010000; + case 0x5d: return 0xfeffff; + case 0x5e: return 0x000100; + case 0x5f: return 0x00feff; + + //general purpose registers + case 0x60: case 0x70: return r.gpr[ 0]; + case 0x61: case 0x71: return r.gpr[ 1]; + case 0x62: case 0x72: return r.gpr[ 2]; + case 0x63: case 0x73: return r.gpr[ 3]; + case 0x64: case 0x74: return r.gpr[ 4]; + case 0x65: case 0x75: return r.gpr[ 5]; + case 0x66: case 0x76: return r.gpr[ 6]; + case 0x67: case 0x77: return r.gpr[ 7]; + case 0x68: case 0x78: return r.gpr[ 8]; + case 0x69: case 0x79: return r.gpr[ 9]; + case 0x6a: case 0x7a: return r.gpr[10]; + case 0x6b: case 0x7b: return r.gpr[11]; + case 0x6c: case 0x7c: return r.gpr[12]; + case 0x6d: case 0x7d: return r.gpr[13]; + case 0x6e: case 0x7e: return r.gpr[14]; + case 0x6f: case 0x7f: return r.gpr[15]; + } + + return 0x000000; //verified +} + +auto HG51B::writeRegister(uint7 address, uint24 data) -> void { + switch(address) { + case 0x01: r.mul = r.mul & 0xffffffull | data << 24; return; + case 0x02: r.mul = r.mul & ~0xffffffull | data << 0; return; + case 0x03: r.mdr = data; return; + case 0x08: r.rom = data; return; + case 0x0c: r.ram = data; return; + case 0x13: r.mar = data; return; + case 0x1c: r.dpr = data; return; + case 0x20: r.pc = data; return; + case 0x28: r.p = data; return; + case 0x2e: + io.bus.enable = 1; + io.bus.writing = 1; + io.bus.pending = 1 + io.wait.rom; + io.bus.address = r.mar; + return; + case 0x2f: + io.bus.enable = 1; + io.bus.writing = 1; + io.bus.pending = 1 + io.wait.ram; + io.bus.address = r.mar; + return; + + case 0x60: case 0x70: r.gpr[ 0] = data; return; + case 0x61: case 0x71: r.gpr[ 1] = data; return; + case 0x62: case 0x72: r.gpr[ 2] = data; return; + case 0x63: case 0x73: r.gpr[ 3] = data; return; + case 0x64: case 0x74: r.gpr[ 4] = data; return; + case 0x65: case 0x75: r.gpr[ 5] = data; return; + case 0x66: case 0x76: r.gpr[ 6] = data; return; + case 0x67: case 0x77: r.gpr[ 7] = data; return; + case 0x68: case 0x78: r.gpr[ 8] = data; return; + case 0x69: case 0x79: r.gpr[ 9] = data; return; + case 0x6a: case 0x7a: r.gpr[10] = data; return; + case 0x6b: case 0x7b: r.gpr[11] = data; return; + case 0x6c: case 0x7c: r.gpr[12] = data; return; + case 0x6d: case 0x7d: r.gpr[13] = data; return; + case 0x6e: case 0x7e: r.gpr[14] = data; return; + case 0x6f: case 0x7f: r.gpr[15] = data; return; + } +} diff --git a/waterbox/bsnescore/bsnes/processor/hg51b/serialization.cpp b/waterbox/bsnescore/bsnes/processor/hg51b/serialization.cpp new file mode 100644 index 0000000000..d8fd4eeb6e --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/hg51b/serialization.cpp @@ -0,0 +1,57 @@ +auto HG51B::serialize(serializer& s) -> void { + s.array(programRAM[0]); + s.array(programRAM[1]); + s.array(dataRAM); + + s.integer(r.pb); + s.integer(r.pc); + + s.boolean(r.n); + s.boolean(r.z); + s.boolean(r.c); + s.boolean(r.v); + s.boolean(r.i); + + s.integer(r.a); + s.integer(r.p); + s.integer(r.mul); + s.integer(r.mdr); + s.integer(r.rom); + s.integer(r.ram); + s.integer(r.mar); + s.integer(r.dpr); + s.array(r.gpr); + + s.integer(io.lock); + s.integer(io.halt); + s.integer(io.irq); + s.integer(io.rom); + s.array(io.vector); + + s.integer(io.wait.rom); + s.integer(io.wait.ram); + + s.integer(io.suspend.enable); + s.integer(io.suspend.duration); + + s.integer(io.cache.enable); + s.integer(io.cache.page); + s.array(io.cache.lock); + s.array(io.cache.address); + s.integer(io.cache.base); + s.integer(io.cache.pb); + s.integer(io.cache.pc); + + s.integer(io.dma.enable); + s.integer(io.dma.source); + s.integer(io.dma.target); + s.integer(io.dma.length); + + s.integer(io.bus.enable); + s.integer(io.bus.reading); + s.integer(io.bus.writing); + s.integer(io.bus.pending); + s.integer(io.bus.address); + + s.array(stack); +} diff --git a/waterbox/bsnescore/bsnes/processor/processor.hpp b/waterbox/bsnescore/bsnes/processor/processor.hpp new file mode 100644 index 0000000000..b549982c63 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/processor.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/waterbox/bsnescore/bsnes/processor/sm83/algorithms.cpp b/waterbox/bsnescore/bsnes/processor/sm83/algorithms.cpp new file mode 100644 index 0000000000..1b2a5df247 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/sm83/algorithms.cpp @@ -0,0 +1,154 @@ +auto SM83::ADD(uint8 target, uint8 source, bool carry) -> uint8 { + uint16 x = target + source + carry; + uint16 y = (uint4)target + (uint4)source + carry; + CF = x > 0xff; + HF = y > 0x0f; + NF = 0; + ZF = (uint8)x == 0; + return x; +} + +auto SM83::AND(uint8 target, uint8 source) -> uint8 { + target &= source; + CF = 0; + HF = 1; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::BIT(uint3 index, uint8 target) -> void { + HF = 1; + NF = 0; + ZF = bit1(target,index) == 0; +} + +auto SM83::CP(uint8 target, uint8 source) -> void { + uint16 x = target - source; + uint16 y = (uint4)target - (uint4)source; + CF = x > 0xff; + HF = y > 0x0f; + NF = 1; + ZF = (uint8)x == 0; +} + +auto SM83::DEC(uint8 target) -> uint8 { + target--; + HF = (uint4)target == 0x0f; + NF = 1; + ZF = target == 0; + return target; +} + +auto SM83::INC(uint8 target) -> uint8 { + target++; + HF = (uint4)target == 0x00; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::OR(uint8 target, uint8 source) -> uint8 { + target |= source; + CF = 0; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::RL(uint8 target) -> uint8 { + bool carry = target >> 7; + target = target << 1 | CF; + CF = carry; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::RLC(uint8 target) -> uint8 { + target = target << 1 | target >> 7; + CF = target & 1; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::RR(uint8 target) -> uint8 { + bool carry = target & 1; + target = CF << 7 | target >> 1; + CF = carry; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::RRC(uint8 target) -> uint8 { + target = target << 7 | target >> 1; + CF = target >> 7; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::SLA(uint8 target) -> uint8 { + bool carry = target >> 7; + target <<= 1; + CF = carry; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::SRA(uint8 target) -> uint8 { + bool carry = target & 1; + target = (int8)target >> 1; + CF = carry; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::SRL(uint8 target) -> uint8 { + bool carry = target & 1; + target >>= 1; + CF = carry; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::SUB(uint8 target, uint8 source, bool carry) -> uint8 { + uint16 x = target - source - carry; + uint16 y = (uint4)target - (uint4)source - carry; + CF = x > 0xff; + HF = y > 0x0f; + NF = 1; + ZF = (uint8)x == 0; + return x; +} + +auto SM83::SWAP(uint8 target) -> uint8 { + target = target << 4 | target >> 4; + CF = 0; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} + +auto SM83::XOR(uint8 target, uint8 source) -> uint8 { + target ^= source; + CF = 0; + HF = 0; + NF = 0; + ZF = target == 0; + return target; +} diff --git a/waterbox/bsnescore/bsnes/processor/sm83/disassembler.cpp b/waterbox/bsnescore/bsnes/processor/sm83/disassembler.cpp new file mode 100644 index 0000000000..ed081644b5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/sm83/disassembler.cpp @@ -0,0 +1,533 @@ +auto SM83::disassemble(uint16 pc) -> string { + return { + hex(pc, 4L), " ", + pad(disassembleOpcode(pc), -16, ' '), " ", + " AF:", hex(AF, 4L), + " BC:", hex(BC, 4L), + " DE:", hex(DE, 4L), + " HL:", hex(HL, 4L), + " SP:", hex(SP, 4L) + }; +} + +auto SM83::disassembleOpcode(uint16 pc) -> string { + auto opcode = readDebugger(pc); + auto lo = readDebugger(pc + 1); + auto hi = readDebugger(pc + 2); + auto word = hi << 8 | lo << 0; + + switch(opcode) { + case 0x00: return {"nop"}; + case 0x01: return {"ld bc,$", hex(word, 4L)}; + case 0x02: return {"ld (bc),a"}; + case 0x03: return {"inc bc"}; + case 0x04: return {"inc b"}; + case 0x05: return {"dec b"}; + case 0x06: return {"ld b,$", hex(lo, 2L)}; + case 0x07: return {"rlca"}; + case 0x08: return {"ld ($", hex(word, 4L), "),sp"}; + case 0x09: return {"add hl,bc"}; + case 0x0a: return {"ld a,(bc)"}; + case 0x0b: return {"dec bc"}; + case 0x0c: return {"inc c"}; + case 0x0d: return {"dec c"}; + case 0x0e: return {"ld c,$", hex(lo, 2L)}; + case 0x0f: return {"rrca"}; + case 0x10: return {"stop"}; + case 0x11: return {"ld de,$", hex(word, 4L)}; + case 0x12: return {"ld (de),a"}; + case 0x13: return {"inc de"}; + case 0x14: return {"inc d"}; + case 0x15: return {"dec d"}; + case 0x16: return {"ld d,$", hex(lo, 2L)}; + case 0x17: return {"rla"}; + case 0x18: return {"jr $", hex(pc + 2 + (int8)lo, 4L)}; + case 0x19: return {"add hl,de"}; + case 0x1a: return {"ld a,(de)"}; + case 0x1b: return {"dec de"}; + case 0x1c: return {"inc e"}; + case 0x1d: return {"dec e"}; + case 0x1e: return {"ld e,$", hex(lo, 2L)}; + case 0x1f: return {"rra"}; + case 0x20: return {"jr nz,$", hex(pc + 2 + (int8)lo, 4L)}; + case 0x21: return {"ld hl,$", hex(word, 4L)}; + case 0x22: return {"ldi (hl),a"}; + case 0x23: return {"inc hl"}; + case 0x24: return {"inc h"}; + case 0x25: return {"dec h"}; + case 0x26: return {"ld h,$", hex(lo, 2L)}; + case 0x27: return {"daa"}; + case 0x28: return {"jr z,$", hex(pc + 2 + (int8)lo, 4L)}; + case 0x29: return {"add hl,hl"}; + case 0x2a: return {"ldi a,(hl)"}; + case 0x2b: return {"dec hl"}; + case 0x2c: return {"inc l"}; + case 0x2d: return {"dec l"}; + case 0x2e: return {"ld l,$", hex(lo, 2L)}; + case 0x2f: return {"cpl"}; + case 0x30: return {"jr nc,$", hex(pc + 2 + (int8)lo, 4L)}; + case 0x31: return {"ld sp,$", hex(word, 4L)}; + case 0x32: return {"ldd (hl),a"}; + case 0x33: return {"inc sp"}; + case 0x34: return {"inc (hl)"}; + case 0x35: return {"dec (hl)"}; + case 0x36: return {"ld (hl),$", hex(lo, 2L)}; + case 0x37: return {"scf"}; + case 0x38: return {"jr c,$", hex(pc + 2 + (int8)lo, 4L)}; + case 0x39: return {"add hl,sp"}; + case 0x3a: return {"ldd a,(hl)"}; + case 0x3b: return {"dec sp"}; + case 0x3c: return {"inc a"}; + case 0x3d: return {"dec a"}; + case 0x3e: return {"ld a,$", hex(lo, 2L)}; + case 0x3f: return {"ccf"}; + case 0x40: return {"ld b,b"}; + case 0x41: return {"ld b,c"}; + case 0x42: return {"ld b,d"}; + case 0x43: return {"ld b,e"}; + case 0x44: return {"ld b,h"}; + case 0x45: return {"ld b,l"}; + case 0x46: return {"ld b,(hl)"}; + case 0x47: return {"ld b,a"}; + case 0x48: return {"ld c,b"}; + case 0x49: return {"ld c,c"}; + case 0x4a: return {"ld c,d"}; + case 0x4b: return {"ld c,e"}; + case 0x4c: return {"ld c,h"}; + case 0x4d: return {"ld c,l"}; + case 0x4e: return {"ld c,(hl)"}; + case 0x4f: return {"ld c,a"}; + case 0x50: return {"ld d,b"}; + case 0x51: return {"ld d,c"}; + case 0x52: return {"ld d,d"}; + case 0x53: return {"ld d,e"}; + case 0x54: return {"ld d,h"}; + case 0x55: return {"ld d,l"}; + case 0x56: return {"ld d,(hl)"}; + case 0x57: return {"ld d,a"}; + case 0x58: return {"ld e,b"}; + case 0x59: return {"ld e,c"}; + case 0x5a: return {"ld e,d"}; + case 0x5b: return {"ld e,e"}; + case 0x5c: return {"ld e,h"}; + case 0x5d: return {"ld e,l"}; + case 0x5e: return {"ld e,(hl)"}; + case 0x5f: return {"ld e,a"}; + case 0x60: return {"ld h,b"}; + case 0x61: return {"ld h,c"}; + case 0x62: return {"ld h,d"}; + case 0x63: return {"ld h,e"}; + case 0x64: return {"ld h,h"}; + case 0x65: return {"ld h,l"}; + case 0x66: return {"ld h,(hl)"}; + case 0x67: return {"ld h,a"}; + case 0x68: return {"ld l,b"}; + case 0x69: return {"ld l,c"}; + case 0x6a: return {"ld l,d"}; + case 0x6b: return {"ld l,e"}; + case 0x6c: return {"ld l,h"}; + case 0x6d: return {"ld l,l"}; + case 0x6e: return {"ld l,(hl)"}; + case 0x6f: return {"ld l,a"}; + case 0x70: return {"ld (hl),b"}; + case 0x71: return {"ld (hl),c"}; + case 0x72: return {"ld (hl),d"}; + case 0x73: return {"ld (hl),e"}; + case 0x74: return {"ld (hl),h"}; + case 0x75: return {"ld (hl),l"}; + case 0x76: return {"halt"}; + case 0x77: return {"ld (hl),a"}; + case 0x78: return {"ld a,b"}; + case 0x79: return {"ld a,c"}; + case 0x7a: return {"ld a,d"}; + case 0x7b: return {"ld a,e"}; + case 0x7c: return {"ld a,h"}; + case 0x7d: return {"ld a,l"}; + case 0x7e: return {"ld a,(hl)"}; + case 0x7f: return {"ld a,a"}; + case 0x80: return {"add a,b"}; + case 0x81: return {"add a,c"}; + case 0x82: return {"add a,d"}; + case 0x83: return {"add a,e"}; + case 0x84: return {"add a,h"}; + case 0x85: return {"add a,l"}; + case 0x86: return {"add a,(hl)"}; + case 0x87: return {"add a,a"}; + case 0x88: return {"adc a,b"}; + case 0x89: return {"adc a,c"}; + case 0x8a: return {"adc a,d"}; + case 0x8b: return {"adc a,e"}; + case 0x8c: return {"adc a,h"}; + case 0x8d: return {"adc a,l"}; + case 0x8e: return {"adc a,(hl)"}; + case 0x8f: return {"adc a,a"}; + case 0x90: return {"sub a,b"}; + case 0x91: return {"sub a,c"}; + case 0x92: return {"sub a,d"}; + case 0x93: return {"sub a,e"}; + case 0x94: return {"sub a,h"}; + case 0x95: return {"sub a,l"}; + case 0x96: return {"sub a,(hl)"}; + case 0x97: return {"sub a,a"}; + case 0x98: return {"sbc a,b"}; + case 0x99: return {"sbc a,c"}; + case 0x9a: return {"sbc a,d"}; + case 0x9b: return {"sbc a,e"}; + case 0x9c: return {"sbc a,h"}; + case 0x9d: return {"sbc a,l"}; + case 0x9e: return {"sbc a,(hl)"}; + case 0x9f: return {"sbc a,a"}; + case 0xa0: return {"and a,b"}; + case 0xa1: return {"and a,c"}; + case 0xa2: return {"and a,d"}; + case 0xa3: return {"and a,e"}; + case 0xa4: return {"and a,h"}; + case 0xa5: return {"and a,l"}; + case 0xa6: return {"and a,(hl)"}; + case 0xa7: return {"and a,a"}; + case 0xa8: return {"xor a,b"}; + case 0xa9: return {"xor a,c"}; + case 0xaa: return {"xor a,d"}; + case 0xab: return {"xor a,e"}; + case 0xac: return {"xor a,h"}; + case 0xad: return {"xor a,l"}; + case 0xae: return {"xor a,(hl)"}; + case 0xaf: return {"xor a,a"}; + case 0xb0: return {"or a,b"}; + case 0xb1: return {"or a,c"}; + case 0xb2: return {"or a,d"}; + case 0xb3: return {"or a,e"}; + case 0xb4: return {"or a,h"}; + case 0xb5: return {"or a,l"}; + case 0xb6: return {"or a,(hl)"}; + case 0xb7: return {"or a,a"}; + case 0xb8: return {"cp a,b"}; + case 0xb9: return {"cp a,c"}; + case 0xba: return {"cp a,d"}; + case 0xbb: return {"cp a,e"}; + case 0xbc: return {"cp a,h"}; + case 0xbd: return {"cp a,l"}; + case 0xbe: return {"cp a,(hl)"}; + case 0xbf: return {"cp a,a"}; + case 0xc0: return {"ret nz"}; + case 0xc1: return {"pop bc"}; + case 0xc2: return {"jp nz,$", hex(word, 4L)}; + case 0xc3: return {"jp $", hex(word, 4L)}; + case 0xc4: return {"call nz,$", hex(word, 4L)}; + case 0xc5: return {"push bc"}; + case 0xc6: return {"add a,$", hex(lo, 2L)}; + case 0xc7: return {"rst $0000"}; + case 0xc8: return {"ret z"}; + case 0xc9: return {"ret"}; + case 0xca: return {"jp z,$", hex(word, 4L)}; + case 0xcb: return disassembleOpcodeCB(pc + 1); + case 0xcc: return {"call z,$", hex(word, 4L)}; + case 0xcd: return {"call $", hex(word, 4L)}; + case 0xce: return {"adc a,$", hex(lo, 2L)}; + case 0xcf: return {"rst $0008"}; + case 0xd0: return {"ret nc"}; + case 0xd1: return {"pop de"}; + case 0xd2: return {"jp nc,$", hex(word, 4L)}; + case 0xd4: return {"call nc,$", hex(word, 4L)}; + case 0xd5: return {"push de"}; + case 0xd6: return {"sub a,$", hex(lo, 2L)}; + case 0xd7: return {"rst $0010"}; + case 0xd8: return {"ret c"}; + case 0xd9: return {"reti"}; + case 0xda: return {"jp c,$", hex(word, 4L)}; + case 0xdc: return {"call c,$", hex(word, 4L)}; + case 0xde: return {"sbc a,$", hex(lo, 2L)}; + case 0xdf: return {"rst $0018"}; + case 0xe0: return {"ldh ($ff", hex(lo, 2L), "),a"}; + case 0xe1: return {"pop hl"}; + case 0xe2: return {"ldh ($ff00+c),a"}; + case 0xe5: return {"push hl"}; + case 0xe6: return {"and a,$", hex(lo, 2L)}; + case 0xe7: return {"rst $0020"}; + case 0xe8: return {"add sp,$", hex((int8)lo, 4L)}; + case 0xe9: return {"jp hl"}; + case 0xea: return {"ld ($", hex(word, 4L), "),a"}; + case 0xee: return {"xor a,$", hex(lo, 2L)}; + case 0xef: return {"rst $0028"}; + case 0xf0: return {"ldh a,($ff", hex(lo, 2L), ")"}; + case 0xf1: return {"pop af"}; + case 0xf2: return {"ldh a,($ff00+c)"}; + case 0xf3: return {"di"}; + case 0xf5: return {"push af"}; + case 0xf6: return {"or a,$", hex(lo, 2L)}; + case 0xf7: return {"rst $0030"}; + case 0xf8: return {"ld hl,sp+$", hex((int8)lo, 4L)}; + case 0xf9: return {"ld sp,hl"}; + case 0xfa: return {"ld a,($", hex(word, 4L), ")"}; + case 0xfb: return {"ei"}; + case 0xfe: return {"cp a,$", hex(lo, 2L)}; + case 0xff: return {"rst $0038"}; + } + + return {"xx"}; +} + +auto SM83::disassembleOpcodeCB(uint16 pc) -> string { + auto opcode = readDebugger(pc); + + switch(opcode) { + case 0x00: return {"rlc b"}; + case 0x01: return {"rlc c"}; + case 0x02: return {"rlc d"}; + case 0x03: return {"rlc e"}; + case 0x04: return {"rlc h"}; + case 0x05: return {"rlc l"}; + case 0x06: return {"rlc (hl)"}; + case 0x07: return {"rlc a"}; + case 0x08: return {"rrc b"}; + case 0x09: return {"rrc c"}; + case 0x0a: return {"rrc d"}; + case 0x0b: return {"rrc e"}; + case 0x0c: return {"rrc h"}; + case 0x0d: return {"rrc l"}; + case 0x0e: return {"rrc (hl)"}; + case 0x0f: return {"rrc a"}; + case 0x10: return {"rl b"}; + case 0x11: return {"rl c"}; + case 0x12: return {"rl d"}; + case 0x13: return {"rl e"}; + case 0x14: return {"rl h"}; + case 0x15: return {"rl l"}; + case 0x16: return {"rl (hl)"}; + case 0x17: return {"rl a"}; + case 0x18: return {"rr b"}; + case 0x19: return {"rr c"}; + case 0x1a: return {"rr d"}; + case 0x1b: return {"rr e"}; + case 0x1c: return {"rr h"}; + case 0x1d: return {"rr l"}; + case 0x1e: return {"rr (hl)"}; + case 0x1f: return {"rr a"}; + case 0x20: return {"sla b"}; + case 0x21: return {"sla c"}; + case 0x22: return {"sla d"}; + case 0x23: return {"sla e"}; + case 0x24: return {"sla h"}; + case 0x25: return {"sla l"}; + case 0x26: return {"sla (hl)"}; + case 0x27: return {"sla a"}; + case 0x28: return {"sra b"}; + case 0x29: return {"sra c"}; + case 0x2a: return {"sra d"}; + case 0x2b: return {"sra e"}; + case 0x2c: return {"sra h"}; + case 0x2d: return {"sra l"}; + case 0x2e: return {"sra (hl)"}; + case 0x2f: return {"sra a"}; + case 0x30: return {"swap b"}; + case 0x31: return {"swap c"}; + case 0x32: return {"swap d"}; + case 0x33: return {"swap e"}; + case 0x34: return {"swap h"}; + case 0x35: return {"swap l"}; + case 0x36: return {"swap (hl)"}; + case 0x37: return {"swap a"}; + case 0x38: return {"srl b"}; + case 0x39: return {"srl c"}; + case 0x3a: return {"srl d"}; + case 0x3b: return {"srl e"}; + case 0x3c: return {"srl h"}; + case 0x3d: return {"srl l"}; + case 0x3e: return {"srl (hl)"}; + case 0x3f: return {"srl a"}; + case 0x40: return {"bit 0,b"}; + case 0x41: return {"bit 0,c"}; + case 0x42: return {"bit 0,d"}; + case 0x43: return {"bit 0,e"}; + case 0x44: return {"bit 0,h"}; + case 0x45: return {"bit 0,l"}; + case 0x46: return {"bit 0,(hl)"}; + case 0x47: return {"bit 0,a"}; + case 0x48: return {"bit 1,b"}; + case 0x49: return {"bit 1,c"}; + case 0x4a: return {"bit 1,d"}; + case 0x4b: return {"bit 1,e"}; + case 0x4c: return {"bit 1,h"}; + case 0x4d: return {"bit 1,l"}; + case 0x4e: return {"bit 1,(hl)"}; + case 0x4f: return {"bit 1,a"}; + case 0x50: return {"bit 2,b"}; + case 0x51: return {"bit 2,c"}; + case 0x52: return {"bit 2,d"}; + case 0x53: return {"bit 2,e"}; + case 0x54: return {"bit 2,h"}; + case 0x55: return {"bit 2,l"}; + case 0x56: return {"bit 2,(hl)"}; + case 0x57: return {"bit 2,a"}; + case 0x58: return {"bit 3,b"}; + case 0x59: return {"bit 3,c"}; + case 0x5a: return {"bit 3,d"}; + case 0x5b: return {"bit 3,e"}; + case 0x5c: return {"bit 3,h"}; + case 0x5d: return {"bit 3,l"}; + case 0x5e: return {"bit 3,(hl)"}; + case 0x5f: return {"bit 3,a"}; + case 0x60: return {"bit 4,b"}; + case 0x61: return {"bit 4,c"}; + case 0x62: return {"bit 4,d"}; + case 0x63: return {"bit 4,e"}; + case 0x64: return {"bit 4,h"}; + case 0x65: return {"bit 4,l"}; + case 0x66: return {"bit 4,(hl)"}; + case 0x67: return {"bit 4,a"}; + case 0x68: return {"bit 5,b"}; + case 0x69: return {"bit 5,c"}; + case 0x6a: return {"bit 5,d"}; + case 0x6b: return {"bit 5,e"}; + case 0x6c: return {"bit 5,h"}; + case 0x6d: return {"bit 5,l"}; + case 0x6e: return {"bit 5,(hl)"}; + case 0x6f: return {"bit 5,a"}; + case 0x70: return {"bit 6,b"}; + case 0x71: return {"bit 6,c"}; + case 0x72: return {"bit 6,d"}; + case 0x73: return {"bit 6,e"}; + case 0x74: return {"bit 6,h"}; + case 0x75: return {"bit 6,l"}; + case 0x76: return {"bit 6,(hl)"}; + case 0x77: return {"bit 6,a"}; + case 0x78: return {"bit 7,b"}; + case 0x79: return {"bit 7,c"}; + case 0x7a: return {"bit 7,d"}; + case 0x7b: return {"bit 7,e"}; + case 0x7c: return {"bit 7,h"}; + case 0x7d: return {"bit 7,l"}; + case 0x7e: return {"bit 7,(hl)"}; + case 0x7f: return {"bit 7,a"}; + case 0x80: return {"res 0,b"}; + case 0x81: return {"res 0,c"}; + case 0x82: return {"res 0,d"}; + case 0x83: return {"res 0,e"}; + case 0x84: return {"res 0,h"}; + case 0x85: return {"res 0,l"}; + case 0x86: return {"res 0,(hl)"}; + case 0x87: return {"res 0,a"}; + case 0x88: return {"res 1,b"}; + case 0x89: return {"res 1,c"}; + case 0x8a: return {"res 1,d"}; + case 0x8b: return {"res 1,e"}; + case 0x8c: return {"res 1,h"}; + case 0x8d: return {"res 1,l"}; + case 0x8e: return {"res 1,(hl)"}; + case 0x8f: return {"res 1,a"}; + case 0x90: return {"res 2,b"}; + case 0x91: return {"res 2,c"}; + case 0x92: return {"res 2,d"}; + case 0x93: return {"res 2,e"}; + case 0x94: return {"res 2,h"}; + case 0x95: return {"res 2,l"}; + case 0x96: return {"res 2,(hl)"}; + case 0x97: return {"res 2,a"}; + case 0x98: return {"res 3,b"}; + case 0x99: return {"res 3,c"}; + case 0x9a: return {"res 3,d"}; + case 0x9b: return {"res 3,e"}; + case 0x9c: return {"res 3,h"}; + case 0x9d: return {"res 3,l"}; + case 0x9e: return {"res 3,(hl)"}; + case 0x9f: return {"res 3,a"}; + case 0xa0: return {"res 4,b"}; + case 0xa1: return {"res 4,c"}; + case 0xa2: return {"res 4,d"}; + case 0xa3: return {"res 4,e"}; + case 0xa4: return {"res 4,h"}; + case 0xa5: return {"res 4,l"}; + case 0xa6: return {"res 4,(hl)"}; + case 0xa7: return {"res 4,a"}; + case 0xa8: return {"res 5,b"}; + case 0xa9: return {"res 5,c"}; + case 0xaa: return {"res 5,d"}; + case 0xab: return {"res 5,e"}; + case 0xac: return {"res 5,h"}; + case 0xad: return {"res 5,l"}; + case 0xae: return {"res 5,(hl)"}; + case 0xaf: return {"res 5,a"}; + case 0xb0: return {"res 6,b"}; + case 0xb1: return {"res 6,c"}; + case 0xb2: return {"res 6,d"}; + case 0xb3: return {"res 6,e"}; + case 0xb4: return {"res 6,h"}; + case 0xb5: return {"res 6,l"}; + case 0xb6: return {"res 6,(hl)"}; + case 0xb7: return {"res 6,a"}; + case 0xb8: return {"res 7,b"}; + case 0xb9: return {"res 7,c"}; + case 0xba: return {"res 7,d"}; + case 0xbb: return {"res 7,e"}; + case 0xbc: return {"res 7,h"}; + case 0xbd: return {"res 7,l"}; + case 0xbe: return {"res 7,(hl)"}; + case 0xbf: return {"res 7,a"}; + case 0xc0: return {"set 0,b"}; + case 0xc1: return {"set 0,c"}; + case 0xc2: return {"set 0,d"}; + case 0xc3: return {"set 0,e"}; + case 0xc4: return {"set 0,h"}; + case 0xc5: return {"set 0,l"}; + case 0xc6: return {"set 0,(hl)"}; + case 0xc7: return {"set 0,a"}; + case 0xc8: return {"set 1,b"}; + case 0xc9: return {"set 1,c"}; + case 0xca: return {"set 1,d"}; + case 0xcb: return {"set 1,e"}; + case 0xcc: return {"set 1,h"}; + case 0xcd: return {"set 1,l"}; + case 0xce: return {"set 1,(hl)"}; + case 0xcf: return {"set 1,a"}; + case 0xd0: return {"set 2,b"}; + case 0xd1: return {"set 2,c"}; + case 0xd2: return {"set 2,d"}; + case 0xd3: return {"set 2,e"}; + case 0xd4: return {"set 2,h"}; + case 0xd5: return {"set 2,l"}; + case 0xd6: return {"set 2,(hl)"}; + case 0xd7: return {"set 2,a"}; + case 0xd8: return {"set 3,b"}; + case 0xd9: return {"set 3,c"}; + case 0xda: return {"set 3,d"}; + case 0xdb: return {"set 3,e"}; + case 0xdc: return {"set 3,h"}; + case 0xdd: return {"set 3,l"}; + case 0xde: return {"set 3,(hl)"}; + case 0xdf: return {"set 3,a"}; + case 0xe0: return {"set 4,b"}; + case 0xe1: return {"set 4,c"}; + case 0xe2: return {"set 4,d"}; + case 0xe3: return {"set 4,e"}; + case 0xe4: return {"set 4,h"}; + case 0xe5: return {"set 4,l"}; + case 0xe6: return {"set 4,(hl)"}; + case 0xe7: return {"set 4,a"}; + case 0xe8: return {"set 5,b"}; + case 0xe9: return {"set 5,c"}; + case 0xea: return {"set 5,d"}; + case 0xeb: return {"set 5,e"}; + case 0xec: return {"set 5,h"}; + case 0xed: return {"set 5,l"}; + case 0xee: return {"set 5,(hl)"}; + case 0xef: return {"set 5,a"}; + case 0xf0: return {"set 6,b"}; + case 0xf1: return {"set 6,c"}; + case 0xf2: return {"set 6,d"}; + case 0xf3: return {"set 6,e"}; + case 0xf4: return {"set 6,h"}; + case 0xf5: return {"set 6,l"}; + case 0xf6: return {"set 6,(hl)"}; + case 0xf7: return {"set 6,a"}; + case 0xf8: return {"set 7,b"}; + case 0xf9: return {"set 7,c"}; + case 0xfa: return {"set 7,d"}; + case 0xfb: return {"set 7,e"}; + case 0xfc: return {"set 7,h"}; + case 0xfd: return {"set 7,l"}; + case 0xfe: return {"set 7,(hl)"}; + case 0xff: return {"set 7,a"}; + } + + unreachable; +} diff --git a/waterbox/bsnescore/bsnes/processor/sm83/instruction.cpp b/waterbox/bsnescore/bsnes/processor/sm83/instruction.cpp new file mode 100644 index 0000000000..0a424be34d --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/sm83/instruction.cpp @@ -0,0 +1,364 @@ +auto SM83::interrupt(uint16 vector) -> void { + idle(); + idle(); + idle(); + r.ime = 0; + push(PC); + PC = vector; +} + +#define op(id, name, ...) case id: return instruction##name(__VA_ARGS__); + +auto SM83::instruction() -> void { + auto opcode = operand(); + + switch(opcode) { + op(0x00, NOP) + op(0x01, LD_Direct_Data, BC) + op(0x02, LD_Indirect_Direct, BC, A) + op(0x03, INC_Direct, BC) + op(0x04, INC_Direct, B) + op(0x05, DEC_Direct, B) + op(0x06, LD_Direct_Data, B) + op(0x07, RLCA) + op(0x08, LD_Address_Direct, SP) + op(0x09, ADD_Direct_Direct, HL, BC) + op(0x0a, LD_Direct_Indirect, A, BC) + op(0x0b, DEC_Direct, BC) + op(0x0c, INC_Direct, C) + op(0x0d, DEC_Direct, C) + op(0x0e, LD_Direct_Data, C) + op(0x0f, RRCA) + op(0x10, STOP) + op(0x11, LD_Direct_Data, DE) + op(0x12, LD_Indirect_Direct, DE, A) + op(0x13, INC_Direct, DE) + op(0x14, INC_Direct, D) + op(0x15, DEC_Direct, D) + op(0x16, LD_Direct_Data, D) + op(0x17, RLA) + op(0x18, JR_Condition_Relative, 1) + op(0x19, ADD_Direct_Direct, HL, DE) + op(0x1a, LD_Direct_Indirect, A, DE) + op(0x1b, DEC_Direct, DE) + op(0x1c, INC_Direct, E) + op(0x1d, DEC_Direct, E) + op(0x1e, LD_Direct_Data, E) + op(0x1f, RRA) + op(0x20, JR_Condition_Relative, ZF == 0) + op(0x21, LD_Direct_Data, HL) + op(0x22, LD_IndirectIncrement_Direct, HL, A) + op(0x23, INC_Direct, HL) + op(0x24, INC_Direct, H) + op(0x25, DEC_Direct, H) + op(0x26, LD_Direct_Data, H) + op(0x27, DAA) + op(0x28, JR_Condition_Relative, ZF == 1) + op(0x29, ADD_Direct_Direct, HL, HL) + op(0x2a, LD_Direct_IndirectIncrement, A, HL) + op(0x2b, DEC_Direct, HL) + op(0x2c, INC_Direct, L) + op(0x2d, DEC_Direct, L) + op(0x2e, LD_Direct_Data, L) + op(0x2f, CPL) + op(0x30, JR_Condition_Relative, CF == 0) + op(0x31, LD_Direct_Data, SP) + op(0x32, LD_IndirectDecrement_Direct, HL, A) + op(0x33, INC_Direct, SP) + op(0x34, INC_Indirect, HL) + op(0x35, DEC_Indirect, HL) + op(0x36, LD_Indirect_Data, HL) + op(0x37, SCF) + op(0x38, JR_Condition_Relative, CF == 1) + op(0x39, ADD_Direct_Direct, HL, SP) + op(0x3a, LD_Direct_IndirectDecrement, A, HL) + op(0x3b, DEC_Direct, SP) + op(0x3c, INC_Direct, A) + op(0x3d, DEC_Direct, A) + op(0x3e, LD_Direct_Data, A) + op(0x3f, CCF) + op(0x40, LD_Direct_Direct, B, B) + op(0x41, LD_Direct_Direct, B, C) + op(0x42, LD_Direct_Direct, B, D) + op(0x43, LD_Direct_Direct, B, E) + op(0x44, LD_Direct_Direct, B, H) + op(0x45, LD_Direct_Direct, B, L) + op(0x46, LD_Direct_Indirect, B, HL) + op(0x47, LD_Direct_Direct, B, A) + op(0x48, LD_Direct_Direct, C, B) + op(0x49, LD_Direct_Direct, C, C) + op(0x4a, LD_Direct_Direct, C, D) + op(0x4b, LD_Direct_Direct, C, E) + op(0x4c, LD_Direct_Direct, C, H) + op(0x4d, LD_Direct_Direct, C, L) + op(0x4e, LD_Direct_Indirect, C, HL) + op(0x4f, LD_Direct_Direct, C, A) + op(0x50, LD_Direct_Direct, D, B) + op(0x51, LD_Direct_Direct, D, C) + op(0x52, LD_Direct_Direct, D, D) + op(0x53, LD_Direct_Direct, D, E) + op(0x54, LD_Direct_Direct, D, H) + op(0x55, LD_Direct_Direct, D, L) + op(0x56, LD_Direct_Indirect, D, HL) + op(0x57, LD_Direct_Direct, D, A) + op(0x58, LD_Direct_Direct, E, B) + op(0x59, LD_Direct_Direct, E, C) + op(0x5a, LD_Direct_Direct, E, D) + op(0x5b, LD_Direct_Direct, E, E) + op(0x5c, LD_Direct_Direct, E, H) + op(0x5d, LD_Direct_Direct, E, L) + op(0x5e, LD_Direct_Indirect, E, HL) + op(0x5f, LD_Direct_Direct, E, A) + op(0x60, LD_Direct_Direct, H, B) + op(0x61, LD_Direct_Direct, H, C) + op(0x62, LD_Direct_Direct, H, D) + op(0x63, LD_Direct_Direct, H, E) + op(0x64, LD_Direct_Direct, H, H) + op(0x65, LD_Direct_Direct, H, L) + op(0x66, LD_Direct_Indirect, H, HL) + op(0x67, LD_Direct_Direct, H, A) + op(0x68, LD_Direct_Direct, L, B) + op(0x69, LD_Direct_Direct, L, C) + op(0x6a, LD_Direct_Direct, L, D) + op(0x6b, LD_Direct_Direct, L, E) + op(0x6c, LD_Direct_Direct, L, H) + op(0x6d, LD_Direct_Direct, L, L) + op(0x6e, LD_Direct_Indirect, L, HL) + op(0x6f, LD_Direct_Direct, L, A) + op(0x70, LD_Indirect_Direct, HL, B) + op(0x71, LD_Indirect_Direct, HL, C) + op(0x72, LD_Indirect_Direct, HL, D) + op(0x73, LD_Indirect_Direct, HL, E) + op(0x74, LD_Indirect_Direct, HL, H) + op(0x75, LD_Indirect_Direct, HL, L) + op(0x76, HALT) + op(0x77, LD_Indirect_Direct, HL, A) + op(0x78, LD_Direct_Direct, A, B) + op(0x79, LD_Direct_Direct, A, C) + op(0x7a, LD_Direct_Direct, A, D) + op(0x7b, LD_Direct_Direct, A, E) + op(0x7c, LD_Direct_Direct, A, H) + op(0x7d, LD_Direct_Direct, A, L) + op(0x7e, LD_Direct_Indirect, A, HL) + op(0x7f, LD_Direct_Direct, A, A) + op(0x80, ADD_Direct_Direct, A, B) + op(0x81, ADD_Direct_Direct, A, C) + op(0x82, ADD_Direct_Direct, A, D) + op(0x83, ADD_Direct_Direct, A, E) + op(0x84, ADD_Direct_Direct, A, H) + op(0x85, ADD_Direct_Direct, A, L) + op(0x86, ADD_Direct_Indirect, A, HL) + op(0x87, ADD_Direct_Direct, A, A) + op(0x88, ADC_Direct_Direct, A, B) + op(0x89, ADC_Direct_Direct, A, C) + op(0x8a, ADC_Direct_Direct, A, D) + op(0x8b, ADC_Direct_Direct, A, E) + op(0x8c, ADC_Direct_Direct, A, H) + op(0x8d, ADC_Direct_Direct, A, L) + op(0x8e, ADC_Direct_Indirect, A, HL) + op(0x8f, ADC_Direct_Direct, A, A) + op(0x90, SUB_Direct_Direct, A, B) + op(0x91, SUB_Direct_Direct, A, C) + op(0x92, SUB_Direct_Direct, A, D) + op(0x93, SUB_Direct_Direct, A, E) + op(0x94, SUB_Direct_Direct, A, H) + op(0x95, SUB_Direct_Direct, A, L) + op(0x96, SUB_Direct_Indirect, A, HL) + op(0x97, SUB_Direct_Direct, A, A) + op(0x98, SBC_Direct_Direct, A, B) + op(0x99, SBC_Direct_Direct, A, C) + op(0x9a, SBC_Direct_Direct, A, D) + op(0x9b, SBC_Direct_Direct, A, E) + op(0x9c, SBC_Direct_Direct, A, H) + op(0x9d, SBC_Direct_Direct, A, L) + op(0x9e, SBC_Direct_Indirect, A, HL) + op(0x9f, SBC_Direct_Direct, A, A) + op(0xa0, AND_Direct_Direct, A, B) + op(0xa1, AND_Direct_Direct, A, C) + op(0xa2, AND_Direct_Direct, A, D) + op(0xa3, AND_Direct_Direct, A, E) + op(0xa4, AND_Direct_Direct, A, H) + op(0xa5, AND_Direct_Direct, A, L) + op(0xa6, AND_Direct_Indirect, A, HL) + op(0xa7, AND_Direct_Direct, A, A) + op(0xa8, XOR_Direct_Direct, A, B) + op(0xa9, XOR_Direct_Direct, A, C) + op(0xaa, XOR_Direct_Direct, A, D) + op(0xab, XOR_Direct_Direct, A, E) + op(0xac, XOR_Direct_Direct, A, H) + op(0xad, XOR_Direct_Direct, A, L) + op(0xae, XOR_Direct_Indirect, A, HL) + op(0xaf, XOR_Direct_Direct, A, A) + op(0xb0, OR_Direct_Direct, A, B) + op(0xb1, OR_Direct_Direct, A, C) + op(0xb2, OR_Direct_Direct, A, D) + op(0xb3, OR_Direct_Direct, A, E) + op(0xb4, OR_Direct_Direct, A, H) + op(0xb5, OR_Direct_Direct, A, L) + op(0xb6, OR_Direct_Indirect, A, HL) + op(0xb7, OR_Direct_Direct, A, A) + op(0xb8, CP_Direct_Direct, A, B) + op(0xb9, CP_Direct_Direct, A, C) + op(0xba, CP_Direct_Direct, A, D) + op(0xbb, CP_Direct_Direct, A, E) + op(0xbc, CP_Direct_Direct, A, H) + op(0xbd, CP_Direct_Direct, A, L) + op(0xbe, CP_Direct_Indirect, A, HL) + op(0xbf, CP_Direct_Direct, A, A) + op(0xc0, RET_Condition, ZF == 0) + op(0xc1, POP_Direct, BC) + op(0xc2, JP_Condition_Address, ZF == 0) + op(0xc3, JP_Condition_Address, 1) + op(0xc4, CALL_Condition_Address, ZF == 0) + op(0xc5, PUSH_Direct, BC) + op(0xc6, ADD_Direct_Data, A) + op(0xc7, RST_Implied, 0x00) + op(0xc8, RET_Condition, ZF == 1) + op(0xc9, RET) + op(0xca, JP_Condition_Address, ZF == 1) + op(0xcb, CB) + op(0xcc, CALL_Condition_Address, ZF == 1) + op(0xcd, CALL_Condition_Address, 1) + op(0xce, ADC_Direct_Data, A) + op(0xcf, RST_Implied, 0x08) + op(0xd0, RET_Condition, CF == 0) + op(0xd1, POP_Direct, DE) + op(0xd2, JP_Condition_Address, CF == 0) + op(0xd4, CALL_Condition_Address, CF == 0) + op(0xd5, PUSH_Direct, DE) + op(0xd6, SUB_Direct_Data, A) + op(0xd7, RST_Implied, 0x10) + op(0xd8, RET_Condition, CF == 1) + op(0xd9, RETI) + op(0xda, JP_Condition_Address, CF == 1) + op(0xdc, CALL_Condition_Address, CF == 1) + op(0xde, SBC_Direct_Data, A) + op(0xdf, RST_Implied, 0x18) + op(0xe0, LDH_Address_Direct, A) + op(0xe1, POP_Direct, HL) + op(0xe2, LDH_Indirect_Direct, C, A) + op(0xe5, PUSH_Direct, HL) + op(0xe6, AND_Direct_Data, A) + op(0xe7, RST_Implied, 0x20) + op(0xe8, ADD_Direct_Relative, SP) + op(0xe9, JP_Direct, HL) + op(0xea, LD_Address_Direct, A) + op(0xee, XOR_Direct_Data, A) + op(0xef, RST_Implied, 0x28) + op(0xf0, LDH_Direct_Address, A) + op(0xf1, POP_Direct, AF) + op(0xf2, LDH_Direct_Indirect, A, C) + op(0xf3, DI) + op(0xf5, PUSH_Direct, AF) + op(0xf6, OR_Direct_Data, A) + op(0xf7, RST_Implied, 0x30) + op(0xf8, LD_Direct_DirectRelative, HL, SP) + op(0xf9, LD_Direct_Direct, SP, HL) + op(0xfa, LD_Direct_Address, A) + op(0xfb, EI) + op(0xfe, CP_Direct_Data, A) + op(0xff, RST_Implied, 0x38) + } +} + +auto SM83::instructionCB() -> void { + auto opcode = operand(); + + switch(opcode) { + op(0x00, RLC_Direct, B) + op(0x01, RLC_Direct, C) + op(0x02, RLC_Direct, D) + op(0x03, RLC_Direct, E) + op(0x04, RLC_Direct, H) + op(0x05, RLC_Direct, L) + op(0x06, RLC_Indirect, HL) + op(0x07, RLC_Direct, A) + op(0x08, RRC_Direct, B) + op(0x09, RRC_Direct, C) + op(0x0a, RRC_Direct, D) + op(0x0b, RRC_Direct, E) + op(0x0c, RRC_Direct, H) + op(0x0d, RRC_Direct, L) + op(0x0e, RRC_Indirect, HL) + op(0x0f, RRC_Direct, A) + op(0x10, RL_Direct, B) + op(0x11, RL_Direct, C) + op(0x12, RL_Direct, D) + op(0x13, RL_Direct, E) + op(0x14, RL_Direct, H) + op(0x15, RL_Direct, L) + op(0x16, RL_Indirect, HL) + op(0x17, RL_Direct, A) + op(0x18, RR_Direct, B) + op(0x19, RR_Direct, C) + op(0x1a, RR_Direct, D) + op(0x1b, RR_Direct, E) + op(0x1c, RR_Direct, H) + op(0x1d, RR_Direct, L) + op(0x1e, RR_Indirect, HL) + op(0x1f, RR_Direct, A) + op(0x20, SLA_Direct, B) + op(0x21, SLA_Direct, C) + op(0x22, SLA_Direct, D) + op(0x23, SLA_Direct, E) + op(0x24, SLA_Direct, H) + op(0x25, SLA_Direct, L) + op(0x26, SLA_Indirect, HL) + op(0x27, SLA_Direct, A) + op(0x28, SRA_Direct, B) + op(0x29, SRA_Direct, C) + op(0x2a, SRA_Direct, D) + op(0x2b, SRA_Direct, E) + op(0x2c, SRA_Direct, H) + op(0x2d, SRA_Direct, L) + op(0x2e, SRA_Indirect, HL) + op(0x2f, SRA_Direct, A) + op(0x30, SWAP_Direct, B) + op(0x31, SWAP_Direct, C) + op(0x32, SWAP_Direct, D) + op(0x33, SWAP_Direct, E) + op(0x34, SWAP_Direct, H) + op(0x35, SWAP_Direct, L) + op(0x36, SWAP_Indirect, HL) + op(0x37, SWAP_Direct, A) + op(0x38, SRL_Direct, B) + op(0x39, SRL_Direct, C) + op(0x3a, SRL_Direct, D) + op(0x3b, SRL_Direct, E) + op(0x3c, SRL_Direct, H) + op(0x3d, SRL_Direct, L) + op(0x3e, SRL_Indirect, HL) + op(0x3f, SRL_Direct, A) + } + + //opcodes 0x40-0xff [op(0x00 - 0x07) declared above] + uint3 bit = bits(opcode,3-5); + switch(bits(opcode,6-7) << 3 | bits(opcode,0-2)) { + op(0x08, BIT_Index_Direct, bit, B) + op(0x09, BIT_Index_Direct, bit, C) + op(0x0a, BIT_Index_Direct, bit, D) + op(0x0b, BIT_Index_Direct, bit, E) + op(0x0c, BIT_Index_Direct, bit, H) + op(0x0d, BIT_Index_Direct, bit, L) + op(0x0e, BIT_Index_Indirect, bit, HL) + op(0x0f, BIT_Index_Direct, bit, A) + op(0x10, RES_Index_Direct, bit, B) + op(0x11, RES_Index_Direct, bit, C) + op(0x12, RES_Index_Direct, bit, D) + op(0x13, RES_Index_Direct, bit, E) + op(0x14, RES_Index_Direct, bit, H) + op(0x15, RES_Index_Direct, bit, L) + op(0x16, RES_Index_Indirect, bit, HL) + op(0x17, RES_Index_Direct, bit, A) + op(0x18, SET_Index_Direct, bit, B) + op(0x19, SET_Index_Direct, bit, C) + op(0x1a, SET_Index_Direct, bit, D) + op(0x1b, SET_Index_Direct, bit, E) + op(0x1c, SET_Index_Direct, bit, H) + op(0x1d, SET_Index_Direct, bit, L) + op(0x1e, SET_Index_Indirect, bit, HL) + op(0x1f, SET_Index_Direct, bit, A) + } +} + +#undef op diff --git a/waterbox/bsnescore/bsnes/processor/sm83/instructions.cpp b/waterbox/bsnescore/bsnes/processor/sm83/instructions.cpp new file mode 100644 index 0000000000..df96d94f68 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/sm83/instructions.cpp @@ -0,0 +1,467 @@ +auto SM83::instructionADC_Direct_Data(uint8& target) -> void { + target = ADD(target, operand(), CF); +} + +auto SM83::instructionADC_Direct_Direct(uint8& target, uint8& source) -> void { + target = ADD(target, source, CF); +} + +auto SM83::instructionADC_Direct_Indirect(uint8& target, uint16& source) -> void { + target = ADD(target, read(source), CF); +} + +auto SM83::instructionADD_Direct_Data(uint8& target) -> void { + target = ADD(target, operand()); +} + +auto SM83::instructionADD_Direct_Direct(uint8& target, uint8& source) -> void { + target = ADD(target, source); +} + +auto SM83::instructionADD_Direct_Direct(uint16& target, uint16& source) -> void { + idle(); + uint32 x = target + source; + uint32 y = (uint12)target + (uint12)source; + target = x; + CF = x > 0xffff; + HF = y > 0x0fff; + NF = 0; +} + +auto SM83::instructionADD_Direct_Indirect(uint8& target, uint16& source) -> void { + target = ADD(target, read(source)); +} + +auto SM83::instructionADD_Direct_Relative(uint16& target) -> void { + auto data = operand(); + idle(); + idle(); + CF = (uint8)target + (uint8)data > 0xff; + HF = (uint4)target + (uint4)data > 0x0f; + NF = 0; + ZF = 0; + target += (int8)data; +} + +auto SM83::instructionAND_Direct_Data(uint8& target) -> void { + target = AND(target, operand()); +} + +auto SM83::instructionAND_Direct_Direct(uint8& target, uint8& source) -> void { + target = AND(target, source); +} + +auto SM83::instructionAND_Direct_Indirect(uint8& target, uint16& source) -> void { + target = AND(target, read(source)); +} + +auto SM83::instructionBIT_Index_Direct(uint3 index, uint8& data) -> void { + BIT(index, data); +} + +auto SM83::instructionBIT_Index_Indirect(uint3 index, uint16& address) -> void { + auto data = read(address); + BIT(index, data); +} + +auto SM83::instructionCALL_Condition_Address(bool take) -> void { + auto address = operands(); + if(!take) return; + idle(); + push(PC); + PC = address; +} + +auto SM83::instructionCCF() -> void { + CF = !CF; + HF = 0; + NF = 0; +} + +auto SM83::instructionCP_Direct_Data(uint8& target) -> void { + CP(target, operand()); +} + +auto SM83::instructionCP_Direct_Direct(uint8& target, uint8& source) -> void { + CP(target, source); +} + +auto SM83::instructionCP_Direct_Indirect(uint8& target, uint16& source) -> void { + CP(target, read(source)); +} + +auto SM83::instructionCPL() -> void { + A = ~A; + HF = 1; + NF = 1; +} + +auto SM83::instructionDAA() -> void { + uint16 a = A; + if(!NF) { + if(HF || (uint4)a > 0x09) a += 0x06; + if(CF || (uint8)a > 0x9f) a += 0x60; + } else { + if(HF) { + a -= 0x06; + if(!CF) a &= 0xff; + } + if(CF) a -= 0x60; + } + A = a; + CF |= bit1(a,8); + HF = 0; + ZF = A == 0; +} + +auto SM83::instructionDEC_Direct(uint8& data) -> void { + data = DEC(data); +} + +auto SM83::instructionDEC_Direct(uint16& data) -> void { + idle(); + data--; +} + +auto SM83::instructionDEC_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, DEC(data)); +} + +auto SM83::instructionDI() -> void { + r.ime = 0; +} + +auto SM83::instructionEI() -> void { + r.ei = 1; +} + +auto SM83::instructionHALT() -> void { + r.halt = 1; + while(r.halt) halt(); +} + +auto SM83::instructionINC_Direct(uint8& data) -> void { + data = INC(data); +} + +auto SM83::instructionINC_Direct(uint16& data) -> void { + idle(); + data++; +} + +auto SM83::instructionINC_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, INC(data)); +} + +auto SM83::instructionJP_Condition_Address(bool take) -> void { + auto address = operands(); + if(!take) return; + idle(); + PC = address; +} + +auto SM83::instructionJP_Direct(uint16& data) -> void { + PC = data; +} + +auto SM83::instructionJR_Condition_Relative(bool take) -> void { + auto data = operand(); + if(!take) return; + idle(); + PC += (int8)data; +} + +auto SM83::instructionLD_Address_Direct(uint8& data) -> void { + write(operands(), data); +} + +auto SM83::instructionLD_Address_Direct(uint16& data) -> void { + store(operands(), data); +} + +auto SM83::instructionLD_Direct_Address(uint8& data) -> void { + data = read(operands()); +} + +auto SM83::instructionLD_Direct_Data(uint8& target) -> void { + target = operand(); +} + +auto SM83::instructionLD_Direct_Data(uint16& target) -> void { + target = operands(); +} + +auto SM83::instructionLD_Direct_Direct(uint8& target, uint8& source) -> void { + target = source; +} + +auto SM83::instructionLD_Direct_Direct(uint16& target, uint16& source) -> void { + idle(); + target = source; +} + +auto SM83::instructionLD_Direct_DirectRelative(uint16& target, uint16& source) -> void { + auto data = operand(); + idle(); + CF = (uint8)source + (uint8)data > 0xff; + HF = (uint4)source + (uint4)data > 0x0f; + NF = 0; + ZF = 0; + target = source + (int8)data; +} + +auto SM83::instructionLD_Direct_Indirect(uint8& target, uint16& source) -> void { + target = read(source); +} + +auto SM83::instructionLD_Direct_IndirectDecrement(uint8& target, uint16& source) -> void { + target = read(source--); +} + +auto SM83::instructionLD_Direct_IndirectIncrement(uint8& target, uint16& source) -> void { + target = read(source++); +} + +auto SM83::instructionLD_Indirect_Data(uint16& target) -> void { + write(target, operand()); +} + +auto SM83::instructionLD_Indirect_Direct(uint16& target, uint8& source) -> void { + write(target, source); +} + +auto SM83::instructionLD_IndirectDecrement_Direct(uint16& target, uint8& source) -> void { + write(target--, source); +} + +auto SM83::instructionLD_IndirectIncrement_Direct(uint16& target, uint8& source) -> void { + write(target++, source); +} + +auto SM83::instructionLDH_Address_Direct(uint8& data) -> void { + write(0xff00 | operand(), data); +} + +auto SM83::instructionLDH_Direct_Address(uint8& data) -> void { + data = read(0xff00 | operand()); +} + +auto SM83::instructionLDH_Direct_Indirect(uint8& target, uint8& source) -> void { + target = read(0xff00 | source); +} + +auto SM83::instructionLDH_Indirect_Direct(uint8& target, uint8& source) -> void { + write(0xff00 | target, source); +} + +auto SM83::instructionNOP() -> void { +} + +auto SM83::instructionOR_Direct_Data(uint8& target) -> void { + target = OR(target, operand()); +} + +auto SM83::instructionOR_Direct_Direct(uint8& target, uint8& source) -> void { + target = OR(target, source); +} + +auto SM83::instructionOR_Direct_Indirect(uint8& target, uint16& source) -> void { + target = OR(target, read(source)); +} + +auto SM83::instructionPOP_Direct(uint16& data) -> void { + data = pop(); +} + +auto SM83::instructionPUSH_Direct(uint16& data) -> void { + idle(); + push(data); +} + +auto SM83::instructionRES_Index_Direct(uint3 index, uint8& data) -> void { + bit1(data,index) = 0; +} + +auto SM83::instructionRES_Index_Indirect(uint3 index, uint16& address) -> void { + auto data = read(address); + bit1(data,index) = 0; + write(address, data); +} + +auto SM83::instructionRET() -> void { + auto address = pop(); + idle(); + PC = address; +} + +auto SM83::instructionRET_Condition(bool take) -> void { + idle(); + if(!take) return; + PC = pop(); + idle(); +} + +auto SM83::instructionRETI() -> void { + auto address = pop(); + idle(); + PC = address; + r.ime = 1; +} + +auto SM83::instructionRL_Direct(uint8& data) -> void { + data = RL(data); +} + +auto SM83::instructionRL_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, RL(data)); +} + +auto SM83::instructionRLA() -> void { + A = RL(A); + ZF = 0; +} + +auto SM83::instructionRLC_Direct(uint8& data) -> void { + data = RLC(data); +} + +auto SM83::instructionRLC_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, RLC(data)); +} + +auto SM83::instructionRLCA() -> void { + A = RLC(A); + ZF = 0; +} + +auto SM83::instructionRR_Direct(uint8& data) -> void { + data = RR(data); +} + +auto SM83::instructionRR_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, RR(data)); +} + +auto SM83::instructionRRA() -> void { + A = RR(A); + ZF = 0; +} + +auto SM83::instructionRRC_Direct(uint8& data) -> void { + data = RRC(data); +} + +auto SM83::instructionRRC_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, RRC(data)); +} + +auto SM83::instructionRRCA() -> void { + A = RRC(A); + ZF = 0; +} + +auto SM83::instructionRST_Implied(uint8 vector) -> void { + idle(); + push(PC); + PC = vector; +} + +auto SM83::instructionSBC_Direct_Data(uint8& target) -> void { + target = SUB(target, operand(), CF); +} + +auto SM83::instructionSBC_Direct_Direct(uint8& target, uint8& source) -> void { + target = SUB(target, source, CF); +} + +auto SM83::instructionSBC_Direct_Indirect(uint8& target, uint16& source) -> void { + target = SUB(target, read(source), CF); +} + +auto SM83::instructionSCF() -> void { + CF = 1; + HF = 0; + NF = 0; +} + +auto SM83::instructionSET_Index_Direct(uint3 index, uint8& data) -> void { + bit1(data,index) = 1; +} + +auto SM83::instructionSET_Index_Indirect(uint3 index, uint16& address) -> void { + auto data = read(address); + bit1(data,index) = 1; + write(address, data); +} + +auto SM83::instructionSLA_Direct(uint8& data) -> void { + data = SLA(data); +} + +auto SM83::instructionSLA_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, SLA(data)); +} + +auto SM83::instructionSRA_Direct(uint8& data) -> void { + data = SRA(data); +} + +auto SM83::instructionSRA_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, SRA(data)); +} + +auto SM83::instructionSRL_Direct(uint8& data) -> void { + data = SRL(data); +} + +auto SM83::instructionSRL_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, SRL(data)); +} + +auto SM83::instructionSTOP() -> void { + if(!stoppable()) return; + r.stop = 1; + while(r.stop) stop(); +} + +auto SM83::instructionSUB_Direct_Data(uint8& target) -> void { + target = SUB(target, operand()); +} + +auto SM83::instructionSUB_Direct_Direct(uint8& target, uint8& source) -> void { + target = SUB(target, source); +} + +auto SM83::instructionSUB_Direct_Indirect(uint8& target, uint16& source) -> void { + target = SUB(target, read(source)); +} + +auto SM83::instructionSWAP_Direct(uint8& data) -> void { + data = SWAP(data); +} + +auto SM83::instructionSWAP_Indirect(uint16& address) -> void { + auto data = read(address); + write(address, SWAP(data)); +} + +auto SM83::instructionXOR_Direct_Data(uint8& target) -> void { + target = XOR(target, operand()); +} + +auto SM83::instructionXOR_Direct_Direct(uint8& target, uint8& source) -> void { + target = XOR(target, source); +} + +auto SM83::instructionXOR_Direct_Indirect(uint8& target, uint16& source) -> void { + target = XOR(target, read(source)); +} diff --git a/waterbox/bsnescore/bsnes/processor/sm83/memory.cpp b/waterbox/bsnescore/bsnes/processor/sm83/memory.cpp new file mode 100644 index 0000000000..2833f4d065 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/sm83/memory.cpp @@ -0,0 +1,28 @@ +auto SM83::operand() -> uint8 { + return read(PC++); +} + +auto SM83::operands() -> uint16 { + uint16 data = read(PC++) << 0; + return data | read(PC++) << 8; +} + +auto SM83::load(uint16 address) -> uint16 { + uint16 data = read(address++) << 0; + return data | read(address++) << 8; +} + +auto SM83::store(uint16 address, uint16 data) -> void { + write(address++, data >> 0); + write(address++, data >> 8); +} + +auto SM83::pop() -> uint16 { + uint16 data = read(SP++) << 0; + return data | read(SP++) << 8; +} + +auto SM83::push(uint16 data) -> void { + write(--SP, data >> 8); + write(--SP, data >> 0); +} diff --git a/waterbox/bsnescore/bsnes/processor/sm83/registers.cpp b/waterbox/bsnescore/bsnes/processor/sm83/registers.cpp new file mode 100644 index 0000000000..a46a557fd0 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/sm83/registers.cpp @@ -0,0 +1,20 @@ +#define AF r.af.word +#define BC r.bc.word +#define DE r.de.word +#define HL r.hl.word +#define SP r.sp.word +#define PC r.pc.word + +#define A r.af.byte.hi +#define F r.af.byte.lo +#define B r.bc.byte.hi +#define C r.bc.byte.lo +#define D r.de.byte.hi +#define E r.de.byte.lo +#define H r.hl.byte.hi +#define L r.hl.byte.lo + +#define CF bit1(r.af.byte.lo,4) +#define HF bit1(r.af.byte.lo,5) +#define NF bit1(r.af.byte.lo,6) +#define ZF bit1(r.af.byte.lo,7) diff --git a/waterbox/bsnescore/bsnes/processor/sm83/serialization.cpp b/waterbox/bsnescore/bsnes/processor/sm83/serialization.cpp new file mode 100644 index 0000000000..e75cd8b35d --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/sm83/serialization.cpp @@ -0,0 +1,12 @@ +auto SM83::serialize(serializer& s) -> void { + s.integer(r.af.word); + s.integer(r.bc.word); + s.integer(r.de.word); + s.integer(r.hl.word); + s.integer(r.sp.word); + s.integer(r.pc.word); + s.integer(r.ei); + s.integer(r.halt); + s.integer(r.stop); + s.integer(r.ime); +} diff --git a/waterbox/bsnescore/bsnes/processor/sm83/sm83.cpp b/waterbox/bsnescore/bsnes/processor/sm83/sm83.cpp new file mode 100644 index 0000000000..7ecf53389f --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/sm83/sm83.cpp @@ -0,0 +1,18 @@ +#include +#include "sm83.hpp" + +namespace Processor { + +#include "registers.cpp" +#include "memory.cpp" +#include "algorithms.cpp" +#include "instruction.cpp" +#include "instructions.cpp" +#include "serialization.cpp" +#include "disassembler.cpp" + +auto SM83::power() -> void { + r = {}; +} + +} diff --git a/waterbox/bsnescore/bsnes/processor/sm83/sm83.hpp b/waterbox/bsnescore/bsnes/processor/sm83/sm83.hpp new file mode 100644 index 0000000000..28da1a355a --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/sm83/sm83.hpp @@ -0,0 +1,184 @@ +//Sharp SM83 + +//the Game Boy SoC is commonly referred to as the Sharp LR35902 +//SM83 is most likely the internal CPU core, based on strong datasheet similarities +//as such, this CPU core could serve as a foundation for any SM83xx SoC + +#pragma once + +namespace Processor { + +struct SM83 { + virtual auto stoppable() -> bool = 0; + virtual auto stop() -> void = 0; + virtual auto halt() -> void = 0; + virtual auto idle() -> void = 0; + virtual auto read(uint16 address) -> uint8 = 0; + virtual auto write(uint16 address, uint8 data) -> void = 0; + + //lr35902.cpp + auto power() -> void; + + //instruction.cpp + auto interrupt(uint16 vector) -> void; + auto instruction() -> void; + auto instructionCB() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + //disassembler.cpp + virtual auto readDebugger(uint16 address) -> uint8 { return 0; } + auto disassemble(uint16 pc) -> string; + + //memory.cpp + auto operand() -> uint8; + auto operands() -> uint16; + auto load(uint16 address) -> uint16; + auto store(uint16 address, uint16 data) -> void; + auto pop() -> uint16; + auto push(uint16 data) -> void; + + //algorithms.cpp + auto ADD(uint8, uint8, bool = 0) -> uint8; + auto AND(uint8, uint8) -> uint8; + auto BIT(uint3, uint8) -> void; + auto CP(uint8, uint8) -> void; + auto DEC(uint8) -> uint8; + auto INC(uint8) -> uint8; + auto OR(uint8, uint8) -> uint8; + auto RL(uint8) -> uint8; + auto RLC(uint8) -> uint8; + auto RR(uint8) -> uint8; + auto RRC(uint8) -> uint8; + auto SLA(uint8) -> uint8; + auto SRA(uint8) -> uint8; + auto SRL(uint8) -> uint8; + auto SUB(uint8, uint8, bool = 0) -> uint8; + auto SWAP(uint8) -> uint8; + auto XOR(uint8, uint8) -> uint8; + + //instructions.cpp + auto instructionADC_Direct_Data(uint8&) -> void; + auto instructionADC_Direct_Direct(uint8&, uint8&) -> void; + auto instructionADC_Direct_Indirect(uint8&, uint16&) -> void; + auto instructionADD_Direct_Data(uint8&) -> void; + auto instructionADD_Direct_Direct(uint8&, uint8&) -> void; + auto instructionADD_Direct_Direct(uint16&, uint16&) -> void; + auto instructionADD_Direct_Indirect(uint8&, uint16&) -> void; + auto instructionADD_Direct_Relative(uint16&) -> void; + auto instructionAND_Direct_Data(uint8&) -> void; + auto instructionAND_Direct_Direct(uint8&, uint8&) -> void; + auto instructionAND_Direct_Indirect(uint8&, uint16&) -> void; + auto instructionBIT_Index_Direct(uint3, uint8&) -> void; + auto instructionBIT_Index_Indirect(uint3, uint16&) -> void; + auto instructionCALL_Condition_Address(bool) -> void; + auto instructionCCF() -> void; + auto instructionCP_Direct_Data(uint8&) -> void; + auto instructionCP_Direct_Direct(uint8&, uint8&) -> void; + auto instructionCP_Direct_Indirect(uint8&, uint16&) -> void; + auto instructionCPL() -> void; + auto instructionDAA() -> void; + auto instructionDEC_Direct(uint8&) -> void; + auto instructionDEC_Direct(uint16&) -> void; + auto instructionDEC_Indirect(uint16&) -> void; + auto instructionDI() -> void; + auto instructionEI() -> void; + auto instructionHALT() -> void; + auto instructionINC_Direct(uint8&) -> void; + auto instructionINC_Direct(uint16&) -> void; + auto instructionINC_Indirect(uint16&) -> void; + auto instructionJP_Condition_Address(bool) -> void; + auto instructionJP_Direct(uint16&) -> void; + auto instructionJR_Condition_Relative(bool) -> void; + auto instructionLD_Address_Direct(uint8&) -> void; + auto instructionLD_Address_Direct(uint16&) -> void; + auto instructionLD_Direct_Address(uint8&) -> void; + auto instructionLD_Direct_Data(uint8&) -> void; + auto instructionLD_Direct_Data(uint16&) -> void; + auto instructionLD_Direct_Direct(uint8&, uint8&) -> void; + auto instructionLD_Direct_Direct(uint16&, uint16&) -> void; + auto instructionLD_Direct_DirectRelative(uint16&, uint16&) -> void; + auto instructionLD_Direct_Indirect(uint8&, uint16&) -> void; + auto instructionLD_Direct_IndirectDecrement(uint8&, uint16&) -> void; + auto instructionLD_Direct_IndirectIncrement(uint8&, uint16&) -> void; + auto instructionLD_Indirect_Data(uint16&) -> void; + auto instructionLD_Indirect_Direct(uint16&, uint8&) -> void; + auto instructionLD_IndirectDecrement_Direct(uint16&, uint8&) -> void; + auto instructionLD_IndirectIncrement_Direct(uint16&, uint8&) -> void; + auto instructionLDH_Address_Direct(uint8&) -> void; + auto instructionLDH_Direct_Address(uint8&) -> void; + auto instructionLDH_Direct_Indirect(uint8&, uint8&) -> void; + auto instructionLDH_Indirect_Direct(uint8&, uint8&) -> void; + auto instructionNOP() -> void; + auto instructionOR_Direct_Data(uint8&) -> void; + auto instructionOR_Direct_Direct(uint8&, uint8&) -> void; + auto instructionOR_Direct_Indirect(uint8&, uint16&) -> void; + auto instructionPOP_Direct(uint16&) -> void; + auto instructionPUSH_Direct(uint16&) -> void; + auto instructionRES_Index_Direct(uint3, uint8&) -> void; + auto instructionRES_Index_Indirect(uint3, uint16&) -> void; + auto instructionRET() -> void; + auto instructionRET_Condition(bool) -> void; + auto instructionRETI() -> void; + auto instructionRL_Direct(uint8&) -> void; + auto instructionRL_Indirect(uint16&) -> void; + auto instructionRLA() -> void; + auto instructionRLC_Direct(uint8&) -> void; + auto instructionRLC_Indirect(uint16&) -> void; + auto instructionRLCA() -> void; + auto instructionRR_Direct(uint8&) -> void; + auto instructionRR_Indirect(uint16&) -> void; + auto instructionRRA() -> void; + auto instructionRRC_Direct(uint8&) -> void; + auto instructionRRC_Indirect(uint16&) -> void; + auto instructionRRCA() -> void; + auto instructionRST_Implied(uint8) -> void; + auto instructionSBC_Direct_Data(uint8&) -> void; + auto instructionSBC_Direct_Direct(uint8&, uint8&) -> void; + auto instructionSBC_Direct_Indirect(uint8&, uint16&) -> void; + auto instructionSCF() -> void; + auto instructionSET_Index_Direct(uint3, uint8&) -> void; + auto instructionSET_Index_Indirect(uint3, uint16&) -> void; + auto instructionSLA_Direct(uint8&) -> void; + auto instructionSLA_Indirect(uint16&) -> void; + auto instructionSRA_Direct(uint8&) -> void; + auto instructionSRA_Indirect(uint16&) -> void; + auto instructionSRL_Direct(uint8&) -> void; + auto instructionSRL_Indirect(uint16&) -> void; + auto instructionSUB_Direct_Data(uint8&) -> void; + auto instructionSUB_Direct_Direct(uint8&, uint8&) -> void; + auto instructionSUB_Direct_Indirect(uint8&, uint16&) -> void; + auto instructionSWAP_Direct(uint8& data) -> void; + auto instructionSWAP_Indirect(uint16& address) -> void; + auto instructionSTOP() -> void; + auto instructionXOR_Direct_Data(uint8&) -> void; + auto instructionXOR_Direct_Direct(uint8&, uint8&) -> void; + auto instructionXOR_Direct_Indirect(uint8&, uint16&) -> void; + + struct Registers { + union Pair { + Pair() : word(0) {} + uint16 word; + struct Byte { uint8 order_msb2(hi, lo); } byte; + }; + + Pair af; + Pair bc; + Pair de; + Pair hl; + Pair sp; + Pair pc; + + uint1 ei; + uint1 halt; + uint1 stop; + uint1 ime; + } r; + + //disassembler.cpp + auto disassembleOpcode(uint16 pc) -> string; + auto disassembleOpcodeCB(uint16 pc) -> string; +}; + +} diff --git a/waterbox/bsnescore/bsnes/processor/spc700/algorithms.cpp b/waterbox/bsnescore/bsnes/processor/spc700/algorithms.cpp new file mode 100644 index 0000000000..d7e3745ef8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/spc700/algorithms.cpp @@ -0,0 +1,130 @@ +auto SPC700::algorithmADC(uint8 x, uint8 y) -> uint8 { + int z = x + y + CF; + CF = z > 0xff; + ZF = (uint8)z == 0; + HF = (x ^ y ^ z) & 0x10; + VF = ~(x ^ y) & (x ^ z) & 0x80; + NF = z & 0x80; + return z; +} + +auto SPC700::algorithmAND(uint8 x, uint8 y) -> uint8 { + x &= y; + ZF = x == 0; + NF = x & 0x80; + return x; +} + +auto SPC700::algorithmASL(uint8 x) -> uint8 { + CF = x & 0x80; + x <<= 1; + ZF = x == 0; + NF = x & 0x80; + return x; +} + +auto SPC700::algorithmCMP(uint8 x, uint8 y) -> uint8 { + int z = x - y; + CF = z >= 0; + ZF = (uint8)z == 0; + NF = z & 0x80; + return x; +} + +auto SPC700::algorithmDEC(uint8 x) -> uint8 { + x--; + ZF = x == 0; + NF = x & 0x80; + return x; +} + +auto SPC700::algorithmEOR(uint8 x, uint8 y) -> uint8 { + x ^= y; + ZF = x == 0; + NF = x & 0x80; + return x; +} + +auto SPC700::algorithmINC(uint8 x) -> uint8 { + x++; + ZF = x == 0; + NF = x & 0x80; + return x; +} + +auto SPC700::algorithmLD(uint8 x, uint8 y) -> uint8 { + ZF = y == 0; + NF = y & 0x80; + return y; +} + +auto SPC700::algorithmLSR(uint8 x) -> uint8 { + CF = x & 0x01; + x >>= 1; + ZF = x == 0; + NF = x & 0x80; + return x; +} + +auto SPC700::algorithmOR(uint8 x, uint8 y) -> uint8 { + x |= y; + ZF = x == 0; + NF = x & 0x80; + return x; +} + +auto SPC700::algorithmROL(uint8 x) -> uint8 { + bool carry = CF; + CF = x & 0x80; + x = x << 1 | carry; + ZF = x == 0; + NF = x & 0x80; + return x; +} + +auto SPC700::algorithmROR(uint8 x) -> uint8 { + bool carry = CF; + CF = x & 0x01; + x = carry << 7 | x >> 1; + ZF = x == 0; + NF = x & 0x80; + return x; +} + +auto SPC700::algorithmSBC(uint8 x, uint8 y) -> uint8 { + return algorithmADC(x, ~y); +} + +// + +auto SPC700::algorithmADW(uint16 x, uint16 y) -> uint16 { + uint16 z; + CF = 0; + z = algorithmADC(x, y); + z |= algorithmADC(x >> 8, y >> 8) << 8; + ZF = z == 0; + return z; +} + +auto SPC700::algorithmCPW(uint16 x, uint16 y) -> uint16 { + int z = x - y; + CF = z >= 0; + ZF = (uint16)z == 0; + NF = z & 0x8000; + return x; +} + +auto SPC700::algorithmLDW(uint16 x, uint16 y) -> uint16 { + ZF = y == 0; + NF = y & 0x8000; + return y; +} + +auto SPC700::algorithmSBW(uint16 x, uint16 y) -> uint16 { + uint16 z; + CF = 1; + z = algorithmSBC(x, y); + z |= algorithmSBC(x >> 8, y >> 8) << 8; + ZF = z == 0; + return z; +} diff --git a/waterbox/bsnescore/bsnes/processor/spc700/disassembler.cpp b/waterbox/bsnescore/bsnes/processor/spc700/disassembler.cpp new file mode 100644 index 0000000000..1251cb755e --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/spc700/disassembler.cpp @@ -0,0 +1,305 @@ +auto SPC700::disassemble(uint16 addr, bool p) -> string { + auto read = [&](uint16 addr) -> uint8 { + return readDisassembler(addr); + }; + + auto relative = [&](uint length, int8 offset) -> uint16 { + uint16 pc = addr + length; + return pc + offset; + }; + + auto a = [&] { return hex((read(addr + 1) << 0) + (read(addr + 2) << 8), 4L); }; + auto b = [&](uint n) { return hex(read(addr + 1 + n), 2L); }; + auto rel = [&](uint r, uint n = 0) { return hex(addr + r + (int8)read(addr + 1 + n), 4L); }; + auto dp = [&](uint n) { return hex((p << 8) + read(addr + 1 + n), 3L); }; + auto ab = [&] { + uint n = (read(addr + 1) << 0) + (read(addr + 2) << 8); + return string{hex(n & 0x1fff, 4L), ":", hex(n >> 13, 1L)}; + }; + + auto mnemonic = [&]() -> string { + switch(read(addr)) { + case 0x00: return { "nop" }; + case 0x01: return { "jst $ffde" }; + case 0x02: return { "set $", dp(0), ":0" }; + case 0x03: return { "bbs $", dp(0), ":0=$", rel(+3, 1) }; + case 0x04: return { "ora $", dp(0) }; + case 0x05: return { "ora $", a() }; + case 0x06: return { "ora (x)" }; + case 0x07: return { "ora ($", dp(0), ",x)" }; + case 0x08: return { "ora #$", b(0) }; + case 0x09: return { "orr $", dp(1), "=$", dp(0) }; + case 0x0a: return { "orc $", ab() }; + case 0x0b: return { "asl $", dp(0) }; + case 0x0c: return { "asl $", a() }; + case 0x0d: return { "php" }; + case 0x0e: return { "tsb $", a() }; + case 0x0f: return { "brk" }; + case 0x10: return { "bpl $", rel(+2) }; + case 0x11: return { "jst $ffdc" }; + case 0x12: return { "clr $", dp(0), ":0" }; + case 0x13: return { "bbc $", dp(0), ":0=$", rel(+3, 1) }; + case 0x14: return { "ora $", dp(0), ",x" }; + case 0x15: return { "ora $", a(), ",x" }; + case 0x16: return { "ora $", a(), ",y" }; + case 0x17: return { "ora ($", dp(0), "),y" }; + case 0x18: return { "orr $", dp(1), "=#$", b(0) }; + case 0x19: return { "orr (x)=(y)" }; + case 0x1a: return { "dew $", dp(0) }; + case 0x1b: return { "asl $", dp(0), ",x" }; + case 0x1c: return { "asl" }; + case 0x1d: return { "dex" }; + case 0x1e: return { "cpx $", a() }; + case 0x1f: return { "jmp ($", a(), ",x)" }; + case 0x20: return { "clp" }; + case 0x21: return { "jst $ffda" }; + case 0x22: return { "set $", dp(0), ":1" }; + case 0x23: return { "bbs $", dp(0), ":1=$", rel(+3, 1) }; + case 0x24: return { "and $", dp(0) }; + case 0x25: return { "and $", a() }; + case 0x26: return { "and (x)" }; + case 0x27: return { "and ($", dp(0), ",x)" }; + case 0x28: return { "and #$", b(0) }; + case 0x29: return { "and $", dp(1), "=$", dp(0) }; + case 0x2a: return { "orc !$", ab() }; + case 0x2b: return { "rol $", dp(0) }; + case 0x2c: return { "rol $", a() }; + case 0x2d: return { "pha" }; + case 0x2e: return { "bne $", dp(0), "=$", rel(+3, 1) }; + case 0x2f: return { "bra $", rel(+2) }; + case 0x30: return { "bmi $", rel(+2) }; + case 0x31: return { "jst $ffd8" }; + case 0x32: return { "clr $", dp(0), ":1" }; + case 0x33: return { "bbc $", dp(0), ":1=$", rel(+3, 1) }; + case 0x34: return { "and $", dp(0), ",x" }; + case 0x35: return { "and $", a(), ",x" }; + case 0x36: return { "and $", a(), ",y" }; + case 0x37: return { "and ($", dp(0), "),y" }; + case 0x38: return { "and $", dp(1), "=#$", b(0) }; + case 0x39: return { "and (x)=(y)" }; + case 0x3a: return { "inw $", dp(0) }; + case 0x3b: return { "rol $", dp(0), ",x" }; + case 0x3c: return { "rol" }; + case 0x3d: return { "inx" }; + case 0x3e: return { "cpx $", dp(0) }; + case 0x3f: return { "jsr $", a() }; + case 0x40: return { "sep" }; + case 0x41: return { "jst $ffd6" }; + case 0x42: return { "set $", dp(0), ":2" }; + case 0x43: return { "bbs $", dp(0), ":2=$", rel(+3, 1) }; + case 0x44: return { "eor $", dp(0) }; + case 0x45: return { "eor $", a() }; + case 0x46: return { "eor (x)" }; + case 0x47: return { "eor ($", dp(0), ",x)" }; + case 0x48: return { "eor #$", b(0) }; + case 0x49: return { "eor $", dp(1), "=$", dp(0) }; + case 0x4a: return { "and $", ab() }; + case 0x4b: return { "lsr $", dp(0) }; + case 0x4c: return { "lsr $", a() }; + case 0x4d: return { "phx" }; + case 0x4e: return { "trb $", a() }; + case 0x4f: return { "jsp $ff", b(0) }; + case 0x50: return { "bvc $", rel(+2) }; + case 0x51: return { "jst $ffd4" }; + case 0x52: return { "clr $", dp(0), ":2" }; + case 0x53: return { "bbc $", dp(0), ":2=$", rel(+3, 1) }; + case 0x54: return { "eor $", dp(0), ",x" }; + case 0x55: return { "eor $", a(), ",x" }; + case 0x56: return { "eor $", a(), ",y" }; + case 0x57: return { "eor ($", dp(0), "),y" }; + case 0x58: return { "eor $", dp(1), "=#$", b(0) }; + case 0x59: return { "eor (x)=(y)" }; + case 0x5a: return { "cpw $", a() }; + case 0x5b: return { "lsr $", dp(0), ",x" }; + case 0x5c: return { "lsr" }; + case 0x5d: return { "tax" }; + case 0x5e: return { "cpy $", a() }; + case 0x5f: return { "jmp $", a() }; + case 0x60: return { "clc" }; + case 0x61: return { "jst $ffd2" }; + case 0x62: return { "set $", dp(0), ":3" }; + case 0x63: return { "bbs $", dp(0), ":3=$", rel(+3, 1) }; + case 0x64: return { "cmp $", dp(0) }; + case 0x65: return { "cmp $", a() }; + case 0x66: return { "cmp (x)" }; + case 0x67: return { "cmp ($", dp(0), ",x)" }; + case 0x68: return { "cmp #$", b(0) }; + case 0x69: return { "cmp $", dp(1), "=$", dp(0) }; + case 0x6a: return { "and !$", ab() }; + case 0x6b: return { "ror $", dp(0) }; + case 0x6c: return { "ror $", a() }; + case 0x6d: return { "phy" }; + case 0x6e: return { "bne --$", dp(0), "=$", rel(+3, 1) }; + case 0x6f: return { "rts" }; + case 0x70: return { "bvs $", rel(+2) }; + case 0x71: return { "jst $ffd0" }; + case 0x72: return { "clr $", dp(0), ":3" }; + case 0x73: return { "bbc $", dp(0), ":3=$", rel(+3, 1) }; + case 0x74: return { "cmp $", dp(0), ",x" }; + case 0x75: return { "cmp $", a(), ",x" }; + case 0x76: return { "cmp $", a(), ",y" }; + case 0x77: return { "cmp ($", dp(0), "),y" }; + case 0x78: return { "cmp $", dp(1), "=#$", b(0) }; + case 0x79: return { "cmp (x)=(y)" }; + case 0x7a: return { "adw $", a() }; + case 0x7b: return { "ror $", dp(0), ",x" }; + case 0x7c: return { "ror" }; + case 0x7d: return { "txa" }; + case 0x7e: return { "cpy $", dp(0) }; + case 0x7f: return { "rti" }; + case 0x80: return { "sec" }; + case 0x81: return { "jst $ffce" }; + case 0x82: return { "set $", dp(0), ":4" }; + case 0x83: return { "bbs $", dp(0), ":4=$", rel(+3, 1) }; + case 0x84: return { "adc $", dp(0) }; + case 0x85: return { "adc $", a() }; + case 0x86: return { "adc (x)" }; + case 0x87: return { "adc ($", dp(0), ",x)" }; + case 0x88: return { "adc #$", b(0) }; + case 0x89: return { "adc $", dp(1), "=$", dp(0) }; + case 0x8a: return { "eor $", ab() }; + case 0x8b: return { "dec $", dp(0) }; + case 0x8c: return { "dec $", a() }; + case 0x8d: return { "ldy #$", b(0) }; + case 0x8e: return { "plp" }; + case 0x8f: return { "str $", dp(1), "=#$", b(0) }; + case 0x90: return { "bcc $", rel(+2) }; + case 0x91: return { "jst $ffcc" }; + case 0x92: return { "clr $", dp(0), ":4" }; + case 0x93: return { "bbc $", dp(0), ":4=$", rel(+3, 1) }; + case 0x94: return { "adc $", dp(0), ",x" }; + case 0x95: return { "adc $", a(), ",x" }; + case 0x96: return { "adc $", a(), ",y" }; + case 0x97: return { "adc ($", dp(0), "),y" }; + case 0x98: return { "adc $", dp(1), "=#$", b(0) }; + case 0x99: return { "adc (x)=(y)" }; + case 0x9a: return { "sbw $", a() }; + case 0x9b: return { "dec $", dp(0), ",x" }; + case 0x9c: return { "dec" }; + case 0x9d: return { "tsx" }; + case 0x9e: return { "div" }; + case 0x9f: return { "xcn" }; + case 0xa0: return { "sei" }; + case 0xa1: return { "jst $ffca" }; + case 0xa2: return { "set $", dp(0), ":5" }; + case 0xa3: return { "bbs $", dp(0), ":5=$", rel(+3, 1) }; + case 0xa4: return { "sbc $", dp(0) }; + case 0xa5: return { "sbc $", a() }; + case 0xa6: return { "sbc (x)" }; + case 0xa7: return { "sbc ($", dp(0), ",x)" }; + case 0xa8: return { "sbc #$", b(0) }; + case 0xa9: return { "sbc $", dp(1), "=$", dp(0) }; + case 0xaa: return { "ldc $", ab() }; + case 0xab: return { "inc $", dp(0) }; + case 0xac: return { "inc $", a() }; + case 0xad: return { "cpy #$", b(0) }; + case 0xae: return { "pla" }; + case 0xaf: return { "sta (x++)" }; + case 0xb0: return { "bcs $", rel(+2) }; + case 0xb1: return { "jst $ffc8" }; + case 0xb2: return { "clr $", dp(0), ":5" }; + case 0xb3: return { "bbc $", dp(0), ":5=$", rel(+3, 1) }; + case 0xb4: return { "sbc $", dp(0), ",x" }; + case 0xb5: return { "sbc $", a(), ",x" }; + case 0xb6: return { "sbc $", a(), ",y" }; + case 0xb7: return { "sbc ($", dp(0), "),y" }; + case 0xb8: return { "sbc $", dp(1), "=#$", b(0) }; + case 0xb9: return { "sbc (x)=(y)" }; + case 0xba: return { "ldw $", dp(0) }; + case 0xbb: return { "inc $", dp(0), ",x" }; + case 0xbc: return { "inc" }; + case 0xbd: return { "txs" }; + case 0xbe: return { "das" }; + case 0xbf: return { "lda (x++)" }; + case 0xc0: return { "cli" }; + case 0xc1: return { "jst $ffc6" }; + case 0xc2: return { "set $", dp(0), ":6" }; + case 0xc3: return { "bbs $", dp(0), ":6=$", rel(+3, 1) }; + case 0xc4: return { "sta $", dp(0) }; + case 0xc5: return { "sta $", a() }; + case 0xc6: return { "sta (x)" }; + case 0xc7: return { "sta ($", dp(0), ",x)" }; + case 0xc8: return { "cpx #$", b(0) }; + case 0xc9: return { "stx $", a() }; + case 0xca: return { "stc $", ab() }; + case 0xcb: return { "sty $", dp(0) }; + case 0xcc: return { "sty $", a() }; + case 0xcd: return { "ldx #$", b(0) }; + case 0xce: return { "plx" }; + case 0xcf: return { "mul" }; + case 0xd0: return { "bne $", rel(+2) }; + case 0xd1: return { "jst $ffc4" }; + case 0xd2: return { "clr $", dp(0), ":6" }; + case 0xd3: return { "bbc $", dp(0), ":6=$", rel(+3, 1) }; + case 0xd4: return { "sta $", dp(0), ",x" }; + case 0xd5: return { "sta $", a(), ",x" }; + case 0xd6: return { "sta $", a(), ",y" }; + case 0xd7: return { "sta ($", dp(0), "),y" }; + case 0xd8: return { "stx $", dp(0) }; + case 0xd9: return { "stx $", dp(0), ",y" }; + case 0xda: return { "stw $", dp(0) }; + case 0xdb: return { "sty $", dp(0), ",x" }; + case 0xdc: return { "dey" }; + case 0xdd: return { "tya" }; + case 0xde: return { "bne $", dp(0), ",x=$", rel(+3, 1) }; + case 0xdf: return { "daa" }; + case 0xe0: return { "clv" }; + case 0xe1: return { "jst $ffc2" }; + case 0xe2: return { "set $", dp(0), ":7" }; + case 0xe3: return { "bbs $", dp(0), ":7=$", rel(+3, 1) }; + case 0xe4: return { "lda $", dp(0) }; + case 0xe5: return { "lda $", a() }; + case 0xe6: return { "lda (x)" }; + case 0xe7: return { "lda ($", dp(0), ",x)" }; + case 0xe8: return { "lda #$", b(0) }; + case 0xe9: return { "ldx $", a() }; + case 0xea: return { "not $", ab() }; + case 0xeb: return { "ldy $", dp(0) }; + case 0xec: return { "ldy $", a() }; + case 0xed: return { "cmc" }; + case 0xee: return { "ply" }; + case 0xef: return { "wai" }; + case 0xf0: return { "beq $", rel(+2) }; + case 0xf1: return { "jst $ffc0" }; + case 0xf2: return { "clr $", dp(0), ":7" }; + case 0xf3: return { "bbc $", dp(0), ":7=$", rel(+3, 1) }; + case 0xf4: return { "lda $", dp(0), ",x" }; + case 0xf5: return { "lda $", a(), ",x" }; + case 0xf6: return { "lda $", a(), ",y" }; + case 0xf7: return { "lda ($", dp(0), "),y" }; + case 0xf8: return { "ldx $", dp(0) }; + case 0xf9: return { "ldx $", dp(0), ",y" }; + case 0xfa: return { "str $", dp(1), "=$", dp(0) }; + case 0xfb: return { "ldy $", dp(0), ",x" }; + case 0xfc: return { "iny" }; + case 0xfd: return { "tay" }; + case 0xfe: return { "bne --y=$", rel(+2) }; + case 0xff: return { "stp" }; + } + throw; + }; + + string output = {"..", hex(addr, 4L), " ", mnemonic()}; + + uint length = output.length(); + while(length++ < 30) output.append(" "); + + output.append( + "YA:", hex(YA, 4L), + " A:", hex(A, 2L), + " X:", hex(X, 2L), + " Y:", hex(Y, 2L), + " S:", hex(S, 2L), + " ", + NF ? "N" : "n", + VF ? "V" : "v", + PF ? "P" : "p", + BF ? "B" : "b", + HF ? "H" : "h", + IF ? "I" : "i", + ZF ? "Z" : "z", + CF ? "C" : "c" + ); + + return output; +} diff --git a/waterbox/bsnescore/bsnes/processor/spc700/instruction.cpp b/waterbox/bsnescore/bsnes/processor/spc700/instruction.cpp new file mode 100644 index 0000000000..4d04d88db6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/spc700/instruction.cpp @@ -0,0 +1,266 @@ +#define op(id, name, ...) case id: return instruction##name(__VA_ARGS__); +#define fp(name) &SPC700::algorithm##name + +auto SPC700::instruction() -> void { + switch(fetch()) { + op(0x00, NoOperation) + op(0x01, CallTable, 0) + op(0x02, AbsoluteBitSet, 0, true) + op(0x03, BranchBit, 0, true) + op(0x04, DirectRead, fp(OR), A) + op(0x05, AbsoluteRead, fp(OR), A) + op(0x06, IndirectXRead, fp(OR)) + op(0x07, IndexedIndirectRead, fp(OR), X) + op(0x08, ImmediateRead, fp(OR), A) + op(0x09, DirectDirectModify, fp(OR)) + op(0x0a, AbsoluteBitModify, 0) + op(0x0b, DirectModify, fp(ASL)) + op(0x0c, AbsoluteModify, fp(ASL)) + op(0x0d, Push, P) + op(0x0e, TestSetBitsAbsolute, true) + op(0x0f, Break) + op(0x10, Branch, NF == 0) + op(0x11, CallTable, 1) + op(0x12, AbsoluteBitSet, 0, false) + op(0x13, BranchBit, 0, false) + op(0x14, DirectIndexedRead, fp(OR), A, X) + op(0x15, AbsoluteIndexedRead, fp(OR), X) + op(0x16, AbsoluteIndexedRead, fp(OR), Y) + op(0x17, IndirectIndexedRead, fp(OR), Y) + op(0x18, DirectImmediateModify, fp(OR)) + op(0x19, IndirectXWriteIndirectY, fp(OR)) + op(0x1a, DirectModifyWord, -1) + op(0x1b, DirectIndexedModify, fp(ASL), X) + op(0x1c, ImpliedModify, fp(ASL), A) + op(0x1d, ImpliedModify, fp(DEC), X) + op(0x1e, AbsoluteRead, fp(CMP), X) + op(0x1f, JumpIndirectX) + op(0x20, FlagSet, PF, false) + op(0x21, CallTable, 2) + op(0x22, AbsoluteBitSet, 1, true) + op(0x23, BranchBit, 1, true) + op(0x24, DirectRead, fp(AND), A) + op(0x25, AbsoluteRead, fp(AND), A) + op(0x26, IndirectXRead, fp(AND)) + op(0x27, IndexedIndirectRead, fp(AND), X) + op(0x28, ImmediateRead, fp(AND), A) + op(0x29, DirectDirectModify, fp(AND)) + op(0x2a, AbsoluteBitModify, 1) + op(0x2b, DirectModify, fp(ROL)) + op(0x2c, AbsoluteModify, fp(ROL)) + op(0x2d, Push, A) + op(0x2e, BranchNotDirect) + op(0x2f, Branch, true) + op(0x30, Branch, NF == 1) + op(0x31, CallTable, 3) + op(0x32, AbsoluteBitSet, 1, false) + op(0x33, BranchBit, 1, false) + op(0x34, DirectIndexedRead, fp(AND), A, X) + op(0x35, AbsoluteIndexedRead, fp(AND), X) + op(0x36, AbsoluteIndexedRead, fp(AND), Y) + op(0x37, IndirectIndexedRead, fp(AND), Y) + op(0x38, DirectImmediateModify, fp(AND)) + op(0x39, IndirectXWriteIndirectY, fp(AND)) + op(0x3a, DirectModifyWord, +1) + op(0x3b, DirectIndexedModify, fp(ROL), X) + op(0x3c, ImpliedModify, fp(ROL), A) + op(0x3d, ImpliedModify, fp(INC), X) + op(0x3e, DirectRead, fp(CMP), X) + op(0x3f, CallAbsolute) + op(0x40, FlagSet, PF, true) + op(0x41, CallTable, 4) + op(0x42, AbsoluteBitSet, 2, true) + op(0x43, BranchBit, 2, true) + op(0x44, DirectRead, fp(EOR), A) + op(0x45, AbsoluteRead, fp(EOR), A) + op(0x46, IndirectXRead, fp(EOR)) + op(0x47, IndexedIndirectRead, fp(EOR), X) + op(0x48, ImmediateRead, fp(EOR), A) + op(0x49, DirectDirectModify, fp(EOR)) + op(0x4a, AbsoluteBitModify, 2) + op(0x4b, DirectModify, fp(LSR)) + op(0x4c, AbsoluteModify, fp(LSR)) + op(0x4d, Push, X) + op(0x4e, TestSetBitsAbsolute, false) + op(0x4f, CallPage) + op(0x50, Branch, VF == 0) + op(0x51, CallTable, 5) + op(0x52, AbsoluteBitSet, 2, false) + op(0x53, BranchBit, 2, false) + op(0x54, DirectIndexedRead, fp(EOR), A, X) + op(0x55, AbsoluteIndexedRead, fp(EOR), X) + op(0x56, AbsoluteIndexedRead, fp(EOR), Y) + op(0x57, IndirectIndexedRead, fp(EOR), Y) + op(0x58, DirectImmediateModify, fp(EOR)) + op(0x59, IndirectXWriteIndirectY, fp(EOR)) + op(0x5a, DirectCompareWord, fp(CPW)) + op(0x5b, DirectIndexedModify, fp(LSR), X) + op(0x5c, ImpliedModify, fp(LSR), A) + op(0x5d, Transfer, A, X) + op(0x5e, AbsoluteRead, fp(CMP), Y) + op(0x5f, JumpAbsolute) + op(0x60, FlagSet, CF, false) + op(0x61, CallTable, 6) + op(0x62, AbsoluteBitSet, 3, true) + op(0x63, BranchBit, 3, true) + op(0x64, DirectRead, fp(CMP), A) + op(0x65, AbsoluteRead, fp(CMP), A) + op(0x66, IndirectXRead, fp(CMP)) + op(0x67, IndexedIndirectRead, fp(CMP), X) + op(0x68, ImmediateRead, fp(CMP), A) + op(0x69, DirectDirectCompare, fp(CMP)) + op(0x6a, AbsoluteBitModify, 3) + op(0x6b, DirectModify, fp(ROR)) + op(0x6c, AbsoluteModify, fp(ROR)) + op(0x6d, Push, Y) + op(0x6e, BranchNotDirectDecrement) + op(0x6f, ReturnSubroutine) + op(0x70, Branch, VF == 1) + op(0x71, CallTable, 7) + op(0x72, AbsoluteBitSet, 3, false) + op(0x73, BranchBit, 3, false) + op(0x74, DirectIndexedRead, fp(CMP), A, X) + op(0x75, AbsoluteIndexedRead, fp(CMP), X) + op(0x76, AbsoluteIndexedRead, fp(CMP), Y) + op(0x77, IndirectIndexedRead, fp(CMP), Y) + op(0x78, DirectImmediateCompare, fp(CMP)) + op(0x79, IndirectXCompareIndirectY, fp(CMP)) + op(0x7a, DirectReadWord, fp(ADW)) + op(0x7b, DirectIndexedModify, fp(ROR), X) + op(0x7c, ImpliedModify, fp(ROR), A) + op(0x7d, Transfer, X, A) + op(0x7e, DirectRead, fp(CMP), Y) + op(0x7f, ReturnInterrupt) + op(0x80, FlagSet, CF, true) + op(0x81, CallTable, 8) + op(0x82, AbsoluteBitSet, 4, true) + op(0x83, BranchBit, 4, true) + op(0x84, DirectRead, fp(ADC), A) + op(0x85, AbsoluteRead, fp(ADC), A) + op(0x86, IndirectXRead, fp(ADC)) + op(0x87, IndexedIndirectRead, fp(ADC), X) + op(0x88, ImmediateRead, fp(ADC), A) + op(0x89, DirectDirectModify, fp(ADC)) + op(0x8a, AbsoluteBitModify, 4) + op(0x8b, DirectModify, fp(DEC)) + op(0x8c, AbsoluteModify, fp(DEC)) + op(0x8d, ImmediateRead, fp(LD), Y) + op(0x8e, PullP) + op(0x8f, DirectImmediateWrite) + op(0x90, Branch, CF == 0) + op(0x91, CallTable, 9) + op(0x92, AbsoluteBitSet, 4, false) + op(0x93, BranchBit, 4, false) + op(0x94, DirectIndexedRead, fp(ADC), A, X) + op(0x95, AbsoluteIndexedRead, fp(ADC), X) + op(0x96, AbsoluteIndexedRead, fp(ADC), Y) + op(0x97, IndirectIndexedRead, fp(ADC), Y) + op(0x98, DirectImmediateModify, fp(ADC)) + op(0x99, IndirectXWriteIndirectY, fp(ADC)) + op(0x9a, DirectReadWord, fp(SBW)) + op(0x9b, DirectIndexedModify, fp(DEC), X) + op(0x9c, ImpliedModify, fp(DEC), A) + op(0x9d, Transfer, S, X) + op(0x9e, Divide) + op(0x9f, ExchangeNibble) + op(0xa0, FlagSet, IF, true) + op(0xa1, CallTable, 10) + op(0xa2, AbsoluteBitSet, 5, true) + op(0xa3, BranchBit, 5, true) + op(0xa4, DirectRead, fp(SBC), A) + op(0xa5, AbsoluteRead, fp(SBC), A) + op(0xa6, IndirectXRead, fp(SBC)) + op(0xa7, IndexedIndirectRead, fp(SBC), X) + op(0xa8, ImmediateRead, fp(SBC), A) + op(0xa9, DirectDirectModify, fp(SBC)) + op(0xaa, AbsoluteBitModify, 5) + op(0xab, DirectModify, fp(INC)) + op(0xac, AbsoluteModify, fp(INC)) + op(0xad, ImmediateRead, fp(CMP), Y) + op(0xae, Pull, A) + op(0xaf, IndirectXIncrementWrite, A) + op(0xb0, Branch, CF == 1) + op(0xb1, CallTable, 11) + op(0xb2, AbsoluteBitSet, 5, false) + op(0xb3, BranchBit, 5, false) + op(0xb4, DirectIndexedRead, fp(SBC), A, X) + op(0xb5, AbsoluteIndexedRead, fp(SBC), X) + op(0xb6, AbsoluteIndexedRead, fp(SBC), Y) + op(0xb7, IndirectIndexedRead, fp(SBC), Y) + op(0xb8, DirectImmediateModify, fp(SBC)) + op(0xb9, IndirectXWriteIndirectY, fp(SBC)) + op(0xba, DirectReadWord, fp(LDW)) + op(0xbb, DirectIndexedModify, fp(INC), X) + op(0xbc, ImpliedModify, fp(INC), A) + op(0xbd, Transfer, X, S) + op(0xbe, DecimalAdjustSub) + op(0xbf, IndirectXIncrementRead, A) + op(0xc0, FlagSet, IF, false) + op(0xc1, CallTable, 12) + op(0xc2, AbsoluteBitSet, 6, true) + op(0xc3, BranchBit, 6, true) + op(0xc4, DirectWrite, A) + op(0xc5, AbsoluteWrite, A) + op(0xc6, IndirectXWrite, A) + op(0xc7, IndexedIndirectWrite, A, X) + op(0xc8, ImmediateRead, fp(CMP), X) + op(0xc9, AbsoluteWrite, X) + op(0xca, AbsoluteBitModify, 6) + op(0xcb, DirectWrite, Y) + op(0xcc, AbsoluteWrite, Y) + op(0xcd, ImmediateRead, fp(LD), X) + op(0xce, Pull, X) + op(0xcf, Multiply) + op(0xd0, Branch, ZF == 0) + op(0xd1, CallTable, 13) + op(0xd2, AbsoluteBitSet, 6, false) + op(0xd3, BranchBit, 6, false) + op(0xd4, DirectIndexedWrite, A, X) + op(0xd5, AbsoluteIndexedWrite, X) + op(0xd6, AbsoluteIndexedWrite, Y) + op(0xd7, IndirectIndexedWrite, A, Y) + op(0xd8, DirectWrite, X) + op(0xd9, DirectIndexedWrite, X, Y) + op(0xda, DirectWriteWord) + op(0xdb, DirectIndexedWrite, Y, X) + op(0xdc, ImpliedModify, fp(DEC), Y) + op(0xdd, Transfer, Y, A) + op(0xde, BranchNotDirectIndexed, X) + op(0xdf, DecimalAdjustAdd) + op(0xe0, OverflowClear) + op(0xe1, CallTable, 14) + op(0xe2, AbsoluteBitSet, 7, true) + op(0xe3, BranchBit, 7, true) + op(0xe4, DirectRead, fp(LD), A) + op(0xe5, AbsoluteRead, fp(LD), A) + op(0xe6, IndirectXRead, fp(LD)) + op(0xe7, IndexedIndirectRead, fp(LD), X) + op(0xe8, ImmediateRead, fp(LD), A) + op(0xe9, AbsoluteRead, fp(LD), X) + op(0xea, AbsoluteBitModify, 7) + op(0xeb, DirectRead, fp(LD), Y) + op(0xec, AbsoluteRead, fp(LD), Y) + op(0xed, ComplementCarry) + op(0xee, Pull, Y) + op(0xef, Wait) + op(0xf0, Branch, ZF == 1) + op(0xf1, CallTable, 15) + op(0xf2, AbsoluteBitSet, 7, false) + op(0xf3, BranchBit, 7, false) + op(0xf4, DirectIndexedRead, fp(LD), A, X) + op(0xf5, AbsoluteIndexedRead, fp(LD), X) + op(0xf6, AbsoluteIndexedRead, fp(LD), Y) + op(0xf7, IndirectIndexedRead, fp(LD), Y) + op(0xf8, DirectRead, fp(LD), X) + op(0xf9, DirectIndexedRead, fp(LD), X, Y) + op(0xfa, DirectDirectWrite) + op(0xfb, DirectIndexedRead, fp(LD), Y, X) + op(0xfc, ImpliedModify, fp(INC), Y) + op(0xfd, Transfer, A, Y) + op(0xfe, BranchNotYDecrement) + op(0xff, Stop) + } +} + +#undef op +#undef fp diff --git a/waterbox/bsnescore/bsnes/processor/spc700/instructions.cpp b/waterbox/bsnescore/bsnes/processor/spc700/instructions.cpp new file mode 100644 index 0000000000..d7844893e2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/spc700/instructions.cpp @@ -0,0 +1,592 @@ +auto SPC700::instructionAbsoluteBitModify(uint3 mode) -> void { + uint16 address = fetch(); + address |= fetch() << 8; + uint3 bit = address >> 13; + address &= 0x1fff; + uint8 data = read(address); + switch(mode) { + case 0: //or addr:bit + idle(); + CF |= bool(data & 1 << bit); + break; + case 1: //or !addr:bit + idle(); + CF |= !bool(data & 1 << bit); + break; + case 2: //and addr:bit + CF &= bool(data & 1 << bit); + break; + case 3: //and !addr:bit + CF &= !bool(data & 1 << bit); + break; + case 4: //eor addr:bit + idle(); + CF ^= bool(data & 1 << bit); + break; + case 5: //ld addr:bit + CF = bool(data & 1 << bit); + break; + case 6: //st addr:bit + idle(); + data &= ~(1 << bit); + data |= CF << bit; + write(address, data); + break; + case 7: //not addr:bit + data ^= 1 << bit; + write(address, data); + break; + } +} + +auto SPC700::instructionAbsoluteBitSet(uint3 bit, bool value) -> void { + uint8 address = fetch(); + uint8 data = load(address); + data &= ~(1 << bit); + data |= value << bit; + store(address, data); +} + +auto SPC700::instructionAbsoluteRead(fpb op, uint8& target) -> void { + uint16 address = fetch(); + address |= fetch() << 8; + uint8 data = read(address); + target = alu(target, data); +} + +auto SPC700::instructionAbsoluteModify(fps op) -> void { + uint16 address = fetch(); + address |= fetch() << 8; + uint8 data = read(address); + write(address, alu(data)); +} + +auto SPC700::instructionAbsoluteWrite(uint8& data) -> void { + uint16 address = fetch(); + address |= fetch() << 8; + read(address); + write(address, data); +} + +auto SPC700::instructionAbsoluteIndexedRead(fpb op, uint8& index) -> void { + uint16 address = fetch(); + address |= fetch() << 8; + idle(); + uint8 data = read(address + index); + A = alu(A, data); +} + +auto SPC700::instructionAbsoluteIndexedWrite(uint8& index) -> void { + uint16 address = fetch(); + address |= fetch() << 8; + idle(); + read(address + index); + write(address + index, A); +} + +auto SPC700::instructionBranch(bool take) -> void { + uint8 data = fetch(); + if(!take) return; + idle(); + idle(); + PC += (int8)data; +} + +auto SPC700::instructionBranchBit(uint3 bit, bool match) -> void { + uint8 address = fetch(); + uint8 data = load(address); + idle(); + uint8 displacement = fetch(); + if(bool(data & 1 << bit) != match) return; + idle(); + idle(); + PC += (int8)displacement; +} + +auto SPC700::instructionBranchNotDirect() -> void { + uint8 address = fetch(); + uint8 data = load(address); + idle(); + uint8 displacement = fetch(); + if(A == data) return; + idle(); + idle(); + PC += (int8)displacement; +} + +auto SPC700::instructionBranchNotDirectDecrement() -> void { + uint8 address = fetch(); + uint8 data = load(address); + store(address, --data); + uint8 displacement = fetch(); + if(data == 0) return; + idle(); + idle(); + PC += (int8)displacement; +} + +auto SPC700::instructionBranchNotDirectIndexed(uint8& index) -> void { + uint8 address = fetch(); + idle(); + uint8 data = load(address + index); + idle(); + uint8 displacement = fetch(); + if(A == data) return; + idle(); + idle(); + PC += (int8)displacement; +} + +auto SPC700::instructionBranchNotYDecrement() -> void { + read(PC); + idle(); + uint8 displacement = fetch(); + if(--Y == 0) return; + idle(); + idle(); + PC += (int8)displacement; +} + +auto SPC700::instructionBreak() -> void { + read(PC); + push(PC >> 8); + push(PC >> 0); + push(P); + idle(); + uint16 address = read(0xffde + 0); + address |= read(0xffde + 1) << 8; + PC = address; + IF = 0; + BF = 1; +} + +auto SPC700::instructionCallAbsolute() -> void { + uint16 address = fetch(); + address |= fetch() << 8; + idle(); + push(PC >> 8); + push(PC >> 0); + idle(); + idle(); + PC = address; +} + +auto SPC700::instructionCallPage() -> void { + uint8 address = fetch(); + idle(); + push(PC >> 8); + push(PC >> 0); + idle(); + PC = 0xff00 | address; +} + +auto SPC700::instructionCallTable(uint4 vector) -> void { + read(PC); + idle(); + push(PC >> 8); + push(PC >> 0); + idle(); + uint16 address = 0xffde - (vector << 1); + uint16 pc = read(address + 0); + pc |= read(address + 1) << 8; + PC = pc; +} + +auto SPC700::instructionComplementCarry() -> void { + read(PC); + idle(); + CF = !CF; +} + +auto SPC700::instructionDecimalAdjustAdd() -> void { + read(PC); + idle(); + if(CF || A > 0x99) { + A += 0x60; + CF = 1; + } + if(HF || (A & 15) > 0x09) { + A += 0x06; + } + ZF = A == 0; + NF = A & 0x80; +} + +auto SPC700::instructionDecimalAdjustSub() -> void { + read(PC); + idle(); + if(!CF || A > 0x99) { + A -= 0x60; + CF = 0; + } + if(!HF || (A & 15) > 0x09) { + A -= 0x06; + } + ZF = A == 0; + NF = A & 0x80; +} + +auto SPC700::instructionDirectRead(fpb op, uint8& target) -> void { + uint8 address = fetch(); + uint8 data = load(address); + target = alu(target, data); +} + +auto SPC700::instructionDirectModify(fps op) -> void { + uint8 address = fetch(); + uint8 data = load(address); + store(address, alu(data)); +} + +auto SPC700::instructionDirectWrite(uint8& data) -> void { + uint8 address = fetch(); + load(address); + store(address, data); +} + +auto SPC700::instructionDirectDirectCompare(fpb op) -> void { + uint8 source = fetch(); + uint8 rhs = load(source); + uint8 target = fetch(); + uint8 lhs = load(target); + lhs = alu(lhs, rhs); + idle(); +} + +auto SPC700::instructionDirectDirectModify(fpb op) -> void { + uint8 source = fetch(); + uint8 rhs = load(source); + uint8 target = fetch(); + uint8 lhs = load(target); + lhs = alu(lhs, rhs); + store(target, lhs); +} + +auto SPC700::instructionDirectDirectWrite() -> void { + uint8 source = fetch(); + uint8 data = load(source); + uint8 target = fetch(); + store(target, data); +} + +auto SPC700::instructionDirectImmediateCompare(fpb op) -> void { + uint8 immediate = fetch(); + uint8 address = fetch(); + uint8 data = load(address); + data = alu(data, immediate); + idle(); +} + +auto SPC700::instructionDirectImmediateModify(fpb op) -> void { + uint8 immediate = fetch(); + uint8 address = fetch(); + uint8 data = load(address); + data = alu(data, immediate); + store(address, data); +} + +auto SPC700::instructionDirectImmediateWrite() -> void { + uint8 immediate = fetch(); + uint8 address = fetch(); + load(address); + store(address, immediate); +} + +auto SPC700::instructionDirectCompareWord(fpw op) -> void { + uint8 address = fetch(); + uint16 data = load(address + 0); + data |= load(address + 1) << 8; + YA = alu(YA, data); +} + +auto SPC700::instructionDirectReadWord(fpw op) -> void { + uint8 address = fetch(); + uint16 data = load(address + 0); + idle(); + data |= load(address + 1) << 8; + YA = alu(YA, data); +} + +auto SPC700::instructionDirectModifyWord(int adjust) -> void { + uint8 address = fetch(); + uint16 data = load(address + 0) + adjust; + store(address + 0, data >> 0); + data += load(address + 1) << 8; + store(address + 1, data >> 8); + ZF = data == 0; + NF = data & 0x8000; +} + +auto SPC700::instructionDirectWriteWord() -> void { + uint8 address = fetch(); + load(address + 0); + store(address + 0, A); + store(address + 1, Y); +} + +auto SPC700::instructionDirectIndexedRead(fpb op, uint8& target, uint8& index) -> void { + uint8 address = fetch(); + idle(); + uint8 data = load(address + index); + target = alu(target, data); +} + +auto SPC700::instructionDirectIndexedModify(fps op, uint8& index) -> void { + uint8 address = fetch(); + idle(); + uint8 data = load(address + index); + store(address + index, alu(data)); +} + +auto SPC700::instructionDirectIndexedWrite(uint8& data, uint8& index) -> void { + uint8 address = fetch(); + idle(); + load(address + index); + store(address + index, data); +} + +auto SPC700::instructionDivide() -> void { + read(PC); + idle(); + idle(); + idle(); + idle(); + idle(); + idle(); + idle(); + idle(); + idle(); + idle(); + uint16 ya = YA; + //overflow set if quotient >= 256 + HF = (Y & 15) >= (X & 15); + VF = Y >= X; + if(Y < (X << 1)) { + //if quotient is <= 511 (will fit into 9-bit result) + A = ya / X; + Y = ya % X; + } else { + //otherwise, the quotient won't fit into VF + A + //this emulates the odd behavior of the S-SMP in this case + A = 255 - (ya - (X << 9)) / (256 - X); + Y = X + (ya - (X << 9)) % (256 - X); + } + //result is set based on a (quotient) only + ZF = A == 0; + NF = A & 0x80; +} + +auto SPC700::instructionExchangeNibble() -> void { + read(PC); + idle(); + idle(); + idle(); + A = A >> 4 | A << 4; + ZF = A == 0; + NF = A & 0x80; +} + +auto SPC700::instructionFlagSet(bool& flag, bool value) -> void { + read(PC); + if(&flag == &IF) idle(); + flag = value; +} + +auto SPC700::instructionImmediateRead(fpb op, uint8& target) -> void { + uint8 data = fetch(); + target = alu(target, data); +} + +auto SPC700::instructionImpliedModify(fps op, uint8& target) -> void { + read(PC); + target = alu(target); +} + +auto SPC700::instructionIndexedIndirectRead(fpb op, uint8& index) -> void { + uint8 indirect = fetch(); + idle(); + uint16 address = load(indirect + index + 0); + address |= load(indirect + index + 1) << 8; + uint8 data = read(address); + A = alu(A, data); +} + +auto SPC700::instructionIndexedIndirectWrite(uint8& data, uint8& index) -> void { + uint8 indirect = fetch(); + idle(); + uint16 address = load(indirect + index + 0); + address |= load(indirect + index + 1) << 8; + read(address); + write(address, data); +} + +auto SPC700::instructionIndirectIndexedRead(fpb op, uint8& index) -> void { + uint8 indirect = fetch(); + uint16 address = load(indirect + 0); + address |= load(indirect + 1) << 8; + idle(); + uint8 data = read(address + index); + A = alu(A, data); +} + +auto SPC700::instructionIndirectIndexedWrite(uint8& data, uint8& index) -> void { + uint8 indirect = fetch(); + uint16 address = load(indirect + 0); + address |= load(indirect + 1) << 8; + idle(); + read(address + index); + write(address + index, data); +} + +auto SPC700::instructionIndirectXRead(fpb op) -> void { + read(PC); + uint8 data = load(X); + A = alu(A, data); +} + +auto SPC700::instructionIndirectXWrite(uint8& data) -> void { + read(PC); + load(X); + store(X, data); +} + +auto SPC700::instructionIndirectXIncrementRead(uint8& data) -> void { + read(PC); + data = load(X++); + idle(); //quirk: consumes extra idle cycle compared to most read instructions + ZF = data == 0; + NF = data & 0x80; +} + +auto SPC700::instructionIndirectXIncrementWrite(uint8& data) -> void { + read(PC); + idle(); //quirk: not a read cycle as with most write instructions + store(X++, data); +} + +auto SPC700::instructionIndirectXCompareIndirectY(fpb op) -> void { + read(PC); + uint8 rhs = load(Y); + uint8 lhs = load(X); + lhs = alu(lhs, rhs); + idle(); +} + +auto SPC700::instructionIndirectXWriteIndirectY(fpb op) -> void { + read(PC); + uint8 rhs = load(Y); + uint8 lhs = load(X); + lhs = alu(lhs, rhs); + store(X, lhs); +} + +auto SPC700::instructionJumpAbsolute() -> void { + uint16 address = fetch(); + address |= fetch() << 8; + PC = address; +} + +auto SPC700::instructionJumpIndirectX() -> void { + uint16 address = fetch(); + address |= fetch() << 8; + idle(); + uint16 pc = read(address + X + 0); + pc |= read(address + X + 1) << 8; + PC = pc; +} + +auto SPC700::instructionMultiply() -> void { + read(PC); + idle(); + idle(); + idle(); + idle(); + idle(); + idle(); + idle(); + uint16 ya = Y * A; + A = ya >> 0; + Y = ya >> 8; + //result is set based on y (high-byte) only + ZF = Y == 0; + NF = Y & 0x80; +} + +auto SPC700::instructionNoOperation() -> void { + read(PC); +} + +auto SPC700::instructionOverflowClear() -> void { + read(PC); + HF = 0; + VF = 0; +} + +auto SPC700::instructionPull(uint8& data) -> void { + read(PC); + idle(); + data = pull(); +} + +auto SPC700::instructionPullP() -> void { + read(PC); + idle(); + P = pull(); +} + +auto SPC700::instructionPush(uint8 data) -> void { + read(PC); + push(data); + idle(); +} + +auto SPC700::instructionReturnInterrupt() -> void { + read(PC); + idle(); + P = pull(); + uint16 address = pull(); + address |= pull() << 8; + PC = address; +} + +auto SPC700::instructionReturnSubroutine() -> void { + read(PC); + idle(); + uint16 address = pull(); + address |= pull() << 8; + PC = address; +} + +auto SPC700::instructionStop() -> void { + r.stop = true; + while(r.stop && !synchronizing()) { + read(PC); + idle(); + } +} + +auto SPC700::instructionTestSetBitsAbsolute(bool set) -> void { + uint16 address = fetch(); + address |= fetch() << 8; + uint8 data = read(address); + ZF = (A - data) == 0; + NF = (A - data) & 0x80; + read(address); + write(address, set ? data | A : data & ~A); +} + +auto SPC700::instructionTransfer(uint8& from, uint8& to) -> void { + read(PC); + to = from; + if(&to == &S) return; + ZF = to == 0; + NF = to & 0x80; +} + +auto SPC700::instructionWait() -> void { + r.wait = true; + while(r.wait && !synchronizing()) { + read(PC); + idle(); + } +} diff --git a/waterbox/bsnescore/bsnes/processor/spc700/memory.cpp b/waterbox/bsnescore/bsnes/processor/spc700/memory.cpp new file mode 100644 index 0000000000..3c0f92a74f --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/spc700/memory.cpp @@ -0,0 +1,19 @@ +auto SPC700::fetch() -> uint8 { + return read(PC++); +} + +auto SPC700::load(uint8 address) -> uint8 { + return read(PF << 8 | address); +} + +auto SPC700::store(uint8 address, uint8 data) -> void { + return write(PF << 8 | address, data); +} + +auto SPC700::pull() -> uint8 { + return read(1 << 8 | ++S); +} + +auto SPC700::push(uint8 data) -> void { + return write(1 << 8 | S--, data); +} diff --git a/waterbox/bsnescore/bsnes/processor/spc700/serialization.cpp b/waterbox/bsnescore/bsnes/processor/spc700/serialization.cpp new file mode 100644 index 0000000000..2c258f5626 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/spc700/serialization.cpp @@ -0,0 +1,17 @@ +auto SPC700::serialize(serializer& s) -> void { + s.integer(r.pc.w); + s.integer(r.ya.w); + s.integer(r.x); + s.integer(r.s); + s.integer(r.p.c); + s.integer(r.p.z); + s.integer(r.p.i); + s.integer(r.p.h); + s.integer(r.p.b); + s.integer(r.p.p); + s.integer(r.p.v); + s.integer(r.p.n); + + s.integer(r.wait); + s.integer(r.stop); +} diff --git a/waterbox/bsnescore/bsnes/processor/spc700/spc700.cpp b/waterbox/bsnescore/bsnes/processor/spc700/spc700.cpp new file mode 100644 index 0000000000..69ec693cad --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/spc700/spc700.cpp @@ -0,0 +1,62 @@ +#include +#include "spc700.hpp" + +namespace Processor { + +#define PC r.pc.w +#define YA r.ya.w +#define A r.ya.byte.l +#define X r.x +#define Y r.ya.byte.h +#define S r.s +#define P r.p + +#define CF r.p.c +#define ZF r.p.z +#define IF r.p.i +#define HF r.p.h +#define BF r.p.b +#define PF r.p.p +#define VF r.p.v +#define NF r.p.n + +#define alu (this->*op) + +#include "memory.cpp" +#include "algorithms.cpp" +#include "instructions.cpp" +#include "instruction.cpp" +#include "serialization.cpp" +#include "disassembler.cpp" + +auto SPC700::power() -> void { + PC = 0x0000; + YA = 0x0000; + X = 0x00; + S = 0xef; + P = 0x02; + + r.wait = false; + r.stop = false; +} + +#undef PC +#undef YA +#undef A +#undef X +#undef Y +#undef S +#undef P + +#undef CF +#undef ZF +#undef IF +#undef HF +#undef BF +#undef PF +#undef VF +#undef NF + +#undef alu + +} diff --git a/waterbox/bsnescore/bsnes/processor/spc700/spc700.hpp b/waterbox/bsnescore/bsnes/processor/spc700/spc700.hpp new file mode 100644 index 0000000000..788e036588 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/spc700/spc700.hpp @@ -0,0 +1,164 @@ +#pragma once + +namespace Processor { + +struct SPC700 { + virtual auto idle() -> void = 0; + virtual auto read(uint16 address) -> uint8 = 0; + virtual auto write(uint16 address, uint8 data) -> void = 0; + virtual auto synchronizing() const -> bool = 0; + + virtual auto readDisassembler(uint16 address) -> uint8 { return 0; } + + //spc700.cpp + auto power() -> void; + + //memory.cpp + inline auto fetch() -> uint8; + inline auto load(uint8 address) -> uint8; + inline auto store(uint8 address, uint8 data) -> void; + inline auto pull() -> uint8; + inline auto push(uint8 data) -> void; + + //instruction.cpp + auto instruction() -> void; + + //algorithms.cpp + auto algorithmADC(uint8, uint8) -> uint8; + auto algorithmAND(uint8, uint8) -> uint8; + auto algorithmASL(uint8) -> uint8; + auto algorithmCMP(uint8, uint8) -> uint8; + auto algorithmDEC(uint8) -> uint8; + auto algorithmEOR(uint8, uint8) -> uint8; + auto algorithmINC(uint8) -> uint8; + auto algorithmLD (uint8, uint8) -> uint8; + auto algorithmLSR(uint8) -> uint8; + auto algorithmOR (uint8, uint8) -> uint8; + auto algorithmROL(uint8) -> uint8; + auto algorithmROR(uint8) -> uint8; + auto algorithmSBC(uint8, uint8) -> uint8; + auto algorithmADW(uint16, uint16) -> uint16; + auto algorithmCPW(uint16, uint16) -> uint16; + auto algorithmLDW(uint16, uint16) -> uint16; + auto algorithmSBW(uint16, uint16) -> uint16; + + //instructions.cpp + using fps = auto (SPC700::*)(uint8) -> uint8; + using fpb = auto (SPC700::*)(uint8, uint8) -> uint8; + using fpw = auto (SPC700::*)(uint16, uint16) -> uint16; + + auto instructionAbsoluteBitModify(uint3) -> void; + auto instructionAbsoluteBitSet(uint3, bool) -> void; + auto instructionAbsoluteRead(fpb, uint8&) -> void; + auto instructionAbsoluteModify(fps) -> void; + auto instructionAbsoluteWrite(uint8&) -> void; + auto instructionAbsoluteIndexedRead(fpb, uint8&) -> void; + auto instructionAbsoluteIndexedWrite(uint8&) -> void; + auto instructionBranch(bool) -> void; + auto instructionBranchBit(uint3, bool) -> void; + auto instructionBranchNotDirect() -> void; + auto instructionBranchNotDirectDecrement() -> void; + auto instructionBranchNotDirectIndexed(uint8&) -> void; + auto instructionBranchNotYDecrement() -> void; + auto instructionBreak() -> void; + auto instructionCallAbsolute() -> void; + auto instructionCallPage() -> void; + auto instructionCallTable(uint4) -> void; + auto instructionComplementCarry() -> void; + auto instructionDecimalAdjustAdd() -> void; + auto instructionDecimalAdjustSub() -> void; + auto instructionDirectRead(fpb, uint8&) -> void; + auto instructionDirectModify(fps) -> void; + auto instructionDirectWrite(uint8&) -> void; + auto instructionDirectDirectCompare(fpb) -> void; + auto instructionDirectDirectModify(fpb) -> void; + auto instructionDirectDirectWrite() -> void; + auto instructionDirectImmediateCompare(fpb) -> void; + auto instructionDirectImmediateModify(fpb) -> void; + auto instructionDirectImmediateWrite() -> void; + auto instructionDirectCompareWord(fpw) -> void; + auto instructionDirectReadWord(fpw) -> void; + auto instructionDirectModifyWord(int) -> void; + auto instructionDirectWriteWord() -> void; + auto instructionDirectIndexedRead(fpb, uint8&, uint8&) -> void; + auto instructionDirectIndexedModify(fps, uint8&) -> void; + auto instructionDirectIndexedWrite(uint8&, uint8&) -> void; + auto instructionDivide() -> void; + auto instructionExchangeNibble() -> void; + auto instructionFlagSet(bool&, bool) -> void; + auto instructionImmediateRead(fpb, uint8&) -> void; + auto instructionImpliedModify(fps, uint8&) -> void; + auto instructionIndexedIndirectRead(fpb, uint8&) -> void; + auto instructionIndexedIndirectWrite(uint8&, uint8&) -> void; + auto instructionIndirectIndexedRead(fpb, uint8&) -> void; + auto instructionIndirectIndexedWrite(uint8&, uint8&) -> void; + auto instructionIndirectXRead(fpb) -> void; + auto instructionIndirectXWrite(uint8&) -> void; + auto instructionIndirectXIncrementRead(uint8&) -> void; + auto instructionIndirectXIncrementWrite(uint8&) -> void; + auto instructionIndirectXCompareIndirectY(fpb) -> void; + auto instructionIndirectXWriteIndirectY(fpb) -> void; + auto instructionJumpAbsolute() -> void; + auto instructionJumpIndirectX() -> void; + auto instructionMultiply() -> void; + auto instructionNoOperation() -> void; + auto instructionOverflowClear() -> void; + auto instructionPull(uint8&) -> void; + auto instructionPullP() -> void; + auto instructionPush(uint8) -> void; + auto instructionReturnInterrupt() -> void; + auto instructionReturnSubroutine() -> void; + auto instructionStop() -> void; + auto instructionTestSetBitsAbsolute(bool) -> void; + auto instructionTransfer(uint8&, uint8&) -> void; + auto instructionWait() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + //disassembler.cpp + auto disassemble(uint16 address, bool p) -> string; + + struct Flags { + bool c = 0; //carry + bool z = 0; //zero + bool i = 0; //interrupt disable + bool h = 0; //half-carry + bool b = 0; //break + bool p = 0; //page + bool v = 0; //overflow + bool n = 0; //negative + + inline operator uint() const { + return c << 0 | z << 1 | i << 2 | h << 3 | b << 4 | p << 5 | v << 6 | n << 7; + } + + inline auto& operator=(uint8 data) { + c = data & 0x01; + z = data & 0x02; + i = data & 0x04; + h = data & 0x08; + b = data & 0x10; + p = data & 0x20; + v = data & 0x40; + n = data & 0x80; + return *this; + } + }; + + struct Registers { + union Pair { + Pair() : w(0) {} + uint16 w; + struct Byte { uint8 order_lsb2(l, h); } byte; + } pc, ya; + uint8 x = 0; + uint8 s = 0; + Flags p; + + bool wait = 0; + bool stop = 0; + } r; +}; + +} diff --git a/waterbox/bsnescore/bsnes/processor/upd96050/disassembler.cpp b/waterbox/bsnescore/bsnes/processor/upd96050/disassembler.cpp new file mode 100644 index 0000000000..8fb645201b --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/upd96050/disassembler.cpp @@ -0,0 +1,192 @@ +auto uPD96050::disassemble(uint14 ip) -> string { + string output = {hex(ip, 4L), " "}; + uint24 opcode = programROM[ip]; + uint2 type = opcode >> 22; + + if(type == 0 || type == 1) { //OP,RT + uint2 pselect = opcode >> 20; + uint4 alu = opcode >> 16; + uint1 asl = opcode >> 15; + uint2 dpl = opcode >> 13; + uint4 dphm = opcode >> 9; + uint1 rpdcr = opcode >> 8; + uint4 src = opcode >> 4; + uint4 dst = opcode >> 0; + + switch(alu) { + case 0: output.append("nop "); break; + case 1: output.append("or "); break; + case 2: output.append("and "); break; + case 3: output.append("xor "); break; + case 4: output.append("sub "); break; + case 5: output.append("add "); break; + case 6: output.append("sbb "); break; + case 7: output.append("adc "); break; + case 8: output.append("dec "); break; + case 9: output.append("inc "); break; + case 10: output.append("cmp "); break; + case 11: output.append("shr1 "); break; + case 12: output.append("shl1 "); break; + case 13: output.append("shl2 "); break; + case 14: output.append("shl4 "); break; + case 15: output.append("xchg "); break; + } + + if(alu < 8) { + switch(pselect) { + case 0: output.append("ram,"); break; + case 1: output.append("idb,"); break; + case 2: output.append("m," ); break; + case 3: output.append("n," ); break; + } + } + + switch(asl) { + case 0: output.append("a"); break; + case 1: output.append("b"); break; + } + + output.append("\n mov "); + + switch(src) { + case 0: output.append("trb," ); break; + case 1: output.append("a," ); break; + case 2: output.append("b," ); break; + case 3: output.append("tr," ); break; + case 4: output.append("dp," ); break; + case 5: output.append("rp," ); break; + case 6: output.append("ro," ); break; + case 7: output.append("sgn," ); break; + case 8: output.append("dr," ); break; + case 9: output.append("drnf,"); break; + case 10: output.append("sr," ); break; + case 11: output.append("sim," ); break; + case 12: output.append("sil," ); break; + case 13: output.append("k," ); break; + case 14: output.append("l," ); break; + case 15: output.append("mem," ); break; + } + + switch(dst) { + case 0: output.append("non"); break; + case 1: output.append("a" ); break; + case 2: output.append("b" ); break; + case 3: output.append("tr" ); break; + case 4: output.append("dp" ); break; + case 5: output.append("rp" ); break; + case 6: output.append("dr" ); break; + case 7: output.append("sr" ); break; + case 8: output.append("sol"); break; + case 9: output.append("som"); break; + case 10: output.append("k" ); break; + case 11: output.append("klr"); break; + case 12: output.append("klm"); break; + case 13: output.append("l" ); break; + case 14: output.append("trb"); break; + case 15: output.append("mem"); break; + } + + if(dpl) { + switch(dpl) { + case 0: output.append("\n dpnop"); break; + case 1: output.append("\n dpinc"); break; + case 2: output.append("\n dpdec"); break; + case 3: output.append("\n dpclr"); break; + } + } + + if(dphm) { + output.append("\n m", hex(dphm, 1L)); + } + + if(rpdcr == 1) { + output.append("\n rpdec"); + } + + if(type == 1) { + output.append("\n ret"); + } + } + + if(type == 2) { //JP + uint9 brch = opcode >> 13; + uint11 na = opcode >> 2; + uint8 bank = opcode >> 0; + + uint14 jp = (regs.pc & 0x2000) | (bank << 11) | (na << 0); + + switch(brch) { + case 0x000: output.append("jmpso "); jp = 0; break; + case 0x080: output.append("jnca "); break; + case 0x082: output.append("jca "); break; + case 0x084: output.append("jncb "); break; + case 0x086: output.append("jcb "); break; + case 0x088: output.append("jnza "); break; + case 0x08a: output.append("jza "); break; + case 0x08c: output.append("jnzb "); break; + case 0x08e: output.append("jzb "); break; + case 0x090: output.append("jnova0 "); break; + case 0x092: output.append("jova0 "); break; + case 0x094: output.append("jnovb0 "); break; + case 0x096: output.append("jovb0 "); break; + case 0x098: output.append("jnova1 "); break; + case 0x09a: output.append("jova1 "); break; + case 0x09c: output.append("jnovb1 "); break; + case 0x09e: output.append("jovb1 "); break; + case 0x0a0: output.append("jnsa0 "); break; + case 0x0a2: output.append("jsa0 "); break; + case 0x0a4: output.append("jnsb0 "); break; + case 0x0a6: output.append("jsb0 "); break; + case 0x0a8: output.append("jnsa1 "); break; + case 0x0aa: output.append("jsa1 "); break; + case 0x0ac: output.append("jnsb1 "); break; + case 0x0ae: output.append("jsb1 "); break; + case 0x0b0: output.append("jdpl0 "); break; + case 0x0b1: output.append("jdpln0 "); break; + case 0x0b2: output.append("jdplf "); break; + case 0x0b3: output.append("jdplnf "); break; + case 0x0b4: output.append("jnsiak "); break; + case 0x0b6: output.append("jsiak "); break; + case 0x0b8: output.append("jnsoak "); break; + case 0x0ba: output.append("jsoak "); break; + case 0x0bc: output.append("jnrqm "); break; + case 0x0be: output.append("jrqm "); break; + case 0x100: output.append("ljmp "); jp &= ~0x2000; break; + case 0x101: output.append("hjmp "); jp |= 0x2000; break; + case 0x140: output.append("lcall "); jp &= ~0x2000; break; + case 0x141: output.append("hcall "); jp |= 0x2000; break; + default: output.append("?????? "); break; + } + + output.append("$", hex(jp, 4L)); + } + + if(type == 3) { //LD + output.append("ld "); + uint16 id = opcode >> 6; + uint4 dst = opcode >> 0; + + output.append("$", hex(id, 4L), ","); + + switch(dst) { + case 0: output.append("non"); break; + case 1: output.append("a" ); break; + case 2: output.append("b" ); break; + case 3: output.append("tr" ); break; + case 4: output.append("dp" ); break; + case 5: output.append("rp" ); break; + case 6: output.append("dr" ); break; + case 7: output.append("sr" ); break; + case 8: output.append("sol"); break; + case 9: output.append("som"); break; + case 10: output.append("k" ); break; + case 11: output.append("klr"); break; + case 12: output.append("klm"); break; + case 13: output.append("l" ); break; + case 14: output.append("trb"); break; + case 15: output.append("mem"); break; + } + } + + return output; +} diff --git a/waterbox/bsnescore/bsnes/processor/upd96050/instructions.cpp b/waterbox/bsnescore/bsnes/processor/upd96050/instructions.cpp new file mode 100644 index 0000000000..5ac63a8448 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/upd96050/instructions.cpp @@ -0,0 +1,245 @@ +auto uPD96050::exec() -> void { + uint24 opcode = programROM[regs.pc++]; + switch(opcode >> 22) { + case 0: execOP(opcode); break; + case 1: execRT(opcode); break; + case 2: execJP(opcode); break; + case 3: execLD(opcode); break; + } + + int32 result = (int32)regs.k * regs.l; //sign + 30-bit result + regs.m = result >> 15; //store sign + top 15-bits + regs.n = result << 1; //store low 15-bits + zero +} + +auto uPD96050::execOP(uint24 opcode) -> void { + uint2 pselect = opcode >> 20; //P select + uint4 alu = opcode >> 16; //ALU operation mode + uint1 asl = opcode >> 15; //accumulator select + uint2 dpl = opcode >> 13; //DP low modify + uint4 dphm = opcode >> 9; //DP high XOR modify + uint1 rpdcr = opcode >> 8; //RP decrement + uint4 src = opcode >> 4; //move source + uint4 dst = opcode >> 0; //move destination + + uint16 idb; + switch(src) { + case 0: idb = regs.trb; break; + case 1: idb = regs.a; break; + case 2: idb = regs.b; break; + case 3: idb = regs.tr; break; + case 4: idb = regs.dp; break; + case 5: idb = regs.rp; break; + case 6: idb = dataROM[regs.rp]; break; + case 7: idb = 0x8000 - flags.a.s1; break; //ASL ignored; always SA1 + case 8: idb = regs.dr; regs.sr.rqm = 1; break; + case 9: idb = regs.dr; break; + case 10: idb = regs.sr; break; + case 11: idb = regs.si; break; //MSB + case 12: idb = regs.si; break; //LSB + case 13: idb = regs.k; break; + case 14: idb = regs.l; break; + case 15: idb = dataRAM[regs.dp]; break; + } + + if(alu) { + uint16 p, q, r; + Flag flag; + boolean c; + + switch(pselect) { + case 0: p = dataRAM[regs.dp]; break; + case 1: p = idb; break; + case 2: p = regs.m; break; + case 3: p = regs.n; break; + } + + switch(asl) { + case 0: q = regs.a; flag = flags.a; c = flags.b.c; break; + case 1: q = regs.b; flag = flags.b; c = flags.a.c; break; + } + + switch(alu) { + case 1: r = q | p; break; //OR + case 2: r = q & p; break; //AND + case 3: r = q ^ p; break; //XOR + case 4: r = q - p; break; //SUB + case 5: r = q + p; break; //ADD + case 6: r = q - p - c; break; //SBB + case 7: r = q + p + c; break; //ADC + case 8: r = q - 1; p = 1; break; //DEC + case 9: r = q + 1; p = 1; break; //INC + case 10: r = ~q; break; //CMP + case 11: r = q >> 1 | q & 0x8000; break; //SHR1 (ASR) + case 12: r = q << 1 | c; break; //SHL1 (ROL) + case 13: r = q << 2 | 3; break; //SHL2 + case 14: r = q << 4 | 15; break; //SHL4 + case 15: r = q << 8 | q >> 8; break; //XCHG + } + + flag.z = r == 0; + flag.s0 = r & 0x8000; + if(!flag.ov1) flag.s1 = flag.s0; + + switch(alu) { + + case 1: //OR + case 2: //AND + case 3: //XOR + case 10: //CMP + case 13: //SHL2 + case 14: //SHL4 + case 15: { //XCHG + flag.ov0 = 0; + flag.ov1 = 0; + flag.c = 0; + break; + } + + case 4: //SUB + case 5: //ADD + case 6: //SBB + case 7: //ADC + case 8: //DEC + case 9: { //INC + if(alu & 1) { + //addition + flag.ov0 = (q ^ r) & ~(q ^ p) & 0x8000; + flag.c = r < q; + } else { + //subtraction + flag.ov0 = (q ^ r) & (q ^ p) & 0x8000; + flag.c = r > q; + } + flag.ov1 = flag.ov0 & flag.ov1 ? flag.s0 == flag.s1 : flag.ov0 | flag.ov1; + break; + } + + case 11: { //SHR1 (ASR) + flag.ov0 = 0; + flag.ov1 = 0; + flag.c = q & 1; + break; + } + + case 12: { //SHL1 (ROL) + flag.ov0 = 0; + flag.ov1 = 0; + flag.c = q >> 15; + break; + } + + } + + switch(asl) { + case 0: regs.a = r; flags.a = flag; break; + case 1: regs.b = r; flags.b = flag; break; + } + } + + execLD(idb << 6 | dst); + + if(dst != 4) { //if LD does not write to DP + switch(dpl) { + case 1: regs.dp = (regs.dp & 0xf0) + (regs.dp + 1 & 0x0f); break; //DPINC + case 2: regs.dp = (regs.dp & 0xf0) + (regs.dp - 1 & 0x0f); break; //DPDEC + case 3: regs.dp = (regs.dp & 0xf0); break; //DPCLR + } + regs.dp ^= dphm << 4; + } + + if(dst != 5) { //if LD does not write to RP + if(rpdcr) regs.rp--; + } +} + +auto uPD96050::execRT(uint24 opcode) -> void { + execOP(opcode); + regs.pc = regs.stack[--regs.sp]; +} + +auto uPD96050::execJP(uint24 opcode) -> void { + uint9 brch = opcode >> 13; //branch + uint11 na = opcode >> 2; //next address + uint2 bank = opcode >> 0; //bank address + + uint14 jp = regs.pc & 0x2000 | bank << 11 | na << 0; + + switch(brch) { + case 0x000: regs.pc = regs.so; return; //JMPSO + + case 0x080: if(flags.a.c == 0) regs.pc = jp; return; //JNCA + case 0x082: if(flags.a.c == 1) regs.pc = jp; return; //JCA + case 0x084: if(flags.b.c == 0) regs.pc = jp; return; //JNCB + case 0x086: if(flags.b.c == 1) regs.pc = jp; return; //JCB + + case 0x088: if(flags.a.z == 0) regs.pc = jp; return; //JNZA + case 0x08a: if(flags.a.z == 1) regs.pc = jp; return; //JZA + case 0x08c: if(flags.b.z == 0) regs.pc = jp; return; //JNZB + case 0x08e: if(flags.b.z == 1) regs.pc = jp; return; //JZB + + case 0x090: if(flags.a.ov0 == 0) regs.pc = jp; return; //JNOVA0 + case 0x092: if(flags.a.ov0 == 1) regs.pc = jp; return; //JOVA0 + case 0x094: if(flags.b.ov0 == 0) regs.pc = jp; return; //JNOVB0 + case 0x096: if(flags.b.ov0 == 1) regs.pc = jp; return; //JOVB0 + + case 0x098: if(flags.a.ov1 == 0) regs.pc = jp; return; //JNOVA1 + case 0x09a: if(flags.a.ov1 == 1) regs.pc = jp; return; //JOVA1 + case 0x09c: if(flags.b.ov1 == 0) regs.pc = jp; return; //JNOVB1 + case 0x09e: if(flags.b.ov1 == 1) regs.pc = jp; return; //JOVB1 + + case 0x0a0: if(flags.a.s0 == 0) regs.pc = jp; return; //JNSA0 + case 0x0a2: if(flags.a.s0 == 1) regs.pc = jp; return; //JSA0 + case 0x0a4: if(flags.b.s0 == 0) regs.pc = jp; return; //JNSB0 + case 0x0a6: if(flags.b.s0 == 1) regs.pc = jp; return; //JSB0 + + case 0x0a8: if(flags.a.s1 == 0) regs.pc = jp; return; //JNSA1 + case 0x0aa: if(flags.a.s1 == 1) regs.pc = jp; return; //JSA1 + case 0x0ac: if(flags.b.s1 == 0) regs.pc = jp; return; //JNSB1 + case 0x0ae: if(flags.b.s1 == 1) regs.pc = jp; return; //JSB1 + + case 0x0b0: if((regs.dp & 0x0f) == 0x00) regs.pc = jp; return; //JDPL0 + case 0x0b1: if((regs.dp & 0x0f) != 0x00) regs.pc = jp; return; //JDPLN0 + case 0x0b2: if((regs.dp & 0x0f) == 0x0f) regs.pc = jp; return; //JDPLF + case 0x0b3: if((regs.dp & 0x0f) != 0x0f) regs.pc = jp; return; //JDPLNF + + //serial input/output acknowledge not emulated + case 0x0b4: if(regs.sr.siack == 0) regs.pc = jp; return; //JNSIAK + case 0x0b6: if(regs.sr.siack == 1) regs.pc = jp; return; //JSIAK + case 0x0b8: if(regs.sr.soack == 0) regs.pc = jp; return; //JNSOAK + case 0x0ba: if(regs.sr.soack == 1) regs.pc = jp; return; //JSOAK + + case 0x0bc: if(regs.sr.rqm == 0) regs.pc = jp; return; //JNRQM + case 0x0be: if(regs.sr.rqm == 1) regs.pc = jp; return; //JRQM + + case 0x100: regs.pc = jp & ~0x2000; return; //LJMP + case 0x101: regs.pc = jp | 0x2000; return; //HJMP + + case 0x140: regs.stack[regs.sp++] = regs.pc; regs.pc = jp & ~0x2000; return; //LCALL + case 0x141: regs.stack[regs.sp++] = regs.pc; regs.pc = jp | 0x2000; return; //HCALL + } +} + +auto uPD96050::execLD(uint24 opcode) -> void { + uint16 id = opcode >> 6; //immediate data + uint4 dst = opcode >> 0; //destination + + switch(dst) { + case 0: break; + case 1: regs.a = id; break; + case 2: regs.b = id; break; + case 3: regs.tr = id; break; + case 4: regs.dp = id; break; + case 5: regs.rp = id; break; + case 6: regs.dr = id; regs.sr.rqm = 1; break; + case 7: regs.sr = regs.sr & 0x907c | id & ~0x907c; break; + case 8: regs.so = id; break; //LSB + case 9: regs.so = id; break; //MSB + case 10: regs.k = id; break; + case 11: regs.k = id; regs.l = dataROM[regs.rp]; break; + case 12: regs.l = id; regs.k = dataRAM[regs.dp | 0x40]; break; + case 13: regs.l = id; break; + case 14: regs.trb = id; break; + case 15: dataRAM[regs.dp] = id; break; + } +} diff --git a/waterbox/bsnescore/bsnes/processor/upd96050/memory.cpp b/waterbox/bsnescore/bsnes/processor/upd96050/memory.cpp new file mode 100644 index 0000000000..1d3df9a244 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/upd96050/memory.cpp @@ -0,0 +1,64 @@ +auto uPD96050::readSR() -> uint8 { + return regs.sr >> 8; +} + +auto uPD96050::writeSR(uint8 data) -> void { +} + +auto uPD96050::readDR() -> uint8 { + if(regs.sr.drc == 0) { + //16-bit + if(regs.sr.drs == 0) { + regs.sr.drs = 1; + return regs.dr >> 0; + } else { + regs.sr.rqm = 0; + regs.sr.drs = 0; + return regs.dr >> 8; + } + } else { + //8-bit + regs.sr.rqm = 0; + return regs.dr >> 0; + } +} + +auto uPD96050::writeDR(uint8 data) -> void { + if(regs.sr.drc == 0) { + //16-bit + if(regs.sr.drs == 0) { + regs.sr.drs = 1; + regs.dr = (regs.dr & 0xff00) | (data << 0); + } else { + regs.sr.rqm = 0; + regs.sr.drs = 0; + regs.dr = (data << 8) | (regs.dr & 0x00ff); + } + } else { + //8-bit + regs.sr.rqm = 0; + regs.dr = (regs.dr & 0xff00) | (data << 0); + } +} + +auto uPD96050::readDP(uint12 addr) -> uint8 { + bool hi = addr & 1; + addr = (addr >> 1) & 2047; + + if(hi == false) { + return dataRAM[addr] >> 0; + } else { + return dataRAM[addr] >> 8; + } +} + +auto uPD96050::writeDP(uint12 addr, uint8 data) -> void { + bool hi = addr & 1; + addr = (addr >> 1) & 2047; + + if(hi == false) { + dataRAM[addr] = (dataRAM[addr] & 0xff00) | (data << 0); + } else { + dataRAM[addr] = (dataRAM[addr] & 0x00ff) | (data << 8); + } +} diff --git a/waterbox/bsnescore/bsnes/processor/upd96050/serialization.cpp b/waterbox/bsnescore/bsnes/processor/upd96050/serialization.cpp new file mode 100644 index 0000000000..43dac5f3f1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/upd96050/serialization.cpp @@ -0,0 +1,51 @@ +auto uPD96050::serialize(serializer& s) -> void { + s.array(dataRAM); + regs.serialize(s); + flags.a.serialize(s); + flags.b.serialize(s); +} + +auto uPD96050::Flag::serialize(serializer& s) -> void { + s.boolean(ov0); + s.boolean(ov1); + s.boolean(z); + s.boolean(c); + s.boolean(s0); + s.boolean(s1); +} + +auto uPD96050::Status::serialize(serializer& s) -> void { + s.boolean(p0); + s.boolean(p1); + s.boolean(ei); + s.boolean(sic); + s.boolean(soc); + s.boolean(drc); + s.boolean(dma); + s.boolean(drs); + s.boolean(usf0); + s.boolean(usf1); + s.boolean(rqm); + s.boolean(siack); + s.boolean(soack); +} + +auto uPD96050::Registers::serialize(serializer& s) -> void { + s.array(stack); + s.integer(pc); + s.integer(rp); + s.integer(dp); + s.integer(sp); + s.integer(si); + s.integer(so); + s.integer(k); + s.integer(l); + s.integer(m); + s.integer(n); + s.integer(a); + s.integer(b); + s.integer(tr); + s.integer(trb); + s.integer(dr); + sr.serialize(s); +} diff --git a/waterbox/bsnescore/bsnes/processor/upd96050/upd96050.cpp b/waterbox/bsnescore/bsnes/processor/upd96050/upd96050.cpp new file mode 100644 index 0000000000..e6cf261359 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/upd96050/upd96050.cpp @@ -0,0 +1,48 @@ +#include +#include "upd96050.hpp" + +namespace Processor { + +#include "instructions.cpp" +#include "memory.cpp" +#include "disassembler.cpp" +#include "serialization.cpp" + +auto uPD96050::power() -> void { + if(revision == Revision::uPD7725) { + regs.pc.resize(11); + regs.rp.resize(10); + regs.dp.resize( 8); + } + + if(revision == Revision::uPD96050) { + regs.pc.resize(14); + regs.rp.resize(11); + regs.dp.resize(11); + } + + for(auto n : range(16)) regs.stack[n] = 0x0000; + regs.pc = 0x0000; + regs.rp = 0x0000; + regs.dp = 0x0000; + regs.sp = 0x0; + regs.si = 0x0000; + regs.so = 0x0000; + regs.k = 0x0000; + regs.l = 0x0000; + regs.m = 0x0000; + regs.n = 0x0000; + regs.a = 0x0000; + regs.b = 0x0000; + regs.tr = 0x0000; + regs.trb = 0x0000; + regs.dr = 0x0000; + regs.sr = 0x0000; + regs.sr.siack = 0; + regs.sr.soack = 0; + + flags.a = 0x0000; + flags.b = 0x0000; +} + +} diff --git a/waterbox/bsnescore/bsnes/processor/upd96050/upd96050.hpp b/waterbox/bsnescore/bsnes/processor/upd96050/upd96050.hpp new file mode 100644 index 0000000000..fe99d7aa38 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/upd96050/upd96050.hpp @@ -0,0 +1,128 @@ +//NEC uPD7725 +//NEC uPD96050 + +#pragma once + +namespace Processor { + +struct uPD96050 { + auto power() -> void; + auto exec() -> void; + auto serialize(serializer&) -> void; + + auto execOP(uint24 opcode) -> void; + auto execRT(uint24 opcode) -> void; + auto execJP(uint24 opcode) -> void; + auto execLD(uint24 opcode) -> void; + + auto readSR() -> uint8; + auto writeSR(uint8 data) -> void; + + auto readDR() -> uint8; + auto writeDR(uint8 data) -> void; + + auto readDP(uint12 addr) -> uint8; + auto writeDP(uint12 addr, uint8 data) -> void; + + auto disassemble(uint14 ip) -> string; + + enum class Revision : uint { uPD7725, uPD96050 } revision; + uint24 programROM[16384]; + uint16 dataROM[2048]; + uint16 dataRAM[2048]; + + struct Flag { + inline operator uint() const { + return ov0 << 0 | ov1 << 1 | z << 2 | c << 3 | s0 << 4 | s1 << 5; + } + + inline auto operator=(uint16 data) -> Flag& { + ov0 = data >> 0 & 1; + ov1 = data >> 1 & 1; + z = data >> 2 & 1; + c = data >> 3 & 1; + s0 = data >> 4 & 1; + s1 = data >> 5 & 1; + return *this; + } + + auto serialize(serializer&) -> void; + + boolean ov0; //overflow 0 + boolean ov1; //overflow 1 + boolean z; //zero + boolean c; //carry + boolean s0; //sign 0 + boolean s1; //sign 1 + }; + + struct Status { + inline operator uint() const { + bool _drs = drs & !drc; //when DRC=1, DRS=0 + return p0 << 0 | p1 << 1 | ei << 7 | sic << 8 | soc << 9 | drc << 10 + | dma << 11 | _drs << 12 | usf0 << 13 | usf1 << 14 | rqm << 15; + } + + inline auto operator=(uint16 data) -> Status& { + p0 = data >> 0 & 1; + p1 = data >> 1 & 1; + ei = data >> 7 & 1; + sic = data >> 8 & 1; + soc = data >> 9 & 1; + drc = data >> 10 & 1; + dma = data >> 11 & 1; + drs = data >> 12 & 1; + usf0 = data >> 13 & 1; + usf1 = data >> 14 & 1; + rqm = data >> 15 & 1; + return *this; + } + + auto serialize(serializer&) -> void; + + boolean p0; //output port 0 + boolean p1; //output port 1 + boolean ei; //enable interrupts + boolean sic; //serial input control (0 = 16-bit; 1 = 8-bit) + boolean soc; //serial output control (0 = 16-bit; 1 = 8-bit) + boolean drc; //data register size (0 = 16-bit; 1 = 8-bit) + boolean dma; //data register DMA mode + boolean drs; //data register status (1 = active; 0 = stopped) + boolean usf0; //user flag 0 + boolean usf1; //user flag 1 + boolean rqm; //request for master (=1 on internal access; =0 on external access) + + //internal + boolean siack; //serial input acknowledge + boolean soack; //serial output acknowledge + }; + + struct Registers { + auto serialize(serializer&) -> void; + + uint16 stack[16]; //LIFO + VariadicNatural pc; //program counter + VariadicNatural rp; //ROM pointer + VariadicNatural dp; //data pointer + uint4 sp; //stack pointer + uint16 si; //serial input + uint16 so; //serial output + int16 k; + int16 l; + int16 m; + int16 n; + int16 a; //accumulator + int16 b; //accumulator + uint16 tr; //temporary register + uint16 trb; //temporary register + uint16 dr; //data register + Status sr; //status register + } regs; + + struct Flags { + Flag a; + Flag b; + } flags; +}; + +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/algorithms.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/algorithms.cpp new file mode 100644 index 0000000000..f4eacc34f3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/algorithms.cpp @@ -0,0 +1,363 @@ +auto WDC65816::algorithmADC8(uint8 data) -> uint8 { + int result; + + if(!DF) { + result = A.l + data + CF; + } else { + result = (A.l & 0x0f) + (data & 0x0f) + (CF << 0); + if(result > 0x09) result += 0x06; + CF = result > 0x0f; + result = (A.l & 0xf0) + (data & 0xf0) + (CF << 4) + (result & 0x0f); + } + + VF = ~(A.l ^ data) & (A.l ^ result) & 0x80; + if(DF && result > 0x9f) result += 0x60; + CF = result > 0xff; + ZF = (uint8)result == 0; + NF = result & 0x80; + + return A.l = result; +} + +auto WDC65816::algorithmADC16(uint16 data) -> uint16 { + int result; + + if(!DF) { + result = A.w + data + CF; + } else { + result = (A.w & 0x000f) + (data & 0x000f) + (CF << 0); + if(result > 0x0009) result += 0x0006; + CF = result > 0x000f; + result = (A.w & 0x00f0) + (data & 0x00f0) + (CF << 4) + (result & 0x000f); + if(result > 0x009f) result += 0x0060; + CF = result > 0x00ff; + result = (A.w & 0x0f00) + (data & 0x0f00) + (CF << 8) + (result & 0x00ff); + if(result > 0x09ff) result += 0x0600; + CF = result > 0x0fff; + result = (A.w & 0xf000) + (data & 0xf000) + (CF << 12) + (result & 0x0fff); + } + + VF = ~(A.w ^ data) & (A.w ^ result) & 0x8000; + if(DF && result > 0x9fff) result += 0x6000; + CF = result > 0xffff; + ZF = (uint16)result == 0; + NF = result & 0x8000; + + return A.w = result; +} + +auto WDC65816::algorithmAND8(uint8 data) -> uint8 { + A.l &= data; + ZF = A.l == 0; + NF = A.l & 0x80; + return A.l; +} + +auto WDC65816::algorithmAND16(uint16 data) -> uint16 { + A.w &= data; + ZF = A.w == 0; + NF = A.w & 0x8000; + return A.w; +} + +auto WDC65816::algorithmASL8(uint8 data) -> uint8 { + CF = data & 0x80; + data <<= 1; + ZF = data == 0; + NF = data & 0x80; + return data; +} + +auto WDC65816::algorithmASL16(uint16 data) -> uint16 { + CF = data & 0x8000; + data <<= 1; + ZF = data == 0; + NF = data & 0x8000; + return data; +} + +auto WDC65816::algorithmBIT8(uint8 data) -> uint8 { + ZF = (data & A.l) == 0; + VF = data & 0x40; + NF = data & 0x80; + return data; +} + +auto WDC65816::algorithmBIT16(uint16 data) -> uint16 { + ZF = (data & A.w) == 0; + VF = data & 0x4000; + NF = data & 0x8000; + return data; +} + +auto WDC65816::algorithmCMP8(uint8 data) -> uint8 { + int result = A.l - data; + CF = result >= 0; + ZF = (uint8)result == 0; + NF = result & 0x80; + return result; +} + +auto WDC65816::algorithmCMP16(uint16 data) -> uint16 { + int result = A.w - data; + CF = result >= 0; + ZF = (uint16)result == 0; + NF = result & 0x8000; + return result; +} + +auto WDC65816::algorithmCPX8(uint8 data) -> uint8 { + int result = X.l - data; + CF = result >= 0; + ZF = (uint8)result == 0; + NF = result & 0x80; + return result; +} + +auto WDC65816::algorithmCPX16(uint16 data) -> uint16 { + int result = X.w - data; + CF = result >= 0; + ZF = (uint16)result == 0; + NF = result & 0x8000; + return result; +} + +auto WDC65816::algorithmCPY8(uint8 data) -> uint8 { + int result = Y.l - data; + CF = result >= 0; + ZF = (uint8)result == 0; + NF = result & 0x80; + return result; +} + +auto WDC65816::algorithmCPY16(uint16 data) -> uint16 { + int result = Y.w - data; + CF = result >= 0; + ZF = (uint16)result == 0; + NF = result & 0x8000; + return result; +} + +auto WDC65816::algorithmDEC8(uint8 data) -> uint8 { + data--; + ZF = data == 0; + NF = data & 0x80; + return data; +} + +auto WDC65816::algorithmDEC16(uint16 data) -> uint16 { + data--; + ZF = data == 0; + NF = data & 0x8000; + return data; +} + +auto WDC65816::algorithmEOR8(uint8 data) -> uint8 { + A.l ^= data; + ZF = A.l == 0; + NF = A.l & 0x80; + return A.l; +} + +auto WDC65816::algorithmEOR16(uint16 data) -> uint16 { + A.w ^= data; + ZF = A.w == 0; + NF = A.w & 0x8000; + return A.w; +} + +auto WDC65816::algorithmINC8(uint8 data) -> uint8 { + data++; + ZF = data == 0; + NF = data & 0x80; + return data; +} + +auto WDC65816::algorithmINC16(uint16 data) -> uint16 { + data++; + ZF = data == 0; + NF = data & 0x8000; + return data; +} + +auto WDC65816::algorithmLDA8(uint8 data) -> uint8 { + A.l = data; + ZF = A.l == 0; + NF = A.l & 0x80; + return data; +} + +auto WDC65816::algorithmLDA16(uint16 data) -> uint16 { + A.w = data; + ZF = A.w == 0; + NF = A.w & 0x8000; + return data; +} + +auto WDC65816::algorithmLDX8(uint8 data) -> uint8 { + X.l = data; + ZF = X.l == 0; + NF = X.l & 0x80; + return data; +} + +auto WDC65816::algorithmLDX16(uint16 data) -> uint16 { + X.w = data; + ZF = X.w == 0; + NF = X.w & 0x8000; + return data; +} + +auto WDC65816::algorithmLDY8(uint8 data) -> uint8 { + Y.l = data; + ZF = Y.l == 0; + NF = Y.l & 0x80; + return data; +} + +auto WDC65816::algorithmLDY16(uint16 data) -> uint16 { + Y.w = data; + ZF = Y.w == 0; + NF = Y.w & 0x8000; + return data; +} + +auto WDC65816::algorithmLSR8(uint8 data) -> uint8 { + CF = data & 1; + data >>= 1; + ZF = data == 0; + NF = data & 0x80; + return data; +} + +auto WDC65816::algorithmLSR16(uint16 data) -> uint16 { + CF = data & 1; + data >>= 1; + ZF = data == 0; + NF = data & 0x8000; + return data; +} + +auto WDC65816::algorithmORA8(uint8 data) -> uint8 { + A.l |= data; + ZF = A.l == 0; + NF = A.l & 0x80; + return A.l; +} + +auto WDC65816::algorithmORA16(uint16 data) -> uint16 { + A.w |= data; + ZF = A.w == 0; + NF = A.w & 0x8000; + return A.w; +} + +auto WDC65816::algorithmROL8(uint8 data) -> uint8 { + bool carry = CF; + CF = data & 0x80; + data = data << 1 | carry; + ZF = data == 0; + NF = data & 0x80; + return data; +} + +auto WDC65816::algorithmROL16(uint16 data) -> uint16 { + bool carry = CF; + CF = data & 0x8000; + data = data << 1 | carry; + ZF = data == 0; + NF = data & 0x8000; + return data; +} + +auto WDC65816::algorithmROR8(uint8 data) -> uint8 { + bool carry = CF; + CF = data & 1; + data = carry << 7 | data >> 1; + ZF = data == 0; + NF = data & 0x80; + return data; +} + +auto WDC65816::algorithmROR16(uint16 data) -> uint16 { + bool carry = CF; + CF = data & 1; + data = carry << 15 | data >> 1; + ZF = data == 0; + NF = data & 0x8000; + return data; +} + +auto WDC65816::algorithmSBC8(uint8 data) -> uint8 { + int result; + data = ~data; + + if(!DF) { + result = A.l + data + CF; + } else { + result = (A.l & 0x0f) + (data & 0x0f) + (CF << 0); + if(result <= 0x0f) result -= 0x06; + CF = result > 0x0f; + result = (A.l & 0xf0) + (data & 0xf0) + (CF << 4) + (result & 0x0f); + } + + VF = ~(A.l ^ data) & (A.l ^ result) & 0x80; + if(DF && result <= 0xff) result -= 0x60; + CF = result > 0xff; + ZF = (uint8)result == 0; + NF = result & 0x80; + + return A.l = result; +} + +auto WDC65816::algorithmSBC16(uint16 data) -> uint16 { + int result; + data = ~data; + + if(!DF) { + result = A.w + data + CF; + } else { + result = (A.w & 0x000f) + (data & 0x000f) + (CF << 0); + if(result <= 0x000f) result -= 0x0006; + CF = result > 0x000f; + result = (A.w & 0x00f0) + (data & 0x00f0) + (CF << 4) + (result & 0x000f); + if(result <= 0x00ff) result -= 0x0060; + CF = result > 0x00ff; + result = (A.w & 0x0f00) + (data & 0x0f00) + (CF << 8) + (result & 0x00ff); + if(result <= 0x0fff) result -= 0x0600; + CF = result > 0x0fff; + result = (A.w & 0xf000) + (data & 0xf000) + (CF << 12) + (result & 0x0fff); + } + + VF = ~(A.w ^ data) & (A.w ^ result) & 0x8000; + if(DF && result <= 0xffff) result -= 0x6000; + CF = result > 0xffff; + ZF = (uint16)result == 0; + NF = result & 0x8000; + + return A.w = result; +} + +auto WDC65816::algorithmTRB8(uint8 data) -> uint8 { + ZF = (data & A.l) == 0; + data &= ~A.l; + return data; +} + +auto WDC65816::algorithmTRB16(uint16 data) -> uint16 { + ZF = (data & A.w) == 0; + data &= ~A.w; + return data; +} + +auto WDC65816::algorithmTSB8(uint8 data) -> uint8 { + ZF = (data & A.l) == 0; + data |= A.l; + return data; +} + +auto WDC65816::algorithmTSB16(uint16 data) -> uint16 { + ZF = (data & A.w) == 0; + data |= A.w; + return data; +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/disassembler.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/disassembler.cpp new file mode 100644 index 0000000000..0a24a51b71 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/disassembler.cpp @@ -0,0 +1,476 @@ +auto WDC65816::disassemble() -> vector { + return disassemble(r.pc.d, r.e, r.p.m, r.p.x); +} + +auto WDC65816::disassemble(uint24 address, bool e, bool m, bool x) -> vector { + string s; + + uint24 pc = address; + s = {hex(pc, 6), " "}; + + string name; + string operand; + maybe effective; + + auto read = [&](uint24 address) -> uint8 { + //$00-3f,80-bf:2000-5fff: do not attempt to read I/O registers from the disassembler: + //this is because such reads are much more likely to have side effects to emulation. + if((address & 0x40ffff) >= 0x2000 && (address & 0x40ffff) <= 0x5fff) return 0x00; + return readDisassembler(address); + }; + + auto readByte = [&](uint24 address) -> uint8 { + return read(address); + }; + auto readWord = [&](uint24 address) -> uint16 { + uint16 data = readByte(address + 0) << 0; + return data | readByte(address + 1) << 8; + }; + auto readLong = [&](uint24 address) -> uint24 { + uint24 data = readByte(address + 0) << 0; + return data | readWord(address + 1) << 8; + }; + + auto opcode = read(address); address.bit(0,15)++; + auto operand0 = read(address); address.bit(0,15)++; + auto operand1 = read(address); address.bit(0,15)++; + auto operand2 = read(address); address.bit(0,15)++; + + uint8 operandByte = operand0 << 0; + uint16 operandWord = operand0 << 0 | operand1 << 8; + uint24 operandLong = operand0 << 0 | operand1 << 8 | operand2 << 16; + + auto absolute = [&]() -> string { + effective = r.b << 16 | operandWord; + return {"$", hex(operandWord, 4L)}; + }; + + auto absolutePC = [&]() -> string { + effective = pc & 0xff0000 | operandWord; + return {"$", hex(operandWord, 4L)}; + }; + + auto absoluteX = [&]() -> string { + effective = (r.b << 16) + operandWord + r.x.w; + return {"$", hex(operandWord, 4L), ",x"}; + }; + + auto absoluteY = [&]() -> string { + effective = (r.b << 16) + operandWord + r.y.w; + return {"$", hex(operandWord, 4L), ",y"}; + }; + + auto absoluteLong = [&]() -> string { + effective = operandLong; + return {"$", hex(operandLong, 6L)}; + }; + + auto absoluteLongX = [&]() -> string { + effective = operandLong + r.x.w; + return {"$", hex(operandLong, 6L), ",x"}; + }; + + auto direct = [&]() -> string { + effective = uint16(r.d.w + operandByte); + return {"$", hex(operandByte, 2L)}; + }; + + auto directX = [&]() -> string { + effective = uint16(r.d.w + operandByte + r.x.w); + return {"$", hex(operandByte, 2L), ",x"}; + }; + + auto directY = [&]() -> string { + effective = uint16(r.d.w + operandByte + r.y.w); + return {"$", hex(operandByte, 2L), ",y"}; + }; + + auto immediate = [&]() -> string { + return {"#$", hex(operandByte, 2L)}; + }; + + auto immediateA = [&]() -> string { + return {"#$", m ? hex(operandByte, 2L) : hex(operandWord, 4L)}; + }; + + auto immediateX = [&]() -> string { + return {"#$", x ? hex(operandByte, 2L) : hex(operandWord, 4L)}; + }; + + auto implied = [&]() -> string { + return {}; + }; + + auto indexedIndirectX = [&]() -> string { + effective = uint16(r.d.w + operandByte + r.x.w); + effective = r.b << 16 | readWord(*effective); + return {"($", hex(operandByte, 2L), ",x)"}; + }; + + auto indirect = [&]() -> string { + effective = uint16(r.d.w + operandByte); + effective = (r.b << 16) + readWord(*effective); + return {"($", hex(operandByte, 2L), ")"}; + }; + + auto indirectPC = [&]() -> string { + effective = operandWord; + effective = pc & 0xff0000 | readWord(*effective); + return {"($", hex(operandWord, 4L), ")"}; + }; + + auto indirectX = [&]() -> string { + effective = operandWord; + effective = pc & 0xff0000 | uint16(*effective + r.x.w); + effective = pc & 0xff0000 | readWord(*effective); + return {"($", hex(operandWord, 4L), ",x)"}; + }; + + auto indirectIndexedY = [&]() -> string { + effective = uint16(r.d.w + operandByte); + effective = (r.b << 16) + readWord(*effective) + r.y.w; + return {"($", hex(operandByte, 2L), "),y"}; + }; + + auto indirectLong = [&]() -> string { + effective = uint16(r.d.w + operandByte); + effective = readLong(*effective); + return {"[$", hex(operandByte, 2L), "]"}; + }; + + auto indirectLongPC = [&]() -> string { + effective = readLong(operandWord); + return {"[$", hex(operandWord, 4L), "]"}; + }; + + auto indirectLongY = [&]() -> string { + effective = uint16(r.d.w + operandByte); + effective = readLong(*effective) + r.y.w; + return {"[$", hex(operandByte, 2L), "],y"}; + }; + + auto move = [&]() -> string { + return {"$", hex(operand0, 2L), "=$", hex(operand1, 2L)}; + }; + + auto relative = [&]() -> string { + effective = pc & 0xff0000 | uint16(pc + 2 + (int8)operandByte); + return {"$", hex(*effective, 4L)}; + }; + + auto relativeWord = [&]() -> string { + effective = pc & 0xff0000 | uint16(pc + 3 + (int16)operandWord); + return {"$", hex(*effective, 4L)}; + }; + + auto stack = [&]() -> string { + effective = uint16(r.s.w + operandByte); + return {"$", hex(operandByte, 2L), ",s"}; + }; + + auto stackIndirect = [&]() -> string { + effective = uint16(operandByte + r.s.w); + effective = (r.b << 16) + readWord(*effective) + r.y.w; + return {"($", hex(operandByte, 2L), ",s),y"}; + }; + + #define op(id, label, function) case id: name = label; operand = function(); break; + switch(opcode) { + op(0x00, "brk", immediate) + op(0x01, "ora", indexedIndirectX) + op(0x02, "cop", immediate) + op(0x03, "ora", stack) + op(0x04, "tsb", direct) + op(0x05, "ora", direct) + op(0x06, "asl", direct) + op(0x07, "ora", indirectLong) + op(0x08, "php", implied) + op(0x09, "ora", immediateA) + op(0x0a, "asl", implied) + op(0x0b, "phd", implied) + op(0x0c, "tsb", absolute) + op(0x0d, "ora", absolute) + op(0x0e, "asl", absolute) + op(0x0f, "ora", absoluteLong) + op(0x10, "bpl", relative) + op(0x11, "ora", indirectIndexedY) + op(0x12, "ora", indirect) + op(0x13, "ora", stackIndirect) + op(0x14, "trb", direct) + op(0x15, "ora", directX) + op(0x16, "asl", directX) + op(0x17, "ora", indirectLongY) + op(0x18, "clc", implied) + op(0x19, "ora", absoluteY) + op(0x1a, "inc", implied) + op(0x1b, "tas", implied) + op(0x1c, "trb", absolute) + op(0x1d, "ora", absoluteX) + op(0x1e, "asl", absoluteX) + op(0x1f, "ora", absoluteLongX) + + op(0x20, "jsr", absolutePC) + op(0x21, "and", indexedIndirectX) + op(0x22, "jsl", absoluteLong) + op(0x23, "and", stack) + op(0x24, "bit", direct) + op(0x25, "and", direct) + op(0x26, "rol", direct) + op(0x27, "and", indirectLong) + op(0x28, "plp", implied) + op(0x29, "and", immediateA) + op(0x2a, "rol", implied) + op(0x2b, "pld", implied) + op(0x2c, "bit", absolute) + op(0x2d, "and", absolute) + op(0x2e, "rol", absolute) + op(0x2f, "and", absoluteLong) + op(0x30, "bmi", relative) + op(0x31, "and", indirectIndexedY) + op(0x32, "and", indirect) + op(0x33, "and", stackIndirect) + op(0x34, "bit", directX) + op(0x35, "and", directX) + op(0x36, "rol", directX) + op(0x37, "and", indirectLongY) + op(0x38, "sec", implied) + op(0x39, "and", absoluteY) + op(0x3a, "dec", implied) + op(0x3b, "tsa", implied) + op(0x3c, "bit", absoluteX) + op(0x3d, "and", absoluteX) + op(0x3e, "rol", absoluteX) + op(0x3f, "and", absoluteLongX) + + op(0x40, "rti", implied) + op(0x41, "eor", indexedIndirectX) + op(0x42, "wdm", immediate) + op(0x43, "eor", stack) + op(0x44, "mvp", move) + op(0x45, "eor", direct) + op(0x46, "lsr", direct) + op(0x47, "eor", indirectLong) + op(0x48, "pha", implied) + op(0x49, "eor", immediateA) + op(0x4a, "lsr", implied) + op(0x4b, "phk", implied) + op(0x4c, "jmp", absolutePC) + op(0x4d, "eor", absolute) + op(0x4e, "lsr", absolute) + op(0x4f, "eor", absoluteLong) + op(0x50, "bvc", relative) + op(0x51, "eor", indirectIndexedY) + op(0x52, "eor", indirect) + op(0x53, "eor", stackIndirect) + op(0x54, "mvn", move) + op(0x55, "eor", directX) + op(0x56, "lsr", directX) + op(0x57, "eor", indirectLongY) + op(0x58, "cli", implied) + op(0x59, "eor", absoluteY) + op(0x5a, "phy", implied) + op(0x5b, "tad", implied) + op(0x5c, "jml", absoluteLong) + op(0x5d, "eor", absoluteX) + op(0x5e, "lsr", absoluteX) + op(0x5f, "eor", absoluteLongX) + + op(0x60, "rts", implied) + op(0x61, "adc", indexedIndirectX) + op(0x62, "per", absolute) + op(0x63, "adc", stack) + op(0x64, "stz", direct) + op(0x65, "adc", direct) + op(0x66, "ror", direct) + op(0x67, "adc", indirectLong) + op(0x68, "pla", implied) + op(0x69, "adc", immediateA) + op(0x6a, "ror", implied) + op(0x6b, "rtl", implied) + op(0x6c, "jmp", indirectPC) + op(0x6d, "adc", absolute) + op(0x6e, "ror", absolute) + op(0x6f, "adc", absoluteLong) + op(0x70, "bvs", relative) + op(0x71, "adc", indirectIndexedY) + op(0x72, "adc", indirect) + op(0x73, "adc", stackIndirect) + op(0x74, "stz", directX) + op(0x75, "adc", directX) + op(0x76, "ror", directX) + op(0x77, "adc", indirectLongY) + op(0x78, "sei", implied) + op(0x79, "adc", absoluteY) + op(0x7a, "ply", implied) + op(0x7b, "tda", implied) + op(0x7c, "jmp", indirectX) + op(0x7d, "adc", absoluteX) + op(0x7e, "ror", absoluteX) + op(0x7f, "adc", absoluteLongX) + + op(0x80, "bra", relative) + op(0x81, "sta", indexedIndirectX) + op(0x82, "brl", relativeWord) + op(0x83, "sta", stack) + op(0x84, "sty", direct) + op(0x85, "sta", direct) + op(0x86, "stx", direct) + op(0x87, "sta", indirectLong) + op(0x88, "dey", implied) + op(0x89, "bit", immediateA) + op(0x8a, "txa", implied) + op(0x8b, "phb", implied) + op(0x8c, "sty", absolute) + op(0x8d, "sta", absolute) + op(0x8e, "stx", absolute) + op(0x8f, "sta", absoluteLong) + op(0x90, "bcc", relative) + op(0x91, "sta", indirectIndexedY) + op(0x92, "sta", indirect) + op(0x93, "sta", stackIndirect) + op(0x94, "sty", directX) + op(0x95, "sta", directX) + op(0x96, "stx", directY) + op(0x97, "sta", indirectLongY) + op(0x98, "tya", implied) + op(0x99, "sta", absoluteY) + op(0x9a, "txs", implied) + op(0x9b, "txy", implied) + op(0x9c, "stz", absolute) + op(0x9d, "sta", absoluteX) + op(0x9e, "stz", absoluteX) + op(0x9f, "sta", absoluteLongX) + + op(0xa0, "ldy", immediateX) + op(0xa1, "lda", indexedIndirectX) + op(0xa2, "ldx", immediateX) + op(0xa3, "lda", stack) + op(0xa4, "ldy", direct) + op(0xa5, "lda", direct) + op(0xa6, "ldx", direct) + op(0xa7, "lda", indirectLong) + op(0xa8, "tay", implied) + op(0xa9, "lda", immediateA) + op(0xaa, "tax", implied) + op(0xab, "plb", implied) + op(0xac, "ldy", absolute) + op(0xad, "lda", absolute) + op(0xae, "ldx", absolute) + op(0xaf, "lda", absoluteLong) + op(0xb0, "bcs", relative) + op(0xb1, "lda", indirectIndexedY) + op(0xb2, "lda", indirect) + op(0xb3, "lda", stackIndirect) + op(0xb4, "ldy", directX) + op(0xb5, "lda", directX) + op(0xb6, "ldx", directY) + op(0xb7, "lda", indirectLongY) + op(0xb8, "clv", implied) + op(0xb9, "lda", absoluteY) + op(0xba, "tsx", implied) + op(0xbb, "tyx", implied) + op(0xbc, "ldy", absoluteX) + op(0xbd, "lda", absoluteX) + op(0xbe, "ldx", absoluteY) + op(0xbf, "lda", absoluteLongX) + + op(0xc0, "cpy", immediateX) + op(0xc1, "cmp", indexedIndirectX) + op(0xc2, "rep", immediate) + op(0xc3, "cmp", stack) + op(0xc4, "cpy", direct) + op(0xc5, "cmp", direct) + op(0xc6, "dec", direct) + op(0xc7, "cmp", indirectLong) + op(0xc8, "iny", implied) + op(0xc9, "cmp", immediateA) + op(0xca, "dex", implied) + op(0xcb, "wai", implied) + op(0xcc, "cpy", absolute) + op(0xcd, "cmp", absolute) + op(0xce, "dec", absolute) + op(0xcf, "cmp", absoluteLong) + op(0xd0, "bne", relative) + op(0xd1, "cmp", indirectIndexedY) + op(0xd2, "cmp", indirect) + op(0xd3, "cmp", stackIndirect) + op(0xd4, "pei", indirect) + op(0xd5, "cmp", directX) + op(0xd6, "dec", directX) + op(0xd7, "cmp", indirectLongY) + op(0xd8, "cld", implied) + op(0xd9, "cmp", absoluteY) + op(0xda, "phx", implied) + op(0xdb, "stp", implied) + op(0xdc, "jmp", indirectLongPC) + op(0xdd, "cmp", absoluteX) + op(0xde, "dec", absoluteX) + op(0xdf, "cmp", absoluteLongX) + + op(0xe0, "cpx", immediateX) + op(0xe1, "sbc", indexedIndirectX) + op(0xe2, "sep", immediate) + op(0xe3, "sbc", stack) + op(0xe4, "cpx", direct) + op(0xe5, "sbc", direct) + op(0xe6, "inc", direct) + op(0xe7, "sbc", indirectLong) + op(0xe8, "inx", implied) + op(0xe9, "sbc", immediateA) + op(0xea, "nop", implied) + op(0xeb, "xba", implied) + op(0xec, "cpx", absolute) + op(0xed, "sbc", absolute) + op(0xee, "inc", absolute) + op(0xef, "sbc", absoluteLong) + op(0xf0, "beq", relative) + op(0xf1, "sbc", indirectIndexedY) + op(0xf2, "sbc", indirect) + op(0xf3, "sbc", stackIndirect) + op(0xf4, "pea", absolute) + op(0xf5, "sbc", directX) + op(0xf6, "inc", directX) + op(0xf7, "sbc", indirectLongY) + op(0xf8, "sed", implied) + op(0xf9, "sbc", absoluteY) + op(0xfa, "plx", implied) + op(0xfb, "xce", implied) + op(0xfc, "jsr", indirectX) + op(0xfd, "sbc", absoluteX) + op(0xfe, "inc", absoluteX) + op(0xff, "sbc", absoluteLongX) + } + #undef op + + s.append(name, " ", operand); + while(s.size() < 23) s.append(" "); + if(effective) s.append("[", hex(*effective, 6L), "]"); + while(s.size() < 31) s.append(" "); + + string s2; + + s2.append(" A:", hex(r.a.w, 4L)); + s2.append(" X:", hex(r.x.w, 4L)); + s2.append(" Y:", hex(r.y.w, 4L)); + s2.append(" S:", hex(r.s.w, 4L)); + s2.append(" D:", hex(r.d.w, 4L)); + s2.append(" B:", hex(r.b , 2L)); + + if(e) { + s2.append(' ', + r.p.n ? 'N' : 'n', r.p.v ? 'V' : 'v', + r.p.m ? '1' : '0', r.p.x ? 'B' : 'b', + r.p.d ? 'D' : 'd', r.p.i ? 'I' : 'i', + r.p.z ? 'Z' : 'z', r.p.c ? 'C' : 'c' + ); + } else { + s2.append(' ', + r.p.n ? 'N' : 'n', r.p.v ? 'V' : 'v', + r.p.m ? 'M' : 'm', r.p.x ? 'X' : 'x', + r.p.d ? 'D' : 'd', r.p.i ? 'I' : 'i', + r.p.z ? 'Z' : 'z', r.p.c ? 'C' : 'c' + ); + } + + return vector(s, s2); +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/instruction.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/instruction.cpp new file mode 100644 index 0000000000..1d39fa2c1a --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/instruction.cpp @@ -0,0 +1,64 @@ +auto WDC65816::interrupt() -> void { + read(PC.d); + idle(); +N push(PC.b); + push(PC.h); + push(PC.l); + push(EF ? P & ~0x10 : P); + IF = 1; + DF = 0; + PC.l = read(r.vector + 0); +L PC.h = read(r.vector + 1); + PC.b = 0x00; + idleJump(); +} + +//both the accumulator and index registers can independently be in either 8-bit or 16-bit mode. +//controlled via the M/X flags, this changes the execution details of various instructions. +//rather than implement four instruction tables for all possible combinations of these bits, +//instead use macro abuse to generate all four tables based off of a single template table. +auto WDC65816::instruction() -> void { + //a = instructions unaffected by M/X flags + //m = instructions affected by M flag (1 = 8-bit; 0 = 16-bit) + //x = instructions affected by X flag (1 = 8-bit; 0 = 16-bit) + + #define opA(id, name, ...) case id: return instruction##name(__VA_ARGS__); + if(MF) { + #define opM(id, name, ...) case id: return instruction##name##8(__VA_ARGS__); + #define m(name) &WDC65816::algorithm##name##8 + if(XF) { + #define opX(id, name, ...) case id: return instruction##name##8(__VA_ARGS__); + #define x(name) &WDC65816::algorithm##name##8 + #include "instruction.hpp" + #undef opX + #undef x + } else { + #define opX(id, name, ...) case id: return instruction##name##16(__VA_ARGS__); + #define x(name) &WDC65816::algorithm##name##16 + #include "instruction.hpp" + #undef opX + #undef x + } + #undef opM + #undef m + } else { + #define opM(id, name, ...) case id: return instruction##name##16(__VA_ARGS__); + #define m(name) &WDC65816::algorithm##name##16 + if(XF) { + #define opX(id, name, ...) case id: return instruction##name##8(__VA_ARGS__); + #define x(name) &WDC65816::algorithm##name##8 + #include "instruction.hpp" + #undef opX + #undef x + } else { + #define opX(id, name, ...) case id: return instruction##name##16(__VA_ARGS__); + #define x(name) &WDC65816::algorithm##name##16 + #include "instruction.hpp" + #undef opX + #undef x + } + #undef opM + #undef m + } + #undef opA +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/instruction.hpp b/waterbox/bsnescore/bsnes/processor/wdc65816/instruction.hpp new file mode 100644 index 0000000000..34693105d9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/instruction.hpp @@ -0,0 +1,258 @@ + switch(fetch()) { + opA(0x00, Interrupt, EF ? (r16)0xfffe : (r16)0xffe6) //emulation mode lacks BRK vector; uses IRQ vector instead + opM(0x01, IndexedIndirectRead, m(ORA)) + opA(0x02, Interrupt, EF ? (r16)0xfff4 : (r16)0xffe4) + opM(0x03, StackRead, m(ORA)) + opM(0x04, DirectModify, m(TSB)) + opM(0x05, DirectRead, m(ORA)) + opM(0x06, DirectModify, m(ASL)) + opM(0x07, IndirectLongRead, m(ORA)) + opA(0x08, Push8, (r16)P) + opM(0x09, ImmediateRead, m(ORA)) + opM(0x0a, ImpliedModify, m(ASL), A) + opA(0x0b, PushD) + opM(0x0c, BankModify, m(TSB)) + opM(0x0d, BankRead, m(ORA)) + opM(0x0e, BankModify, m(ASL)) + opM(0x0f, LongRead, m(ORA)) + opA(0x10, Branch, NF == 0) + opM(0x11, IndirectIndexedRead, m(ORA)) + opM(0x12, IndirectRead, m(ORA)) + opM(0x13, IndirectStackRead, m(ORA)) + opM(0x14, DirectModify, m(TRB)) + opM(0x15, DirectRead, m(ORA), X) + opM(0x16, DirectIndexedModify, m(ASL)) + opM(0x17, IndirectLongRead, m(ORA), Y) + opA(0x18, ClearFlag, CF) + opM(0x19, BankRead, m(ORA), Y) + opM(0x1a, ImpliedModify, m(INC), A) + opA(0x1b, TransferCS) + opM(0x1c, BankModify, m(TRB)) + opM(0x1d, BankRead, m(ORA), X) + opM(0x1e, BankIndexedModify, m(ASL)) + opM(0x1f, LongRead, m(ORA), X) + opA(0x20, CallShort) + opM(0x21, IndexedIndirectRead, m(AND)) + opA(0x22, CallLong) + opM(0x23, StackRead, m(AND)) + opM(0x24, DirectRead, m(BIT)) + opM(0x25, DirectRead, m(AND)) + opM(0x26, DirectModify, m(ROL)) + opM(0x27, IndirectLongRead, m(AND)) + opA(0x28, PullP) + opM(0x29, ImmediateRead, m(AND)) + opM(0x2a, ImpliedModify, m(ROL), A) + opA(0x2b, PullD) + opM(0x2c, BankRead, m(BIT)) + opM(0x2d, BankRead, m(AND)) + opM(0x2e, BankModify, m(ROL)) + opM(0x2f, LongRead, m(AND)) + opA(0x30, Branch, NF == 1) + opM(0x31, IndirectIndexedRead, m(AND)) + opM(0x32, IndirectRead, m(AND)) + opM(0x33, IndirectStackRead, m(AND)) + opM(0x34, DirectRead, m(BIT), X) + opM(0x35, DirectRead, m(AND), X) + opM(0x36, DirectIndexedModify, m(ROL)) + opM(0x37, IndirectLongRead, m(AND), Y) + opA(0x38, SetFlag, CF) + opM(0x39, BankRead, m(AND), Y) + opM(0x3a, ImpliedModify, m(DEC), A) + opA(0x3b, Transfer16, S, A) + opM(0x3c, BankRead, m(BIT), X) + opM(0x3d, BankRead, m(AND), X) + opM(0x3e, BankIndexedModify, m(ROL)) + opM(0x3f, LongRead, m(AND), X) + opA(0x40, ReturnInterrupt) + opM(0x41, IndexedIndirectRead, m(EOR)) + opA(0x42, Prefix) + opM(0x43, StackRead, m(EOR)) + opX(0x44, BlockMove, -1) + opM(0x45, DirectRead, m(EOR)) + opM(0x46, DirectModify, m(LSR)) + opM(0x47, IndirectLongRead, m(EOR)) + opM(0x48, Push, A) + opM(0x49, ImmediateRead, m(EOR)) + opM(0x4a, ImpliedModify, m(LSR), A) + opA(0x4b, Push8, (r16)PC.b) + opA(0x4c, JumpShort) + opM(0x4d, BankRead, m(EOR)) + opM(0x4e, BankModify, m(LSR)) + opM(0x4f, LongRead, m(EOR)) + opA(0x50, Branch, VF == 0) + opM(0x51, IndirectIndexedRead, m(EOR)) + opM(0x52, IndirectRead, m(EOR)) + opM(0x53, IndirectStackRead, m(EOR)) + opX(0x54, BlockMove, +1) + opM(0x55, DirectRead, m(EOR), X) + opM(0x56, DirectIndexedModify, m(LSR)) + opM(0x57, IndirectLongRead, m(EOR), Y) + opA(0x58, ClearFlag, IF) + opM(0x59, BankRead, m(EOR), Y) + opX(0x5a, Push, Y) + opA(0x5b, Transfer16, A, D) + opA(0x5c, JumpLong) + opM(0x5d, BankRead, m(EOR), X) + opM(0x5e, BankIndexedModify, m(LSR)) + opM(0x5f, LongRead, m(EOR), X) + opA(0x60, ReturnShort) + opM(0x61, IndexedIndirectRead, m(ADC)) + opA(0x62, PushEffectiveRelativeAddress) + opM(0x63, StackRead, m(ADC)) + opM(0x64, DirectWrite, Z) + opM(0x65, DirectRead, m(ADC)) + opM(0x66, DirectModify, m(ROR)) + opM(0x67, IndirectLongRead, m(ADC)) + opM(0x68, Pull, A) + opM(0x69, ImmediateRead, m(ADC)) + opM(0x6a, ImpliedModify, m(ROR), A) + opA(0x6b, ReturnLong) + opA(0x6c, JumpIndirect) + opM(0x6d, BankRead, m(ADC)) + opM(0x6e, BankModify, m(ROR)) + opM(0x6f, LongRead, m(ADC)) + opA(0x70, Branch, VF == 1) + opM(0x71, IndirectIndexedRead, m(ADC)) + opM(0x72, IndirectRead, m(ADC)) + opM(0x73, IndirectStackRead, m(ADC)) + opM(0x74, DirectWrite, Z, X) + opM(0x75, DirectRead, m(ADC), X) + opM(0x76, DirectIndexedModify, m(ROR)) + opM(0x77, IndirectLongRead, m(ADC), Y) + opA(0x78, SetFlag, IF) + opM(0x79, BankRead, m(ADC), Y) + opX(0x7a, Pull, Y) + opA(0x7b, Transfer16, D, A) + opA(0x7c, JumpIndexedIndirect) + opM(0x7d, BankRead, m(ADC), X) + opM(0x7e, BankIndexedModify, m(ROR)) + opM(0x7f, LongRead, m(ADC), X) + opA(0x80, Branch) + opM(0x81, IndexedIndirectWrite) + opA(0x82, BranchLong) + opM(0x83, StackWrite) + opX(0x84, DirectWrite, Y) + opM(0x85, DirectWrite, A) + opX(0x86, DirectWrite, X) + opM(0x87, IndirectLongWrite) + opX(0x88, ImpliedModify, x(DEC), Y) + opM(0x89, BitImmediate) + opM(0x8a, Transfer, X, A) + opA(0x8b, Push8, (r16)B) + opX(0x8c, BankWrite, Y) + opM(0x8d, BankWrite, A) + opX(0x8e, BankWrite, X) + opM(0x8f, LongWrite) + opA(0x90, Branch, CF == 0) + opM(0x91, IndirectIndexedWrite) + opM(0x92, IndirectWrite) + opM(0x93, IndirectStackWrite) + opX(0x94, DirectWrite, Y, X) + opM(0x95, DirectWrite, A, X) + opX(0x96, DirectWrite, X, Y) + opM(0x97, IndirectLongWrite, Y) + opM(0x98, Transfer, Y, A) + opM(0x99, BankWrite, A, Y) + opA(0x9a, TransferXS) + opX(0x9b, Transfer, X, Y) + opM(0x9c, BankWrite, Z) + opM(0x9d, BankWrite, A, X) + opM(0x9e, BankWrite, Z, X) + opM(0x9f, LongWrite, X) + opX(0xa0, ImmediateRead, x(LDY)) + opM(0xa1, IndexedIndirectRead, m(LDA)) + opX(0xa2, ImmediateRead, x(LDX)) + opM(0xa3, StackRead, m(LDA)) + opX(0xa4, DirectRead, x(LDY)) + opM(0xa5, DirectRead, m(LDA)) + opX(0xa6, DirectRead, x(LDX)) + opM(0xa7, IndirectLongRead, m(LDA)) + opX(0xa8, Transfer, A, Y) + opM(0xa9, ImmediateRead, m(LDA)) + opX(0xaa, Transfer, A, X) + opA(0xab, PullB) + opX(0xac, BankRead, x(LDY)) + opM(0xad, BankRead, m(LDA)) + opX(0xae, BankRead, x(LDX)) + opM(0xaf, LongRead, m(LDA)) + opA(0xb0, Branch, CF == 1) + opM(0xb1, IndirectIndexedRead, m(LDA)) + opM(0xb2, IndirectRead, m(LDA)) + opM(0xb3, IndirectStackRead, m(LDA)) + opX(0xb4, DirectRead, x(LDY), X) + opM(0xb5, DirectRead, m(LDA), X) + opX(0xb6, DirectRead, x(LDX), Y) + opM(0xb7, IndirectLongRead, m(LDA), Y) + opA(0xb8, ClearFlag, VF) + opM(0xb9, BankRead, m(LDA), Y) + opX(0xba, TransferSX) + opX(0xbb, Transfer, Y, X) + opX(0xbc, BankRead, x(LDY), X) + opM(0xbd, BankRead, m(LDA), X) + opX(0xbe, BankRead, x(LDX), Y) + opM(0xbf, LongRead, m(LDA), X) + opX(0xc0, ImmediateRead, x(CPY)) + opM(0xc1, IndexedIndirectRead, m(CMP)) + opA(0xc2, ResetP) + opM(0xc3, StackRead, m(CMP)) + opX(0xc4, DirectRead, x(CPY)) + opM(0xc5, DirectRead, m(CMP)) + opM(0xc6, DirectModify, m(DEC)) + opM(0xc7, IndirectLongRead, m(CMP)) + opX(0xc8, ImpliedModify, x(INC), Y) + opM(0xc9, ImmediateRead, m(CMP)) + opX(0xca, ImpliedModify, x(DEC), X) + opA(0xcb, Wait) + opX(0xcc, BankRead, x(CPY)) + opM(0xcd, BankRead, m(CMP)) + opM(0xce, BankModify, m(DEC)) + opM(0xcf, LongRead, m(CMP)) + opA(0xd0, Branch, ZF == 0) + opM(0xd1, IndirectIndexedRead, m(CMP)) + opM(0xd2, IndirectRead, m(CMP)) + opM(0xd3, IndirectStackRead, m(CMP)) + opA(0xd4, PushEffectiveIndirectAddress) + opM(0xd5, DirectRead, m(CMP), X) + opM(0xd6, DirectIndexedModify, m(DEC)) + opM(0xd7, IndirectLongRead, m(CMP), Y) + opA(0xd8, ClearFlag, DF) + opM(0xd9, BankRead, m(CMP), Y) + opX(0xda, Push, X) + opA(0xdb, Stop) + opA(0xdc, JumpIndirectLong) + opM(0xdd, BankRead, m(CMP), X) + opM(0xde, BankIndexedModify, m(DEC)) + opM(0xdf, LongRead, m(CMP), X) + opX(0xe0, ImmediateRead, x(CPX)) + opM(0xe1, IndexedIndirectRead, m(SBC)) + opA(0xe2, SetP) + opM(0xe3, StackRead, m(SBC)) + opX(0xe4, DirectRead, x(CPX)) + opM(0xe5, DirectRead, m(SBC)) + opM(0xe6, DirectModify, m(INC)) + opM(0xe7, IndirectLongRead, m(SBC)) + opX(0xe8, ImpliedModify, x(INC), X) + opM(0xe9, ImmediateRead, m(SBC)) + opA(0xea, NoOperation) + opA(0xeb, ExchangeBA) + opX(0xec, BankRead, x(CPX)) + opM(0xed, BankRead, m(SBC)) + opM(0xee, BankModify, m(INC)) + opM(0xef, LongRead, m(SBC)) + opA(0xf0, Branch, ZF == 1) + opM(0xf1, IndirectIndexedRead, m(SBC)) + opM(0xf2, IndirectRead, m(SBC)) + opM(0xf3, IndirectStackRead, m(SBC)) + opA(0xf4, PushEffectiveAddress) + opM(0xf5, DirectRead, m(SBC), X) + opM(0xf6, DirectIndexedModify, m(INC)) + opM(0xf7, IndirectLongRead, m(SBC), Y) + opA(0xf8, SetFlag, DF) + opM(0xf9, BankRead, m(SBC), Y) + opX(0xfa, Pull, X) + opA(0xfb, ExchangeCE) + opA(0xfc, CallIndexedIndirect) + opM(0xfd, BankRead, m(SBC), X) + opM(0xfe, BankIndexedModify, m(INC)) + opM(0xff, LongRead, m(SBC), X) + } diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-modify.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-modify.cpp new file mode 100644 index 0000000000..9359352a22 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-modify.cpp @@ -0,0 +1,93 @@ +auto WDC65816::instructionImpliedModify8(alu8 op, r16& M) -> void { +L idleIRQ(); + M.l = alu(M.l); +} + +auto WDC65816::instructionImpliedModify16(alu16 op, r16& M) -> void { +L idleIRQ(); + M.w = alu(M.w); +} + +auto WDC65816::instructionBankModify8(alu8 op) -> void { + V.l = fetch(); + V.h = fetch(); + W.l = readBank(V.w + 0); + idle(); + W.l = alu(W.l); +L writeBank(V.w + 0, W.l); +} + +auto WDC65816::instructionBankModify16(alu16 op) -> void { + V.l = fetch(); + V.h = fetch(); + W.l = readBank(V.w + 0); + W.h = readBank(V.w + 1); + idle(); + W.w = alu(W.w); + writeBank(V.w + 1, W.h); +L writeBank(V.w + 0, W.l); +} + +auto WDC65816::instructionBankIndexedModify8(alu8 op) -> void { + V.l = fetch(); + V.h = fetch(); + idle(); + W.l = readBank(V.w + X.w + 0); + idle(); + W.l = alu(W.l); +L writeBank(V.w + X.w + 0, W.l); +} + +auto WDC65816::instructionBankIndexedModify16(alu16 op) -> void { + V.l = fetch(); + V.h = fetch(); + idle(); + W.l = readBank(V.w + X.w + 0); + W.h = readBank(V.w + X.w + 1); + idle(); + W.w = alu(W.w); + writeBank(V.w + X.w + 1, W.h); +L writeBank(V.w + X.w + 0, W.l); +} + +auto WDC65816::instructionDirectModify8(alu8 op) -> void { + U.l = fetch(); + idle2(); + W.l = readDirect(U.l + 0); + idle(); + W.l = alu(W.l); +L writeDirect(U.l + 0, W.l); +} + +auto WDC65816::instructionDirectModify16(alu16 op) -> void { + U.l = fetch(); + idle2(); + W.l = readDirect(U.l + 0); + W.h = readDirect(U.l + 1); + idle(); + W.w = alu(W.w); + writeDirect(U.l + 1, W.h); +L writeDirect(U.l + 0, W.l); +} + +auto WDC65816::instructionDirectIndexedModify8(alu8 op) -> void { + U.l = fetch(); + idle2(); + idle(); + W.l = readDirect(U.l + X.w + 0); + idle(); + W.l = alu(W.l); +L writeDirect(U.l + X.w + 0, W.l); +} + +auto WDC65816::instructionDirectIndexedModify16(alu16 op) -> void { + U.l = fetch(); + idle2(); + idle(); + W.l = readDirect(U.l + X.w + 0); + W.h = readDirect(U.l + X.w + 1); + idle(); + W.w = alu(W.w); + writeDirect(U.l + X.w + 1, W.h); +L writeDirect(U.l + X.w + 0, W.l); +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-other.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-other.cpp new file mode 100644 index 0000000000..c7290eaa63 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-other.cpp @@ -0,0 +1,247 @@ +auto WDC65816::instructionBitImmediate8() -> void { +L U.l = fetch(); + ZF = (U.l & A.l) == 0; +} + +auto WDC65816::instructionBitImmediate16() -> void { + U.l = fetch(); +L U.h = fetch(); + ZF = (U.w & A.w) == 0; +} + +auto WDC65816::instructionNoOperation() -> void { +L idleIRQ(); +} + +auto WDC65816::instructionPrefix() -> void { +L fetch(); +} + +auto WDC65816::instructionExchangeBA() -> void { + idle(); +L idle(); + A.w = A.w >> 8 | A.w << 8; + ZF = A.l == 0; + NF = A.l & 0x80; +} + +auto WDC65816::instructionBlockMove8(int adjust) -> void { + U.b = fetch(); + V.b = fetch(); + B = U.b; + W.l = read(V.b << 16 | X.w); + write(U.b << 16 | Y.w, W.l); + idle(); + X.l += adjust; + Y.l += adjust; +L idle(); + if(A.w--) PC.w -= 3; +} + +auto WDC65816::instructionBlockMove16(int adjust) -> void { + U.b = fetch(); + V.b = fetch(); + B = U.b; + W.l = read(V.b << 16 | X.w); + write(U.b << 16 | Y.w, W.l); + idle(); + X.w += adjust; + Y.w += adjust; +L idle(); + if(A.w--) PC.w -= 3; +} + +auto WDC65816::instructionInterrupt(r16 vector) -> void { + fetch(); +N push(PC.b); + push(PC.h); + push(PC.l); + push(P); + IF = 1; + DF = 0; + PC.l = read(vector.w + 0); +L PC.h = read(vector.w + 1); + PC.b = 0x00; +} + +auto WDC65816::instructionStop() -> void { + r.stp = true; + while(r.stp && !synchronizing()) { +L idle(); + } +} + +auto WDC65816::instructionWait() -> void { + r.wai = true; + while(r.wai && !synchronizing()) { +L idle(); + } + idle(); +} + +auto WDC65816::instructionExchangeCE() -> void { +L idleIRQ(); + swap(CF, EF); + if(EF) { + XF = 1; + MF = 1; + X.h = 0x00; + Y.h = 0x00; + S.h = 0x01; + } +} + +auto WDC65816::instructionSetFlag(bool& flag) -> void { +L idleIRQ(); + flag = 1; +} + +auto WDC65816::instructionClearFlag(bool& flag) -> void { +L idleIRQ(); + flag = 0; +} + +auto WDC65816::instructionResetP() -> void { + W.l = fetch(); +L idle(); + P = P & ~W.l; +E XF = 1, MF = 1; + if(XF) X.h = 0x00, Y.h = 0x00; +} + +auto WDC65816::instructionSetP() -> void { + W.l = fetch(); +L idle(); + P = P | W.l; +E XF = 1, MF = 1; + if(XF) X.h = 0x00, Y.h = 0x00; +} + +auto WDC65816::instructionTransfer8(r16 F, r16& T) -> void { +L idleIRQ(); + T.l = F.l; + ZF = T.l == 0; + NF = T.l & 0x80; +} + +auto WDC65816::instructionTransfer16(r16 F, r16& T) -> void { +L idleIRQ(); + T.w = F.w; + ZF = T.w == 0; + NF = T.w & 0x8000; +} + +auto WDC65816::instructionTransferCS() -> void { +L idleIRQ(); + S.w = A.w; +E S.h = 0x01; +} + +auto WDC65816::instructionTransferSX8() -> void { +L idleIRQ(); + X.l = S.l; + ZF = X.l == 0; + NF = X.l & 0x80; +} + +auto WDC65816::instructionTransferSX16() -> void { +L idleIRQ(); + X.w = S.w; + ZF = X.w == 0; + NF = X.w & 0x8000; +} + +auto WDC65816::instructionTransferXS() -> void { +L idleIRQ(); +E S.l = X.l; +N S.w = X.w; +} + +auto WDC65816::instructionPush8(r16 F) -> void { + idle(); +L push(F.l); +} + +auto WDC65816::instructionPush16(r16 F) -> void { + idle(); + push(F.h); +L push(F.l); +} + +auto WDC65816::instructionPushD() -> void { + idle(); + pushN(D.h); +L pushN(D.l); +E S.h = 0x01; +} + +auto WDC65816::instructionPull8(r16& T) -> void { + idle(); + idle(); +L T.l = pull(); + ZF = T.l == 0; + NF = T.l & 0x80; +} + +auto WDC65816::instructionPull16(r16& T) -> void { + idle(); + idle(); + T.l = pull(); +L T.h = pull(); + ZF = T.w == 0; + NF = T.w & 0x8000; +} + +auto WDC65816::instructionPullD() -> void { + idle(); + idle(); + D.l = pullN(); +L D.h = pullN(); + ZF = D.w == 0; + NF = D.w & 0x8000; +E S.h = 0x01; +} + +auto WDC65816::instructionPullB() -> void { + idle(); + idle(); +L B = pull(); + ZF = B == 0; + NF = B & 0x80; +} + +auto WDC65816::instructionPullP() -> void { + idle(); + idle(); +L P = pull(); +E XF = 1, MF = 1; + if(XF) X.h = 0x00, Y.h = 0x00; +} + +auto WDC65816::instructionPushEffectiveAddress() -> void { + W.l = fetch(); + W.h = fetch(); + pushN(W.h); +L pushN(W.l); +E S.h = 0x01; +} + +auto WDC65816::instructionPushEffectiveIndirectAddress() -> void { + U.l = fetch(); + idle2(); + W.l = readDirectN(U.l + 0); + W.h = readDirectN(U.l + 1); + pushN(W.h); +L pushN(W.l); +E S.h = 0x01; +} + +auto WDC65816::instructionPushEffectiveRelativeAddress() -> void { + V.l = fetch(); + V.h = fetch(); + idle(); + W.w = PC.d + (int16)V.w; + pushN(W.h); +L pushN(W.l); +E S.h = 0x01; +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-pc.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-pc.cpp new file mode 100644 index 0000000000..beb6f7c1d3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-pc.cpp @@ -0,0 +1,142 @@ +auto WDC65816::instructionBranch(bool take) -> void { + if(!take) { +L fetch(); + } else { + U.l = fetch(); + V.w = PC.d + (int8)U.l; + idle6(V.w); +L idle(); + PC.w = V.w; + idleBranch(); + } +} + +auto WDC65816::instructionBranchLong() -> void { + U.l = fetch(); + U.h = fetch(); + V.w = PC.d + (int16)U.w; +L idle(); + PC.w = V.w; + idleBranch(); +} + +auto WDC65816::instructionJumpShort() -> void { + W.l = fetch(); +L W.h = fetch(); + PC.w = W.w; + idleJump(); +} + +auto WDC65816::instructionJumpLong() -> void { + V.l = fetch(); + V.h = fetch(); +L V.b = fetch(); + PC.d = V.d; + idleJump(); +} + +auto WDC65816::instructionJumpIndirect() -> void { + V.l = fetch(); + V.h = fetch(); + W.l = read(uint16(V.w + 0)); +L W.h = read(uint16(V.w + 1)); + PC.w = W.w; + idleJump(); +} + +auto WDC65816::instructionJumpIndexedIndirect() -> void { + V.l = fetch(); + V.h = fetch(); + idle(); + W.l = read(PC.b << 16 | uint16(V.w + X.w + 0)); +L W.h = read(PC.b << 16 | uint16(V.w + X.w + 1)); + PC.w = W.w; + idleJump(); +} + +auto WDC65816::instructionJumpIndirectLong() -> void { + U.l = fetch(); + U.h = fetch(); + V.l = read(uint16(U.w + 0)); + V.h = read(uint16(U.w + 1)); +L V.b = read(uint16(U.w + 2)); + PC.d = V.d; + idleJump(); +} + +auto WDC65816::instructionCallShort() -> void { + W.l = fetch(); + W.h = fetch(); + idle(); + PC.w--; + push(PC.h); +L push(PC.l); + PC.w = W.w; + idleJump(); +} + +auto WDC65816::instructionCallLong() -> void { + V.l = fetch(); + V.h = fetch(); + pushN(PC.b); + idle(); + V.b = fetch(); + PC.w--; + pushN(PC.h); +L pushN(PC.l); + PC.d = V.d; +E S.h = 0x01; + idleJump(); +} + +auto WDC65816::instructionCallIndexedIndirect() -> void { + V.l = fetch(); + pushN(PC.h); + pushN(PC.l); + V.h = fetch(); + idle(); + W.l = read(PC.b << 16 | uint16(V.w + X.w + 0)); +L W.h = read(PC.b << 16 | uint16(V.w + X.w + 1)); + PC.w = W.w; +E S.h = 0x01; + idleJump(); +} + +auto WDC65816::instructionReturnInterrupt() -> void { + idle(); + idle(); + P = pull(); +E XF = 1, MF = 1; + if(XF) X.h = 0x00, Y.h = 0x00; + PC.l = pull(); + if(EF) { + L PC.h = pull(); + } else { + PC.h = pull(); + L PC.b = pull(); + } + idleJump(); +} + +auto WDC65816::instructionReturnShort() -> void { + idle(); + idle(); + W.l = pull(); + W.h = pull(); +L idle(); + PC.w = W.w; + PC.w++; + idleJump(); +} + +auto WDC65816::instructionReturnLong() -> void { + idle(); + idle(); + V.l = pullN(); + V.h = pullN(); +L V.b = pullN(); + PC.d = V.d; + PC.w++; +E S.h = 0x01; + idleJump(); +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-read.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-read.cpp new file mode 100644 index 0000000000..e364725b7d --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-read.cpp @@ -0,0 +1,209 @@ +auto WDC65816::instructionImmediateRead8(alu8 op) -> void { +L W.l = fetch(); + alu(W.l); +} + +auto WDC65816::instructionImmediateRead16(alu16 op) -> void { + W.l = fetch(); +L W.h = fetch(); + alu(W.w); +} + +auto WDC65816::instructionBankRead8(alu8 op) -> void { + V.l = fetch(); + V.h = fetch(); +L W.l = readBank(V.w + 0); + alu(W.l); +} + +auto WDC65816::instructionBankRead16(alu16 op) -> void { + V.l = fetch(); + V.h = fetch(); + W.l = readBank(V.w + 0); +L W.h = readBank(V.w + 1); + alu(W.w); +} + +auto WDC65816::instructionBankRead8(alu8 op, r16 I) -> void { + V.l = fetch(); + V.h = fetch(); + idle4(V.w, V.w + I.w); +L W.l = readBank(V.w + I.w + 0); + alu(W.l); +} + +auto WDC65816::instructionBankRead16(alu16 op, r16 I) -> void { + V.l = fetch(); + V.h = fetch(); + idle4(V.w, V.w + I.w); + W.l = readBank(V.w + I.w + 0); +L W.h = readBank(V.w + I.w + 1); + alu(W.w); +} + +auto WDC65816::instructionLongRead8(alu8 op, r16 I) -> void { + V.l = fetch(); + V.h = fetch(); + V.b = fetch(); +L W.l = readLong(V.d + I.w + 0); + alu(W.l); +} + +auto WDC65816::instructionLongRead16(alu16 op, r16 I) -> void { + V.l = fetch(); + V.h = fetch(); + V.b = fetch(); + W.l = readLong(V.d + I.w + 0); +L W.h = readLong(V.d + I.w + 1); + alu(W.w); +} + +auto WDC65816::instructionDirectRead8(alu8 op) -> void { + U.l = fetch(); + idle2(); +L W.l = readDirect(U.l + 0); + alu(W.l); +} + +auto WDC65816::instructionDirectRead16(alu16 op) -> void { + U.l = fetch(); + idle2(); + W.l = readDirect(U.l + 0); +L W.h = readDirect(U.l + 1); + alu(W.w); +} + +auto WDC65816::instructionDirectRead8(alu8 op, r16 I) -> void { + U.l = fetch(); + idle2(); + idle(); +L W.l = readDirect(U.l + I.w + 0); + alu(W.l); +} + +auto WDC65816::instructionDirectRead16(alu16 op, r16 I) -> void { + U.l = fetch(); + idle2(); + idle(); + W.l = readDirect(U.l + I.w + 0); +L W.h = readDirect(U.l + I.w + 1); + alu(W.w); +} + +auto WDC65816::instructionIndirectRead8(alu8 op) -> void { + U.l = fetch(); + idle2(); + V.l = readDirect(U.l + 0); + V.h = readDirect(U.l + 1); +L W.l = readBank(V.w + 0); + alu(W.l); +} + +auto WDC65816::instructionIndirectRead16(alu16 op) -> void { + U.l = fetch(); + idle2(); + V.l = readDirect(U.l + 0); + V.h = readDirect(U.l + 1); + W.l = readBank(V.w + 0); +L W.h = readBank(V.w + 1); + alu(W.w); +} + +auto WDC65816::instructionIndexedIndirectRead8(alu8 op) -> void { + U.l = fetch(); + idle2(); + idle(); + V.l = readDirect(U.l + X.w + 0); + V.h = readDirect(U.l + X.w + 1); +L W.l = readBank(V.w + 0); + alu(W.l); +} + +auto WDC65816::instructionIndexedIndirectRead16(alu16 op) -> void { + U.l = fetch(); + idle2(); + idle(); + V.l = readDirect(U.l + X.w + 0); + V.h = readDirect(U.l + X.w + 1); + W.l = readBank(V.w + 0); +L W.h = readBank(V.w + 1); + alu(W.w); +} + +auto WDC65816::instructionIndirectIndexedRead8(alu8 op) -> void { + U.l = fetch(); + idle2(); + V.l = readDirect(U.l + 0); + V.h = readDirect(U.l + 1); + idle4(V.w, V.w + Y.w); +L W.l = readBank(V.w + Y.w + 0); + alu(W.l); +} + +auto WDC65816::instructionIndirectIndexedRead16(alu16 op) -> void { + U.l = fetch(); + idle2(); + V.l = readDirect(U.l + 0); + V.h = readDirect(U.l + 1); + idle4(V.w, V.w + Y.w); + W.l = readBank(V.w + Y.w + 0); +L W.h = readBank(V.w + Y.w + 1); + alu(W.w); +} + +auto WDC65816::instructionIndirectLongRead8(alu8 op, r16 I) -> void { + U.l = fetch(); + idle2(); + V.l = readDirectN(U.l + 0); + V.h = readDirectN(U.l + 1); + V.b = readDirectN(U.l + 2); +L W.l = readLong(V.d + I.w + 0); + alu(W.l); +} + +auto WDC65816::instructionIndirectLongRead16(alu16 op, r16 I) -> void { + U.l = fetch(); + idle2(); + V.l = readDirectN(U.l + 0); + V.h = readDirectN(U.l + 1); + V.b = readDirectN(U.l + 2); + W.l = readLong(V.d + I.w + 0); +L W.h = readLong(V.d + I.w + 1); + alu(W.w); +} + +auto WDC65816::instructionStackRead8(alu8 op) -> void { + U.l = fetch(); + idle(); +L W.l = readStack(U.l + 0); + alu(W.l); +} + +auto WDC65816::instructionStackRead16(alu16 op) -> void { + U.l = fetch(); + idle(); + W.l = readStack(U.l + 0); +L W.h = readStack(U.l + 1); + alu(W.w); +} + +auto WDC65816::instructionIndirectStackRead8(alu8 op) -> void { + U.l = fetch(); + idle(); + V.l = readStack(U.l + 0); + V.h = readStack(U.l + 1); + idle(); +L W.l = readBank(V.w + Y.w + 0); + alu(W.l); +} + +auto WDC65816::instructionIndirectStackRead16(alu16 op) -> void { + U.l = fetch(); + idle(); + V.l = readStack(U.l + 0); + V.h = readStack(U.l + 1); + idle(); + W.l = readBank(V.w + Y.w + 0); +L W.h = readBank(V.w + Y.w + 1); + alu(W.w); +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-write.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-write.cpp new file mode 100644 index 0000000000..f2851ed688 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/instructions-write.cpp @@ -0,0 +1,176 @@ +auto WDC65816::instructionBankWrite8(r16 F) -> void { + V.l = fetch(); + V.h = fetch(); +L writeBank(V.w + 0, F.l); +} + +auto WDC65816::instructionBankWrite16(r16 F) -> void { + V.l = fetch(); + V.h = fetch(); + writeBank(V.w + 0, F.l); +L writeBank(V.w + 1, F.h); +} + +auto WDC65816::instructionBankWrite8(r16 F, r16 I) -> void { + V.l = fetch(); + V.h = fetch(); + idle(); +L writeBank(V.w + I.w + 0, F.l); +} + +auto WDC65816::instructionBankWrite16(r16 F, r16 I) -> void { + V.l = fetch(); + V.h = fetch(); + idle(); + writeBank(V.w + I.w + 0, F.l); +L writeBank(V.w + I.w + 1, F.h); +} + +auto WDC65816::instructionLongWrite8(r16 I) -> void { + V.l = fetch(); + V.h = fetch(); + V.b = fetch(); +L writeLong(V.d + I.w + 0, A.l); +} + +auto WDC65816::instructionLongWrite16(r16 I) -> void { + V.l = fetch(); + V.h = fetch(); + V.b = fetch(); + writeLong(V.d + I.w + 0, A.l); +L writeLong(V.d + I.w + 1, A.h); +} + +auto WDC65816::instructionDirectWrite8(r16 F) -> void { + U.l = fetch(); + idle2(); +L writeDirect(U.l + 0, F.l); +} + +auto WDC65816::instructionDirectWrite16(r16 F) -> void { + U.l = fetch(); + idle2(); + writeDirect(U.l + 0, F.l); +L writeDirect(U.l + 1, F.h); +} + +auto WDC65816::instructionDirectWrite8(r16 F, r16 I) -> void { + U.l = fetch(); + idle2(); + idle(); +L writeDirect(U.l + I.w + 0, F.l); +} + +auto WDC65816::instructionDirectWrite16(r16 F, r16 I) -> void { + U.l = fetch(); + idle2(); + idle(); + writeDirect(U.l + I.w + 0, F.l); +L writeDirect(U.l + I.w + 1, F.h); +} + +auto WDC65816::instructionIndirectWrite8() -> void { + U.l = fetch(); + idle2(); + V.l = readDirect(U.l + 0); + V.h = readDirect(U.l + 1); +L writeBank(V.w + 0, A.l); +} + +auto WDC65816::instructionIndirectWrite16() -> void { + U.l = fetch(); + idle2(); + V.l = readDirect(U.l + 0); + V.h = readDirect(U.l + 1); + writeBank(V.w + 0, A.l); +L writeBank(V.w + 1, A.h); +} + +auto WDC65816::instructionIndexedIndirectWrite8() -> void { + U.l = fetch(); + idle2(); + idle(); + V.l = readDirect(U.l + X.w + 0); + V.h = readDirect(U.l + X.w + 1); +L writeBank(V.w + 0, A.l); +} + +auto WDC65816::instructionIndexedIndirectWrite16() -> void { + U.l = fetch(); + idle2(); + idle(); + V.l = readDirect(U.l + X.w + 0); + V.h = readDirect(U.l + X.w + 1); + writeBank(V.w + 0, A.l); +L writeBank(V.w + 1, A.h); +} + +auto WDC65816::instructionIndirectIndexedWrite8() -> void { + U.l = fetch(); + idle2(); + V.l = readDirect(U.l + 0); + V.h = readDirect(U.l + 1); + idle(); +L writeBank(V.w + Y.w + 0, A.l); +} + +auto WDC65816::instructionIndirectIndexedWrite16() -> void { + U.l = fetch(); + idle2(); + V.l = readDirect(U.l + 0); + V.h = readDirect(U.l + 1); + idle(); + writeBank(V.w + Y.w + 0, A.l); +L writeBank(V.w + Y.w + 1, A.h); +} + +auto WDC65816::instructionIndirectLongWrite8(r16 I) -> void { + U.l = fetch(); + idle2(); + V.l = readDirectN(U.l + 0); + V.h = readDirectN(U.l + 1); + V.b = readDirectN(U.l + 2); +L writeLong(V.d + I.w + 0, A.l); +} + +auto WDC65816::instructionIndirectLongWrite16(r16 I) -> void { + U.l = fetch(); + idle2(); + V.l = readDirectN(U.l + 0); + V.h = readDirectN(U.l + 1); + V.b = readDirectN(U.l + 2); + writeLong(V.d + I.w + 0, A.l); +L writeLong(V.d + I.w + 1, A.h); +} + +auto WDC65816::instructionStackWrite8() -> void { + U.l = fetch(); + idle(); +L writeStack(U.l + 0, A.l); +} + +auto WDC65816::instructionStackWrite16() -> void { + U.l = fetch(); + idle(); + writeStack(U.l + 0, A.l); +L writeStack(U.l + 1, A.h); +} + +auto WDC65816::instructionIndirectStackWrite8() -> void { + U.l = fetch(); + idle(); + V.l = readStack(U.l + 0); + V.h = readStack(U.l + 1); + idle(); +L writeBank(V.w + Y.w + 0, A.l); +} + +auto WDC65816::instructionIndirectStackWrite16() -> void { + U.l = fetch(); + idle(); + V.l = readStack(U.l + 0); + V.h = readStack(U.l + 1); + idle(); + writeBank(V.w + Y.w + 0, A.l); +L writeBank(V.w + Y.w + 1, A.h); +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/memory.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/memory.cpp new file mode 100644 index 0000000000..4f6037407b --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/memory.cpp @@ -0,0 +1,88 @@ +//immediate, 2-cycle opcodes with idle cycle will become bus read +//when an IRQ is to be triggered immediately after opcode completion. +//this affects the following opcodes: +// clc, cld, cli, clv, sec, sed, sei, +// tax, tay, txa, txy, tya, tyx, +// tcd, tcs, tdc, tsc, tsx, txs, +// inc, inx, iny, dec, dex, dey, +// asl, lsr, rol, ror, nop, xce. +auto WDC65816::idleIRQ() -> void { + if(interruptPending()) { + //modify I/O cycle to bus read cycle, do not increment PC + read(PC.d); + } else { + idle(); + } +} + +auto WDC65816::idle2() -> void { + if(D.l) idle(); +} + +auto WDC65816::idle4(uint16 x, uint16 y) -> void { + if(!XF || x >> 8 != y >> 8) idle(); +} + +auto WDC65816::idle6(uint16 address) -> void { + if(EF && PC.h != address >> 8) idle(); +} + +auto WDC65816::fetch() -> uint8 { + return read(PC.b << 16 | PC.w++); +} + +auto WDC65816::pull() -> uint8 { + EF ? (void)S.l++ : (void)S.w++; + return read(S.w); +} + +auto WDC65816::push(uint8 data) -> void { + write(S.w, data); + EF ? (void)S.l-- : (void)S.w--; +} + +auto WDC65816::pullN() -> uint8 { + return read(++S.w); +} + +auto WDC65816::pushN(uint8 data) -> void { + write(S.w--, data); +} + +auto WDC65816::readDirect(uint address) -> uint8 { + if(EF && !D.l) return read(D.w | address & 0xff); + return read(D.w + address & 0xffff); +} + +auto WDC65816::writeDirect(uint address, uint8 data) -> void { + if(EF && !D.l) return write(D.w | address & 0xff, data); + write(D.w + address & 0xffff, data); +} + +auto WDC65816::readDirectN(uint address) -> uint8 { + return read(D.w + address & 0xffff); +} + +auto WDC65816::readBank(uint address) -> uint8 { + return read((B << 16) + address & 0xffffff); +} + +auto WDC65816::writeBank(uint address, uint8 data) -> void { + write((B << 16) + address & 0xffffff, data); +} + +auto WDC65816::readLong(uint address) -> uint8 { + return read(address & 0xffffff); +} + +auto WDC65816::writeLong(uint address, uint8 data) -> void { + write(address & 0xffffff, data); +} + +auto WDC65816::readStack(uint address) -> uint8 { + return read(S.w + address & 0xffff); +} + +auto WDC65816::writeStack(uint address, uint8 data) -> void { + write(S.w + address & 0xffff, data); +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/registers.hpp b/waterbox/bsnescore/bsnes/processor/wdc65816/registers.hpp new file mode 100644 index 0000000000..4b07a534f0 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/registers.hpp @@ -0,0 +1,65 @@ +#if !defined(WDC65816_REGISTERS_HPP) + #define WDC65816_REGISTERS_HPP + + #define PC r.pc + #define A r.a + #define X r.x + #define Y r.y + #define Z r.z + #define S r.s + #define D r.d + #define B r.b + #define P r.p + + #define CF r.p.c + #define ZF r.p.z + #define IF r.p.i + #define DF r.p.d + #define XF r.p.x + #define MF r.p.m + #define VF r.p.v + #define NF r.p.n + #define EF r.e + + #define U r.u + #define V r.v + #define W r.w + + #define E if(r.e) + #define N if(!r.e) + #define L lastCycle(); + + #define alu(...) (this->*op)(__VA_ARGS__) +#else + #undef WDC65816_REGISTERS_HPP + + #undef PC + #undef A + #undef X + #undef Y + #undef Z + #undef S + #undef D + #undef B + #undef P + + #undef CF + #undef ZF + #undef IF + #undef DF + #undef XF + #undef MF + #undef VF + #undef NF + #undef EF + + #undef U + #undef V + #undef W + + #undef E + #undef N + #undef L + + #undef alu +#endif diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/serialization.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/serialization.cpp new file mode 100644 index 0000000000..2b9ae93519 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/serialization.cpp @@ -0,0 +1,33 @@ +auto WDC65816::serialize(serializer& s) -> void { + s.integer(r.pc.d); + + s.integer(r.a.w); + s.integer(r.x.w); + s.integer(r.y.w); + s.integer(r.z.w); + s.integer(r.s.w); + s.integer(r.d.w); + s.integer(r.b); + + s.integer(r.p.c); + s.integer(r.p.z); + s.integer(r.p.i); + s.integer(r.p.d); + s.integer(r.p.x); + s.integer(r.p.m); + s.integer(r.p.v); + s.integer(r.p.n); + + s.integer(r.e); + s.integer(r.irq); + s.integer(r.wai); + s.integer(r.stp); + + s.integer(r.vector); + s.integer(r.mar); + s.integer(r.mdr); + + s.integer(r.u.d); + s.integer(r.v.d); + s.integer(r.w.d); +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/wdc65816.cpp b/waterbox/bsnescore/bsnes/processor/wdc65816/wdc65816.cpp new file mode 100644 index 0000000000..5481693b6d --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/wdc65816.cpp @@ -0,0 +1,41 @@ +#include +#include "wdc65816.hpp" + +namespace Processor { + +#include "registers.hpp" +#include "memory.cpp" +#include "algorithms.cpp" + +#include "instructions-read.cpp" +#include "instructions-write.cpp" +#include "instructions-modify.cpp" +#include "instructions-pc.cpp" +#include "instructions-other.cpp" +#include "instruction.cpp" + +auto WDC65816::power() -> void { + r.pc.d = 0x000000; + r.a = 0x0000; + r.x = 0x0000; + r.y = 0x0000; + r.s = 0x01ff; + r.d = 0x0000; + r.b = 0x00; + r.p = 0x34; + r.e = 1; + + r.irq = 0; + r.wai = 0; + r.stp = 0; + r.mar = 0x000000; + r.mdr = 0x00; + + r.vector = 0xfffc; //reset vector address +} + +#include "registers.hpp" +#include "serialization.cpp" +#include "disassembler.cpp" + +} diff --git a/waterbox/bsnescore/bsnes/processor/wdc65816/wdc65816.hpp b/waterbox/bsnescore/bsnes/processor/wdc65816/wdc65816.hpp new file mode 100644 index 0000000000..e4df741b49 --- /dev/null +++ b/waterbox/bsnescore/bsnes/processor/wdc65816/wdc65816.hpp @@ -0,0 +1,288 @@ +//WDC 65C816 CPU core +//* Ricoh 5A22 +//* Nintendo SA-1 + +#pragma once + +namespace Processor { + +struct WDC65816 { + virtual auto idle() -> void = 0; + virtual auto idleBranch() -> void {} + virtual auto idleJump() -> void {} + virtual auto read(uint addr) -> uint8 = 0; + virtual auto write(uint addr, uint8 data) -> void = 0; + virtual auto lastCycle() -> void = 0; + virtual auto interruptPending() const -> bool = 0; + virtual auto interrupt() -> void; + virtual auto synchronizing() const -> bool = 0; + + virtual auto readDisassembler(uint addr) -> uint8 { return 0; } + + inline auto irq() const -> bool { return r.irq; } + inline auto irq(bool line) -> void { r.irq = line; } + + using r8 = uint8; + + union r16 { + inline r16() : w(0) {} + inline r16(uint data) : w(data) {} + inline auto& operator=(uint data) { w = data; return *this; } + + uint16 w; + struct { uint8 order_lsb2(l, h); }; + }; + + union r24 { + inline r24() : d(0) {} + inline r24(uint data) : d(data) {} + inline auto& operator=(uint data) { d = data; return *this; } + + uint24 d; + struct { uint16 order_lsb2(w, x); }; + struct { uint8 order_lsb4(l, h, b, y); }; + }; + + //wdc65816.cpp + auto power() -> void; + + //memory.cpp + alwaysinline auto idleIRQ() -> void; + alwaysinline auto idle2() -> void; + alwaysinline auto idle4(uint16 x, uint16 y) -> void; + alwaysinline auto idle6(uint16 address) -> void; + alwaysinline auto fetch() -> uint8; + alwaysinline auto pull() -> uint8; + auto push(uint8 data) -> void; + alwaysinline auto pullN() -> uint8; + alwaysinline auto pushN(uint8 data) -> void; + alwaysinline auto readDirect(uint address) -> uint8; + alwaysinline auto writeDirect(uint address, uint8 data) -> void; + alwaysinline auto readDirectN(uint address) -> uint8; + alwaysinline auto readBank(uint address) -> uint8; + alwaysinline auto writeBank(uint address, uint8 data) -> void; + alwaysinline auto readLong(uint address) -> uint8; + alwaysinline auto writeLong(uint address, uint8 data) -> void; + alwaysinline auto readStack(uint address) -> uint8; + alwaysinline auto writeStack(uint address, uint8 data) -> void; + + //algorithms.cpp + using alu8 = auto (WDC65816::*)( uint8) -> uint8; + using alu16 = auto (WDC65816::*)(uint16) -> uint16; + + auto algorithmADC8(uint8) -> uint8; + auto algorithmADC16(uint16) -> uint16; + auto algorithmAND8(uint8) -> uint8; + auto algorithmAND16(uint16) -> uint16; + auto algorithmASL8(uint8) -> uint8; + auto algorithmASL16(uint16) -> uint16; + auto algorithmBIT8(uint8) -> uint8; + auto algorithmBIT16(uint16) -> uint16; + auto algorithmCMP8(uint8) -> uint8; + auto algorithmCMP16(uint16) -> uint16; + auto algorithmCPX8(uint8) -> uint8; + auto algorithmCPX16(uint16) -> uint16; + auto algorithmCPY8(uint8) -> uint8; + auto algorithmCPY16(uint16) -> uint16; + auto algorithmDEC8(uint8) -> uint8; + auto algorithmDEC16(uint16) -> uint16; + auto algorithmEOR8(uint8) -> uint8; + auto algorithmEOR16(uint16) -> uint16; + auto algorithmINC8(uint8) -> uint8; + auto algorithmINC16(uint16) -> uint16; + auto algorithmLDA8(uint8) -> uint8; + auto algorithmLDA16(uint16) -> uint16; + auto algorithmLDX8(uint8) -> uint8; + auto algorithmLDX16(uint16) -> uint16; + auto algorithmLDY8(uint8) -> uint8; + auto algorithmLDY16(uint16) -> uint16; + auto algorithmLSR8(uint8) -> uint8; + auto algorithmLSR16(uint16) -> uint16; + auto algorithmORA8(uint8) -> uint8; + auto algorithmORA16(uint16) -> uint16; + auto algorithmROL8(uint8) -> uint8; + auto algorithmROL16(uint16) -> uint16; + auto algorithmROR8(uint8) -> uint8; + auto algorithmROR16(uint16) -> uint16; + auto algorithmSBC8(uint8) -> uint8; + auto algorithmSBC16(uint16) -> uint16; + auto algorithmTRB8(uint8) -> uint8; + auto algorithmTRB16(uint16) -> uint16; + auto algorithmTSB8(uint8) -> uint8; + auto algorithmTSB16(uint16) -> uint16; + + //instructions-read.cpp + auto instructionImmediateRead8(alu8) -> void; + auto instructionImmediateRead16(alu16) -> void; + auto instructionBankRead8(alu8) -> void; + auto instructionBankRead16(alu16) -> void; + auto instructionBankRead8(alu8, r16) -> void; + auto instructionBankRead16(alu16, r16) -> void; + auto instructionLongRead8(alu8, r16 = {}) -> void; + auto instructionLongRead16(alu16, r16 = {}) -> void; + auto instructionDirectRead8(alu8) -> void; + auto instructionDirectRead16(alu16) -> void; + auto instructionDirectRead8(alu8, r16) -> void; + auto instructionDirectRead16(alu16, r16) -> void; + auto instructionIndirectRead8(alu8) -> void; + auto instructionIndirectRead16(alu16) -> void; + auto instructionIndexedIndirectRead8(alu8) -> void; + auto instructionIndexedIndirectRead16(alu16) -> void; + auto instructionIndirectIndexedRead8(alu8) -> void; + auto instructionIndirectIndexedRead16(alu16) -> void; + auto instructionIndirectLongRead8(alu8, r16 = {}) -> void; + auto instructionIndirectLongRead16(alu16, r16 = {}) -> void; + auto instructionStackRead8(alu8) -> void; + auto instructionStackRead16(alu16) -> void; + auto instructionIndirectStackRead8(alu8) -> void; + auto instructionIndirectStackRead16(alu16) -> void; + + //instructions-write.cpp + auto instructionBankWrite8(r16) -> void; + auto instructionBankWrite16(r16) -> void; + auto instructionBankWrite8(r16, r16) -> void; + auto instructionBankWrite16(r16, r16) -> void; + auto instructionLongWrite8(r16 = {}) -> void; + auto instructionLongWrite16(r16 = {}) -> void; + auto instructionDirectWrite8(r16) -> void; + auto instructionDirectWrite16(r16) -> void; + auto instructionDirectWrite8(r16, r16) -> void; + auto instructionDirectWrite16(r16, r16) -> void; + auto instructionIndirectWrite8() -> void; + auto instructionIndirectWrite16() -> void; + auto instructionIndexedIndirectWrite8() -> void; + auto instructionIndexedIndirectWrite16() -> void; + auto instructionIndirectIndexedWrite8() -> void; + auto instructionIndirectIndexedWrite16() -> void; + auto instructionIndirectLongWrite8(r16 = {}) -> void; + auto instructionIndirectLongWrite16(r16 = {}) -> void; + auto instructionStackWrite8() -> void; + auto instructionStackWrite16() -> void; + auto instructionIndirectStackWrite8() -> void; + auto instructionIndirectStackWrite16() -> void; + + //instructions-modify.cpp + auto instructionImpliedModify8(alu8, r16&) -> void; + auto instructionImpliedModify16(alu16, r16&) -> void; + auto instructionBankModify8(alu8) -> void; + auto instructionBankModify16(alu16) -> void; + auto instructionBankIndexedModify8(alu8) -> void; + auto instructionBankIndexedModify16(alu16) -> void; + auto instructionDirectModify8(alu8) -> void; + auto instructionDirectModify16(alu16) -> void; + auto instructionDirectIndexedModify8(alu8) -> void; + auto instructionDirectIndexedModify16(alu16) -> void; + + //instructions-pc.cpp + auto instructionBranch(bool = 1) -> void; + auto instructionBranchLong() -> void; + auto instructionJumpShort() -> void; + auto instructionJumpLong() -> void; + auto instructionJumpIndirect() -> void; + auto instructionJumpIndexedIndirect() -> void; + auto instructionJumpIndirectLong() -> void; + auto instructionCallShort() -> void; + auto instructionCallLong() -> void; + auto instructionCallIndexedIndirect() -> void; + auto instructionReturnInterrupt() -> void; + auto instructionReturnShort() -> void; + auto instructionReturnLong() -> void; + + //instructions-misc.cpp + auto instructionBitImmediate8() -> void; + auto instructionBitImmediate16() -> void; + auto instructionNoOperation() -> void; + auto instructionPrefix() -> void; + auto instructionExchangeBA() -> void; + auto instructionBlockMove8(int) -> void; + auto instructionBlockMove16(int) -> void; + auto instructionInterrupt(r16) -> void; + auto instructionStop() -> void; + auto instructionWait() -> void; + auto instructionExchangeCE() -> void; + auto instructionSetFlag(bool&) -> void; + auto instructionClearFlag(bool&) -> void; + auto instructionResetP() -> void; + auto instructionSetP() -> void; + auto instructionTransfer8(r16, r16&) -> void; + auto instructionTransfer16(r16, r16&) -> void; + auto instructionTransferCS() -> void; + auto instructionTransferSX8() -> void; + auto instructionTransferSX16() -> void; + auto instructionTransferXS() -> void; + auto instructionPush8(r16) -> void; + auto instructionPush16(r16) -> void; + auto instructionPushD() -> void; + auto instructionPull8(r16&) -> void; + auto instructionPull16(r16&) -> void; + auto instructionPullD() -> void; + auto instructionPullB() -> void; + auto instructionPullP() -> void; + auto instructionPushEffectiveAddress() -> void; + auto instructionPushEffectiveIndirectAddress() -> void; + auto instructionPushEffectiveRelativeAddress() -> void; + + //instruction.cpp + auto instruction() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + //disassembler.cpp + auto disassemble() -> vector; + auto disassemble(uint24 addr, bool e, bool m, bool x) -> vector; + + struct f8 { + bool c = 0; //carry + bool z = 0; //zero + bool i = 0; //interrupt disable + bool d = 0; //decimal mode + bool x = 0; //index register mode + bool m = 0; //accumulator mode + bool v = 0; //overflow + bool n = 0; //negative + + inline operator uint() const { + return c << 0 | z << 1 | i << 2 | d << 3 | x << 4 | m << 5 | v << 6 | n << 7; + } + + inline auto& operator=(uint data) { + c = data & 0x01; + z = data & 0x02; + i = data & 0x04; + d = data & 0x08; + x = data & 0x10; + m = data & 0x20; + v = data & 0x40; + n = data & 0x80; + return *this; + } + }; + + struct Registers { + r24 pc; + r16 a; + r16 x; + r16 y; + r16 z; + r16 s; + r16 d; + r8 b; + f8 p; + + bool e = 0; //emulation mode + bool irq = 0; //IRQ pin (0 = low, 1 = trigger) + bool wai = 0; //raised during wai, cleared after interrupt triggered + bool stp = 0; //raised during stp, never cleared + + uint16 vector; //interrupt vector address + uint24 mar; //memory address register + r8 mdr; //memory data register + + r24 u; //temporary register + r24 v; //temporary register + r24 w; //temporary register + } r; +}; + +} diff --git a/waterbox/bsnescore/bsnes/sfc/cartridge/cartridge.cpp b/waterbox/bsnescore/bsnes/sfc/cartridge/cartridge.cpp new file mode 100644 index 0000000000..b39ceb66bb --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cartridge/cartridge.cpp @@ -0,0 +1,161 @@ +#include + +namespace SuperFamicom { + +#include "load.cpp" +#include "save.cpp" +#include "serialization.cpp" +Cartridge cartridge; + +auto Cartridge::hashes() const -> vector { + vector hashes; + hashes.append(game.sha256); + if(slotGameBoy.sha256) hashes.append(slotGameBoy.sha256); + if(slotBSMemory.sha256) hashes.append(slotBSMemory.sha256); + if(slotSufamiTurboA.sha256) hashes.append(slotSufamiTurboA.sha256); + if(slotSufamiTurboB.sha256) hashes.append(slotSufamiTurboB.sha256); + return hashes; +} + +auto Cartridge::manifests() const -> vector { + vector manifests; + manifests.append(string{BML::serialize(game.document), "\n", BML::serialize(board)}); + if(slotGameBoy.document) manifests.append(BML::serialize(slotGameBoy.document)); + if(slotBSMemory.document) manifests.append(BML::serialize(slotBSMemory.document)); + if(slotSufamiTurboA.document) manifests.append(BML::serialize(slotSufamiTurboA.document)); + if(slotSufamiTurboB.document) manifests.append(BML::serialize(slotSufamiTurboB.document)); + return manifests; +} + +auto Cartridge::titles() const -> vector { + vector titles; + titles.append(game.label); + if(slotGameBoy.label) titles.append(slotGameBoy.label); + if(slotBSMemory.label) titles.append(slotBSMemory.label); + if(slotSufamiTurboA.label) titles.append(slotSufamiTurboA.label); + if(slotSufamiTurboB.label) titles.append(slotSufamiTurboB.label); + return titles; +} + +auto Cartridge::title() const -> string { + if(slotGameBoy.label) return slotGameBoy.label; + if(has.MCC && slotBSMemory.label) return slotBSMemory.label; + if(slotBSMemory.label) return {game.label, " + ", slotBSMemory.label}; + if(slotSufamiTurboA.label && slotSufamiTurboB.label) return {slotSufamiTurboA.label, " + ", slotSufamiTurboB.label}; + if(slotSufamiTurboA.label) return slotSufamiTurboA.label; + if(slotSufamiTurboB.label) return slotSufamiTurboB.label; + if(has.Cx4 || has.DSP1 || has.DSP2 || has.DSP4 || has.ST0010) return {"[HLE] ", game.label}; + return game.label; +} + +auto Cartridge::load() -> bool { + information = {}; + has = {}; + game = {}; + slotGameBoy = {}; + slotBSMemory = {}; + slotSufamiTurboA = {}; + slotSufamiTurboB = {}; + + if(auto loaded = platform->load(ID::SuperFamicom, "Super Famicom", "sfc", {"Auto", "NTSC", "PAL"})) { + information.pathID = loaded.pathID; + information.region = loaded.option; + } else return false; + + if(auto fp = platform->open(ID::SuperFamicom, "manifest.bml", File::Read, File::Required)) { + game.load(fp->reads()); + } else return false; + + loadCartridge(game.document); + + //Game Boy + if(cartridge.has.ICD) { + information.sha256 = ""; //Game Boy cartridge not loaded yet: set later via loadGameBoy() + } + + //BS Memory + else if(cartridge.has.MCC && cartridge.has.BSMemorySlot) { + information.sha256 = Hash::SHA256({bsmemory.memory.data(), bsmemory.memory.size()}).digest(); + } + + //Sufami Turbo + else if(cartridge.has.SufamiTurboSlotA || cartridge.has.SufamiTurboSlotB) { + Hash::SHA256 sha; + if(cartridge.has.SufamiTurboSlotA) sha.input(sufamiturboA.rom.data(), sufamiturboA.rom.size()); + if(cartridge.has.SufamiTurboSlotB) sha.input(sufamiturboB.rom.data(), sufamiturboB.rom.size()); + information.sha256 = sha.digest(); + } + + //Super Famicom + else { + Hash::SHA256 sha; + //hash each ROM image that exists; any with size() == 0 is ignored by sha256_chunk() + sha.input(rom.data(), rom.size()); + sha.input(mcc.rom.data(), mcc.rom.size()); + sha.input(sa1.rom.data(), sa1.rom.size()); + sha.input(superfx.rom.data(), superfx.rom.size()); + sha.input(hitachidsp.rom.data(), hitachidsp.rom.size()); + sha.input(spc7110.prom.data(), spc7110.prom.size()); + sha.input(spc7110.drom.data(), spc7110.drom.size()); + sha.input(sdd1.rom.data(), sdd1.rom.size()); + //hash all firmware that exists + vector buffer; + buffer = armdsp.firmware(); + sha.input(buffer.data(), buffer.size()); + buffer = hitachidsp.firmware(); + sha.input(buffer.data(), buffer.size()); + buffer = necdsp.firmware(); + sha.input(buffer.data(), buffer.size()); + //finalize hash + information.sha256 = sha.digest(); + } + + return true; +} + +auto Cartridge::loadBSMemory() -> bool { + if(auto fp = platform->open(bsmemory.pathID, "manifest.bml", File::Read, File::Required)) { + slotBSMemory.load(fp->reads()); + } else return false; + loadCartridgeBSMemory(slotBSMemory.document); + return true; +} + +auto Cartridge::loadSufamiTurboA() -> bool { + if(auto fp = platform->open(sufamiturboA.pathID, "manifest.bml", File::Read, File::Required)) { + slotSufamiTurboA.load(fp->reads()); + } else return false; + loadCartridgeSufamiTurboA(slotSufamiTurboA.document); + return true; +} + +auto Cartridge::loadSufamiTurboB() -> bool { + if(auto fp = platform->open(sufamiturboB.pathID, "manifest.bml", File::Read, File::Required)) { + slotSufamiTurboB.load(fp->reads()); + } else return false; + loadCartridgeSufamiTurboB(slotSufamiTurboB.document); + return true; +} + +auto Cartridge::save() -> void { + saveCartridge(game.document); + if(has.GameBoySlot) { + icd.save(); + } + if(has.BSMemorySlot) { + saveCartridgeBSMemory(slotBSMemory.document); + } + if(has.SufamiTurboSlotA) { + saveCartridgeSufamiTurboA(slotSufamiTurboA.document); + } + if(has.SufamiTurboSlotB) { + saveCartridgeSufamiTurboB(slotSufamiTurboB.document); + } +} + +auto Cartridge::unload() -> void { + rom.reset(); + ram.reset(); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/cartridge/cartridge.hpp b/waterbox/bsnescore/bsnes/sfc/cartridge/cartridge.hpp new file mode 100644 index 0000000000..b975c19be7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cartridge/cartridge.hpp @@ -0,0 +1,126 @@ +struct Cartridge { + auto pathID() const -> uint { return information.pathID; } + auto region() const -> string { return information.region; } + auto headerTitle() const -> string { return game.title; } + + auto hashes() const -> vector; + auto manifests() const -> vector; + auto titles() const -> vector; + auto title() const -> string; + + auto load() -> bool; + auto save() -> void; + auto unload() -> void; + + auto serialize(serializer&) -> void; + + ReadableMemory rom; + WritableMemory ram; + + struct Information { + uint pathID = 0; + string region; + string sha256; + } information; + + struct Has { + boolean ICD; + boolean MCC; + boolean DIP; + boolean Event; + boolean SA1; + boolean SuperFX; + boolean ARMDSP; + boolean HitachiDSP; + boolean NECDSP; + boolean EpsonRTC; + boolean SharpRTC; + boolean SPC7110; + boolean SDD1; + boolean OBC1; + boolean MSU1; + + boolean Cx4; + boolean DSP1; + boolean DSP2; + boolean DSP4; + boolean ST0010; + + boolean GameBoySlot; + boolean BSMemorySlot; + boolean SufamiTurboSlotA; + boolean SufamiTurboSlotB; + } has; + +private: + Emulator::Game game; + Emulator::Game slotGameBoy; + Emulator::Game slotBSMemory; + Emulator::Game slotSufamiTurboA; + Emulator::Game slotSufamiTurboB; + Markup::Node board; + + //cartridge.cpp + auto loadBSMemory() -> bool; + auto loadSufamiTurboA() -> bool; + auto loadSufamiTurboB() -> bool; + + //load.cpp + auto loadBoard(string) -> Markup::Node; + auto loadCartridge(Markup::Node) -> void; + auto loadCartridgeBSMemory(Markup::Node) -> void; + auto loadCartridgeSufamiTurboA(Markup::Node) -> void; + auto loadCartridgeSufamiTurboB(Markup::Node) -> void; + + auto loadMemory(Memory&, Markup::Node, bool required) -> void; + template auto loadMap(Markup::Node, T&) -> uint; + auto loadMap(Markup::Node, const function&, const function&) -> uint; + + auto loadROM(Markup::Node) -> void; + auto loadRAM(Markup::Node) -> void; + auto loadICD(Markup::Node) -> void; + auto loadMCC(Markup::Node) -> void; + auto loadBSMemory(Markup::Node) -> void; + auto loadSufamiTurboA(Markup::Node) -> void; + auto loadSufamiTurboB(Markup::Node) -> void; + auto loadDIP(Markup::Node) -> void; + auto loadEvent(Markup::Node) -> void; + auto loadSA1(Markup::Node) -> void; + auto loadSuperFX(Markup::Node) -> void; + auto loadARMDSP(Markup::Node) -> void; + auto loadHitachiDSP(Markup::Node, uint roms) -> void; + auto loaduPD7725(Markup::Node) -> void; + auto loaduPD96050(Markup::Node) -> void; + auto loadEpsonRTC(Markup::Node) -> void; + auto loadSharpRTC(Markup::Node) -> void; + auto loadSPC7110(Markup::Node) -> void; + auto loadSDD1(Markup::Node) -> void; + auto loadOBC1(Markup::Node) -> void; + auto loadMSU1() -> void; + + //save.cpp + auto saveCartridge(Markup::Node) -> void; + auto saveCartridgeBSMemory(Markup::Node) -> void; + auto saveCartridgeSufamiTurboA(Markup::Node) -> void; + auto saveCartridgeSufamiTurboB(Markup::Node) -> void; + + auto saveMemory(Memory&, Markup::Node) -> void; + + auto saveRAM(Markup::Node) -> void; + auto saveMCC(Markup::Node) -> void; + auto saveSA1(Markup::Node) -> void; + auto saveSuperFX(Markup::Node) -> void; + auto saveARMDSP(Markup::Node) -> void; + auto saveHitachiDSP(Markup::Node) -> void; + auto saveuPD7725(Markup::Node) -> void; + auto saveuPD96050(Markup::Node) -> void; + auto saveEpsonRTC(Markup::Node) -> void; + auto saveSharpRTC(Markup::Node) -> void; + auto saveSPC7110(Markup::Node) -> void; + auto saveOBC1(Markup::Node) -> void; + + friend class Interface; + friend class ICD; +}; + +extern Cartridge cartridge; diff --git a/waterbox/bsnescore/bsnes/sfc/cartridge/load.cpp b/waterbox/bsnescore/bsnes/sfc/cartridge/load.cpp new file mode 100644 index 0000000000..21b50de9fd --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cartridge/load.cpp @@ -0,0 +1,730 @@ +auto Cartridge::loadBoard(string board) -> Markup::Node { + string output; + + if(board.beginsWith("SNSP-")) board.replace("SNSP-", "SHVC-", 1L); + if(board.beginsWith("MAXI-")) board.replace("MAXI-", "SHVC-", 1L); + if(board.beginsWith("MJSC-")) board.replace("MJSC-", "SHVC-", 1L); + if(board.beginsWith("EA-" )) board.replace("EA-", "SHVC-", 1L); + if(board.beginsWith("WEI-" )) board.replace("WEI-", "SHVC-", 1L); + + if(auto fp = platform->open(ID::System, "boards.bml", File::Read, File::Required)) { + auto document = BML::unserialize(fp->reads()); + for(auto leaf : document.find("board")) { + auto id = leaf.text(); + bool matched = id == board; + if(!matched && id.match("*(*)*")) { + auto part = id.transform("()", "||").split("|"); + for(auto& revision : part(1).split(",")) { + if(string{part(0), revision, part(2)} == board) matched = true; + } + } + if(matched) return leaf; + } + } + + return {}; +} + +auto Cartridge::loadCartridge(Markup::Node node) -> void { + board = node["board"]; + if(!board) board = loadBoard(game.board); + + if(region() == "Auto") { + auto region = game.region; + if(region.endsWith("BRA") + || region.endsWith("CAN") + || region.endsWith("HKG") + || region.endsWith("JPN") + || region.endsWith("KOR") + || region.endsWith("LTN") + || region.endsWith("ROC") + || region.endsWith("USA") + || region.beginsWith("SHVC-") + || region == "NTSC") { + information.region = "NTSC"; + } else { + information.region = "PAL"; + } + } + + if(auto node = board["memory(type=ROM,content=Program)"]) loadROM(node); + if(auto node = board["memory(type=ROM,content=Expansion)"]) loadROM(node); //todo: handle this better + if(auto node = board["memory(type=RAM,content=Save)"]) loadRAM(node); + if(auto node = board["processor(identifier=ICD)"]) loadICD(node); + if(auto node = board["processor(identifier=MCC)"]) loadMCC(node); + if(auto node = board["slot(type=BSMemory)"]) loadBSMemory(node); + if(auto node = board["slot(type=SufamiTurbo)[0]"]) loadSufamiTurboA(node); + if(auto node = board["slot(type=SufamiTurbo)[1]"]) loadSufamiTurboB(node); + if(auto node = board["dip"]) loadDIP(node); + if(auto node = board["processor(architecture=uPD78214)"]) loadEvent(node); + if(auto node = board["processor(architecture=W65C816S)"]) loadSA1(node); + if(auto node = board["processor(architecture=GSU)"]) loadSuperFX(node); + if(auto node = board["processor(architecture=ARM6)"]) loadARMDSP(node); + if(auto node = board["processor(architecture=HG51BS169)"]) loadHitachiDSP(node, game.board.match("2DC*") ? 2 : 1); + if(auto node = board["processor(architecture=uPD7725)"]) loaduPD7725(node); + if(auto node = board["processor(architecture=uPD96050)"]) loaduPD96050(node); + if(auto node = board["rtc(manufacturer=Epson)"]) loadEpsonRTC(node); + if(auto node = board["rtc(manufacturer=Sharp)"]) loadSharpRTC(node); + if(auto node = board["processor(identifier=SPC7110)"]) loadSPC7110(node); + if(auto node = board["processor(identifier=SDD1)"]) loadSDD1(node); + if(auto node = board["processor(identifier=OBC1)"]) loadOBC1(node); + + if(auto fp = platform->open(pathID(), "msu1/data.rom", File::Read)) loadMSU1(); +} + +auto Cartridge::loadCartridgeBSMemory(Markup::Node node) -> void { + if(auto memory = Emulator::Game::Memory{node["game/board/memory(content=Program)"]}) { + bsmemory.ROM = memory.type == "ROM"; + bsmemory.memory.allocate(memory.size); + if(auto fp = platform->open(bsmemory.pathID, memory.name(), File::Read, File::Required)) { + fp->read(bsmemory.memory.data(), memory.size); + } + } +} + +auto Cartridge::loadCartridgeSufamiTurboA(Markup::Node node) -> void { + if(auto memory = Emulator::Game::Memory{node["game/board/memory(type=ROM,content=Program)"]}) { + sufamiturboA.rom.allocate(memory.size); + if(auto fp = platform->open(sufamiturboA.pathID, memory.name(), File::Read, File::Required)) { + fp->read(sufamiturboA.rom.data(), memory.size); + } + } + + if(auto memory = Emulator::Game::Memory{node["game/board/memory(type=RAM,content=Save)"]}) { + sufamiturboA.ram.allocate(memory.size); + if(auto fp = platform->open(sufamiturboA.pathID, memory.name(), File::Read)) { + fp->read(sufamiturboA.ram.data(), memory.size); + } + } +} + +auto Cartridge::loadCartridgeSufamiTurboB(Markup::Node node) -> void { + if(auto memory = Emulator::Game::Memory{node["game/board/memory(type=ROM,content=Program)"]}) { + sufamiturboB.rom.allocate(memory.size); + if(auto fp = platform->open(sufamiturboB.pathID, memory.name(), File::Read, File::Required)) { + fp->read(sufamiturboB.rom.data(), memory.size); + } + } + + if(auto memory = Emulator::Game::Memory{node["game/board/memory(type=RAM,content=Save)"]}) { + sufamiturboB.ram.allocate(memory.size); + if(auto fp = platform->open(sufamiturboB.pathID, memory.name(), File::Read)) { + fp->read(sufamiturboB.ram.data(), memory.size); + } + } +} + +// + +auto Cartridge::loadMemory(Memory& ram, Markup::Node node, bool required) -> void { + if(auto memory = game.memory(node)) { + ram.allocate(memory->size); + if(memory->type == "RAM" && !memory->nonVolatile) return; + if(memory->type == "RTC" && !memory->nonVolatile) return; + if(auto fp = platform->open(pathID(), memory->name(), File::Read, required)) { + fp->read(ram.data(), min(fp->size(), ram.size())); + } + } +} + +template //T = ReadableMemory, WritableMemory, ProtectableMemory +auto Cartridge::loadMap(Markup::Node map, T& memory) -> uint { + auto addr = map["address"].text(); + auto size = map["size"].natural(); + auto base = map["base"].natural(); + auto mask = map["mask"].natural(); + if(size == 0) size = memory.size(); + if(size == 0) return print("loadMap(): size=0\n"), 0; //does this ever actually occur? + return bus.map({&T::read, &memory}, {&T::write, &memory}, addr, size, base, mask); +} + +auto Cartridge::loadMap( + Markup::Node map, + const function& reader, + const function& writer +) -> uint { + auto addr = map["address"].text(); + auto size = map["size"].natural(); + auto base = map["base"].natural(); + auto mask = map["mask"].natural(); + return bus.map(reader, writer, addr, size, base, mask); +} + +//memory(type=ROM,content=Program) +auto Cartridge::loadROM(Markup::Node node) -> void { + loadMemory(rom, node, File::Required); + for(auto leaf : node.find("map")) loadMap(leaf, rom); +} + +//memory(type=RAM,content=Save) +auto Cartridge::loadRAM(Markup::Node node) -> void { + loadMemory(ram, node, File::Optional); + for(auto leaf : node.find("map")) loadMap(leaf, ram); +} + +//processor(identifier=ICD) +auto Cartridge::loadICD(Markup::Node node) -> void { + has.GameBoySlot = true; + has.ICD = true; + + icd.Revision = node["revision"].natural(); + if(auto oscillator = game.oscillator()) { + icd.Frequency = oscillator->frequency; + } else { + icd.Frequency = 0; + } + + //Game Boy core loads data through ICD interface + for(auto map : node.find("map")) { + loadMap(map, {&ICD::readIO, &icd}, {&ICD::writeIO, &icd}); + } +} + +//processor(identifier=MCC) +auto Cartridge::loadMCC(Markup::Node node) -> void { + has.MCC = true; + + for(auto map : node.find("map")) { + loadMap(map, {&MCC::read, &mcc}, {&MCC::write, &mcc}); + } + + if(auto mcu = node["mcu"]) { + for(auto map : mcu.find("map")) { + loadMap(map, {&MCC::mcuRead, &mcc}, {&MCC::mcuWrite, &mcc}); + } + if(auto memory = mcu["memory(type=ROM,content=Program)"]) { + loadMemory(mcc.rom, memory, File::Required); + } + if(auto memory = mcu["memory(type=RAM,content=Download)"]) { + loadMemory(mcc.psram, memory, File::Optional); + } + if(auto slot = mcu["slot(type=BSMemory)"]) { + loadBSMemory(slot); + } + } +} + +//slot(type=BSMemory) +auto Cartridge::loadBSMemory(Markup::Node node) -> void { + has.BSMemorySlot = true; + + if(auto loaded = platform->load(ID::BSMemory, "BS Memory", "bs")) { + bsmemory.pathID = loaded.pathID; + loadBSMemory(); + + for(auto map : node.find("map")) { + loadMap(map, bsmemory); + } + } +} + +//slot(type=SufamiTurbo)[0] +auto Cartridge::loadSufamiTurboA(Markup::Node node) -> void { + has.SufamiTurboSlotA = true; + + if(auto loaded = platform->load(ID::SufamiTurboA, "Sufami Turbo", "st")) { + sufamiturboA.pathID = loaded.pathID; + loadSufamiTurboA(); + + for(auto map : node.find("rom/map")) { + loadMap(map, sufamiturboA.rom); + } + + for(auto map : node.find("ram/map")) { + loadMap(map, sufamiturboA.ram); + } + } +} + +//slot(type=SufamiTurbo)[1] +auto Cartridge::loadSufamiTurboB(Markup::Node node) -> void { + has.SufamiTurboSlotB = true; + + if(auto loaded = platform->load(ID::SufamiTurboB, "Sufami Turbo", "st")) { + sufamiturboB.pathID = loaded.pathID; + loadSufamiTurboB(); + + for(auto map : node.find("rom/map")) { + loadMap(map, sufamiturboB.rom); + } + + for(auto map : node.find("ram/map")) { + loadMap(map, sufamiturboB.ram); + } + } +} + +//dip +auto Cartridge::loadDIP(Markup::Node node) -> void { + has.DIP = true; + dip.value = platform->dipSettings(node); + + for(auto map : node.find("map")) { + loadMap(map, {&DIP::read, &dip}, {&DIP::write, &dip}); + } +} + +//processor(architecture=uPD78214) +auto Cartridge::loadEvent(Markup::Node node) -> void { + has.Event = true; + event.board = Event::Board::Unknown; + if(node["identifier"].text() == "Campus Challenge '92") event.board = Event::Board::CampusChallenge92; + if(node["identifier"].text() == "PowerFest '94") event.board = Event::Board::PowerFest94; + + for(auto map : node.find("map")) { + loadMap(map, {&Event::read, &event}, {&Event::write, &event}); + } + + if(auto mcu = node["mcu"]) { + for(auto map : mcu.find("map")) { + loadMap(map, {&Event::mcuRead, &event}, {&Event::mcuWrite, &event}); + } + if(auto memory = mcu["memory(type=ROM,content=Program)"]) { + loadMemory(event.rom[0], memory, File::Required); + } + if(auto memory = mcu["memory(type=ROM,content=Level-1)"]) { + loadMemory(event.rom[1], memory, File::Required); + } + if(auto memory = mcu["memory(type=ROM,content=Level-2)"]) { + loadMemory(event.rom[2], memory, File::Required); + } + if(auto memory = mcu["memory(type=ROM,content=Level-3)"]) { + loadMemory(event.rom[3], memory, File::Required); + } + } +} + +//processor(architecture=W65C816S) +auto Cartridge::loadSA1(Markup::Node node) -> void { + has.SA1 = true; + + for(auto map : node.find("map")) { + loadMap(map, {&SA1::readIOCPU, &sa1}, {&SA1::writeIOCPU, &sa1}); + } + + if(auto mcu = node["mcu"]) { + for(auto map : mcu.find("map")) { + loadMap(map, {&SA1::ROM::readCPU, &sa1.rom}, {&SA1::ROM::writeCPU, &sa1.rom}); + } + if(auto memory = mcu["memory(type=ROM,content=Program)"]) { + loadMemory(sa1.rom, memory, File::Required); + } + if(auto slot = mcu["slot(type=BSMemory)"]) { + loadBSMemory(slot); + } + } + + if(auto memory = node["memory(type=RAM,content=Save)"]) { + loadMemory(sa1.bwram, memory, File::Optional); + for(auto map : memory.find("map")) { + loadMap(map, {&SA1::BWRAM::readCPU, &sa1.bwram}, {&SA1::BWRAM::writeCPU, &sa1.bwram}); + } + } + + if(auto memory = node["memory(type=RAM,content=Internal)"]) { + loadMemory(sa1.iram, memory, File::Optional); + for(auto map : memory.find("map")) { + loadMap(map, {&SA1::IRAM::readCPU, &sa1.iram}, {&SA1::IRAM::writeCPU, &sa1.iram}); + } + } +} + +//processor(architecture=GSU) +auto Cartridge::loadSuperFX(Markup::Node node) -> void { + has.SuperFX = true; + + if(auto oscillator = game.oscillator()) { + superfx.Frequency = oscillator->frequency; //GSU-1, GSU-2 + } else { + superfx.Frequency = system.cpuFrequency(); //MARIO CHIP 1 + } + + for(auto map : node.find("map")) { + loadMap(map, {&SuperFX::readIO, &superfx}, {&SuperFX::writeIO, &superfx}); + } + + if(auto memory = node["memory(type=ROM,content=Program)"]) { + loadMemory(superfx.rom, memory, File::Required); + for(auto map : memory.find("map")) { + loadMap(map, superfx.cpurom); + } + } + + if(auto memory = node["memory(type=RAM,content=Save)"]) { + loadMemory(superfx.ram, memory, File::Optional); + for(auto map : memory.find("map")) { + loadMap(map, superfx.cpuram); + } + } +} + +//processor(architecture=ARM6) +auto Cartridge::loadARMDSP(Markup::Node node) -> void { + has.ARMDSP = true; + + for(auto& word : armdsp.programROM) word = 0x00; + for(auto& word : armdsp.dataROM) word = 0x00; + for(auto& word : armdsp.programRAM) word = 0x00; + + if(auto oscillator = game.oscillator()) { + armdsp.Frequency = oscillator->frequency; + } else { + armdsp.Frequency = 21'440'000; + } + + for(auto map : node.find("map")) { + loadMap(map, {&ArmDSP::read, &armdsp}, {&ArmDSP::write, &armdsp}); + } + + if(auto memory = node["memory(type=ROM,content=Program,architecture=ARM6)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read, File::Required)) { + for(auto n : range(128 * 1024)) armdsp.programROM[n] = fp->read(); + } + } + } + + if(auto memory = node["memory(type=ROM,content=Data,architecture=ARM6)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read, File::Required)) { + for(auto n : range(32 * 1024)) armdsp.dataROM[n] = fp->read(); + } + } + } + + if(auto memory = node["memory(type=RAM,content=Data,architecture=ARM6)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + for(auto n : range(16 * 1024)) armdsp.programRAM[n] = fp->read(); + } + } + } +} + +//processor(architecture=HG51BS169) +auto Cartridge::loadHitachiDSP(Markup::Node node, uint roms) -> void { + for(auto& word : hitachidsp.dataROM) word = 0x000000; + for(auto& word : hitachidsp.dataRAM) word = 0x00; + + if(auto oscillator = game.oscillator()) { + hitachidsp.Frequency = oscillator->frequency; + } else { + hitachidsp.Frequency = 20'000'000; + } + hitachidsp.Roms = roms; //1 or 2 + hitachidsp.Mapping = 0; //0 or 1 + + if(auto memory = node["memory(type=ROM,content=Program)"]) { + loadMemory(hitachidsp.rom, memory, File::Required); + for(auto map : memory.find("map")) { + loadMap(map, {&HitachiDSP::readROM, &hitachidsp}, {&HitachiDSP::writeROM, &hitachidsp}); + } + } + + if(auto memory = node["memory(type=RAM,content=Save)"]) { + loadMemory(hitachidsp.ram, memory, File::Optional); + for(auto map : memory.find("map")) { + loadMap(map, {&HitachiDSP::readRAM, &hitachidsp}, {&HitachiDSP::writeRAM, &hitachidsp}); + } + } + + if(configuration.hacks.coprocessor.preferHLE) { + has.Cx4 = true; + for(auto map : node.find("map")) { + loadMap(map, {&Cx4::read, &cx4}, {&Cx4::write, &cx4}); + } + if(auto memory = node["memory(type=RAM,content=Data,architecture=HG51BS169)"]) { + for(auto map : memory.find("map")) { + loadMap(map, {&Cx4::read, &cx4}, {&Cx4::write, &cx4}); + } + } + return; + } + + if(auto memory = node["memory(type=ROM,content=Data,architecture=HG51BS169)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + for(auto n : range(1 * 1024)) hitachidsp.dataROM[n] = fp->readl(3); + } else { + for(auto n : range(1 * 1024)) { + hitachidsp.dataROM[n] = hitachidsp.staticDataROM[n * 3 + 0] << 0; + hitachidsp.dataROM[n] |= hitachidsp.staticDataROM[n * 3 + 1] << 8; + hitachidsp.dataROM[n] |= hitachidsp.staticDataROM[n * 3 + 2] << 16; + } + } + } + } + + if(auto memory = node["memory(type=RAM,content=Data,architecture=HG51BS169)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + for(auto n : range(3 * 1024)) hitachidsp.dataRAM[n] = fp->readl(1); + } + } + for(auto map : memory.find("map")) { + loadMap(map, {&HitachiDSP::readDRAM, &hitachidsp}, {&HitachiDSP::writeDRAM, &hitachidsp}); + } + } + + has.HitachiDSP = true; + + for(auto map : node.find("map")) { + loadMap(map, {&HitachiDSP::readIO, &hitachidsp}, {&HitachiDSP::writeIO, &hitachidsp}); + } +} + +//processor(architecture=uPD7725) +auto Cartridge::loaduPD7725(Markup::Node node) -> void { + for(auto& word : necdsp.programROM) word = 0x000000; + for(auto& word : necdsp.dataROM) word = 0x0000; + for(auto& word : necdsp.dataRAM) word = 0x0000; + + if(auto oscillator = game.oscillator()) { + necdsp.Frequency = oscillator->frequency; + } else { + necdsp.Frequency = 7'600'000; + } + + bool failed = false; + + if(auto memory = node["memory(type=ROM,content=Program,architecture=uPD7725)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + for(auto n : range(2048)) necdsp.programROM[n] = fp->readl(3); + } else failed = true; + } + } + + if(auto memory = node["memory(type=ROM,content=Data,architecture=uPD7725)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + for(auto n : range(1024)) necdsp.dataROM[n] = fp->readl(2); + } else failed = true; + } + } + + if(failed || configuration.hacks.coprocessor.preferHLE) { + auto manifest = BML::serialize(game.document); + if(manifest.find("identifier: DSP1")) { //also matches DSP1B + has.DSP1 = true; + for(auto map : node.find("map")) { + loadMap(map, {&DSP1::read, &dsp1}, {&DSP1::write, &dsp1}); + } + return; + } + if(manifest.find("identifier: DSP2")) { + has.DSP2 = true; + for(auto map : node.find("map")) { + loadMap(map, {&DSP2::read, &dsp2}, {&DSP2::write, &dsp2}); + } + return; + } + if(manifest.find("identifier: DSP4")) { + has.DSP4 = true; + for(auto map : node.find("map")) { + loadMap(map, {&DSP4::read, &dsp4}, {&DSP4::write, &dsp4}); + } + return; + } + } + + if(failed) { + //throw an error to the user + platform->open(ID::SuperFamicom, "DSP3", File::Read, File::Required); + return; + } + + if(auto memory = node["memory(type=RAM,content=Data,architecture=uPD7725)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + for(auto n : range(256)) necdsp.dataRAM[n] = fp->readl(2); + } + } + for(auto map : memory.find("map")) { + loadMap(map, {&NECDSP::readRAM, &necdsp}, {&NECDSP::writeRAM, &necdsp}); + } + } + + has.NECDSP = true; + necdsp.revision = NECDSP::Revision::uPD7725; + + for(auto map : node.find("map")) { + loadMap(map, {&NECDSP::read, &necdsp}, {&NECDSP::write, &necdsp}); + } +} + +//processor(architecture=uPD96050) +auto Cartridge::loaduPD96050(Markup::Node node) -> void { + for(auto& word : necdsp.programROM) word = 0x000000; + for(auto& word : necdsp.dataROM) word = 0x0000; + for(auto& word : necdsp.dataRAM) word = 0x0000; + + if(auto oscillator = game.oscillator()) { + necdsp.Frequency = oscillator->frequency; + } else { + necdsp.Frequency = 11'000'000; + } + + bool failed = false; + + if(auto memory = node["memory(type=ROM,content=Program,architecture=uPD96050)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + for(auto n : range(16384)) necdsp.programROM[n] = fp->readl(3); + } else failed = true; + } + } + + if(auto memory = node["memory(type=ROM,content=Data,architecture=uPD96050)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + for(auto n : range(2048)) necdsp.dataROM[n] = fp->readl(2); + } else failed = true; + } + } + + if(failed || configuration.hacks.coprocessor.preferHLE) { + auto manifest = BML::serialize(game.document); + if(manifest.find("identifier: ST010")) { + has.ST0010 = true; + if(auto memory = node["memory(type=RAM,content=Data,architecture=uPD96050)"]) { + for(auto map : memory.find("map")) { + loadMap(map, {&ST0010::read, &st0010}, {&ST0010::write, &st0010}); + } + } + return; + } + } + + if(failed) { + //throw an error to the user + platform->open(ID::SuperFamicom, "ST011", File::Read, File::Required); + return; + } + + if(auto memory = node["memory(type=RAM,content=Data,architecture=uPD96050)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + for(auto n : range(2048)) necdsp.dataRAM[n] = fp->readl(2); + } + } + for(auto map : memory.find("map")) { + loadMap(map, {&NECDSP::readRAM, &necdsp}, {&NECDSP::writeRAM, &necdsp}); + } + } + + has.NECDSP = true; + necdsp.revision = NECDSP::Revision::uPD96050; + + for(auto map : node.find("map")) { + loadMap(map, {&NECDSP::read, &necdsp}, {&NECDSP::write, &necdsp}); + } +} + +//rtc(manufacturer=Epson) +auto Cartridge::loadEpsonRTC(Markup::Node node) -> void { + has.EpsonRTC = true; + + epsonrtc.initialize(); + + for(auto map : node.find("map")) { + loadMap(map, {&EpsonRTC::read, &epsonrtc}, {&EpsonRTC::write, &epsonrtc}); + } + + if(auto memory = node["memory(type=RTC,content=Time,manufacturer=Epson)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + uint8 data[16] = {0}; + for(auto& byte : data) byte = fp->read(); + epsonrtc.load(data); + } + } + } +} + +//rtc(manufacturer=Sharp) +auto Cartridge::loadSharpRTC(Markup::Node node) -> void { + has.SharpRTC = true; + + sharprtc.initialize(); + + for(auto map : node.find("map")) { + loadMap(map, {&SharpRTC::read, &sharprtc}, {&SharpRTC::write, &sharprtc}); + } + + if(auto memory = node["memory(type=RTC,content=Time,manufacturer=Sharp)"]) { + if(auto file = game.memory(memory)) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) { + uint8 data[16] = {0}; + for(auto& byte : data) byte = fp->read(); + sharprtc.load(data); + } + } + } +} + +//processor(identifier=SPC7110) +auto Cartridge::loadSPC7110(Markup::Node node) -> void { + has.SPC7110 = true; + + for(auto map : node.find("map")) { + loadMap(map, {&SPC7110::read, &spc7110}, {&SPC7110::write, &spc7110}); + } + + if(auto mcu = node["mcu"]) { + for(auto map : mcu.find("map")) { + loadMap(map, {&SPC7110::mcuromRead, &spc7110}, {&SPC7110::mcuromWrite, &spc7110}); + } + if(auto memory = mcu["memory(type=ROM,content=Program)"]) { + loadMemory(spc7110.prom, memory, File::Required); + } + if(auto memory = mcu["memory(type=ROM,content=Data)"]) { + loadMemory(spc7110.drom, memory, File::Required); + } + } + + if(auto memory = node["memory(type=RAM,content=Save)"]) { + loadMemory(spc7110.ram, memory, File::Optional); + for(auto map : memory.find("map")) { + loadMap(map, {&SPC7110::mcuramRead, &spc7110}, {&SPC7110::mcuramWrite, &spc7110}); + } + } +} + +//processor(identifier=SDD1) +auto Cartridge::loadSDD1(Markup::Node node) -> void { + has.SDD1 = true; + + for(auto map : node.find("map")) { + loadMap(map, {&SDD1::ioRead, &sdd1}, {&SDD1::ioWrite, &sdd1}); + } + + if(auto mcu = node["mcu"]) { + for(auto map : mcu.find("map")) { + loadMap(map, {&SDD1::mcuRead, &sdd1}, {&SDD1::mcuWrite, &sdd1}); + } + if(auto memory = mcu["memory(type=ROM,content=Program)"]) { + loadMemory(sdd1.rom, memory, File::Required); + } + } +} + +//processor(identifier=OBC1) +auto Cartridge::loadOBC1(Markup::Node node) -> void { + has.OBC1 = true; + + for(auto map : node.find("map")) { + loadMap(map, {&OBC1::read, &obc1}, {&OBC1::write, &obc1}); + } + + if(auto memory = node["memory(type=RAM,content=Save)"]) { + loadMemory(obc1.ram, memory, File::Optional); + } +} + +//file::exists("msu1/data.rom") +auto Cartridge::loadMSU1() -> void { + has.MSU1 = true; + + bus.map({&MSU1::readIO, &msu1}, {&MSU1::writeIO, &msu1}, "00-3f,80-bf:2000-2007"); +} diff --git a/waterbox/bsnescore/bsnes/sfc/cartridge/save.cpp b/waterbox/bsnescore/bsnes/sfc/cartridge/save.cpp new file mode 100644 index 0000000000..89ef772935 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cartridge/save.cpp @@ -0,0 +1,188 @@ +auto Cartridge::saveCartridge(Markup::Node node) -> void { + if(auto node = board["memory(type=RAM,content=Save)"]) saveRAM(node); + if(auto node = board["processor(identifier=MCC)"]) saveMCC(node); + if(auto node = board["processor(architecture=W65C816S)"]) saveSA1(node); + if(auto node = board["processor(architecture=GSU)"]) saveSuperFX(node); + if(auto node = board["processor(architecture=ARM6)"]) saveARMDSP(node); + if(auto node = board["processor(architecture=HG51BS169)"]) saveHitachiDSP(node); + if(auto node = board["processor(architecture=uPD7725)"]) saveuPD7725(node); + if(auto node = board["processor(architecture=uPD96050)"]) saveuPD96050(node); + if(auto node = board["rtc(manufacturer=Epson)"]) saveEpsonRTC(node); + if(auto node = board["rtc(manufacturer=Sharp)"]) saveSharpRTC(node); + if(auto node = board["processor(identifier=SPC7110)"]) saveSPC7110(node); + if(auto node = board["processor(identifier=OBC1)"]) saveOBC1(node); +} + +auto Cartridge::saveCartridgeBSMemory(Markup::Node node) -> void { + if(auto memory = Emulator::Game::Memory{node["game/board/memory(type=Flash,content=Program)"]}) { + if(auto fp = platform->open(bsmemory.pathID, memory.name(), File::Write)) { + fp->write(bsmemory.memory.data(), memory.size); + } + } +} + +auto Cartridge::saveCartridgeSufamiTurboA(Markup::Node node) -> void { + if(auto memory = Emulator::Game::Memory{node["game/board/memory(type=RAM,content=Save)"]}) { + if(memory.nonVolatile) { + if(auto fp = platform->open(sufamiturboA.pathID, memory.name(), File::Write)) { + fp->write(sufamiturboA.ram.data(), memory.size); + } + } + } +} + +auto Cartridge::saveCartridgeSufamiTurboB(Markup::Node node) -> void { + if(auto memory = Emulator::Game::Memory{node["game/board/memory(type=RAM,content=Save)"]}) { + if(memory.nonVolatile) { + if(auto fp = platform->open(sufamiturboB.pathID, memory.name(), File::Write)) { + fp->write(sufamiturboB.ram.data(), memory.size); + } + } + } +} + +// + +auto Cartridge::saveMemory(Memory& ram, Markup::Node node) -> void { + if(auto memory = game.memory(node)) { + if(memory->type == "RAM" && !memory->nonVolatile) return; + if(memory->type == "RTC" && !memory->nonVolatile) return; + if(auto fp = platform->open(pathID(), memory->name(), File::Write)) { + fp->write(ram.data(), ram.size()); + } + } +} + +//memory(type=RAM,content=Save) +auto Cartridge::saveRAM(Markup::Node node) -> void { + saveMemory(ram, node); +} + +//processor(identifier=MCC) +auto Cartridge::saveMCC(Markup::Node node) -> void { + if(auto mcu = node["mcu"]) { + if(auto memory = mcu["memory(type=RAM,content=Download)"]) { + saveMemory(mcc.psram, memory); + } + } +} + +//processor(architecture=W65C816S) +auto Cartridge::saveSA1(Markup::Node node) -> void { + if(auto memory = node["memory(type=RAM,content=Save)"]) { + saveMemory(sa1.bwram, memory); + } + + if(auto memory = node["memory(type=RAM,content=Internal)"]) { + saveMemory(sa1.iram, memory); + } +} + +//processor(architecture=GSU) +auto Cartridge::saveSuperFX(Markup::Node node) -> void { + if(auto memory = node["memory(type=RAM,content=Save)"]) { + saveMemory(superfx.ram, memory); + } +} + +//processor(architecture=ARM6) +auto Cartridge::saveARMDSP(Markup::Node node) -> void { + if(auto memory = node["memory(type=RAM,content=Data,architecture=ARM6)"]) { + if(auto file = game.memory(memory)) { + if(file->nonVolatile) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Write)) { + for(auto n : range(16 * 1024)) fp->write(armdsp.programRAM[n]); + } + } + } + } +} + +//processor(architecture=HG51BS169) +auto Cartridge::saveHitachiDSP(Markup::Node node) -> void { + saveMemory(hitachidsp.ram, node["ram"]); + + if(auto memory = node["memory(type=RAM,content=Save)"]) { + saveMemory(hitachidsp.ram, memory); + } + + if(auto memory = node["memory(type=RAM,content=Data,architecture=HG51BS169)"]) { + if(auto file = game.memory(memory)) { + if(file->nonVolatile) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Write)) { + for(auto n : range(3 * 1024)) fp->write(hitachidsp.dataRAM[n]); + } + } + } + } +} + +//processor(architecture=uPD7725) +auto Cartridge::saveuPD7725(Markup::Node node) -> void { + if(auto memory = node["memory(type=RAM,content=Data,architecture=uPD7725)"]) { + if(auto file = game.memory(memory)) { + if(file->nonVolatile) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Write)) { + for(auto n : range(256)) fp->writel(necdsp.dataRAM[n], 2); + } + } + } + } +} + +//processor(architecture=uPD96050) +auto Cartridge::saveuPD96050(Markup::Node node) -> void { + if(auto memory = node["memory(type=RAM,content=Data,architecture=uPD96050)"]) { + if(auto file = game.memory(memory)) { + if(file->nonVolatile) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Write)) { + for(auto n : range(2 * 1024)) fp->writel(necdsp.dataRAM[n], 2); + } + } + } + } +} + +//rtc(manufacturer=Epson) +auto Cartridge::saveEpsonRTC(Markup::Node node) -> void { + if(auto memory = node["memory(type=RTC,content=Time,manufacturer=Epson)"]) { + if(auto file = game.memory(memory)) { + if(file->nonVolatile) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Write)) { + uint8 data[16] = {0}; + epsonrtc.save(data); + fp->write(data, 16); + } + } + } + } +} + +//rtc(manufacturer=Sharp) +auto Cartridge::saveSharpRTC(Markup::Node node) -> void { + if(auto memory = node["memory(type=RTC,content=Time,manufacturer=Sharp)"]) { + if(auto file = game.memory(memory)) { + if(file->nonVolatile) { + if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Write)) { + uint8 data[16] = {0}; + sharprtc.save(data); + fp->write(data, 16); + } + } + } + } +} + +//processor(identifier=SPC7110) +auto Cartridge::saveSPC7110(Markup::Node node) -> void { + if(auto memory = node["memory(type=RAM,content=Save)"]) { + saveMemory(spc7110.ram, memory); + } +} + +//processor(identifier=OBC1) +auto Cartridge::saveOBC1(Markup::Node node) -> void { + if(auto memory = node["memory(type=RAM,content=Save)"]) { + saveMemory(obc1.ram, memory); + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/cartridge/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/cartridge/serialization.cpp new file mode 100644 index 0000000000..1509ab4977 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cartridge/serialization.cpp @@ -0,0 +1,3 @@ +auto Cartridge::serialize(serializer& s) -> void { + s.array(ram.data(), ram.size()); +} diff --git a/waterbox/bsnescore/bsnes/sfc/controller/controller.cpp b/waterbox/bsnescore/bsnes/sfc/controller/controller.cpp new file mode 100644 index 0000000000..a68a03785d --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/controller.cpp @@ -0,0 +1,63 @@ +#include + +namespace SuperFamicom { + +ControllerPort controllerPort1; +ControllerPort controllerPort2; +#include "gamepad/gamepad.cpp" +#include "mouse/mouse.cpp" +#include "super-multitap/super-multitap.cpp" +#include "super-scope/super-scope.cpp" +#include "justifier/justifier.cpp" + +Controller::Controller(uint port) : port(port) { +} + +Controller::~Controller() { +} + +auto Controller::iobit() -> bool { + switch(port) { + case ID::Port::Controller1: return cpu.pio() & 0x40; + case ID::Port::Controller2: return cpu.pio() & 0x80; + } + unreachable; +} + +auto Controller::iobit(bool data) -> void { + switch(port) { + case ID::Port::Controller1: bus.write(0x4201, (cpu.pio() & ~0x40) | (data << 6)); break; + case ID::Port::Controller2: bus.write(0x4201, (cpu.pio() & ~0x80) | (data << 7)); break; + } +} + +// + +auto ControllerPort::connect(uint deviceID) -> void { + if(!system.loaded()) return; + delete device; + + switch(deviceID) { default: + case ID::Device::None: device = new Controller(port); break; + case ID::Device::Gamepad: device = new Gamepad(port); break; + case ID::Device::Mouse: device = new Mouse(port); break; + case ID::Device::SuperMultitap: device = new SuperMultitap(port); break; + case ID::Device::SuperScope: device = new SuperScope(port); break; + case ID::Device::Justifier: device = new Justifier(port, false); break; + case ID::Device::Justifiers: device = new Justifier(port, true); break; + } +} + +auto ControllerPort::power(uint port) -> void { + this->port = port; +} + +auto ControllerPort::unload() -> void { + delete device; + device = nullptr; +} + +auto ControllerPort::serialize(serializer& s) -> void { +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/controller/controller.hpp b/waterbox/bsnescore/bsnes/sfc/controller/controller.hpp new file mode 100644 index 0000000000..96bc7c2e13 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/controller.hpp @@ -0,0 +1,46 @@ +// SNES controller port pinout: +// ------------------------------- +// | (1) (2) (3) (4) | (5) (6) (7) ) +// ------------------------------- +// pin name port1 port2 +// 1: +5v +// 2: clock $4016 read $4017 read +// 3: latch $4016.d0 write $4016.d0 write +// 4: data1 $4016.d0 read $4017.d0 read +// 5: data2 $4016.d1 read $4017.d1 read +// 6: iobit $4201.d6 write; $4213.d6 read $4201.d7 write; $4213.d7 read +// 7: gnd + +struct Controller { + Controller(uint port); + virtual ~Controller(); + + auto iobit() -> bool; + auto iobit(bool data) -> void; + virtual auto data() -> uint2 { return 0; } + virtual auto latch(bool data) -> void {} + virtual auto latch() -> void {} //light guns + virtual auto draw(uint16_t* output, uint pitch, uint width, uint height) -> void {} //light guns + + const uint port; +}; + +struct ControllerPort { + auto connect(uint deviceID) -> void; + + auto power(uint port) -> void; + auto unload() -> void; + auto serialize(serializer&) -> void; + + uint port; + Controller* device = nullptr; +}; + +extern ControllerPort controllerPort1; +extern ControllerPort controllerPort2; + +#include "gamepad/gamepad.hpp" +#include "mouse/mouse.hpp" +#include "super-multitap/super-multitap.hpp" +#include "super-scope/super-scope.hpp" +#include "justifier/justifier.hpp" diff --git a/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.cpp b/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.cpp new file mode 100644 index 0000000000..bdef37e744 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.cpp @@ -0,0 +1,48 @@ +Gamepad::Gamepad(uint port) : Controller(port) { + latched = 0; + counter = 0; +} + +auto Gamepad::data() -> uint2 { + if(counter >= 16) return 1; + if(latched == 1) return platform->inputPoll(port, ID::Device::Gamepad, B); + + //note: D-pad physically prevents up+down and left+right from being pressed at the same time + switch(counter++) { + case 0: return b; + case 1: return y; + case 2: return select; + case 3: return start; + case 4: return up & !down; + case 5: return down & !up; + case 6: return left & !right; + case 7: return right & !left; + case 8: return a; + case 9: return x; + case 10: return l; + case 11: return r; + } + + return 0; //12-15: signature +} + +auto Gamepad::latch(bool data) -> void { + if(latched == data) return; + latched = data; + counter = 0; + + if(latched == 0) { + b = platform->inputPoll(port, ID::Device::Gamepad, B); + y = platform->inputPoll(port, ID::Device::Gamepad, Y); + select = platform->inputPoll(port, ID::Device::Gamepad, Select); + start = platform->inputPoll(port, ID::Device::Gamepad, Start); + up = platform->inputPoll(port, ID::Device::Gamepad, Up); + down = platform->inputPoll(port, ID::Device::Gamepad, Down); + left = platform->inputPoll(port, ID::Device::Gamepad, Left); + right = platform->inputPoll(port, ID::Device::Gamepad, Right); + a = platform->inputPoll(port, ID::Device::Gamepad, A); + x = platform->inputPoll(port, ID::Device::Gamepad, X); + l = platform->inputPoll(port, ID::Device::Gamepad, L); + r = platform->inputPoll(port, ID::Device::Gamepad, R); + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.hpp b/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.hpp new file mode 100644 index 0000000000..b28c7ba274 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.hpp @@ -0,0 +1,18 @@ +struct Gamepad : Controller { + enum : uint { + Up, Down, Left, Right, B, A, Y, X, L, R, Select, Start, + }; + + Gamepad(uint port); + + auto data() -> uint2; + auto latch(bool data) -> void; + +private: + bool latched; + uint counter; + + boolean b, y, select, start; + boolean up, down, left, right; + boolean a, x, l, r; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.cpp b/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.cpp new file mode 100644 index 0000000000..b735c2d0aa --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.cpp @@ -0,0 +1,162 @@ +Justifier::Justifier(uint port, bool chained): +Controller(port), +chained(chained), +device(!chained ? ID::Device::Justifier : ID::Device::Justifiers) +{ + latched = 0; + counter = 0; + active = 0; + prev = 0; + + player1.x = 256 / 2; + player1.y = 240 / 2; + player1.trigger = false; + player2.start = false; + + player2.x = 256 / 2; + player2.y = 240 / 2; + player2.trigger = false; + player2.start = false; + + if(chained == false) { + player2.x = -1; + player2.y = -1; + } else { + player1.x -= 16; + player2.x += 16; + } +} + +auto Justifier::data() -> uint2 { + if(counter >= 32) return 1; + + if(counter == 0) { + player1.trigger = platform->inputPoll(port, device, 0 + Trigger); + player1.start = platform->inputPoll(port, device, 0 + Start); + } + + if(counter == 0 && chained) { + player2.trigger = platform->inputPoll(port, device, 4 + Trigger); + player2.start = platform->inputPoll(port, device, 4 + Start); + } + + switch(counter++) { + case 0: return 0; + case 1: return 0; + case 2: return 0; + case 3: return 0; + case 4: return 0; + case 5: return 0; + case 6: return 0; + case 7: return 0; + case 8: return 0; + case 9: return 0; + case 10: return 0; + case 11: return 0; + + case 12: return 1; //signature + case 13: return 1; // || + case 14: return 1; // || + case 15: return 0; // || + + case 16: return 0; + case 17: return 1; + case 18: return 0; + case 19: return 1; + case 20: return 0; + case 21: return 1; + case 22: return 0; + case 23: return 1; + + case 24: return player1.trigger; + case 25: return player2.trigger; + case 26: return player1.start; + case 27: return player2.start; + case 28: return active; + + case 29: return 0; + case 30: return 0; + case 31: return 0; + } + + unreachable; +} + +auto Justifier::latch(bool data) -> void { + if(latched == data) return; + latched = data; + counter = 0; + if(latched == 0) active = !active; //toggle between both controllers, even when unchained +} + +auto Justifier::latch() -> void { + /* active value is inverted here ... */ + + if(active != 0) { + int nx = platform->inputPoll(port, device, 0 + X); + int ny = platform->inputPoll(port, device, 0 + Y); + player1.x = max(-16, min(256 + 16, nx + player1.x)); + player1.y = max(-16, min((int)ppu.vdisp() + 16, ny + player1.y)); + bool offscreen = (player1.x < 0 || player1.y < 0 || player1.x >= 256 || player1.y >= (int)ppu.vdisp()); + if(!offscreen) ppu.latchCounters(player1.x, player1.y); + } + + if(active != 1) { + int nx = platform->inputPoll(port, device, 4 + X); + int ny = platform->inputPoll(port, device, 4 + Y); + player2.x = max(-16, min(256 + 16, nx + player2.x)); + player2.y = max(-16, min((int)ppu.vdisp() + 16, ny + player2.y)); + bool offscreen = (player2.x < 0 || player2.y < 0 || player2.x >= 256 || player2.y >= (int)ppu.vdisp()); + if(!offscreen) ppu.latchCounters(player2.x, player2.y); + } +} + +auto Justifier::draw(uint16_t* data, uint pitch, uint width, uint height) -> void { + pitch >>= 1; + float scaleX = (float)width / 256.0; + float scaleY = (float)height / (float)ppu.vdisp(); + int length = (float)width / 256.0 * 4.0; + + auto plot = [&](int x, int y, uint16_t color) -> void { + if(x >= 0 && y >= 0 && x < (int)width && y < (int)height) { + data[y * pitch + x] = color; + } + }; + + { int x = player1.x * scaleX; + int y = player1.y * scaleY; + + uint16_t color = 0x03e0; + uint16_t black = 0x0000; + + for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y - 1, black); + for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y + 1, black); + for(int py = y - length - 1; py <= y + length + 1; py++) plot(x - 1, py, black); + for(int py = y - length - 1; py <= y + length + 1; py++) plot(x + 1, py, black); + plot(x - length - 1, y, black); + plot(x + length + 1, y, black); + plot(x, y - length - 1, black); + plot(x, y + length + 1, black); + for(int px = x - length; px <= x + length; px++) plot(px, y, color); + for(int py = y - length; py <= y + length; py++) plot(x, py, color); + } + + if(chained) + { int x = player2.x * scaleX; + int y = player2.y * scaleY; + + uint16_t color = 0x7c00; + uint16_t black = 0x0000; + + for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y - 1, black); + for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y + 1, black); + for(int py = y - length - 1; py <= y + length + 1; py++) plot(x - 1, py, black); + for(int py = y - length - 1; py <= y + length + 1; py++) plot(x + 1, py, black); + plot(x - length - 1, y, black); + plot(x + length + 1, y, black); + plot(x, y - length - 1, black); + plot(x, y + length + 1, black); + for(int px = x - length; px <= x + length; px++) plot(px, y, color); + for(int py = y - length; py <= y + length; py++) plot(x, py, color); + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.hpp b/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.hpp new file mode 100644 index 0000000000..b5923b7e24 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.hpp @@ -0,0 +1,27 @@ +struct Justifier : Controller { + enum : uint { + X, Y, Trigger, Start, + }; + + Justifier(uint port, bool chained); + + auto data() -> uint2; + auto latch(bool data) -> void; + auto latch() -> void override; + auto draw(uint16_t* data, uint pitch, uint width, uint height) -> void override; + +//private: + const bool chained; //true if the second justifier is attached to the first + const uint device; + bool latched; + uint counter; + uint prev; + + bool active; + struct Player { + int x; + int y; + bool trigger; + bool start; + } player1, player2; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.cpp b/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.cpp new file mode 100644 index 0000000000..88e450c51f --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.cpp @@ -0,0 +1,86 @@ +Mouse::Mouse(uint port) : Controller(port) { + latched = 0; + counter = 0; + + speed = 0; + x = 0; + y = 0; + dx = 0; + dy = 0; + l = 0; + r = 0; +} + +auto Mouse::data() -> uint2 { + if(latched == 1) { + speed = (speed + 1) % 3; + return 0; + } + + if(counter >= 32) return 1; + + switch(counter++) { default: + case 0: return 0; + case 1: return 0; + case 2: return 0; + case 3: return 0; + case 4: return 0; + case 5: return 0; + case 6: return 0; + case 7: return 0; + + case 8: return r; + case 9: return l; + case 10: return (speed >> 1) & 1; + case 11: return (speed >> 0) & 1; + + case 12: return 0; //signature + case 13: return 0; // || + case 14: return 0; // || + case 15: return 1; // || + + case 16: return dy; + case 17: return (y >> 6) & 1; + case 18: return (y >> 5) & 1; + case 19: return (y >> 4) & 1; + case 20: return (y >> 3) & 1; + case 21: return (y >> 2) & 1; + case 22: return (y >> 1) & 1; + case 23: return (y >> 0) & 1; + + case 24: return dx; + case 25: return (x >> 6) & 1; + case 26: return (x >> 5) & 1; + case 27: return (x >> 4) & 1; + case 28: return (x >> 3) & 1; + case 29: return (x >> 2) & 1; + case 30: return (x >> 1) & 1; + case 31: return (x >> 0) & 1; + } +} + +auto Mouse::latch(bool data) -> void { + if(latched == data) return; + latched = data; + counter = 0; + + x = platform->inputPoll(port, ID::Device::Mouse, X); //-n = left, 0 = center, +n = right + y = platform->inputPoll(port, ID::Device::Mouse, Y); //-n = up, 0 = center, +n = down + l = platform->inputPoll(port, ID::Device::Mouse, Left); + r = platform->inputPoll(port, ID::Device::Mouse, Right); + + dx = x < 0; //0 = right, 1 = left + dy = y < 0; //0 = down, 1 = up + + if(x < 0) x = -x; //abs(position_x) + if(y < 0) y = -y; //abs(position_y) + + double multiplier = 1.0; + if(speed == 1) multiplier = 1.5; + if(speed == 2) multiplier = 2.0; + x = (double)x * multiplier; + y = (double)y * multiplier; + + x = min(127, x); + y = min(127, y); +} diff --git a/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.hpp b/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.hpp new file mode 100644 index 0000000000..a094d23e6b --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.hpp @@ -0,0 +1,22 @@ +struct Mouse : Controller { + enum : uint { + X, Y, Left, Right, + }; + + Mouse(uint port); + + auto data() -> uint2; + auto latch(bool data) -> void; + +private: + bool latched; + uint counter; + + uint speed; //0 = slow, 1 = normal, 2 = fast + int x; //x-coordinate + int y; //y-coordinate + bool dx; //x-direction + bool dy; //y-direction + bool l; //left button + bool r; //right button +}; diff --git a/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.cpp b/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.cpp new file mode 100644 index 0000000000..7ef141be4c --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.cpp @@ -0,0 +1,70 @@ +SuperMultitap::SuperMultitap(uint port) : Controller(port) { + latched = 0; + counter1 = 0; + counter2 = 0; +} + +auto SuperMultitap::data() -> uint2 { + if(latched) return 2; //device detection + uint counter, a, b; + + if(iobit()) { + counter = counter1; + if(counter >= 16) return 3; + counter1++; + if(counter >= 12) return 0; + a = 0; //controller 2 + b = 1; //controller 3 + } else { + counter = counter2; + if(counter >= 16) return 3; + counter2++; + if(counter >= 12) return 0; + a = 2; //controller 4 + b = 3; //controller 5 + } + + auto& A = gamepads[a]; + auto& B = gamepads[b]; + + switch(counter) { + case 0: return A.b << 0 | B.b << 1; + case 1: return A.y << 0 | B.y << 1; + case 2: return A.select << 0 | B.select << 1; + case 3: return A.start << 0 | B.start << 1; + case 4: return (A.up & !A.down) << 0 | (B.up & !B.down) << 1; + case 5: return (A.down & !A.up) << 0 | (B.down & !B.up) << 1; + case 6: return (A.left & !A.right) << 0 | (B.left & !B.right) << 1; + case 7: return (A.right & !A.left) << 0 | (B.right & !B.left) << 1; + case 8: return A.a << 0 | B.a << 1; + case 9: return A.x << 0 | B.x << 1; + case 10: return A.l << 0 | B.l << 1; + case 11: return A.r << 0 | B.r << 1; + } + unreachable; +} + +auto SuperMultitap::latch(bool data) -> void { + if(latched == data) return; + latched = data; + counter1 = 0; + counter2 = 0; + + if(latched == 0) { + for(uint id : range(4)) { + auto& gamepad = gamepads[id]; + gamepad.b = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + B); + gamepad.y = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + Y); + gamepad.select = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + Select); + gamepad.start = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + Start); + gamepad.up = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + Up); + gamepad.down = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + Down); + gamepad.left = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + Left); + gamepad.right = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + Right); + gamepad.a = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + A); + gamepad.x = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + X); + gamepad.l = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + L); + gamepad.r = platform->inputPoll(port, ID::Device::SuperMultitap, id * 12 + R); + } + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.hpp b/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.hpp new file mode 100644 index 0000000000..7ab212739e --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.hpp @@ -0,0 +1,21 @@ +struct SuperMultitap : Controller { + enum : uint { + Up, Down, Left, Right, B, A, Y, X, L, R, Select, Start, + }; + + SuperMultitap(uint port); + + auto data() -> uint2; + auto latch(bool data) -> void; + +private: + bool latched; + uint counter1; + uint counter2; + + struct Gamepad { + boolean b, y, select, start; + boolean up, down, left, right; + boolean a, x, l, r; + } gamepads[4]; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.cpp b/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.cpp new file mode 100644 index 0000000000..5b4455f51a --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.cpp @@ -0,0 +1,129 @@ +//The Super Scope is a light-gun: it detects the CRT beam cannon position, +//and latches the counters by toggling iobit. This only works on controller +//port 2, as iobit there is connected to the PPU H/V counter latch. +//(PIO $4201.d7) + +//It is obviously not possible to perfectly simulate an IR light detecting +//a CRT beam cannon, hence this class will read the PPU raster counters. + +//A Super Scope can still technically be used in port 1, however it would +//require manual polling of PIO ($4201.d6) to determine when iobit was written. +//Note that no commercial game ever utilizes a Super Scope in port 1. + +SuperScope::SuperScope(uint port) : Controller(port) { + latched = 0; + counter = 0; + + //center cursor onscreen + x = 256 / 2; + y = 240 / 2; + + trigger = false; + cursor = false; + turbo = false; + pause = false; + offscreen = false; + + oldturbo = false; + triggerlock = false; + pauselock = false; + + prev = 0; +} + +auto SuperScope::data() -> uint2 { + if(counter >= 8) return 1; + + if(counter == 0) { + //turbo is a switch; toggle is edge sensitive + bool newturbo = platform->inputPoll(port, ID::Device::SuperScope, Turbo); + if(newturbo && !oldturbo) { + turbo = !turbo; //toggle state + } + oldturbo = newturbo; + + //trigger is a button + //if turbo is active, trigger is level sensitive; otherwise, it is edge sensitive + trigger = false; + bool newtrigger = platform->inputPoll(port, ID::Device::SuperScope, Trigger); + if(newtrigger && (turbo || !triggerlock)) { + trigger = true; + triggerlock = true; + } else if(!newtrigger) { + triggerlock = false; + } + + //cursor is a button; it is always level sensitive + cursor = platform->inputPoll(port, ID::Device::SuperScope, Cursor); + + //pause is a button; it is always edge sensitive + pause = false; + bool newpause = platform->inputPoll(port, ID::Device::SuperScope, Pause); + if(newpause && !pauselock) { + pause = true; + pauselock = true; + } else if(!newpause) { + pauselock = false; + } + + offscreen = (x < 0 || y < 0 || x >= 256 || y >= ppu.vdisp()); + } + + switch(counter++) { + case 0: return offscreen ? 0 : trigger; + case 1: return cursor; + case 2: return turbo; + case 3: return pause; + case 4: return 0; + case 5: return 0; + case 6: return offscreen; + case 7: return 0; //noise (1 = yes) + } + + unreachable; +} + +auto SuperScope::latch(bool data) -> void { + if(latched == data) return; + latched = data; + counter = 0; +} + +auto SuperScope::latch() -> void { + int nx = platform->inputPoll(port, ID::Device::SuperScope, X); + int ny = platform->inputPoll(port, ID::Device::SuperScope, Y); + x = max(-16, min(256 + 16, nx + x)); + y = max(-16, min((int)ppu.vdisp() + 16, ny + y)); + offscreen = (x < 0 || y < 0 || x >= 256 || y >= (int)ppu.vdisp()); + if(!offscreen) ppu.latchCounters(x, y); +} + +auto SuperScope::draw(uint16_t* data, uint pitch, uint width, uint height) -> void { + pitch >>= 1; + float scaleX = (float)width / 256.0; + float scaleY = (float)height / (float)ppu.vdisp(); + int length = (float)width / 256.0 * 4.0; + + int x = this->x * scaleX; + int y = this->y * scaleY; + + auto plot = [&](int x, int y, uint16_t color) -> void { + if(x >= 0 && y >= 0 && x < (int)width && y < (int)height) { + data[y * pitch + x] = color; + } + }; + + uint16_t color = turbo ? 0x7c00 : 0x03e0; + uint16_t black = 0x0000; + + for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y - 1, black); + for(int px = x - length - 1; px <= x + length + 1; px++) plot(px, y + 1, black); + for(int py = y - length - 1; py <= y + length + 1; py++) plot(x - 1, py, black); + for(int py = y - length - 1; py <= y + length + 1; py++) plot(x + 1, py, black); + plot(x - length - 1, y, black); + plot(x + length + 1, y, black); + plot(x, y - length - 1, black); + plot(x, y + length + 1, black); + for(int px = x - length; px <= x + length; px++) plot(px, y, color); + for(int py = y - length; py <= y + length; py++) plot(x, py, color); +} diff --git a/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.hpp b/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.hpp new file mode 100644 index 0000000000..ffa475f7fc --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.hpp @@ -0,0 +1,31 @@ +struct SuperScope : Controller { + enum : uint { + X, Y, Trigger, Cursor, Turbo, Pause, + }; + + SuperScope(uint port); + + auto data() -> uint2; + auto latch(bool data) -> void; + auto latch() -> void override; + auto draw(uint16_t* data, uint pitch, uint width, uint height) -> void override; + +private: + bool latched; + uint counter; + + int x; + int y; + + bool trigger; + bool cursor; + bool turbo; + bool pause; + bool offscreen; + + bool oldturbo; + bool triggerlock; + bool pauselock; + + uint prev; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/armdsp.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/armdsp.cpp new file mode 100644 index 0000000000..8a7d08d077 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/armdsp.cpp @@ -0,0 +1,109 @@ +#include + +namespace SuperFamicom { + +#include "memory.cpp" +#include "serialization.cpp" +ArmDSP armdsp; + +auto ArmDSP::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto ArmDSP::Enter() -> void { + armdsp.boot(); + while(true) { + scheduler.synchronize(); + armdsp.main(); + } +} + +auto ArmDSP::boot() -> void { + //reset hold delay + while(bridge.reset) { + step(1); + continue; + } + + //reset sequence delay + if(bridge.ready == false) { + step(65'536); + bridge.ready = true; + } +} + +auto ArmDSP::main() -> void { + processor.cpsr.t = 0; //force ARM mode + instruction(); +} + +auto ArmDSP::step(uint clocks) -> void { + if(bridge.timer && --bridge.timer == 0); + clock += clocks * (uint64_t)cpu.frequency; + synchronizeCPU(); +} + +//MMIO: 00-3f,80-bf:3800-38ff +//3800-3807 mirrored throughout +//a0 ignored + +auto ArmDSP::read(uint addr, uint8) -> uint8 { + cpu.synchronizeCoprocessors(); + + uint8 data = 0x00; + addr &= 0xff06; + + if(addr == 0x3800) { + if(bridge.armtocpu.ready) { + bridge.armtocpu.ready = false; + data = bridge.armtocpu.data; + } + } + + if(addr == 0x3802) { + bridge.signal = false; + } + + if(addr == 0x3804) { + data = bridge.status(); + } + + return data; +} + +auto ArmDSP::write(uint addr, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + + addr &= 0xff06; + + if(addr == 0x3802) { + bridge.cputoarm.ready = true; + bridge.cputoarm.data = data; + } + + if(addr == 0x3804) { + data &= 1; + if(!bridge.reset && data) reset(); + bridge.reset = data; + } +} + +auto ArmDSP::power() -> void { + random.array((uint8*)programRAM, sizeof(programRAM)); + bridge.reset = false; + reset(); +} + +auto ArmDSP::reset() -> void { + ARM7TDMI::power(); + create(ArmDSP::Enter, Frequency); + + bridge.ready = false; + bridge.signal = false; + bridge.timer = 0; + bridge.timerlatch = 0; + bridge.cputoarm.ready = false; + bridge.armtocpu.ready = false; +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/armdsp.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/armdsp.hpp new file mode 100644 index 0000000000..732ee89923 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/armdsp.hpp @@ -0,0 +1,36 @@ +//ARMv3 (ARM60) + +//note: this coprocessor uses the ARMv4 (ARM7TDMI) core as its base +//instruction execution forces ARM mode to remove ARMv4 THUMB access +//there is a possibility the ARMv3 supports 26-bit mode; but cannot be verified + +struct ArmDSP : Processor::ARM7TDMI, Thread { + #include "registers.hpp" + + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto boot() -> void; + auto main() -> void; + + auto step(uint clocks) -> void override; + auto sleep() -> void override; + auto get(uint mode, uint32 addr) -> uint32 override; + auto set(uint mode, uint32 addr, uint32 word) -> void override; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto power() -> void; + auto reset() -> void; //soft reset + + auto firmware() const -> nall::vector; + auto serialize(serializer&) -> void; + + uint Frequency; + + uint8 programROM[128 * 1024]; + uint8 dataROM[32 * 1024]; + uint8 programRAM[16 * 1024]; +}; + +extern ArmDSP armdsp; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/memory.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/memory.cpp new file mode 100644 index 0000000000..c672d523e1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/memory.cpp @@ -0,0 +1,91 @@ +//note: timings are completely unverified +//due to the ST018 chip design (on-die ROM), testing is nearly impossible + +auto ArmDSP::sleep() -> void { + step(1); +} + +auto ArmDSP::get(uint mode, uint32 addr) -> uint32 { + step(1); + + static auto memory = [&](const uint8* memory, uint mode, uint32 addr) -> uint32 { + if(mode & Word) { + memory += addr & ~3; + return memory[0] << 0 | memory[1] << 8 | memory[2] << 16 | memory[3] << 24; + } else if(mode & Byte) { + return memory[addr]; + } else { + return 0; //should never occur + } + }; + + switch(addr & 0xe000'0000) { + case 0x0000'0000: return memory(programROM, mode, addr & 0x1ffff); + case 0x2000'0000: return pipeline.fetch.instruction; + case 0x4000'0000: break; + case 0x6000'0000: return 0x40404001; + case 0x8000'0000: return pipeline.fetch.instruction; + case 0xa000'0000: return memory(dataROM, mode, addr & 0x7fff); + case 0xc000'0000: return pipeline.fetch.instruction; + case 0xe000'0000: return memory(programRAM, mode, addr & 0x3fff); + } + + addr &= 0xe000'003f; + + if(addr == 0x4000'0010) { + if(bridge.cputoarm.ready) { + bridge.cputoarm.ready = false; + return bridge.cputoarm.data; + } + } + + if(addr == 0x4000'0020) { + return bridge.status(); + } + + return 0; +} + +auto ArmDSP::set(uint mode, uint32 addr, uint32 word) -> void { + step(1); + + static auto memory = [](uint8* memory, uint mode, uint32 addr, uint32 word) { + if(mode & Word) { + memory += addr & ~3; + *memory++ = word >> 0; + *memory++ = word >> 8; + *memory++ = word >> 16; + *memory++ = word >> 24; + } else if(mode & Byte) { + memory += addr; + *memory++ = word >> 0; + } + }; + + switch(addr & 0xe000'0000) { + case 0x0000'0000: return; + case 0x2000'0000: return; + case 0x4000'0000: break; + case 0x6000'0000: return; + case 0x8000'0000: return; + case 0xa000'0000: return; + case 0xc000'0000: return; + case 0xe000'0000: return memory(programRAM, mode, addr & 0x3fff, word); + } + + addr &= 0xe000'003f; + word &= 0x0000'00ff; + + if(addr == 0x4000'0000) { + bridge.armtocpu.ready = true; + bridge.armtocpu.data = word; + } + + if(addr == 0x4000'0010) bridge.signal = true; + + if(addr == 0x4000'0020) bridge.timerlatch = bridge.timerlatch & 0xffff00 | word << 0; + if(addr == 0x4000'0024) bridge.timerlatch = bridge.timerlatch & 0xff00ff | word << 8; + if(addr == 0x4000'0028) bridge.timerlatch = bridge.timerlatch & 0x00ffff | word << 16; + + if(addr == 0x4000'002c) bridge.timer = bridge.timerlatch; +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/registers.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/registers.hpp new file mode 100644 index 0000000000..c05a097579 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/registers.hpp @@ -0,0 +1,22 @@ +struct Bridge { + struct Buffer { + bool ready; + uint8 data; + }; + Buffer cputoarm; + Buffer armtocpu; + uint32 timer; + uint32 timerlatch; + bool reset; + bool ready; + bool signal; + + auto status() const -> uint8 { + return ( + armtocpu.ready << 0 + | signal << 2 + | cputoarm.ready << 3 + | ready << 7 + ); + } +} bridge; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/serialization.cpp new file mode 100644 index 0000000000..143f3a606f --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/armdsp/serialization.cpp @@ -0,0 +1,25 @@ +auto ArmDSP::firmware() const -> nall::vector { + nall::vector buffer; + if(!cartridge.has.ARMDSP) return buffer; + buffer.reserve(128 * 1024 + 32 * 1024); + for(auto n : range(128 * 1024)) buffer.append(programROM[n]); + for(auto n : range( 32 * 1024)) buffer.append(dataROM[n]); + return buffer; +} + +auto ArmDSP::serialize(serializer& s) -> void { + ARM7TDMI::serialize(s); + Thread::serialize(s); + + s.array(programRAM, 16 * 1024); + + s.integer(bridge.cputoarm.ready); + s.integer(bridge.cputoarm.data); + s.integer(bridge.armtocpu.ready); + s.integer(bridge.armtocpu.data); + s.integer(bridge.timer); + s.integer(bridge.timerlatch); + s.integer(bridge.reset); + s.integer(bridge.ready); + s.integer(bridge.signal); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/coprocessor.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/coprocessor.cpp new file mode 100644 index 0000000000..8fe8ec44ca --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/coprocessor.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/coprocessor.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/coprocessor.hpp new file mode 100644 index 0000000000..f01516a75a --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/coprocessor.hpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/cx4.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/cx4.cpp new file mode 100644 index 0000000000..7dc93e60ae --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/cx4.cpp @@ -0,0 +1,191 @@ +#include + +namespace SuperFamicom { + +Cx4 cx4; +#define CX4_CPP +#include "data.cpp" +#include "functions.cpp" +#include "oam.cpp" +#include "opcodes.cpp" +#include "serialization.cpp" + +auto Cx4::power() -> void { + memset(ram, 0, 0x0c00); + memset(reg, 0, 0x0100); +} + +uint32 Cx4::ldr(uint8 r) { + uint16 addr = 0x0080 + (r * 3); + return (reg[addr + 0] << 0) + | (reg[addr + 1] << 8) + | (reg[addr + 2] << 16); +} + +void Cx4::str(uint8 r, uint32 data) { + uint16 addr = 0x0080 + (r * 3); + reg[addr + 0] = (data >> 0); + reg[addr + 1] = (data >> 8); + reg[addr + 2] = (data >> 16); +} + +void Cx4::mul(uint32 x, uint32 y, uint32 &rl, uint32 &rh) { + int64 rx = x & 0xffffff; + int64 ry = y & 0xffffff; + if(rx & 0x800000)rx |= ~0x7fffff; + if(ry & 0x800000)ry |= ~0x7fffff; + + rx *= ry; + + rl = (rx) & 0xffffff; + rh = (rx >> 24) & 0xffffff; +} + +uint32 Cx4::sin(uint32 rx) { + r0 = rx & 0x1ff; + if(r0 & 0x100)r0 ^= 0x1ff; + if(r0 & 0x080)r0 ^= 0x0ff; + if(rx & 0x100) { + return sin_table[r0 + 0x80]; + } else { + return sin_table[r0]; + } +} + +uint32 Cx4::cos(uint32 rx) { + return sin(rx + 0x080); +} + +void Cx4::immediate_reg(uint32 start) { + r0 = ldr(0); + for(uint32 i = start; i < 48; i++) { + if((r0 & 0x0fff) < 0x0c00) { + ram[r0 & 0x0fff] = immediate_data[i]; + } + r0++; + } + str(0, r0); +} + +void Cx4::transfer_data() { + uint32 src; + uint16 dest, count; + + src = (reg[0x40]) | (reg[0x41] << 8) | (reg[0x42] << 16); + count = (reg[0x43]) | (reg[0x44] << 8); + dest = (reg[0x45]) | (reg[0x46] << 8); + + for(uint32 i=0;i> 2; + return; + } + + switch(data) { + case 0x00: op00(); break; + case 0x01: op01(); break; + case 0x05: op05(); break; + case 0x0d: op0d(); break; + case 0x10: op10(); break; + case 0x13: op13(); break; + case 0x15: op15(); break; + case 0x1f: op1f(); break; + case 0x22: op22(); break; + case 0x25: op25(); break; + case 0x2d: op2d(); break; + case 0x40: op40(); break; + case 0x54: op54(); break; + case 0x5c: op5c(); break; + case 0x5e: op5e(); break; + case 0x60: op60(); break; + case 0x62: op62(); break; + case 0x64: op64(); break; + case 0x66: op66(); break; + case 0x68: op68(); break; + case 0x6a: op6a(); break; + case 0x6c: op6c(); break; + case 0x6e: op6e(); break; + case 0x70: op70(); break; + case 0x72: op72(); break; + case 0x74: op74(); break; + case 0x76: op76(); break; + case 0x78: op78(); break; + case 0x7a: op7a(); break; + case 0x7c: op7c(); break; + case 0x89: op89(); break; + } + } +} + +void Cx4::writeb(uint16 addr, uint8 data) { + write(addr, data); +} + +void Cx4::writew(uint16 addr, uint16 data) { + write(addr + 0, data >> 0); + write(addr + 1, data >> 8); +} + +void Cx4::writel(uint16 addr, uint32 data) { + write(addr + 0, data >> 0); + write(addr + 1, data >> 8); + write(addr + 2, data >> 16); +} + +uint8 Cx4::read(uint addr, uint8 data) { + addr &= 0x1fff; + + if(addr < 0x0c00) { + return ram[addr]; + } + + if(addr >= 0x1f00) { + return reg[addr & 0xff]; + } + + return cpu.r.mdr; +} + +uint8 Cx4::readb(uint16 addr) { + return read(addr); +} + +uint16 Cx4::readw(uint16 addr) { + return read(addr) | (read(addr + 1) << 8); +} + +uint32 Cx4::readl(uint16 addr) { + return read(addr) | (read(addr + 1) << 8) + (read(addr + 2) << 16); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/cx4.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/cx4.hpp new file mode 100644 index 0000000000..4454e1f539 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/cx4.hpp @@ -0,0 +1,90 @@ +struct Cx4 { + auto power() -> void; + + auto read(uint addr, uint8 data = 0) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; + + uint8 ram[0x0c00]; + uint8 reg[0x0100]; + uint32 r0, r1, r2, r3, r4, r5, r6, r7, + r8, r9, r10, r11, r12, r13, r14, r15; + + static const uint8 immediate_data[48]; + static const uint16 wave_data[40]; + static const uint32 sin_table[256]; + + static const int16 SinTable[512]; + static const int16 CosTable[512]; + + int16 C4WFXVal, C4WFYVal, C4WFZVal, C4WFX2Val, C4WFY2Val, C4WFDist, C4WFScale; + int16 C41FXVal, C41FYVal, C41FAngleRes, C41FDist, C41FDistVal; + + void C4TransfWireFrame(); + void C4TransfWireFrame2(); + void C4CalcWireFrame(); + void C4DrawLine(int32 X1, int32 Y1, int16 Z1, int32 X2, int32 Y2, int16 Z2, uint8 Color); + void C4DrawWireFrame(); + void C4DoScaleRotate(int row_padding); + +public: + uint32 ldr(uint8 r); + void str(uint8 r, uint32 data); + void mul(uint32 x, uint32 y, uint32 &rl, uint32 &rh); + uint32 sin(uint32 rx); + uint32 cos(uint32 rx); + + void transfer_data(); + void immediate_reg(uint32 num); + + void op00_00(); + void op00_03(); + void op00_05(); + void op00_07(); + void op00_08(); + void op00_0b(); + void op00_0c(); + + void op00(); + void op01(); + void op05(); + void op0d(); + void op10(); + void op13(); + void op15(); + void op1f(); + void op22(); + void op25(); + void op2d(); + void op40(); + void op54(); + void op5c(); + void op5e(); + void op60(); + void op62(); + void op64(); + void op66(); + void op68(); + void op6a(); + void op6c(); + void op6e(); + void op70(); + void op72(); + void op74(); + void op76(); + void op78(); + void op7a(); + void op7c(); + void op89(); + + uint8 readb(uint16 addr); + uint16 readw(uint16 addr); + uint32 readl(uint16 addr); + + void writeb(uint16 addr, uint8 data); + void writew(uint16 addr, uint16 data); + void writel(uint16 addr, uint32 data); +}; + +extern Cx4 cx4; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/data.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/data.cpp new file mode 100644 index 0000000000..8538f6026d --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/data.cpp @@ -0,0 +1,187 @@ +#ifdef CX4_CPP + +const uint8 Cx4::immediate_data[48] = { + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x80, 0xff, 0xff, 0x7f, + 0x00, 0x80, 0x00, 0xff, 0x7f, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0xff, + 0x00, 0x00, 0x01, 0xff, 0xff, 0xfe, 0x00, 0x01, 0x00, 0xff, 0xfe, 0x00 +}; + +const uint16 Cx4::wave_data[40] = { + 0x0000, 0x0002, 0x0004, 0x0006, 0x0008, 0x000a, 0x000c, 0x000e, + 0x0200, 0x0202, 0x0204, 0x0206, 0x0208, 0x020a, 0x020c, 0x020e, + 0x0400, 0x0402, 0x0404, 0x0406, 0x0408, 0x040a, 0x040c, 0x040e, + 0x0600, 0x0602, 0x0604, 0x0606, 0x0608, 0x060a, 0x060c, 0x060e, + 0x0800, 0x0802, 0x0804, 0x0806, 0x0808, 0x080a, 0x080c, 0x080e +}; + +const uint32 Cx4::sin_table[256] = { + 0x000000, 0x000324, 0x000648, 0x00096c, 0x000c8f, 0x000fb2, 0x0012d5, 0x0015f6, + 0x001917, 0x001c37, 0x001f56, 0x002273, 0x002590, 0x0028aa, 0x002bc4, 0x002edb, + 0x0031f1, 0x003505, 0x003817, 0x003b26, 0x003e33, 0x00413e, 0x004447, 0x00474d, + 0x004a50, 0x004d50, 0x00504d, 0x005347, 0x00563e, 0x005931, 0x005c22, 0x005f0e, + 0x0061f7, 0x0064dc, 0x0067bd, 0x006a9b, 0x006d74, 0x007049, 0x007319, 0x0075e5, + 0x0078ad, 0x007b70, 0x007e2e, 0x0080e7, 0x00839c, 0x00864b, 0x0088f5, 0x008b9a, + 0x008e39, 0x0090d3, 0x009368, 0x0095f6, 0x00987f, 0x009b02, 0x009d7f, 0x009ff6, + 0x00a267, 0x00a4d2, 0x00a736, 0x00a994, 0x00abeb, 0x00ae3b, 0x00b085, 0x00b2c8, + 0x00b504, 0x00b73a, 0x00b968, 0x00bb8f, 0x00bdae, 0x00bfc7, 0x00c1d8, 0x00c3e2, + 0x00c5e4, 0x00c7de, 0x00c9d1, 0x00cbbb, 0x00cd9f, 0x00cf7a, 0x00d14d, 0x00d318, + 0x00d4db, 0x00d695, 0x00d848, 0x00d9f2, 0x00db94, 0x00dd2d, 0x00debe, 0x00e046, + 0x00e1c5, 0x00e33c, 0x00e4aa, 0x00e60f, 0x00e76b, 0x00e8bf, 0x00ea09, 0x00eb4b, + 0x00ec83, 0x00edb2, 0x00eed8, 0x00eff5, 0x00f109, 0x00f213, 0x00f314, 0x00f40b, + 0x00f4fa, 0x00f5de, 0x00f6ba, 0x00f78b, 0x00f853, 0x00f912, 0x00f9c7, 0x00fa73, + 0x00fb14, 0x00fbac, 0x00fc3b, 0x00fcbf, 0x00fd3a, 0x00fdab, 0x00fe13, 0x00fe70, + 0x00fec4, 0x00ff0e, 0x00ff4e, 0x00ff84, 0x00ffb1, 0x00ffd3, 0x00ffec, 0x00fffb, + 0x000000, 0xfffcdb, 0xfff9b7, 0xfff693, 0xfff370, 0xfff04d, 0xffed2a, 0xffea09, + 0xffe6e8, 0xffe3c8, 0xffe0a9, 0xffdd8c, 0xffda6f, 0xffd755, 0xffd43b, 0xffd124, + 0xffce0e, 0xffcafa, 0xffc7e8, 0xffc4d9, 0xffc1cc, 0xffbec1, 0xffbbb8, 0xffb8b2, + 0xffb5af, 0xffb2af, 0xffafb2, 0xffacb8, 0xffa9c1, 0xffa6ce, 0xffa3dd, 0xffa0f1, + 0xff9e08, 0xff9b23, 0xff9842, 0xff9564, 0xff928b, 0xff8fb6, 0xff8ce6, 0xff8a1a, + 0xff8752, 0xff848f, 0xff81d1, 0xff7f18, 0xff7c63, 0xff79b4, 0xff770a, 0xff7465, + 0xff71c6, 0xff6f2c, 0xff6c97, 0xff6a09, 0xff6780, 0xff64fd, 0xff6280, 0xff6009, + 0xff5d98, 0xff5b2d, 0xff58c9, 0xff566b, 0xff5414, 0xff51c4, 0xff4f7a, 0xff4d37, + 0xff4afb, 0xff48c5, 0xff4697, 0xff4470, 0xff4251, 0xff4038, 0xff3e27, 0xff3c1e, + 0xff3a1b, 0xff3821, 0xff362e, 0xff3444, 0xff3260, 0xff3085, 0xff2eb2, 0xff2ce7, + 0xff2b24, 0xff296a, 0xff27b7, 0xff260d, 0xff246b, 0xff22d2, 0xff2141, 0xff1fb9, + 0xff1e3a, 0xff1cc3, 0xff1b55, 0xff19f0, 0xff1894, 0xff1740, 0xff15f6, 0xff14b4, + 0xff137c, 0xff124d, 0xff1127, 0xff100a, 0xff0ef6, 0xff0dec, 0xff0ceb, 0xff0bf4, + 0xff0b05, 0xff0a21, 0xff0945, 0xff0874, 0xff07ac, 0xff06ed, 0xff0638, 0xff058d, + 0xff04eb, 0xff0453, 0xff03c4, 0xff0340, 0xff02c5, 0xff0254, 0xff01ec, 0xff018f, + 0xff013b, 0xff00f1, 0xff00b1, 0xff007b, 0xff004e, 0xff002c, 0xff0013, 0xff0004 +}; + +const int16 Cx4::SinTable[512] = { + 0, 402, 804, 1206, 1607, 2009, 2410, 2811, + 3211, 3611, 4011, 4409, 4808, 5205, 5602, 5997, + 6392, 6786, 7179, 7571, 7961, 8351, 8739, 9126, + 9512, 9896, 10278, 10659, 11039, 11416, 11793, 12167, + 12539, 12910, 13278, 13645, 14010, 14372, 14732, 15090, + 15446, 15800, 16151, 16499, 16846, 17189, 17530, 17869, + 18204, 18537, 18868, 19195, 19519, 19841, 20159, 20475, + 20787, 21097, 21403, 21706, 22005, 22301, 22594, 22884, + 23170, 23453, 23732, 24007, 24279, 24547, 24812, 25073, + 25330, 25583, 25832, 26077, 26319, 26557, 26790, 27020, + 27245, 27466, 27684, 27897, 28106, 28310, 28511, 28707, + 28898, 29086, 29269, 29447, 29621, 29791, 29956, 30117, + 30273, 30425, 30572, 30714, 30852, 30985, 31114, 31237, + 31357, 31471, 31581, 31685, 31785, 31881, 31971, 32057, + 32138, 32214, 32285, 32351, 32413, 32469, 32521, 32568, + 32610, 32647, 32679, 32706, 32728, 32745, 32758, 32765, + 32767, 32765, 32758, 32745, 32728, 32706, 32679, 32647, + 32610, 32568, 32521, 32469, 32413, 32351, 32285, 32214, + 32138, 32057, 31971, 31881, 31785, 31685, 31581, 31471, + 31357, 31237, 31114, 30985, 30852, 30714, 30572, 30425, + 30273, 30117, 29956, 29791, 29621, 29447, 29269, 29086, + 28898, 28707, 28511, 28310, 28106, 27897, 27684, 27466, + 27245, 27020, 26790, 26557, 26319, 26077, 25832, 25583, + 25330, 25073, 24812, 24547, 24279, 24007, 23732, 23453, + 23170, 22884, 22594, 22301, 22005, 21706, 21403, 21097, + 20787, 20475, 20159, 19841, 19519, 19195, 18868, 18537, + 18204, 17869, 17530, 17189, 16846, 16499, 16151, 15800, + 15446, 15090, 14732, 14372, 14010, 13645, 13278, 12910, + 12539, 12167, 11793, 11416, 11039, 10659, 10278, 9896, + 9512, 9126, 8739, 8351, 7961, 7571, 7179, 6786, + 6392, 5997, 5602, 5205, 4808, 4409, 4011, 3611, + 3211, 2811, 2410, 2009, 1607, 1206, 804, 402, + 0, -402, -804, -1206, -1607, -2009, -2410, -2811, + -3211, -3611, -4011, -4409, -4808, -5205, -5602, -5997, + -6392, -6786, -7179, -7571, -7961, -8351, -8739, -9126, + -9512, -9896, -10278, -10659, -11039, -11416, -11793, -12167, + -12539, -12910, -13278, -13645, -14010, -14372, -14732, -15090, + -15446, -15800, -16151, -16499, -16846, -17189, -17530, -17869, + -18204, -18537, -18868, -19195, -19519, -19841, -20159, -20475, + -20787, -21097, -21403, -21706, -22005, -22301, -22594, -22884, + -23170, -23453, -23732, -24007, -24279, -24547, -24812, -25073, + -25330, -25583, -25832, -26077, -26319, -26557, -26790, -27020, + -27245, -27466, -27684, -27897, -28106, -28310, -28511, -28707, + -28898, -29086, -29269, -29447, -29621, -29791, -29956, -30117, + -30273, -30425, -30572, -30714, -30852, -30985, -31114, -31237, + -31357, -31471, -31581, -31685, -31785, -31881, -31971, -32057, + -32138, -32214, -32285, -32351, -32413, -32469, -32521, -32568, + -32610, -32647, -32679, -32706, -32728, -32745, -32758, -32765, + -32767, -32765, -32758, -32745, -32728, -32706, -32679, -32647, + -32610, -32568, -32521, -32469, -32413, -32351, -32285, -32214, + -32138, -32057, -31971, -31881, -31785, -31685, -31581, -31471, + -31357, -31237, -31114, -30985, -30852, -30714, -30572, -30425, + -30273, -30117, -29956, -29791, -29621, -29447, -29269, -29086, + -28898, -28707, -28511, -28310, -28106, -27897, -27684, -27466, + -27245, -27020, -26790, -26557, -26319, -26077, -25832, -25583, + -25330, -25073, -24812, -24547, -24279, -24007, -23732, -23453, + -23170, -22884, -22594, -22301, -22005, -21706, -21403, -21097, + -20787, -20475, -20159, -19841, -19519, -19195, -18868, -18537, + -18204, -17869, -17530, -17189, -16846, -16499, -16151, -15800, + -15446, -15090, -14732, -14372, -14010, -13645, -13278, -12910, + -12539, -12167, -11793, -11416, -11039, -10659, -10278, -9896, + -9512, -9126, -8739, -8351, -7961, -7571, -7179, -6786, + -6392, -5997, -5602, -5205, -4808, -4409, -4011, -3611, + -3211, -2811, -2410, -2009, -1607, -1206, -804, -402 +}; + +const int16 Cx4::CosTable[512] = { + 32767, 32765, 32758, 32745, 32728, 32706, 32679, 32647, + 32610, 32568, 32521, 32469, 32413, 32351, 32285, 32214, + 32138, 32057, 31971, 31881, 31785, 31685, 31581, 31471, + 31357, 31237, 31114, 30985, 30852, 30714, 30572, 30425, + 30273, 30117, 29956, 29791, 29621, 29447, 29269, 29086, + 28898, 28707, 28511, 28310, 28106, 27897, 27684, 27466, + 27245, 27020, 26790, 26557, 26319, 26077, 25832, 25583, + 25330, 25073, 24812, 24547, 24279, 24007, 23732, 23453, + 23170, 22884, 22594, 22301, 22005, 21706, 21403, 21097, + 20787, 20475, 20159, 19841, 19519, 19195, 18868, 18537, + 18204, 17869, 17530, 17189, 16846, 16499, 16151, 15800, + 15446, 15090, 14732, 14372, 14010, 13645, 13278, 12910, + 12539, 12167, 11793, 11416, 11039, 10659, 10278, 9896, + 9512, 9126, 8739, 8351, 7961, 7571, 7179, 6786, + 6392, 5997, 5602, 5205, 4808, 4409, 4011, 3611, + 3211, 2811, 2410, 2009, 1607, 1206, 804, 402, + 0, -402, -804, -1206, -1607, -2009, -2410, -2811, + -3211, -3611, -4011, -4409, -4808, -5205, -5602, -5997, + -6392, -6786, -7179, -7571, -7961, -8351, -8739, -9126, + -9512, -9896, -10278, -10659, -11039, -11416, -11793, -12167, + -12539, -12910, -13278, -13645, -14010, -14372, -14732, -15090, + -15446, -15800, -16151, -16499, -16846, -17189, -17530, -17869, + -18204, -18537, -18868, -19195, -19519, -19841, -20159, -20475, + -20787, -21097, -21403, -21706, -22005, -22301, -22594, -22884, + -23170, -23453, -23732, -24007, -24279, -24547, -24812, -25073, + -25330, -25583, -25832, -26077, -26319, -26557, -26790, -27020, + -27245, -27466, -27684, -27897, -28106, -28310, -28511, -28707, + -28898, -29086, -29269, -29447, -29621, -29791, -29956, -30117, + -30273, -30425, -30572, -30714, -30852, -30985, -31114, -31237, + -31357, -31471, -31581, -31685, -31785, -31881, -31971, -32057, + -32138, -32214, -32285, -32351, -32413, -32469, -32521, -32568, + -32610, -32647, -32679, -32706, -32728, -32745, -32758, -32765, + -32767, -32765, -32758, -32745, -32728, -32706, -32679, -32647, + -32610, -32568, -32521, -32469, -32413, -32351, -32285, -32214, + -32138, -32057, -31971, -31881, -31785, -31685, -31581, -31471, + -31357, -31237, -31114, -30985, -30852, -30714, -30572, -30425, + -30273, -30117, -29956, -29791, -29621, -29447, -29269, -29086, + -28898, -28707, -28511, -28310, -28106, -27897, -27684, -27466, + -27245, -27020, -26790, -26557, -26319, -26077, -25832, -25583, + -25330, -25073, -24812, -24547, -24279, -24007, -23732, -23453, + -23170, -22884, -22594, -22301, -22005, -21706, -21403, -21097, + -20787, -20475, -20159, -19841, -19519, -19195, -18868, -18537, + -18204, -17869, -17530, -17189, -16846, -16499, -16151, -15800, + -15446, -15090, -14732, -14372, -14010, -13645, -13278, -12910, + -12539, -12167, -11793, -11416, -11039, -10659, -10278, -9896, + -9512, -9126, -8739, -8351, -7961, -7571, -7179, -6786, + -6392, -5997, -5602, -5205, -4808, -4409, -4011, -3611, + -3211, -2811, -2410, -2009, -1607, -1206, -804, -402, + 0, 402, 804, 1206, 1607, 2009, 2410, 2811, + 3211, 3611, 4011, 4409, 4808, 5205, 5602, 5997, + 6392, 6786, 7179, 7571, 7961, 8351, 8739, 9126, + 9512, 9896, 10278, 10659, 11039, 11416, 11793, 12167, + 12539, 12910, 13278, 13645, 14010, 14372, 14732, 15090, + 15446, 15800, 16151, 16499, 16846, 17189, 17530, 17869, + 18204, 18537, 18868, 19195, 19519, 19841, 20159, 20475, + 20787, 21097, 21403, 21706, 22005, 22301, 22594, 22884, + 23170, 23453, 23732, 24007, 24279, 24547, 24812, 25073, + 25330, 25583, 25832, 26077, 26319, 26557, 26790, 27020, + 27245, 27466, 27684, 27897, 28106, 28310, 28511, 28707, + 28898, 29086, 29269, 29447, 29621, 29791, 29956, 30117, + 30273, 30425, 30572, 30714, 30852, 30985, 31114, 31237, + 31357, 31471, 31581, 31685, 31785, 31881, 31971, 32057, + 32138, 32214, 32285, 32351, 32413, 32469, 32521, 32568, + 32610, 32647, 32679, 32706, 32728, 32745, 32758, 32765 +}; + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/functions.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/functions.cpp new file mode 100644 index 0000000000..b4684dd4a8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/functions.cpp @@ -0,0 +1,251 @@ +#ifdef CX4_CPP + +#include +#define Tan(a) (CosTable[a] ? ((((int32)SinTable[a]) << 16) / CosTable[a]) : 0x80000000) +#define sar(b, n) ((b) >> (n)) +#ifdef PI +#undef PI +#endif +#define PI 3.1415926535897932384626433832795 + +//Wireframe Helpers +void Cx4::C4TransfWireFrame() { + double c4x = (double)C4WFXVal; + double c4y = (double)C4WFYVal; + double c4z = (double)C4WFZVal - 0x95; + double tanval, c4x2, c4y2, c4z2; + + //Rotate X + tanval = -(double)C4WFX2Val * PI * 2 / 128; + c4y2 = c4y * ::cos(tanval) - c4z * ::sin(tanval); + c4z2 = c4y * ::sin(tanval) + c4z * ::cos(tanval); + + //Rotate Y + tanval = -(double)C4WFY2Val * PI * 2 / 128; + c4x2 = c4x * ::cos(tanval) + c4z2 * ::sin(tanval); + c4z = c4x * -::sin(tanval) + c4z2 * ::cos(tanval); + + //Rotate Z + tanval = -(double)C4WFDist * PI * 2 / 128; + c4x = c4x2 * ::cos(tanval) - c4y2 * ::sin(tanval); + c4y = c4x2 * ::sin(tanval) + c4y2 * ::cos(tanval); + + //Scale + C4WFXVal = (int16)(c4x * C4WFScale / (0x90 * (c4z + 0x95)) * 0x95); + C4WFYVal = (int16)(c4y * C4WFScale / (0x90 * (c4z + 0x95)) * 0x95); +} + +void Cx4::C4CalcWireFrame() { + C4WFXVal = C4WFX2Val - C4WFXVal; + C4WFYVal = C4WFY2Val - C4WFYVal; + + if(abs(C4WFXVal) > abs(C4WFYVal)) { + C4WFDist = abs(C4WFXVal) + 1; + C4WFYVal = (256 * (long)C4WFYVal) / abs(C4WFXVal); + C4WFXVal = (C4WFXVal < 0) ? -256 : 256; + } else if(C4WFYVal != 0) { + C4WFDist = abs(C4WFYVal) + 1; + C4WFXVal = (256 * (long)C4WFXVal) / abs(C4WFYVal); + C4WFYVal = (C4WFYVal < 0) ? -256 : 256; + } else { + C4WFDist = 0; + } +} + +void Cx4::C4TransfWireFrame2() { + double c4x = (double)C4WFXVal; + double c4y = (double)C4WFYVal; + double c4z = (double)C4WFZVal; + double tanval, c4x2, c4y2, c4z2; + + //Rotate X + tanval = -(double)C4WFX2Val * PI * 2 / 128; + c4y2 = c4y * ::cos(tanval) - c4z * ::sin(tanval); + c4z2 = c4y * ::sin(tanval) + c4z * ::cos(tanval); + + //Rotate Y + tanval = -(double)C4WFY2Val * PI * 2 / 128; + c4x2 = c4x * ::cos(tanval) + c4z2 * ::sin(tanval); + c4z = c4x * -::sin(tanval) + c4z2 * ::cos(tanval); + + //Rotate Z + tanval = -(double)C4WFDist * PI * 2 / 128; + c4x = c4x2 * ::cos(tanval) - c4y2 * ::sin(tanval); + c4y = c4x2 * ::sin(tanval) + c4y2 * ::cos(tanval); + + //Scale + C4WFXVal = (int16)(c4x * C4WFScale / 0x100); + C4WFYVal = (int16)(c4y * C4WFScale / 0x100); +} + +void Cx4::C4DrawWireFrame() { + uint32 line = readl(0x1f80); + uint32 point1, point2; + int16 X1, Y1, Z1; + int16 X2, Y2, Z2; + uint8 Color; + + for(int32 i = ram[0x0295]; i > 0; i--, line += 5) { + if(bus.read(line) == 0xff && bus.read(line + 1) == 0xff) { + int32 tmp = line - 5; + while(bus.read(tmp + 2) == 0xff && bus.read(tmp + 3) == 0xff && (tmp + 2) >= 0) { tmp -= 5; } + point1 = (read(0x1f82) << 16) | (bus.read(tmp + 2) << 8) | bus.read(tmp + 3); + } else { + point1 = (read(0x1f82) << 16) | (bus.read(line) << 8) | bus.read(line + 1); + } + point2 = (read(0x1f82) << 16) | (bus.read(line + 2) << 8) | bus.read(line + 3); + + X1=(bus.read(point1 + 0) << 8) | bus.read(point1 + 1); + Y1=(bus.read(point1 + 2) << 8) | bus.read(point1 + 3); + Z1=(bus.read(point1 + 4) << 8) | bus.read(point1 + 5); + X2=(bus.read(point2 + 0) << 8) | bus.read(point2 + 1); + Y2=(bus.read(point2 + 2) << 8) | bus.read(point2 + 3); + Z2=(bus.read(point2 + 4) << 8) | bus.read(point2 + 5); + Color = bus.read(line + 4); + C4DrawLine(X1, Y1, Z1, X2, Y2, Z2, Color); + } +} + +void Cx4::C4DrawLine(int32 X1, int32 Y1, int16 Z1, int32 X2, int32 Y2, int16 Z2, uint8 Color) { + //Transform coordinates + C4WFXVal = (int16)X1; + C4WFYVal = (int16)Y1; + C4WFZVal = Z1; + C4WFScale = read(0x1f90); + C4WFX2Val = read(0x1f86); + C4WFY2Val = read(0x1f87); + C4WFDist = read(0x1f88); + C4TransfWireFrame2(); + X1 = (C4WFXVal + 48) << 8; + Y1 = (C4WFYVal + 48) << 8; + + C4WFXVal = (int16)X2; + C4WFYVal = (int16)Y2; + C4WFZVal = Z2; + C4TransfWireFrame2(); + X2 = (C4WFXVal + 48) << 8; + Y2 = (C4WFYVal + 48) << 8; + + //Get line info + C4WFXVal = (int16)(X1 >> 8); + C4WFYVal = (int16)(Y1 >> 8); + C4WFX2Val = (int16)(X2 >> 8); + C4WFY2Val = (int16)(Y2 >> 8); + C4CalcWireFrame(); + X2 = (int16)C4WFXVal; + Y2 = (int16)C4WFYVal; + + //Render line + for(int32 i = C4WFDist ? C4WFDist : (int16)1; i > 0; i--) { + if(X1 > 0xff && Y1 > 0xff && X1 < 0x6000 && Y1 < 0x6000) { + uint16 addr = (((Y1 >> 8) >> 3) << 8) - (((Y1 >> 8) >> 3) << 6) + (((X1 >> 8) >> 3) << 4) + ((Y1 >> 8) & 7) * 2; + uint8 bit = 0x80 >> ((X1 >> 8) & 7); + ram[addr + 0x300] &= ~bit; + ram[addr + 0x301] &= ~bit; + if(Color & 1) ram[addr + 0x300] |= bit; + if(Color & 2) ram[addr + 0x301] |= bit; + } + X1 += X2; + Y1 += Y2; + } +} + +void Cx4::C4DoScaleRotate(int row_padding) { + int16 A, B, C, D; + + //Calculate matrix + int32 XScale = readw(0x1f8f); + int32 YScale = readw(0x1f92); + + if(XScale & 0x8000)XScale = 0x7fff; + if(YScale & 0x8000)YScale = 0x7fff; + + if(readw(0x1f80) == 0) { //no rotation + A = (int16)XScale; + B = 0; + C = 0; + D = (int16)YScale; + } else if(readw(0x1f80) == 128) { //90 degree rotation + A = 0; + B = (int16)(-YScale); + C = (int16)XScale; + D = 0; + } else if(readw(0x1f80) == 256) { //180 degree rotation + A = (int16)(-XScale); + B = 0; + C = 0; + D = (int16)(-YScale); + } else if(readw(0x1f80) == 384) { //270 degree rotation + A = 0; + B = (int16)YScale; + C = (int16)(-XScale); + D = 0; + } else { + A = (int16) sar(CosTable[readw(0x1f80) & 0x1ff] * XScale, 15); + B = (int16)(-sar(SinTable[readw(0x1f80) & 0x1ff] * YScale, 15)); + C = (int16) sar(SinTable[readw(0x1f80) & 0x1ff] * XScale, 15); + D = (int16) sar(CosTable[readw(0x1f80) & 0x1ff] * YScale, 15); + } + + //Calculate Pixel Resolution + uint8 w = read(0x1f89) & ~7; + uint8 h = read(0x1f8c) & ~7; + + //Clear the output RAM + memset(ram, 0, (w + row_padding / 4) * h / 2); + + int32 Cx = (int16)readw(0x1f83); + int32 Cy = (int16)readw(0x1f86); + + //Calculate start position (i.e. (Ox, Oy) = (0, 0)) + //The low 12 bits are fractional, so (Cx<<12) gives us the Cx we want in + //the function. We do Cx*A etc normally because the matrix parameters + //already have the fractional parts. + int32 LineX = (Cx << 12) - Cx * A - Cx * B; + int32 LineY = (Cy << 12) - Cy * C - Cy * D; + + //Start loop + uint32 X, Y; + uint8 byte; + int32 outidx = 0; + uint8 bit = 0x80; + + for(int32 y = 0; y < h; y++) { + X = LineX; + Y = LineY; + for(int32 x = 0; x < w; x++) { + if((X >> 12) >= w || (Y >> 12) >= h) { + byte = 0; + } else { + uint32 addr = (Y >> 12) * w + (X >> 12); + byte = read(0x600 + (addr >> 1)); + if(addr & 1) { byte >>= 4; } + } + + //De-bitplanify + if(byte & 1) ram[outidx ] |= bit; + if(byte & 2) ram[outidx + 1] |= bit; + if(byte & 4) ram[outidx + 16] |= bit; + if(byte & 8) ram[outidx + 17] |= bit; + + bit >>= 1; + if(!bit) { + bit = 0x80; + outidx += 32; + } + + X += A; //Add 1 to output x => add an A and a C + Y += C; + } + outidx += 2 + row_padding; + if(outidx & 0x10) { + outidx &= ~0x10; + } else { + outidx -= w * 4 + row_padding; + } + LineX += B; //Add 1 to output y => add a B and a D + LineY += D; + } +} + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/oam.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/oam.cpp new file mode 100644 index 0000000000..af958b589f --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/oam.cpp @@ -0,0 +1,228 @@ +#ifdef CX4_CPP + +//Build OAM +void Cx4::op00_00() { + uint32 oamptr = ram[0x626] << 2; + for(int32 i = 0x1fd; i > oamptr && i >= 0; i -= 4) { + //clear oam-to-be + if(i >= 0) ram[i] = 0xe0; + } + + uint16 globalx, globaly; + uint32 oamptr2; + int16 sprx, spry; + uint8 sprname, sprattr; + uint8 sprcount; + + globalx = readw(0x621); + globaly = readw(0x623); + oamptr2 = 0x200 + (ram[0x626] >> 2); + + if(!ram[0x620]) return; + + sprcount = 128 - ram[0x626]; + uint8 offset = (ram[0x626] & 3) * 2; + uint32 srcptr = 0x220; + + for(int i = ram[0x620]; i > 0 && sprcount > 0; i--, srcptr += 16) { + sprx = readw(srcptr) - globalx; + spry = readw(srcptr + 2) - globaly; + sprname = ram[srcptr + 5]; + sprattr = ram[srcptr + 4] | ram[srcptr + 6]; + + uint32 spraddr = readl(srcptr + 7); + if(bus.read(spraddr)) { + int16 x, y; + for(int sprcnt = bus.read(spraddr++); sprcnt > 0 && sprcount > 0; sprcnt--, spraddr += 4) { + x = (int8)bus.read(spraddr + 1); + if(sprattr & 0x40) { + x = -x - ((bus.read(spraddr) & 0x20) ? 16 : 8); + } + x += sprx; + if(x >= -16 && x <= 272) { + y = (int8)bus.read(spraddr + 2); + if(sprattr & 0x80) { + y = -y - ((bus.read(spraddr) & 0x20) ? 16 : 8); + } + y += spry; + if(y >= -16 && y <= 224) { + ram[oamptr ] = (uint8)x; + ram[oamptr + 1] = (uint8)y; + ram[oamptr + 2] = sprname + bus.read(spraddr + 3); + ram[oamptr + 3] = sprattr ^ (bus.read(spraddr) & 0xc0); + ram[oamptr2] &= ~(3 << offset); + if(x & 0x100) ram[oamptr2] |= 1 << offset; + if(bus.read(spraddr) & 0x20) ram[oamptr2] |= 2 << offset; + oamptr += 4; + sprcount--; + offset = (offset + 2) & 6; + if(!offset)oamptr2++; + } + } + } + } else if(sprcount > 0) { + ram[oamptr ] = (uint8)sprx; + ram[oamptr + 1] = (uint8)spry; + ram[oamptr + 2] = sprname; + ram[oamptr + 3] = sprattr; + ram[oamptr2] &= ~(3 << offset); + if(sprx & 0x100) ram[oamptr2] |= 3 << offset; + else ram[oamptr2] |= 2 << offset; + oamptr += 4; + sprcount--; + offset = (offset + 2) & 6; + if(!offset) oamptr2++; + } + } +} + +//Scale and Rotate +void Cx4::op00_03() { + C4DoScaleRotate(0); +} + +//Transform Lines +void Cx4::op00_05() { + C4WFX2Val = read(0x1f83); + C4WFY2Val = read(0x1f86); + C4WFDist = read(0x1f89); + C4WFScale = read(0x1f8c); + +//Transform Vertices +uint32 ptr = 0; + for(int32 i = readw(0x1f80); i > 0; i--, ptr += 0x10) { + C4WFXVal = readw(ptr + 1); + C4WFYVal = readw(ptr + 5); + C4WFZVal = readw(ptr + 9); + C4TransfWireFrame(); + + //Displace + writew(ptr + 1, C4WFXVal + 0x80); + writew(ptr + 5, C4WFYVal + 0x50); + } + + writew(0x600, 23); + writew(0x602, 0x60); + writew(0x605, 0x40); + writew(0x600 + 8, 23); + writew(0x602 + 8, 0x60); + writew(0x605 + 8, 0x40); + + ptr = 0xb02; + uint32 ptr2 = 0; + + for(int32 i = readw(0xb00); i > 0; i--, ptr += 2, ptr2 += 8) { + C4WFXVal = readw((read(ptr + 0) << 4) + 1); + C4WFYVal = readw((read(ptr + 0) << 4) + 5); + C4WFX2Val = readw((read(ptr + 1) << 4) + 1); + C4WFY2Val = readw((read(ptr + 1) << 4) + 5); + C4CalcWireFrame(); + writew(ptr2 + 0x600, C4WFDist ? C4WFDist : (int16)1); + writew(ptr2 + 0x602, C4WFXVal); + writew(ptr2 + 0x605, C4WFYVal); + } +} + +//Scale and Rotate +void Cx4::op00_07() { + C4DoScaleRotate(64); +} + +//Draw Wireframe +void Cx4::op00_08() { + C4DrawWireFrame(); +} + +//Disintegrate +void Cx4::op00_0b() { + uint8 width, height; + uint32 startx, starty; + uint32 srcptr; + uint32 x, y; + int32 scalex, scaley; + int32 cx, cy; + int32 i, j; + + width = read(0x1f89); + height = read(0x1f8c); + cx = readw(0x1f80); + cy = readw(0x1f83); + + scalex = (int16)readw(0x1f86); + scaley = (int16)readw(0x1f8f); + startx = -cx * scalex + (cx << 8); + starty = -cy * scaley + (cy << 8); + srcptr = 0x600; + + for(i = 0; i < (width * height) >> 1; i++) { + write(i, 0); + } + + for(y = starty, i = 0;i < height; i++, y += scaley) { + for(x = startx, j = 0;j < width; j++, x += scalex) { + if((x >> 8) < width && (y >> 8) < height && (y >> 8) * width + (x >> 8) < 0x2000) { + uint8 pixel = (j & 1) ? (uint8)(ram[srcptr] >> 4) : (ram[srcptr]); + int32 index = (y >> 11) * width * 4 + (x >> 11) * 32 + ((y >> 8) & 7) * 2; + uint8 mask = 0x80 >> ((x >> 8) & 7); + + if(pixel & 1) ram[index ] |= mask; + if(pixel & 2) ram[index + 1] |= mask; + if(pixel & 4) ram[index + 16] |= mask; + if(pixel & 8) ram[index + 17] |= mask; + } + if(j & 1) srcptr++; + } + } +} + +//Bitplane Wave +void Cx4::op00_0c() { + uint32 destptr = 0; + uint32 waveptr = read(0x1f83); + uint16 mask1 = 0xc0c0; + uint16 mask2 = 0x3f3f; + + for(int j = 0; j < 0x10; j++) { + do { + int16 height = -((int8)read(waveptr + 0xb00)) - 16; + for(int i = 0; i < 40; i++) { + uint16 temp = readw(destptr + wave_data[i]) & mask2; + if(height >= 0) { + if(height < 8) { + temp |= mask1 & readw(0xa00 + height * 2); + } else { + temp |= mask1 & 0xff00; + } + } + writew(destptr + wave_data[i], temp); + height++; + } + waveptr = (waveptr + 1) & 0x7f; + mask1 = (mask1 >> 2) | (mask1 << 6); + mask2 = (mask2 >> 2) | (mask2 << 6); + } while(mask1 != 0xc0c0); + destptr += 16; + + do { + int16 height = -((int8)read(waveptr + 0xb00)) - 16; + for(int i = 0; i < 40; i++) { + uint16 temp = readw(destptr + wave_data[i]) & mask2; + if(height >= 0) { + if(height < 8) { + temp |= mask1 & readw(0xa10 + height * 2); + } else { + temp |= mask1 & 0xff00; + } + } + writew(destptr + wave_data[i], temp); + height++; + } + waveptr = (waveptr + 1) & 0x7f; + mask1 = (mask1 >> 2) | (mask1 << 6); + mask2 = (mask2 >> 2) | (mask2 << 6); + } while(mask1 != 0xc0c0); + destptr += 16; + } +} + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/opcodes.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/opcodes.cpp new file mode 100644 index 0000000000..639097b149 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/opcodes.cpp @@ -0,0 +1,228 @@ +#ifdef CX4_CPP + +//Sprite Functions +void Cx4::op00() { + switch(reg[0x4d]) { + case 0x00: op00_00(); break; + case 0x03: op00_03(); break; + case 0x05: op00_05(); break; + case 0x07: op00_07(); break; + case 0x08: op00_08(); break; + case 0x0b: op00_0b(); break; + case 0x0c: op00_0c(); break; + } +} + +//Draw Wireframe +void Cx4::op01() { + memset(ram + 0x300, 0, 2304); + C4DrawWireFrame(); +} + +//Propulsion +void Cx4::op05() { + int32 temp = 0x10000; + if(readw(0x1f83)) { + temp = sar((temp / readw(0x1f83)) * readw(0x1f81), 8); + } + writew(0x1f80, temp); +} + +//Set Vector length +void Cx4::op0d() { + C41FXVal = readw(0x1f80); + C41FYVal = readw(0x1f83); + C41FDistVal = readw(0x1f86); + double tanval = sqrt(((double)C41FYVal) * ((double)C41FYVal) + ((double)C41FXVal) * ((double)C41FXVal)); + tanval = (double)C41FDistVal / tanval; + C41FYVal = (int16)(((double)C41FYVal * tanval) * 0.99); + C41FXVal = (int16)(((double)C41FXVal * tanval) * 0.98); + writew(0x1f89, C41FXVal); + writew(0x1f8c, C41FYVal); +} + +//Triangle +void Cx4::op10() { + r0 = ldr(0); + r1 = ldr(1); + + r4 = r0 & 0x1ff; + if(r1 & 0x8000)r1 |= ~0x7fff; + else r1 &= 0x7fff; + + mul(cos(r4), r1, r5, r2); + r5 = (r5 >> 16) & 0xff; + r2 = (r2 << 8) + r5; + + mul(sin(r4), r1, r5, r3); + r5 = (r5 >> 16) & 0xff; + r3 = (r3 << 8) + r5; + + str(0, r0); + str(1, r1); + str(2, r2); + str(3, r3); + str(4, r4); + str(5, r5); +} + +//Triangle +void Cx4::op13() { + r0 = ldr(0); + r1 = ldr(1); + + r4 = r0 & 0x1ff; + + mul(cos(r4), r1, r5, r2); + r5 = (r5 >> 8) & 0xffff; + r2 = (r2 << 16) + r5; + + mul(sin(r4), r1, r5, r3); + r5 = (r5 >> 8) & 0xffff; + r3 = (r3 << 16) + r5; + + str(0, r0); + str(1, r1); + str(2, r2); + str(3, r3); + str(4, r4); + str(5, r5); +} + +//Pythagorean +void Cx4::op15() { + C41FXVal = readw(0x1f80); + C41FYVal = readw(0x1f83); + C41FDist = (int16)sqrt((double)C41FXVal * (double)C41FXVal + (double)C41FYVal * (double)C41FYVal); + writew(0x1f80, C41FDist); +} + +//Calculate distance +void Cx4::op1f() { + C41FXVal = readw(0x1f80); + C41FYVal = readw(0x1f83); + if(!C41FXVal) { + C41FAngleRes = (C41FYVal > 0) ? 0x080 : 0x180; + } else { + double tanval = ((double)C41FYVal) / ((double)C41FXVal); + C41FAngleRes = (short)(atan(tanval) / (PI * 2) * 512); + C41FAngleRes = C41FAngleRes; + if(C41FXVal < 0) { + C41FAngleRes += 0x100; + } + C41FAngleRes &= 0x1ff; + } + writew(0x1f86, C41FAngleRes); +} + +//Trapezoid +void Cx4::op22() { + int16 angle1 = readw(0x1f8c) & 0x1ff; + int16 angle2 = readw(0x1f8f) & 0x1ff; + int32 tan1 = Tan(angle1); + int32 tan2 = Tan(angle2); + int16 y = readw(0x1f83) - readw(0x1f89); + int16 left, right; + + for(int32 j = 0; j < 225; j++, y++) { + if(y >= 0) { + left = sar((int32)tan1 * y, 16) - readw(0x1f80) + readw(0x1f86); + right = sar((int32)tan2 * y, 16) - readw(0x1f80) + readw(0x1f86) + readw(0x1f93); + + if(left < 0 && right < 0) { + left = 1; + right = 0; + } else if(left < 0) { + left = 0; + } else if(right < 0) { + right = 0; + } + + if(left > 255 && right > 255) { + left = 255; + right = 254; + } else if(left > 255) { + left = 255; + } else if(right > 255) { + right = 255; + } + } else { + left = 1; + right = 0; + } + ram[j + 0x800] = (uint8)left; + ram[j + 0x900] = (uint8)right; + } +} + +//Multiply +void Cx4::op25() { + r0 = ldr(0); + r1 = ldr(1); + mul(r0, r1, r0, r1); + str(0, r0); + str(1, r1); +} + +//Transform Coords +void Cx4::op2d() { + C4WFXVal = readw(0x1f81); + C4WFYVal = readw(0x1f84); + C4WFZVal = readw(0x1f87); + C4WFX2Val = read (0x1f89); + C4WFY2Val = read (0x1f8a); + C4WFDist = read (0x1f8b); + C4WFScale = readw(0x1f90); + C4TransfWireFrame2(); + writew(0x1f80, C4WFXVal); + writew(0x1f83, C4WFYVal); +} + +//Sum +void Cx4::op40() { + r0 = 0; + for(uint32 i=0;i<0x800;i++) { + r0 += ram[i]; + } + str(0, r0); +} + +//Square +void Cx4::op54() { + r0 = ldr(0); + mul(r0, r0, r1, r2); + str(1, r1); + str(2, r2); +} + +//Immediate Register +void Cx4::op5c() { + str(0, 0x000000); + immediate_reg(0); +} + +//Immediate Register (Multiple) +void Cx4::op5e() { immediate_reg( 0); } +void Cx4::op60() { immediate_reg( 3); } +void Cx4::op62() { immediate_reg( 6); } +void Cx4::op64() { immediate_reg( 9); } +void Cx4::op66() { immediate_reg(12); } +void Cx4::op68() { immediate_reg(15); } +void Cx4::op6a() { immediate_reg(18); } +void Cx4::op6c() { immediate_reg(21); } +void Cx4::op6e() { immediate_reg(24); } +void Cx4::op70() { immediate_reg(27); } +void Cx4::op72() { immediate_reg(30); } +void Cx4::op74() { immediate_reg(33); } +void Cx4::op76() { immediate_reg(36); } +void Cx4::op78() { immediate_reg(39); } +void Cx4::op7a() { immediate_reg(42); } +void Cx4::op7c() { immediate_reg(45); } + +//Immediate ROM +void Cx4::op89() { + str(0, 0x054336); + str(1, 0xffffff); +} + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/serialization.cpp new file mode 100644 index 0000000000..9502de48e0 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/cx4/serialization.cpp @@ -0,0 +1,35 @@ +auto Cx4::serialize(serializer& s) -> void { + s.array(ram); + s.array(reg); + + s.integer(r0); + s.integer(r1); + s.integer(r2); + s.integer(r3); + s.integer(r4); + s.integer(r5); + s.integer(r6); + s.integer(r7); + s.integer(r8); + s.integer(r9); + s.integer(r10); + s.integer(r11); + s.integer(r12); + s.integer(r13); + s.integer(r14); + s.integer(r15); + + s.integer(C4WFXVal); + s.integer(C4WFYVal); + s.integer(C4WFZVal); + s.integer(C4WFX2Val); + s.integer(C4WFY2Val); + s.integer(C4WFDist); + s.integer(C4WFScale); + + s.integer(C41FXVal); + s.integer(C41FYVal); + s.integer(C41FAngleRes); + s.integer(C41FDist); + s.integer(C41FDistVal); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dip/dip.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dip/dip.cpp new file mode 100644 index 0000000000..a8945714f7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dip/dip.cpp @@ -0,0 +1,21 @@ +//DIP switch +//used for Nintendo Super System emulation + +#include + +namespace SuperFamicom { + +#include "serialization.cpp" +DIP dip; + +auto DIP::power() -> void { +} + +auto DIP::read(uint, uint8) -> uint8 { + return value; +} + +auto DIP::write(uint, uint8) -> void { +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dip/dip.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dip/dip.hpp new file mode 100644 index 0000000000..7004d3d1fe --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dip/dip.hpp @@ -0,0 +1,14 @@ +struct DIP { + //dip.cpp + auto power() -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint8 value = 0x00; +}; + +extern DIP dip; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dip/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dip/serialization.cpp new file mode 100644 index 0000000000..ed645e37d1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dip/serialization.cpp @@ -0,0 +1,3 @@ +auto DIP::serialize(serializer& s) -> void { + s.integer(value); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1.cpp new file mode 100644 index 0000000000..0d76ed8341 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1.cpp @@ -0,0 +1,48 @@ +#include + +namespace SuperFamicom { + +#define int8 int8_t +#define int16 int16_t +#define int32 int32_t +#define int64 int64_t +#define uint8 uint8_t +#define uint16 uint16_t +#define uint32 uint32_t +#define uint64 uint64_t +#define DSP1_CPP +#include "dsp1emu.hpp" +#include "dsp1emu.cpp" +Dsp1 dsp1emu; +#undef int8 +#undef int16 +#undef int32 +#undef int64 +#undef uint8 +#undef uint16 +#undef uint32 +#undef uint64 + +DSP1 dsp1; +#include "serialization.cpp" + +auto DSP1::power() -> void { + dsp1emu.reset(); +} + +auto DSP1::read(uint addr, uint8 data) -> uint8 { + if(addr & 1) { + return dsp1emu.getSr(); + } else { + return dsp1emu.getDr(); + } +} + +auto DSP1::write(uint addr, uint8 data) -> void { + if(addr & 1) { + } else { + return dsp1emu.setDr(data); + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1.hpp new file mode 100644 index 0000000000..69534aa96c --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1.hpp @@ -0,0 +1,10 @@ +struct DSP1 { + auto power() -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; +}; + +extern DSP1 dsp1; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1emu.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1emu.cpp new file mode 100644 index 0000000000..c0ed19d40a --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1emu.cpp @@ -0,0 +1,1626 @@ +#ifdef DSP1_CPP + +// DSP-1's emulation code +// +// Based on research by Overload, The Dumper, Neviksti and Andreas Naive +// Date: June 2006 + +////////////////////////////////////////////////////////////////// + +Dsp1::Dsp1() +{ + reset(); +} + +////////////////////////////////////////////////////////////////// + +uint8 Dsp1::getSr() +{ + mSrLowByteAccess = ~mSrLowByteAccess; + if (mSrLowByteAccess) + return 0; + else + return mSr; +} + +////////////////////////////////////////////////////////////////// + +uint8 Dsp1::getDr() +{ + uint8 oDr; + + fsmStep(true, oDr); + return oDr; +} + +////////////////////////////////////////////////////////////////// + +void Dsp1::setDr(uint8 iDr) +{ + fsmStep(false, iDr); +} + +////////////////////////////////////////////////////////////////// + +void Dsp1::reset() +{ + mSr = DRC|RQM; + mSrLowByteAccess = false; + mDr = 0x0080; // Only a supposition. Is this correct? + mFreeze = false; + mFsmMajorState = WAIT_COMMAND; + memset(&shared, 0, sizeof(SharedData)); // another supposition +} + +////////////////////////////////////////////////////////////////// + +// Though the DSP-1 is unaware of the type of operation (read or write) +// we need to know what is being done by the program, as the class +// is responsible for maintaining the binding between the +// "external" and "internal" representations of the DR (data register). + +void Dsp1::fsmStep(bool read, uint8 &data) +{ + if (0 == (mSr&RQM)) return; + // Now RQM would be cleared; however, as this code is not to be used in + // a multithread environment, we will simply fake RQM operation. + // (The only exception would be Op1A's freeze.) + + // binding + if (read) + { + if (mSr&DRS) + data = static_cast(mDr>>8); + else + data = static_cast(mDr); + } + else + { + if (mSr&DRS) + { + mDr &= 0x00ff; + mDr |= data<<8; + } + else + { + mDr &= 0xff00; + mDr |= data; + } + } + + + switch (mFsmMajorState) + { + case WAIT_COMMAND: + mCommand = static_cast(mDr); + if (!(mCommand & 0xc0)) // valid command? + { + switch(mCommand) + { + // freeze cases + case 0x1a: + case 0x2a: + case 0x3a: + mFreeze = true; + break; + // normal cases + default: + mDataCounter=0; + mFsmMajorState = READ_DATA; + mSr &= ~DRC; + break; + } + } + break; + case READ_DATA: + mSr ^= DRS; + if (!(mSr&DRS)) + { + mReadBuffer[mDataCounter++] = static_cast(mDr); + if (mDataCounter >= mCommandTable[mCommand].reads) + { + (this->*mCommandTable[mCommand].callback)(mReadBuffer, mWriteBuffer); + if (0 != mCommandTable[mCommand].writes) // any output? + { + mDataCounter = 0; + mDr = static_cast(mWriteBuffer[mDataCounter]); + mFsmMajorState = WRITE_DATA; + } + else + { + mDr = 0x0080; // valid command completion + mFsmMajorState = WAIT_COMMAND; + mSr |= DRC; + } + } + } + break; + case WRITE_DATA: + mSr ^= DRS; + if (!(mSr&DRS)) + { + ++mDataCounter; + if (mDataCounter >= mCommandTable[mCommand].writes) + { + if ((mCommand == 0x0a)&&(mDr != 0x8000)) + { + // works in continuous mode + mReadBuffer[0]++; // next raster line + (this->*mCommandTable[mCommand].callback)(mReadBuffer, mWriteBuffer); + mDataCounter = 0; + mDr = static_cast(mWriteBuffer[mDataCounter]); + } + else + { + mDr = 0x0080; // valid command completion + mFsmMajorState = WAIT_COMMAND; + mSr |= DRC; + } + } + else + { + mDr = static_cast(mWriteBuffer[mDataCounter]); + } + } + break; + } + + + + // Now RQM would be set (except when executing Op1A -command equals 0x1a, 0x2a or 0x3a-). + if (mFreeze) + mSr &= ~RQM; +} + +////////////////////////////////////////////////////////////////// + +// The info on this table follows Overload's docs. + +const Dsp1::Command Dsp1::mCommandTable[0x40] = { + {&Dsp1::multiply, 2, 1}, //0x00 + {&Dsp1::attitudeA, 4, 0}, //0x01 + {&Dsp1::parameter, 7, 4}, //0x02 + {&Dsp1::subjectiveA, 3, 3}, //0x03 + {&Dsp1::triangle, 2, 2}, //0x04 + {&Dsp1::attitudeA, 4, 0}, //0x01 + {&Dsp1::project, 3, 3}, //0x06 + {&Dsp1::memoryTest, 1, 1}, //0x0f + {&Dsp1::radius, 3, 2}, //0x08 + {&Dsp1::objectiveA, 3, 3}, //0x0d + {&Dsp1::raster, 1, 4}, // 0x0a. This will normally work in continuous mode + {&Dsp1::scalarA, 3, 1}, //0x0b + {&Dsp1::rotate, 3, 2}, //0x0c + {&Dsp1::objectiveA, 3, 3}, //0x0d + {&Dsp1::target, 2, 2}, //0x0e + {&Dsp1::memoryTest, 1, 1}, //0x0f + + {&Dsp1::inverse, 2, 2}, //0x10 + {&Dsp1::attitudeB, 4, 0}, //0x11 + {&Dsp1::parameter, 7, 4}, //0x02 + {&Dsp1::subjectiveB, 3, 3}, //0x13 + {&Dsp1::gyrate, 6, 3}, //0x14 + {&Dsp1::attitudeB, 4, 0}, //0x11 + {&Dsp1::project, 3, 3}, //0x06 + {&Dsp1::memoryDump, 1, 1024}, //0x1f + {&Dsp1::range, 4, 1}, //0x18 + {&Dsp1::objectiveB, 3, 3}, //0x1d + {0, 0, 0}, // 0x1a; the chip freezes + {&Dsp1::scalarB, 3, 1}, //0x1b + {&Dsp1::polar, 6, 3}, //0x1c + {&Dsp1::objectiveB, 3, 3}, //0x1d + {&Dsp1::target, 2, 2}, //0x0e + {&Dsp1::memoryDump, 1, 1024}, //0x1f + + {&Dsp1::multiply2, 2, 1}, //0x20 + {&Dsp1::attitudeC, 4, 0}, //0x21 + {&Dsp1::parameter, 7, 4}, //0x02 + {&Dsp1::subjectiveC, 3, 3}, //0x23 + {&Dsp1::triangle, 2, 2}, //0x04 + {&Dsp1::attitudeC, 4, 0}, //0x21 + {&Dsp1::project, 3, 3}, //0x06 + {&Dsp1::memorySize, 1, 1}, //0x2f + {&Dsp1::distance, 3, 1}, //0x28 + {&Dsp1::objectiveC, 3, 3}, //0x2d + {0, 0, 0}, // 0x1a; the chip freezes + {&Dsp1::scalarC, 3, 1}, //0x2b + {&Dsp1::rotate, 3, 2}, //0x0c + {&Dsp1::objectiveC, 3, 3}, //0x2d + {&Dsp1::target, 2, 2}, //0x0e + {&Dsp1::memorySize, 1, 1}, //0x2f + + {&Dsp1::inverse, 2, 2}, //0x10 + {&Dsp1::attitudeA, 4, 0}, //0x01 + {&Dsp1::parameter, 7, 4}, //0x02 + {&Dsp1::subjectiveA, 3, 3}, //0x03 + {&Dsp1::gyrate, 6, 3}, //0x14 + {&Dsp1::attitudeA, 4, 0}, //0x01 + {&Dsp1::project, 3, 3}, //0x06 + {&Dsp1::memoryDump, 1, 1024}, //0x1f + {&Dsp1::range2, 4, 1}, //0x38 + {&Dsp1::objectiveA, 3, 3}, //0x0d + {0, 0, 0}, // 0x1a; the chip freezes + {&Dsp1::scalarA, 3, 1}, //0x0b + {&Dsp1::polar, 6, 3}, //0x1c + {&Dsp1::objectiveA, 3, 3}, //0x0d + {&Dsp1::target, 2, 2}, //0x0e + {&Dsp1::memoryDump, 1, 1024}, //0x1f +}; + +////////////////////////////////////////////////////////////////// + +void Dsp1::memoryTest(int16 *input, int16 *output) +{ + int16& Size = input[0]; + int16& Result = output[0]; + + Result = 0x0000; +} + +////////////////////////////////////////////////////////////////// + +void Dsp1::memoryDump(int16 *input, int16 *output) +{ + memcpy(output, DataRom, 1024); +} + +////////////////////////////////////////////////////////////////// + +void Dsp1::memorySize(int16 *input, int16 *output) +{ + int16& Size = output[0]; + + Size = 0x0100; +} + +////////////////////////////////////////////////////////////////// + +// 16-bit multiplication + +void Dsp1::multiply(int16 *input, int16 *output) +{ + int16& Multiplicand = input[0]; + int16& Multiplier = input[1]; + int16& Product = output[0]; + + Product = Multiplicand * Multiplier >> 15; +} + +////////////////////////////////////////////////////////////////// + +// 16-bit multiplication. 'Alternative' method. Can anyone check this carefully? + +void Dsp1::multiply2(int16 *input, int16 *output) +{ + int16& Multiplicand = input[0]; + int16& Multiplier = input[1]; + int16& Product = output[0]; + + Product = (Multiplicand * Multiplier >> 15)+1; +} + +////////////////////////////////////////////////////////////////// + +// This command determines the inverse of a floating point decimal number. + +void Dsp1::inverse(int16 *input, int16 *output) +{ + int16& Coefficient = input[0]; + int16& Exponent = input[1]; + int16& iCoefficient = output[0]; + int16& iExponent = output[1]; + + inverse(Coefficient, Exponent, iCoefficient, iExponent); +} + +////////////////////////////////////////////////////////////////// + +// Vector component calculation. Determines the X and Y components for a +// two-dimensional vector whose size and direction is known. +// Y = Radius * sin(Angle) +// X = Radius * cos(Angle) + +void Dsp1::triangle(int16 *input, int16 *output) +{ + int16& Angle = input[0]; + int16& Radius = input[1]; + int16& Y = output[0]; + int16& X = output[1]; + + Y = sin(Angle) * Radius >> 15; + X = cos(Angle) * Radius >> 15; +} + +////////////////////////////////////////////////////////////////// + +// Determines the squared norm of a vector (X,Y,Z) +// The output is Radius = X^2+Y^2+Z^2 (double integer) + +void Dsp1::radius(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& RadiusLow = output[0]; + int16& RadiusHigh = output[1]; + + int32 Radius; + + Radius = (X * X + Y * Y + Z * Z) << 1; + RadiusLow = static_cast(Radius); + RadiusHigh = static_cast(Radius>>16); +} + +////////////////////////////////////////////////////////////////// + +// Vector size comparison. This command compares the size of the vector (X,Y,Z) and the distance (R) +// from a particular point, and so may be used to determine if a point is within the sphere or radius R. +// The output is D = X^2+Y^2+Z^2-R^2 + +void Dsp1::range(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& Radius = input[3]; + int16& Range = output[0]; + + Range = (X * X + Y * Y + Z * Z - Radius * Radius) >> 15; +} + +////////////////////////////////////////////////////////////////// + +// Vector size comparison. 'Alternative' method. + +void Dsp1::range2(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& Radius = input[3]; + int16& Range = output[0]; + + Range = ((X * X + Y * Y + Z * Z - Radius * Radius) >> 15) + 1; +} + +////////////////////////////////////////////////////////////////// + +// This command calculates the norm of a (X,Y,Z) vector, or the distance from +// the point (X,Y,Z) to (0,0,0), as you prefer to see it. +// Distance = sqrt(X^2+Y^2+Z^2) +// The square root of a number 'a' is calculated by doing this: you +// write 'a' as b*2^2n, with 'b' between 1/4 and 1; then, you calculate +// c=sqrt(b) by using lineal interpolation between points of a +// look-up table and, finally, you output the result as c*2^n. + +void Dsp1::distance(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& Distance = output[0]; + + int32 Radius = X * X + Y * Y + Z * Z; + + if (Radius == 0) Distance = 0; + else + { + int16 C, E; + normalizeDouble(Radius, C, E); + if (E & 1) C = C * 0x4000 >> 15; + + int16 Pos = C * 0x0040 >> 15; + + int16 Node1 = DataRom[0x00d5 + Pos]; + int16 Node2 = DataRom[0x00d6 + Pos]; + + Distance = ((Node2 - Node1) * (C & 0x1ff) >> 9) + Node1; + +#if DSP1_VERSION < 0x0102 + if (Pos & 1) Distance -= (Node2 - Node1); +#endif + Distance >>= (E >> 1); + } +} + +////////////////////////////////////////////////////////////////// + +// Determines the (X2, Y2) coordinates obtained by rotating (X1, Y1) +// clockwise for an angle 'Angle'. The official documentation says +// 'counterclockwise', but it's obviously wrong (surprise! :P) +// +// In matrix notation: +// |X2| |cos(Angle) sin(Angle)| |X1| +// | | = | | | | +// |Y2| |-sin(Angle cos(Angle)| |Y1| + +void Dsp1::rotate(int16 *input, int16 *output) +{ + int16& Angle = input[0]; + int16& X1 = input[1]; + int16& Y1 = input[2]; + int16& X2 = output[0]; + int16& Y2 = output[1]; + + X2 = (Y1 * sin(Angle) >> 15) + (X1 * cos(Angle) >> 15); + Y2 = (Y1 * cos(Angle) >> 15) - (X1 * sin(Angle) >> 15); +} + +////////////////////////////////////////////////////////////////// + +// Calculate the coordinates (X2, Y2, Z2) obtained when rotating (X1, Y1, Z1) +// three-dimensionally. Rotation is done in the order of Az around the Z axis, +// Ay around the Y axis and Ax around the X axis. As occur with the "attitude" commands +// (see comments in the "gyrate" command), this doesn't match what explained in +// the official documentation, but it's coherent with what it is done in the "attitude" +// command (but not with the "gyrate" command). +// +// In matrix notation: +// |X2| |1 0 0 | |cosRy 0 -sinRy| | cosRz sinRz 0| |X1| +// |Y2| = |0 cosRx sinRx| | 0 1 0 | |-sinRz cosRz 0| |Y1| +// |Z2| |0 -sinRx cosRx| |sinRy 0 cosRy| | 0 0 1| |Z1| + +void Dsp1::polar(int16 *input, int16 *output) +{ + int16& Az = input[0]; + int16& Ay = input[1]; + int16& Ax = input[2]; + int16& X1 = input[3]; + int16& Y1 = input[4]; + int16& Z1 = input[5]; + int16& X2 = output[0]; + int16& Y2 = output[1]; + int16& Z2 = output[2]; + + int16 X, Y, Z; + + // Rotate Around Z + X = (Y1 * sin(Az) >> 15) + (X1 * cos(Az) >> 15); + Y = (Y1 * cos(Az) >> 15) - (X1 * sin(Az) >> 15); + X1 = X; Y1 = Y; + + // Rotate Around Y + Z = (X1 * sin(Ay) >> 15) + (Z1 * cos(Ay) >> 15); + X = (X1 * cos(Ay) >> 15) - (Z1 * sin(Ay) >> 15); + X2 = X; Z1 = Z; + + // Rotate Around X + Y = (Z1 * sin(Ax) >> 15) + (Y1 * cos(Ax) >> 15); + Z = (Z1 * cos(Ax) >> 15) - (Y1 * sin(Ax) >> 15); + Y2 = Y; Z2 = Z; +} + +////////////////////////////////////////////////////////////////// + +// Set up the elements of an "attitude matrix" (there are other ones): +// S | cosRz sinRz 0| |cosRy 0 -sinRy| |1 0 0 | +// MatrixA = - |-sinRz cosRz 0| | 0 1 0 | |0 cosRx sinRx| +// 2 | 0 0 1| |sinRy 0 cosRy| |0 -sinRx cosRx| +// This matrix is thought to be used within the following framework: +// let's suppose we define positive rotations around a system of orthogonal axes in this manner: +// a rotation of +90 degrees around axis3 converts axis2 into axis1 +// a rotation of +90 degrees around axis2 converts axis1 into axis3 +// a rotation of +90 degrees around axis1 converts axis3 into axis2 +// and let's suppose that we have defined a new orthonormal axes system (FLU) +// by doing the following operations about the standard one (XYZ): +// first rotating the XYZ system around Z by an angle Rz (obtaining X'Y'Z'), +// then rotating the resulting system around Y by an angle Ry (obtaining X''Y''Z'') +// and, finally, rotating the resulting system around X by an angle Rx (obtaining FLU) +// This FLU (forward/left/up) system represents an "attitude" and, then, the matrix here defined +// is the change of coordinates matrix that transform coordinates in the FLU +// system (the "object coordinates") into the standard XYZ system (the "global coordinates"), +// multiplied by a scale factor S/2, that is: +// |x| S |f| +// |y| * - = MatrixA * |l| +// |z| 2 |u| +// In a similar way, if we use the transpose of the matrix, we can transform global coordinates +// into object coordinates: +// |f| S |x| +// |l| * - = MatrixA_transposed * |y| +// |u| 2 |z| +// +// input[0]: S +// input[1]: Rz +// input[2]: Ry +// input[3]: Rx + +void Dsp1::attitudeA(int16 *input, int16 *output) +{ + int16& S = input[0]; + int16& Rz = input[1]; + int16& Ry = input[2]; + int16& Rx = input[3]; + + int16 SinRz = sin(Rz); + int16 CosRz = cos(Rz); + int16 SinRy = sin(Ry); + int16 CosRy = cos(Ry); + int16 SinRx = sin(Rx); + int16 CosRx = cos(Rx); + + S >>= 1; + + shared.MatrixA[0][0] = (S * CosRz >> 15) * CosRy >> 15; + shared.MatrixA[0][1] = ((S * SinRz >> 15) * CosRx >> 15) + (((S * CosRz >> 15) * SinRx >> 15) * SinRy >> 15); + shared.MatrixA[0][2] = ((S * SinRz >> 15) * SinRx >> 15) - (((S * CosRz >> 15) * CosRx >> 15) * SinRy >> 15); + + shared.MatrixA[1][0] = -((S * SinRz >> 15) * CosRy >> 15); + shared.MatrixA[1][1] = ((S * CosRz >> 15) * CosRx >> 15) - (((S * SinRz >> 15) * SinRx >> 15) * SinRy >> 15); + shared.MatrixA[1][2] = ((S * CosRz >> 15) * SinRx >> 15) + (((S * SinRz >> 15) * CosRx >> 15) * SinRy >> 15); + + shared.MatrixA[2][0] = S * SinRy >> 15; + shared.MatrixA[2][1] = -((S * SinRx >> 15) * CosRy >> 15); + shared.MatrixA[2][2] = (S * CosRx >> 15) * CosRy >> 15; +} + +////////////////////////////////////////////////////////////////// + +// Same than 'attitudeA', but with a difference attitude matrix (matrixB) + +void Dsp1::attitudeB(int16 *input, int16 *output) +{ + int16& S = input[0]; + int16& Rz = input[1]; + int16& Ry = input[2]; + int16& Rx = input[3]; + + int16 SinRz = sin(Rz); + int16 CosRz = cos(Rz); + int16 SinRy = sin(Ry); + int16 CosRy = cos(Ry); + int16 SinRx = sin(Rx); + int16 CosRx = cos(Rx); + + S >>= 1; + + shared.MatrixB[0][0] = (S * CosRz >> 15) * CosRy >> 15; + shared.MatrixB[0][1] = ((S * SinRz >> 15) * CosRx >> 15) + (((S * CosRz >> 15) * SinRx >> 15) * SinRy >> 15); + shared.MatrixB[0][2] = ((S * SinRz >> 15) * SinRx >> 15) - (((S * CosRz >> 15) * CosRx >> 15) * SinRy >> 15); + + shared.MatrixB[1][0] = -((S * SinRz >> 15) * CosRy >> 15); + shared.MatrixB[1][1] = ((S * CosRz >> 15) * CosRx >> 15) - (((S * SinRz >> 15) * SinRx >> 15) * SinRy >> 15); + shared.MatrixB[1][2] = ((S * CosRz >> 15) * SinRx >> 15) + (((S * SinRz >> 15) * CosRx >> 15) * SinRy >> 15); + + shared.MatrixB[2][0] = S * SinRy >> 15; + shared.MatrixB[2][1] = -((S * SinRx >> 15) * CosRy >> 15); + shared.MatrixB[2][2] = (S * CosRx >> 15) * CosRy >> 15; +} + +////////////////////////////////////////////////////////////////// + +// Same than 'attitudeA', but with a difference attitude matrix (matrixC) + +void Dsp1::attitudeC(int16 *input, int16 *output) +{ + int16& S = input[0]; + int16& Rz = input[1]; + int16& Ry = input[2]; + int16& Rx = input[3]; + + int16 SinRz = sin(Rz); + int16 CosRz = cos(Rz); + int16 SinRy = sin(Ry); + int16 CosRy = cos(Ry); + int16 SinRx = sin(Rx); + int16 CosRx = cos(Rx); + + S >>= 1; + + shared.MatrixC[0][0] = (S * CosRz >> 15) * CosRy >> 15; + shared.MatrixC[0][1] = ((S * SinRz >> 15) * CosRx >> 15) + (((S * CosRz >> 15) * SinRx >> 15) * SinRy >> 15); + shared.MatrixC[0][2] = ((S * SinRz >> 15) * SinRx >> 15) - (((S * CosRz >> 15) * CosRx >> 15) * SinRy >> 15); + + shared.MatrixC[1][0] = -((S * SinRz >> 15) * CosRy >> 15); + shared.MatrixC[1][1] = ((S * CosRz >> 15) * CosRx >> 15) - (((S * SinRz >> 15) * SinRx >> 15) * SinRy >> 15); + shared.MatrixC[1][2] = ((S * CosRz >> 15) * SinRx >> 15) + (((S * SinRz >> 15) * CosRx >> 15) * SinRy >> 15); + + shared.MatrixC[2][0] = S * SinRy >> 15; + shared.MatrixC[2][1] = -((S * SinRx >> 15) * CosRy >> 15); + shared.MatrixC[2][2] = (S * CosRx >> 15) * CosRy >> 15; +} + +////////////////////////////////////////////////////////////////// + +// Convert global coordinates (X,Y,Z) to object coordinates (F,L,U) +// See the comment in "attitudeA" for a explanation about the calculation. +// +// input[0]: X ; input[1]: Y ; input[2]: Z +// output[0]: F ; output[1]: L ; output[2]: U + +void Dsp1::objectiveA(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& F = output[0]; + int16& L = output[1]; + int16& U = output[2]; + + F = (shared.MatrixA[0][0] * X >> 15) + (shared.MatrixA[1][0] * Y >> 15) + (shared.MatrixA[2][0] * Z >> 15); + L = (shared.MatrixA[0][1] * X >> 15) + (shared.MatrixA[1][1] * Y >> 15) + (shared.MatrixA[2][1] * Z >> 15); + U = (shared.MatrixA[0][2] * X >> 15) + (shared.MatrixA[1][2] * Y >> 15) + (shared.MatrixA[2][2] * Z >> 15); +} + +////////////////////////////////////////////////////////////////// + +// Same than 'objectiveA', but for the 'B' attitude + +void Dsp1::objectiveB(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& F = output[0]; + int16& L = output[1]; + int16& U = output[2]; + + F = (shared.MatrixB[0][0] * X >> 15) + (shared.MatrixB[1][0] * Y >> 15) + (shared.MatrixB[2][0] * Z >> 15); + L = (shared.MatrixB[0][1] * X >> 15) + (shared.MatrixB[1][1] * Y >> 15) + (shared.MatrixB[2][1] * Z >> 15); + U = (shared.MatrixB[0][2] * X >> 15) + (shared.MatrixB[1][2] * Y >> 15) + (shared.MatrixB[2][2] * Z >> 15); +} + +////////////////////////////////////////////////////////////////// + +// Same than 'objectiveA', but for the 'C' attitude + +void Dsp1::objectiveC(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& F = output[0]; + int16& L = output[1]; + int16& U = output[2]; + + F = (shared.MatrixC[0][0] * X >> 15) + (shared.MatrixC[1][0] * Y >> 15) + (shared.MatrixC[2][0] * Z >> 15); + L = (shared.MatrixC[0][1] * X >> 15) + (shared.MatrixC[1][1] * Y >> 15) + (shared.MatrixC[2][1] * Z >> 15); + U = (shared.MatrixC[0][2] * X >> 15) + (shared.MatrixC[1][2] * Y >> 15) + (shared.MatrixC[2][2] * Z >> 15); +} + +////////////////////////////////////////////////////////////////// + +// Convert object coordinates (F,L,U) to object coordinates (X,Y,Z) +// See the comment in "attitudeA" for a explanation about the calculation. +// +// input[0]: F ; input[1]: L ; input[2]: U +// output[0]: X ; output[1]: Y ; output[2]: Z + +void Dsp1::subjectiveA(int16 *input, int16 *output) +{ + int16& F = input[0]; + int16& L = input[1]; + int16& U = input[2]; + int16& X = output[0]; + int16& Y = output[1]; + int16& Z = output[2]; + + X = (shared.MatrixA[0][0] * F >> 15) + (shared.MatrixA[0][1] * L >> 15) + (shared.MatrixA[0][2] * U >> 15); + Y = (shared.MatrixA[1][0] * F >> 15) + (shared.MatrixA[1][1] * L >> 15) + (shared.MatrixA[1][2] * U >> 15); + Z = (shared.MatrixA[2][0] * F >> 15) + (shared.MatrixA[2][1] * L >> 15) + (shared.MatrixA[2][2] * U >> 15); +} + +////////////////////////////////////////////////////////////////// + +// Same than 'subjectiveA', but for the 'B' attitude + +void Dsp1::subjectiveB(int16 *input, int16 *output) +{ + int16& F = input[0]; + int16& L = input[1]; + int16& U = input[2]; + int16& X = output[0]; + int16& Y = output[1]; + int16& Z = output[2]; + + X = (shared.MatrixB[0][0] * F >> 15) + (shared.MatrixB[0][1] * L >> 15) + (shared.MatrixB[0][2] * U >> 15); + Y = (shared.MatrixB[1][0] * F >> 15) + (shared.MatrixB[1][1] * L >> 15) + (shared.MatrixB[1][2] * U >> 15); + Z = (shared.MatrixB[2][0] * F >> 15) + (shared.MatrixB[2][1] * L >> 15) + (shared.MatrixB[2][2] * U >> 15); +} + +////////////////////////////////////////////////////////////////// + +// Same than 'subjectiveA', but for the 'C' attitude + +void Dsp1::subjectiveC(int16 *input, int16 *output) +{ + int16& F = input[0]; + int16& L = input[1]; + int16& U = input[2]; + int16& X = output[0]; + int16& Y = output[1]; + int16& Z = output[2]; + + X = (shared.MatrixC[0][0] * F >> 15) + (shared.MatrixC[0][1] * L >> 15) + (shared.MatrixC[0][2] * U >> 15); + Y = (shared.MatrixC[1][0] * F >> 15) + (shared.MatrixC[1][1] * L >> 15) + (shared.MatrixC[1][2] * U >> 15); + Z = (shared.MatrixC[2][0] * F >> 15) + (shared.MatrixC[2][1] * L >> 15) + (shared.MatrixC[2][2] * U >> 15); +} + +////////////////////////////////////////////////////////////////// + +// This command calculates the inner product (S) of a vector (X,Y,Z) and +// the first column of MatrixA. It should be noted that that first column +// represent the global coordinates of an unity vector in the forward +// direction in the object coordinate system (coordinates (1,0,0) in the FLU +// axes system). +// +// input[0]: X ; input[1]: Y ; input[2]: Z +// output[0]: S + +void Dsp1::scalarA(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& S = output[0]; + + S = (X * shared.MatrixA[0][0] + Y * shared.MatrixA[1][0] + Z * shared.MatrixA[2][0]) >> 15; +} + +////////////////////////////////////////////////////////////////// + +// Same than 'scalarA', but for the 'B' attitude + +void Dsp1::scalarB(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& S = output[0]; + + S = (X * shared.MatrixB[0][0] + Y * shared.MatrixB[1][0] + Z * shared.MatrixB[2][0]) >> 15; +} + +////////////////////////////////////////////////////////////////// + +// Same than 'scalarA', but for the 'C' attitude + +void Dsp1::scalarC(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& S = output[0]; + + S = (X * shared.MatrixC[0][0] + Y * shared.MatrixC[1][0] + Z * shared.MatrixC[2][0]) >> 15; +} + +////////////////////////////////////////////////////////////////// + +// This command determines the final attitude angles after the body with attitude angles (Ax, Ay, Az) with +// respect to the global coordinates is rotated by the minor angular displacements (DeltaF, DeltaL, DeltaU). +// It means that the XYZ axes are rotated by (Ax, Ay, Az) to obtain the FLU axes and, then, these +// are rotated by (DeltaF, DeltaL, DeltaU). The command calculates and return the new FLU angles respect to the +// XYZ system (Rx, Ry, Rz) +// The formulae are: +// Rx = Ax + (DeltaU*sin(Ay)+DeltaF*cos(Ay)) +// Ry = Ay + DeltaL - tan(Ax)*(DeltaU*cos(Ay)+DeltaF*sin(Ay)) +// Rz = Az + sec(Ax)*(DeltaU*cos(Ay)-DeltaF*sin(Ay)) +// +// Now the discussion: according to the official documentation, as described in various commands, you pass from +// XYZ to FLU by doing the rotations in the order Y, X, Z. In this command, the formulae are coherent with the +// fact that Y is the first axis to do a rotation around it. However, in the "attitude" command, while the official +// document describe it that way, we have discovered, when reverse engineering the command, that the calculated +// matrix do the rotation around Y in the second place. This incoherent behaviour of various commands is, in my +// opinion, a pretty severe implementation error. However, if you only use small "minor displacements", the error term +// introduced by that incoherence should be almost negligible. + +void Dsp1::gyrate(int16 *input, int16 *output) +{ + int16& Az = input[0]; + int16& Ax = input[1]; + int16& Ay = input[2]; + int16& U = input[3]; + int16& F = input[4]; + int16& L = input[5]; + int16& Rz = output[0]; + int16& Rx = output[1]; + int16& Ry = output[2]; + + int16 CSec, ESec, CSin, C, E; + int16 SinAy = sin(Ay); + int16 CosAy = cos(Ay); + + inverse(cos(Ax), 0, CSec, ESec); + + // Rotation Around Z + normalizeDouble(U * CosAy - F * SinAy, C, E); + + E = ESec - E; + + normalize(C * CSec >> 15, C, E); + + Rz = Az + denormalizeAndClip(C, E); + + // Rotation Around X + Rx = Ax + (U * SinAy >> 15) + (F * CosAy >> 15); + + // Rotation Around Y + normalizeDouble(U * CosAy + F * SinAy, C, E); + + E = ESec - E; + + normalize(sin(Ax), CSin, E); + + normalize(-(C * (CSec * CSin >> 15) >> 15), C, E); + + Ry = Ay + denormalizeAndClip(C, E) + L; +} + +////////////////////////////////////////////////////////////////// + +const int16 Dsp1::MaxAZS_Exp[16] = { + 0x38b4, 0x38b7, 0x38ba, 0x38be, 0x38c0, 0x38c4, 0x38c7, 0x38ca, + 0x38ce, 0x38d0, 0x38d4, 0x38d7, 0x38da, 0x38dd, 0x38e0, 0x38e4 +}; + +////////////////////////////////////////////////////////////////// + + +// Set-up the projection framework. Besides returning some values, it store in RAM some values that +// will be used by the other three projection commands (raster, target an project) +// Input: +// (Fx, Fy, Fz)-> coordinates of base point (global coordinates) +// Lfe-> distance between the base point and the viewpoint (center of projection) +// Les-> distance between the base point and the screen +// Aas-> azimuth angle (0 degrees is east; 90 degrees is north) +// Azs-> zenith angle (0 degrees is zenith) +// Output: +// Vof-> raster line of imaginary center (whatever it means ;) ) +// Vva-> raster line representing the horizon line +// (Cx, Cy)-> coordinates of the projection of the center of the screen over the ground (ground coordinates) + +void Dsp1::parameter(int16 *input, int16 *output) +{ + int16& Fx = input[0]; + int16& Fy = input[1]; + int16& Fz = input[2]; + int16& Lfe = input[3]; + int16& Les = input[4]; + int16& Aas = input[5]; + int16& Azs = input[6]; + int16& Vof = output[0]; + int16& Vva = output[1]; + int16& Cx = output[2]; + int16& Cy = output[3]; + + int16 CSec, C, E; + int16 LfeNx, LfeNy, LfeNz; + int16 LesNx, LesNy, LesNz; + + // Copy Zenith angle for clipping + int16 AZS = Azs; + + // Store Les and his coefficient and exponent when normalized + shared.Les = Les; + shared.E_Les=0; + normalize(Les, shared.C_Les, shared.E_Les); + + // Store Sine and Cosine of Azimuth and Zenith angle + shared.SinAas = sin(Aas); + shared.CosAas = cos(Aas); + shared.SinAzs = sin(Azs); + shared.CosAzs = cos(Azs); + + // normal vector to the screen (norm 1, points toward the center of projection) + shared.Nx = shared.SinAzs * -shared.SinAas >> 15; + shared.Ny = shared.SinAzs * shared.CosAas >> 15; + shared.Nz = shared.CosAzs * 0x7fff >> 15; + + // horizontal vector of the screen (Hz=0, norm 1, points toward the right of the screen) + shared.Hx = shared.CosAas*0x7fff>>15; + shared.Hy = shared.SinAas*0x7fff>>15; + + // vertical vector of the screen (norm 1, points toward the top of the screen) + shared.Vx = shared.CosAzs*-shared.SinAas>>15; + shared.Vy = shared.CosAzs*shared.CosAas>>15; + shared.Vz = -shared.SinAzs*0x7fff>>15; + + LfeNx = Lfe*shared.Nx>>15; + LfeNy = Lfe*shared.Ny>>15; + LfeNz = Lfe*shared.Nz>>15; + + // Center of Projection + shared.CentreX = Fx+LfeNx; + shared.CentreY = Fy+LfeNy; + shared.CentreZ = Fz+LfeNz; + + LesNx = Les*shared.Nx>>15; + LesNy = Les*shared.Ny>>15; + LesNz = Les*shared.Nz>>15; + + // center of the screen (global coordinates) + shared.Gx=shared.CentreX-LesNx; + shared.Gy=shared.CentreY-LesNy; + shared.Gz=shared.CentreZ-LesNz; + + + E = 0; + normalize(shared.CentreZ, C, E); + + shared.CentreZ_C = C; + shared.CentreZ_E = E; + + // Determine clip boundary and clip Zenith angle if necessary + // (Why to clip? Maybe to avoid the screen can only show sky with no ground? Only a guess...) + int16 MaxAZS = MaxAZS_Exp[-E]; + + if (AZS < 0) { + MaxAZS = -MaxAZS; + if (AZS < MaxAZS + 1) AZS = MaxAZS + 1; + } else { + if (AZS > MaxAZS) AZS = MaxAZS; + } + + // Store Sine and Cosine of clipped Zenith angle + shared.SinAZS = sin(AZS); + shared.CosAZS = cos(AZS); + + // calculate the separation of (cx, cy) from the projection of + // the 'centre of projection' over the ground... (CentreZ*tg(AZS)) + inverse(shared.CosAZS, 0, shared.SecAZS_C1, shared.SecAZS_E1); + normalize(C * shared.SecAZS_C1 >> 15, C, E); + E += shared.SecAZS_E1; + C = denormalizeAndClip(C, E) * shared.SinAZS >> 15; + + // ... and then take into account the position of the centre of + // projection and the azimuth angle + shared.CentreX += C * shared.SinAas >> 15; + shared.CentreY -= C * shared.CosAas >> 15; + + Cx = shared.CentreX; + Cy = shared.CentreY; + + // Raster number of imaginary center and horizontal line + Vof = 0; + + if ((Azs != AZS) || (Azs == MaxAZS)) + { + // correct vof and vva when Azs is outside the 'non-clipping interval' + // we have only some few Taylor coefficients, so we cannot guess which ones + // are the approximated functions and, what is worse, we don't know why + // the own clipping stuff (and, particularly, this correction) is done + if (Azs == -32768) Azs = -32767; + + C = Azs - MaxAZS; + if (C >= 0) C--; + int16 Aux = ~(C << 2); + + // Vof += x+(1/3)*x^3, where x ranges from 0 to PI/4 when Azs-MaxAZS goes from 0 to 0x2000 + C = Aux * DataRom[0x0328] >> 15; + C = (C * Aux >> 15) + DataRom[0x0327]; + Vof -= (C * Aux >> 15) * Les >> 15; + + // CosAZS *= 1+(1/2)*x^2+(5/24)*x^24, where x ranges from 0 to PI/4 when Azs-MaxAZS goes from 0 to 0x2000 + C = Aux * Aux >> 15; + Aux = (C * DataRom[0x0324] >> 15) + DataRom[0x0325]; + shared.CosAZS += (C * Aux >> 15) * shared.CosAZS >> 15; + } + + // vertical offset of the screen with regard to the horizontal plane + // containing the centre of projection + shared.VOffset = Les * shared.CosAZS >> 15; + + // The horizon line (the line in the screen that is crossed by the horizon plane + // -the horizontal plane containing the 'centre of projection'-), + // will be at distance Les*cotg(AZS) from the centre of the screen. This is difficult + // to explain but easily seen in a graph. To better see it, consider it in this way: + // Les*tg(AZS-90), draw some lines and apply basic trigonometry. ;) + inverse(shared.SinAZS, 0, CSec, E); + normalize(shared.VOffset, C, E); + normalize(C * CSec >> 15, C, E); + + if (C == -32768) { C >>= 1; E++; } + + Vva = denormalizeAndClip(-C, E); + + // Store Secant of clipped Zenith angle + inverse(shared.CosAZS, 0, shared.SecAZS_C2, shared.SecAZS_E2); +} + +////////////////////////////////////////////////////////////////// + +// Calculates the matrix which transform an object situated on a raster line (Vs) into +// his projection over the ground. The modified SecAZS is used here, so +// i don't understand the fine details, but, basically, it's done +// this way: The vertical offset between the point of projection and the +// raster line is calculated (Vs*SinAzs>>15)+VOffset, then the height of +// the center of projection is measured in that units (*CentreZ_C). If, now +// you consider the "reference case" (center of projection at an unit of height), +// the projection of a thin strip containing the raster line will have the same +// width (as the raster line would be on the ground in this case, but will suffer a +// change of scale in height (as the ground and the vertical axis would form an angle of 180-Azs degrees). +// This scale factor, when the angle 'center of screen-center of projection-raster line' is small, +// can be aproximated by the one of the center of the screen, 1/cos(Azs).(**) (Here is when it's used +// SecAZS). By last, you have to consider the effect of the azimuth angle Aas, and you are done. +// +// Using matrix notation: +// |A B| Centre_ZS | cos(Aas) -sin(Aas)| |1 0| +// ProjectionMatrix = | | = ----------- * | | * | | +// |C D| Vs*sin(Azs) |sin(Aas) cos(Aas)| |0 sec(Azs)| +// +// (**) +// If Les=1, the vertical offset between the center +// of projection and the center of the screen is Cos(Azs); then, if the vertical +// offset is 1, the ratio of the projection over the ground respect to the +// line on the screen is 1/cos(Azs). + +void Dsp1::raster(int16 *input, int16 *output) +{ + int16& Vs = input[0]; + int16& An = output[0]; + int16& Bn = output[1]; + int16& Cn = output[2]; + int16& Dn = output[3]; + + int16 C, E, C1, E1; + + inverse((Vs * shared.SinAzs >> 15) + shared.VOffset, 7, C, E); + + E += shared.CentreZ_E; + C1 = C * shared.CentreZ_C >> 15; + + E1 = E + shared.SecAZS_E2; + + normalize(C1, C, E); + C = denormalizeAndClip(C, E); + + An = C * shared.CosAas >> 15; + Cn = C * shared.SinAas >> 15; + + normalize(C1 * shared.SecAZS_C2 >> 15, C, E1); + C = denormalizeAndClip(C, E1); + + Bn = C * -shared.SinAas >> 15; + Dn = C * shared.CosAas >> 15; +} + +////////////////////////////////////////////////////////////////// + +// Calculate the projection over the ground of a selected point of screen +// It simply apply the projection matrix described in the "Raster" command +// to the vector (H,V) transposed, and add the result to the position of +// the centre of projection. +// The only special point to take into account is the directions on the screen: +// H is positive rightward, but V is positive downward; this is why +// the signs take that configuration + +void Dsp1::target(int16 *input, int16 *output) +{ + int16& H = input[0]; + int16& V = input[1]; + int16& X = output[0]; + int16& Y = output[1]; + + int16 C, E, C1, E1; + + inverse((V * shared.SinAzs >> 15) + shared.VOffset, 8, C, E); + + E += shared.CentreZ_E; + C1 = C * shared.CentreZ_C >> 15; + + E1 = E + shared.SecAZS_E1; + + H <<= 8; + normalize(C1, C, E); + C = denormalizeAndClip(C, E) * H >> 15; + + X = shared.CentreX + (C * shared.CosAas >> 15); + Y = shared.CentreY - (C * shared.SinAas >> 15); + + V <<= 8; + normalize(C1 * shared.SecAZS_C1 >> 15, C, E1); + C = denormalizeAndClip(C, E1) * V >> 15; + + X += C * -shared.SinAas >> 15; + Y += C * shared.CosAas >> 15; +} + +////////////////////////////////////////////////////////////////// + +// Calculation of the projection over the screen (H,V) of an object (X,Y,Z) and his +// 'enlargement ratio' (M). The positive directions on the screen are as described +// in the targe command. M is scaled down by 2^-7, that is, M==0x0100 means ratio 1:1 + + void Dsp1::project(int16 *input, int16 *output) +{ + int16& X = input[0]; + int16& Y = input[1]; + int16& Z = input[2]; + int16& H = output[0]; + int16& V = output[1]; + int16& M = output[2]; + + int32 aux, aux4; + int16 E, E2, E3, E4, E5, refE, E6, E7; + int16 C2, C4, C6, C8, C9, C10, C11, C12, C16, C17, C18, C19, C20, C21, C22, C23, C24, C25, C26; + int16 Px, Py, Pz; + + E4=E3=E2=E=E5=0; + + normalizeDouble(int32(X)-shared.Gx, Px, E4); + normalizeDouble(int32(Y)-shared.Gy, Py, E); + normalizeDouble(int32(Z)-shared.Gz, Pz, E3); + Px>>=1; E4--; // to avoid overflows when calculating the scalar products + Py>>=1; E--; + Pz>>=1; E3--; + + refE = (E>15); + C8=- (Py*shared.Ny>>15); + C9=- (Pz*shared.Nz>>15); + C12=C11+C8+C9; // this cannot overflow! + + aux4=C12; // de-normalization with 32-bits arithmetic + refE = 16-refE; // refE can be up to 3 + if (refE>=0) + aux4 <<=(refE); + else + aux4 >>=-(refE); + if (aux4==-1) aux4 = 0; // why? + aux4>>=1; + + aux = static_cast(shared.Les) + aux4; // Les - the scalar product of P with the normal vector of the screen + normalizeDouble(aux, C10, E2); + E2 = 15-E2; + + inverse(C10, 0, C4, E4); + C2=C4*shared.C_Les>>15; // scale factor + + + // H + E7=0; + C16= (Px*shared.Hx>>15); + C20= (Py*shared.Hy>>15); + C17=C16+C20; // scalar product of P with the normalized horizontal vector of the screen... + + C18=C17*C2>>15; // ... multiplied by the scale factor + normalize(C18, C19, E7); + H=denormalizeAndClip(C19, shared.E_Les-E2+refE+E7); + + // V + E6=0; + C21 = Px*shared.Vx>>15; + C22 = Py*shared.Vy>>15; + C23 = Pz*shared.Vz>>15; + C24=C21+C22+C23; // scalar product of P with the normalized vertical vector of the screen... + + C26=C24*C2>>15; // ... multiplied by the scale factor + normalize(C26, C25, E6); + V=denormalizeAndClip(C25, shared.E_Les-E2+refE+E6); + + // M + normalize(C2, C6, E4); + M=denormalizeAndClip(C6, E4+shared.E_Les-E2-7); // M is the scale factor divided by 2^7 +} + +////////////////////////////////////////////////////////////////// + +// Calculate the sine of the input parameter +// this is done by linear interpolation between +// the points of a look-up table + +int16 Dsp1::sin(int16 Angle) +{ + if (Angle < 0) { + if (Angle == -32768) return 0; + return -sin(-Angle); + } + int32 S = SinTable[Angle >> 8] + (MulTable[Angle & 0xff] * SinTable[0x40 + (Angle >> 8)] >> 15); + if (S > 32767) S = 32767; + return (int16) S; +} + +////////////////////////////////////////////////////////////////// + +// Calculate the cosine of the input parameter. +// It's used the same method than in sin(int16) + +int16 Dsp1::cos(int16 Angle) +{ + if (Angle < 0) { + if (Angle == -32768) return -32768; + Angle = -Angle; + } + int32 S = SinTable[0x40 + (Angle >> 8)] - (MulTable[Angle & 0xff] * SinTable[Angle >> 8] >> 15); + if (S < -32768) S = -32767; + return (int16) S; +} + +////////////////////////////////////////////////////////////////// + +// Determines the inverse of a floating point decimal number +// iCoefficient*2^iExponent = 1/(Coefficient*2^Exponent), with the output +// normalized (iCoefficient represents a number whose absolute value is between 1/2 and 1) +// To invert 'Coefficient' a first initial guess is taken from a look-up table +// and, then, two iterations of the Newton method (applied to the function +// f(x)=1/(2*x)-Coefficient) are done. This results in a close approximation (iCoefficient) to a number 'y' +// that verify Coefficient*y=1/2. This is why you have to correct the exponent by one +// unit at the end. + +void Dsp1::inverse(int16 Coefficient, int16 Exponent, int16 &iCoefficient, int16 &iExponent) +{ + // Step One: Division by Zero + if (Coefficient == 0x0000) + { + iCoefficient = 0x7fff; + iExponent = 0x002f; + } + else + { + int16 Sign = 1; + + // Step Two: Remove Sign + if (Coefficient < 0) + { + if (Coefficient < -32767) Coefficient = -32767; + Coefficient = -Coefficient; + Sign = -1; + } + + // Step Three: Normalize + while (Coefficient < 0x4000) + { + Coefficient <<= 1; + Exponent--; + } + + // Step Four: Special Case + if (Coefficient == 0x4000) + if (Sign == 1) iCoefficient = 0x7fff; + else { + iCoefficient = -0x4000; + Exponent--; + } + else { + // Step Five: Initial Guess + int16 i = DataRom[((Coefficient - 0x4000) >> 7) + 0x0065]; + + // Step Six: Iterate Newton's Method + i = (i + (-i * (Coefficient * i >> 15) >> 15)) << 1; + i = (i + (-i * (Coefficient * i >> 15) >> 15)) << 1; + + iCoefficient = i * Sign; + } + + iExponent = 1 - Exponent; + } +} + +////////////////////////////////////////////////////////////////// + +int16 Dsp1::denormalizeAndClip(int16 C, int16 E) +{ + if (E > 0) { + if (C > 0) return 32767; else if (C < 0) return -32767; + } else { + if (E < 0) return C * DataRom[0x0031 + E] >> 15; + } + return C; +} + +////////////////////////////////////////////////////////////////// + +// Normalize the input number (m), understood as ranging from -1 to 1, +// to the form: Coefficient*2^Exponent, +// where the absolute value of Coefficient is >= 1/2 +// (Coefficient>=0x4000 or Coefficient <= (int16)0xc001) + +void Dsp1::normalize(int16 m, int16 &Coefficient, int16 &Exponent) +{ + int16 i = 0x4000; + int16 e = 0; + + if (m < 0) + while ((m & i) && i) + { + i >>= 1; + e++; + } + else + while (!(m & i) && i) + { + i >>= 1; + e++; + } + + if (e > 0) + Coefficient = m * DataRom[0x21 + e] << 1; + else + Coefficient = m; + + Exponent -= e; +} + +////////////////////////////////////////////////////////////////// + +// Same than 'normalize' but with an int32 input + +void Dsp1::normalizeDouble(int32 Product, int16 &Coefficient, int16 &Exponent) +{ + int16 n = Product & 0x7fff; + int16 m = Product >> 15; + int16 i = 0x4000; + int16 e = 0; + + if (m < 0) + while ((m & i) && i) + { + i >>= 1; + e++; + } + else + while (!(m & i) && i) + { + i >>= 1; + e++; + } + + if (e > 0) + { + Coefficient = m * DataRom[0x0021 + e] << 1; + + if (e < 15) + Coefficient += n * DataRom[0x0040 - e] >> 15; + else + { + i = 0x4000; + + if (m < 0) + while ((n & i) && i) + { + i >>= 1; + e++; + } + else + while (!(n & i) && i) + { + i >>= 1; + e++; + } + + if (e > 15) + Coefficient = n * DataRom[0x0012 + e] << 1; + else + Coefficient += n; + } + } + else + Coefficient = m; + + Exponent = e; +} + +////////////////////////////////////////////////////////////////// + +// Shift to the right + +int16 Dsp1::shiftR(int16 C, int16 E) +{ + return (C * DataRom[0x0031 + E] >> 15); +} + +////////////////////////////////////////////////////////////////// + +// this is, indeed, only part of the Data ROM +const int16 Dsp1::SinTable[256] = { + 0x0000, 0x0324, 0x0647, 0x096a, 0x0c8b, 0x0fab, 0x12c8, 0x15e2, + 0x18f8, 0x1c0b, 0x1f19, 0x2223, 0x2528, 0x2826, 0x2b1f, 0x2e11, + 0x30fb, 0x33de, 0x36ba, 0x398c, 0x3c56, 0x3f17, 0x41ce, 0x447a, + 0x471c, 0x49b4, 0x4c3f, 0x4ebf, 0x5133, 0x539b, 0x55f5, 0x5842, + 0x5a82, 0x5cb4, 0x5ed7, 0x60ec, 0x62f2, 0x64e8, 0x66cf, 0x68a6, + 0x6a6d, 0x6c24, 0x6dca, 0x6f5f, 0x70e2, 0x7255, 0x73b5, 0x7504, + 0x7641, 0x776c, 0x7884, 0x798a, 0x7a7d, 0x7b5d, 0x7c29, 0x7ce3, + 0x7d8a, 0x7e1d, 0x7e9d, 0x7f09, 0x7f62, 0x7fa7, 0x7fd8, 0x7ff6, + 0x7fff, 0x7ff6, 0x7fd8, 0x7fa7, 0x7f62, 0x7f09, 0x7e9d, 0x7e1d, + 0x7d8a, 0x7ce3, 0x7c29, 0x7b5d, 0x7a7d, 0x798a, 0x7884, 0x776c, + 0x7641, 0x7504, 0x73b5, 0x7255, 0x70e2, 0x6f5f, 0x6dca, 0x6c24, + 0x6a6d, 0x68a6, 0x66cf, 0x64e8, 0x62f2, 0x60ec, 0x5ed7, 0x5cb4, + 0x5a82, 0x5842, 0x55f5, 0x539b, 0x5133, 0x4ebf, 0x4c3f, 0x49b4, + 0x471c, 0x447a, 0x41ce, 0x3f17, 0x3c56, 0x398c, 0x36ba, 0x33de, + 0x30fb, 0x2e11, 0x2b1f, 0x2826, 0x2528, 0x2223, 0x1f19, 0x1c0b, + 0x18f8, 0x15e2, 0x12c8, 0x0fab, 0x0c8b, 0x096a, 0x0647, 0x0324, + -0x0000, -0x0324, -0x0647, -0x096a, -0x0c8b, -0x0fab, -0x12c8, -0x15e2, + -0x18f8, -0x1c0b, -0x1f19, -0x2223, -0x2528, -0x2826, -0x2b1f, -0x2e11, + -0x30fb, -0x33de, -0x36ba, -0x398c, -0x3c56, -0x3f17, -0x41ce, -0x447a, + -0x471c, -0x49b4, -0x4c3f, -0x4ebf, -0x5133, -0x539b, -0x55f5, -0x5842, + -0x5a82, -0x5cb4, -0x5ed7, -0x60ec, -0x62f2, -0x64e8, -0x66cf, -0x68a6, + -0x6a6d, -0x6c24, -0x6dca, -0x6f5f, -0x70e2, -0x7255, -0x73b5, -0x7504, + -0x7641, -0x776c, -0x7884, -0x798a, -0x7a7d, -0x7b5d, -0x7c29, -0x7ce3, + -0x7d8a, -0x7e1d, -0x7e9d, -0x7f09, -0x7f62, -0x7fa7, -0x7fd8, -0x7ff6, + -0x7fff, -0x7ff6, -0x7fd8, -0x7fa7, -0x7f62, -0x7f09, -0x7e9d, -0x7e1d, + -0x7d8a, -0x7ce3, -0x7c29, -0x7b5d, -0x7a7d, -0x798a, -0x7884, -0x776c, + -0x7641, -0x7504, -0x73b5, -0x7255, -0x70e2, -0x6f5f, -0x6dca, -0x6c24, + -0x6a6d, -0x68a6, -0x66cf, -0x64e8, -0x62f2, -0x60ec, -0x5ed7, -0x5cb4, + -0x5a82, -0x5842, -0x55f5, -0x539b, -0x5133, -0x4ebf, -0x4c3f, -0x49b4, + -0x471c, -0x447a, -0x41ce, -0x3f17, -0x3c56, -0x398c, -0x36ba, -0x33de, + -0x30fb, -0x2e11, -0x2b1f, -0x2826, -0x2528, -0x2223, -0x1f19, -0x1c0b, + -0x18f8, -0x15e2, -0x12c8, -0x0fab, -0x0c8b, -0x096a, -0x0647, -0x0324}; + + ////////////////////////////////////////////////////////////////// + +// Optimised for Performance + const int16 Dsp1::MulTable[256] = { + 0x0000, 0x0003, 0x0006, 0x0009, 0x000c, 0x000f, 0x0012, 0x0015, + 0x0019, 0x001c, 0x001f, 0x0022, 0x0025, 0x0028, 0x002b, 0x002f, + 0x0032, 0x0035, 0x0038, 0x003b, 0x003e, 0x0041, 0x0045, 0x0048, + 0x004b, 0x004e, 0x0051, 0x0054, 0x0057, 0x005b, 0x005e, 0x0061, + 0x0064, 0x0067, 0x006a, 0x006d, 0x0071, 0x0074, 0x0077, 0x007a, + 0x007d, 0x0080, 0x0083, 0x0087, 0x008a, 0x008d, 0x0090, 0x0093, + 0x0096, 0x0099, 0x009d, 0x00a0, 0x00a3, 0x00a6, 0x00a9, 0x00ac, + 0x00af, 0x00b3, 0x00b6, 0x00b9, 0x00bc, 0x00bf, 0x00c2, 0x00c5, + 0x00c9, 0x00cc, 0x00cf, 0x00d2, 0x00d5, 0x00d8, 0x00db, 0x00df, + 0x00e2, 0x00e5, 0x00e8, 0x00eb, 0x00ee, 0x00f1, 0x00f5, 0x00f8, + 0x00fb, 0x00fe, 0x0101, 0x0104, 0x0107, 0x010b, 0x010e, 0x0111, + 0x0114, 0x0117, 0x011a, 0x011d, 0x0121, 0x0124, 0x0127, 0x012a, + 0x012d, 0x0130, 0x0133, 0x0137, 0x013a, 0x013d, 0x0140, 0x0143, + 0x0146, 0x0149, 0x014d, 0x0150, 0x0153, 0x0156, 0x0159, 0x015c, + 0x015f, 0x0163, 0x0166, 0x0169, 0x016c, 0x016f, 0x0172, 0x0175, + 0x0178, 0x017c, 0x017f, 0x0182, 0x0185, 0x0188, 0x018b, 0x018e, + 0x0192, 0x0195, 0x0198, 0x019b, 0x019e, 0x01a1, 0x01a4, 0x01a8, + 0x01ab, 0x01ae, 0x01b1, 0x01b4, 0x01b7, 0x01ba, 0x01be, 0x01c1, + 0x01c4, 0x01c7, 0x01ca, 0x01cd, 0x01d0, 0x01d4, 0x01d7, 0x01da, + 0x01dd, 0x01e0, 0x01e3, 0x01e6, 0x01ea, 0x01ed, 0x01f0, 0x01f3, + 0x01f6, 0x01f9, 0x01fc, 0x0200, 0x0203, 0x0206, 0x0209, 0x020c, + 0x020f, 0x0212, 0x0216, 0x0219, 0x021c, 0x021f, 0x0222, 0x0225, + 0x0228, 0x022c, 0x022f, 0x0232, 0x0235, 0x0238, 0x023b, 0x023e, + 0x0242, 0x0245, 0x0248, 0x024b, 0x024e, 0x0251, 0x0254, 0x0258, + 0x025b, 0x025e, 0x0261, 0x0264, 0x0267, 0x026a, 0x026e, 0x0271, + 0x0274, 0x0277, 0x027a, 0x027d, 0x0280, 0x0284, 0x0287, 0x028a, + 0x028d, 0x0290, 0x0293, 0x0296, 0x029a, 0x029d, 0x02a0, 0x02a3, + 0x02a6, 0x02a9, 0x02ac, 0x02b0, 0x02b3, 0x02b6, 0x02b9, 0x02bc, + 0x02bf, 0x02c2, 0x02c6, 0x02c9, 0x02cc, 0x02cf, 0x02d2, 0x02d5, + 0x02d8, 0x02db, 0x02df, 0x02e2, 0x02e5, 0x02e8, 0x02eb, 0x02ee, + 0x02f1, 0x02f5, 0x02f8, 0x02fb, 0x02fe, 0x0301, 0x0304, 0x0307, + 0x030b, 0x030e, 0x0311, 0x0314, 0x0317, 0x031a, 0x031d, 0x0321}; + +////////////////////////////////////////////////////////////////// + +// Data ROM, as logged from a DSP-1B with the 0x1f command; +// it contains the tables and constants used by the commands. +// The tables used are: two shift tables (0x022-0x031 and 0x031-0x040 -this last one +// with an error in 0x03c which has survived to all the DSP-1 revisions-); a inverse +// table (used as initial guess) at 0x065-0x0e4; a square root table (used also +// as initial guess) at 0x0e5-0x115; two sin and cos tables (used as nodes to construct +// a interpolation curve) at, respectively, 0x116-0x197 and 0x196-0x215. +// As a curiosity, in the positions 0x21c-0x31c it's contained a +// 257-points arccos table that, apparently, have been not used anywhere +// (maybe for the MaxAZS_Exp table?). +// byuu note: the error at DataRom[0x3c] has been corrected from 0x0001 to 0x0010 + const uint16 Dsp1::DataRom[1024] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, + 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, + 0x4000, 0x7fff, 0x4000, 0x2000, 0x1000, 0x0800, 0x0400, 0x0200, + 0x0100, 0x0080, 0x0040, 0x0020, 0x0010, 0x0008, 0x0004, 0x0002, + 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x8000, 0xffe5, 0x0100, 0x7fff, 0x7f02, 0x7e08, + 0x7d12, 0x7c1f, 0x7b30, 0x7a45, 0x795d, 0x7878, 0x7797, 0x76ba, + 0x75df, 0x7507, 0x7433, 0x7361, 0x7293, 0x71c7, 0x70fe, 0x7038, + 0x6f75, 0x6eb4, 0x6df6, 0x6d3a, 0x6c81, 0x6bca, 0x6b16, 0x6a64, + 0x69b4, 0x6907, 0x685b, 0x67b2, 0x670b, 0x6666, 0x65c4, 0x6523, + 0x6484, 0x63e7, 0x634c, 0x62b3, 0x621c, 0x6186, 0x60f2, 0x6060, + 0x5fd0, 0x5f41, 0x5eb5, 0x5e29, 0x5d9f, 0x5d17, 0x5c91, 0x5c0c, + 0x5b88, 0x5b06, 0x5a85, 0x5a06, 0x5988, 0x590b, 0x5890, 0x5816, + 0x579d, 0x5726, 0x56b0, 0x563b, 0x55c8, 0x5555, 0x54e4, 0x5474, + 0x5405, 0x5398, 0x532b, 0x52bf, 0x5255, 0x51ec, 0x5183, 0x511c, + 0x50b6, 0x5050, 0x4fec, 0x4f89, 0x4f26, 0x4ec5, 0x4e64, 0x4e05, + 0x4da6, 0x4d48, 0x4cec, 0x4c90, 0x4c34, 0x4bda, 0x4b81, 0x4b28, + 0x4ad0, 0x4a79, 0x4a23, 0x49cd, 0x4979, 0x4925, 0x48d1, 0x487f, + 0x482d, 0x47dc, 0x478c, 0x473c, 0x46ed, 0x469f, 0x4651, 0x4604, + 0x45b8, 0x456c, 0x4521, 0x44d7, 0x448d, 0x4444, 0x43fc, 0x43b4, + 0x436d, 0x4326, 0x42e0, 0x429a, 0x4255, 0x4211, 0x41cd, 0x4189, + 0x4146, 0x4104, 0x40c2, 0x4081, 0x4040, 0x3fff, 0x41f7, 0x43e1, + 0x45bd, 0x478d, 0x4951, 0x4b0b, 0x4cbb, 0x4e61, 0x4fff, 0x5194, + 0x5322, 0x54a9, 0x5628, 0x57a2, 0x5914, 0x5a81, 0x5be9, 0x5d4a, + 0x5ea7, 0x5fff, 0x6152, 0x62a0, 0x63ea, 0x6530, 0x6672, 0x67b0, + 0x68ea, 0x6a20, 0x6b53, 0x6c83, 0x6daf, 0x6ed9, 0x6fff, 0x7122, + 0x7242, 0x735f, 0x747a, 0x7592, 0x76a7, 0x77ba, 0x78cb, 0x79d9, + 0x7ae5, 0x7bee, 0x7cf5, 0x7dfa, 0x7efe, 0x7fff, 0x0000, 0x0324, + 0x0647, 0x096a, 0x0c8b, 0x0fab, 0x12c8, 0x15e2, 0x18f8, 0x1c0b, + 0x1f19, 0x2223, 0x2528, 0x2826, 0x2b1f, 0x2e11, 0x30fb, 0x33de, + 0x36ba, 0x398c, 0x3c56, 0x3f17, 0x41ce, 0x447a, 0x471c, 0x49b4, + 0x4c3f, 0x4ebf, 0x5133, 0x539b, 0x55f5, 0x5842, 0x5a82, 0x5cb4, + 0x5ed7, 0x60ec, 0x62f2, 0x64e8, 0x66cf, 0x68a6, 0x6a6d, 0x6c24, + 0x6dca, 0x6f5f, 0x70e2, 0x7255, 0x73b5, 0x7504, 0x7641, 0x776c, + 0x7884, 0x798a, 0x7a7d, 0x7b5d, 0x7c29, 0x7ce3, 0x7d8a, 0x7e1d, + 0x7e9d, 0x7f09, 0x7f62, 0x7fa7, 0x7fd8, 0x7ff6, 0x7fff, 0x7ff6, + 0x7fd8, 0x7fa7, 0x7f62, 0x7f09, 0x7e9d, 0x7e1d, 0x7d8a, 0x7ce3, + 0x7c29, 0x7b5d, 0x7a7d, 0x798a, 0x7884, 0x776c, 0x7641, 0x7504, + 0x73b5, 0x7255, 0x70e2, 0x6f5f, 0x6dca, 0x6c24, 0x6a6d, 0x68a6, + 0x66cf, 0x64e8, 0x62f2, 0x60ec, 0x5ed7, 0x5cb4, 0x5a82, 0x5842, + 0x55f5, 0x539b, 0x5133, 0x4ebf, 0x4c3f, 0x49b4, 0x471c, 0x447a, + 0x41ce, 0x3f17, 0x3c56, 0x398c, 0x36ba, 0x33de, 0x30fb, 0x2e11, + 0x2b1f, 0x2826, 0x2528, 0x2223, 0x1f19, 0x1c0b, 0x18f8, 0x15e2, + 0x12c8, 0x0fab, 0x0c8b, 0x096a, 0x0647, 0x0324, 0x7fff, 0x7ff6, + 0x7fd8, 0x7fa7, 0x7f62, 0x7f09, 0x7e9d, 0x7e1d, 0x7d8a, 0x7ce3, + 0x7c29, 0x7b5d, 0x7a7d, 0x798a, 0x7884, 0x776c, 0x7641, 0x7504, + 0x73b5, 0x7255, 0x70e2, 0x6f5f, 0x6dca, 0x6c24, 0x6a6d, 0x68a6, + 0x66cf, 0x64e8, 0x62f2, 0x60ec, 0x5ed7, 0x5cb4, 0x5a82, 0x5842, + 0x55f5, 0x539b, 0x5133, 0x4ebf, 0x4c3f, 0x49b4, 0x471c, 0x447a, + 0x41ce, 0x3f17, 0x3c56, 0x398c, 0x36ba, 0x33de, 0x30fb, 0x2e11, + 0x2b1f, 0x2826, 0x2528, 0x2223, 0x1f19, 0x1c0b, 0x18f8, 0x15e2, + 0x12c8, 0x0fab, 0x0c8b, 0x096a, 0x0647, 0x0324, 0x0000, 0xfcdc, + 0xf9b9, 0xf696, 0xf375, 0xf055, 0xed38, 0xea1e, 0xe708, 0xe3f5, + 0xe0e7, 0xdddd, 0xdad8, 0xd7da, 0xd4e1, 0xd1ef, 0xcf05, 0xcc22, + 0xc946, 0xc674, 0xc3aa, 0xc0e9, 0xbe32, 0xbb86, 0xb8e4, 0xb64c, + 0xb3c1, 0xb141, 0xaecd, 0xac65, 0xaa0b, 0xa7be, 0xa57e, 0xa34c, + 0xa129, 0x9f14, 0x9d0e, 0x9b18, 0x9931, 0x975a, 0x9593, 0x93dc, + 0x9236, 0x90a1, 0x8f1e, 0x8dab, 0x8c4b, 0x8afc, 0x89bf, 0x8894, + 0x877c, 0x8676, 0x8583, 0x84a3, 0x83d7, 0x831d, 0x8276, 0x81e3, + 0x8163, 0x80f7, 0x809e, 0x8059, 0x8028, 0x800a, 0x6488, 0x0080, + 0x03ff, 0x0116, 0x0002, 0x0080, 0x4000, 0x3fd7, 0x3faf, 0x3f86, + 0x3f5d, 0x3f34, 0x3f0c, 0x3ee3, 0x3eba, 0x3e91, 0x3e68, 0x3e40, + 0x3e17, 0x3dee, 0x3dc5, 0x3d9c, 0x3d74, 0x3d4b, 0x3d22, 0x3cf9, + 0x3cd0, 0x3ca7, 0x3c7f, 0x3c56, 0x3c2d, 0x3c04, 0x3bdb, 0x3bb2, + 0x3b89, 0x3b60, 0x3b37, 0x3b0e, 0x3ae5, 0x3abc, 0x3a93, 0x3a69, + 0x3a40, 0x3a17, 0x39ee, 0x39c5, 0x399c, 0x3972, 0x3949, 0x3920, + 0x38f6, 0x38cd, 0x38a4, 0x387a, 0x3851, 0x3827, 0x37fe, 0x37d4, + 0x37aa, 0x3781, 0x3757, 0x372d, 0x3704, 0x36da, 0x36b0, 0x3686, + 0x365c, 0x3632, 0x3609, 0x35df, 0x35b4, 0x358a, 0x3560, 0x3536, + 0x350c, 0x34e1, 0x34b7, 0x348d, 0x3462, 0x3438, 0x340d, 0x33e3, + 0x33b8, 0x338d, 0x3363, 0x3338, 0x330d, 0x32e2, 0x32b7, 0x328c, + 0x3261, 0x3236, 0x320b, 0x31df, 0x31b4, 0x3188, 0x315d, 0x3131, + 0x3106, 0x30da, 0x30ae, 0x3083, 0x3057, 0x302b, 0x2fff, 0x2fd2, + 0x2fa6, 0x2f7a, 0x2f4d, 0x2f21, 0x2ef4, 0x2ec8, 0x2e9b, 0x2e6e, + 0x2e41, 0x2e14, 0x2de7, 0x2dba, 0x2d8d, 0x2d60, 0x2d32, 0x2d05, + 0x2cd7, 0x2ca9, 0x2c7b, 0x2c4d, 0x2c1f, 0x2bf1, 0x2bc3, 0x2b94, + 0x2b66, 0x2b37, 0x2b09, 0x2ada, 0x2aab, 0x2a7c, 0x2a4c, 0x2a1d, + 0x29ed, 0x29be, 0x298e, 0x295e, 0x292e, 0x28fe, 0x28ce, 0x289d, + 0x286d, 0x283c, 0x280b, 0x27da, 0x27a9, 0x2777, 0x2746, 0x2714, + 0x26e2, 0x26b0, 0x267e, 0x264c, 0x2619, 0x25e7, 0x25b4, 0x2581, + 0x254d, 0x251a, 0x24e6, 0x24b2, 0x247e, 0x244a, 0x2415, 0x23e1, + 0x23ac, 0x2376, 0x2341, 0x230b, 0x22d6, 0x229f, 0x2269, 0x2232, + 0x21fc, 0x21c4, 0x218d, 0x2155, 0x211d, 0x20e5, 0x20ad, 0x2074, + 0x203b, 0x2001, 0x1fc7, 0x1f8d, 0x1f53, 0x1f18, 0x1edd, 0x1ea1, + 0x1e66, 0x1e29, 0x1ded, 0x1db0, 0x1d72, 0x1d35, 0x1cf6, 0x1cb8, + 0x1c79, 0x1c39, 0x1bf9, 0x1bb8, 0x1b77, 0x1b36, 0x1af4, 0x1ab1, + 0x1a6e, 0x1a2a, 0x19e6, 0x19a1, 0x195c, 0x1915, 0x18ce, 0x1887, + 0x183f, 0x17f5, 0x17ac, 0x1761, 0x1715, 0x16c9, 0x167c, 0x162e, + 0x15df, 0x158e, 0x153d, 0x14eb, 0x1497, 0x1442, 0x13ec, 0x1395, + 0x133c, 0x12e2, 0x1286, 0x1228, 0x11c9, 0x1167, 0x1104, 0x109e, + 0x1036, 0x0fcc, 0x0f5f, 0x0eef, 0x0e7b, 0x0e04, 0x0d89, 0x0d0a, + 0x0c86, 0x0bfd, 0x0b6d, 0x0ad6, 0x0a36, 0x098d, 0x08d7, 0x0811, + 0x0736, 0x063e, 0x0519, 0x039a, 0x0000, 0x7fff, 0x0100, 0x0080, + 0x021d, 0x00c8, 0x00ce, 0x0048, 0x0a26, 0x277a, 0x00ce, 0x6488, + 0x14ac, 0x0001, 0x00f9, 0x00fc, 0x00ff, 0x00fc, 0x00f9, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}; + +////////////////////////////////////////////////////////////////// + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1emu.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1emu.hpp new file mode 100644 index 0000000000..9ae313acd8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/dsp1emu.hpp @@ -0,0 +1,129 @@ +// DSP-1's emulation code +// +// Based on research by Overload, The Dumper, Neviksti and Andreas Naive +// Date: June 2006 + +#ifndef __DSP1EMUL_H +#define __DSP1EMUL_H + +#define DSP1_VERSION 0x0102 + +class Dsp1 +{ + public: + // The DSP-1 status register has 16 bits, but only + // the upper 8 bits can be accessed from an external device, so all these + // positions are referred to the upper byte (bits D8 to D15) + enum SrFlags {DRC=0x04, DRS=0x10, RQM=0x80}; + + // According to Overload's docs, these are the meanings of the flags: + // DRC: The Data Register Control (DRC) bit specifies the data transfer length to and from the host CPU. + // 0: Data transfer to and from the DSP-1 is 16 bits. + // 1: Data transfer to and from the DSP-1 is 8 bits. + // DRS: The Data Register Status (DRS) bit indicates the data transfer status in the case of transfering 16-bit data. + // 0: Data transfer has terminated. + // 1: Data transfer in progress. + // RQM: The Request for Master (RQM) indicates that the DSP1 is requesting host CPU for data read/write. + // 0: Internal Data Register Transfer. + // 1: External Data Register Transfer. + + Dsp1(); + uint8 getSr(); // return the status register's high byte + uint8 getDr(); + void setDr(uint8 iDr); + void reset(); + + void serialize(serializer&); + + private: + enum FsmMajorState {WAIT_COMMAND, READ_DATA, WRITE_DATA}; + enum MaxDataAccesses {MAX_READS=7, MAX_WRITES=1024}; + + struct Command { + void (Dsp1::*callback)(int16 *, int16 *); + unsigned int reads; + unsigned int writes; + }; + + static const Command mCommandTable[]; + static const int16 MaxAZS_Exp[16]; + static const int16 SinTable[]; + static const int16 MulTable[]; + static const uint16 DataRom[]; + + struct SharedData { // some RAM variables shared between commands + int16 MatrixA[3][3]; // attitude matrix A + int16 MatrixB[3][3]; + int16 MatrixC[3][3]; + int16 CentreX, CentreY, CentreZ; // center of projection + int16 CentreZ_C, CentreZ_E; + int16 VOffset; // vertical offset of the screen with regard to the centre of projection + int16 Les, C_Les, E_Les; + int16 SinAas, CosAas; + int16 SinAzs, CosAzs; + int16 SinAZS, CosAZS; + int16 SecAZS_C1, SecAZS_E1; + int16 SecAZS_C2, SecAZS_E2; + int16 Nx, Ny, Nz; // normal vector to the screen (norm 1, points toward the center of projection) + int16 Gx, Gy, Gz; // center of the screen (global coordinates) + int16 Hx, Hy; // horizontal vector of the screen (Hz=0, norm 1, points toward the right of the screen) + int16 Vx, Vy, Vz; // vertical vector of the screen (norm 1, points toward the top of the screen) + + } shared; + + uint8 mSr; // status register + int mSrLowByteAccess; + uint16 mDr; // "internal" representation of the data register + unsigned mFsmMajorState; // current major state of the FSM + uint8 mCommand; // current command processed by the FSM + uint8 mDataCounter; // #uint16 read/writes counter used by the FSM + int16 mReadBuffer[MAX_READS]; + int16 mWriteBuffer[MAX_WRITES]; + bool mFreeze; // need explanation? ;) + + void fsmStep(bool read, uint8 &data); // FSM logic + + // commands + void memoryTest(int16 *input, int16 *output); + void memoryDump(int16 *input, int16 *output); + void memorySize(int16 *input, int16 *output); + void multiply(int16* input, int16* output); + void multiply2(int16* input, int16* output); + void inverse(int16 *input, int16 *output); + void triangle(int16 *input, int16 *output); + void radius(int16 *input, int16 *output); + void range(int16 *input, int16 *output); + void range2(int16 *input, int16 *output); + void distance(int16 *input, int16 *output); + void rotate(int16 *input, int16 *output); + void polar(int16 *input, int16 *output); + void attitudeA(int16 *input, int16 *output); + void attitudeB(int16 *input, int16 *output); + void attitudeC(int16 *input, int16 *output); + void objectiveA(int16 *input, int16 *output); + void objectiveB(int16 *input, int16 *output); + void objectiveC(int16 *input, int16 *output); + void subjectiveA(int16 *input, int16 *output); + void subjectiveB(int16 *input, int16 *output); + void subjectiveC(int16 *input, int16 *output); + void scalarA(int16 *input, int16 *output); + void scalarB(int16 *input, int16 *output); + void scalarC(int16 *input, int16 *output); + void gyrate(int16 *input, int16 *output); + void parameter(int16 *input, int16 *output); + void raster(int16 *input, int16 *output); + void target(int16 *input, int16 *output); + void project(int16 *input, int16 *output); + + // auxiliar functions + int16 sin(int16 Angle); + int16 cos(int16 Angle); + void inverse(int16 Coefficient, int16 Exponent, int16 &iCoefficient, int16 &iExponent); + int16 denormalizeAndClip(int16 C, int16 E); + void normalize(int16 m, int16 &Coefficient, int16 &Exponent); + void normalizeDouble(int32 Product, int16 &Coefficient, int16 &Exponent); + int16 shiftR(int16 C, int16 E); +}; + +#endif + diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/serialization.cpp new file mode 100644 index 0000000000..fb952a2555 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp1/serialization.cpp @@ -0,0 +1,52 @@ +auto DSP1::serialize(serializer& s) -> void { + dsp1emu.serialize(s); +} + +auto Dsp1::serialize(serializer &s) -> void { + for(unsigned i = 0; i < 3; i++) { + s.array(shared.MatrixA[i]); + s.array(shared.MatrixB[i]); + s.array(shared.MatrixC[i]); + } + + s.integer(shared.CentreX); + s.integer(shared.CentreY); + s.integer(shared.CentreZ); + s.integer(shared.CentreZ_C); + s.integer(shared.CentreZ_E); + s.integer(shared.VOffset); + s.integer(shared.Les); + s.integer(shared.C_Les); + s.integer(shared.E_Les); + s.integer(shared.SinAas); + s.integer(shared.CosAas); + s.integer(shared.SinAzs); + s.integer(shared.CosAzs); + s.integer(shared.SinAZS); + s.integer(shared.CosAZS); + s.integer(shared.SecAZS_C1); + s.integer(shared.SecAZS_E1); + s.integer(shared.SecAZS_C2); + s.integer(shared.SecAZS_E2); + s.integer(shared.Nx); + s.integer(shared.Ny); + s.integer(shared.Nz); + s.integer(shared.Gx); + s.integer(shared.Gy); + s.integer(shared.Gz); + s.integer(shared.Hx); + s.integer(shared.Hy); + s.integer(shared.Vx); + s.integer(shared.Vy); + s.integer(shared.Vz); + + s.integer(mSr); + s.integer(mSrLowByteAccess); + s.integer(mDr); + s.integer(mFsmMajorState); + s.integer(mCommand); + s.integer(mDataCounter); + s.array(mReadBuffer); + s.array(mWriteBuffer); + s.integer(mFreeze); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/dsp2.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/dsp2.cpp new file mode 100644 index 0000000000..234bf28a55 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/dsp2.cpp @@ -0,0 +1,136 @@ +#include + +namespace SuperFamicom { + +#define DSP2_CPP +#include "opcodes.cpp" + +DSP2 dsp2; +#include "serialization.cpp" + +auto DSP2::power() -> void { + status.waiting_for_command = true; + status.in_count = 0; + status.in_index = 0; + status.out_count = 0; + status.out_index = 0; + + status.op05transparent = 0; + status.op05haslen = false; + status.op05len = 0; + status.op06haslen = false; + status.op06len = 0; + status.op09word1 = 0; + status.op09word2 = 0; + status.op0dhaslen = false; + status.op0doutlen = 0; + status.op0dinlen = 0; +} + +auto DSP2::read(uint addr, uint8 data) -> uint8 { + if(addr & 1) return 0x00; + + uint8 r = 0xff; + if(status.out_count) { + r = status.output[status.out_index++]; + status.out_index &= 511; + if(status.out_count == status.out_index) { + status.out_count = 0; + } + } + return r; +} + +auto DSP2::write(uint addr, uint8 data) -> void { + if(addr & 1) return; + + if(status.waiting_for_command) { + status.command = data; + status.in_index = 0; + status.waiting_for_command = false; + + switch(data) { + case 0x01: status.in_count = 32; break; + case 0x03: status.in_count = 1; break; + case 0x05: status.in_count = 1; break; + case 0x06: status.in_count = 1; break; + case 0x07: break; + case 0x08: break; + case 0x09: status.in_count = 4; break; + case 0x0d: status.in_count = 2; break; + case 0x0f: status.in_count = 0; break; + } + } else { + status.parameters[status.in_index++] = data; + status.in_index &= 511; + } + + if(status.in_count == status.in_index) { + status.waiting_for_command = true; + status.out_index = 0; + switch(status.command) { + case 0x01: { + status.out_count = 32; + op01(); + } break; + + case 0x03: { + op03(); + } break; + + case 0x05: { + if(status.op05haslen) { + status.op05haslen = false; + status.out_count = status.op05len; + op05(); + } else { + status.op05len = status.parameters[0]; + status.in_index = 0; + status.in_count = status.op05len * 2; + status.op05haslen = true; + if(data)status.waiting_for_command = false; + } + } break; + + case 0x06: { + if(status.op06haslen) { + status.op06haslen = false; + status.out_count = status.op06len; + op06(); + } else { + status.op06len = status.parameters[0]; + status.in_index = 0; + status.in_count = status.op06len; + status.op06haslen = true; + if(data)status.waiting_for_command = false; + } + } break; + + case 0x07: break; + case 0x08: break; + + case 0x09: { + op09(); + } break; + + case 0x0d: { + if(status.op0dhaslen) { + status.op0dhaslen = false; + status.out_count = status.op0doutlen; + op0d(); + } else { + status.op0dinlen = status.parameters[0]; + status.op0doutlen = status.parameters[1]; + status.in_index = 0; + status.in_count = (status.op0dinlen + 1) >> 1; + status.op0dhaslen = true; + if(data)status.waiting_for_command = false; + } + } break; + + case 0x0f: break; + } + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/dsp2.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/dsp2.hpp new file mode 100644 index 0000000000..d02d18f8c3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/dsp2.hpp @@ -0,0 +1,38 @@ +struct DSP2 { + auto power() -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; + + struct { + bool waiting_for_command; + unsigned command; + unsigned in_count, in_index; + unsigned out_count, out_index; + + uint8_t parameters[512]; + uint8_t output[512]; + + uint8 op05transparent; + bool op05haslen; + int op05len; + bool op06haslen; + int op06len; + uint16 op09word1; + uint16 op09word2; + bool op0dhaslen; + int op0doutlen; + int op0dinlen; + } status; + + void op01(); + void op03(); + void op05(); + void op06(); + void op09(); + void op0d(); +}; + +extern DSP2 dsp2; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/opcodes.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/opcodes.cpp new file mode 100644 index 0000000000..f015ac32ca --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/opcodes.cpp @@ -0,0 +1,177 @@ +#ifdef DSP2_CPP + +//convert bitmap to bitplane tile +void DSP2::op01() { +//op01 size is always 32 bytes input and output +//the hardware does strange things if you vary the size + +unsigned char c0, c1, c2, c3; +unsigned char *p1 = status.parameters; +unsigned char *p2a = status.output; +unsigned char *p2b = status.output + 16; //halfway + +//process 8 blocks of 4 bytes each + for(int j = 0; j < 8; j++) { + c0 = *p1++; + c1 = *p1++; + c2 = *p1++; + c3 = *p1++; + + *p2a++ = (c0 & 0x10) << 3 | + (c0 & 0x01) << 6 | + (c1 & 0x10) << 1 | + (c1 & 0x01) << 4 | + (c2 & 0x10) >> 1 | + (c2 & 0x01) << 2 | + (c3 & 0x10) >> 3 | + (c3 & 0x01); + + *p2a++ = (c0 & 0x20) << 2 | + (c0 & 0x02) << 5 | + (c1 & 0x20) | + (c1 & 0x02) << 3 | + (c2 & 0x20) >> 2 | + (c2 & 0x02) << 1 | + (c3 & 0x20) >> 4 | + (c3 & 0x02) >> 1; + + *p2b++ = (c0 & 0x40) << 1 | + (c0 & 0x04) << 4 | + (c1 & 0x40) >> 1 | + (c1 & 0x04) << 2 | + (c2 & 0x40) >> 3 | + (c2 & 0x04) | + (c3 & 0x40) >> 5 | + (c3 & 0x04) >> 2; + + *p2b++ = (c0 & 0x80) | + (c0 & 0x08) << 3 | + (c1 & 0x80) >> 2 | + (c1 & 0x08) << 1 | + (c2 & 0x80) >> 4 | + (c2 & 0x08) >> 1 | + (c3 & 0x80) >> 6 | + (c3 & 0x08) >> 3; + } +} + +//set transparent color +void DSP2::op03() { + status.op05transparent = status.parameters[0]; +} + +//replace bitmap using transparent color +void DSP2::op05() { +uint8 color; +// Overlay bitmap with transparency. +// Input: +// +// Bitmap 1: i[0] <=> i[size-1] +// Bitmap 2: i[size] <=> i[2*size-1] +// +// Output: +// +// Bitmap 3: o[0] <=> o[size-1] +// +// Processing: +// +// Process all 4-bit pixels (nibbles) in the bitmap +// +// if ( BM2_pixel == transparent_color ) +// pixelout = BM1_pixel +// else +// pixelout = BM2_pixel + +// The max size bitmap is limited to 255 because the size parameter is a byte +// I think size=0 is an error. The behavior of the chip on size=0 is to +// return the last value written to DR if you read DR on Op05 with +// size = 0. I don't think it's worth implementing this quirk unless it's +// proven necessary. + +unsigned char c1, c2; +unsigned char *p1 = status.parameters; +unsigned char *p2 = status.parameters + status.op05len; +unsigned char *p3 = status.output; + + color = status.op05transparent & 0x0f; + + for(int n = 0; n < status.op05len; n++) { + c1 = *p1++; + c2 = *p2++; + *p3++ = ( ((c2 >> 4) == color ) ? c1 & 0xf0 : c2 & 0xf0 ) | + ( ((c2 & 0x0f) == color ) ? c1 & 0x0f : c2 & 0x0f ); + } +} + +//reverse bitmap +void DSP2::op06() { +// Input: +// size +// bitmap + +int i, j; + for(i = 0, j = status.op06len - 1; i < status.op06len; i++, j--) { + status.output[j] = (status.parameters[i] << 4) | (status.parameters[i] >> 4); + } +} + +//multiply +void DSP2::op09() { + status.out_count = 4; + + status.op09word1 = status.parameters[0] | (status.parameters[1] << 8); + status.op09word2 = status.parameters[2] | (status.parameters[3] << 8); + +uint32 r; + r = status.op09word1 * status.op09word2; + status.output[0] = r; + status.output[1] = r >> 8; + status.output[2] = r >> 16; + status.output[3] = r >> 24; +} + +//scale bitmap +void DSP2::op0d() { +// Bit accurate hardware algorithm - uses fixed point math +// This should match the DSP2 Op0D output exactly +// I wouldn't recommend using this unless you're doing hardware debug. +// In some situations it has small visual artifacts that +// are not readily apparent on a TV screen but show up clearly +// on a monitor. Use Overload's scaling instead. +// This is for hardware verification testing. +// +// One note: the HW can do odd byte scaling but since we divide +// by two to get the count of bytes this won't work well for +// odd byte scaling (in any of the current algorithm implementations). +// So far I haven't seen Dungeon Master use it. +// If it does we can adjust the parameters and code to work with it + +uint32 multiplier; // Any size int >= 32-bits +uint32 pixloc; // match size of multiplier +int i, j; +uint8 pixelarray[512]; + if(status.op0dinlen <= status.op0doutlen) { + multiplier = 0x10000; // In our self defined fixed point 0x10000 == 1 + } else { + multiplier = (status.op0dinlen << 17) / ((status.op0doutlen << 1) + 1); + } + + pixloc = 0; + for(i = 0; i < status.op0doutlen * 2; i++) { + j = pixloc >> 16; + + if(j & 1) { + pixelarray[i] = (status.parameters[j >> 1] & 0x0f); + } else { + pixelarray[i] = (status.parameters[j >> 1] & 0xf0) >> 4; + } + + pixloc += multiplier; + } + + for(i = 0; i < status.op0doutlen; i++) { + status.output[i] = (pixelarray[i << 1] << 4) | pixelarray[(i << 1) + 1]; + } +} + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/serialization.cpp new file mode 100644 index 0000000000..e2a8647d58 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp2/serialization.cpp @@ -0,0 +1,22 @@ +auto DSP2::serialize(serializer& s) -> void { + s.integer(status.waiting_for_command); + s.integer(status.command); + s.integer(status.in_count); + s.integer(status.in_index); + s.integer(status.out_count); + s.integer(status.out_index); + + s.array(status.parameters); + s.array(status.output); + + s.integer(status.op05transparent); + s.integer(status.op05haslen); + s.integer(status.op05len); + s.integer(status.op06haslen); + s.integer(status.op06len); + s.integer(status.op09word1); + s.integer(status.op09word2); + s.integer(status.op0dhaslen); + s.integer(status.op0doutlen); + s.integer(status.op0dinlen); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4.cpp new file mode 100644 index 0000000000..b935b136e6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4.cpp @@ -0,0 +1,62 @@ +#include + +namespace SuperFamicom { + +namespace DSP4i { + #define bool8 uint8_t + #define int8 int8_t + #define int16 int16_t + #define int32 int32_t + #define int64 int64_t + #define uint8 uint8_t + #define uint16 uint16_t + #define uint32 uint32_t + #define uint64 uint64_t + #define DSP4_CPP + inline uint16 READ_WORD(uint8 *addr) { + return (addr[0]) + (addr[1] << 8); + } + inline uint32 READ_DWORD(uint8 *addr) { + return (addr[0]) + (addr[1] << 8) + (addr[2] << 16) + (addr[3] << 24); + } + inline void WRITE_WORD(uint8 *addr, uint16 data) { + addr[0] = data; + addr[1] = data >> 8; + } + #include "dsp4emu.h" + #include "dsp4emu.c" + #undef bool8 + #undef int8 + #undef int16 + #undef int32 + #undef int64 + #undef uint8 + #undef uint16 + #undef uint32 + #undef uint64 +} + +DSP4 dsp4; +#include "serialization.cpp" + +auto DSP4::power() -> void { + DSP4i::InitDSP4(); +} + +auto DSP4::read(uint addr, uint8 data) -> uint8 { + if(addr & 1) return 0x80; + + DSP4i::dsp4_address = addr; + DSP4i::DSP4GetByte(); + return DSP4i::dsp4_byte; +} + +auto DSP4::write(uint addr, uint8 data) -> void { + if(addr & 1) return; + + DSP4i::dsp4_address = addr; + DSP4i::dsp4_byte = data; + DSP4i::DSP4SetByte(); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4.hpp new file mode 100644 index 0000000000..c447618470 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4.hpp @@ -0,0 +1,10 @@ +struct DSP4 { + auto power() -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; +}; + +extern DSP4 dsp4; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4emu.c b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4emu.c new file mode 100644 index 0000000000..73c1ec3d69 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4emu.c @@ -0,0 +1,2150 @@ +#ifdef DSP4_CPP + +//DSP-4 emulator code +//Copyright (c) 2004-2006 Dreamer Nom, John Weidman, Kris Bleakley, Nach, z80 gaiden + +/* +Due recognition and credit are given on Overload's DSP website. +Thank those contributors for their hard work on this chip. + + +Fixed-point math reminder: + +[sign, integer, fraction] +1.15.00 * 1.15.00 = 2.30.00 -> 1.30.00 (DSP) -> 1.31.00 (LSB is '0') +1.15.00 * 1.00.15 = 2.15.15 -> 1.15.15 (DSP) -> 1.15.16 (LSB is '0') +*/ + +#include "dsp4emu.h" + +struct DSP4_t DSP4; +struct DSP4_vars_t DSP4_vars; + +////////////////////////////////////////////////////////////// + +// input protocol + +static int16 DSP4_READ_WORD() +{ + int16 out; + + out = READ_WORD(DSP4.parameters + DSP4.in_index); + DSP4.in_index += 2; + + return out; +} + +static int32 DSP4_READ_DWORD() +{ + int32 out; + + out = READ_DWORD(DSP4.parameters + DSP4.in_index); + DSP4.in_index += 4; + + return out; +} + + +////////////////////////////////////////////////////////////// + +// output protocol + +#define DSP4_CLEAR_OUT() \ +{ DSP4.out_count = 0; DSP4.out_index = 0; } + +#define DSP4_WRITE_BYTE( d ) \ +{ WRITE_WORD( DSP4.output + DSP4.out_count, ( d ) ); DSP4.out_count++; } + +#define DSP4_WRITE_WORD( d ) \ +{ WRITE_WORD( DSP4.output + DSP4.out_count, ( d ) ); DSP4.out_count += 2; } + +#ifndef MSB_FIRST +#define DSP4_WRITE_16_WORD( d ) \ +{ memcpy(DSP4.output + DSP4.out_count, ( d ), 32); DSP4.out_count += 32; } +#else +#define DSP4_WRITE_16_WORD( d ) \ +{ int16 *p = ( d ), *end = ( d )+16; \ + for (; p != end; p++) \ + { \ + WRITE_WORD( DSP4.output + DSP4.out_count, *p ); \ + } \ + DSP4.out_count += 32; \ +} +#endif + +#ifdef PRINT_OP +#define DSP4_WRITE_DEBUG( x, d ) \ + WRITE_WORD( nop + x, d ); +#endif + +#ifdef DEBUG_DSP +#define DSP4_WRITE_DEBUG( x, d ) \ + WRITE_WORD( nop + x, d ); +#endif + +////////////////////////////////////////////////////////////// + +// used to wait for dsp i/o + +#define DSP4_WAIT( x ) \ + DSP4.in_index = 0; DSP4_vars.DSP4_Logic = x; return; + +////////////////////////////////////////////////////////////// + +// 1.7.8 -> 1.15.16 +#define SEX78( a ) ( ( (int32) ( (int16) (a) ) ) << 8 ) + +// 1.15.0 -> 1.15.16 +#define SEX16( a ) ( ( (int32) ( (int16) (a) ) ) << 16 ) + +#ifdef PRINT_OP +#define U16( a ) ( (uint16) ( a ) ) +#endif + +#ifdef DEBUG_DSP +#define U16( a ) ( (uint16) ( a ) ) +#endif + +////////////////////////////////////////////////////////////// + +// Attention: This lookup table is not verified +static const uint16 div_lut[64] = { 0x0000, 0x8000, 0x4000, 0x2aaa, 0x2000, 0x1999, 0x1555, 0x1249, 0x1000, 0x0e38, + 0x0ccc, 0x0ba2, 0x0aaa, 0x09d8, 0x0924, 0x0888, 0x0800, 0x0787, 0x071c, 0x06bc, + 0x0666, 0x0618, 0x05d1, 0x0590, 0x0555, 0x051e, 0x04ec, 0x04bd, 0x0492, 0x0469, + 0x0444, 0x0421, 0x0400, 0x03e0, 0x03c3, 0x03a8, 0x038e, 0x0375, 0x035e, 0x0348, + 0x0333, 0x031f, 0x030c, 0x02fa, 0x02e8, 0x02d8, 0x02c8, 0x02b9, 0x02aa, 0x029c, + 0x028f, 0x0282, 0x0276, 0x026a, 0x025e, 0x0253, 0x0249, 0x023e, 0x0234, 0x022b, + 0x0222, 0x0219, 0x0210, 0x0208, }; +int16 DSP4_Inverse(int16 value) +{ + // saturate bounds + if (value < 0) + { + value = 0; + } + if (value > 63) + { + value = 63; + } + + return div_lut[value]; +} + +////////////////////////////////////////////////////////////// + +// Prototype +void DSP4_OP0B(bool8 *draw, int16 sp_x, int16 sp_y, int16 sp_attr, bool8 size, bool8 stop); + +////////////////////////////////////////////////////////////// + +// OP00 +void DSP4_Multiply(int16 Multiplicand, int16 Multiplier, int32 *Product) +{ + *Product = (Multiplicand * Multiplier << 1) >> 1; +} + +////////////////////////////////////////////////////////////// + + +void DSP4_OP01() +{ + DSP4.waiting4command = FALSE; + + // op flow control + switch (DSP4_vars.DSP4_Logic) + { + case 1: + goto resume1; break; + case 2: + goto resume2; break; + case 3: + goto resume3; break; + } + + //////////////////////////////////////////////////// + // process initial inputs + + // sort inputs + DSP4_vars.world_y = DSP4_READ_DWORD(); + DSP4_vars.poly_bottom[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_top[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_cx[1][0] = DSP4_READ_WORD(); + DSP4_vars.viewport_bottom = DSP4_READ_WORD(); + DSP4_vars.world_x = DSP4_READ_DWORD(); + DSP4_vars.poly_cx[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_ptr[0][0] = DSP4_READ_WORD(); + DSP4_vars.world_yofs = DSP4_READ_WORD(); + DSP4_vars.world_dy = DSP4_READ_DWORD(); + DSP4_vars.world_dx = DSP4_READ_DWORD(); + DSP4_vars.distance = DSP4_READ_WORD(); + DSP4_READ_WORD(); // 0x0000 + DSP4_vars.world_xenv = DSP4_READ_DWORD(); + DSP4_vars.world_ddy = DSP4_READ_WORD(); + DSP4_vars.world_ddx = DSP4_READ_WORD(); + DSP4_vars.view_yofsenv = DSP4_READ_WORD(); + + // initial (x,y,offset) at starting DSP4_vars.raster line + DSP4_vars.view_x1 = (int16)((DSP4_vars.world_x + DSP4_vars.world_xenv) >> 16); + DSP4_vars.view_y1 = (int16)(DSP4_vars.world_y >> 16); + DSP4_vars.view_xofs1 = (int16)(DSP4_vars.world_x >> 16); + DSP4_vars.view_yofs1 = DSP4_vars.world_yofs; + DSP4_vars.view_turnoff_x = 0; + DSP4_vars.view_turnoff_dx = 0; + + // first DSP4_vars.raster line + DSP4_vars.poly_raster[0][0] = DSP4_vars.poly_bottom[0][0]; + + do + { + //////////////////////////////////////////////////// + // process one iteration of projection + + // perspective projection of world (x,y,scroll) points + // based on the current projection lines + DSP4_vars.view_x2 = (int16)(( ( ( DSP4_vars.world_x + DSP4_vars.world_xenv ) >> 16 ) * DSP4_vars.distance >> 15 ) + ( DSP4_vars.view_turnoff_x * DSP4_vars.distance >> 15 )); + DSP4_vars.view_y2 = (int16)((DSP4_vars.world_y >> 16) * DSP4_vars.distance >> 15); + DSP4_vars.view_xofs2 = DSP4_vars.view_x2; + DSP4_vars.view_yofs2 = (DSP4_vars.world_yofs * DSP4_vars.distance >> 15) + DSP4_vars.poly_bottom[0][0] - DSP4_vars.view_y2; + + + // 1. World x-location before transformation + // 2. Viewer x-position at the next + // 3. World y-location before perspective projection + // 4. Viewer y-position below the horizon + // 5. Number of DSP4_vars.raster lines drawn in this iteration + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD((uint16)((DSP4_vars.world_x + DSP4_vars.world_xenv) >> 16)); + DSP4_WRITE_WORD(DSP4_vars.view_x2); + DSP4_WRITE_WORD((uint16)(DSP4_vars.world_y >> 16)); + DSP4_WRITE_WORD(DSP4_vars.view_y2); + + ////////////////////////////////////////////////////// + + // SR = 0x00 + + // determine # of DSP4_vars.raster lines used + DSP4_vars.segments = DSP4_vars.poly_raster[0][0] - DSP4_vars.view_y2; + + // prevent overdraw + if (DSP4_vars.view_y2 >= DSP4_vars.poly_raster[0][0]) + DSP4_vars.segments = 0; + else + DSP4_vars.poly_raster[0][0] = DSP4_vars.view_y2; + + // don't draw outside the window + if (DSP4_vars.view_y2 < DSP4_vars.poly_top[0][0]) + { + DSP4_vars.segments = 0; + + // flush remaining DSP4_vars.raster lines + if (DSP4_vars.view_y1 >= DSP4_vars.poly_top[0][0]) + DSP4_vars.segments = DSP4_vars.view_y1 - DSP4_vars.poly_top[0][0]; + } + + // SR = 0x80 + + DSP4_WRITE_WORD(DSP4_vars.segments); + + ////////////////////////////////////////////////////// + + // scan next command if no SR check needed + if (DSP4_vars.segments) + { + int32 px_dx, py_dy; + int32 x_scroll, y_scroll; + + // SR = 0x00 + + // linear interpolation (lerp) between projected points + px_dx = (DSP4_vars.view_xofs2 - DSP4_vars.view_xofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + py_dy = (DSP4_vars.view_yofs2 - DSP4_vars.view_yofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + + // starting step values + x_scroll = SEX16(DSP4_vars.poly_cx[0][0] + DSP4_vars.view_xofs1); + y_scroll = SEX16(-DSP4_vars.viewport_bottom + DSP4_vars.view_yofs1 + DSP4_vars.view_yofsenv + DSP4_vars.poly_cx[1][0] - DSP4_vars.world_yofs); + + // SR = 0x80 + + // rasterize line + for (DSP4_vars.lcv = 0; DSP4_vars.lcv < DSP4_vars.segments; DSP4_vars.lcv++) + { + // 1. HDMA memory pointer (bg1) + // 2. vertical scroll offset ($210E) + // 3. horizontal scroll offset ($210D) + + DSP4_WRITE_WORD(DSP4_vars.poly_ptr[0][0]); + DSP4_WRITE_WORD((uint16)((y_scroll + 0x8000) >> 16)); + DSP4_WRITE_WORD((uint16)((x_scroll + 0x8000) >> 16)); + + + // update memory address + DSP4_vars.poly_ptr[0][0] -= 4; + + // update screen values + x_scroll += px_dx; + y_scroll += py_dy; + } + } + + //////////////////////////////////////////////////// + // Post-update + + // update new viewer (x,y,scroll) to last DSP4_vars.raster line drawn + DSP4_vars.view_x1 = DSP4_vars.view_x2; + DSP4_vars.view_y1 = DSP4_vars.view_y2; + DSP4_vars.view_xofs1 = DSP4_vars.view_xofs2; + DSP4_vars.view_yofs1 = DSP4_vars.view_yofs2; + + // add deltas for projection lines + DSP4_vars.world_dx += SEX78(DSP4_vars.world_ddx); + DSP4_vars.world_dy += SEX78(DSP4_vars.world_ddy); + + // update projection lines + DSP4_vars.world_x += (DSP4_vars.world_dx + DSP4_vars.world_xenv); + DSP4_vars.world_y += DSP4_vars.world_dy; + + // update road turnoff position + DSP4_vars.view_turnoff_x += DSP4_vars.view_turnoff_dx; + + //////////////////////////////////////////////////// + // command check + + // scan next command + DSP4.in_count = 2; + DSP4_WAIT(1) resume1 : + + // check for termination + DSP4_vars.distance = DSP4_READ_WORD(); + if (DSP4_vars.distance == -0x8000) + break; + + // road turnoff + if( (uint16) DSP4_vars.distance == 0x8001 ) + { + DSP4.in_count = 6; + DSP4_WAIT(2) resume2: + + DSP4_vars.distance = DSP4_READ_WORD(); + DSP4_vars.view_turnoff_x = DSP4_READ_WORD(); + DSP4_vars.view_turnoff_dx = DSP4_READ_WORD(); + + // factor in new changes + DSP4_vars.view_x1 += ( DSP4_vars.view_turnoff_x * DSP4_vars.distance >> 15 ); + DSP4_vars.view_xofs1 += ( DSP4_vars.view_turnoff_x * DSP4_vars.distance >> 15 ); + + // update stepping values + DSP4_vars.view_turnoff_x += DSP4_vars.view_turnoff_dx; + + DSP4.in_count = 2; + DSP4_WAIT(1) + } + + // already have 2 bytes read + DSP4.in_count = 6; + DSP4_WAIT(3) resume3 : + + // inspect inputs + DSP4_vars.world_ddy = DSP4_READ_WORD(); + DSP4_vars.world_ddx = DSP4_READ_WORD(); + DSP4_vars.view_yofsenv = DSP4_READ_WORD(); + + // no envelope here + DSP4_vars.world_xenv = 0; + } + while (1); + + // terminate op + DSP4.waiting4command = TRUE; +} + +////////////////////////////////////////////////////////////// + + +void DSP4_OP03() +{ + DSP4_vars.OAM_RowMax = 33; + memset(DSP4_vars.OAM_Row, 0, 64); +} + + +////////////////////////////////////////////////////////////// + + +void DSP4_OP05() +{ + DSP4_vars.OAM_index = 0; + DSP4_vars.OAM_bits = 0; + memset(DSP4_vars.OAM_attr, 0, 32); + DSP4_vars.sprite_count = 0; +} + + +////////////////////////////////////////////////////////////// + +void DSP4_OP06() +{ + DSP4_CLEAR_OUT(); + DSP4_WRITE_16_WORD(DSP4_vars.OAM_attr); +} + +////////////////////////////////////////////////////////////// + + +void DSP4_OP07() +{ + DSP4.waiting4command = FALSE; + + // op flow control + switch (DSP4_vars.DSP4_Logic) + { + case 1: + goto resume1; break; + case 2: + goto resume2; break; + } + + //////////////////////////////////////////////////// + // sort inputs + + DSP4_vars.world_y = DSP4_READ_DWORD(); + DSP4_vars.poly_bottom[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_top[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_cx[1][0] = DSP4_READ_WORD(); + DSP4_vars.viewport_bottom = DSP4_READ_WORD(); + DSP4_vars.world_x = DSP4_READ_DWORD(); + DSP4_vars.poly_cx[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_ptr[0][0] = DSP4_READ_WORD(); + DSP4_vars.world_yofs = DSP4_READ_WORD(); + DSP4_vars.distance = DSP4_READ_WORD(); + DSP4_vars.view_y2 = DSP4_READ_WORD(); + DSP4_vars.view_dy = DSP4_READ_WORD() * DSP4_vars.distance >> 15; + DSP4_vars.view_x2 = DSP4_READ_WORD(); + DSP4_vars.view_dx = DSP4_READ_WORD() * DSP4_vars.distance >> 15; + DSP4_vars.view_yofsenv = DSP4_READ_WORD(); + + // initial (x,y,offset) at starting DSP4_vars.raster line + DSP4_vars.view_x1 = (int16)(DSP4_vars.world_x >> 16); + DSP4_vars.view_y1 = (int16)(DSP4_vars.world_y >> 16); + DSP4_vars.view_xofs1 = DSP4_vars.view_x1; + DSP4_vars.view_yofs1 = DSP4_vars.world_yofs; + + // first DSP4_vars.raster line + DSP4_vars.poly_raster[0][0] = DSP4_vars.poly_bottom[0][0]; + + + do + { + //////////////////////////////////////////////////// + // process one iteration of projection + + // add shaping + DSP4_vars.view_x2 += DSP4_vars.view_dx; + DSP4_vars.view_y2 += DSP4_vars.view_dy; + + // vertical scroll calculation + DSP4_vars.view_xofs2 = DSP4_vars.view_x2; + DSP4_vars.view_yofs2 = (DSP4_vars.world_yofs * DSP4_vars.distance >> 15) + DSP4_vars.poly_bottom[0][0] - DSP4_vars.view_y2; + + // 1. Viewer x-position at the next + // 2. Viewer y-position below the horizon + // 3. Number of DSP4_vars.raster lines drawn in this iteration + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD(DSP4_vars.view_x2); + DSP4_WRITE_WORD(DSP4_vars.view_y2); + + ////////////////////////////////////////////////////// + + // SR = 0x00 + + // determine # of DSP4_vars.raster lines used + DSP4_vars.segments = DSP4_vars.view_y1 - DSP4_vars.view_y2; + + // prevent overdraw + if (DSP4_vars.view_y2 >= DSP4_vars.poly_raster[0][0]) + DSP4_vars.segments = 0; + else + DSP4_vars.poly_raster[0][0] = DSP4_vars.view_y2; + + // don't draw outside the window + if (DSP4_vars.view_y2 < DSP4_vars.poly_top[0][0]) + { + DSP4_vars.segments = 0; + + // flush remaining DSP4_vars.raster lines + if (DSP4_vars.view_y1 >= DSP4_vars.poly_top[0][0]) + DSP4_vars.segments = DSP4_vars.view_y1 - DSP4_vars.poly_top[0][0]; + } + + // SR = 0x80 + + DSP4_WRITE_WORD(DSP4_vars.segments); + + ////////////////////////////////////////////////////// + + // scan next command if no SR check needed + if (DSP4_vars.segments) + { + int32 px_dx, py_dy; + int32 x_scroll, y_scroll; + + // SR = 0x00 + + // linear interpolation (lerp) between projected points + px_dx = (DSP4_vars.view_xofs2 - DSP4_vars.view_xofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + py_dy = (DSP4_vars.view_yofs2 - DSP4_vars.view_yofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + + // starting step values + x_scroll = SEX16(DSP4_vars.poly_cx[0][0] + DSP4_vars.view_xofs1); + y_scroll = SEX16(-DSP4_vars.viewport_bottom + DSP4_vars.view_yofs1 + DSP4_vars.view_yofsenv + DSP4_vars.poly_cx[1][0] - DSP4_vars.world_yofs); + + // SR = 0x80 + + // rasterize line + for (DSP4_vars.lcv = 0; DSP4_vars.lcv < DSP4_vars.segments; DSP4_vars.lcv++) + { + // 1. HDMA memory pointer (bg2) + // 2. vertical scroll offset ($2110) + // 3. horizontal scroll offset ($210F) + + DSP4_WRITE_WORD(DSP4_vars.poly_ptr[0][0]); + DSP4_WRITE_WORD((uint16)((y_scroll + 0x8000) >> 16)); + DSP4_WRITE_WORD((uint16)((x_scroll + 0x8000) >> 16)); + + // update memory address + DSP4_vars.poly_ptr[0][0] -= 4; + + // update screen values + x_scroll += px_dx; + y_scroll += py_dy; + } + } + + ///////////////////////////////////////////////////// + // Post-update + + // update new viewer (x,y,scroll) to last DSP4_vars.raster line drawn + DSP4_vars.view_x1 = DSP4_vars.view_x2; + DSP4_vars.view_y1 = DSP4_vars.view_y2; + DSP4_vars.view_xofs1 = DSP4_vars.view_xofs2; + DSP4_vars.view_yofs1 = DSP4_vars.view_yofs2; + + //////////////////////////////////////////////////// + // command check + + // scan next command + DSP4.in_count = 2; + DSP4_WAIT(1) resume1 : + + // check for opcode termination + DSP4_vars.distance = DSP4_READ_WORD(); + if (DSP4_vars.distance == -0x8000) + break; + + // already have 2 bytes in queue + DSP4.in_count = 10; + DSP4_WAIT(2) resume2 : + + // inspect inputs + DSP4_vars.view_y2 = DSP4_READ_WORD(); + DSP4_vars.view_dy = DSP4_READ_WORD() * DSP4_vars.distance >> 15; + DSP4_vars.view_x2 = DSP4_READ_WORD(); + DSP4_vars.view_dx = DSP4_READ_WORD() * DSP4_vars.distance >> 15; + DSP4_vars.view_yofsenv = DSP4_READ_WORD(); + } + while (1); + + DSP4.waiting4command = TRUE; +} + +////////////////////////////////////////////////////////////// + +void DSP4_OP08() +{ + int16 win_left, win_right; + int16 view_x[2], view_y[2]; + int16 envelope[2][2]; + + DSP4.waiting4command = FALSE; + + // op flow control + switch (DSP4_vars.DSP4_Logic) + { + case 1: + goto resume1; break; + case 2: + goto resume2; break; + } + + //////////////////////////////////////////////////// + // process initial inputs for two polygons + + // clip values + DSP4_vars.poly_clipRt[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_clipRt[0][1] = DSP4_READ_WORD(); + DSP4_vars.poly_clipRt[1][0] = DSP4_READ_WORD(); + DSP4_vars.poly_clipRt[1][1] = DSP4_READ_WORD(); + + DSP4_vars.poly_clipLf[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_clipLf[0][1] = DSP4_READ_WORD(); + DSP4_vars.poly_clipLf[1][0] = DSP4_READ_WORD(); + DSP4_vars.poly_clipLf[1][1] = DSP4_READ_WORD(); + + // unknown (constant) (ex. 1P/2P = $00A6, $00A6, $00A6, $00A6) + DSP4_READ_WORD(); + DSP4_READ_WORD(); + DSP4_READ_WORD(); + DSP4_READ_WORD(); + + // unknown (constant) (ex. 1P/2P = $00A5, $00A5, $00A7, $00A7) + DSP4_READ_WORD(); + DSP4_READ_WORD(); + DSP4_READ_WORD(); + DSP4_READ_WORD(); + + // polygon centering (left,right) + DSP4_vars.poly_cx[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_cx[0][1] = DSP4_READ_WORD(); + DSP4_vars.poly_cx[1][0] = DSP4_READ_WORD(); + DSP4_vars.poly_cx[1][1] = DSP4_READ_WORD(); + + // HDMA pointer locations + DSP4_vars.poly_ptr[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_ptr[0][1] = DSP4_READ_WORD(); + DSP4_vars.poly_ptr[1][0] = DSP4_READ_WORD(); + DSP4_vars.poly_ptr[1][1] = DSP4_READ_WORD(); + + // starting DSP4_vars.raster line below the horizon + DSP4_vars.poly_bottom[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_bottom[0][1] = DSP4_READ_WORD(); + DSP4_vars.poly_bottom[1][0] = DSP4_READ_WORD(); + DSP4_vars.poly_bottom[1][1] = DSP4_READ_WORD(); + + // top boundary line to clip + DSP4_vars.poly_top[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_top[0][1] = DSP4_READ_WORD(); + DSP4_vars.poly_top[1][0] = DSP4_READ_WORD(); + DSP4_vars.poly_top[1][1] = DSP4_READ_WORD(); + + // unknown + // (ex. 1P = $2FC8, $0034, $FF5C, $0035) + // + // (ex. 2P = $3178, $0034, $FFCC, $0035) + // (ex. 2P = $2FC8, $0034, $FFCC, $0035) + + DSP4_READ_WORD(); + DSP4_READ_WORD(); + DSP4_READ_WORD(); + DSP4_READ_WORD(); + + // look at guidelines for both polygon shapes + DSP4_vars.distance = DSP4_READ_WORD(); + view_x[0] = DSP4_READ_WORD(); + view_y[0] = DSP4_READ_WORD(); + view_x[1] = DSP4_READ_WORD(); + view_y[1] = DSP4_READ_WORD(); + + // envelope shaping guidelines (one frame only) + envelope[0][0] = DSP4_READ_WORD(); + envelope[0][1] = DSP4_READ_WORD(); + envelope[1][0] = DSP4_READ_WORD(); + envelope[1][1] = DSP4_READ_WORD(); + + // starting base values to project from + DSP4_vars.poly_start[0] = view_x[0]; + DSP4_vars.poly_start[1] = view_x[1]; + + // starting DSP4_vars.raster lines to begin drawing + DSP4_vars.poly_raster[0][0] = view_y[0]; + DSP4_vars.poly_raster[0][1] = view_y[0]; + DSP4_vars.poly_raster[1][0] = view_y[1]; + DSP4_vars.poly_raster[1][1] = view_y[1]; + + // starting distances + DSP4_vars.poly_plane[0] = DSP4_vars.distance; + DSP4_vars.poly_plane[1] = DSP4_vars.distance; + + // SR = 0x00 + + // re-center coordinates + win_left = DSP4_vars.poly_cx[0][0] - view_x[0] + envelope[0][0]; + win_right = DSP4_vars.poly_cx[0][1] - view_x[0] + envelope[0][1]; + + // saturate offscreen data for polygon #1 + if (win_left < DSP4_vars.poly_clipLf[0][0]) + { + win_left = DSP4_vars.poly_clipLf[0][0]; + } + if (win_left > DSP4_vars.poly_clipRt[0][0]) + { + win_left = DSP4_vars.poly_clipRt[0][0]; + } + if (win_right < DSP4_vars.poly_clipLf[0][1]) + { + win_right = DSP4_vars.poly_clipLf[0][1]; + } + if (win_right > DSP4_vars.poly_clipRt[0][1]) + { + win_right = DSP4_vars.poly_clipRt[0][1]; + } + + // SR = 0x80 + + // initial output for polygon #1 + DSP4_CLEAR_OUT(); + DSP4_WRITE_BYTE(win_left & 0xff); + DSP4_WRITE_BYTE(win_right & 0xff); + + + do + { + int16 polygon; + //////////////////////////////////////////////////// + // command check + + // scan next command + DSP4.in_count = 2; + DSP4_WAIT(1) resume1 : + + // terminate op + DSP4_vars.distance = DSP4_READ_WORD(); + if (DSP4_vars.distance == -0x8000) + break; + + // already have 2 bytes in queue + DSP4.in_count = 16; + + DSP4_WAIT(2) resume2 : + + // look at guidelines for both polygon shapes + view_x[0] = DSP4_READ_WORD(); + view_y[0] = DSP4_READ_WORD(); + view_x[1] = DSP4_READ_WORD(); + view_y[1] = DSP4_READ_WORD(); + + // envelope shaping guidelines (one frame only) + envelope[0][0] = DSP4_READ_WORD(); + envelope[0][1] = DSP4_READ_WORD(); + envelope[1][0] = DSP4_READ_WORD(); + envelope[1][1] = DSP4_READ_WORD(); + + //////////////////////////////////////////////////// + // projection begins + + // init + DSP4_CLEAR_OUT(); + + + ////////////////////////////////////////////// + // solid polygon renderer - 2 shapes + + for (polygon = 0; polygon < 2; polygon++) + { + int32 left_inc, right_inc; + int16 x1_final, x2_final; + int16 env[2][2]; + int16 poly; + + // SR = 0x00 + + // # DSP4_vars.raster lines to draw + DSP4_vars.segments = DSP4_vars.poly_raster[polygon][0] - view_y[polygon]; + + // prevent overdraw + if (DSP4_vars.segments > 0) + { + // bump drawing cursor + DSP4_vars.poly_raster[polygon][0] = view_y[polygon]; + DSP4_vars.poly_raster[polygon][1] = view_y[polygon]; + } + else + DSP4_vars.segments = 0; + + // don't draw outside the window + if (view_y[polygon] < DSP4_vars.poly_top[polygon][0]) + { + DSP4_vars.segments = 0; + + // flush remaining DSP4_vars.raster lines + if (view_y[polygon] >= DSP4_vars.poly_top[polygon][0]) + DSP4_vars.segments = view_y[polygon] - DSP4_vars.poly_top[polygon][0]; + } + + // SR = 0x80 + + // tell user how many DSP4_vars.raster structures to read in + DSP4_WRITE_WORD(DSP4_vars.segments); + + // normal parameters + poly = polygon; + + ///////////////////////////////////////////////////// + + // scan next command if no SR check needed + if (DSP4_vars.segments) + { + int32 win_left, win_right; + + // road turnoff selection + if( (uint16) envelope[ polygon ][ 0 ] == (uint16) 0xc001 ) + poly = 1; + else if( envelope[ polygon ][ 1 ] == 0x3fff ) + poly = 1; + + /////////////////////////////////////////////// + // left side of polygon + + // perspective correction on additional shaping parameters + env[0][0] = envelope[polygon][0] * DSP4_vars.poly_plane[poly] >> 15; + env[0][1] = envelope[polygon][0] * DSP4_vars.distance >> 15; + + // project new shapes (left side) + x1_final = view_x[poly] + env[0][0]; + x2_final = DSP4_vars.poly_start[poly] + env[0][1]; + + // interpolate between projected points with shaping + left_inc = (x2_final - x1_final) * DSP4_Inverse(DSP4_vars.segments) << 1; + if (DSP4_vars.segments == 1) + left_inc = -left_inc; + + /////////////////////////////////////////////// + // right side of polygon + + // perspective correction on additional shaping parameters + env[1][0] = envelope[polygon][1] * DSP4_vars.poly_plane[poly] >> 15;; + env[1][1] = envelope[polygon][1] * DSP4_vars.distance >> 15; + + // project new shapes (right side) + x1_final = view_x[poly] + env[1][0]; + x2_final = DSP4_vars.poly_start[poly] + env[1][1]; + + + // interpolate between projected points with shaping + right_inc = (x2_final - x1_final) * DSP4_Inverse(DSP4_vars.segments) << 1; + if (DSP4_vars.segments == 1) + right_inc = -right_inc; + + /////////////////////////////////////////////// + // update each point on the line + + win_left = SEX16(DSP4_vars.poly_cx[polygon][0] - DSP4_vars.poly_start[poly] + env[0][0]); + win_right = SEX16(DSP4_vars.poly_cx[polygon][1] - DSP4_vars.poly_start[poly] + env[1][0]); + + // update DSP4_vars.distance drawn into world + DSP4_vars.poly_plane[polygon] = DSP4_vars.distance; + + // rasterize line + for (DSP4_vars.lcv = 0; DSP4_vars.lcv < DSP4_vars.segments; DSP4_vars.lcv++) + { + int16 x_left, x_right; + + // project new coordinates + win_left += left_inc; + win_right += right_inc; + + // grab integer portion, drop fraction (no rounding) + x_left = (int16)(win_left >> 16); + x_right = (int16)(win_right >> 16); + + // saturate offscreen data + if (x_left < DSP4_vars.poly_clipLf[polygon][0]) + x_left = DSP4_vars.poly_clipLf[polygon][0]; + if (x_left > DSP4_vars.poly_clipRt[polygon][0]) + x_left = DSP4_vars.poly_clipRt[polygon][0]; + if (x_right < DSP4_vars.poly_clipLf[polygon][1]) + x_right = DSP4_vars.poly_clipLf[polygon][1]; + if (x_right > DSP4_vars.poly_clipRt[polygon][1]) + x_right = DSP4_vars.poly_clipRt[polygon][1]; + + // 1. HDMA memory pointer + // 2. Left window position ($2126/$2128) + // 3. Right window position ($2127/$2129) + + DSP4_WRITE_WORD(DSP4_vars.poly_ptr[polygon][0]); + DSP4_WRITE_BYTE(x_left & 0xff); + DSP4_WRITE_BYTE(x_right & 0xff); + + + // update memory pointers + DSP4_vars.poly_ptr[polygon][0] -= 4; + DSP4_vars.poly_ptr[polygon][1] -= 4; + } // end rasterize line + } + + //////////////////////////////////////////////// + // Post-update + + // new projection spot to continue rasterizing from + DSP4_vars.poly_start[polygon] = view_x[poly]; + } // end polygon rasterizer + } + while (1); + + // unknown output + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD(0); + + + DSP4.waiting4command = TRUE; +} + +////////////////////////////////////////////////////////////// + +void DSP4_OP09() +{ + DSP4.waiting4command = FALSE; + + // op flow control + switch (DSP4_vars.DSP4_Logic) + { + case 1: + goto resume1; break; + case 2: + goto resume2; break; + case 3: + goto resume3; break; + case 4: + goto resume4; break; + case 5: + goto resume5; break; + case 6: + goto resume6; break; + } + + //////////////////////////////////////////////////// + // process initial inputs + + // grab screen information + DSP4_vars.viewport_cx = DSP4_READ_WORD(); + DSP4_vars.viewport_cy = DSP4_READ_WORD(); + DSP4_READ_WORD(); // 0x0000 + DSP4_vars.viewport_left = DSP4_READ_WORD(); + DSP4_vars.viewport_right = DSP4_READ_WORD(); + DSP4_vars.viewport_top = DSP4_READ_WORD(); + DSP4_vars.viewport_bottom = DSP4_READ_WORD(); + + // starting DSP4_vars.raster line below the horizon + DSP4_vars.poly_bottom[0][0] = DSP4_vars.viewport_bottom - DSP4_vars.viewport_cy; + DSP4_vars.poly_raster[0][0] = 0x100; + + do + { + //////////////////////////////////////////////////// + // check for new sprites + + DSP4.in_count = 4; + DSP4_WAIT(1) resume1 : + + //////////////////////////////////////////////// + // DSP4_vars.raster overdraw check + + DSP4_vars.raster = DSP4_READ_WORD(); + + // continue updating the DSP4_vars.raster line where overdraw begins + if (DSP4_vars.raster < DSP4_vars.poly_raster[0][0]) + { + DSP4_vars.sprite_clipy = DSP4_vars.viewport_bottom - (DSP4_vars.poly_bottom[0][0] - DSP4_vars.raster); + DSP4_vars.poly_raster[0][0] = DSP4_vars.raster; + } + + ///////////////////////////////////////////////// + // identify sprite + + // op termination + DSP4_vars.distance = DSP4_READ_WORD(); + if (DSP4_vars.distance == -0x8000) + goto terminate; + + + // no sprite + if (DSP4_vars.distance == 0x0000) + { + continue; + } + + //////////////////////////////////////////////////// + // process projection information + + // vehicle sprite + if ((uint16) DSP4_vars.distance == 0x9000) + { + int16 car_left, car_right, car_back; + int16 impact_left, impact_back; + int16 world_spx, world_spy; + int16 view_spx, view_spy; + uint16 energy; + + // we already have 4 bytes we want + DSP4.in_count = 14; + DSP4_WAIT(2) resume2 : + + // filter inputs + energy = DSP4_READ_WORD(); + impact_back = DSP4_READ_WORD(); + car_back = DSP4_READ_WORD(); + impact_left = DSP4_READ_WORD(); + car_left = DSP4_READ_WORD(); + DSP4_vars.distance = DSP4_READ_WORD(); + car_right = DSP4_READ_WORD(); + + // calculate car's world (x,y) values + world_spx = car_right - car_left; + world_spy = car_back; + + // add in collision vector [needs bit-twiddling] + world_spx -= energy * (impact_left - car_left) >> 16; + world_spy -= energy * (car_back - impact_back) >> 16; + + // perspective correction for world (x,y) + view_spx = world_spx * DSP4_vars.distance >> 15; + view_spy = world_spy * DSP4_vars.distance >> 15; + + // convert to screen values + DSP4_vars.sprite_x = DSP4_vars.viewport_cx + view_spx; + DSP4_vars.sprite_y = DSP4_vars.viewport_bottom - (DSP4_vars.poly_bottom[0][0] - view_spy); + + // make the car's (x)-coordinate available + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD(world_spx); + + // grab a few remaining vehicle values + DSP4.in_count = 4; + DSP4_WAIT(3) resume3 : + + // add vertical lift factor + DSP4_vars.sprite_y += DSP4_READ_WORD(); + } + // terrain sprite + else + { + int16 world_spx, world_spy; + int16 view_spx, view_spy; + + // we already have 4 bytes we want + DSP4.in_count = 10; + DSP4_WAIT(4) resume4 : + + // sort loop inputs + DSP4_vars.poly_cx[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_raster[0][1] = DSP4_READ_WORD(); + world_spx = DSP4_READ_WORD(); + world_spy = DSP4_READ_WORD(); + + // compute base DSP4_vars.raster line from the bottom + DSP4_vars.segments = DSP4_vars.poly_bottom[0][0] - DSP4_vars.raster; + + // perspective correction for world (x,y) + view_spx = world_spx * DSP4_vars.distance >> 15; + view_spy = world_spy * DSP4_vars.distance >> 15; + + // convert to screen values + DSP4_vars.sprite_x = DSP4_vars.viewport_cx + view_spx - DSP4_vars.poly_cx[0][0]; + DSP4_vars.sprite_y = DSP4_vars.viewport_bottom - DSP4_vars.segments + view_spy; + } + + // default sprite size: 16x16 + DSP4_vars.sprite_size = 1; + DSP4_vars.sprite_attr = DSP4_READ_WORD(); + + //////////////////////////////////////////////////// + // convert tile data to SNES OAM format + + do + { + uint16 header; + + int16 sp_x, sp_y, sp_attr, sp_dattr; + int16 sp_dx, sp_dy; + int16 pixels; + + bool8 draw; + + DSP4.in_count = 2; + DSP4_WAIT(5) resume5 : + + draw = TRUE; + + // opcode termination + DSP4_vars.raster = DSP4_READ_WORD(); + if (DSP4_vars.raster == -0x8000) + goto terminate; + + // stop code + if (DSP4_vars.raster == 0x0000 && !DSP4_vars.sprite_size) + break; + + // toggle sprite size + if (DSP4_vars.raster == 0x0000) + { + DSP4_vars.sprite_size = !DSP4_vars.sprite_size; + continue; + } + + // check for valid sprite header + header = DSP4_vars.raster; + header >>= 8; + if (header != 0x20 && + header != 0x2e && //This is for attractor sprite + header != 0x40 && + header != 0x60 && + header != 0xa0 && + header != 0xc0 && + header != 0xe0) + break; + + // read in rest of sprite data + DSP4.in_count = 4; + DSP4_WAIT(6) resume6 : + + draw = TRUE; + + ///////////////////////////////////// + // process tile data + + // sprite deltas + sp_dattr = DSP4_vars.raster; + sp_dy = DSP4_READ_WORD(); + sp_dx = DSP4_READ_WORD(); + + // update coordinates to screen space + sp_x = DSP4_vars.sprite_x + sp_dx; + sp_y = DSP4_vars.sprite_y + sp_dy; + + // update sprite nametable/attribute information + sp_attr = DSP4_vars.sprite_attr + sp_dattr; + + // allow partially visibile tiles + pixels = DSP4_vars.sprite_size ? 15 : 7; + + DSP4_CLEAR_OUT(); + + // transparent tile to clip off parts of a sprite (overdraw) + if (DSP4_vars.sprite_clipy - pixels <= sp_y && + sp_y <= DSP4_vars.sprite_clipy && + sp_x >= DSP4_vars.viewport_left - pixels && + sp_x <= DSP4_vars.viewport_right && + DSP4_vars.sprite_clipy >= DSP4_vars.viewport_top - pixels && + DSP4_vars.sprite_clipy <= DSP4_vars.viewport_bottom) + { + DSP4_OP0B(&draw, sp_x, DSP4_vars.sprite_clipy, 0x00EE, DSP4_vars.sprite_size, 0); + } + + + // normal sprite tile + if (sp_x >= DSP4_vars.viewport_left - pixels && + sp_x <= DSP4_vars.viewport_right && + sp_y >= DSP4_vars.viewport_top - pixels && + sp_y <= DSP4_vars.viewport_bottom && + sp_y <= DSP4_vars.sprite_clipy) + { + DSP4_OP0B(&draw, sp_x, sp_y, sp_attr, DSP4_vars.sprite_size, 0); + } + + + // no following OAM data + DSP4_OP0B(&draw, 0, 0x0100, 0, 0, 1); + } + while (1); + } + while (1); + + terminate : DSP4.waiting4command = TRUE; +} + +////////////////////////////////////////////////////////////// + +const uint16 OP0A_Values[16] = { 0x0000, 0x0030, 0x0060, 0x0090, 0x00c0, 0x00f0, 0x0120, 0x0150, 0xfe80, + 0xfeb0, 0xfee0, 0xff10, 0xff40, 0xff70, 0xffa0, 0xffd0 }; + +void DSP4_OP0A(int16 n2, int16 *o1, int16 *o2, int16 *o3, int16 *o4) +{ + *o4 = OP0A_Values[(n2 & 0x000f)]; + *o3 = OP0A_Values[(n2 & 0x00f0) >> 4]; + *o2 = OP0A_Values[(n2 & 0x0f00) >> 8]; + *o1 = OP0A_Values[(n2 & 0xf000) >> 12]; +} + +////////////////////////////////////////////////////////////// + +void DSP4_OP0B(bool8 *draw, int16 sp_x, int16 sp_y, int16 sp_attr, bool8 size, bool8 stop) +{ + int16 Row1, Row2; + + // SR = 0x00 + + // align to nearest 8-pixel row + Row1 = (sp_y >> 3) & 0x1f; + Row2 = (Row1 + 1) & 0x1f; + + // check boundaries + if (!((sp_y < 0) || ((sp_y & 0x01ff) < 0x00eb))) + { + *draw = 0; + } + if (size) + { + if (DSP4_vars.OAM_Row[Row1] + 1 >= DSP4_vars.OAM_RowMax) + *draw = 0; + if (DSP4_vars.OAM_Row[Row2] + 1 >= DSP4_vars.OAM_RowMax) + *draw = 0; + } + else + { + if (DSP4_vars.OAM_Row[Row1] >= DSP4_vars.OAM_RowMax) + { + *draw = 0; + } + } + + // emulator fail-safe (unknown if this really exists) + if (DSP4_vars.sprite_count >= 128) + { + *draw = 0; + } + + // SR = 0x80 + + if (*draw) + { + // Row tiles + if (size) + { + DSP4_vars.OAM_Row[Row1] += 2; + DSP4_vars.OAM_Row[Row2] += 2; + } + else + { + DSP4_vars.OAM_Row[Row1]++; + } + + // yield OAM output + DSP4_WRITE_WORD(1); + + // pack OAM data: x,y,name,attr + DSP4_WRITE_BYTE(sp_x & 0xff); + DSP4_WRITE_BYTE(sp_y & 0xff); + DSP4_WRITE_WORD(sp_attr); + + DSP4_vars.sprite_count++; + + // OAM: size,msb data + // save post-oam table data for future retrieval + DSP4_vars.OAM_attr[DSP4_vars.OAM_index] |= ((sp_x <0 || sp_x> 255) << DSP4_vars.OAM_bits); + DSP4_vars.OAM_bits++; + + DSP4_vars.OAM_attr[DSP4_vars.OAM_index] |= (size << DSP4_vars.OAM_bits); + DSP4_vars.OAM_bits++; + + // move to next byte in buffer + if (DSP4_vars.OAM_bits == 16) + { + DSP4_vars.OAM_bits = 0; + DSP4_vars.OAM_index++; + } + } + else if (stop) + { + // yield no OAM output + DSP4_WRITE_WORD(0); + } +} + +////////////////////////////////////////////////////////////// + +void DSP4_OP0D() +{ + DSP4.waiting4command = FALSE; + + // op flow control + switch (DSP4_vars.DSP4_Logic) + { + case 1: + goto resume1; break; + case 2: + goto resume2; break; + } + + //////////////////////////////////////////////////// + // process initial inputs + + // sort inputs + DSP4_vars.world_y = DSP4_READ_DWORD(); + DSP4_vars.poly_bottom[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_top[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_cx[1][0] = DSP4_READ_WORD(); + DSP4_vars.viewport_bottom = DSP4_READ_WORD(); + DSP4_vars.world_x = DSP4_READ_DWORD(); + DSP4_vars.poly_cx[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_ptr[0][0] = DSP4_READ_WORD(); + DSP4_vars.world_yofs = DSP4_READ_WORD(); + DSP4_vars.world_dy = DSP4_READ_DWORD(); + DSP4_vars.world_dx = DSP4_READ_DWORD(); + DSP4_vars.distance = DSP4_READ_WORD(); + DSP4_READ_WORD(); // 0x0000 + DSP4_vars.world_xenv = SEX78(DSP4_READ_WORD()); + DSP4_vars.world_ddy = DSP4_READ_WORD(); + DSP4_vars.world_ddx = DSP4_READ_WORD(); + DSP4_vars.view_yofsenv = DSP4_READ_WORD(); + + // initial (x,y,offset) at starting DSP4_vars.raster line + DSP4_vars.view_x1 = (int16)((DSP4_vars.world_x + DSP4_vars.world_xenv) >> 16); + DSP4_vars.view_y1 = (int16)(DSP4_vars.world_y >> 16); + DSP4_vars.view_xofs1 = (int16)(DSP4_vars.world_x >> 16); + DSP4_vars.view_yofs1 = DSP4_vars.world_yofs; + + // first DSP4_vars.raster line + DSP4_vars.poly_raster[0][0] = DSP4_vars.poly_bottom[0][0]; + + + do + { + //////////////////////////////////////////////////// + // process one iteration of projection + + // perspective projection of world (x,y,scroll) points + // based on the current projection lines + DSP4_vars.view_x2 = (int16)(( ( ( DSP4_vars.world_x + DSP4_vars.world_xenv ) >> 16 ) * DSP4_vars.distance >> 15 ) + ( DSP4_vars.view_turnoff_x * DSP4_vars.distance >> 15 )); + DSP4_vars.view_y2 = (int16)((DSP4_vars.world_y >> 16) * DSP4_vars.distance >> 15); + DSP4_vars.view_xofs2 = DSP4_vars.view_x2; + DSP4_vars.view_yofs2 = (DSP4_vars.world_yofs * DSP4_vars.distance >> 15) + DSP4_vars.poly_bottom[0][0] - DSP4_vars.view_y2; + + // 1. World x-location before transformation + // 2. Viewer x-position at the current + // 3. World y-location before perspective projection + // 4. Viewer y-position below the horizon + // 5. Number of DSP4_vars.raster lines drawn in this iteration + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD((uint16)((DSP4_vars.world_x + DSP4_vars.world_xenv) >> 16)); + DSP4_WRITE_WORD(DSP4_vars.view_x2); + DSP4_WRITE_WORD((uint16)(DSP4_vars.world_y >> 16)); + DSP4_WRITE_WORD(DSP4_vars.view_y2); + + ////////////////////////////////////////////////////////// + + // SR = 0x00 + + // determine # of DSP4_vars.raster lines used + DSP4_vars.segments = DSP4_vars.view_y1 - DSP4_vars.view_y2; + + // prevent overdraw + if (DSP4_vars.view_y2 >= DSP4_vars.poly_raster[0][0]) + DSP4_vars.segments = 0; + else + DSP4_vars.poly_raster[0][0] = DSP4_vars.view_y2; + + // don't draw outside the window + if (DSP4_vars.view_y2 < DSP4_vars.poly_top[0][0]) + { + DSP4_vars.segments = 0; + + // flush remaining DSP4_vars.raster lines + if (DSP4_vars.view_y1 >= DSP4_vars.poly_top[0][0]) + DSP4_vars.segments = DSP4_vars.view_y1 - DSP4_vars.poly_top[0][0]; + } + + // SR = 0x80 + + DSP4_WRITE_WORD(DSP4_vars.segments); + + ////////////////////////////////////////////////////////// + + // scan next command if no SR check needed + if (DSP4_vars.segments) + { + int32 px_dx, py_dy; + int32 x_scroll, y_scroll; + + // SR = 0x00 + + // linear interpolation (lerp) between projected points + px_dx = (DSP4_vars.view_xofs2 - DSP4_vars.view_xofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + py_dy = (DSP4_vars.view_yofs2 - DSP4_vars.view_yofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + + // starting step values + x_scroll = SEX16(DSP4_vars.poly_cx[0][0] + DSP4_vars.view_xofs1); + y_scroll = SEX16(-DSP4_vars.viewport_bottom + DSP4_vars.view_yofs1 + DSP4_vars.view_yofsenv + DSP4_vars.poly_cx[1][0] - DSP4_vars.world_yofs); + + // SR = 0x80 + + // rasterize line + for (DSP4_vars.lcv = 0; DSP4_vars.lcv < DSP4_vars.segments; DSP4_vars.lcv++) + { + // 1. HDMA memory pointer (bg1) + // 2. vertical scroll offset ($210E) + // 3. horizontal scroll offset ($210D) + + DSP4_WRITE_WORD(DSP4_vars.poly_ptr[0][0]); + DSP4_WRITE_WORD((uint16)((y_scroll + 0x8000) >> 16)); + DSP4_WRITE_WORD((uint16)((x_scroll + 0x8000) >> 16)); + + + // update memory address + DSP4_vars.poly_ptr[0][0] -= 4; + + // update screen values + x_scroll += px_dx; + y_scroll += py_dy; + } + } + + ///////////////////////////////////////////////////// + // Post-update + + // update new viewer (x,y,scroll) to last DSP4_vars.raster line drawn + DSP4_vars.view_x1 = DSP4_vars.view_x2; + DSP4_vars.view_y1 = DSP4_vars.view_y2; + DSP4_vars.view_xofs1 = DSP4_vars.view_xofs2; + DSP4_vars.view_yofs1 = DSP4_vars.view_yofs2; + + // add deltas for projection lines + DSP4_vars.world_dx += SEX78(DSP4_vars.world_ddx); + DSP4_vars.world_dy += SEX78(DSP4_vars.world_ddy); + + // update projection lines + DSP4_vars.world_x += (DSP4_vars.world_dx + DSP4_vars.world_xenv); + DSP4_vars.world_y += DSP4_vars.world_dy; + + //////////////////////////////////////////////////// + // command check + + // scan next command + DSP4.in_count = 2; + DSP4_WAIT(1) resume1 : + + // inspect input + DSP4_vars.distance = DSP4_READ_WORD(); + + // terminate op + if (DSP4_vars.distance == -0x8000) + break; + + // already have 2 bytes in queue + DSP4.in_count = 6; + DSP4_WAIT(2) resume2: + + // inspect inputs + DSP4_vars.world_ddy = DSP4_READ_WORD(); + DSP4_vars.world_ddx = DSP4_READ_WORD(); + DSP4_vars.view_yofsenv = DSP4_READ_WORD(); + + // no envelope here + DSP4_vars.world_xenv = 0; + } + while (1); + + DSP4.waiting4command = TRUE; +} + +////////////////////////////////////////////////////////////// + + +void DSP4_OP0E() +{ + DSP4_vars.OAM_RowMax = 16; + memset(DSP4_vars.OAM_Row, 0, 64); +} + + +////////////////////////////////////////////////////////////// + +void DSP4_OP0F() +{ + DSP4.waiting4command = FALSE; + + // op flow control + switch (DSP4_vars.DSP4_Logic) + { + case 1: + goto resume1; break; + case 2: + goto resume2; break; + case 3: + goto resume3; break; + case 4: + goto resume4; break; + } + + //////////////////////////////////////////////////// + // process initial inputs + + // sort inputs + DSP4_READ_WORD(); // 0x0000 + DSP4_vars.world_y = DSP4_READ_DWORD(); + DSP4_vars.poly_bottom[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_top[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_cx[1][0] = DSP4_READ_WORD(); + DSP4_vars.viewport_bottom = DSP4_READ_WORD(); + DSP4_vars.world_x = DSP4_READ_DWORD(); + DSP4_vars.poly_cx[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_ptr[0][0] = DSP4_READ_WORD(); + DSP4_vars.world_yofs = DSP4_READ_WORD(); + DSP4_vars.world_dy = DSP4_READ_DWORD(); + DSP4_vars.world_dx = DSP4_READ_DWORD(); + DSP4_vars.distance = DSP4_READ_WORD(); + DSP4_READ_WORD(); // 0x0000 + DSP4_vars.world_xenv = DSP4_READ_DWORD(); + DSP4_vars.world_ddy = DSP4_READ_WORD(); + DSP4_vars.world_ddx = DSP4_READ_WORD(); + DSP4_vars.view_yofsenv = DSP4_READ_WORD(); + + // initial (x,y,offset) at starting DSP4_vars.raster line + DSP4_vars.view_x1 = (int16)((DSP4_vars.world_x + DSP4_vars.world_xenv) >> 16); + DSP4_vars.view_y1 = (int16)(DSP4_vars.world_y >> 16); + DSP4_vars.view_xofs1 = (int16)(DSP4_vars.world_x >> 16); + DSP4_vars.view_yofs1 = DSP4_vars.world_yofs; + DSP4_vars.view_turnoff_x = 0; + DSP4_vars.view_turnoff_dx = 0; + + // first DSP4_vars.raster line + DSP4_vars.poly_raster[0][0] = DSP4_vars.poly_bottom[0][0]; + + + do + { + //////////////////////////////////////////////////// + // process one iteration of projection + + // perspective projection of world (x,y,scroll) points + // based on the current projection lines + DSP4_vars.view_x2 = (int16)(((DSP4_vars.world_x + DSP4_vars.world_xenv) >> 16) * DSP4_vars.distance >> 15); + DSP4_vars.view_y2 = (int16)((DSP4_vars.world_y >> 16) * DSP4_vars.distance >> 15); + DSP4_vars.view_xofs2 = DSP4_vars.view_x2; + DSP4_vars.view_yofs2 = (DSP4_vars.world_yofs * DSP4_vars.distance >> 15) + DSP4_vars.poly_bottom[0][0] - DSP4_vars.view_y2; + + // 1. World x-location before transformation + // 2. Viewer x-position at the next + // 3. World y-location before perspective projection + // 4. Viewer y-position below the horizon + // 5. Number of DSP4_vars.raster lines drawn in this iteration + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD((uint16)((DSP4_vars.world_x + DSP4_vars.world_xenv) >> 16)); + DSP4_WRITE_WORD(DSP4_vars.view_x2); + DSP4_WRITE_WORD((uint16)(DSP4_vars.world_y >> 16)); + DSP4_WRITE_WORD(DSP4_vars.view_y2); + + ////////////////////////////////////////////////////// + + // SR = 0x00 + + // determine # of DSP4_vars.raster lines used + DSP4_vars.segments = DSP4_vars.poly_raster[0][0] - DSP4_vars.view_y2; + + // prevent overdraw + if (DSP4_vars.view_y2 >= DSP4_vars.poly_raster[0][0]) + DSP4_vars.segments = 0; + else + DSP4_vars.poly_raster[0][0] = DSP4_vars.view_y2; + + // don't draw outside the window + if (DSP4_vars.view_y2 < DSP4_vars.poly_top[0][0]) + { + DSP4_vars.segments = 0; + + // flush remaining DSP4_vars.raster lines + if (DSP4_vars.view_y1 >= DSP4_vars.poly_top[0][0]) + DSP4_vars.segments = DSP4_vars.view_y1 - DSP4_vars.poly_top[0][0]; + } + + // SR = 0x80 + + DSP4_WRITE_WORD(DSP4_vars.segments); + + ////////////////////////////////////////////////////// + + // scan next command if no SR check needed + if (DSP4_vars.segments) + { + int32 px_dx, py_dy; + int32 x_scroll, y_scroll; + + for (DSP4_vars.lcv = 0; DSP4_vars.lcv < 4; DSP4_vars.lcv++) + { + // grab inputs + DSP4.in_count = 4; + DSP4_WAIT(1); + resume1 : + for (;;) + { + int16 distance; + int16 color, red, green, blue; + + distance = DSP4_READ_WORD(); + color = DSP4_READ_WORD(); + + // U1+B5+G5+R5 + red = color & 0x1f; + green = (color >> 5) & 0x1f; + blue = (color >> 10) & 0x1f; + + // dynamic lighting + red = (red * distance >> 15) & 0x1f; + green = (green * distance >> 15) & 0x1f; + blue = (blue * distance >> 15) & 0x1f; + color = red | (green << 5) | (blue << 10); + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD(color); + break; + } + } + + ////////////////////////////////////////////////////// + + // SR = 0x00 + + // linear interpolation (lerp) between projected points + px_dx = (DSP4_vars.view_xofs2 - DSP4_vars.view_xofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + py_dy = (DSP4_vars.view_yofs2 - DSP4_vars.view_yofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + + + // starting step values + x_scroll = SEX16(DSP4_vars.poly_cx[0][0] + DSP4_vars.view_xofs1); + y_scroll = SEX16(-DSP4_vars.viewport_bottom + DSP4_vars.view_yofs1 + DSP4_vars.view_yofsenv + DSP4_vars.poly_cx[1][0] - DSP4_vars.world_yofs); + + // SR = 0x80 + + // rasterize line + for (DSP4_vars.lcv = 0; DSP4_vars.lcv < DSP4_vars.segments; DSP4_vars.lcv++) + { + // 1. HDMA memory pointer + // 2. vertical scroll offset ($210E) + // 3. horizontal scroll offset ($210D) + + DSP4_WRITE_WORD(DSP4_vars.poly_ptr[0][0]); + DSP4_WRITE_WORD((uint16)((y_scroll + 0x8000) >> 16)); + DSP4_WRITE_WORD((uint16)((x_scroll + 0x8000) >> 16)); + + // update memory address + DSP4_vars.poly_ptr[0][0] -= 4; + + // update screen values + x_scroll += px_dx; + y_scroll += py_dy; + } + } + + //////////////////////////////////////////////////// + // Post-update + + // update new viewer (x,y,scroll) to last DSP4_vars.raster line drawn + DSP4_vars.view_x1 = DSP4_vars.view_x2; + DSP4_vars.view_y1 = DSP4_vars.view_y2; + DSP4_vars.view_xofs1 = DSP4_vars.view_xofs2; + DSP4_vars.view_yofs1 = DSP4_vars.view_yofs2; + + // add deltas for projection lines + DSP4_vars.world_dx += SEX78(DSP4_vars.world_ddx); + DSP4_vars.world_dy += SEX78(DSP4_vars.world_ddy); + + // update projection lines + DSP4_vars.world_x += (DSP4_vars.world_dx + DSP4_vars.world_xenv); + DSP4_vars.world_y += DSP4_vars.world_dy; + + // update road turnoff position + DSP4_vars.view_turnoff_x += DSP4_vars.view_turnoff_dx; + + //////////////////////////////////////////////////// + // command check + + // scan next command + DSP4.in_count = 2; + DSP4_WAIT(2) resume2: + + // check for termination + DSP4_vars.distance = DSP4_READ_WORD(); + if (DSP4_vars.distance == -0x8000) + break; + + // road splice + if( (uint16) DSP4_vars.distance == 0x8001 ) + { + DSP4.in_count = 6; + DSP4_WAIT(3) resume3: + + DSP4_vars.distance = DSP4_READ_WORD(); + DSP4_vars.view_turnoff_x = DSP4_READ_WORD(); + DSP4_vars.view_turnoff_dx = DSP4_READ_WORD(); + + // factor in new changes + DSP4_vars.view_x1 += ( DSP4_vars.view_turnoff_x * DSP4_vars.distance >> 15 ); + DSP4_vars.view_xofs1 += ( DSP4_vars.view_turnoff_x * DSP4_vars.distance >> 15 ); + + // update stepping values + DSP4_vars.view_turnoff_x += DSP4_vars.view_turnoff_dx; + + DSP4.in_count = 2; + DSP4_WAIT(2) + } + + // already have 2 bytes in queue + DSP4.in_count = 6; + DSP4_WAIT(4) resume4 : + + // inspect inputs + DSP4_vars.world_ddy = DSP4_READ_WORD(); + DSP4_vars.world_ddx = DSP4_READ_WORD(); + DSP4_vars.view_yofsenv = DSP4_READ_WORD(); + + // no envelope here + DSP4_vars.world_xenv = 0; + } + while (1); + + // terminate op + DSP4.waiting4command = TRUE; +} + +////////////////////////////////////////////////////////////// + + +void DSP4_OP10() +{ + DSP4.waiting4command = FALSE; + + // op flow control + switch (DSP4_vars.DSP4_Logic) + { + case 1: + goto resume1; break; + case 2: + goto resume2; break; + case 3: + goto resume3; break; + } + + //////////////////////////////////////////////////// + // sort inputs + + DSP4_READ_WORD(); // 0x0000 + DSP4_vars.world_y = DSP4_READ_DWORD(); + DSP4_vars.poly_bottom[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_top[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_cx[1][0] = DSP4_READ_WORD(); + DSP4_vars.viewport_bottom = DSP4_READ_WORD(); + DSP4_vars.world_x = DSP4_READ_DWORD(); + DSP4_vars.poly_cx[0][0] = DSP4_READ_WORD(); + DSP4_vars.poly_ptr[0][0] = DSP4_READ_WORD(); + DSP4_vars.world_yofs = DSP4_READ_WORD(); + DSP4_vars.distance = DSP4_READ_WORD(); + DSP4_vars.view_y2 = DSP4_READ_WORD(); + DSP4_vars.view_dy = DSP4_READ_WORD() * DSP4_vars.distance >> 15; + DSP4_vars.view_x2 = DSP4_READ_WORD(); + DSP4_vars.view_dx = DSP4_READ_WORD() * DSP4_vars.distance >> 15; + DSP4_vars.view_yofsenv = DSP4_READ_WORD(); + + // initial (x,y,offset) at starting DSP4_vars.raster line + DSP4_vars.view_x1 = (int16)(DSP4_vars.world_x >> 16); + DSP4_vars.view_y1 = (int16)(DSP4_vars.world_y >> 16); + DSP4_vars.view_xofs1 = DSP4_vars.view_x1; + DSP4_vars.view_yofs1 = DSP4_vars.world_yofs; + + // first DSP4_vars.raster line + DSP4_vars.poly_raster[0][0] = DSP4_vars.poly_bottom[0][0]; + + do + { + //////////////////////////////////////////////////// + // process one iteration of projection + + // add shaping + DSP4_vars.view_x2 += DSP4_vars.view_dx; + DSP4_vars.view_y2 += DSP4_vars.view_dy; + + // vertical scroll calculation + DSP4_vars.view_xofs2 = DSP4_vars.view_x2; + DSP4_vars.view_yofs2 = (DSP4_vars.world_yofs * DSP4_vars.distance >> 15) + DSP4_vars.poly_bottom[0][0] - DSP4_vars.view_y2; + + // 1. Viewer x-position at the next + // 2. Viewer y-position below the horizon + // 3. Number of DSP4_vars.raster lines drawn in this iteration + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD(DSP4_vars.view_x2); + DSP4_WRITE_WORD(DSP4_vars.view_y2); + + ////////////////////////////////////////////////////// + + // SR = 0x00 + + // determine # of DSP4_vars.raster lines used + DSP4_vars.segments = DSP4_vars.view_y1 - DSP4_vars.view_y2; + + // prevent overdraw + if (DSP4_vars.view_y2 >= DSP4_vars.poly_raster[0][0]) + DSP4_vars.segments = 0; + else + DSP4_vars.poly_raster[0][0] = DSP4_vars.view_y2; + + // don't draw outside the window + if (DSP4_vars.view_y2 < DSP4_vars.poly_top[0][0]) + { + DSP4_vars.segments = 0; + + // flush remaining DSP4_vars.raster lines + if (DSP4_vars.view_y1 >= DSP4_vars.poly_top[0][0]) + DSP4_vars.segments = DSP4_vars.view_y1 - DSP4_vars.poly_top[0][0]; + } + + // SR = 0x80 + + DSP4_WRITE_WORD(DSP4_vars.segments); + + ////////////////////////////////////////////////////// + + // scan next command if no SR check needed + if (DSP4_vars.segments) + { + for (DSP4_vars.lcv = 0; DSP4_vars.lcv < 4; DSP4_vars.lcv++) + { + // grab inputs + DSP4.in_count = 4; + DSP4_WAIT(1); + resume1 : + for (;;) + { + int16 distance; + int16 color, red, green, blue; + + distance = DSP4_READ_WORD(); + color = DSP4_READ_WORD(); + + // U1+B5+G5+R5 + red = color & 0x1f; + green = (color >> 5) & 0x1f; + blue = (color >> 10) & 0x1f; + + // dynamic lighting + red = (red * distance >> 15) & 0x1f; + green = (green * distance >> 15) & 0x1f; + blue = (blue * distance >> 15) & 0x1f; + color = red | (green << 5) | (blue << 10); + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD(color); + break; + } + } + } + + ////////////////////////////////////////////////////// + + // scan next command if no SR check needed + if (DSP4_vars.segments) + { + int32 px_dx, py_dy; + int32 x_scroll, y_scroll; + + // SR = 0x00 + + // linear interpolation (lerp) between projected points + px_dx = (DSP4_vars.view_xofs2 - DSP4_vars.view_xofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + py_dy = (DSP4_vars.view_yofs2 - DSP4_vars.view_yofs1) * DSP4_Inverse(DSP4_vars.segments) << 1; + + // starting step values + x_scroll = SEX16(DSP4_vars.poly_cx[0][0] + DSP4_vars.view_xofs1); + y_scroll = SEX16(-DSP4_vars.viewport_bottom + DSP4_vars.view_yofs1 + DSP4_vars.view_yofsenv + DSP4_vars.poly_cx[1][0] - DSP4_vars.world_yofs); + + // SR = 0x80 + + // rasterize line + for (DSP4_vars.lcv = 0; DSP4_vars.lcv < DSP4_vars.segments; DSP4_vars.lcv++) + { + // 1. HDMA memory pointer (bg2) + // 2. vertical scroll offset ($2110) + // 3. horizontal scroll offset ($210F) + + DSP4_WRITE_WORD(DSP4_vars.poly_ptr[0][0]); + DSP4_WRITE_WORD((uint16)((y_scroll + 0x8000) >> 16)); + DSP4_WRITE_WORD((uint16)((x_scroll + 0x8000) >> 16)); + + // update memory address + DSP4_vars.poly_ptr[0][0] -= 4; + + // update screen values + x_scroll += px_dx; + y_scroll += py_dy; + } + } + + ///////////////////////////////////////////////////// + // Post-update + + // update new viewer (x,y,scroll) to last DSP4_vars.raster line drawn + DSP4_vars.view_x1 = DSP4_vars.view_x2; + DSP4_vars.view_y1 = DSP4_vars.view_y2; + DSP4_vars.view_xofs1 = DSP4_vars.view_xofs2; + DSP4_vars.view_yofs1 = DSP4_vars.view_yofs2; + + //////////////////////////////////////////////////// + // command check + + // scan next command + DSP4.in_count = 2; + DSP4_WAIT(2) resume2 : + + // check for opcode termination + DSP4_vars.distance = DSP4_READ_WORD(); + if (DSP4_vars.distance == -0x8000) + break; + + // already have 2 bytes in queue + DSP4.in_count = 10; + DSP4_WAIT(3) resume3 : + + + // inspect inputs + DSP4_vars.view_y2 = DSP4_READ_WORD(); + DSP4_vars.view_dy = DSP4_READ_WORD() * DSP4_vars.distance >> 15; + DSP4_vars.view_x2 = DSP4_READ_WORD(); + DSP4_vars.view_dx = DSP4_READ_WORD() * DSP4_vars.distance >> 15; + } + while (1); + + DSP4.waiting4command = TRUE; +} + +////////////////////////////////////////////////////////////// + +void DSP4_OP11(int16 A, int16 B, int16 C, int16 D, int16 *M) +{ + // 0x155 = 341 = Horizontal Width of the Screen + *M = ((A * 0x0155 >> 2) & 0xf000) | + ((B * 0x0155 >> 6) & 0x0f00) | + ((C * 0x0155 >> 10) & 0x00f0) | + ((D * 0x0155 >> 14) & 0x000f); +} + + + + + +///////////////////////////////////////////////////////////// +//Processing Code +///////////////////////////////////////////////////////////// +uint8 dsp4_byte; +uint16 dsp4_address; + +void InitDSP4() +{ + memset(&DSP4, 0, sizeof(DSP4)); + DSP4.waiting4command = TRUE; +} + +void DSP4SetByte() +{ + // clear pending read + if (DSP4.out_index < DSP4.out_count) + { + DSP4.out_index++; + return; + } + + if (DSP4.waiting4command) + { + if (DSP4.half_command) + { + DSP4.command |= (dsp4_byte << 8); + DSP4.in_index = 0; + DSP4.waiting4command = FALSE; + DSP4.half_command = FALSE; + DSP4.out_count = 0; + DSP4.out_index = 0; + + DSP4_vars.DSP4_Logic = 0; + + + switch (DSP4.command) + { + case 0x0000: + DSP4.in_count = 4; break; + case 0x0001: + DSP4.in_count = 44; break; + case 0x0003: + DSP4.in_count = 0; break; + case 0x0005: + DSP4.in_count = 0; break; + case 0x0006: + DSP4.in_count = 0; break; + case 0x0007: + DSP4.in_count = 34; break; + case 0x0008: + DSP4.in_count = 90; break; + case 0x0009: + DSP4.in_count = 14; break; + case 0x000a: + DSP4.in_count = 6; break; + case 0x000b: + DSP4.in_count = 6; break; + case 0x000d: + DSP4.in_count = 42; break; + case 0x000e: + DSP4.in_count = 0; break; + case 0x000f: + DSP4.in_count = 46; break; + case 0x0010: + DSP4.in_count = 36; break; + case 0x0011: + DSP4.in_count = 8; break; + default: + DSP4.waiting4command = TRUE; + break; + } + } + else + { + DSP4.command = dsp4_byte; + DSP4.half_command = TRUE; + } + } + else + { + DSP4.parameters[DSP4.in_index] = dsp4_byte; + DSP4.in_index++; + } + + if (!DSP4.waiting4command && DSP4.in_count == DSP4.in_index) + { + // Actually execute the command + DSP4.waiting4command = TRUE; + DSP4.out_index = 0; + DSP4.in_index = 0; + + switch (DSP4.command) + { + // 16-bit multiplication + case 0x0000: + { + int16 multiplier, multiplicand; + int32 product; + + multiplier = DSP4_READ_WORD(); + multiplicand = DSP4_READ_WORD(); + + DSP4_Multiply(multiplicand, multiplier, &product); + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD((uint16)(product)); + DSP4_WRITE_WORD((uint16)(product >> 16)); + } + break; + + // single-player track projection + case 0x0001: + DSP4_OP01(); break; + + // single-player selection + case 0x0003: + DSP4_OP03(); break; + + // clear OAM + case 0x0005: + DSP4_OP05(); break; + + // transfer OAM + case 0x0006: + DSP4_OP06(); break; + + // single-player track turnoff projection + case 0x0007: + DSP4_OP07(); break; + + // solid polygon projection + case 0x0008: + DSP4_OP08(); break; + + // sprite projection + case 0x0009: + DSP4_OP09(); break; + + // unknown + case 0x000A: + { + int16 in1a = DSP4_READ_WORD(); + int16 in2a = DSP4_READ_WORD(); + int16 in3a = DSP4_READ_WORD(); + int16 out1a, out2a, out3a, out4a; + + DSP4_OP0A(in2a, &out2a, &out1a, &out4a, &out3a); + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD(out1a); + DSP4_WRITE_WORD(out2a); + DSP4_WRITE_WORD(out3a); + DSP4_WRITE_WORD(out4a); + } + break; + + // set OAM + case 0x000B: + { + int16 sp_x = DSP4_READ_WORD(); + int16 sp_y = DSP4_READ_WORD(); + int16 sp_attr = DSP4_READ_WORD(); + bool8 draw = 1; + + DSP4_CLEAR_OUT(); + + DSP4_OP0B(&draw, sp_x, sp_y, sp_attr, 0, 1); + } + break; + + // multi-player track projection + case 0x000D: + DSP4_OP0D(); break; + + // multi-player selection + case 0x000E: + DSP4_OP0E(); break; + + // single-player track projection with lighting + case 0x000F: + DSP4_OP0F(); break; + + // single-player track turnoff projection with lighting + case 0x0010: + DSP4_OP10(); break; + + // unknown: horizontal mapping command + case 0x0011: + { + int16 a, b, c, d, m; + + + d = DSP4_READ_WORD(); + c = DSP4_READ_WORD(); + b = DSP4_READ_WORD(); + a = DSP4_READ_WORD(); + + DSP4_OP11(a, b, c, d, &m); + + DSP4_CLEAR_OUT(); + DSP4_WRITE_WORD(m); + + break; + } + + default: + break; + } + } +} + +void DSP4GetByte() +{ + if (DSP4.out_count) + { + dsp4_byte = (uint8) DSP4.output[DSP4.out_index&0x1FF]; + DSP4.out_index++; + if (DSP4.out_count == DSP4.out_index) + DSP4.out_count = 0; + } + else + { + dsp4_byte = 0xff; + } +} + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4emu.h b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4emu.h new file mode 100644 index 0000000000..834b33de4b --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/dsp4emu.h @@ -0,0 +1,108 @@ +//DSP-4 emulator code +//Copyright (c) 2004-2006 Dreamer Nom, John Weidman, Kris Bleakley, Nach, z80 gaiden + +#ifndef DSP4EMU_H +#define DSP4EMU_H + +#undef TRUE +#undef FALSE +#define TRUE true +#define FALSE false + +struct DSP4_t +{ + bool8 waiting4command; + bool8 half_command; + uint16 command; + uint32 in_count; + uint32 in_index; + uint32 out_count; + uint32 out_index; + uint8 parameters[512]; + uint8 output[512]; +}; + +extern struct DSP4_t DSP4; + +struct DSP4_vars_t +{ + // op control + int8 DSP4_Logic; // controls op flow + + + // projection format + int16 lcv; // loop-control variable + int16 distance; // z-position into virtual world + int16 raster; // current raster line + int16 segments; // number of raster lines drawn + + // 1.15.16 or 1.15.0 [sign, integer, fraction] + int32 world_x; // line of x-projection in world + int32 world_y; // line of y-projection in world + int32 world_dx; // projection line x-delta + int32 world_dy; // projection line y-delta + int16 world_ddx; // x-delta increment + int16 world_ddy; // y-delta increment + int32 world_xenv; // world x-shaping factor + int16 world_yofs; // world y-vertical scroll + + int16 view_x1; // current viewer-x + int16 view_y1; // current viewer-y + int16 view_x2; // future viewer-x + int16 view_y2; // future viewer-y + int16 view_dx; // view x-delta factor + int16 view_dy; // view y-delta factor + int16 view_xofs1; // current viewer x-vertical scroll + int16 view_yofs1; // current viewer y-vertical scroll + int16 view_xofs2; // future viewer x-vertical scroll + int16 view_yofs2; // future viewer y-vertical scroll + int16 view_yofsenv; // y-scroll shaping factor + int16 view_turnoff_x; // road turnoff data + int16 view_turnoff_dx; // road turnoff delta factor + + + // drawing area + + int16 viewport_cx; // x-center of viewport window + int16 viewport_cy; // y-center of render window + int16 viewport_left; // x-left of viewport + int16 viewport_right; // x-right of viewport + int16 viewport_top; // y-top of viewport + int16 viewport_bottom; // y-bottom of viewport + + + // sprite structure + + int16 sprite_x; // projected x-pos of sprite + int16 sprite_y; // projected y-pos of sprite + int16 sprite_attr; // obj attributes + bool8 sprite_size; // sprite size: 8x8 or 16x16 + int16 sprite_clipy; // visible line to clip pixels off + int16 sprite_count; + + // generic projection variables designed for + // two solid polygons + two polygon sides + + int16 poly_clipLf[2][2]; // left clip boundary + int16 poly_clipRt[2][2]; // right clip boundary + int16 poly_ptr[2][2]; // HDMA structure pointers + int16 poly_raster[2][2]; // current raster line below horizon + int16 poly_top[2][2]; // top clip boundary + int16 poly_bottom[2][2]; // bottom clip boundary + int16 poly_cx[2][2]; // center for left/right points + int16 poly_start[2]; // current projection points + int16 poly_plane[2]; // previous z-plane distance + + + // OAM + int16 OAM_attr[16]; // OAM (size,MSB) data + int16 OAM_index; // index into OAM table + int16 OAM_bits; // offset into OAM table + + int16 OAM_RowMax; // maximum number of tiles per 8 aligned pixels (row) + int16 OAM_Row[32]; // current number of tiles per row +}; + +extern struct DSP4_vars_t DSP4_vars; + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/serialization.cpp new file mode 100644 index 0000000000..7fa38ebbba --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/dsp4/serialization.cpp @@ -0,0 +1,72 @@ +auto DSP4::serialize(serializer& s) -> void { + s.integer(DSP4i::DSP4.waiting4command); + s.integer(DSP4i::DSP4.half_command); + s.integer(DSP4i::DSP4.command); + s.integer(DSP4i::DSP4.in_count); + s.integer(DSP4i::DSP4.in_index); + s.integer(DSP4i::DSP4.out_count); + s.integer(DSP4i::DSP4.out_index); + s.array(DSP4i::DSP4.parameters); + s.array(DSP4i::DSP4.output); + + s.integer(DSP4i::DSP4_vars.DSP4_Logic); + s.integer(DSP4i::DSP4_vars.lcv); + s.integer(DSP4i::DSP4_vars.distance); + s.integer(DSP4i::DSP4_vars.raster); + s.integer(DSP4i::DSP4_vars.segments); + s.integer(DSP4i::DSP4_vars.world_x); + s.integer(DSP4i::DSP4_vars.world_y); + s.integer(DSP4i::DSP4_vars.world_dx); + s.integer(DSP4i::DSP4_vars.world_dy); + s.integer(DSP4i::DSP4_vars.world_ddx); + s.integer(DSP4i::DSP4_vars.world_ddy); + s.integer(DSP4i::DSP4_vars.world_xenv); + s.integer(DSP4i::DSP4_vars.world_yofs); + s.integer(DSP4i::DSP4_vars.view_x1); + s.integer(DSP4i::DSP4_vars.view_y1); + s.integer(DSP4i::DSP4_vars.view_x2); + s.integer(DSP4i::DSP4_vars.view_y2); + s.integer(DSP4i::DSP4_vars.view_dx); + s.integer(DSP4i::DSP4_vars.view_dy); + s.integer(DSP4i::DSP4_vars.view_xofs1); + s.integer(DSP4i::DSP4_vars.view_yofs1); + s.integer(DSP4i::DSP4_vars.view_xofs2); + s.integer(DSP4i::DSP4_vars.view_yofs2); + s.integer(DSP4i::DSP4_vars.view_yofsenv); + s.integer(DSP4i::DSP4_vars.view_turnoff_x); + s.integer(DSP4i::DSP4_vars.view_turnoff_dx); + s.integer(DSP4i::DSP4_vars.viewport_cx); + s.integer(DSP4i::DSP4_vars.viewport_cy); + s.integer(DSP4i::DSP4_vars.viewport_left); + s.integer(DSP4i::DSP4_vars.viewport_right); + s.integer(DSP4i::DSP4_vars.viewport_top); + s.integer(DSP4i::DSP4_vars.viewport_bottom); + s.integer(DSP4i::DSP4_vars.sprite_x); + s.integer(DSP4i::DSP4_vars.sprite_y); + s.integer(DSP4i::DSP4_vars.sprite_attr); + s.integer(DSP4i::DSP4_vars.sprite_size); + s.integer(DSP4i::DSP4_vars.sprite_clipy); + s.integer(DSP4i::DSP4_vars.sprite_count); + + s.array(DSP4i::DSP4_vars.poly_clipLf[0]); + s.array(DSP4i::DSP4_vars.poly_clipLf[1]); + s.array(DSP4i::DSP4_vars.poly_clipRt[0]); + s.array(DSP4i::DSP4_vars.poly_clipRt[1]); + s.array(DSP4i::DSP4_vars.poly_ptr[0]); + s.array(DSP4i::DSP4_vars.poly_ptr[1]); + s.array(DSP4i::DSP4_vars.poly_raster[0]); + s.array(DSP4i::DSP4_vars.poly_raster[1]); + s.array(DSP4i::DSP4_vars.poly_top[0]); + s.array(DSP4i::DSP4_vars.poly_top[1]); + s.array(DSP4i::DSP4_vars.poly_bottom[0]); + s.array(DSP4i::DSP4_vars.poly_bottom[1]); + s.array(DSP4i::DSP4_vars.poly_cx[0]); + s.array(DSP4i::DSP4_vars.poly_cx[1]); + s.array(DSP4i::DSP4_vars.poly_start); + s.array(DSP4i::DSP4_vars.poly_plane); + s.array(DSP4i::DSP4_vars.OAM_attr); + s.integer(DSP4i::DSP4_vars.OAM_index); + s.integer(DSP4i::DSP4_vars.OAM_bits); + s.integer(DSP4i::DSP4_vars.OAM_RowMax); + s.array(DSP4i::DSP4_vars.OAM_Row); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/epsonrtc.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/epsonrtc.cpp new file mode 100644 index 0000000000..74e0c2febd --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/epsonrtc.cpp @@ -0,0 +1,205 @@ +#include + +namespace SuperFamicom { + +#include "memory.cpp" +#include "time.cpp" +#include "serialization.cpp" +EpsonRTC epsonrtc; + +auto EpsonRTC::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto EpsonRTC::Enter() -> void { + while(true) { + scheduler.synchronize(); + epsonrtc.main(); + } +} + +auto EpsonRTC::main() -> void { + if(wait) { if(--wait == 0) ready = 1; } + + clocks++; + if((clocks & ~0x00ff) == 0) roundSeconds(); //125 microseconds + if((clocks & ~0x3fff) == 0) duty(); //1/128th second + if((clocks & ~0x7fff) == 0) irq(0); //1/64th second + if(clocks == 0) { //1 second + seconds++; + irq(1); + if(seconds % 60 == 0) irq(2); //1 minute + if(seconds % 1440 == 0) irq(3), seconds = 0; //1 hour + tick(); + } + + step(1); + synchronizeCPU(); +} + +auto EpsonRTC::step(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; +} + +auto EpsonRTC::initialize() -> void { + secondlo = 0; + secondhi = 0; + batteryfailure = 1; + + minutelo = 0; + minutehi = 0; + resync = 0; + + hourlo = 0; + hourhi = 0; + meridian = 0; + + daylo = 0; + dayhi = 0; + dayram = 0; + + monthlo = 0; + monthhi = 0; + monthram = 0; + + yearlo = 0; + yearhi = 0; + + weekday = 0; + + hold = 0; + calendar = 0; + irqflag = 0; + roundseconds = 0; + + irqmask = 0; + irqduty = 0; + irqperiod = 0; + + pause = 0; + stop = 0; + atime = 0; + test = 0; +} + +auto EpsonRTC::power() -> void { + create(EpsonRTC::Enter, 32'768 * 64); + + clocks = 0; + seconds = 0; + + chipselect = 0; + state = State::Mode; + offset = 0; + wait = 0; + ready = 0; + holdtick = 0; +} + +auto EpsonRTC::synchronize(uint64 timestamp) -> void { + time_t systime = timestamp; + tm* timeinfo = localtime(&systime); + + uint second = min(59, timeinfo->tm_sec); + secondlo = second % 10; + secondhi = second / 10; + + uint minute = timeinfo->tm_min; + minutelo = minute % 10; + minutehi = minute / 10; + + uint hour = timeinfo->tm_hour; + if(atime) { + hourlo = hour % 10; + hourhi = hour / 10; + } else { + meridian = hour >= 12; + hour %= 12; + if(hour == 0) hour = 12; + hourlo = hour % 10; + hourhi = hour / 10; + } + + uint day = timeinfo->tm_mday; + daylo = day % 10; + dayhi = day / 10; + + uint month = 1 + timeinfo->tm_mon; + monthlo = month % 10; + monthhi = month / 10; + + uint year = timeinfo->tm_year % 100; + yearlo = year % 10; + yearhi = year / 10; + + weekday = timeinfo->tm_wday; + + resync = true; //alert program that time has changed +} + +auto EpsonRTC::read(uint addr, uint8 data) -> uint8 { + cpu.synchronizeCoprocessors(); + addr &= 3; + + if(addr == 0) { + return chipselect; + } + + if(addr == 1) { + if(chipselect != 1) return 0; + if(ready == 0) return 0; + if(state == State::Write) return mdr; + if(state != State::Read) return 0; + ready = 0; + wait = 8; + return rtcRead(offset++); + } + + if(addr == 2) { + return ready << 7; + } + + return data; +} + +auto EpsonRTC::write(uint addr, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + addr &= 3, data &= 15; + + if(addr == 0) { + chipselect = data; + if(chipselect != 1) rtcReset(); + ready = 1; + } + + if(addr == 1) { + if(chipselect != 1) return; + if(ready == 0) return; + + if(state == State::Mode) { + if(data != 0x03 && data != 0x0c) return; + state = State::Seek; + ready = 0; + wait = 8; + mdr = data; + } + + else if(state == State::Seek) { + if(mdr == 0x03) state = State::Write; + if(mdr == 0x0c) state = State::Read; + offset = data; + ready = 0; + wait = 8; + mdr = data; + } + + else if(state == State::Write) { + rtcWrite(offset++, data); + ready = 0; + wait = 8; + mdr = data; + } + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/epsonrtc.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/epsonrtc.hpp new file mode 100644 index 0000000000..62ff70efbe --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/epsonrtc.hpp @@ -0,0 +1,90 @@ +//Epson RTC-4513 Real-Time Clock + +struct EpsonRTC : Thread { + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; + + auto initialize() -> void; + auto power() -> void; + auto synchronize(uint64 timestamp) -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; + + uint21 clocks; + uint seconds; + + uint2 chipselect; + enum class State : uint { Mode, Seek, Read, Write } state; + uint4 mdr; + uint4 offset; + uint wait; + uint1 ready; + uint1 holdtick; + + uint4 secondlo; + uint3 secondhi; + uint1 batteryfailure; + + uint4 minutelo; + uint3 minutehi; + uint1 resync; + + uint4 hourlo; + uint2 hourhi; + uint1 meridian; + + uint4 daylo; + uint2 dayhi; + uint1 dayram; + + uint4 monthlo; + uint1 monthhi; + uint2 monthram; + + uint4 yearlo; + uint4 yearhi; + + uint3 weekday; + + uint1 hold; + uint1 calendar; + uint1 irqflag; + uint1 roundseconds; + + uint1 irqmask; + uint1 irqduty; + uint2 irqperiod; + + uint1 pause; + uint1 stop; + uint1 atime; //astronomical time (24-hour mode) + uint1 test; + + //memory.cpp + auto rtcReset() -> void; + auto rtcRead(uint4 addr) -> uint4; + auto rtcWrite(uint4 addr, uint4 data) -> void; + + auto load(const uint8* data) -> void; + auto save(uint8* data) -> void; + + //time.cpp + auto irq(uint2 period) -> void; + auto duty() -> void; + auto roundSeconds() -> void; + auto tick() -> void; + + auto tickSecond() -> void; + auto tickMinute() -> void; + auto tickHour() -> void; + auto tickDay() -> void; + auto tickMonth() -> void; + auto tickYear() -> void; +}; + +extern EpsonRTC epsonrtc; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/memory.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/memory.cpp new file mode 100644 index 0000000000..f3ab59ee80 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/memory.cpp @@ -0,0 +1,180 @@ +auto EpsonRTC::rtcReset() -> void { + state = State::Mode; + offset = 0; + + resync = 0; + pause = 0; + test = 0; +} + +auto EpsonRTC::rtcRead(uint4 addr) -> uint4 { + switch(addr) { default: + case 0: return secondlo; + case 1: return secondhi | batteryfailure << 3; + case 2: return minutelo; + case 3: return minutehi | resync << 3; + case 4: return hourlo; + case 5: return hourhi | meridian << 2 | resync << 3; + case 6: return daylo; + case 7: return dayhi | dayram << 2 | resync << 3; + case 8: return monthlo; + case 9: return monthhi | monthram << 1 | resync << 3; + case 10: return yearlo; + case 11: return yearhi; + case 12: return weekday | resync << 3; + case 13: { + uint1 readflag = irqflag & !irqmask; + irqflag = 0; + return hold | calendar << 1 | readflag << 2 | roundseconds << 3; + } + case 14: return irqmask | irqduty << 1 | irqperiod << 2; + case 15: return pause | stop << 1 | atime << 2 | test << 3; + } +} + +auto EpsonRTC::rtcWrite(uint4 addr, uint4 data) -> void { + switch(addr) { + case 0: + secondlo = data; + break; + case 1: + secondhi = data; + batteryfailure = data >> 3; + break; + case 2: + minutelo = data; + break; + case 3: + minutehi = data; + break; + case 4: + hourlo = data; + break; + case 5: + hourhi = data; + meridian = data >> 2; + if(atime == 1) meridian = 0; + if(atime == 0) hourhi &= 1; + break; + case 6: + daylo = data; + break; + case 7: + dayhi = data; + dayram = data >> 2; + break; + case 8: + monthlo = data; + break; + case 9: + monthhi = data; + monthram = data >> 1; + break; + case 10: + yearlo = data; + break; + case 11: + yearhi = data; + break; + case 12: + weekday = data; + break; + case 13: { + bool held = hold; + hold = data; + calendar = data >> 1; + roundseconds = data >> 3; + if(held == 1 && hold == 0 && holdtick == 1) { + //if a second has passed during hold, increment one second upon resuming + holdtick = 0; + tickSecond(); + } + } break; + case 14: + irqmask = data; + irqduty = data >> 1; + irqperiod = data >> 2; + break; + case 15: + pause = data; + stop = data >> 1; + atime = data >> 2; + test = data >> 3; + if(atime == 1) meridian = 0; + if(atime == 0) hourhi &= 1; + if(pause) { + secondlo = 0; + secondhi = 0; + } + break; + } +} + +auto EpsonRTC::load(const uint8* data) -> void { + secondlo = data[0] >> 0; + secondhi = data[0] >> 4; + batteryfailure = data[0] >> 7; + + minutelo = data[1] >> 0; + minutehi = data[1] >> 4; + resync = data[1] >> 7; + + hourlo = data[2] >> 0; + hourhi = data[2] >> 4; + meridian = data[2] >> 6; + + daylo = data[3] >> 0; + dayhi = data[3] >> 4; + dayram = data[3] >> 6; + + monthlo = data[4] >> 0; + monthhi = data[4] >> 4; + monthram = data[4] >> 5; + + yearlo = data[5] >> 0; + yearhi = data[5] >> 4; + + weekday = data[6] >> 0; + + hold = data[6] >> 4; + calendar = data[6] >> 5; + irqflag = data[6] >> 6; + roundseconds = data[6] >> 7; + + irqmask = data[7] >> 0; + irqduty = data[7] >> 1; + irqperiod = data[7] >> 2; + + pause = data[7] >> 4; + stop = data[7] >> 5; + atime = data[7] >> 6; + test = data[7] >> 7; + + uint64 timestamp = 0; + for(auto byte : range(8)) { + timestamp |= data[8 + byte] << (byte * 8); + } + + uint64 diff = (uint64)time(0) - timestamp; + while(diff >= 60 * 60 * 24) { tickDay(); diff -= 60 * 60 * 24; } + while(diff >= 60 * 60) { tickHour(); diff -= 60 * 60; } + while(diff >= 60) { tickMinute(); diff -= 60; } + while(diff--) tickSecond(); +} + +auto EpsonRTC::save(uint8* data) -> void { + data[0] = secondlo << 0 | secondhi << 4 | batteryfailure << 7; + data[1] = minutelo << 0 | minutehi << 4 | resync << 7; + data[2] = hourlo << 0 | hourhi << 4 | meridian << 6 | resync << 7; + data[3] = daylo << 0 | dayhi << 4 | dayram << 6 | resync << 7; + data[4] = monthlo << 0 | monthhi << 4 | monthram << 5 | resync << 7; + data[5] = yearlo << 0 | yearhi << 4; + data[6] = weekday << 0 | resync << 3 | hold << 4 | calendar << 5 | irqflag << 6 | roundseconds << 7; + data[7] = irqmask << 0 | irqduty << 1 | irqperiod << 2 | pause << 4 | stop << 5 | atime << 6 | test << 7; + + uint64 timestamp = (uint64)time(0); + for(auto byte : range(8)) { + data[8 + byte] = timestamp; + timestamp >>= 8; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/serialization.cpp new file mode 100644 index 0000000000..0b75af61a6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/serialization.cpp @@ -0,0 +1,53 @@ +auto EpsonRTC::serialize(serializer& s) -> void { + Thread::serialize(s); + + s.integer(clocks); + s.integer(seconds); + + s.integer(chipselect); + s.integer((uint&)state); + s.integer(mdr); + s.integer(offset); + s.integer(wait); + s.integer(ready); + s.integer(holdtick); + + s.integer(secondlo); + s.integer(secondhi); + s.integer(batteryfailure); + + s.integer(minutelo); + s.integer(minutehi); + s.integer(resync); + + s.integer(hourlo); + s.integer(hourhi); + s.integer(meridian); + + s.integer(daylo); + s.integer(dayhi); + s.integer(dayram); + + s.integer(monthlo); + s.integer(monthhi); + s.integer(monthram); + + s.integer(yearlo); + s.integer(yearhi); + + s.integer(weekday); + + s.integer(hold); + s.integer(calendar); + s.integer(irqflag); + s.integer(roundseconds); + + s.integer(irqmask); + s.integer(irqduty); + s.integer(irqperiod); + + s.integer(pause); + s.integer(stop); + s.integer(atime); + s.integer(test); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/time.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/time.cpp new file mode 100644 index 0000000000..08c13a49f4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/epsonrtc/time.cpp @@ -0,0 +1,182 @@ +auto EpsonRTC::irq(uint2 period) -> void { + if(stop || pause) return; + + if(period == irqperiod) irqflag = 1; +} + +auto EpsonRTC::duty() -> void { + if(irqduty) irqflag = 0; +} + +auto EpsonRTC::roundSeconds() -> void { + if(roundseconds == 0) return; + roundseconds = 0; + + if(secondhi >= 3) tickMinute(); + secondlo = 0; + secondhi = 0; +} + +auto EpsonRTC::tick() -> void { + if(stop || pause) return; + + if(hold) { + holdtick = 1; + return; + } + + resync = 1; + tickSecond(); +} + +//below code provides bit-perfect emulation of invalid BCD values on the RTC-4513 +//code makes extensive use of variable-length integers (see epsonrtc.hpp for sizes) + +auto EpsonRTC::tickSecond() -> void { + if(secondlo <= 8 || secondlo == 12) { + secondlo++; + } else { + secondlo = 0; + if(secondhi < 5) { + secondhi++; + } else { + secondhi = 0; + tickMinute(); + } + } +} + +auto EpsonRTC::tickMinute() -> void { + if(minutelo <= 8 || minutelo == 12) { + minutelo++; + } else { + minutelo = 0; + if(minutehi < 5) { + minutehi++; + } else { + minutehi = 0; + tickHour(); + } + } +} + +auto EpsonRTC::tickHour() -> void { + if(atime) { + if(hourhi < 2) { + if(hourlo <= 8 || hourlo == 12) { + hourlo++; + } else { + hourlo = !(hourlo & 1); + hourhi++; + } + } else { + if(hourlo != 3 && !(hourlo & 4)) { + if(hourlo <= 8 || hourlo >= 12) { + hourlo++; + } else { + hourlo = !(hourlo & 1); + hourhi++; + } + } else { + hourlo = !(hourlo & 1); + hourhi = 0; + tickDay(); + } + } + } else { + if(hourhi == 0) { + if(hourlo <= 8 || hourlo == 12) { + hourlo++; + } else { + hourlo = !(hourlo & 1); + hourhi ^= 1; + } + } else { + if(hourlo & 1) meridian ^= 1; + if(hourlo < 2 || hourlo == 4 || hourlo == 5 || hourlo == 8 || hourlo == 12) { + hourlo++; + } else { + hourlo = !(hourlo & 1); + hourhi ^= 1; + } + if(meridian == 0 && !(hourlo & 1)) tickDay(); + } + } +} + +auto EpsonRTC::tickDay() -> void { + if(calendar == 0) return; + weekday = (weekday + 1) + (weekday == 6); + + //January - December = 0x01 - 0x09; 0x10 - 0x12 + static const uint daysinmonth[32] = { + 30, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 30, 31, 30, + 31, 30, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30, + }; + + uint days = daysinmonth[monthhi << 4 | monthlo]; + if(days == 28) { + //add one day for leap years + if((yearhi & 1) == 0 && ((yearlo - 0) & 3) == 0) days++; + if((yearhi & 1) == 1 && ((yearlo - 2) & 3) == 0) days++; + } + + if(days == 28 && (dayhi == 3 || (dayhi == 2 && daylo >= 8))) { + daylo = 1; + dayhi = 0; + return tickMonth(); + } + + if(days == 29 && (dayhi == 3 || (dayhi == 2 && (daylo > 8 && daylo != 12)))) { + daylo = 1; + dayhi = 0; + return tickMonth(); + } + + if(days == 30 && (dayhi == 3 || (dayhi == 2 && (daylo == 10 || daylo == 14)))) { + daylo = 1; + dayhi = 0; + return tickMonth(); + } + + if(days == 31 && (dayhi == 3 && (daylo & 3))) { + daylo = 1; + dayhi = 0; + return tickMonth(); + } + + if(daylo <= 8 || daylo == 12) { + daylo++; + } else { + daylo = !(daylo & 1); + dayhi++; + } +} + +auto EpsonRTC::tickMonth() -> void { + if(monthhi == 0 || !(monthlo & 2)) { + if(monthlo <= 8 || monthlo == 12) { + monthlo++; + } else { + monthlo = !(monthlo & 1); + monthhi ^= 1; + } + } else { + monthlo = !(monthlo & 1); + monthhi = 0; + tickYear(); + } +} + +auto EpsonRTC::tickYear() -> void { + if(yearlo <= 8 || yearlo == 12) { + yearlo++; + } else { + yearlo = !(yearlo & 1); + if(yearhi <= 8 || yearhi == 12) { + yearhi++; + } else { + yearhi = !(yearhi & 1); + } + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/event/event.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/event/event.cpp new file mode 100644 index 0000000000..08c053a3f7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/event/event.cpp @@ -0,0 +1,122 @@ +#include + +namespace SuperFamicom { + +#include "serialization.cpp" +Event event; + +auto Event::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto Event::Enter() -> void { + while(true) { + scheduler.synchronize(); + event.main(); + } +} + +auto Event::main() -> void { + if(scoreActive && scoreSecondsRemaining) { + if(--scoreSecondsRemaining == 0) { + scoreActive = false; + } + } + + if(timerActive && timerSecondsRemaining) { + if(--timerSecondsRemaining == 0) { + timerActive = false; + status |= 0x02; //time over + scoreActive = true; + scoreSecondsRemaining = 5; + } + } + + step(1); + synchronizeCPU(); +} + +auto Event::step(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; +} + +auto Event::unload() -> void { + rom[0].reset(); + rom[1].reset(); + rom[2].reset(); + rom[3].reset(); +} + +auto Event::power() -> void { + create(Event::Enter, 1); + + //DIP switches 0-3 control the time: 3 minutes + 0-15 extra minutes + timer = (3 + (dip.value & 15)) * 60; //in seconds + //DIP switches 4-5 serve an unknown purpose + //DIP switches 6-7 are not connected + + status = 0x00; + select = 0x00; + timerActive = false; + scoreActive = false; + timerSecondsRemaining = 0; + scoreSecondsRemaining = 0; +} + +auto Event::mcuRead(uint addr, uint8 data) -> uint8 { + if(board == Board::CampusChallenge92) { + uint id = 0; + if(select == 0x09) id = 1; + if(select == 0x05) id = 2; + if(select == 0x03) id = 3; + if((addr & 0x808000) == 0x808000) id = 0; + + if(addr & 0x008000) { + addr = ((addr & 0x7f0000) >> 1) | (addr & 0x7fff); + return rom[id].read(bus.mirror(addr, rom[id].size()), data); + } + } + + if(board == Board::PowerFest94) { + uint id = 0; + if(select == 0x09) id = 1; + if(select == 0x0c) id = 2; + if(select == 0x0a) id = 3; + if((addr & 0x208000) == 0x208000) id = 0; + + if(addr & 0x400000) { + addr &= 0x3fffff; + return rom[id].read(bus.mirror(addr, rom[id].size()), data); + } + + if(addr & 0x008000) { + addr &= 0x1fffff; + if(id != 2) addr = ((addr & 0x1f0000) >> 1) | (addr & 0x7fff); + return rom[id].read(bus.mirror(addr, rom[id].size()), data); + } + } + + return data; +} + +auto Event::mcuWrite(uint addr, uint8 data) -> void { +} + +auto Event::read(uint addr, uint8 data) -> uint8 { + if(addr == 0x106000 || addr == 0xc00000) { + return status; + } + return data; +} + +auto Event::write(uint addr, uint8 data) -> void { + if(addr == 0x206000 || addr == 0xe00000) { + select = data; + if(timer && data == 0x09) { + timerActive = true; + timerSecondsRemaining = timer; + } + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/event/event.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/event/event.hpp new file mode 100644 index 0000000000..b2f0de4c60 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/event/event.hpp @@ -0,0 +1,51 @@ +//HLE of the NEC uPD78P214GC processor found on SNES-EVENT PCBs, used by: +//* Campus Challenge '92 +//* PowerFest '94 + +//The NEC uPD78214 family are 8-bit microprocessors containing: +//* UART/CSI serial interface +//* ALU (MUL, DIV, BCD) +//* interrupts (12 internal; 7 external; 2 priority levels) +//* 16384 x 8-bit ROM +//* 512 x 8-bit RAM +//* 4 x timer/counters + +//None of the SNES-EVENT games have had their uPD78214 firmware dumped. +//As such, our only option is very basic high-level emulation, provided here. + +struct Event : Thread { + //event.cpp + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; + auto unload() -> void; + auto power() -> void; + + auto mcuRead(uint addr, uint8) -> uint8; + auto mcuWrite(uint addr, uint8) -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + +public: + ReadableMemory rom[4]; + + enum class Board : uint { Unknown, CampusChallenge92, PowerFest94 } board; + uint timer; + +private: + uint8 status; + uint8 select; + + bool timerActive; + bool scoreActive; + + uint timerSecondsRemaining; + uint scoreSecondsRemaining; +}; + +extern Event event; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/event/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/event/serialization.cpp new file mode 100644 index 0000000000..b315cdc9b1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/event/serialization.cpp @@ -0,0 +1,9 @@ +auto Event::serialize(serializer& s) -> void { + Thread::serialize(s); + s.integer(status); + s.integer(select); + s.integer(timerActive); + s.integer(scoreActive); + s.integer(timerSecondsRemaining); + s.integer(scoreSecondsRemaining); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/data-rom.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/data-rom.cpp new file mode 100644 index 0000000000..6d1d0e38fe --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/data-rom.cpp @@ -0,0 +1,100 @@ +/* note: this is not copyrightable, as it is purely math tables such as sin and cos */ + +const uint8_t HitachiDSP::staticDataROM[3072] = { + 255,255,255,0,0,128,0,0,64,170,170,42,0,0,32,153,153,25,85,85,21,36,73,18,0,0,16,227,56,14,204,204, + 12,232,162,11,170,170,10,157,216,9,146,36,9,136,136,8,0,0,8,135,135,7,113,28,7,161,188,6,102,102,6,97, + 24,6,116,209,5,178,144,5,85,85,5,184,30,5,78,236,4,161,189,4,73,146,4,238,105,4,68,68,4,8,33,4, + 0,0,4,248,224,3,195,195,3,58,168,3,56,142,3,159,117,3,80,94,3,52,72,3,51,51,3,56,31,3,48,12, + 3,11,250,2,186,232,2,45,216,2,89,200,2,49,185,2,170,170,2,188,156,2,92,143,2,130,130,2,39,118,2,67, + 106,2,208,94,2,200,83,2,36,73,2,224,62,2,247,52,2,99,43,2,34,34,2,46,25,2,132,16,2,32,8,2, + 0,0,2,31,248,1,124,240,1,19,233,1,225,225,1,230,218,1,29,212,1,133,205,1,28,199,1,224,192,1,207,186, + 1,232,180,1,40,175,1,142,169,1,26,164,1,200,158,1,153,153,1,139,148,1,156,143,1,203,138,1,24,134,1,129, + 129,1,5,125,1,164,120,1,93,116,1,46,112,1,22,108,1,22,104,1,44,100,1,88,96,1,152,92,1,237,88,1, + 85,85,1,208,81,1,94,78,1,253,74,1,174,71,1,111,68,1,65,65,1,34,62,1,19,59,1,19,56,1,33,53, + 1,62,50,1,104,47,1,159,44,1,228,41,1,53,39,1,146,36,1,251,33,1,112,31,1,240,28,1,123,26,1,17, + 24,1,177,21,1,92,19,1,17,17,1,207,14,1,151,12,1,104,10,1,66,8,1,36,6,1,16,4,1,4,2,1, + 0,0,1,3,254,0,15,252,0,35,250,0,62,248,0,96,246,0,137,244,0,185,242,0,240,240,0,46,239,0,115,237, + 0,189,235,0,14,234,0,101,232,0,194,230,0,37,229,0,142,227,0,252,225,0,112,224,0,233,222,0,103,221,0,235, + 219,0,116,218,0,1,217,0,148,215,0,43,214,0,199,212,0,104,211,0,13,210,0,182,208,0,100,207,0,22,206,0, + 204,204,0,135,203,0,69,202,0,7,201,0,206,199,0,152,198,0,101,197,0,55,196,0,12,195,0,228,193,0,192,192, + 0,160,191,0,130,190,0,105,189,0,82,188,0,62,187,0,46,186,0,33,185,0,23,184,0,15,183,0,11,182,0,9, + 181,0,11,180,0,15,179,0,22,178,0,31,177,0,44,176,0,58,175,0,76,174,0,96,173,0,118,172,0,143,171,0, + 170,170,0,200,169,0,232,168,0,10,168,0,47,167,0,85,166,0,126,165,0,169,164,0,215,163,0,6,163,0,55,162, + 0,107,161,0,160,160,0,216,159,0,17,159,0,76,158,0,137,157,0,200,156,0,9,156,0,76,155,0,144,154,0,215, + 153,0,31,153,0,104,152,0,180,151,0,1,151,0,79,150,0,160,149,0,242,148,0,69,148,0,154,147,0,241,146,0, + 73,146,0,162,145,0,253,144,0,90,144,0,184,143,0,23,143,0,120,142,0,218,141,0,61,141,0,162,140,0,8,140, + 0,112,139,0,216,138,0,66,138,0,174,137,0,26,137,0,136,136,0,247,135,0,103,135,0,217,134,0,75,134,0,191, + 133,0,52,133,0,169,132,0,33,132,0,153,131,0,18,131,0,140,130,0,8,130,0,132,129,0,2,129,0,128,128,0, + 0,0,0,0,0,16,158,160,22,122,182,27,0,0,32,239,198,35,28,49,39,255,84,42,60,65,45,0,0,48,176,152, + 50,229,16,53,245,108,55,86,176,57,212,221,59,189,247,61,0,0,64,61,248,65,219,225,67,12,190,69,222,141,71,58, + 82,73,241,11,75,185,187,76,56,98,78,0,0,80,149,149,81,112,35,83,254,169,84,162,41,86,183,162,87,144,21,89, + 121,130,90,186,233,91,148,75,93,67,168,94,0,0,96,254,82,97,112,161,98,131,235,99,96,49,101,50,115,102,29,177, + 103,68,235,104,202,33,106,205,84,107,108,132,108,194,176,109,235,217,110,0,0,112,24,35,113,74,67,114,173,96,115,84, + 123,116,84,147,117,191,168,118,168,187,119,31,204,120,52,218,121,249,229,122,122,239,123,200,246,124,239,251,125,253,254,126, + 0,0,128,1,255,128,15,252,129,52,247,130,123,240,131,238,231,132,152,221,133,130,209,134,182,195,135,61,180,136,31,163, + 137,102,144,138,25,124,139,65,102,140,228,78,141,11,54,142,188,27,143,0,0,144,219,226,144,86,196,145,117,164,146,65, + 131,147,189,96,148,241,60,149,226,23,150,150,241,150,17,202,151,89,161,152,115,119,153,100,76,154,49,32,155,222,242,155, + 112,196,156,235,148,157,84,100,158,175,50,159,0,0,160,74,204,160,146,151,161,220,97,162,42,43,163,130,243,163,230,186, + 164,90,129,165,225,70,166,126,11,167,53,207,167,9,146,168,253,83,169,19,21,170,80,213,170,180,148,171,69,83,172,3, + 17,173,242,205,173,21,138,174,110,69,175,0,0,176,204,185,176,214,114,177,32,43,178,172,226,178,124,153,179,147,79,180, + 243,4,181,157,185,181,149,109,182,220,32,183,117,211,183,96,133,184,160,54,185,56,231,185,40,151,186,115,70,187,26,245, + 187,32,163,188,134,80,189,78,253,189,121,169,190,9,85,191,0,0,192,95,170,192,40,84,193,92,253,193,253,165,194,13, + 78,195,140,245,195,125,156,196,225,66,197,184,232,197,5,142,198,201,50,199,6,215,199,187,122,200,235,29,201,152,192,201, + 193,98,202,105,4,203,145,165,203,58,70,204,100,230,204,18,134,205,68,37,206,252,195,206,58,98,207,0,0,208,78,157, + 208,38,58,209,137,214,209,119,114,210,243,13,211,252,168,211,148,67,212,188,221,212,116,119,213,190,16,214,155,169,214,11, + 66,215,15,218,215,169,113,216,216,8,217,159,159,217,254,53,218,245,203,218,133,97,219,176,246,219,118,139,220,216,31,221, + 215,179,221,115,71,222,173,218,222,134,109,223,0,0,224,25,146,224,212,35,225,48,181,225,48,70,226,210,214,226,25,103, + 227,4,247,227,148,134,228,203,21,229,168,164,229,45,51,230,90,193,230,47,79,231,173,220,231,214,105,232,169,246,232,38, + 131,233,80,15,234,38,155,234,168,38,235,217,177,235,183,60,236,67,199,236,127,81,237,106,219,237,6,101,238,82,238,238, + 80,119,239,0,0,240,97,136,240,118,16,241,62,152,241,186,31,242,234,166,242,207,45,243,105,180,243,185,58,244,192,192, + 244,125,70,245,242,203,245,30,81,246,2,214,246,159,90,247,245,222,247,5,99,248,206,230,248,82,106,249,144,237,249,138, + 112,250,63,243,250,177,117,251,223,247,251,202,121,252,114,251,252,216,124,253,251,253,253,222,126,254,127,255,254,223,127,255, + 0,0,0,58,36,3,85,72,6,50,108,9,178,143,12,183,178,15,32,213,18,208,246,21,166,23,25,133,55,28,78,86, + 31,225,115,34,32,144,37,237,170,40,40,196,43,179,219,46,112,241,49,64,5,53,4,23,56,159,38,59,242,51,62,224, + 62,65,73,71,68,16,77,71,24,80,74,67,80,77,114,77,80,137,71,83,105,62,86,247,49,89,20,34,92,164,14,95, + 138,247,97,169,220,100,229,189,103,32,155,106,64,116,109,39,73,112,186,25,115,221,229,117,116,173,120,101,112,123,147,46, + 126,228,231,128,60,156,131,130,75,134,154,245,136,107,154,139,217,57,142,204,211,144,42,104,147,217,246,149,191,127,152,197, + 2,155,209,127,157,202,246,159,153,103,162,36,210,164,85,54,167,20,148,169,73,235,171,221,59,174,186,133,176,201,200,178, + 243,4,181,34,58,183,65,104,185,58,143,187,249,174,189,103,199,191,112,216,193,0,226,195,3,228,197,101,222,199,18,209, + 201,247,187,203,2,159,205,31,122,207,61,77,209,72,24,211,49,219,212,228,149,214,82,72,216,105,242,217,26,148,219,83, + 45,221,5,190,222,33,70,224,151,197,225,89,60,227,89,170,228,135,15,230,215,107,231,59,191,232,166,9,234,11,75,235, + 94,131,236,147,178,237,157,216,238,115,245,239,8,9,241,82,19,242,71,20,243,221,11,244,10,250,244,198,222,245,7,186, + 246,197,139,247,247,83,248,151,18,249,157,199,249,1,115,250,190,20,251,205,172,251,39,59,252,201,191,252,171,58,253,203, + 171,253,35,19,254,175,112,254,109,196,254,87,14,255,109,78,255,171,132,255,15,177,255,151,211,255,67,236,255,16,251,255, + 0,0,0,249,162,0,246,69,1,248,232,1,1,140,2,20,47,3,52,210,3,100,117,4,165,24,5,251,187,5,104,95, + 6,239,2,7,146,166,7,84,74,8,56,238,8,64,146,9,110,54,10,199,218,10,76,127,11,1,36,12,231,200,12,2, + 110,13,85,19,14,227,184,14,174,94,15,185,4,16,8,171,16,158,81,17,125,248,17,169,159,18,37,71,19,244,238,19, + 25,151,20,153,63,21,117,232,21,178,145,22,83,59,23,92,229,23,209,143,24,180,58,25,10,230,25,216,145,26,32,62, + 27,231,234,27,49,152,28,2,70,29,95,244,29,76,163,30,206,82,31,234,2,32,163,179,32,0,101,33,5,23,34,184, + 201,34,30,125,35,60,49,36,24,230,36,185,155,37,36,82,38,95,9,39,113,193,39,97,122,40,54,52,41,246,238,41, + 170,170,42,89,103,43,10,37,44,199,227,44,152,163,45,133,100,46,153,38,47,220,233,47,89,174,48,27,116,49,44,59, + 50,152,3,51,107,205,51,177,152,52,120,101,53,206,51,54,193,3,55,96,213,55,187,168,56,228,125,57,236,84,58,230, + 45,59,230,8,60,1,230,60,77,197,61,227,166,62,220,138,63,82,113,64,98,90,65,44,70,66,208,52,67,113,38,68, + 55,27,69,74,19,70,214,14,71,12,14,72,32,17,73,76,24,74,205,35,75,234,51,76,236,72,77,39,99,78,249,130, + 79,201,168,80,10,213,81,63,8,83,252,66,84,234,133,85,204,209,86,130,39,88,21,136,89,188,244,90,237,110,92,108, + 248,93,105,147,95,163,66,97,165,9,99,30,237,100,129,243,102,23,38,105,34,147,107,165,82,110,124,147,113,180,206,117, + 0,0,0,36,3,0,72,6,0,109,9,0,147,12,0,186,15,0,226,18,0,11,22,0,54,25,0,99,28,0,147,31, + 0,196,34,0,249,37,0,48,41,0,107,44,0,169,47,0,235,50,0,50,54,0,124,57,0,203,60,0,31,64,0,121, + 67,0,216,70,0,61,74,0,168,77,0,25,81,0,146,84,0,17,88,0,153,91,0,40,95,0,192,98,0,96,102,0, + 9,106,0,188,109,0,122,113,0,65,117,0,20,121,0,242,124,0,220,128,0,210,132,0,213,136,0,230,140,0,5,145, + 0,51,149,0,112,153,0,190,157,0,28,162,0,139,166,0,13,171,0,162,175,0,75,180,0,9,185,0,220,189,0,198, + 194,0,200,199,0,227,204,0,24,210,0,103,215,0,211,220,0,93,226,0,6,232,0,207,237,0,187,243,0,202,249,0, + 0,0,1,92,6,1,226,12,1,148,19,1,115,26,1,131,33,1,198,40,1,62,48,1,239,55,1,220,63,1,8,72, + 1,119,80,1,45,89,1,45,98,1,125,107,1,34,117,1,33,127,1,128,137,1,68,148,1,118,159,1,28,171,1,62, + 183,1,231,195,1,31,209,1,241,222,1,105,237,1,149,252,1,131,12,2,68,29,2,233,46,2,134,65,2,51,85,2, + 9,106,2,37,128,2,167,151,2,181,176,2,120,203,2,35,232,2,236,6,3,21,40,3,235,75,3,198,114,3,16,157, + 3,71,203,3,2,254,3,247,53,4,5,116,4,63,185,4,255,6,5,249,94,5,93,195,5,9,55,6,207,189,6,230, + 92,7,151,27,8,109,4,9,54,39,10,198,156,11,129,142,13,233,70,16,255,90,20,113,38,27,72,188,40,181,123,81, + 255,255,255,16,251,255,67,236,255,151,211,255,15,177,255,171,132,255,109,78,255,87,14,255,109,196,254,175,112,254,35,19, + 254,203,171,253,171,58,253,201,191,252,39,59,252,205,172,251,190,20,251,1,115,250,157,199,249,151,18,249,247,83,248,197, + 139,247,7,186,246,198,222,245,10,250,244,221,11,244,71,20,243,82,19,242,8,9,241,115,245,239,157,216,238,147,178,237, + 94,131,236,11,75,235,166,9,234,59,191,232,215,107,231,135,15,230,89,170,228,89,60,227,151,197,225,33,70,224,5,190, + 222,83,45,221,26,148,219,105,242,217,82,72,216,228,149,214,49,219,212,72,24,211,61,77,209,31,122,207,2,159,205,247, + 187,203,18,209,201,101,222,199,3,228,197,0,226,195,112,216,193,103,199,191,249,174,189,58,143,187,65,104,185,34,58,183, + 243,4,181,201,200,178,186,133,176,221,59,174,73,235,171,20,148,169,85,54,167,36,210,164,153,103,162,202,246,159,209,127, + 157,197,2,155,191,127,152,217,246,149,42,104,147,204,211,144,217,57,142,107,154,139,154,245,136,130,75,134,60,156,131,228, + 231,128,147,46,126,101,112,123,116,173,120,221,229,117,186,25,115,39,73,112,64,116,109,32,155,106,229,189,103,169,220,100, + 138,247,97,164,14,95,20,34,92,247,49,89,105,62,86,137,71,83,114,77,80,67,80,77,24,80,74,16,77,71,73,71, + 68,224,62,65,242,51,62,159,38,59,4,23,56,64,5,53,112,241,49,179,219,46,40,196,43,237,170,40,32,144,37,225, + 115,34,78,86,31,133,55,28,166,23,25,208,246,21,32,213,18,183,178,15,178,143,12,50,108,9,85,72,6,58,36,3, +}; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.cpp new file mode 100644 index 0000000000..c4d2a513d6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.cpp @@ -0,0 +1,43 @@ +#include +#include + +namespace SuperFamicom { + +#include "memory.cpp" +#include "serialization.cpp" +#include "data-rom.cpp" +HitachiDSP hitachidsp; + +auto HitachiDSP::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto HitachiDSP::Enter() -> void { + while(true) { + scheduler.synchronize(); + hitachidsp.main(); + } +} + +auto HitachiDSP::step(uint clocks) -> void { + HG51B::step(clocks); + clock += clocks * (uint64_t)cpu.frequency; + synchronizeCPU(); +} + +auto HitachiDSP::halt() -> void { + HG51B::halt(); + if(io.irq == 0) cpu.irq(r.i = 1); +} + +auto HitachiDSP::unload() -> void { + rom.reset(); + ram.reset(); +} + +auto HitachiDSP::power() -> void { + HG51B::power(); + create(HitachiDSP::Enter, Frequency); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.hpp new file mode 100644 index 0000000000..632aca3ee9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.hpp @@ -0,0 +1,52 @@ +struct HitachiDSP : Processor::HG51B, Thread { + ReadableMemory rom; + WritableMemory ram; + + //hitachidsp.cpp + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto step(uint clocks) -> void override; + auto halt() -> void override; + + auto unload() -> void; + auto power() -> void; + + auto isROM(uint address) -> bool override; + auto isRAM(uint address) -> bool override; + + //HG51B read/write + auto read(uint address) -> uint8 override; + auto write(uint address, uint8 data) -> void override; + + //CPU ROM read/write + auto addressROM(uint address) const -> maybe; + auto readROM(uint address, uint8 data = 0) -> uint8; + auto writeROM(uint address, uint8 data) -> void; + + //CPU RAM read/write + auto addressRAM(uint address) const -> maybe; + auto readRAM(uint address, uint8 data = 0) -> uint8; + auto writeRAM(uint address, uint8 data) -> void; + + //HG51B data RAM read/write + auto addressDRAM(uint address) const -> maybe; + auto readDRAM(uint address, uint8 data = 0) -> uint8; + auto writeDRAM(uint address, uint8 data) -> void; + + //CPU IO read/write + auto addressIO(uint address) const -> maybe; + auto readIO(uint address, uint8 data = 0) -> uint8; + auto writeIO(uint address, uint8 data) -> void; + + auto firmware() const -> vector; + auto serialize(serializer&) -> void; + + uint Frequency; + uint Roms; + bool Mapping; + + //data-rom.cpp + static const uint8_t staticDataROM[3072]; +}; + +extern HitachiDSP hitachidsp; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/memory.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/memory.cpp new file mode 100644 index 0000000000..c7663c2f7b --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/memory.cpp @@ -0,0 +1,273 @@ +auto HitachiDSP::isROM(uint address) -> bool { + return (bool)addressROM(address); +} + +auto HitachiDSP::isRAM(uint address) -> bool { + return (bool)addressRAM(address); +} + +auto HitachiDSP::read(uint address) -> uint8 { + if(auto linear = addressROM (address)) return readROM (*linear); + if(auto linear = addressRAM (address)) return readRAM (*linear); + if(auto linear = addressDRAM(address)) return readDRAM(*linear); + if(auto linear = addressIO (address)) return readIO (*linear); + return 0x00; +} + +auto HitachiDSP::write(uint address, uint8 data) -> void { + if(auto linear = addressROM (address)) return writeROM (*linear, data); + if(auto linear = addressRAM (address)) return writeRAM (*linear, data); + if(auto linear = addressDRAM(address)) return writeDRAM(*linear, data); + if(auto linear = addressIO (address)) return writeIO (*linear, data); +} + +// + +auto HitachiDSP::addressROM(uint address) const -> maybe { + if(Mapping == 0) { + //00-3f,80-bf:8000-ffff; c0-ff:0000-ffff + if((address & 0x408000) == 0x008000 || (address & 0xc00000) == 0xc00000) { + address = (address & 0x3f0000) >> 1 | (address & 0x7fff); + return {address & 0x1fffff}; + } + } else { + //00-3f,80-bf:8000-ffff; c0-ff:0000-ffff + if((address & 0x408000) == 0x008000 || (address & 0xc00000) == 0xc00000) { + return {address & 0x3fffff}; + } + } + return {}; +} + +auto HitachiDSP::readROM(uint address, uint8 data) -> uint8 { + if(hitachidsp.active() || !busy()) { + address = bus.mirror(address, rom.size()); + //if(Roms == 2 && mmio.r1f52 == 1 && address >= (bit::round(rom.size()) >> 1)) return 0x00; + return rom.read(address, data); + } + //DSP has the bus acquired: CPU reads from 00:ffc0-ffff return IO registers (including reset vector overrides) + if(Mapping == 0 && (address & 0xbfffc0) == 0x007fc0) return readIO(0x7f40 | address & 0x3f); + if(Mapping == 1 && (address & 0xbfffc0) == 0x00ffc0) return readIO(0x7f40 | address & 0x3f); + return data; +} + +auto HitachiDSP::writeROM(uint address, uint8 data) -> void { +} + +// + +auto HitachiDSP::addressRAM(uint address) const -> maybe { + if(Mapping == 0) { + //70-77:0000-7fff + if((address & 0xf88000) == 0x700000) { + address = (address & 0x070000) >> 1 | (address & 0x7fff); + return {address & 0x03ffff}; + } + } else { + //30-3f,b0-bf:6000-7fff + if((address & 0x70e000) == 0x306000) { + address = (address & 0x0f0000) >> 3 | (address & 0x1fff); + return {address & 0x01ffff}; + } + } + return {}; +} + +auto HitachiDSP::readRAM(uint address, uint8 data) -> uint8 { + if(ram.size() == 0) return 0x00; //not open bus + return ram.read(bus.mirror(address, ram.size()), data); +} + +auto HitachiDSP::writeRAM(uint address, uint8 data) -> void { + if(ram.size() == 0) return; + return ram.write(bus.mirror(address, ram.size()), data); +} + +// + +auto HitachiDSP::addressDRAM(uint address) const -> maybe { + if(Mapping == 0) { + //00-3f,80-bf:6000-6bff,7000-7bff + if((address & 0x40e000) == 0x006000 && (address & 0x0c00) != 0x0c00) { + return {address & 0x0fff}; + } + } else { + //00-2f,80-af:6000-6bff,7000-7bff + if((address & 0x40e000) == 0x006000 && (address & 0x0c00) != 0x0c00 && (address & 0x300000) != 0x300000) { + return {address & 0x0fff}; + } + } + return {}; +} + +auto HitachiDSP::readDRAM(uint address, uint8 data) -> uint8 { + address &= 0xfff; + if(address >= 0xc00) return data; + return dataRAM[address]; +} + +auto HitachiDSP::writeDRAM(uint address, uint8 data) -> void { + address &= 0xfff; + if(address >= 0xc00) return; + dataRAM[address] = data; +} + +// + +auto HitachiDSP::addressIO(uint address) const -> maybe { + if(Mapping == 0) { + //00-3f,80-bf:6c00-6fff,7c00-7fff + if((address & 0x40ec00) == 0x006c00) { + return {address & 0x03ff}; + } + } else { + //00-2f,80-af:6c00-6fff,7c00-7fff + if((address & 0x40ec00) == 0x006c00 && (address & 0x300000) != 0x300000) { + return {address & 0x03ff}; + } + } + return {}; +} + +auto HitachiDSP::readIO(uint address, uint8 data) -> uint8 { + address = 0x7c00 | (address & 0x03ff); + + //IO + switch(address) { + case 0x7f40: return io.dma.source >> 0; + case 0x7f41: return io.dma.source >> 8; + case 0x7f42: return io.dma.source >> 16; + case 0x7f43: return io.dma.length >> 0; + case 0x7f44: return io.dma.length >> 8; + case 0x7f45: return io.dma.target >> 0; + case 0x7f46: return io.dma.target >> 8; + case 0x7f47: return io.dma.target >> 16; + case 0x7f48: return io.cache.page; + case 0x7f49: return io.cache.base >> 0; + case 0x7f4a: return io.cache.base >> 8; + case 0x7f4b: return io.cache.base >> 16; + case 0x7f4c: return io.cache.lock[0] << 0 | io.cache.lock[1] << 1; + case 0x7f4d: return io.cache.pb >> 0; + case 0x7f4e: return io.cache.pb >> 8; + case 0x7f4f: return io.cache.pc; + case 0x7f50: return io.wait.ram << 0 | io.wait.rom << 4; + case 0x7f51: return io.irq; + case 0x7f52: return io.rom; + case 0x7f53: case 0x7f54: case 0x7f55: case 0x7f56: case 0x7f57: + case 0x7f59: case 0x7f5b: case 0x7f5c: case 0x7f5d: case 0x7f5e: + case 0x7f5f: return io.suspend.enable << 0 | r.i << 1 | running() << 6 | busy() << 7; + } + + //vectors + if(address >= 0x7f60 && address <= 0x7f7f) { + return io.vector[address & 0x1f]; + } + + //registers + if((address >= 0x7f80 && address <= 0x7faf) || (address >= 0x7fc0 && address <= 0x7fef)) { + address &= 0x3f; + switch(address % 3) { + case 0: return r.gpr[address / 3] >> 0; + case 1: return r.gpr[address / 3] >> 8; + case 2: return r.gpr[address / 3] >> 16; + } + } + + return 0x00; +} + +auto HitachiDSP::writeIO(uint address, uint8 data) -> void { + address = 0x7c00 | (address & 0x03ff); + + //IO + switch(address) { + case 0x7f40: io.dma.source = io.dma.source & 0xffff00 | data << 0; return; + case 0x7f41: io.dma.source = io.dma.source & 0xff00ff | data << 8; return; + case 0x7f42: io.dma.source = io.dma.source & 0x00ffff | data << 16; return; + + case 0x7f43: io.dma.length = io.dma.length & 0xff00 | data << 0; return; + case 0x7f44: io.dma.length = io.dma.length & 0x00ff | data << 8; return; + + case 0x7f45: io.dma.target = io.dma.target & 0xffff00 | data << 0; return; + case 0x7f46: io.dma.target = io.dma.target & 0xff00ff | data << 8; return; + case 0x7f47: io.dma.target = io.dma.target & 0x00ffff | data << 16; + if(io.halt) io.dma.enable = 1; + return; + + case 0x7f48: + io.cache.page = data & 1; + if(io.halt) io.cache.enable = 1; + return; + + case 0x7f49: io.cache.base = io.cache.base & 0xffff00 | data << 0; return; + case 0x7f4a: io.cache.base = io.cache.base & 0xff00ff | data << 8; return; + case 0x7f4b: io.cache.base = io.cache.base & 0x00ffff | data << 16; return; + + case 0x7f4c: + io.cache.lock[0] = bool(data & 1); + io.cache.lock[1] = bool(data & 2); + return; + + case 0x7f4d: io.cache.pb = io.cache.pb & 0xff00 | data << 0; return; + case 0x7f4e: io.cache.pb = io.cache.pb & 0x00ff | data << 8; return; + + case 0x7f4f: + io.cache.pc = data; + if(io.halt) { + io.halt = 0; + r.pb = io.cache.pb; + r.pc = io.cache.pc; + } + return; + + case 0x7f50: + io.wait.ram = data >> 0 & 7; + io.wait.rom = data >> 4 & 7; + return; + + case 0x7f51: + io.irq = data & 1; + if(io.irq == 1) cpu.irq(r.i = 0); + return; + + case 0x7f52: + io.rom = data & 1; + return; + + case 0x7f53: + io.lock = 0; + io.halt = 1; + return; + + case 0x7f55: io.suspend.enable = 1; io.suspend.duration = 0; return; //indefinite + case 0x7f56: io.suspend.enable = 1; io.suspend.duration = 32; return; + case 0x7f57: io.suspend.enable = 1; io.suspend.duration = 64; return; + case 0x7f58: io.suspend.enable = 1; io.suspend.duration = 96; return; + case 0x7f59: io.suspend.enable = 1; io.suspend.duration = 128; return; + case 0x7f5a: io.suspend.enable = 1; io.suspend.duration = 160; return; + case 0x7f5b: io.suspend.enable = 1; io.suspend.duration = 192; return; + case 0x7f5c: io.suspend.enable = 1; io.suspend.duration = 224; return; + case 0x7f5d: io.suspend.enable = 0; return; //resume + + case 0x7f5e: + r.i = 0; //does not deassert CPU IRQ line + return; + } + + //vectors + if(address >= 0x7f60 && address <= 0x7f7f) { + io.vector[address & 0x1f] = data; + return; + } + + //registers + if((address >= 0x7f80 && address <= 0x7faf) || (address >= 0x7fc0 && address <= 0x7fef)) { + address &= 0x3f; + switch(address % 3) { + case 0: r.gpr[address / 3] = r.gpr[address / 3] & 0xffff00 | data << 0; break; + case 1: r.gpr[address / 3] = r.gpr[address / 3] & 0xff00ff | data << 8; break; + case 2: r.gpr[address / 3] = r.gpr[address / 3] & 0x00ffff | data << 16; break; + } + return; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/serialization.cpp new file mode 100644 index 0000000000..b4b8521354 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/hitachidsp/serialization.cpp @@ -0,0 +1,16 @@ +auto HitachiDSP::firmware() const -> vector { + vector buffer; + if(!cartridge.has.HitachiDSP) return buffer; + buffer.reserve(1024 * 3); + for(auto n : range(1024)) { + buffer.append(dataROM[n] >> 0); + buffer.append(dataROM[n] >> 8); + buffer.append(dataROM[n] >> 16); + } + return buffer; +} + +auto HitachiDSP::serialize(serializer& s) -> void { + HG51B::serialize(s); + Thread::serialize(s); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/boot-roms.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/boot-roms.cpp new file mode 100644 index 0000000000..1794c587ae --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/boot-roms.cpp @@ -0,0 +1,21 @@ +const uint8_t ICD::SGB1BootROM[256] = { + 49,254,255,62,48,224,0,175,33,255,159,50,203,124,32,251,33,38,255,14,17,62,128,50,226,12,62,243,226,50,62,119, + 119,62,252,224,71,33,95,192,14,8,175,50,13,32,252,17,79,1,62,251,14,6,245,6,0,26,27,50,128,71,13,32, + 248,50,241,50,14,14,214,2,254,239,32,234,17,4,1,33,16,128,26,205,211,0,205,212,0,19,123,254,52,32,243,17, + 230,0,6,8,26,19,34,35,5,32,249,62,25,234,16,153,33,47,153,14,12,61,40,8,50,13,32,249,46,15,24,243, + 62,145,224,64,33,0,192,14,0,62,0,226,62,48,226,6,16,30,8,42,87,203,66,62,16,32,2,62,32,226,62,48, + 226,203,26,29,32,239,5,32,232,62,32,226,62,48,226,205,194,0,125,254,96,32,210,14,19,62,193,226,12,62,7,226, + 24,58,22,4,240,68,254,144,32,250,30,0,29,32,253,21,32,242,201,79,6,4,197,203,17,23,193,203,17,23,5,32, + 245,34,35,34,35,201,60,66,185,165,185,165,66,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62,1,224,80, +}; + +const uint8_t ICD::SGB2BootROM[256] = { + 49,254,255,62,48,224,0,175,33,255,159,50,203,124,32,251,33,38,255,14,17,62,128,50,226,12,62,243,226,50,62,119, + 119,62,252,224,71,33,95,192,14,8,175,50,13,32,252,17,79,1,62,251,14,6,245,6,0,26,27,50,128,71,13,32, + 248,50,241,50,14,14,214,2,254,239,32,234,17,4,1,33,16,128,26,205,211,0,205,212,0,19,123,254,52,32,243,17, + 230,0,6,8,26,19,34,35,5,32,249,62,25,234,16,153,33,47,153,14,12,61,40,8,50,13,32,249,46,15,24,243, + 62,145,224,64,33,0,192,14,0,62,0,226,62,48,226,6,16,30,8,42,87,203,66,62,16,32,2,62,32,226,62,48, + 226,203,26,29,32,239,5,32,232,62,32,226,62,48,226,205,194,0,125,254,96,32,210,14,19,62,193,226,12,62,7,226, + 24,58,22,4,240,68,254,144,32,250,30,0,29,32,253,21,32,242,201,79,6,4,197,203,17,23,193,203,17,23,5,32, + 245,34,35,34,35,201,60,66,185,165,185,165,66,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62,255,224,80, +}; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.cpp new file mode 100644 index 0000000000..1b5199c43a --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.cpp @@ -0,0 +1,181 @@ +#include + +namespace SuperFamicom { + +ICD icd; +#include "interface.cpp" +#include "io.cpp" +#include "boot-roms.cpp" +#include "serialization.cpp" + +namespace SameBoy { + static auto hreset(GB_gameboy_t*) -> void { + icd.ppuHreset(); + } + + static auto vreset(GB_gameboy_t*) -> void { + icd.ppuVreset(); + } + + static auto icd_pixel(GB_gameboy_t*, uint8_t pixel) -> void { + icd.ppuWrite(pixel); + } + + static auto joyp_write(GB_gameboy_t*, uint8_t value) -> void { + bool p14 = value & 0x10; + bool p15 = value & 0x20; + icd.joypWrite(p14, p15); + } + + static auto read_memory(GB_gameboy_t*, uint16_t addr, uint8_t data) -> uint8_t { + if(auto replace = icd.cheats.find(addr, data)) return replace(); + return data; + } + + static auto rgb_encode(GB_gameboy_t*, uint8_t r, uint8_t g, uint8_t b) -> uint32_t { + return r << 16 | g << 8 | b << 0; + } + + static auto sample(GB_gameboy_t*, GB_sample_t* sample) -> void { + float left = sample->left / 32768.0f; + float right = sample->right / 32768.0f; + icd.apuWrite(left, right); + } + + static auto vblank(GB_gameboy_t*) -> void { + } +} + +auto ICD::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto ICD::Enter() -> void { + while(true) { + scheduler.synchronize(); + icd.main(); + } +} + +auto ICD::main() -> void { + if(r6003 & 0x80) { + auto clocks = GB_run(&sameboy); + step(clocks >> 1); + } else { //DMG halted + apuWrite(0.0, 0.0); + step(128); + } + synchronizeCPU(); +} + +auto ICD::step(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; +} + +//SGB1 uses the CPU oscillator (~2.4% faster than a real Game Boy) +//SGB2 uses a dedicated oscillator (same speed as a real Game Boy) +auto ICD::clockFrequency() const -> uint { + return Frequency ? Frequency : system.cpuFrequency(); +} + +auto ICD::load() -> bool { + information = {}; + + GB_random_set_enabled(configuration.hacks.entropy != "None"); + if(Frequency == 0) { + GB_init(&sameboy, GB_MODEL_SGB_NO_SFC); + GB_load_boot_rom_from_buffer(&sameboy, (const unsigned char*)&SGB1BootROM[0], 256); + } else { + GB_init(&sameboy, GB_MODEL_SGB2_NO_SFC); + GB_load_boot_rom_from_buffer(&sameboy, (const unsigned char*)&SGB2BootROM[0], 256); + } + GB_set_sample_rate_by_clocks(&sameboy, 256); + GB_set_highpass_filter_mode(&sameboy, GB_HIGHPASS_ACCURATE); + GB_set_icd_hreset_callback(&sameboy, &SameBoy::hreset); + GB_set_icd_vreset_callback(&sameboy, &SameBoy::vreset); + GB_set_icd_pixel_callback(&sameboy, &SameBoy::icd_pixel); + GB_set_joyp_write_callback(&sameboy, &SameBoy::joyp_write); + GB_set_read_memory_callback(&sameboy, &SameBoy::read_memory); + GB_set_rgb_encode_callback(&sameboy, &SameBoy::rgb_encode); + GB_apu_set_sample_callback(&sameboy, &SameBoy::sample); + GB_set_vblank_callback(&sameboy, &SameBoy::vblank); + GB_set_pixels_output(&sameboy, &bitmap[0]); + if(auto loaded = platform->load(ID::GameBoy, "Game Boy", "gb")) { + information.pathID = loaded.pathID; + } else return unload(), false; + if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) { + auto manifest = fp->reads(); + cartridge.slotGameBoy.load(manifest); + } else return unload(), false; + if(auto fp = platform->open(pathID(), "program.rom", File::Read, File::Required)) { + auto size = fp->size(); + auto data = (uint8_t*)malloc(size); + cartridge.information.sha256 = Hash::SHA256({data, (uint64_t)size}).digest(); + fp->read(data, size); + GB_load_rom_from_buffer(&sameboy, data, size); + free(data); + } else return unload(), false; + if(auto fp = platform->open(pathID(), "save.ram", File::Read)) { + auto size = fp->size(); + auto data = (uint8_t*)malloc(size); + fp->read(data, size); + GB_load_battery_from_buffer(&sameboy, data, size); + free(data); + } + return true; +} + +auto ICD::save() -> void { + if(auto size = GB_save_battery_size(&sameboy)) { + auto data = (uint8_t*)malloc(size); + GB_save_battery_to_buffer(&sameboy, data, size); + if(auto fp = platform->open(pathID(), "save.ram", File::Write)) { + fp->write(data, size); + } + free(data); + } +} + +auto ICD::unload() -> void { + save(); + GB_free(&sameboy); +} + +auto ICD::power(bool reset) -> void { + auto frequency = clockFrequency() / 5; + create(ICD::Enter, frequency); + if(!reset) stream = Emulator::audio.createStream(2, frequency / 128); + + for(auto& packet : this->packet) packet = {}; + packetSize = 0; + + joypID = 0; + joypLock = 1; + pulseLock = 1; + strobeLock = 0; + packetLock = 0; + joypPacket = {}; + packetOffset = 0; + bitData = 0; + bitOffset = 0; + + for(auto& n : output) n = 0xff; + readBank = 0; + readAddress = 0; + writeBank = 0; + + r6003 = 0x00; + r6004 = 0xff; + r6005 = 0xff; + r6006 = 0xff; + r6007 = 0xff; + for(auto& r : r7000) r = 0x00; + mltReq = 0; + + hcounter = 0; + vcounter = 0; + + GB_reset(&sameboy); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.hpp new file mode 100644 index 0000000000..baef0ec6da --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.hpp @@ -0,0 +1,85 @@ +struct ICD : Emulator::Platform, Thread { + shared_pointer stream; + Emulator::Cheat cheats; + + inline auto pathID() const -> uint { return information.pathID; } + + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; + auto clockFrequency() const -> uint; + + auto load() -> bool; + auto save() -> void; + auto unload() -> void; + auto power(bool reset = false) -> void; + + //interface.cpp + auto ppuHreset() -> void; + auto ppuVreset() -> void; + auto ppuWrite(uint2 color) -> void; + auto apuWrite(float left, float right) -> void; + auto joypWrite(bool p14, bool p15) -> void; + + //io.cpp + auto readIO(uint addr, uint8 data) -> uint8; + auto writeIO(uint addr, uint8 data) -> void; + + //boot-roms.cpp + static const uint8_t SGB1BootROM[256]; + static const uint8_t SGB2BootROM[256]; + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint Revision = 0; + uint Frequency = 0; + +private: + struct Packet { + auto operator[](uint4 address) -> uint8& { return data[address]; } + uint8 data[16]; + }; + Packet packet[64]; + uint7 packetSize; + + uint2 joypID; + uint1 joypLock; + uint1 pulseLock; + uint1 strobeLock; + uint1 packetLock; + Packet joypPacket; + uint4 packetOffset; + uint8 bitData; + uint3 bitOffset; + + uint8 output[4 * 512]; + uint2 readBank; + uint9 readAddress; + uint2 writeBank; + + uint8 r6003; //control port + uint8 r6004; //joypad 1 + uint8 r6005; //joypad 2 + uint8 r6006; //joypad 3 + uint8 r6007; //joypad 4 + uint8 r7000[16]; //JOYP packet data + uint8 mltReq; //number of active joypads + + uint8 hcounter; + uint8 vcounter; + + struct Information { + uint pathID = 0; + } information; + +public: + //warning: the size of this object will be too large due to C++ size rules differing from C rules. + //in practice, this won't pose a problem so long as the struct is never accessed from C++ code, + //as the offsets of all member variables will be wrong compared to what the C SameBoy code expects. + GB_gameboy_t sameboy; + uint32_t bitmap[160 * 144]; +}; + +extern ICD icd; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/interface.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/interface.cpp new file mode 100644 index 0000000000..67cd411b87 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/interface.cpp @@ -0,0 +1,105 @@ +auto ICD::ppuHreset() -> void { + hcounter = 0; + vcounter++; + if((uint3)vcounter == 0) writeBank++; +} + +auto ICD::ppuVreset() -> void { + hcounter = 0; + vcounter = 0; +} + +auto ICD::ppuWrite(uint2 color) -> void { + auto x = (uint8)hcounter++; + auto y = (uint3)vcounter; + if(x >= 160) return; //unverified behavior + + uint11 address = writeBank * 512 + y * 2 + x / 8 * 16; + output[address + 0] = (output[address + 0] << 1) | !!(color & 1); + output[address + 1] = (output[address + 1] << 1) | !!(color & 2); +} + +auto ICD::apuWrite(float left, float right) -> void { + double samples[] = {left, right}; + if(!system.runAhead && system.renderAudio) stream->write(samples); +} + +auto ICD::joypWrite(bool p14, bool p15) -> void { + //joypad handling + if(p14 == 1 && p15 == 1) { + if(joypLock == 0) { + joypLock = 1; + joypID++; + if(mltReq == 0) joypID &= 0; //1-player mode + if(mltReq == 1) joypID &= 1; //2-player mode + if(mltReq == 2) joypID &= 3; //4-player mode (unverified; but the most likely behavior) + if(mltReq == 3) joypID &= 3; //4-player mode + } + } + + uint8 joypad; + if(joypID == 0) joypad = r6004; + if(joypID == 1) joypad = r6005; + if(joypID == 2) joypad = r6006; + if(joypID == 3) joypad = r6007; + + uint4 input = 0xf; + if(p14 == 1 && p15 == 1) input = 0xf - joypID; + if(p14 == 0) input &= (joypad >> 0 & 15); //d-pad + if(p15 == 0) input &= (joypad >> 4 & 15); //buttons + + GB_icd_set_joyp(&sameboy, input); + + if(p14 == 0 && p15 == 1); + if(p14 == 1 && p15 == 0) joypLock ^= 1; + + //packet handling + if(p14 == 0 && p15 == 0) { //pulse + pulseLock = 0; + packetOffset = 0; + bitOffset = 0; + strobeLock = 1; + packetLock = 0; + return; + } + + if(pulseLock == 1) return; + + if(p14 == 1 && p15 == 1) { + strobeLock = 0; + return; + } + + if(strobeLock == 1) { + if(p14 == 1 || p15 == 1) { //malformed packet + packetLock = 0; + pulseLock = 1; + bitOffset = 0; + packetOffset = 0; + } else { + return; + } + } + + //p14:0, p15:1 = 0 + //p14:1, p15:0 = 1 + bool bit = p15 == 0; + strobeLock = 1; + + if(packetLock == 1) { + if(p14 == 0 && p15 == 1) { + if(packetSize < 64) packet[packetSize++] = joypPacket; + packetLock = 0; + pulseLock = 1; + } + return; + } + + bitData = bit << 7 | bitData >> 1; + if(++bitOffset) return; + + joypPacket[packetOffset] = bitData; + if(++packetOffset) return; + + packetLock = 1; +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/io.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/io.cpp new file mode 100644 index 0000000000..1f32b2a956 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/io.cpp @@ -0,0 +1,81 @@ +auto ICD::readIO(uint addr, uint8 data) -> uint8 { + addr &= 0x40ffff; + + //LY counter + if(addr == 0x6000) { + return vcounter & ~7 | writeBank; + } + + //command ready port + if(addr == 0x6002) { + data = packetSize > 0; + if(data) { + for(auto n : range(16)) r7000[n] = packet[0][n]; + packetSize--; + for(auto n : range(packetSize)) packet[n] = packet[n + 1]; + } + return data; + } + + //ICD2 revision + if(addr == 0x600f) { + return 0x21; + } + + //command port + if((addr & 0x40fff0) == 0x7000) { + return r7000[addr & 15]; + } + + //VRAM port + if(addr == 0x7800) { + data = output[readBank * 512 + readAddress]; + readAddress = (readAddress + 1) & 511; + return data; + } + + return 0x00; +} + +auto ICD::writeIO(uint addr, uint8 data) -> void { + addr &= 0xffff; + + //VRAM port + if(addr == 0x6001) { + readBank = data & 3; + readAddress = 0; + return; + } + + //control port + //d7: 0 = halt, 1 = reset + //d5,d4: 0 = 1-player, 1 = 2-player, 2 = 4-player, 3 = ??? + //d1,d0: 0 = frequency divider (clock rate adjust) + if(addr == 0x6003) { + if((r6003 & 0x80) == 0x00 && (data & 0x80) == 0x80) { + power(true); //soft reset + } + + mltReq = data >> 4 & 3; + if(mltReq == 0) joypID &= ~0; //1-player mode + if(mltReq == 1) joypID &= ~1; //2-player mode + if(mltReq == 2) joypID &= ~3; //4-player mode (unverified; but the most likely behavior) + if(mltReq == 3) joypID &= ~3; //4-player mode + + auto frequency = clockFrequency(); + switch(data & 3) { + case 0: this->frequency = frequency / 4; break; //fast (glitchy, even on real hardware) + case 1: this->frequency = frequency / 5; break; //normal + case 2: this->frequency = frequency / 7; break; //slow + case 3: this->frequency = frequency / 9; break; //very slow + } + stream->setFrequency(this->frequency / 128); + r6003 = data; + return; + } + + if(addr == 0x6004) { r6004 = data; return; } //joypad 1 + if(addr == 0x6005) { r6005 = data; return; } //joypad 2 + if(addr == 0x6006) { r6006 = data; return; } //joypad 3 + if(addr == 0x6007) { r6007 = data; return; } //joypad 4 +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/serialization.cpp new file mode 100644 index 0000000000..fd0259cb04 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/serialization.cpp @@ -0,0 +1,43 @@ +auto ICD::serialize(serializer& s) -> void { + Thread::serialize(s); + + auto size = GB_get_save_state_size(&sameboy); + auto data = new uint8_t[size]; + if(s.mode() == serializer::Save) { + GB_save_state_to_buffer(&sameboy, data); + } + s.array(data, size); + if(s.mode() == serializer::Load) { + GB_load_state_from_buffer(&sameboy, data, size); + } + delete[] data; + + for(auto n : range(64)) s.array(packet[n].data); + s.integer(packetSize); + + s.integer(joypID); + s.integer(joypLock); + s.integer(pulseLock); + s.integer(strobeLock); + s.integer(packetLock); + s.array(joypPacket.data); + s.integer(packetOffset); + s.integer(bitData); + s.integer(bitOffset); + + s.array(output); + s.integer(readBank); + s.integer(readAddress); + s.integer(writeBank); + + s.integer(r6003); + s.integer(r6004); + s.integer(r6005); + s.integer(r6006); + s.integer(r6007); + s.array(r7000); + s.integer(mltReq); + + s.integer(hcounter); + s.integer(vcounter); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/mcc/mcc.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/mcc/mcc.cpp new file mode 100644 index 0000000000..17136273b1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/mcc/mcc.cpp @@ -0,0 +1,257 @@ +#include + +namespace SuperFamicom { + +#include "serialization.cpp" +MCC mcc; + +auto MCC::unload() -> void { + rom.reset(); + psram.reset(); +} + +auto MCC::power() -> void { + irq.flag = 0; + irq.enable = 0; + w.mapping = 1; + w.psramEnableLo = 1; + w.psramEnableHi = 0; + w.psramMapping = 3; + w.romEnableLo = 1; + w.romEnableHi = 1; + w.exEnableLo = 1; + w.exEnableHi = 0; + w.exMapping = 1; + w.internallyWritable = 0; + w.externallyWritable = 0; + commit(); +} + +auto MCC::commit() -> void { + r = w; + bsmemory.writable(r.externallyWritable); +} + +auto MCC::read(uint address, uint8 data) -> uint8 { + if((address & 0xf0f000) == 0x005000) { //$00-0f:5000-5fff + switch(address >> 16 & 15) { + case 0: return irq.flag << 7; + case 1: return irq.enable << 7; + case 2: return r.mapping << 7; + case 3: return r.psramEnableLo << 7; + case 4: return r.psramEnableHi << 7; + case 5: return (r.psramMapping >> 0 & 1) << 7; + case 6: return (r.psramMapping >> 1 & 1) << 7; + case 7: return r.romEnableLo << 7; + case 8: return r.romEnableHi << 7; + case 9: return r.exEnableLo << 7; + case 10: return r.exEnableHi << 7; + case 11: return r.exMapping << 7; + case 12: return r.internallyWritable << 7; + case 13: return r.externallyWritable << 7; + case 14: return 0; //commit (always zero) + case 15: return 0; //unknown (always zero) + } + } + + return data; +} + +auto MCC::write(uint address, uint8 data) -> void { + if((address & 0xf0f000) == 0x005000) { //$00-0f:5000-5fff + switch(address >> 16 & 15) { + case 1: irq.enable = data >> 7; break; + case 2: w.mapping = data >> 7; break; + case 3: w.psramEnableLo = data >> 7; break; + case 4: w.psramEnableHi = data >> 7; break; + case 5: w.psramMapping = w.psramMapping & 2 | data >> 7 << 0; break; + case 6: w.psramMapping = w.psramMapping & 1 | data >> 7 << 1; break; + case 7: w.romEnableLo = data >> 7; break; + case 8: w.romEnableHi = data >> 7; break; + case 9: w.exEnableLo = data >> 7; break; + case 10: w.exEnableHi = data >> 7; break; + case 11: w.exMapping = data >> 7; break; + case 12: w.internallyWritable = data >> 7; break; + case 13: w.externallyWritable = data >> 7; break; + case 14: if(data >> 7) commit(); break; + } + } +} + +auto MCC::mcuRead(uint address, uint8 data) -> uint8 { + return mcuAccess(0, address, data); +} + +auto MCC::mcuWrite(uint address, uint8 data) -> void { + return mcuAccess(1, address, data), void(); +} + +auto MCC::mcuAccess(bool mode, uint address, uint8 data) -> uint8 { + //[[ROM]] + + if(r.romEnableLo) { + if((address & 0xc08000) == 0x008000) { //00-3f:8000-ffff + return romAccess(mode, (address & 0x3f0000) >> 1 | (address & 0x7fff), data); + } + } + + if(r.romEnableHi) { + if((address & 0xc08000) == 0x808000) { //80-bf:8000-ffff + return romAccess(mode, (address & 0x3f0000) >> 1 | (address & 0x7fff), data); + } + } + + //[[PSRAM]] + + if(r.psramEnableLo && r.mapping == 0) { + if(((address & 0xf08000) == 0x008000 && r.psramMapping == 0) //00-0f:8000-ffff + || ((address & 0xf08000) == 0x208000 && r.psramMapping == 1) //20-2f:8000-ffff + || ((address & 0xf00000) == 0x400000 && r.psramMapping == 2) //40-4f:0000-ffff + || ((address & 0xf00000) == 0x600000 && r.psramMapping == 3) //60-6f:0000-ffff + ) { + return psramAccess(mode, (address & 0x0f0000) >> 1 | (address & 0x7fff), data); + } + + if((address & 0xf08000) == 0x700000) { //70-7d:0000-7fff + return psramAccess(mode, (address & 0x0f0000) >> 1 | (address & 0x7fff), data); + } + } + + if(r.psramEnableHi && r.mapping == 0) { + if(((address & 0xf08000) == 0x808000 && r.psramMapping == 0) //80-8f:8000-ffff + || ((address & 0xf08000) == 0xa08000 && r.psramMapping == 1) //a0-af:8000-ffff + || ((address & 0xf00000) == 0xc00000 && r.psramMapping == 2) //c0-cf:0000-ffff + || ((address & 0xf00000) == 0xe00000 && r.psramMapping == 3) //e0-ef:0000-ffff + ) { + return psramAccess(mode, (address & 0x0f0000) >> 1 | (address & 0x7fff), data); + } + + if((address & 0xf08000) == 0xf00000) { //f0-ff:0000-7fff + return psramAccess(mode, (address & 0x0f0000) >> 1 | (address & 0x7fff), data); + } + } + + if(r.psramEnableLo && r.mapping == 1) { + if(((address & 0xf88000) == 0x008000 && r.psramMapping == 0) //00-07:8000-ffff + || ((address & 0xf88000) == 0x108000 && r.psramMapping == 1) //10-17:8000-ffff + || ((address & 0xf88000) == 0x208000 && r.psramMapping == 2) //20-27:8000-ffff + || ((address & 0xf88000) == 0x308000 && r.psramMapping == 3) //30-37:8000-ffff + || ((address & 0xf80000) == 0x400000 && r.psramMapping == 0) //40-47:0000-ffff + || ((address & 0xf80000) == 0x500000 && r.psramMapping == 1) //50-57:0000-ffff + || ((address & 0xf80000) == 0x600000 && r.psramMapping == 2) //60-67:0000-ffff + || ((address & 0xf80000) == 0x700000 && r.psramMapping == 3) //70-77:0000-ffff + ) { + return psramAccess(mode, address & 0x07ffff, data); + } + + if((address & 0xe0e000) == 0x206000) { //20-3f:6000-7fff + return psramAccess(mode, (address & 0x3f0000) >> 3 | (address & 0x1fff), data); + } + } + + if(r.psramEnableHi && r.mapping == 1) { + if(((address & 0xf88000) == 0x808000 && r.psramMapping == 0) //80-87:8000-ffff + || ((address & 0xf88000) == 0x908000 && r.psramMapping == 1) //90-97:8000-ffff + || ((address & 0xf88000) == 0xa08000 && r.psramMapping == 2) //a0-a7:8000-ffff + || ((address & 0xf88000) == 0xb08000 && r.psramMapping == 3) //b0-b7:8000-ffff + || ((address & 0xf80000) == 0xc00000 && r.psramMapping == 0) //c0-c7:0000-ffff + || ((address & 0xf80000) == 0xd00000 && r.psramMapping == 1) //d0-d7:0000-ffff + || ((address & 0xf80000) == 0xe00000 && r.psramMapping == 2) //e0-e7:0000-ffff + || ((address & 0xf80000) == 0xf00000 && r.psramMapping == 3) //f0-f7:0000-ffff + ) { + return psramAccess(mode, address & 0x07ffff, data); + } + + if((address & 0xe0e000) == 0xa06000) { //a0-bf:6000-7fff + return psramAccess(mode, (address & 0x3f0000) >> 3 | (address & 0x1fff), data); + } + } + + //[[EXMEMORY]] + + if(r.exEnableLo && r.mapping == 0) { + if(((address & 0xe08000) == 0x008000 && r.exMapping == 0) //00-1f:8000-ffff + || ((address & 0xe00000) == 0x400000 && r.exMapping == 1) //40-5f:0000-ffff + ) { + return exAccess(mode, (address & 0x1f0000) >> 1 | (address & 0x7fff), data); + } + } + + if(r.exEnableLo && r.mapping == 1) { + if(((address & 0xf08000) == 0x008000 && r.exMapping == 0) //00-0f:8000-ffff + || ((address & 0xf08000) == 0x208000 && r.exMapping == 1) //20-2f:8000-ffff + || ((address & 0xf00000) == 0x400000 && r.exMapping == 0) //40-4f:0000-ffff + || ((address & 0xf00000) == 0x600000 && r.exMapping == 1) //60-6f:0000-ffff + ) { + return exAccess(mode, address & 0x0fffff, data); + } + } + + if(r.exEnableHi && r.mapping == 0) { + if(((address & 0xe08000) == 0x808000 && r.exMapping == 0) //80-9f:8000-ffff + || ((address & 0xe00000) == 0xc00000 && r.exMapping == 1) //c0-df:0000-ffff + ) { + return exAccess(mode, (address & 0x1f0000) >> 1 | (address & 0x7fff), data); + } + } + + if(r.exEnableHi && r.mapping == 1) { + if(((address & 0xf08000) == 0x808000 && r.exMapping == 0) //80-8f:8000-ffff + || ((address & 0xf08000) == 0xa08000 && r.exMapping == 1) //a0-af:8000-ffff + || ((address & 0xf00000) == 0xc00000 && r.exMapping == 0) //c0-cf:0000-ffff + || ((address & 0xf00000) == 0xe00000 && r.exMapping == 1) //e0-ef:0000-ffff + ) { + return exAccess(mode, address & 0x0fffff, data); + } + } + + //[[BSMEMORY]] + + if(bsmemory.size() && r.mapping == 0) { + if(((address & 0x408000) == 0x008000) //00-3f,80-bf:8000-ffff + || ((address & 0x400000) == 0x400000) //40-7d,c0-ff:0000-ffff + ) { + return bsAccess(mode, (address & 0x3f0000) >> 1 | (address & 0x7fff), data); + } + } + + if(bsmemory.size() && r.mapping == 1) { + if(((address & 0x408000) == 0x008000) //00-3f,80-bf:8000-ffff + || ((address & 0x400000) == 0x400000) //40-7d,c0-ff:0000-ffff + ) { + return bsAccess(mode, address & 0x3fffff, data); + } + } + + return data; +} + +//size: 0x100000 +auto MCC::romAccess(bool mode, uint address, uint8 data) -> uint8 { + address = bus.mirror(address, rom.size()); + if(mode == 0) return rom.read(address); + return data; +} + +//size: 0x80000 +auto MCC::psramAccess(bool mode, uint address, uint8 data) -> uint8 { + address = bus.mirror(address, psram.size()); + if(mode == 0) return psram.read(address); + return psram.write(address, data), data; +} + +//size: 0x100000 (?) +auto MCC::exAccess(bool mode, uint address, uint8 data) -> uint8 { + //not physically present on BSC-1A5B9P-01 + return data; +} + +//size: 0x100000, 0x200000, 0x400000 +auto MCC::bsAccess(bool mode, uint address, uint8 data) -> uint8 { + address = bus.mirror(address, bsmemory.size()); + if(mode == 0) return bsmemory.read(address, data); + if(!r.internallyWritable) return data; + return bsmemory.write(address, data), data; +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/mcc/mcc.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/mcc/mcc.hpp new file mode 100644 index 0000000000..7409e66531 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/mcc/mcc.hpp @@ -0,0 +1,52 @@ +//MCC - Memory Controller Chip +//Custom logic chip inside the BS-X Satellaview base cartridge + +struct MCC { + ReadableMemory rom; + WritableMemory psram; + + //mcc.cpp + auto unload() -> void; + auto power() -> void; + auto commit() -> void; + + auto read(uint address, uint8 data) -> uint8; + auto write(uint address, uint8 data) -> void; + + auto mcuRead(uint address, uint8 data) -> uint8; + auto mcuWrite(uint address, uint8 data) -> void; + + auto mcuAccess(bool mode, uint address, uint8 data) -> uint8; + auto romAccess(bool mode, uint address, uint8 data) -> uint8; + auto psramAccess(bool mode, uint address, uint8 data) -> uint8; + auto exAccess(bool mode, uint address, uint8 data) -> uint8; + auto bsAccess(bool mode, uint address, uint8 data) -> uint8; + + //serialization.cpp + auto serialize(serializer&) -> void; + +private: + struct IRQ { + uint1 flag; //bit 0 + uint1 enable; //bit 1 + } irq; + + struct Registers { + uint1 mapping; //bit 2 (0 = ignore A15; 1 = use A15) + uint1 psramEnableLo; //bit 3 + uint1 psramEnableHi; //bit 4 + uint2 psramMapping; //bits 5-6 + uint1 romEnableLo; //bit 7 + uint1 romEnableHi; //bit 8 + uint1 exEnableLo; //bit 9 + uint1 exEnableHi; //bit 10 + uint1 exMapping; //bit 11 + uint1 internallyWritable; //bit 12 (1 = MCC allows writes to BS Memory Cassette) + uint1 externallyWritable; //bit 13 (1 = BS Memory Cassette allows writes to flash memory) + } r, w; + + //bit 14 = commit + //bit 15 = unknown (test register interface?) +}; + +extern MCC mcc; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/mcc/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/mcc/serialization.cpp new file mode 100644 index 0000000000..0876c69fc1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/mcc/serialization.cpp @@ -0,0 +1,30 @@ +auto MCC::serialize(serializer& s) -> void { + s.array(psram.data(), psram.size()); + + s.integer(irq.flag); + s.integer(irq.enable); + + s.integer(r.mapping); + s.integer(r.psramEnableLo); + s.integer(r.psramEnableHi); + s.integer(r.psramMapping); + s.integer(r.romEnableLo); + s.integer(r.romEnableHi); + s.integer(r.exEnableLo); + s.integer(r.exEnableHi); + s.integer(r.exMapping); + s.integer(r.internallyWritable); + s.integer(r.externallyWritable); + + s.integer(w.mapping); + s.integer(w.psramEnableLo); + s.integer(w.psramEnableHi); + s.integer(w.psramMapping); + s.integer(w.romEnableLo); + s.integer(w.romEnableHi); + s.integer(w.exEnableLo); + s.integer(w.exEnableHi); + s.integer(w.exMapping); + s.integer(w.internallyWritable); + s.integer(w.externallyWritable); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/msu1/msu1.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/msu1/msu1.cpp new file mode 100644 index 0000000000..82801e13f7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/msu1/msu1.cpp @@ -0,0 +1,182 @@ +#include + +namespace SuperFamicom { + +MSU1 msu1; + +#include "serialization.cpp" + +auto MSU1::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + + +auto MSU1::Enter() -> void { + while(true) { + scheduler.synchronize(); + msu1.main(); + } +} + +auto MSU1::main() -> void { + double left = 0.0; + double right = 0.0; + + if(io.audioPlay) { + if(audioFile) { + if(audioFile->end()) { + if(!io.audioRepeat) { + io.audioPlay = false; + audioFile->seek(io.audioPlayOffset = 8); + } else { + audioFile->seek(io.audioPlayOffset = io.audioLoopOffset); + } + } else { + io.audioPlayOffset += 4; + left = (double)(int16)audioFile->readl(2) / 32768.0 * (double)io.audioVolume / 255.0; + right = (double)(int16)audioFile->readl(2) / 32768.0 * (double)io.audioVolume / 255.0; + if(dsp.mute()) left = 0, right = 0; + } + } else { + io.audioPlay = false; + } + } + + if(!system.runAhead && system.renderAudio) stream->sample(float(left), float(right)); + step(1); + synchronizeCPU(); +} + +auto MSU1::step(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; +} + +auto MSU1::unload() -> void { + dataFile.reset(); + audioFile.reset(); +} + +auto MSU1::power() -> void { + create(MSU1::Enter, 44100); + stream = Emulator::audio.createStream(2, frequency); + + io.dataSeekOffset = 0; + io.dataReadOffset = 0; + + io.audioPlayOffset = 0; + io.audioLoopOffset = 0; + + io.audioTrack = 0; + io.audioVolume = 0; + + io.audioResumeTrack = ~0; //no resume + io.audioResumeOffset = 0; + + io.audioError = false; + io.audioPlay = false; + io.audioRepeat = false; + io.audioBusy = false; + io.dataBusy = false; + + dataOpen(); + audioOpen(); +} + +auto MSU1::dataOpen() -> void { + dataFile.reset(); + string name = {"msu1/data.rom"}; + if(dataFile = platform->open(ID::SuperFamicom, name, File::Read)) { + dataFile->seek(io.dataReadOffset); + } +} + +auto MSU1::audioOpen() -> void { + audioFile.reset(); + string name = {"msu1/track-", io.audioTrack, ".pcm"}; + if(audioFile = platform->open(ID::SuperFamicom, name, File::Read)) { + if(audioFile->size() >= 8) { + uint32 header = audioFile->readm(4); + if(header == 0x4d535531) { //"MSU1" + io.audioLoopOffset = 8 + audioFile->readl(4) * 4; + if(io.audioLoopOffset > audioFile->size()) io.audioLoopOffset = 8; + io.audioError = false; + audioFile->seek(io.audioPlayOffset); + return; + } + } + audioFile.reset(); + } + io.audioError = true; +} + +auto MSU1::readIO(uint addr, uint8) -> uint8 { + cpu.synchronizeCoprocessors(); + + switch(0x2000 | addr & 7) { + case 0x2000: + return ( + Revision << 0 + | io.audioError << 3 + | io.audioPlay << 4 + | io.audioRepeat << 5 + | io.audioBusy << 6 + | io.dataBusy << 7 + ); + case 0x2001: + if(io.dataBusy) return 0x00; + if(!dataFile) return 0x00; + if(dataFile->end()) return 0x00; + io.dataReadOffset++; + return dataFile->read(); + case 0x2002: return 'S'; + case 0x2003: return '-'; + case 0x2004: return 'M'; + case 0x2005: return 'S'; + case 0x2006: return 'U'; + case 0x2007: return '1'; + } + + unreachable; +} + +auto MSU1::writeIO(uint addr, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + + switch(0x2000 | addr & 7) { + case 0x2000: io.dataSeekOffset = io.dataSeekOffset & 0xffffff00 | data << 0; break; + case 0x2001: io.dataSeekOffset = io.dataSeekOffset & 0xffff00ff | data << 8; break; + case 0x2002: io.dataSeekOffset = io.dataSeekOffset & 0xff00ffff | data << 16; break; + case 0x2003: io.dataSeekOffset = io.dataSeekOffset & 0x00ffffff | data << 24; + io.dataReadOffset = io.dataSeekOffset; + if(dataFile) dataFile->seek(io.dataReadOffset); + break; + case 0x2004: io.audioTrack = io.audioTrack & 0xff00 | data << 0; break; + case 0x2005: io.audioTrack = io.audioTrack & 0x00ff | data << 8; + io.audioPlay = false; + io.audioRepeat = false; + io.audioPlayOffset = 8; + if(io.audioTrack == io.audioResumeTrack) { + io.audioPlayOffset = io.audioResumeOffset; + io.audioResumeTrack = ~0; //erase resume track + io.audioResumeOffset = 0; + } + audioOpen(); + break; + case 0x2006: + io.audioVolume = data; + break; + case 0x2007: + if(io.audioBusy) break; + if(io.audioError) break; + io.audioPlay = bool(data & 1); + io.audioRepeat = bool(data & 2); + boolean audioResume = bool(data & 4); + if(!io.audioPlay && audioResume) { + io.audioResumeTrack = io.audioTrack; + io.audioResumeOffset = io.audioPlayOffset; + } + break; + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/msu1/msu1.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/msu1/msu1.hpp new file mode 100644 index 0000000000..0b3f9375f3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/msu1/msu1.hpp @@ -0,0 +1,53 @@ +struct MSU1 : Thread { + shared_pointer stream; + + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; + auto unload() -> void; + auto power() -> void; + + auto dataOpen() -> void; + auto audioOpen() -> void; + + auto readIO(uint addr, uint8 data) -> uint8; + auto writeIO(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; + +private: + shared_pointer dataFile; + shared_pointer audioFile; + + enum Flag : uint { + Revision = 0x02, //max: 0x07 + AudioError = 0x08, + AudioPlaying = 0x10, + AudioRepeating = 0x20, + AudioBusy = 0x40, + DataBusy = 0x80, + }; + + struct IO { + uint32 dataSeekOffset; + uint32 dataReadOffset; + + uint32 audioPlayOffset; + uint32 audioLoopOffset; + + uint16 audioTrack; + uint8 audioVolume; + + uint32 audioResumeTrack; + uint32 audioResumeOffset; + + boolean audioError; + boolean audioPlay; + boolean audioRepeat; + boolean audioBusy; + boolean dataBusy; + } io; +}; + +extern MSU1 msu1; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/msu1/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/msu1/serialization.cpp new file mode 100644 index 0000000000..d6658dff03 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/msu1/serialization.cpp @@ -0,0 +1,24 @@ +auto MSU1::serialize(serializer& s) -> void { + Thread::serialize(s); + + s.integer(io.dataSeekOffset); + s.integer(io.dataReadOffset); + + s.integer(io.audioPlayOffset); + s.integer(io.audioLoopOffset); + + s.integer(io.audioTrack); + s.integer(io.audioVolume); + + s.integer(io.audioResumeTrack); + s.integer(io.audioResumeOffset); + + s.boolean(io.audioError); + s.boolean(io.audioPlay); + s.boolean(io.audioRepeat); + s.boolean(io.audioBusy); + s.boolean(io.dataBusy); + + dataOpen(); + audioOpen(); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/necdsp/necdsp.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/necdsp/necdsp.cpp new file mode 100644 index 0000000000..3942d98a59 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/necdsp/necdsp.cpp @@ -0,0 +1,63 @@ +#include +#include + +namespace SuperFamicom { + +#include "serialization.cpp" +NECDSP necdsp; + +auto NECDSP::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto NECDSP::Enter() -> void { + while(true) { + scheduler.synchronize(); + necdsp.main(); + } +} + +auto NECDSP::main() -> void { + exec(); + step(1); + synchronizeCPU(); +} + +auto NECDSP::step(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; +} + +auto NECDSP::read(uint addr, uint8) -> uint8 { + cpu.synchronizeCoprocessors(); + if(addr & 1) { + return uPD96050::readSR(); + } else { + return uPD96050::readDR(); + } +} + +auto NECDSP::write(uint addr, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + if(addr & 1) { + return uPD96050::writeSR(data); + } else { + return uPD96050::writeDR(data); + } +} + +auto NECDSP::readRAM(uint addr, uint8) -> uint8 { + cpu.synchronizeCoprocessors(); + return uPD96050::readDP(addr); +} + +auto NECDSP::writeRAM(uint addr, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + return uPD96050::writeDP(addr, data); +} + +auto NECDSP::power() -> void { + uPD96050::power(); + create(NECDSP::Enter, Frequency); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/necdsp/necdsp.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/necdsp/necdsp.hpp new file mode 100644 index 0000000000..dd29fbbd42 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/necdsp/necdsp.hpp @@ -0,0 +1,21 @@ +struct NECDSP : Processor::uPD96050, Thread { + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto readRAM(uint addr, uint8 data) -> uint8; + auto writeRAM(uint addr, uint8 data) -> void; + + auto power() -> void; + + auto firmware() const -> vector; + auto serialize(serializer&) -> void; + + uint Frequency = 0; +}; + +extern NECDSP necdsp; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/necdsp/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/necdsp/serialization.cpp new file mode 100644 index 0000000000..8708471387 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/necdsp/serialization.cpp @@ -0,0 +1,25 @@ +auto NECDSP::firmware() const -> vector { + vector buffer; + if(!cartridge.has.NECDSP) return buffer; + uint plength = 2048, dlength = 1024; + if(revision == Revision::uPD96050) plength = 16384, dlength = 2048; + buffer.reserve(plength * 3 + dlength * 2); + + for(auto n : range(plength)) { + buffer.append(programROM[n] >> 0); + buffer.append(programROM[n] >> 8); + buffer.append(programROM[n] >> 16); + } + + for(auto n : range(dlength)) { + buffer.append(dataROM[n] >> 0); + buffer.append(dataROM[n] >> 8); + } + + return buffer; +} + +auto NECDSP::serialize(serializer& s) -> void { + uPD96050::serialize(s); + Thread::serialize(s); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/obc1/obc1.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/obc1/obc1.cpp new file mode 100644 index 0000000000..9124067cee --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/obc1/obc1.cpp @@ -0,0 +1,70 @@ +#include + +namespace SuperFamicom { + +#include "serialization.cpp" +OBC1 obc1; + +auto OBC1::unload() -> void { + ram.reset(); +} + +auto OBC1::power() -> void { + status.baseptr = (ramRead(0x1ff5) & 1) ? 0x1800 : 0x1c00; + status.address = (ramRead(0x1ff6) & 0x7f); + status.shift = (ramRead(0x1ff6) & 3) << 1; +} + +auto OBC1::read(uint addr, uint8) -> uint8 { + addr &= 0x1fff; + + switch(addr) { + case 0x1ff0: return ramRead(status.baseptr + (status.address << 2) + 0); + case 0x1ff1: return ramRead(status.baseptr + (status.address << 2) + 1); + case 0x1ff2: return ramRead(status.baseptr + (status.address << 2) + 2); + case 0x1ff3: return ramRead(status.baseptr + (status.address << 2) + 3); + case 0x1ff4: return ramRead(status.baseptr + (status.address >> 2) + 0x200); + } + + return ramRead(addr); +} + +auto OBC1::write(uint addr, uint8 data) -> void { + addr &= 0x1fff; + + switch(addr) { + case 0x1ff0: ramWrite(status.baseptr + (status.address << 2) + 0, data); return; + case 0x1ff1: ramWrite(status.baseptr + (status.address << 2) + 1, data); return; + case 0x1ff2: ramWrite(status.baseptr + (status.address << 2) + 2, data); return; + case 0x1ff3: ramWrite(status.baseptr + (status.address << 2) + 3, data); return; + case 0x1ff4: { + uint8 temp = ramRead(status.baseptr + (status.address >> 2) + 0x200); + temp = (temp & ~(3 << status.shift)) | ((data & 3) << status.shift); + ramWrite(status.baseptr + (status.address >> 2) + 0x200, temp); + } return; + case 0x1ff5: + status.baseptr = (data & 1) ? 0x1800 : 0x1c00; + ramWrite(addr, data); + return; + case 0x1ff6: + status.address = (data & 0x7f); + status.shift = (data & 3) << 1; + ramWrite(addr, data); + return; + case 0x1ff7: + ramWrite(addr, data); + return; + } + + return ramWrite(addr, data); +} + +auto OBC1::ramRead(uint addr) -> uint8 { + return ram.read(addr & 0x1fff); +} + +auto OBC1::ramWrite(uint addr, uint8 data) -> void { + ram.write(addr & 0x1fff, data); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/obc1/obc1.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/obc1/obc1.hpp new file mode 100644 index 0000000000..4c42eb8b8b --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/obc1/obc1.hpp @@ -0,0 +1,23 @@ +struct OBC1 { + auto unload() -> void; + auto power() -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; + + WritableMemory ram; + +private: + auto ramRead(uint addr) -> uint8; + auto ramWrite(uint addr, uint8 data) -> void; + + struct { + uint16 address; + uint16 baseptr; + uint16 shift; + } status; +}; + +extern OBC1 obc1; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/obc1/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/obc1/serialization.cpp new file mode 100644 index 0000000000..851a16de3e --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/obc1/serialization.cpp @@ -0,0 +1,7 @@ +auto OBC1::serialize(serializer& s) -> void { + s.array(ram.data(), ram.size()); + + s.integer(status.address); + s.integer(status.baseptr); + s.integer(status.shift); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/bwram.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/bwram.cpp new file mode 100644 index 0000000000..7d6059c7e5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/bwram.cpp @@ -0,0 +1,121 @@ +auto SA1::BWRAM::conflict() const -> bool { + if(configuration.hacks.coprocessor.delayedSync) return false; + + if((cpu.r.mar & 0x40e000) == 0x006000) return true; //00-3f,80-bf:6000-7fff + if((cpu.r.mar & 0xf00000) == 0x400000) return true; //40-4f:0000-ffff + return false; +} + +auto SA1::BWRAM::read(uint address, uint8 data) -> uint8 { + if(!size()) return data; + address = bus.mirror(address, size()); + return WritableMemory::read(address, data); +} + +auto SA1::BWRAM::write(uint address, uint8 data) -> void { + if(!size()) return; + address = bus.mirror(address, size()); + return WritableMemory::write(address, data); +} + +//note: addresses are translated prior to invoking this function: +//00-3f,80-bf:6000-7fff size=0x2000 => 00:0000-1fff +//40-4f:0000-ffff => untranslated +auto SA1::BWRAM::readCPU(uint address, uint8 data) -> uint8 { + cpu.synchronizeCoprocessors(); + + if(address < 0x2000) { //$00-3f,80-bf:6000-7fff + address = sa1.mmio.sbm * 0x2000 + (address & 0x1fff); + } + + if(dma) return sa1.dmaCC1Read(address); + return read(address, data); +} + +auto SA1::BWRAM::writeCPU(uint address, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + + if(address < 0x2000) { //$00-3f,80-bf:6000-7fff + address = sa1.mmio.sbm * 0x2000 + (address & 0x1fff); + } + + return write(address, data); +} + +auto SA1::BWRAM::readSA1(uint address, uint8 data) -> uint8 { + if(sa1.mmio.sw46 == 0) { + //$40-43:0000-ffff x 32 projection + address = (sa1.mmio.cbm & 0x1f) * 0x2000 + (address & 0x1fff); + return readLinear(address, data); + } else { + //$60-6f:0000-ffff x 128 projection + address = sa1.mmio.cbm * 0x2000 + (address & 0x1fff); + return readBitmap(address, data); + } +} + +auto SA1::BWRAM::writeSA1(uint address, uint8 data) -> void { + if(sa1.mmio.sw46 == 0) { + //$40-43:0000-ffff x 32 projection + address = (sa1.mmio.cbm & 0x1f) * 0x2000 + (address & 0x1fff); + return writeLinear(address, data); + } else { + //$60-6f:0000-ffff x 128 projection + address = sa1.mmio.cbm * 0x2000 + (address & 0x1fff); + return writeBitmap(address, data); + } +} + +auto SA1::BWRAM::readLinear(uint address, uint8 data) -> uint8 { + return read(address, data); +} + +auto SA1::BWRAM::writeLinear(uint address, uint8 data) -> void { + return write(address, data); +} + +auto SA1::BWRAM::readBitmap(uint20 address, uint8 data) -> uint8 { + if(sa1.mmio.bbf == 0) { + //4bpp + uint shift = address & 1; + address >>= 1; + switch(shift) { + case 0: return read(address) >> 0 & 15; + case 1: return read(address) >> 4 & 15; + } + } else { + //2bpp + uint shift = address & 3; + address >>= 2; + switch(shift) { + case 0: return read(address) >> 0 & 3; + case 1: return read(address) >> 2 & 3; + case 2: return read(address) >> 4 & 3; + case 3: return read(address) >> 6 & 3; + } + } + unreachable; +} + +auto SA1::BWRAM::writeBitmap(uint20 address, uint8 data) -> void { + if(sa1.mmio.bbf == 0) { + //4bpp + uint shift = address & 1; + address >>= 1; + switch(shift) { + case 0: data = read(address) & 0xf0 | (data & 0x0f) << 0; break; + case 1: data = read(address) & 0x0f | (data & 0x0f) << 4; break; + } + } else { + //2bpp + uint shift = address & 3; + address >>= 2; + switch(shift) { + case 0: data = read(address) & 0xfc | (data & 0x03) << 0; break; + case 1: data = read(address) & 0xf3 | (data & 0x03) << 2; break; + case 2: data = read(address) & 0xcf | (data & 0x03) << 4; break; + case 3: data = read(address) & 0x3f | (data & 0x03) << 6; break; + } + } + write(address, data); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/dma.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/dma.cpp new file mode 100644 index 0000000000..ba494a6bc5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/dma.cpp @@ -0,0 +1,128 @@ +//direct data transfer +auto SA1::dmaNormal() -> void { + while(mmio.dtc--) { + uint8 data = r.mdr; + uint24 source = mmio.dsa++; + uint16 target = mmio.dda++; + + if(mmio.sd == DMA::SourceROM && mmio.dd == DMA::DestBWRAM) { + step(); + step(); + if(bwram.conflict()) step(); + if(bwram.conflict()) step(); + data = rom.readSA1(source, data); + bwram.write(target, data); + } + + if(mmio.sd == DMA::SourceROM && mmio.dd == DMA::DestIRAM) { + step(); + if(iram.conflict() || rom.conflict()) step(); + if(iram.conflict()) step(); + data = rom.readSA1(source, data); + iram.write(target, data); + } + + if(mmio.sd == DMA::SourceBWRAM && mmio.dd == DMA::DestIRAM) { + step(); + step(); + if(bwram.conflict() || iram.conflict()) step(); + if(bwram.conflict()) step(); + data = bwram.read(source, data); + iram.write(target, data); + } + + if(mmio.sd == DMA::SourceIRAM && mmio.dd == DMA::DestBWRAM) { + step(); + step(); + if(bwram.conflict() || iram.conflict()) step(); + if(bwram.conflict()) step(); + data = iram.read(source, data); + bwram.write(target, data); + } + } + + mmio.dma_irqfl = true; + if(mmio.dma_irqen) mmio.dma_irqcl = 0; +} + +//type-1 character conversion +auto SA1::dmaCC1() -> void { + bwram.dma = true; + mmio.chdma_irqfl = true; + if(mmio.chdma_irqen) { + mmio.chdma_irqcl = 0; + cpu.irq(1); + } +} + +//((byte & 6) << 3) + (byte & 1) explanation: +//transforms a byte index (0-7) into a planar index: +//result[] = {0, 1, 16, 17, 32, 33, 48, 49}; +//works for 2bpp, 4bpp and 8bpp modes + +//type-1 character conversion +auto SA1::dmaCC1Read(uint addr) -> uint8 { + //16 bytes/char (2bpp); 32 bytes/char (4bpp); 64 bytes/char (8bpp) + uint charmask = (1 << (6 - mmio.dmacb)) - 1; + + if((addr & charmask) == 0) { + //buffer next character to I-RAM + uint bpp = 2 << (2 - mmio.dmacb); + uint bpl = (8 << mmio.dmasize) >> mmio.dmacb; + uint bwmask = bwram.size() - 1; + uint tile = ((addr - mmio.dsa) & bwmask) >> (6 - mmio.dmacb); + uint ty = (tile >> mmio.dmasize); + uint tx = tile & ((1 << mmio.dmasize) - 1); + uint bwaddr = mmio.dsa + ty * 8 * bpl + tx * bpp; + + for(auto y : range(8)) { + uint64 data = 0; + for(auto byte : range(bpp)) { + data |= (uint64)bwram.read((bwaddr + byte) & bwmask) << (byte << 3); + } + bwaddr += bpl; + + uint8 out[] = {0, 0, 0, 0, 0, 0, 0, 0}; + for(auto x : range(8)) { + out[0] |= (data & 1) << 7 - x; data >>= 1; + out[1] |= (data & 1) << 7 - x; data >>= 1; + if(mmio.dmacb == 2) continue; + out[2] |= (data & 1) << 7 - x; data >>= 1; + out[3] |= (data & 1) << 7 - x; data >>= 1; + if(mmio.dmacb == 1) continue; + out[4] |= (data & 1) << 7 - x; data >>= 1; + out[5] |= (data & 1) << 7 - x; data >>= 1; + out[6] |= (data & 1) << 7 - x; data >>= 1; + out[7] |= (data & 1) << 7 - x; data >>= 1; + } + + for(auto byte : range(bpp)) { + uint p = mmio.dda + (y << 1) + ((byte & 6) << 3) + (byte & 1); + iram.write(p & 0x07ff, out[byte]); + } + } + } + + return iram.read((mmio.dda + (addr & charmask)) & 0x07ff); +} + +//type-2 character conversion +auto SA1::dmaCC2() -> void { + //select register file index (0-7 or 8-15) + const uint8* brf = &mmio.brf[(dma.line & 1) << 3]; + uint bpp = 2 << (2 - mmio.dmacb); + uint addr = mmio.dda & 0x07ff; + addr &= ~((1 << (7 - mmio.dmacb)) - 1); + addr += (dma.line & 8) * bpp; + addr += (dma.line & 7) * 2; + + for(auto byte : range(bpp)) { + uint8 output = 0; + for(auto bit : range(8)) { + output |= ((brf[bit] >> byte) & 1) << (7 - bit); + } + iram.write(addr + ((byte & 6) << 3) + (byte & 1), output); + } + + dma.line = (dma.line + 1) & 15; +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/io.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/io.cpp new file mode 100644 index 0000000000..5822937965 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/io.cpp @@ -0,0 +1,522 @@ +auto SA1::readIOCPU(uint address, uint8 data) -> uint8 { + cpu.synchronizeCoprocessors(); + + switch(0x2200 | address & 0x1ff) { + + //(SFR) S-CPU flag read + case 0x2300: { + uint8 data; + data = mmio.cpu_irqfl << 7; + data |= mmio.cpu_ivsw << 6; + data |= mmio.chdma_irqfl << 5; + data |= mmio.cpu_nvsw << 4; + data |= mmio.cmeg; + return data; + } + + //(VC) version code register + case 0x230e: { + break; //does not actually exist on real hardware ... always returns open bus + } + + } + + return data; +} + +auto SA1::readIOSA1(uint address, uint8) -> uint8 { + synchronizeCPU(); + + switch(0x2200 | address & 0x1ff) { + + //(CFR) SA-1 flag read + case 0x2301: { + uint8 data; + data = mmio.sa1_irqfl << 7; + data |= mmio.timer_irqfl << 6; + data |= mmio.dma_irqfl << 5; + data |= mmio.sa1_nmifl << 4; + data |= mmio.smeg; + return data; + } + + //(HCR) hcounter read + case 0x2302: { + //latch counters + mmio.hcr = status.hcounter >> 2; + mmio.vcr = status.vcounter; + return mmio.hcr >> 0; + } + + case 0x2303: { + return mmio.hcr >> 8; + } + + //(VCR) vcounter read + case 0x2304: return mmio.vcr >> 0; + case 0x2305: return mmio.vcr >> 8; + + //(MR) arithmetic result + case 0x2306: return mmio.mr >> 0; + case 0x2307: return mmio.mr >> 8; + case 0x2308: return mmio.mr >> 16; + case 0x2309: return mmio.mr >> 24; + case 0x230a: return mmio.mr >> 32; + + //(OF) arithmetic overflow flag + case 0x230b: return mmio.overflow << 7; + + //(VDPL) variable-length data read port low + case 0x230c: { + uint24 data; + data.byte(0) = readVBR(mmio.va + 0); + data.byte(1) = readVBR(mmio.va + 1); + data.byte(2) = readVBR(mmio.va + 2); + data >>= mmio.vbit; + + return data >> 0; + } + + //(VDPH) variable-length data read port high + case 0x230d: { + uint24 data; + data.byte(0) = readVBR(mmio.va + 0); + data.byte(1) = readVBR(mmio.va + 1); + data.byte(2) = readVBR(mmio.va + 2); + data >>= mmio.vbit; + + if(mmio.hl == 1) { + //auto-increment mode + mmio.vbit += mmio.vb; + mmio.va += (mmio.vbit >> 3); + mmio.vbit &= 7; + } + + return data >> 8; + } + + } + + return 0xff; +} + +auto SA1::writeIOCPU(uint address, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + + switch(0x2200 | address & 0x1ff) { + + //(CCNT) SA-1 control + case 0x2200: { + if(mmio.sa1_resb && !(data & 0x20)) { + //reset SA-1 CPU (PC bank set to 0x00) + r.pc.d = mmio.crv; + } + + mmio.sa1_irq = (data & 0x80); + mmio.sa1_rdyb = (data & 0x40); + mmio.sa1_resb = (data & 0x20); + mmio.sa1_nmi = (data & 0x10); + mmio.smeg = (data & 0x0f); + + if(mmio.sa1_irq) { + mmio.sa1_irqfl = true; + if(mmio.sa1_irqen) mmio.sa1_irqcl = 0; + } + + if(mmio.sa1_nmi) { + mmio.sa1_nmifl = true; + if(mmio.sa1_nmien) mmio.sa1_nmicl = 0; + } + + return; + } + + //(SIE) S-CPU interrupt enable + case 0x2201: { + if(!mmio.cpu_irqen && (data & 0x80)) { + if(mmio.cpu_irqfl) { + mmio.cpu_irqcl = 0; + cpu.irq(1); + } + } + + if(!mmio.chdma_irqen && (data & 0x20)) { + if(mmio.chdma_irqfl) { + mmio.chdma_irqcl = 0; + cpu.irq(1); + } + } + + mmio.cpu_irqen = (data & 0x80); + mmio.chdma_irqen = (data & 0x20); + return; + } + + //(SIC) S-CPU interrupt clear + case 0x2202: { + mmio.cpu_irqcl = (data & 0x80); + mmio.chdma_irqcl = (data & 0x20); + + if(mmio.cpu_irqcl ) mmio.cpu_irqfl = false; + if(mmio.chdma_irqcl) mmio.chdma_irqfl = false; + + if(!mmio.cpu_irqfl && !mmio.chdma_irqfl) cpu.irq(0); + return; + } + + //(CRV) SA-1 reset vector + case 0x2203: { mmio.crv = (mmio.crv & 0xff00) | data; return; } + case 0x2204: { mmio.crv = (data << 8) | (mmio.crv & 0xff); return; } + + //(CNV) SA-1 NMI vector + case 0x2205: { mmio.cnv = (mmio.cnv & 0xff00) | data; return; } + case 0x2206: { mmio.cnv = (data << 8) | (mmio.cnv & 0xff); return; } + + //(CIV) SA-1 IRQ vector + case 0x2207: { mmio.civ = (mmio.civ & 0xff00) | data; return; } + case 0x2208: { mmio.civ = (data << 8) | (mmio.civ & 0xff); return; } + + //(CXB) Super MMC bank C + case 0x2220: { + mmio.cbmode = (data & 0x80); + mmio.cb = (data & 0x07); + return; + } + + //(DXB) Super MMC bank D + case 0x2221: { + mmio.dbmode = (data & 0x80); + mmio.db = (data & 0x07); + return; + } + + //(EXB) Super MMC bank E + case 0x2222: { + mmio.ebmode = (data & 0x80); + mmio.eb = (data & 0x07); + return; + } + + //(FXB) Super MMC bank F + case 0x2223: { + mmio.fbmode = (data & 0x80); + mmio.fb = (data & 0x07); + return; + } + + //(BMAPS) S-CPU BW-RAM address mapping + case 0x2224: { + mmio.sbm = (data & 0x1f); + return; + } + + //(SWBE) S-CPU BW-RAM write enable + case 0x2226: { + mmio.swen = (data & 0x80); + return; + } + + //(BWPA) BW-RAM write-protected area + case 0x2228: { + mmio.bwp = (data & 0x0f); + return; + } + + //(SIWP) S-CPU I-RAM write protection + case 0x2229: { + mmio.siwp = data; + return; + } + + case 0x2231: case 0x2232: case 0x2233: case 0x2234: case 0x2235: case 0x2236: case 0x2237: { + return writeIOShared(address, data); + } + + } +} + +auto SA1::writeIOSA1(uint address, uint8 data) -> void { + synchronizeCPU(); + + switch(0x2200 | address & 0x1ff) { + + //(SCNT) S-CPU control + case 0x2209: { + mmio.cpu_irq = (data & 0x80); + mmio.cpu_ivsw = (data & 0x40); + mmio.cpu_nvsw = (data & 0x10); + mmio.cmeg = (data & 0x0f); + + if(mmio.cpu_irq) { + mmio.cpu_irqfl = true; + if(mmio.cpu_irqen) { + mmio.cpu_irqcl = 0; + cpu.irq(1); + } + } + + return; + } + + //(CIE) SA-1 interrupt enable + case 0x220a: { + if(!mmio.sa1_irqen && (data & 0x80) && mmio.sa1_irqfl ) mmio.sa1_irqcl = 0; + if(!mmio.timer_irqen && (data & 0x40) && mmio.timer_irqfl) mmio.timer_irqcl = 0; + if(!mmio.dma_irqen && (data & 0x20) && mmio.dma_irqfl ) mmio.dma_irqcl = 0; + if(!mmio.sa1_nmien && (data & 0x10) && mmio.sa1_nmifl ) mmio.sa1_nmicl = 0; + + mmio.sa1_irqen = (data & 0x80); + mmio.timer_irqen = (data & 0x40); + mmio.dma_irqen = (data & 0x20); + mmio.sa1_nmien = (data & 0x10); + return; + } + + //(CIC) SA-1 interrupt clear + case 0x220b: { + mmio.sa1_irqcl = (data & 0x80); + mmio.timer_irqcl = (data & 0x40); + mmio.dma_irqcl = (data & 0x20); + mmio.sa1_nmicl = (data & 0x10); + + if(mmio.sa1_irqcl) mmio.sa1_irqfl = false; + if(mmio.timer_irqcl) mmio.timer_irqfl = false; + if(mmio.dma_irqcl) mmio.dma_irqfl = false; + if(mmio.sa1_nmicl) mmio.sa1_nmifl = false; + return; + } + + //(SNV) S-CPU NMI vector + case 0x220c: { mmio.snv = (mmio.snv & 0xff00) | data; return; } + case 0x220d: { mmio.snv = (data << 8) | (mmio.snv & 0xff); return; } + + //(SIV) S-CPU IRQ vector + case 0x220e: { mmio.siv = (mmio.siv & 0xff00) | data; return; } + case 0x220f: { mmio.siv = (data << 8) | (mmio.siv & 0xff); return; } + + //(TMC) H/V timer control + case 0x2210: { + mmio.hvselb = (data & 0x80); + mmio.ven = (data & 0x02); + mmio.hen = (data & 0x01); + return; + } + + //(CTR) SA-1 timer restart + case 0x2211: { + status.vcounter = 0; + status.hcounter = 0; + return; + } + + //(HCNT) H-count + case 0x2212: { mmio.hcnt = (mmio.hcnt & 0xff00) | (data << 0); return; } + case 0x2213: { mmio.hcnt = (mmio.hcnt & 0x00ff) | (data << 8); return; } + + //(VCNT) V-count + case 0x2214: { mmio.vcnt = (mmio.vcnt & 0xff00) | (data << 0); return; } + case 0x2215: { mmio.vcnt = (mmio.vcnt & 0x00ff) | (data << 8); return; } + + //(BMAP) SA-1 BW-RAM address mapping + case 0x2225: { + mmio.sw46 = (data & 0x80); + mmio.cbm = (data & 0x7f); + return; + } + + //(CWBE) SA-1 BW-RAM write enable + case 0x2227: { + mmio.cwen = (data & 0x80); + return; + } + + //(CIWP) SA-1 I-RAM write protection + case 0x222a: { + mmio.ciwp = data; + return; + } + + //(DCNT) DMA control + case 0x2230: { + mmio.dmaen = (data & 0x80); + mmio.dprio = (data & 0x40); + mmio.cden = (data & 0x20); + mmio.cdsel = (data & 0x10); + mmio.dd = (data & 0x04); + mmio.sd = (data & 0x03); + + if(mmio.dmaen == 0) dma.line = 0; + return; + } + + case 0x2231: case 0x2232: case 0x2233: case 0x2234: case 0x2235: case 0x2236: case 0x2237: { + return writeIOShared(address, data); + } + + //(DTC) DMA terminal counter + case 0x2238: { mmio.dtc = (mmio.dtc & 0xff00) | (data << 0); return; } + case 0x2239: { mmio.dtc = (mmio.dtc & 0x00ff) | (data << 8); return; } + + //(BBF) BW-RAM bitmap format + case 0x223f: { mmio.bbf = (data & 0x80); return; } + + //(BRF) bitmap register files + case 0x2240: { mmio.brf[ 0] = data; return; } + case 0x2241: { mmio.brf[ 1] = data; return; } + case 0x2242: { mmio.brf[ 2] = data; return; } + case 0x2243: { mmio.brf[ 3] = data; return; } + case 0x2244: { mmio.brf[ 4] = data; return; } + case 0x2245: { mmio.brf[ 5] = data; return; } + case 0x2246: { mmio.brf[ 6] = data; return; } + case 0x2247: { mmio.brf[ 7] = data; + if(mmio.dmaen) { + if(mmio.cden == 1 && mmio.cdsel == 0) { + dmaCC2(); + } + } + return; + } + case 0x2248: { mmio.brf[ 8] = data; return; } + case 0x2249: { mmio.brf[ 9] = data; return; } + case 0x224a: { mmio.brf[10] = data; return; } + case 0x224b: { mmio.brf[11] = data; return; } + case 0x224c: { mmio.brf[12] = data; return; } + case 0x224d: { mmio.brf[13] = data; return; } + case 0x224e: { mmio.brf[14] = data; return; } + case 0x224f: { mmio.brf[15] = data; + if(mmio.dmaen) { + if(mmio.cden == 1 && mmio.cdsel == 0) { + dmaCC2(); + } + } + return; + } + + //(MCNT) arithmetic control + case 0x2250: { + mmio.acm = (data & 0x02); + mmio.md = (data & 0x01); + + if(mmio.acm) mmio.mr = 0; + return; + } + + //(MAL) multiplicand / dividend low + case 0x2251: { + mmio.ma = mmio.ma & ~0x00ff | data << 0; + return; + } + + //(MAH) multiplicand / dividend high + case 0x2252: { + mmio.ma = mmio.ma & ~0xff00 | data << 8; + return; + } + + //(MBL) multiplier / divisor low + case 0x2253: { + mmio.mb = mmio.mb & ~0x00ff | data << 0; + return; + } + + //(MBH) multiplier / divisor high + //multiplication / cumulative sum only resets MB + //division resets both MA and MB + case 0x2254: { + mmio.mb = mmio.mb & ~0xff00 | data << 8; + + if(mmio.acm == 0) { + if(mmio.md == 0) { + //signed multiplication + mmio.mr = (uint32)((int16)mmio.ma * (int16)mmio.mb); + mmio.mb = 0; + } else { + //unsigned division + if(mmio.mb == 0) { + mmio.mr = 0; + } else { + int16 dividend = mmio.ma; + uint16 divisor = mmio.mb; + uint16 remainder = dividend >= 0 ? uint16(dividend % divisor) : uint16((dividend % divisor + divisor) % divisor); + uint16 quotient = (dividend - remainder) / divisor; + mmio.mr = remainder << 16 | quotient; + } + mmio.ma = 0; + mmio.mb = 0; + } + } else { + //sigma (accumulative multiplication) + mmio.mr += (int16)mmio.ma * (int16)mmio.mb; + mmio.overflow = mmio.mr >> 40; + mmio.mr = (uint40)mmio.mr; + mmio.mb = 0; + } + return; + } + + //(VBD) variable-length bit processing + case 0x2258: { + mmio.hl = (data & 0x80); + mmio.vb = (data & 0x0f); + if(mmio.vb == 0) mmio.vb = 16; + + if(mmio.hl == 0) { + //fixed mode + mmio.vbit += mmio.vb; + mmio.va += (mmio.vbit >> 3); + mmio.vbit &= 7; + } + return; + } + + //(VDA) variable-length bit game pak ROM start address + case 0x2259: { mmio.va = (mmio.va & 0xffff00) | (data << 0); return; } + case 0x225a: { mmio.va = (mmio.va & 0xff00ff) | (data << 8); return; } + case 0x225b: { mmio.va = (mmio.va & 0x00ffff) | (data << 16); mmio.vbit = 0; return; } + + } +} + +auto SA1::writeIOShared(uint address, uint8 data) -> void { + switch(0x2200 | address & 0x1ff) { + + //(CDMA) character conversion DMA parameters + case 0x2231: { + mmio.chdend = (data & 0x80); + mmio.dmasize = (data >> 2) & 7; + mmio.dmacb = (data & 0x03); + + if(mmio.chdend) bwram.dma = false; + if(mmio.dmasize > 5) mmio.dmasize = 5; + if(mmio.dmacb > 2) mmio.dmacb = 2; + return; + } + + //(SDA) DMA source device start address + case 0x2232: { mmio.dsa = (mmio.dsa & 0xffff00) | (data << 0); return; } + case 0x2233: { mmio.dsa = (mmio.dsa & 0xff00ff) | (data << 8); return; } + case 0x2234: { mmio.dsa = (mmio.dsa & 0x00ffff) | (data << 16); return; } + + //(DDA) DMA destination start address + case 0x2235: { mmio.dda = (mmio.dda & 0xffff00) | (data << 0); return; } + case 0x2236: { mmio.dda = (mmio.dda & 0xff00ff) | (data << 8); + if(mmio.dmaen) { + if(mmio.cden == 0 && mmio.dd == DMA::DestIRAM) { + dmaNormal(); + } else if(mmio.cden == 1 && mmio.cdsel == 1) { + dmaCC1(); + } + } + return; + } + case 0x2237: { mmio.dda = (mmio.dda & 0x00ffff) | (data << 16); + if(mmio.dmaen) { + if(mmio.cden == 0 && mmio.dd == DMA::DestBWRAM) { + dmaNormal(); + } + } + return; + } + + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/iram.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/iram.cpp new file mode 100644 index 0000000000..b4b01ddff1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/iram.cpp @@ -0,0 +1,36 @@ +auto SA1::IRAM::conflict() const -> bool { + if(configuration.hacks.coprocessor.delayedSync) return false; + + if((cpu.r.mar & 0x40f800) == 0x003000) return cpu.refresh() == 0; //00-3f,80-bf:3000-37ff + return false; +} + +auto SA1::IRAM::read(uint address, uint8 data) -> uint8 { + if(!size()) return data; + address = bus.mirror(address, size()); + return WritableMemory::read(address, data); +} + +auto SA1::IRAM::write(uint address, uint8 data) -> void { + if(!size()) return; + address = bus.mirror(address, size()); + return WritableMemory::write(address, data); +} + +auto SA1::IRAM::readCPU(uint address, uint8 data) -> uint8 { + cpu.synchronizeCoprocessors(); + return read(address, data); +} + +auto SA1::IRAM::writeCPU(uint address, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + return write(address, data); +} + +auto SA1::IRAM::readSA1(uint address, uint8 data) -> uint8 { + return read(address, data); +} + +auto SA1::IRAM::writeSA1(uint address, uint8 data) -> void { + return write(address, data); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/memory.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/memory.cpp new file mode 100644 index 0000000000..d82d974b89 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/memory.cpp @@ -0,0 +1,139 @@ +auto SA1::idle() -> void { + step(); +} + +//RTx, JMx, JSx +auto SA1::idleJump() -> void { + //ROM access penalty cycle: does not apply to BWRAM or IRAM + if((r.pc.d & 0x408000) == 0x008000 //00-3f,80-bf:8000-ffff + || (r.pc.d & 0xc00000) == 0xc00000 //c0-ff:0000-ffff + ) { + step(); + if(rom.conflict()) step(); + } +} + +//Bxx +auto SA1::idleBranch() -> void { + if(r.pc.d & 1) idleJump(); +} + +auto SA1::read(uint address) -> uint8 { + r.mar = address; + uint8 data = r.mdr; + + if((address & 0x40fe00) == 0x002200 //00-3f,80-bf:2200-23ff + ) { + step(); + return r.mdr = readIOSA1(address, data); + } + + if((address & 0x408000) == 0x008000 //00-3f,80-bf:8000-ffff + || (address & 0xc00000) == 0xc00000 //c0-ff:0000-ffff + ) { + step(); + if(rom.conflict()) step(); + return r.mdr = rom.readSA1(address, data); + } + + if((address & 0x40e000) == 0x006000 //00-3f,80-bf:6000-7fff + || (address & 0xf00000) == 0x400000 //40-4f:0000-ffff + || (address & 0xf00000) == 0x600000 //60-6f:0000-ffff + ) { + step(); + step(); + if(bwram.conflict()) step(); + if(bwram.conflict()) step(); + if((address & 1 << 22) && (address & 1 << 21)) return r.mdr = bwram.readBitmap(address, data); + if((address & 1 << 22)) return r.mdr = bwram.readLinear(address, data); + return r.mdr = bwram.readSA1(address, data); + } + + if((address & 0x40f800) == 0x000000 //00-3f,80-bf:0000-07ff + || (address & 0x40f800) == 0x003000 //00-3f,80-bf:3000-37ff + ) { + step(); + if(iram.conflict()) step(); + if(iram.conflict()) step(); + return r.mdr = iram.readSA1(address, data); + } + + step(); + return data; +} + +auto SA1::write(uint address, uint8 data) -> void { + r.mar = address; + r.mdr = data; + + if((address & 0x40fe00) == 0x002200 //00-3f,80-bf:2200-23ff + ) { + step(); + return writeIOSA1(address, data); + } + + if((address & 0x408000) == 0x008000 //00-3f,80-bf:8000-ffff + || (address & 0xc00000) == 0xc00000 //c0-ff:0000-ffff + ) { + step(); + if(rom.conflict()) step(); + return rom.writeSA1(address, data); + } + + if((address & 0x40e000) == 0x006000 //00-3f,80-bf:6000-7fff + || (address & 0xf00000) == 0x400000 //40-4f:0000-ffff + || (address & 0xf00000) == 0x600000 //60-6f:0000-ffff + ) { + step(); + step(); + if(bwram.conflict()) step(); + if(bwram.conflict()) step(); + if((address & 1 << 22) && (address & 1 << 21)) return bwram.writeBitmap(address, data); + if((address & 1 << 22)) return bwram.writeLinear(address, data); + return bwram.writeSA1(address, data); + } + + if((address & 0x40f800) == 0x000000 //00-3f,80-bf:0000-07ff + || (address & 0x40f800) == 0x003000 //00-3f,80-bf:3000-37ff + ) { + step(); + if(iram.conflict()) step(); + if(iram.conflict()) step(); + return iram.writeSA1(address, data); + } + + step(); + return; +} + +//$230c (VDPL), $230d (VDPH) use this bus to read variable-length data. +//this is used both to keep VBR-reads from accessing MMIO registers, and +//to avoid syncing the S-CPU and SA-1*; as both chips are able to access +//these ports. +auto SA1::readVBR(uint address, uint8 data) -> uint8 { + if((address & 0x408000) == 0x008000 //00-3f,80-bf:8000-ffff + || (address & 0xc00000) == 0xc00000 //c0-ff:0000-ffff + ) { + return rom.readSA1(address, data); + } + + if((address & 0x40e000) == 0x006000 //00-3f,80-bf:6000-7fff + || (address & 0xf00000) == 0x400000 //40-4f:0000-ffff + ) { + return bwram.read(address, data); + } + + if((address & 0x40f800) == 0x000000 //00-3f,80-bf:0000-07ff + || (address & 0x40f800) == 0x003000 //00-3f,80-bf:3000-37ff + ) { + return iram.read(address, data); + } + + return 0xff; +} + +auto SA1::readDisassembler(uint address) -> uint8 { + //TODO: this is a hack; SA1::read() advances the clock; whereas Bus::read() does not + //the CPU and SA1 bus are identical for ROM, but have differences in BWRAM and IRAM + return bus.read(address, r.mdr); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/rom.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/rom.cpp new file mode 100644 index 0000000000..1ce356470a --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/rom.cpp @@ -0,0 +1,71 @@ +auto SA1::ROM::conflict() const -> bool { + if(configuration.hacks.coprocessor.delayedSync) return false; + + if((cpu.r.mar & 0x408000) == 0x008000) return true; //00-3f,80-bf:8000-ffff + if((cpu.r.mar & 0xc00000) == 0xc00000) return true; //c0-ff:0000-ffff + return false; +} + +auto SA1::ROM::read(uint address, uint8 data) -> uint8 { + address = bus.mirror(address, size()); + return ReadableMemory::read(address, data); +} + +auto SA1::ROM::write(uint address, uint8 data) -> void { +} + +//note: addresses are translated prior to invoking this function: +//00-3f,80-bf:8000-ffff mask=0x408000 => 00-3f:0000-ffff +//c0-ff:0000-ffff => untranslated +auto SA1::ROM::readCPU(uint address, uint8 data) -> uint8 { + //reset vector overrides + if((address & 0xffffe0) == 0x007fe0) { //00:ffe0-ffef + if(address == 0x7fea && sa1.mmio.cpu_nvsw) return sa1.mmio.snv >> 0; + if(address == 0x7feb && sa1.mmio.cpu_nvsw) return sa1.mmio.snv >> 8; + if(address == 0x7fee && sa1.mmio.cpu_ivsw) return sa1.mmio.siv >> 0; + if(address == 0x7fef && sa1.mmio.cpu_ivsw) return sa1.mmio.siv >> 8; + } + + static auto read = [](uint address) { + if((address & 0x400000) && bsmemory.size()) return bsmemory.read(address, 0x00); + return sa1.rom.read(address); + }; + + bool lo = address < 0x400000; //*bmode==0 only applies to 00-3f,80-bf:8000-ffff + address &= 0x3fffff; + + if(address < 0x100000) { //00-1f,8000-ffff; c0-cf:0000-ffff + if(lo && sa1.mmio.cbmode == 0) return read(address); + return read(sa1.mmio.cb << 20 | address & 0x0fffff); + } + + if(address < 0x200000) { //20-3f,8000-ffff; d0-df:0000-ffff + if(lo && sa1.mmio.dbmode == 0) return read(address); + return read(sa1.mmio.db << 20 | address & 0x0fffff); + } + + if(address < 0x300000) { //80-9f,8000-ffff; e0-ef:0000-ffff + if(lo && sa1.mmio.ebmode == 0) return read(address); + return read(sa1.mmio.eb << 20 | address & 0x0fffff); + } + + if(address < 0x400000) { //a0-bf,8000-ffff; f0-ff:0000-ffff + if(lo && sa1.mmio.fbmode == 0) return read(address); + return read(sa1.mmio.fb << 20 | address & 0x0fffff); + } + + unreachable; +} + +auto SA1::ROM::writeCPU(uint address, uint8 data) -> void { +} + +auto SA1::ROM::readSA1(uint address, uint8 data) -> uint8 { + if((address & 0x408000) == 0x008000) { + address = (address & 0x800000) >> 2 | (address & 0x3f0000) >> 1 | address & 0x007fff; + } + return readCPU(address, data); +} + +auto SA1::ROM::writeSA1(uint address, uint8 data) -> void { +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/sa1.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/sa1.cpp new file mode 100644 index 0000000000..de9593e724 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/sa1.cpp @@ -0,0 +1,314 @@ +#include + +namespace SuperFamicom { + +#include "rom.cpp" +#include "bwram.cpp" +#include "iram.cpp" +#include "dma.cpp" +#include "memory.cpp" +#include "io.cpp" +#include "serialization.cpp" +SA1 sa1; + +auto SA1::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto SA1::Enter() -> void { + while(true) { + scheduler.synchronize(); + sa1.main(); + } +} + +auto SA1::main() -> void { + if(r.wai) return instructionWait(); + if(r.stp) return instructionStop(); + + if(mmio.sa1_rdyb || mmio.sa1_resb) { + //SA-1 co-processor is asleep + step(); + return; + } + + if(status.interruptPending) { + status.interruptPending = false; + interrupt(); + return; + } + + instruction(); +} + +//override R65816::interrupt() to support SA-1 vector location IO registers +auto SA1::interrupt() -> void { + read(r.pc.d); + idle(); + if(!r.e) push(r.pc.b); + push(r.pc.h); + push(r.pc.l); + push(r.e ? r.p & ~0x10 : r.p); + r.p.i = 1; + r.p.d = 0; + r.pc.d = r.vector; //PC bank set to 0x00 +} + +auto SA1::lastCycle() -> void { + if(mmio.sa1_nmi && !mmio.sa1_nmicl) { + status.interruptPending = true; + r.vector = mmio.cnv; + mmio.sa1_nmifl = true; + mmio.sa1_nmicl = 1; + r.wai = false; + } else if(!r.p.i) { + if(mmio.timer_irqen && !mmio.timer_irqcl) { + status.interruptPending = true; + r.vector = mmio.civ; + mmio.timer_irqfl = true; + r.wai = false; + } else if(mmio.dma_irqen && !mmio.dma_irqcl) { + status.interruptPending = true; + r.vector = mmio.civ; + mmio.dma_irqfl = true; + r.wai = false; + } else if(mmio.sa1_irq && !mmio.sa1_irqcl) { + status.interruptPending = true; + r.vector = mmio.civ; + mmio.sa1_irqfl = true; + r.wai = false; + } + } +} + +auto SA1::interruptPending() const -> bool { + return status.interruptPending; +} + +auto SA1::step() -> void { + clock += (uint64_t)cpu.frequency << 1; + synchronizeCPU(); + + //adjust counters: + //note that internally, status counters are in clocks; + //whereas MMIO register counters are in dots (4 clocks = 1 dot) + if(mmio.hvselb == 0) { + //HV timer + status.hcounter += 2; + if(status.hcounter >= 1364) { + status.hcounter = 0; + if(++status.vcounter >= status.scanlines) { + status.vcounter = 0; + } + } + } else { + //linear timer + status.hcounter += 2; + status.vcounter += status.hcounter >> 11; + status.hcounter &= 0x07ff; + status.vcounter &= 0x01ff; + } + + //test counters for timer IRQ + switch(mmio.hen << 0 | mmio.ven << 1) { + case 0: break; + case 1: if(status.hcounter == mmio.hcnt << 2) triggerIRQ(); break; + case 2: if(status.vcounter == mmio.vcnt && status.hcounter == 0) triggerIRQ(); break; + case 3: if(status.vcounter == mmio.vcnt && status.hcounter == mmio.hcnt << 2) triggerIRQ(); break; + } +} + +auto SA1::triggerIRQ() -> void { + mmio.timer_irqfl = true; + if(mmio.timer_irqen) mmio.timer_irqcl = 0; +} + +auto SA1::unload() -> void { + rom.reset(); + iram.reset(); + bwram.reset(); +} + +auto SA1::power() -> void { + double overclock = max(1.0, min(4.0, configuration.hacks.sa1.overclock / 100.0)); + + WDC65816::power(); + create(SA1::Enter, system.cpuFrequency() * overclock); + + bwram.dma = false; + for(uint address : range(iram.size())) { + iram.write(address, 0x00); + } + + status.counter = 0; + + status.interruptPending = false; + + status.scanlines = Region::PAL() ? 312 : 262; + status.vcounter = 0; + status.hcounter = 0; + + dma.line = 0; + + //$2200 CCNT + mmio.sa1_irq = false; + mmio.sa1_rdyb = false; + mmio.sa1_resb = true; + mmio.sa1_nmi = false; + mmio.smeg = 0; + + //$2201 SIE + mmio.cpu_irqen = false; + mmio.chdma_irqen = false; + + //$2202 SIC + mmio.cpu_irqcl = false; + mmio.chdma_irqcl = false; + + //$2203,$2204 CRV + mmio.crv = 0x0000; + + //$2205,$2206 CNV + mmio.cnv = 0x0000; + + //$2207,$2208 CIV + mmio.civ = 0x0000; + + //$2209 SCNT + mmio.cpu_irq = false; + mmio.cpu_ivsw = false; + mmio.cpu_nvsw = false; + mmio.cmeg = 0; + + //$220a CIE + mmio.sa1_irqen = false; + mmio.timer_irqen = false; + mmio.dma_irqen = false; + mmio.sa1_nmien = false; + + //$220b CIC + mmio.sa1_irqcl = false; + mmio.timer_irqcl = false; + mmio.dma_irqcl = false; + mmio.sa1_nmicl = false; + + //$220c,$220d SNV + mmio.snv = 0x0000; + + //$220e,$220f SIV + mmio.siv = 0x0000; + + //$2210 + mmio.hvselb = false; + mmio.ven = false; + mmio.hen = false; + + //$2212,$2213 HCNT + mmio.hcnt = 0x0000; + + //$2214,$2215 VCNT + mmio.vcnt = 0x0000; + + //$2220-2223 CXB, DXB, EXB, FXB + mmio.cbmode = 0; + mmio.dbmode = 0; + mmio.ebmode = 0; + mmio.fbmode = 0; + + mmio.cb = 0x00; + mmio.db = 0x01; + mmio.eb = 0x02; + mmio.fb = 0x03; + + //$2224 BMAPS + mmio.sbm = 0x00; + + //$2225 BMAP + mmio.sw46 = false; + mmio.cbm = 0x00; + + //$2226 SWBE + mmio.swen = false; + + //$2227 CWBE + mmio.cwen = false; + + //$2228 BWPA + mmio.bwp = 0x0f; + + //$2229 SIWP + mmio.siwp = 0x00; + + //$222a CIWP + mmio.ciwp = 0x00; + + //$2230 DCNT + mmio.dmaen = false; + mmio.dprio = false; + mmio.cden = false; + mmio.cdsel = false; + mmio.dd = 0; + mmio.sd = 0; + + //$2231 CDMA + mmio.chdend = false; + mmio.dmasize = 0; + mmio.dmacb = 0; + + //$2232-$2234 SDA + mmio.dsa = 0x000000; + + //$2235-$2237 DDA + mmio.dda = 0x000000; + + //$2238,$2239 DTC + mmio.dtc = 0x0000; + + //$223f BBF + mmio.bbf = 0; + + //$2240-$224f BRF + for(auto& n : mmio.brf) n = 0x00; + + //$2250 MCNT + mmio.acm = 0; + mmio.md = 0; + + //$2251,$2252 MA + mmio.ma = 0x0000; + + //$2253,$2254 MB + mmio.mb = 0x0000; + + //$2258 VBD + mmio.hl = false; + mmio.vb = 16; + + //$2259-$225b + mmio.va = 0x000000; + mmio.vbit = 0; + + //$2300 SFR + mmio.cpu_irqfl = false; + mmio.chdma_irqfl = false; + + //$2301 CFR + mmio.sa1_irqfl = false; + mmio.timer_irqfl = false; + mmio.dma_irqfl = false; + mmio.sa1_nmifl = false; + + //$2302,$2303 HCR + mmio.hcr = 0x0000; + + //$2304,$2305 VCR + mmio.vcr = 0x0000; + + //$2306-$230a MR + mmio.mr = 0; + + //$230b + mmio.overflow = false; +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/sa1.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/sa1.hpp new file mode 100644 index 0000000000..66dc6c4c3e --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/sa1.hpp @@ -0,0 +1,287 @@ +//Super Accelerator (SA-1) + +struct SA1 : Processor::WDC65816, Thread { + inline auto synchronizing() const -> bool override { return scheduler.synchronizing(); } + + //sa1.cpp + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto step() -> void; + auto interrupt() -> void override; + + alwaysinline auto triggerIRQ() -> void; + alwaysinline auto lastCycle() -> void override; + alwaysinline auto interruptPending() const -> bool override; + + auto unload() -> void; + auto power() -> void; + + //dma.cpp + struct DMA { + enum CDEN : uint { DmaNormal = 0, DmaCharConversion = 1 }; + enum SD : uint { SourceROM = 0, SourceBWRAM = 1, SourceIRAM = 2 }; + enum DD : uint { DestIRAM = 0, DestBWRAM = 1 }; + uint line; + }; + + auto dmaNormal() -> void; + auto dmaCC1() -> void; + auto dmaCC1Read(uint addr) -> uint8; + auto dmaCC2() -> void; + + //memory.cpp + alwaysinline auto conflictROM() const -> bool; + alwaysinline auto conflictBWRAM() const -> bool; + alwaysinline auto conflictIRAM() const -> bool; + + alwaysinline auto idle() -> void override; + alwaysinline auto idleJump() -> void override; + alwaysinline auto idleBranch() -> void override; + alwaysinline auto read(uint address) -> uint8 override; + alwaysinline auto write(uint address, uint8 data) -> void override; + auto readVBR(uint address, uint8 data = 0) -> uint8; + auto readDisassembler(uint address) -> uint8 override; + + //io.cpp + auto readIOCPU(uint address, uint8 data) -> uint8; + auto readIOSA1(uint address, uint8 data) -> uint8; + auto writeIOCPU(uint address, uint8 data) -> void; + auto writeIOSA1(uint address, uint8 data) -> void; + auto writeIOShared(uint address, uint8 data) -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + struct ROM : ReadableMemory { + //rom.cpp + alwaysinline auto conflict() const -> bool; + + alwaysinline auto read(uint address, uint8 data = 0) -> uint8 override; + alwaysinline auto write(uint address, uint8 data) -> void override; + + auto readCPU(uint address, uint8 data = 0) -> uint8; + auto writeCPU(uint address, uint8 data) -> void; + + auto readSA1(uint address, uint8 data = 0) -> uint8; + auto writeSA1(uint address, uint8 data) -> void; + } rom; + + struct BWRAM : WritableMemory { + //bwram.cpp + alwaysinline auto conflict() const -> bool; + + alwaysinline auto read(uint address, uint8 data = 0) -> uint8 override; + alwaysinline auto write(uint address, uint8 data) -> void override; + + auto readCPU(uint address, uint8 data = 0) -> uint8; + auto writeCPU(uint address, uint8 data) -> void; + + auto readSA1(uint address, uint8 data = 0) -> uint8; + auto writeSA1(uint address, uint8 data) -> void; + + auto readLinear(uint address, uint8 data = 0) -> uint8; + auto writeLinear(uint address, uint8 data) -> void; + + auto readBitmap(uint20 address, uint8 data = 0) -> uint8; + auto writeBitmap(uint20 address, uint8 data) -> void; + + bool dma; + } bwram; + + struct IRAM : WritableMemory { + //iram.cpp + alwaysinline auto conflict() const -> bool; + + alwaysinline auto read(uint address, uint8 data = 0) -> uint8 override; + alwaysinline auto write(uint address, uint8 data) -> void override; + + auto readCPU(uint address, uint8 data) -> uint8; + auto writeCPU(uint address, uint8 data) -> void; + + auto readSA1(uint address, uint8 data = 0) -> uint8; + auto writeSA1(uint address, uint8 data) -> void; + } iram; + +private: + DMA dma; + + struct Status { + uint8 counter; + + bool interruptPending; + + uint16 scanlines; + uint16 vcounter; + uint16 hcounter; + } status; + + struct MMIO { + //$2200 CCNT + bool sa1_irq; + bool sa1_rdyb; + bool sa1_resb; + bool sa1_nmi; + uint8 smeg; + + //$2201 SIE + bool cpu_irqen; + bool chdma_irqen; + + //$2202 SIC + bool cpu_irqcl; + bool chdma_irqcl; + + //$2203,$2204 CRV + uint16 crv; + + //$2205,$2206 CNV + uint16 cnv; + + //$2207,$2208 CIV + uint16 civ; + + //$2209 SCNT + bool cpu_irq; + bool cpu_ivsw; + bool cpu_nvsw; + uint8 cmeg; + + //$220a CIE + bool sa1_irqen; + bool timer_irqen; + bool dma_irqen; + bool sa1_nmien; + + //$220b CIC + bool sa1_irqcl; + bool timer_irqcl; + bool dma_irqcl; + bool sa1_nmicl; + + //$220c,$220d SNV + uint16 snv; + + //$220e,$220f SIV + uint16 siv; + + //$2210 TMC + bool hvselb; + bool ven; + bool hen; + + //$2212,$2213 + uint16 hcnt; + + //$2214,$2215 + uint16 vcnt; + + //$2220 CXB + bool cbmode; + uint cb; + + //$2221 DXB + bool dbmode; + uint db; + + //$2222 EXB + bool ebmode; + uint eb; + + //$2223 FXB + bool fbmode; + uint fb; + + //$2224 BMAPS + uint8 sbm; + + //$2225 BMAP + bool sw46; + uint8 cbm; + + //$2226 SBWE + bool swen; + + //$2227 CBWE + bool cwen; + + //$2228 BWPA + uint8 bwp; + + //$2229 SIWP + uint8 siwp; + + //$222a CIWP + uint8 ciwp; + + //$2230 DCNT + bool dmaen; + bool dprio; + bool cden; + bool cdsel; + bool dd; + uint8 sd; + + //$2231 CDMA + bool chdend; + uint8 dmasize; + uint8 dmacb; + + //$2232-$2234 SDA + uint32 dsa; + + //$2235-$2237 DDA + uint32 dda; + + //$2238,$2239 DTC + uint16 dtc; + + //$223f BBF + bool bbf; + + //$2240-224f BRF + uint8 brf[16]; + + //$2250 MCNT + bool acm; + bool md; + + //$2251,$2252 MA + uint16 ma; + + //$2253,$2254 MB + uint16 mb; + + //$2258 VBD + bool hl; + uint8 vb; + + //$2259-$225b VDA + uint32 va; + uint8 vbit; + + //$2300 SFR + bool cpu_irqfl; + bool chdma_irqfl; + + //$2301 CFR + bool sa1_irqfl; + bool timer_irqfl; + bool dma_irqfl; + bool sa1_nmifl; + + //$2302,$2303 HCR + uint16 hcr; + + //$2304,$2305 VCR + uint16 vcr; + + //$2306-230a MR + uint64 mr; + + //$230b OF + bool overflow; + } mmio; +}; + +extern SA1 sa1; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/serialization.cpp new file mode 100644 index 0000000000..d528563c10 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sa1/serialization.cpp @@ -0,0 +1,143 @@ +auto SA1::serialize(serializer& s) -> void { + WDC65816::serialize(s); + Thread::serialize(s); + + s.array(iram.data(), iram.size()); + s.array(bwram.data(), bwram.size()); + s.integer(bwram.dma); + + //sa1.hpp + s.integer(status.counter); + + s.integer(status.interruptPending); + + s.integer(status.scanlines); + s.integer(status.vcounter); + s.integer(status.hcounter); + + //dma/dma.hpp + s.integer(dma.line); + + //mmio/mmio.hpp + s.integer(mmio.sa1_irq); + s.integer(mmio.sa1_rdyb); + s.integer(mmio.sa1_resb); + s.integer(mmio.sa1_nmi); + s.integer(mmio.smeg); + + s.integer(mmio.cpu_irqen); + s.integer(mmio.chdma_irqen); + + s.integer(mmio.cpu_irqcl); + s.integer(mmio.chdma_irqcl); + + s.integer(mmio.crv); + + s.integer(mmio.cnv); + + s.integer(mmio.civ); + + s.integer(mmio.cpu_irq); + s.integer(mmio.cpu_ivsw); + s.integer(mmio.cpu_nvsw); + s.integer(mmio.cmeg); + + s.integer(mmio.sa1_irqen); + s.integer(mmio.timer_irqen); + s.integer(mmio.dma_irqen); + s.integer(mmio.sa1_nmien); + + s.integer(mmio.sa1_irqcl); + s.integer(mmio.timer_irqcl); + s.integer(mmio.dma_irqcl); + s.integer(mmio.sa1_nmicl); + + s.integer(mmio.snv); + + s.integer(mmio.siv); + + s.integer(mmio.hvselb); + s.integer(mmio.ven); + s.integer(mmio.hen); + + s.integer(mmio.hcnt); + + s.integer(mmio.vcnt); + + s.integer(mmio.cbmode); + s.integer(mmio.cb); + + s.integer(mmio.dbmode); + s.integer(mmio.db); + + s.integer(mmio.ebmode); + s.integer(mmio.eb); + + s.integer(mmio.fbmode); + s.integer(mmio.fb); + + s.integer(mmio.sbm); + + s.integer(mmio.sw46); + s.integer(mmio.cbm); + + s.integer(mmio.swen); + + s.integer(mmio.cwen); + + s.integer(mmio.bwp); + + s.integer(mmio.siwp); + + s.integer(mmio.ciwp); + + s.integer(mmio.dmaen); + s.integer(mmio.dprio); + s.integer(mmio.cden); + s.integer(mmio.cdsel); + s.integer(mmio.dd); + s.integer(mmio.sd); + + s.integer(mmio.chdend); + s.integer(mmio.dmasize); + s.integer(mmio.dmacb); + + s.integer(mmio.dsa); + + s.integer(mmio.dda); + + s.integer(mmio.dtc); + + s.integer(mmio.bbf); + + s.array(mmio.brf); + + s.integer(mmio.acm); + s.integer(mmio.md); + + s.integer(mmio.ma); + + s.integer(mmio.mb); + + s.integer(mmio.hl); + s.integer(mmio.vb); + + s.integer(mmio.va); + s.integer(mmio.vbit); + + s.integer(mmio.cpu_irqfl); + s.integer(mmio.chdma_irqfl); + + s.integer(mmio.sa1_irqfl); + s.integer(mmio.timer_irqfl); + s.integer(mmio.dma_irqfl); + s.integer(mmio.sa1_nmifl); + + s.integer(mmio.hcr); + + s.integer(mmio.vcr); + + s.integer(mmio.mr); + + s.integer(mmio.overflow); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/decompressor.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/decompressor.cpp new file mode 100644 index 0000000000..edce9e738e --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/decompressor.cpp @@ -0,0 +1,286 @@ +//S-DD1 decompression algorithm implementation +//original code written by Andreas Naive (public domain license) +//bsnes port written by byuu + +//note: decompression module does not need to be serialized with bsnes +//this is because decompression only runs during DMA, and bsnes will complete +//any pending DMA transfers prior to serialization. + +//input manager + +auto SDD1::Decompressor::IM::init(uint offset_) -> void { + offset = offset_; + bitCount = 4; +} + +auto SDD1::Decompressor::IM::getCodeWord(uint8 codeLength) -> uint8 { + uint8 codeWord; + uint8 compCount; + + codeWord = sdd1.mmcRead(offset) << bitCount; + bitCount++; + + if(codeWord & 0x80) { + codeWord |= sdd1.mmcRead(offset + 1) >> (9 - bitCount); + bitCount += codeLength; + } + + if(bitCount & 0x08) { + offset++; + bitCount &= 0x07; + } + + return codeWord; +} + +//golomb-code decoder + +const uint8 SDD1::Decompressor::GCD::runCount[] = { + 0x00, 0x00, 0x01, 0x00, 0x03, 0x01, 0x02, 0x00, + 0x07, 0x03, 0x05, 0x01, 0x06, 0x02, 0x04, 0x00, + 0x0f, 0x07, 0x0b, 0x03, 0x0d, 0x05, 0x09, 0x01, + 0x0e, 0x06, 0x0a, 0x02, 0x0c, 0x04, 0x08, 0x00, + 0x1f, 0x0f, 0x17, 0x07, 0x1b, 0x0b, 0x13, 0x03, + 0x1d, 0x0d, 0x15, 0x05, 0x19, 0x09, 0x11, 0x01, + 0x1e, 0x0e, 0x16, 0x06, 0x1a, 0x0a, 0x12, 0x02, + 0x1c, 0x0c, 0x14, 0x04, 0x18, 0x08, 0x10, 0x00, + 0x3f, 0x1f, 0x2f, 0x0f, 0x37, 0x17, 0x27, 0x07, + 0x3b, 0x1b, 0x2b, 0x0b, 0x33, 0x13, 0x23, 0x03, + 0x3d, 0x1d, 0x2d, 0x0d, 0x35, 0x15, 0x25, 0x05, + 0x39, 0x19, 0x29, 0x09, 0x31, 0x11, 0x21, 0x01, + 0x3e, 0x1e, 0x2e, 0x0e, 0x36, 0x16, 0x26, 0x06, + 0x3a, 0x1a, 0x2a, 0x0a, 0x32, 0x12, 0x22, 0x02, + 0x3c, 0x1c, 0x2c, 0x0c, 0x34, 0x14, 0x24, 0x04, + 0x38, 0x18, 0x28, 0x08, 0x30, 0x10, 0x20, 0x00, + 0x7f, 0x3f, 0x5f, 0x1f, 0x6f, 0x2f, 0x4f, 0x0f, + 0x77, 0x37, 0x57, 0x17, 0x67, 0x27, 0x47, 0x07, + 0x7b, 0x3b, 0x5b, 0x1b, 0x6b, 0x2b, 0x4b, 0x0b, + 0x73, 0x33, 0x53, 0x13, 0x63, 0x23, 0x43, 0x03, + 0x7d, 0x3d, 0x5d, 0x1d, 0x6d, 0x2d, 0x4d, 0x0d, + 0x75, 0x35, 0x55, 0x15, 0x65, 0x25, 0x45, 0x05, + 0x79, 0x39, 0x59, 0x19, 0x69, 0x29, 0x49, 0x09, + 0x71, 0x31, 0x51, 0x11, 0x61, 0x21, 0x41, 0x01, + 0x7e, 0x3e, 0x5e, 0x1e, 0x6e, 0x2e, 0x4e, 0x0e, + 0x76, 0x36, 0x56, 0x16, 0x66, 0x26, 0x46, 0x06, + 0x7a, 0x3a, 0x5a, 0x1a, 0x6a, 0x2a, 0x4a, 0x0a, + 0x72, 0x32, 0x52, 0x12, 0x62, 0x22, 0x42, 0x02, + 0x7c, 0x3c, 0x5c, 0x1c, 0x6c, 0x2c, 0x4c, 0x0c, + 0x74, 0x34, 0x54, 0x14, 0x64, 0x24, 0x44, 0x04, + 0x78, 0x38, 0x58, 0x18, 0x68, 0x28, 0x48, 0x08, + 0x70, 0x30, 0x50, 0x10, 0x60, 0x20, 0x40, 0x00, +}; + +auto SDD1::Decompressor::GCD::getRunCount(uint8 codeNumber, uint8& mpsCount, bool& lpsIndex) -> void { + uint8 codeWord = self.im.getCodeWord(codeNumber); + + if(codeWord & 0x80) { + lpsIndex = 1; + mpsCount = runCount[codeWord >> (codeNumber ^ 0x07)]; + } else { + mpsCount = 1 << codeNumber; + } +} + +//bits generator + +auto SDD1::Decompressor::BG::init() -> void { + mpsCount = 0; + lpsIndex = 0; +} + +auto SDD1::Decompressor::BG::getBit(bool& endOfRun) -> uint8 { + if(!(mpsCount || lpsIndex)) self.gcd.getRunCount(codeNumber, mpsCount, lpsIndex); + + uint8 bit; + if(mpsCount) { + bit = 0; + mpsCount--; + } else { + bit = 1; + lpsIndex = 0; + } + + endOfRun = !(mpsCount || lpsIndex); + return bit; +} + +//probability estimation module + +const SDD1::Decompressor::PEM::State SDD1::Decompressor::PEM::evolutionTable[33] = { + {0, 25, 25}, + {0, 2, 1}, + {0, 3, 1}, + {0, 4, 2}, + {0, 5, 3}, + {1, 6, 4}, + {1, 7, 5}, + {1, 8, 6}, + {1, 9, 7}, + {2, 10, 8}, + {2, 11, 9}, + {2, 12, 10}, + {2, 13, 11}, + {3, 14, 12}, + {3, 15, 13}, + {3, 16, 14}, + {3, 17, 15}, + {4, 18, 16}, + {4, 19, 17}, + {5, 20, 18}, + {5, 21, 19}, + {6, 22, 20}, + {6, 23, 21}, + {7, 24, 22}, + {7, 24, 23}, + {0, 26, 1}, + {1, 27, 2}, + {2, 28, 4}, + {3, 29, 8}, + {4, 30, 12}, + {5, 31, 16}, + {6, 32, 18}, + {7, 24, 22}, +}; + +auto SDD1::Decompressor::PEM::init() -> void { + for(auto n : range(32)) { + contextInfo[n].status = 0; + contextInfo[n].mps = 0; + } +} + +auto SDD1::Decompressor::PEM::getBit(uint8 context) -> uint8 { + ContextInfo& info = contextInfo[context]; + uint8 currentStatus = info.status; + uint8 currentMps = info.mps; + const State& s = SDD1::Decompressor::PEM::evolutionTable[currentStatus]; + + uint8 bit; + bool endOfRun; + switch(s.codeNumber) { + case 0: bit = self.bg0.getBit(endOfRun); break; + case 1: bit = self.bg1.getBit(endOfRun); break; + case 2: bit = self.bg2.getBit(endOfRun); break; + case 3: bit = self.bg3.getBit(endOfRun); break; + case 4: bit = self.bg4.getBit(endOfRun); break; + case 5: bit = self.bg5.getBit(endOfRun); break; + case 6: bit = self.bg6.getBit(endOfRun); break; + case 7: bit = self.bg7.getBit(endOfRun); break; + } + + if(endOfRun) { + if(bit) { + if(!(currentStatus & 0xfe)) info.mps ^= 0x01; + info.status = s.nextIfLps; + } else { + info.status = s.nextIfMps; + } + } + + return bit ^ currentMps; +} + +//context model + +auto SDD1::Decompressor::CM::init(uint offset) -> void { + bitplanesInfo = sdd1.mmcRead(offset) & 0xc0; + contextBitsInfo = sdd1.mmcRead(offset) & 0x30; + bitNumber = 0; + for(auto n : range(8)) previousBitplaneBits[n] = 0; + switch(bitplanesInfo) { + case 0x00: currentBitplane = 1; break; + case 0x40: currentBitplane = 7; break; + case 0x80: currentBitplane = 3; break; + } +} + +auto SDD1::Decompressor::CM::getBit() -> uint8 { + switch(bitplanesInfo) { + case 0x00: + currentBitplane ^= 0x01; + break; + case 0x40: + currentBitplane ^= 0x01; + if(!(bitNumber & 0x7f)) currentBitplane = ((currentBitplane + 2) & 0x07); + break; + case 0x80: + currentBitplane ^= 0x01; + if(!(bitNumber & 0x7f)) currentBitplane ^= 0x02; + break; + case 0xc0: + currentBitplane = bitNumber & 0x07; + break; + } + + uint16& contextBits = previousBitplaneBits[currentBitplane]; + uint8 currentContext = (currentBitplane & 0x01) << 4; + switch(contextBitsInfo) { + case 0x00: currentContext |= ((contextBits & 0x01c0) >> 5) | (contextBits & 0x0001); break; + case 0x10: currentContext |= ((contextBits & 0x0180) >> 5) | (contextBits & 0x0001); break; + case 0x20: currentContext |= ((contextBits & 0x00c0) >> 5) | (contextBits & 0x0001); break; + case 0x30: currentContext |= ((contextBits & 0x0180) >> 5) | (contextBits & 0x0003); break; + } + + uint8 bit = self.pem.getBit(currentContext); + contextBits <<= 1; + contextBits |= bit; + bitNumber++; + return bit; +} + +//output logic + +auto SDD1::Decompressor::OL::init(uint offset) -> void { + bitplanesInfo = sdd1.mmcRead(offset) & 0xc0; + r0 = 0x01; +} + +auto SDD1::Decompressor::OL::decompress() -> uint8 { + switch(bitplanesInfo) { + case 0x00: case 0x40: case 0x80: + if(r0 == 0) { + r0 = ~r0; + return r2; + } + for(r0 = 0x80, r1 = 0, r2 = 0; r0; r0 >>= 1) { + if(self.cm.getBit()) r1 |= r0; + if(self.cm.getBit()) r2 |= r0; + } + return r1; + case 0xc0: + for(r0 = 0x01, r1 = 0; r0; r0 <<= 1) { + if(self.cm.getBit()) r1 |= r0; + } + return r1; + } + + return 0; //unreachable? +} + +//core + +SDD1::Decompressor::Decompressor(): +im(*this), gcd(*this), +bg0(*this, 0), bg1(*this, 1), bg2(*this, 2), bg3(*this, 3), +bg4(*this, 4), bg5(*this, 5), bg6(*this, 6), bg7(*this, 7), +pem(*this), cm(*this), ol(*this) { +} + +auto SDD1::Decompressor::init(uint offset) -> void { + im.init(offset); + bg0.init(); + bg1.init(); + bg2.init(); + bg3.init(); + bg4.init(); + bg5.init(); + bg6.init(); + bg7.init(); + pem.init(); + cm.init(offset); + ol.init(offset); +} + +auto SDD1::Decompressor::read() -> uint8 { + return ol.decompress(); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/decompressor.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/decompressor.hpp new file mode 100644 index 0000000000..e972c82165 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/decompressor.hpp @@ -0,0 +1,95 @@ +struct Decompressor { + struct IM { //input manager + IM(SDD1::Decompressor& self) : self(self) {} + auto init(uint offset) -> void; + auto getCodeWord(uint8 codeLength) -> uint8; + auto serialize(serializer&) -> void; + + private: + Decompressor& self; + uint offset; + uint bitCount; + }; + + struct GCD { //golomb-code decoder + GCD(SDD1::Decompressor& self) : self(self) {} + auto getRunCount(uint8 codeNumber, uint8& mpsCount, bool& lpsIndex) -> void; + auto serialize(serializer&) -> void; + + private: + Decompressor& self; + static const uint8 runCount[256]; + }; + + struct BG { //bits generator + BG(SDD1::Decompressor& self, uint8 codeNumber) : self(self), codeNumber(codeNumber) {} + auto init() -> void; + auto getBit(bool& endOfRun) -> uint8; + auto serialize(serializer&) -> void; + + private: + Decompressor& self; + const uint8 codeNumber; + uint8 mpsCount; + bool lpsIndex; + }; + + struct PEM { //probability estimation module + PEM(SDD1::Decompressor& self) : self(self) {} + auto init() -> void; + auto getBit(uint8 context) -> uint8; + auto serialize(serializer&) -> void; + + private: + Decompressor& self; + struct State { + uint8 codeNumber; + uint8 nextIfMps; + uint8 nextIfLps; + }; + static const State evolutionTable[33]; + struct ContextInfo { + uint8 status; + uint8 mps; + } contextInfo[32]; + }; + + struct CM { //context model + CM(SDD1::Decompressor& self) : self(self) {} + auto init(uint offset) -> void; + auto getBit() -> uint8; + auto serialize(serializer&) -> void; + + private: + Decompressor& self; + uint8 bitplanesInfo; + uint8 contextBitsInfo; + uint8 bitNumber; + uint8 currentBitplane; + uint16 previousBitplaneBits[8]; + }; + + struct OL { //output logic + OL(SDD1::Decompressor& self) : self(self) {} + auto init(uint offset) -> void; + auto decompress() -> uint8; + auto serialize(serializer&) -> void; + + private: + Decompressor& self; + uint8 bitplanesInfo; + uint8 r0, r1, r2; + }; + + Decompressor(); + auto init(uint offset) -> void; + auto read() -> uint8; + auto serialize(serializer&) -> void; + + IM im; + GCD gcd; + BG bg0, bg1, bg2, bg3, bg4, bg5, bg6, bg7; + PEM pem; + CM cm; + OL ol; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/sdd1.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/sdd1.cpp new file mode 100644 index 0000000000..0727e1b233 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/sdd1.cpp @@ -0,0 +1,132 @@ +#include + +namespace SuperFamicom { + +SDD1 sdd1; + +#include "decompressor.cpp" +#include "serialization.cpp" + +auto SDD1::unload() -> void { + rom.reset(); +} + +auto SDD1::power() -> void { + //hook S-CPU DMA MMIO registers to gather information for struct dma[]; + //buffer address and transfer size information for use in SDD1::mcu_read() + bus.map({&SDD1::dmaRead, &sdd1}, {&SDD1::dmaWrite, &sdd1}, "00-3f,80-bf:4300-437f"); + + r4800 = 0x00; + r4801 = 0x00; + r4804 = 0x00; + r4805 = 0x01; + r4806 = 0x02; + r4807 = 0x03; + + for(auto n : range(8)) { + dma[n].addr = 0; + dma[n].size = 0; + } + dmaReady = false; +} + +auto SDD1::ioRead(uint addr, uint8 data) -> uint8 { + addr = 0x4800 | addr & 0xf; + + switch(addr) { + case 0x4800: return r4800; + case 0x4801: return r4801; + case 0x4804: return r4804; + case 0x4805: return r4805; + case 0x4806: return r4806; + case 0x4807: return r4807; + } + + //00-3f,80-bf:4802-4803,4808-480f falls through to ROM + return rom.read(addr); +} + +auto SDD1::ioWrite(uint addr, uint8 data) -> void { + addr = 0x4800 | addr & 0xf; + + switch(addr) { + case 0x4800: r4800 = data; break; + case 0x4801: r4801 = data; break; + case 0x4804: r4804 = data & 0x8f; break; + case 0x4805: r4805 = data & 0x8f; break; + case 0x4806: r4806 = data & 0x8f; break; + case 0x4807: r4807 = data & 0x8f; break; + } +} + +auto SDD1::dmaRead(uint addr, uint8 data) -> uint8 { + return cpu.readDMA(addr, data); +} + +auto SDD1::dmaWrite(uint addr, uint8 data) -> void { + uint channel = addr >> 4 & 7; + switch(addr & 15) { + case 2: dma[channel].addr = dma[channel].addr & 0xffff00 | data << 0; break; + case 3: dma[channel].addr = dma[channel].addr & 0xff00ff | data << 8; break; + case 4: dma[channel].addr = dma[channel].addr & 0x00ffff | data << 16; break; + case 5: dma[channel].size = dma[channel].size & 0xff00 | data << 0; break; + case 6: dma[channel].size = dma[channel].size & 0x00ff | data << 8; break; + } + return cpu.writeDMA(addr, data); +} + +auto SDD1::mmcRead(uint addr) -> uint8 { + switch(addr >> 20 & 3) { + case 0: return rom.read((r4804 & 0xf) << 20 | addr & 0xfffff); //c0-cf:0000-ffff + case 1: return rom.read((r4805 & 0xf) << 20 | addr & 0xfffff); //d0-df:0000-ffff + case 2: return rom.read((r4806 & 0xf) << 20 | addr & 0xfffff); //e0-ef:0000-ffff + case 3: return rom.read((r4807 & 0xf) << 20 | addr & 0xfffff); //f0-ff:0000-ffff + } + unreachable; +} + +//map address=00-3f,80-bf:8000-ffff +//map address=c0-ff:0000-ffff +auto SDD1::mcuRead(uint addr, uint8 data) -> uint8 { + //map address=00-3f,80-bf:8000-ffff + if(!(addr & 1 << 22)) { + if(!(addr & 1 << 23) && (addr & 1 << 21) && (r4805 & 0x80)) addr &= ~(1 << 21); //20-3f:8000-ffff + if( (addr & 1 << 23) && (addr & 1 << 21) && (r4807 & 0x80)) addr &= ~(1 << 21); //a0-bf:8000-ffff + addr = addr >> 1 & 0x1f8000 | addr & 0x7fff; + return rom.read(addr); + } + + //map address=c0-ff:0000-ffff + if(r4800 & r4801) { + //at least one channel has S-DD1 decompression enabled ... + for(auto n : range(8)) { + if((r4800 & 1 << n) && (r4801 & 1 << n)) { + //S-DD1 always uses fixed transfer mode, so address will not change during transfer + if(addr == dma[n].addr) { + if(!dmaReady) { + //prepare streaming decompression + decompressor.init(addr); + dmaReady = true; + } + + //fetch a decompressed byte; once finished, disable channel and invalidate buffer + data = decompressor.read(); + if(--dma[n].size == 0) { + dmaReady = false; + r4801 &= ~(1 << n); + } + + return data; + } //address matched + } //channel enabled + } //channel loop + } //S-DD1 decompressor enabled + + //S-DD1 decompression mode inactive; return ROM data + return mmcRead(addr); +} + +auto SDD1::mcuWrite(uint addr, uint8 data) -> void { +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/sdd1.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/sdd1.hpp new file mode 100644 index 0000000000..dd20afe169 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/sdd1.hpp @@ -0,0 +1,39 @@ +struct SDD1 { + auto unload() -> void; + auto power() -> void; + + auto ioRead(uint addr, uint8 data) -> uint8; + auto ioWrite(uint addr, uint8 data) -> void; + + auto dmaRead(uint addr, uint8 data) -> uint8; + auto dmaWrite(uint addr, uint8 data) -> void; + + auto mmcRead(uint addr) -> uint8; + + auto mcuRead(uint addr, uint8 data) -> uint8; + auto mcuWrite(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; + + ReadableMemory rom; + +private: + uint8 r4800; //hard enable + uint8 r4801; //soft enable + uint8 r4804; //MMC bank 0 + uint8 r4805; //MMC bank 1 + uint8 r4806; //MMC bank 2 + uint8 r4807; //MMC bank 3 + + struct DMA { + uint24 addr; //$43x2-$43x4 -- DMA transfer address + uint16 size; //$43x5-$43x6 -- DMA transfer size + } dma[8]; + bool dmaReady; //used to initialize decompression module + +public: + #include "decompressor.hpp" + Decompressor decompressor; +}; + +extern SDD1 sdd1; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/serialization.cpp new file mode 100644 index 0000000000..b4b57a83ef --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sdd1/serialization.cpp @@ -0,0 +1,67 @@ +auto SDD1::serialize(serializer& s) -> void { + s.integer(r4800); + s.integer(r4801); + s.integer(r4804); + s.integer(r4805); + s.integer(r4806); + s.integer(r4807); + + for(auto& channel : dma) { + s.integer(channel.addr); + s.integer(channel.size); + } + s.integer(dmaReady); + + decompressor.serialize(s); +} + +auto SDD1::Decompressor::serialize(serializer& s) -> void { + im.serialize(s); + gcd.serialize(s); + bg0.serialize(s); + bg1.serialize(s); + bg2.serialize(s); + bg3.serialize(s); + bg4.serialize(s); + bg5.serialize(s); + bg6.serialize(s); + bg7.serialize(s); + pem.serialize(s); + cm.serialize(s); + ol.serialize(s); +} + +auto SDD1::Decompressor::IM::serialize(serializer& s) -> void { + s.integer(offset); + s.integer(bitCount); +} + +auto SDD1::Decompressor::GCD::serialize(serializer& s) -> void { +} + +auto SDD1::Decompressor::BG::serialize(serializer& s) -> void { + s.integer(mpsCount); + s.integer(lpsIndex); +} + +auto SDD1::Decompressor::PEM::serialize(serializer& s) -> void { + for(auto& info : contextInfo) { + s.integer(info.status); + s.integer(info.mps); + } +} + +auto SDD1::Decompressor::CM::serialize(serializer& s) -> void { + s.integer(bitplanesInfo); + s.integer(contextBitsInfo); + s.integer(bitNumber); + s.integer(currentBitplane); + s.array(previousBitplaneBits); +} + +auto SDD1::Decompressor::OL::serialize(serializer& s) -> void { + s.integer(bitplanesInfo); + s.integer(r0); + s.integer(r1); + s.integer(r2); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/memory.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/memory.cpp new file mode 100644 index 0000000000..3803f1ec79 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/memory.cpp @@ -0,0 +1,67 @@ +auto SharpRTC::rtcRead(uint4 addr) -> uint4 { + switch(addr) { + case 0: return second % 10; + case 1: return second / 10; + case 2: return minute % 10; + case 3: return minute / 10; + case 4: return hour % 10; + case 5: return hour / 10; + case 6: return day % 10; + case 7: return day / 10; + case 8: return month; + case 9: return year % 10; + case 10: return year / 10 % 10; + case 11: return year / 100; + case 12: return weekday; + default: return 0; + } +} + +auto SharpRTC::rtcWrite(uint4 addr, uint4 data) -> void { + switch(addr) { + case 0: second = second / 10 * 10 + data; break; + case 1: second = data * 10 + second % 10; break; + case 2: minute = minute / 10 * 10 + data; break; + case 3: minute = data * 10 + minute % 10; break; + case 4: hour = hour / 10 * 10 + data; break; + case 5: hour = data * 10 + hour % 10; break; + case 6: day = day / 10 * 10 + data; break; + case 7: day = data * 10 + day % 10; break; + case 8: month = data; break; + case 9: year = year / 10 * 10 + data; break; + case 10: year = year / 100 * 100 + data * 10 + year % 10; break; + case 11: year = data * 100 + year % 100; break; + case 12: weekday = data; break; + } +} + +auto SharpRTC::load(const uint8* data) -> void { + for(auto byte : range(8)) { + rtcWrite(byte * 2 + 0, data[byte] >> 0); + rtcWrite(byte * 2 + 1, data[byte] >> 4); + } + + uint64 timestamp = 0; + for(auto byte : range(8)) { + timestamp |= data[8 + byte] << (byte * 8); + } + + uint64 diff = (uint64)time(0) - timestamp; + while(diff >= 60 * 60 * 24) { tickDay(); diff -= 60 * 60 * 24; } + while(diff >= 60 * 60) { tickHour(); diff -= 60 * 60; } + while(diff >= 60) { tickMinute(); diff -= 60; } + while(diff--) tickSecond(); +} + +auto SharpRTC::save(uint8* data) -> void { + for(auto byte : range(8)) { + data[byte] = rtcRead(byte * 2 + 0) << 0; + data[byte] |= rtcRead(byte * 2 + 1) << 4; + } + + uint64 timestamp = (uint64)time(nullptr); + for(auto byte : range(8)) { + data[8 + byte] = timestamp; + timestamp >>= 8; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/serialization.cpp new file mode 100644 index 0000000000..d4666a1996 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/serialization.cpp @@ -0,0 +1,14 @@ +auto SharpRTC::serialize(serializer& s) -> void { + Thread::serialize(s); + + s.integer((uint&)state); + s.integer(index); + + s.integer(second); + s.integer(minute); + s.integer(hour); + s.integer(day); + s.integer(month); + s.integer(year); + s.integer(weekday); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/sharprtc.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/sharprtc.cpp new file mode 100644 index 0000000000..7078265729 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/sharprtc.cpp @@ -0,0 +1,134 @@ +#include + +namespace SuperFamicom { + +#include "memory.cpp" +#include "time.cpp" +#include "serialization.cpp" +SharpRTC sharprtc; + +auto SharpRTC::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto SharpRTC::Enter() -> void { + while(true) { + scheduler.synchronize(); + sharprtc.main(); + } +} + +auto SharpRTC::main() -> void { + tickSecond(); + + step(1); + synchronizeCPU(); +} + +auto SharpRTC::step(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; +} + +auto SharpRTC::initialize() -> void { + second = 0; + minute = 0; + hour = 0; + day = 0; + month = 0; + year = 0; + weekday = 0; +} + +auto SharpRTC::power() -> void { + create(SharpRTC::Enter, 1); + + state = State::Read; + index = -1; +} + +auto SharpRTC::synchronize(uint64 timestamp) -> void { + time_t systime = timestamp; + tm* timeinfo = localtime(&systime); + + second = min(59, timeinfo->tm_sec); + minute = timeinfo->tm_min; + hour = timeinfo->tm_hour; + day = timeinfo->tm_mday; + month = 1 + timeinfo->tm_mon; + year = 900 + timeinfo->tm_year; + weekday = timeinfo->tm_wday; +} + +auto SharpRTC::read(uint addr, uint8 data) -> uint8 { + addr &= 1; + + if(addr == 0) { + if(state != State::Read) return 0; + + if(index < 0) { + index++; + return 15; + } else if(index > 12) { + index = -1; + return 15; + } else { + return rtcRead(index++); + } + } + + return data; +} + +auto SharpRTC::write(uint addr, uint8 data) -> void { + addr &= 1, data &= 15; + + if(addr == 1) { + if(data == 0x0d) { + state = State::Read; + index = -1; + return; + } + + if(data == 0x0e) { + state = State::Command; + return; + } + + if(data == 0x0f) return; //unknown behavior + + if(state == State::Command) { + if(data == 0) { + state = State::Write; + index = 0; + } else if(data == 4) { + state = State::Ready; + index = -1; + //reset time + second = 0; + minute = 0; + hour = 0; + day = 0; + month = 0; + year = 0; + weekday = 0; + } else { + //unknown behavior + state = State::Ready; + } + return; + } + + if(state == State::Write) { + if(index >= 0 && index < 12) { + rtcWrite(index++, data); + if(index == 12) { + //day of week is automatically calculated and written + weekday = calculateWeekday(1000 + year, month, day); + } + } + return; + } + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/sharprtc.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/sharprtc.hpp new file mode 100644 index 0000000000..9ce97b78e6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/sharprtc.hpp @@ -0,0 +1,46 @@ +struct SharpRTC : Thread { + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; + + auto initialize() -> void; + auto power() -> void; + auto synchronize(uint64 timestamp) -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; + + enum class State : uint { Ready, Command, Read, Write } state; + int index; + + uint second; + uint minute; + uint hour; + uint day; + uint month; + uint year; + uint weekday; + + //memory.cpp + auto rtcRead(uint4 addr) -> uint4; + auto rtcWrite(uint4 addr, uint4 data) -> void; + + auto load(const uint8* data) -> void; + auto save(uint8* data) -> void; + + //time.cpp + static const uint daysInMonth[12]; + auto tickSecond() -> void; + auto tickMinute() -> void; + auto tickHour() -> void; + auto tickDay() -> void; + auto tickMonth() -> void; + auto tickYear() -> void; + + auto calculateWeekday(uint year, uint month, uint day) -> uint; +}; + +extern SharpRTC sharprtc; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/time.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/time.cpp new file mode 100644 index 0000000000..be7355df81 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/sharprtc/time.cpp @@ -0,0 +1,83 @@ +const uint SharpRTC::daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +auto SharpRTC::tickSecond() -> void { + if(++second < 60) return; + second = 0; + tickMinute(); +} + +auto SharpRTC::tickMinute() -> void { + if(++minute < 60) return; + minute = 0; + tickHour(); +} + +auto SharpRTC::tickHour() -> void { + if(++hour < 24) return; + hour = 0; + tickDay(); +} + +auto SharpRTC::tickDay() -> void { + uint days = daysInMonth[(month - 1) % 12]; + + //add one day in February for leap years + if(month == 2) { + if(year % 400 == 0) days++; + else if(year % 100 == 0); + else if(year % 4 == 0) days++; + } + + if(day++ < days) return; + day = 1; + tickMonth(); +} + +auto SharpRTC::tickMonth() -> void { + if(month++ < 12) return; + month = 1; + tickYear(); +} + +auto SharpRTC::tickYear() -> void { + year++; + year = (uint12)year; +} + +//returns day of week for specified date +//eg 0 = Sunday, 1 = Monday, ... 6 = Saturday +//usage: calculate_weekday(2008, 1, 1) returns weekday of January 1st, 2008 +auto SharpRTC::calculateWeekday(uint year, uint month, uint day) -> uint { + uint y = 1000, m = 1; //SharpRTC epoch is 1000-01-01 + uint sum = 0; //number of days passed since epoch + + year = max(1000, year); + month = max(1, min(12, month)); + day = max(1, min(31, day)); + + while(y < year) { + bool leapyear = false; + if(y % 4 == 0) { + leapyear = true; + if(y % 100 == 0 && y % 400 != 0) leapyear = false; + } + sum += 365 + leapyear; + y++; + } + + while(m < month) { + uint days = daysInMonth[(m - 1) % 12]; + bool leapyearmonth = false; + if(days == 28) { + if(y % 4 == 0) { + leapyearmonth = true; + if(y % 100 == 0 && y % 400 != 0) leapyearmonth = false; + } + } + sum += days + leapyearmonth; + m++; + } + + sum += day - 1; + return (sum + 3) % 7; //1000-01-01 was a Wednesday +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/alu.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/alu.cpp new file mode 100644 index 0000000000..6b71dbc64d --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/alu.cpp @@ -0,0 +1,83 @@ +auto SPC7110::aluMultiply() -> void { + addClocks(30); + + if(r482e & 1) { + //signed 16-bit x 16-bit multiplication + int16 r0 = (int16)(r4824 | r4825 << 8); + int16 r1 = (int16)(r4820 | r4821 << 8); + + int result = r0 * r1; + r4828 = result; + r4829 = result >> 8; + r482a = result >> 16; + r482b = result >> 24; + } else { + //unsigned 16-bit x 16-bit multiplication + uint16 r0 = (uint16)(r4824 | r4825 << 8); + uint16 r1 = (uint16)(r4820 | r4821 << 8); + + uint result = r0 * r1; + r4828 = result; + r4829 = result >> 8; + r482a = result >> 16; + r482b = result >> 24; + } + + r482f &= 0x7f; +} + +auto SPC7110::aluDivide() -> void { + addClocks(40); + + if(r482e & 1) { + //signed 32-bit x 16-bit division + int32 dividend = (int32)(r4820 | r4821 << 8 | r4822 << 16 | r4823 << 24); + int16 divisor = (int16)(r4826 | r4827 << 8); + + int32 quotient; + int16 remainder; + + if(divisor) { + quotient = (int32)(dividend / divisor); + remainder = (int32)(dividend % divisor); + } else { + //illegal division by zero + quotient = 0; + remainder = dividend; + } + + r4828 = quotient; + r4829 = quotient >> 8; + r482a = quotient >> 16; + r482b = quotient >> 24; + + r482c = remainder; + r482d = remainder >> 8; + } else { + //unsigned 32-bit x 16-bit division + uint32 dividend = (uint32)(r4820 | r4821 << 8 | r4822 << 16 | r4823 << 24); + uint16 divisor = (uint16)(r4826 | r4827 << 8); + + uint32 quotient; + uint16 remainder; + + if(divisor) { + quotient = (uint32)(dividend / divisor); + remainder = (uint16)(dividend % divisor); + } else { + //illegal division by zero + quotient = 0; + remainder = dividend; + } + + r4828 = quotient; + r4829 = quotient >> 8; + r482a = quotient >> 16; + r482b = quotient >> 24; + + r482c = remainder; + r482d = remainder >> 8; + } + + r482f &= 0x7f; +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/data.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/data.cpp new file mode 100644 index 0000000000..b10c0f1080 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/data.cpp @@ -0,0 +1,58 @@ +auto SPC7110::dataromRead(uint addr) -> uint8 { + uint size = 1 << (r4834 & 3); //size in MB + uint mask = 0x100000 * size - 1; + uint offset = addr & mask; + if((r4834 & 3) != 3 && (addr & 0x400000)) return 0x00; + return drom.read(Bus::mirror(offset, drom.size())); +} + +auto SPC7110::dataOffset() -> uint { return r4811 | r4812 << 8 | r4813 << 16; } +auto SPC7110::dataAdjust() -> uint { return r4814 | r4815 << 8; } +auto SPC7110::dataStride() -> uint { return r4816 | r4817 << 8; } +auto SPC7110::setDataOffset(uint addr) -> void { r4811 = addr; r4812 = addr >> 8; r4813 = addr >> 16; } +auto SPC7110::setDataAdjust(uint addr) -> void { r4814 = addr; r4815 = addr >> 8; } + +auto SPC7110::dataPortRead() -> void { + uint offset = dataOffset(); + uint adjust = r4818 & 2 ? dataAdjust() : 0; + if(r4818 & 8) adjust = (int16)adjust; + r4810 = dataromRead(offset + adjust); +} + +auto SPC7110::dataPortIncrement4810() -> void { + uint offset = dataOffset(); + uint stride = r4818 & 1 ? dataStride() : 1; + uint adjust = dataAdjust(); + if(r4818 & 4) stride = (int16)stride; + if(r4818 & 8) adjust = (int16)adjust; + if((r4818 & 16) == 0) setDataOffset(offset + stride); + if((r4818 & 16) != 0) setDataAdjust(adjust + stride); + dataPortRead(); +} + +auto SPC7110::dataPortIncrement4814() -> void { + if(r4818 >> 5 != 1) return; + uint offset = dataOffset(); + uint adjust = dataAdjust(); + if(r4818 & 8) adjust = (int16)adjust; + setDataOffset(offset + adjust); + dataPortRead(); +} + +auto SPC7110::dataPortIncrement4815() -> void { + if(r4818 >> 5 != 2) return; + uint offset = dataOffset(); + uint adjust = dataAdjust(); + if(r4818 & 8) adjust = (int16)adjust; + setDataOffset(offset + adjust); + dataPortRead(); +} + +auto SPC7110::dataPortIncrement481a() -> void { + if(r4818 >> 5 != 3) return; + uint offset = dataOffset(); + uint adjust = dataAdjust(); + if(r4818 & 8) adjust = (int16)adjust; + setDataOffset(offset + adjust); + dataPortRead(); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/dcu.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/dcu.cpp new file mode 100644 index 0000000000..b91f2b63bb --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/dcu.cpp @@ -0,0 +1,57 @@ +#include "decompressor.cpp" + +auto SPC7110::dcuLoadAddress() -> void { + uint table = r4801 | r4802 << 8 | r4803 << 16; + uint index = r4804 << 2; + + uint address = table + index; + dcuMode = dataromRead(address + 0); + dcuAddress = dataromRead(address + 1) << 16; + dcuAddress |= dataromRead(address + 2) << 8; + dcuAddress |= dataromRead(address + 3) << 0; +} + +auto SPC7110::dcuBeginTransfer() -> void { + if(dcuMode == 3) return; //invalid mode + + addClocks(20); + decompressor->initialize(dcuMode, dcuAddress); + decompressor->decode(); + + uint seek = r480b & 2 ? r4805 | r4806 << 8 : 0; + while(seek--) decompressor->decode(); + + r480c |= 0x80; + dcuOffset = 0; +} + +auto SPC7110::dcuRead() -> uint8 { + if((r480c & 0x80) == 0) return 0x00; + + if(dcuOffset == 0) { + for(auto row : range(8)) { + switch(decompressor->bpp) { + case 1: + dcuTile[row] = decompressor->result; + break; + case 2: + dcuTile[row * 2 + 0] = decompressor->result >> 0; + dcuTile[row * 2 + 1] = decompressor->result >> 8; + break; + case 4: + dcuTile[row * 2 + 0] = decompressor->result >> 0; + dcuTile[row * 2 + 1] = decompressor->result >> 8; + dcuTile[row * 2 + 16] = decompressor->result >> 16; + dcuTile[row * 2 + 17] = decompressor->result >> 24; + break; + } + + uint seek = r480b & 1 ? r4807 : (uint8)1; + while(seek--) decompressor->decode(); + } + } + + uint8 data = dcuTile[dcuOffset++]; + dcuOffset &= 8 * decompressor->bpp - 1; + return data; +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/decompressor.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/decompressor.cpp new file mode 100644 index 0000000000..3ca163c974 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/decompressor.cpp @@ -0,0 +1,190 @@ +//SPC7110 decompressor +//original implementation: neviksti +//optimized implementation: talarubi + +struct Decompressor { + SPC7110& spc7110; + + Decompressor(SPC7110& spc7110) : spc7110(spc7110) {} + + auto read() -> uint8 { + return spc7110.dataromRead(offset++); + } + + //inverse morton code transform: unpack big-endian packed pixels + //returns odd bits in lower half; even bits in upper half + auto deinterleave(uint64 data, uint bits) -> uint32 { + data = data & (1ull << bits) - 1; + data = 0x5555555555555555ull & (data << bits | data >> 1); + data = 0x3333333333333333ull & (data | data >> 1); + data = 0x0f0f0f0f0f0f0f0full & (data | data >> 2); + data = 0x00ff00ff00ff00ffull & (data | data >> 4); + data = 0x0000ffff0000ffffull & (data | data >> 8); + return data | data >> 16; + } + + //extract a nibble and move it to the low four bits + auto moveToFront(uint64 list, uint nibble) -> uint64 { + for(uint64 n = 0, mask = ~15; n < 64; n += 4, mask <<= 4) { + if((list >> n & 15) != nibble) continue; + return list = (list & mask) + (list << 4 & ~mask) + nibble; + } + return list; + } + + auto initialize(uint mode, uint origin) -> void { + for(auto& root : context) for(auto& node : root) node = {0, 0}; + bpp = 1 << mode; + offset = origin; + bits = 8; + range = Max + 1; + input = read(); + input = input << 8 | read(); + output = 0; + pixels = 0; + colormap = 0xfedcba9876543210ull; + } + + auto decode() -> void { + for(uint pixel = 0; pixel < 8; pixel++) { + uint64 map = colormap; + uint diff = 0; + + if(bpp > 1) { + uint pa = (bpp == 2 ? pixels >> 2 & 3 : pixels >> 0 & 15); + uint pb = (bpp == 2 ? pixels >> 14 & 3 : pixels >> 28 & 15); + uint pc = (bpp == 2 ? pixels >> 16 & 3 : pixels >> 32 & 15); + + if(pa != pb || pb != pc) { + uint match = pa ^ pb ^ pc; + diff = 4; //no match; all pixels differ + if((match ^ pc) == 0) diff = 3; //a == b; pixel c differs + if((match ^ pb) == 0) diff = 2; //c == a; pixel b differs + if((match ^ pa) == 0) diff = 1; //b == c; pixel a differs + } + + colormap = moveToFront(colormap, pa); + + map = moveToFront(map, pc); + map = moveToFront(map, pb); + map = moveToFront(map, pa); + } + + for(uint plane = 0; plane < bpp; plane++) { + uint bit = bpp > 1 ? 1 << plane : 1 << (pixel & 3); + uint history = bit - 1 & output; + uint set = 0; + + if(bpp == 1) set = pixel >= 4; + if(bpp == 2) set = diff; + if(plane >= 2 && history <= 1) set = diff; + + auto& ctx = context[set][bit + history - 1]; + auto& model = evolution[ctx.prediction]; + uint8 lps_offset = range - model.probability; + bool symbol = input >= (lps_offset << 8); //test only the MSB + + output = output << 1 | (symbol ^ ctx.swap); + + if(symbol == MPS) { //[0 ... range-p] + range = lps_offset; //range = range-p + } else { //[range-p+1 ... range] + range -= lps_offset; //range = p-1, with p < 0.75 + input -= lps_offset << 8; //therefore, always rescale + } + + while(range <= Max / 2) { //scale back into [0.75 ... 1.5] + ctx.prediction = model.next[symbol]; + + range <<= 1; + input <<= 1; + + if(--bits == 0) { + bits = 8; + input += read(); + } + } + + if(symbol == LPS && model.probability > Half) ctx.swap ^= 1; + } + + uint index = output & (1 << bpp) - 1; + if(bpp == 1) index ^= pixels >> 15 & 1; + + pixels = pixels << bpp | (map >> 4 * index & 15); + } + + if(bpp == 1) result = pixels; + if(bpp == 2) result = deinterleave(pixels, 16); + if(bpp == 4) result = deinterleave(deinterleave(pixels, 32), 32); + } + + auto serialize(serializer& s) -> void { + for(auto& root : context) { + for(auto& node : root) { + s.integer(node.prediction); + s.integer(node.swap); + } + } + + s.integer(bpp); + s.integer(offset); + s.integer(bits); + s.integer(range); + s.integer(input); + s.integer(output); + s.integer(pixels); + s.integer(colormap); + s.integer(result); + } + + enum : uint { MPS = 0, LPS = 1 }; + enum : uint { One = 0xaa, Half = 0x55, Max = 0xff }; + + struct ModelState { + uint8 probability; //of the more probable symbol (MPS) + uint8 next[2]; //next state after output {MPS, LPS} + }; + static ModelState evolution[53]; + + struct Context { + uint8 prediction; //current model state + uint8 swap; //if 1, exchange the role of MPS and LPS + } context[5][15]; //not all 75 contexts exists; this simplifies the code + + uint bpp; //bits per pixel (1bpp = 1; 2bpp = 2; 4bpp = 4) + uint offset; //SPC7110 data ROM read offset + uint bits; //bits remaining in input + uint16 range; //arithmetic range: technically 8-bits, but Max+1 = 256 + uint16 input; //input data from SPC7110 data ROM + uint8 output; + uint64 pixels; + uint64 colormap; //most recently used list + uint32 result; //decompressed word after calling decode() +}; + +Decompressor::ModelState Decompressor::evolution[53] = { + {0x5a, { 1, 1}}, {0x25, { 2, 6}}, {0x11, { 3, 8}}, + {0x08, { 4,10}}, {0x03, { 5,12}}, {0x01, { 5,15}}, + + {0x5a, { 7, 7}}, {0x3f, { 8,19}}, {0x2c, { 9,21}}, + {0x20, {10,22}}, {0x17, {11,23}}, {0x11, {12,25}}, + {0x0c, {13,26}}, {0x09, {14,28}}, {0x07, {15,29}}, + {0x05, {16,31}}, {0x04, {17,32}}, {0x03, {18,34}}, + {0x02, { 5,35}}, + + {0x5a, {20,20}}, {0x48, {21,39}}, {0x3a, {22,40}}, + {0x2e, {23,42}}, {0x26, {24,44}}, {0x1f, {25,45}}, + {0x19, {26,46}}, {0x15, {27,25}}, {0x11, {28,26}}, + {0x0e, {29,26}}, {0x0b, {30,27}}, {0x09, {31,28}}, + {0x08, {32,29}}, {0x07, {33,30}}, {0x05, {34,31}}, + {0x04, {35,33}}, {0x04, {36,33}}, {0x03, {37,34}}, + {0x02, {38,35}}, {0x02, { 5,36}}, + + {0x58, {40,39}}, {0x4d, {41,47}}, {0x43, {42,48}}, + {0x3b, {43,49}}, {0x34, {44,50}}, {0x2e, {45,51}}, + {0x29, {46,44}}, {0x25, {24,45}}, + + {0x56, {48,47}}, {0x4f, {49,47}}, {0x47, {50,48}}, + {0x41, {51,49}}, {0x3c, {52,50}}, {0x37, {43,51}}, +}; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/serialization.cpp new file mode 100644 index 0000000000..5860be4bc1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/serialization.cpp @@ -0,0 +1,60 @@ +auto SPC7110::serialize(serializer& s) -> void { + Thread::serialize(s); + s.array(ram.data(), ram.size()); + + s.integer(r4801); + s.integer(r4802); + s.integer(r4803); + s.integer(r4804); + s.integer(r4805); + s.integer(r4806); + s.integer(r4807); + s.integer(r4809); + s.integer(r480a); + s.integer(r480b); + s.integer(r480c); + + s.integer(dcuPending); + s.integer(dcuMode); + s.integer(dcuAddress); + s.integer(dcuOffset); + s.array(dcuTile); + decompressor->serialize(s); + + s.integer(r4810); + s.integer(r4811); + s.integer(r4812); + s.integer(r4813); + s.integer(r4814); + s.integer(r4815); + s.integer(r4816); + s.integer(r4817); + s.integer(r4818); + s.integer(r481a); + + s.integer(r4820); + s.integer(r4821); + s.integer(r4822); + s.integer(r4823); + s.integer(r4824); + s.integer(r4825); + s.integer(r4826); + s.integer(r4827); + s.integer(r4828); + s.integer(r4829); + s.integer(r482a); + s.integer(r482b); + s.integer(r482c); + s.integer(r482d); + s.integer(r482e); + s.integer(r482f); + + s.integer(mulPending); + s.integer(divPending); + + s.integer(r4830); + s.integer(r4831); + s.integer(r4832); + s.integer(r4833); + s.integer(r4834); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/spc7110.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/spc7110.cpp new file mode 100644 index 0000000000..a29812450c --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/spc7110.cpp @@ -0,0 +1,316 @@ +#include + +namespace SuperFamicom { + +#include "dcu.cpp" +#include "data.cpp" +#include "alu.cpp" +#include "serialization.cpp" +SPC7110 spc7110; + +SPC7110::SPC7110() { + decompressor = new Decompressor(*this); +} + +SPC7110::~SPC7110() { + delete decompressor; +} + +auto SPC7110::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto SPC7110::Enter() -> void { + while(true) { + scheduler.synchronize(); + spc7110.main(); + } +} + +auto SPC7110::main() -> void { + if(dcuPending) { dcuPending = 0; dcuBeginTransfer(); } + if(mulPending) { mulPending = 0; aluMultiply(); } + if(divPending) { divPending = 0; aluDivide(); } + addClocks(1); +} + +auto SPC7110::step(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; +} + +auto SPC7110::addClocks(uint clocks) -> void { + step(clocks); + synchronizeCPU(); +} + +auto SPC7110::unload() -> void { + prom.reset(); + drom.reset(); + ram.reset(); +} + +auto SPC7110::power() -> void { + create(SPC7110::Enter, 21'477'272); + + r4801 = 0x00; + r4802 = 0x00; + r4803 = 0x00; + r4804 = 0x00; + r4805 = 0x00; + r4806 = 0x00; + r4807 = 0x00; + r4809 = 0x00; + r480a = 0x00; + r480b = 0x00; + r480c = 0x00; + + dcuPending = 0; + dcuMode = 0; + dcuAddress = 0; + + r4810 = 0x00; + r4811 = 0x00; + r4812 = 0x00; + r4813 = 0x00; + r4814 = 0x00; + r4815 = 0x00; + r4816 = 0x00; + r4817 = 0x00; + r4818 = 0x00; + r481a = 0x00; + + r4820 = 0x00; + r4821 = 0x00; + r4822 = 0x00; + r4823 = 0x00; + r4824 = 0x00; + r4825 = 0x00; + r4826 = 0x00; + r4827 = 0x00; + r4828 = 0x00; + r4829 = 0x00; + r482a = 0x00; + r482b = 0x00; + r482c = 0x00; + r482d = 0x00; + r482e = 0x00; + r482f = 0x00; + + mulPending = 0; + divPending = 0; + + r4830 = 0x00; + r4831 = 0x00; + r4832 = 0x01; + r4833 = 0x02; + r4834 = 0x00; +} + +auto SPC7110::read(uint addr, uint8 data) -> uint8 { + cpu.synchronizeCoprocessors(); + if((addr & 0xff0000) == 0x500000) addr = 0x4800; //$50:0000-ffff == $4800 + if((addr & 0xff0000) == 0x580000) addr = 0x4808; //$58:0000-ffff == $4808 + addr = 0x4800 | (addr & 0x3f); //$00-3f,80-bf:4800-483f + + switch(addr) { + //================== + //decompression unit + //================== + case 0x4800: { + uint16 counter = r4809 | r480a << 8; + counter--; + r4809 = counter >> 0; + r480a = counter >> 8; + return dcuRead(); + } + case 0x4801: return r4801; + case 0x4802: return r4802; + case 0x4803: return r4803; + case 0x4804: return r4804; + case 0x4805: return r4805; + case 0x4806: return r4806; + case 0x4807: return r4807; + case 0x4808: return 0x00; + case 0x4809: return r4809; + case 0x480a: return r480a; + case 0x480b: return r480b; + case 0x480c: return r480c; + + //============== + //data port unit + //============== + case 0x4810: { + data = r4810; + dataPortIncrement4810(); + return data; + } + case 0x4811: return r4811; + case 0x4812: return r4812; + case 0x4813: return r4813; + case 0x4814: return r4814; + case 0x4815: return r4815; + case 0x4816: return r4816; + case 0x4817: return r4817; + case 0x4818: return r4818; + case 0x481a: { + dataPortIncrement481a(); + return 0x00; + } + + //===================== + //arithmetic logic unit + //===================== + case 0x4820: return r4820; + case 0x4821: return r4821; + case 0x4822: return r4822; + case 0x4823: return r4823; + case 0x4824: return r4824; + case 0x4825: return r4825; + case 0x4826: return r4826; + case 0x4827: return r4827; + case 0x4828: return r4828; + case 0x4829: return r4829; + case 0x482a: return r482a; + case 0x482b: return r482b; + case 0x482c: return r482c; + case 0x482d: return r482d; + case 0x482e: return r482e; + case 0x482f: return r482f; + + //=================== + //memory control unit + //=================== + case 0x4830: return r4830; + case 0x4831: return r4831; + case 0x4832: return r4832; + case 0x4833: return r4833; + case 0x4834: return r4834; + } + + return data; +} + +auto SPC7110::write(uint addr, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + if((addr & 0xff0000) == 0x500000) addr = 0x4800; //$50:0000-ffff == $4800 + if((addr & 0xff0000) == 0x580000) addr = 0x4808; //$58:0000-ffff == $4808 + addr = 0x4800 | (addr & 0x3f); //$00-3f,80-bf:4800-483f + + switch(addr) { + //================== + //decompression unit + //================== + case 0x4801: r4801 = data; break; + case 0x4802: r4802 = data; break; + case 0x4803: r4803 = data; break; + case 0x4804: r4804 = data; dcuLoadAddress(); break; + case 0x4805: r4805 = data; break; + case 0x4806: r4806 = data; r480c &= 0x7f; dcuPending = 1; break; + case 0x4807: r4807 = data; break; + case 0x4808: break; + case 0x4809: r4809 = data; break; + case 0x480a: r480a = data; break; + case 0x480b: r480b = data & 0x03; break; + + //============== + //data port unit + //============== + case 0x4811: r4811 = data; break; + case 0x4812: r4812 = data; break; + case 0x4813: r4813 = data; dataPortRead(); break; + case 0x4814: r4814 = data; dataPortIncrement4814(); break; + case 0x4815: r4815 = data; if(r4818 & 2) dataPortRead(); dataPortIncrement4815(); break; + case 0x4816: r4816 = data; break; + case 0x4817: r4817 = data; break; + case 0x4818: r4818 = data & 0x7f; dataPortRead(); break; + + //===================== + //arithmetic logic unit + //===================== + case 0x4820: r4820 = data; break; + case 0x4821: r4821 = data; break; + case 0x4822: r4822 = data; break; + case 0x4823: r4823 = data; break; + case 0x4824: r4824 = data; break; + case 0x4825: r4825 = data; r482f |= 0x81; mulPending = 1; break; + case 0x4826: r4826 = data; break; + case 0x4827: r4827 = data; r482f |= 0x80; divPending = 1; break; + case 0x482e: r482e = data & 0x01; break; + + //=================== + //memory control unit + //=================== + case 0x4830: r4830 = data & 0x87; break; + case 0x4831: r4831 = data & 0x07; break; + case 0x4832: r4832 = data & 0x07; break; + case 0x4833: r4833 = data & 0x07; break; + case 0x4834: r4834 = data & 0x07; break; + } +} + +//=============== +//SPC7110::MCUROM +//=============== + +//map address=00-3f,80-bf:8000-ffff mask=0x800000 => 00-3f:8000-ffff +//map address=c0-ff:0000-ffff mask=0xc00000 => c0-ff:0000-ffff +auto SPC7110::mcuromRead(uint addr, uint8 data) -> uint8 { + uint mask = (1 << (r4834 & 3)) - 1; //8mbit, 16mbit, 32mbit, 64mbit DROM + + if(addr < 0x100000) { //$00-0f,80-8f:8000-ffff; $c0-cf:0000-ffff + addr &= 0x0fffff; + if(prom.size()) { //8mbit PROM + return prom.read(bus.mirror(0x000000 + addr, prom.size())); + } + addr |= 0x100000 * (r4830 & 7); + return dataromRead(addr); + } + + if(addr < 0x200000) { //$10-1f,90-9f:8000-ffff; $d0-df:0000-ffff + addr &= 0x0fffff; + if(r4834 & 4) { //16mbit PROM + return prom.read(bus.mirror(0x100000 + addr, prom.size())); + } + addr |= 0x100000 * (r4831 & 7); + return dataromRead(addr); + } + + if(addr < 0x300000) { //$20-2f,a0-af:8000-ffff; $e0-ef:0000-ffff + addr &= 0x0fffff; + addr |= 0x100000 * (r4832 & 7); + return dataromRead(addr); + } + + if(addr < 0x400000) { //$30-3f,b0-bf:8000-ffff; $f0-ff:0000-ffff + addr &= 0x0fffff; + addr |= 0x100000 * (r4833 & 7); + return dataromRead(addr); + } + + return data; +} + +auto SPC7110::mcuromWrite(uint addr, uint8 data) -> void { +} + +//=============== +//SPC7110::MCURAM +//=============== + +//map address=00-3f,80-bf:6000-7fff mask=0x80e000 => 00-07:0000-ffff +auto SPC7110::mcuramRead(uint addr, uint8) -> uint8 { + if(r4830 & 0x80) { + addr = bus.mirror(addr, ram.size()); + return ram.read(addr); + } + return 0x00; +} + +auto SPC7110::mcuramWrite(uint addr, uint8 data) -> void { + if(r4830 & 0x80) { + addr = bus.mirror(addr, ram.size()); + ram.write(addr, data); + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/spc7110.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/spc7110.hpp new file mode 100644 index 0000000000..ab8d4561d6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/spc7110/spc7110.hpp @@ -0,0 +1,123 @@ +struct Decompressor; + +struct SPC7110 : Thread { + SPC7110(); + ~SPC7110(); + + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; + auto unload() -> void; + auto power() -> void; + + auto addClocks(uint clocks) -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto mcuromRead(uint addr, uint8 data) -> uint8; + auto mcuromWrite(uint addr, uint8 data) -> void; + + auto mcuramRead(uint addr, uint8 data) -> uint8; + auto mcuramWrite(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; + + //dcu.cpp + auto dcuLoadAddress() -> void; + auto dcuBeginTransfer() -> void; + auto dcuRead() -> uint8; + + auto deinterleave1bpp(uint length) -> void; + auto deinterleave2bpp(uint length) -> void; + auto deinterleave4bpp(uint length) -> void; + + //data.cpp + auto dataromRead(uint addr) -> uint8; + + auto dataOffset() -> uint; + auto dataAdjust() -> uint; + auto dataStride() -> uint; + + auto setDataOffset(uint addr) -> void; + auto setDataAdjust(uint addr) -> void; + + auto dataPortRead() -> void; + + auto dataPortIncrement4810() -> void; + auto dataPortIncrement4814() -> void; + auto dataPortIncrement4815() -> void; + auto dataPortIncrement481a() -> void; + + //alu.cpp + auto aluMultiply() -> void; + auto aluDivide() -> void; + + ReadableMemory prom; //program ROM + ReadableMemory drom; //data ROM + WritableMemory ram; + +private: + //decompression unit + uint8 r4801; //compression table B0 + uint8 r4802; //compression table B1 + uint7 r4803; //compression table B2 + uint8 r4804; //compression table index + uint8 r4805; //adjust length B0 + uint8 r4806; //adjust length B1 + uint8 r4807; //stride length + uint8 r4809; //compression counter B0 + uint8 r480a; //compression counter B1 + uint8 r480b; //decompression settings + uint8 r480c; //decompression status + + bool dcuPending; + uint2 dcuMode; + uint23 dcuAddress; + uint dcuOffset; + uint8 dcuTile[32]; + Decompressor* decompressor; + + //data port unit + uint8 r4810; //data port read + seek + uint8 r4811; //data offset B0 + uint8 r4812; //data offset B1 + uint7 r4813; //data offset B2 + uint8 r4814; //data adjust B0 + uint8 r4815; //data adjust B1 + uint8 r4816; //data stride B0 + uint8 r4817; //data stride B1 + uint8 r4818; //data port settings + uint8 r481a; //data port seek + + //arithmetic logic unit + uint8 r4820; //16-bit multiplicand B0, 32-bit dividend B0 + uint8 r4821; //16-bit multiplicand B1, 32-bit dividend B1 + uint8 r4822; //32-bit dividend B2 + uint8 r4823; //32-bit dividend B3 + uint8 r4824; //16-bit multiplier B0 + uint8 r4825; //16-bit multiplier B1 + uint8 r4826; //16-bit divisor B0 + uint8 r4827; //16-bit divisor B1 + uint8 r4828; //32-bit product B0, 32-bit quotient B0 + uint8 r4829; //32-bit product B1, 32-bit quotient B1 + uint8 r482a; //32-bit product B2, 32-bit quotient B2 + uint8 r482b; //32-bit product B3, 32-bit quotient B3 + uint8 r482c; //16-bit remainder B0 + uint8 r482d; //16-bit remainder B1 + uint8 r482e; //math settings + uint8 r482f; //math status + + bool mulPending; + bool divPending; + + //memory control unit + uint8 r4830; //bank 0 mapping + SRAM write enable + uint8 r4831; //bank 1 mapping + uint8 r4832; //bank 2 mapping + uint8 r4833; //bank 3 mapping + uint8 r4834; //bank mapping settings +}; + +extern SPC7110 spc7110; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/data.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/data.hpp new file mode 100644 index 0000000000..52b251f903 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/data.hpp @@ -0,0 +1,130 @@ +#ifdef ST0010_CPP + +const int16 ST0010::sin_table[256] = { + 0x0000, 0x0324, 0x0648, 0x096a, 0x0c8c, 0x0fab, 0x12c8, 0x15e2, + 0x18f9, 0x1c0b, 0x1f1a, 0x2223, 0x2528, 0x2826, 0x2b1f, 0x2e11, + 0x30fb, 0x33df, 0x36ba, 0x398c, 0x3c56, 0x3f17, 0x41ce, 0x447a, + 0x471c, 0x49b4, 0x4c3f, 0x4ebf, 0x5133, 0x539b, 0x55f5, 0x5842, + 0x5a82, 0x5cb3, 0x5ed7, 0x60eb, 0x62f1, 0x64e8, 0x66cf, 0x68a6, + 0x6a6d, 0x6c23, 0x6dc9, 0x6f5e, 0x70e2, 0x7254, 0x73b5, 0x7504, + 0x7641, 0x776b, 0x7884, 0x7989, 0x7a7c, 0x7b5c, 0x7c29, 0x7ce3, + 0x7d89, 0x7e1d, 0x7e9c, 0x7f09, 0x7f61, 0x7fa6, 0x7fd8, 0x7ff5, + 0x7fff, 0x7ff5, 0x7fd8, 0x7fa6, 0x7f61, 0x7f09, 0x7e9c, 0x7e1d, + 0x7d89, 0x7ce3, 0x7c29, 0x7b5c, 0x7a7c, 0x7989, 0x7884, 0x776b, + 0x7641, 0x7504, 0x73b5, 0x7254, 0x70e2, 0x6f5e, 0x6dc9, 0x6c23, + 0x6a6d, 0x68a6, 0x66cf, 0x64e8, 0x62f1, 0x60eb, 0x5ed7, 0x5cb3, + 0x5a82, 0x5842, 0x55f5, 0x539b, 0x5133, 0x4ebf, 0x4c3f, 0x49b4, + 0x471c, 0x447a, 0x41ce, 0x3f17, 0x3c56, 0x398c, 0x36ba, 0x33df, + 0x30fb, 0x2e11, 0x2b1f, 0x2826, 0x2528, 0x2223, 0x1f1a, 0x1c0b, + 0x18f8, 0x15e2, 0x12c8, 0x0fab, 0x0c8c, 0x096a, 0x0648, 0x0324, + 0x0000, -0x0324, -0x0648, -0x096b, -0x0c8c, -0x0fab, -0x12c8, -0x15e2, + -0x18f9, -0x1c0b, -0x1f1a, -0x2223, -0x2528, -0x2826, -0x2b1f, -0x2e11, + -0x30fb, -0x33df, -0x36ba, -0x398d, -0x3c56, -0x3f17, -0x41ce, -0x447a, + -0x471c, -0x49b4, -0x4c3f, -0x4ebf, -0x5133, -0x539b, -0x55f5, -0x5842, + -0x5a82, -0x5cb3, -0x5ed7, -0x60ec, -0x62f1, -0x64e8, -0x66cf, -0x68a6, + -0x6a6d, -0x6c23, -0x6dc9, -0x6f5e, -0x70e2, -0x7254, -0x73b5, -0x7504, + -0x7641, -0x776b, -0x7884, -0x7989, -0x7a7c, -0x7b5c, -0x7c29, -0x7ce3, + -0x7d89, -0x7e1d, -0x7e9c, -0x7f09, -0x7f61, -0x7fa6, -0x7fd8, -0x7ff5, + -0x7fff, -0x7ff5, -0x7fd8, -0x7fa6, -0x7f61, -0x7f09, -0x7e9c, -0x7e1d, + -0x7d89, -0x7ce3, -0x7c29, -0x7b5c, -0x7a7c, -0x7989, -0x7883, -0x776b, + -0x7641, -0x7504, -0x73b5, -0x7254, -0x70e2, -0x6f5e, -0x6dc9, -0x6c23, + -0x6a6d, -0x68a6, -0x66cf, -0x64e8, -0x62f1, -0x60eb, -0x5ed7, -0x5cb3, + -0x5a82, -0x5842, -0x55f5, -0x539a, -0x5133, -0x4ebf, -0x4c3f, -0x49b3, + -0x471c, -0x447a, -0x41cd, -0x3f17, -0x3c56, -0x398c, -0x36b9, -0x33de, + -0x30fb, -0x2e10, -0x2b1f, -0x2826, -0x2527, -0x2223, -0x1f19, -0x1c0b, + -0x18f8, -0x15e2, -0x12c8, -0x0fab, -0x0c8b, -0x096a, -0x0647, -0x0324 +}; + +const int16 ST0010::mode7_scale[176] = { + 0x0380, 0x0325, 0x02da, 0x029c, 0x0268, 0x023b, 0x0215, 0x01f3, + 0x01d5, 0x01bb, 0x01a3, 0x018e, 0x017b, 0x016a, 0x015a, 0x014b, + 0x013e, 0x0132, 0x0126, 0x011c, 0x0112, 0x0109, 0x0100, 0x00f8, + 0x00f0, 0x00e9, 0x00e3, 0x00dc, 0x00d6, 0x00d1, 0x00cb, 0x00c6, + 0x00c1, 0x00bd, 0x00b8, 0x00b4, 0x00b0, 0x00ac, 0x00a8, 0x00a5, + 0x00a2, 0x009e, 0x009b, 0x0098, 0x0095, 0x0093, 0x0090, 0x008d, + 0x008b, 0x0088, 0x0086, 0x0084, 0x0082, 0x0080, 0x007e, 0x007c, + 0x007a, 0x0078, 0x0076, 0x0074, 0x0073, 0x0071, 0x006f, 0x006e, + 0x006c, 0x006b, 0x0069, 0x0068, 0x0067, 0x0065, 0x0064, 0x0063, + 0x0062, 0x0060, 0x005f, 0x005e, 0x005d, 0x005c, 0x005b, 0x005a, + 0x0059, 0x0058, 0x0057, 0x0056, 0x0055, 0x0054, 0x0053, 0x0052, + 0x0051, 0x0051, 0x0050, 0x004f, 0x004e, 0x004d, 0x004d, 0x004c, + 0x004b, 0x004b, 0x004a, 0x0049, 0x0048, 0x0048, 0x0047, 0x0047, + 0x0046, 0x0045, 0x0045, 0x0044, 0x0044, 0x0043, 0x0042, 0x0042, + 0x0041, 0x0041, 0x0040, 0x0040, 0x003f, 0x003f, 0x003e, 0x003e, + 0x003d, 0x003d, 0x003c, 0x003c, 0x003b, 0x003b, 0x003a, 0x003a, + 0x003a, 0x0039, 0x0039, 0x0038, 0x0038, 0x0038, 0x0037, 0x0037, + 0x0036, 0x0036, 0x0036, 0x0035, 0x0035, 0x0035, 0x0034, 0x0034, + 0x0034, 0x0033, 0x0033, 0x0033, 0x0032, 0x0032, 0x0032, 0x0031, + 0x0031, 0x0031, 0x0030, 0x0030, 0x0030, 0x0030, 0x002f, 0x002f, + 0x002f, 0x002e, 0x002e, 0x002e, 0x002e, 0x002d, 0x002d, 0x002d, + 0x002d, 0x002c, 0x002c, 0x002c, 0x002c, 0x002b, 0x002b, 0x002b +}; + +const uint8 ST0010::arctan[32][32] = { + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, + { 0x80, 0xa0, 0xad, 0xb3, 0xb6, 0xb8, 0xb9, 0xba, 0xbb, 0xbb, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf, 0xbf }, + { 0x80, 0x93, 0xa0, 0xa8, 0xad, 0xb0, 0xb3, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd }, + { 0x80, 0x8d, 0x98, 0xa0, 0xa6, 0xaa, 0xad, 0xb0, 0xb1, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb7, 0xb8, + 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xba, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbc, 0xbc, 0xbc, 0xbc }, + { 0x80, 0x8a, 0x93, 0x9a, 0xa0, 0xa5, 0xa8, 0xab, 0xad, 0xaf, 0xb0, 0xb2, 0xb3, 0xb4, 0xb5, 0xb5, + 0xb6, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xba, 0xba, 0xbb, 0xbb }, + { 0x80, 0x88, 0x90, 0x96, 0x9b, 0xa0, 0xa4, 0xa7, 0xa9, 0xab, 0xad, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb4, 0xb5, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9 }, + { 0x80, 0x87, 0x8d, 0x93, 0x98, 0x9c, 0xa0, 0xa3, 0xa6, 0xa8, 0xaa, 0xac, 0xad, 0xae, 0xb0, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb4, 0xb5, 0xb5, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8 }, + { 0x80, 0x86, 0x8b, 0x90, 0x95, 0x99, 0x9d, 0xa0, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, 0xac, 0xad, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb2, 0xb3, 0xb3, 0xb4, 0xb4, 0xb5, 0xb5, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7 }, + { 0x80, 0x85, 0x8a, 0x8f, 0x93, 0x97, 0x9a, 0x9d, 0xa0, 0xa2, 0xa5, 0xa6, 0xa8, 0xaa, 0xab, 0xac, + 0xad, 0xae, 0xaf, 0xb0, 0xb0, 0xb1, 0xb2, 0xb2, 0xb3, 0xb3, 0xb4, 0xb4, 0xb5, 0xb5, 0xb5, 0xb5 }, + { 0x80, 0x85, 0x89, 0x8d, 0x91, 0x95, 0x98, 0x9b, 0x9e, 0xa0, 0xa0, 0xa4, 0xa6, 0xa7, 0xa9, 0xaa, + 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb0, 0xb1, 0xb1, 0xb2, 0xb2, 0xb3, 0xb3, 0xb4, 0xb4, 0xb4 }, + { 0x80, 0x84, 0x88, 0x8c, 0x90, 0x93, 0x96, 0x99, 0x9b, 0x9e, 0xa0, 0xa2, 0xa4, 0xa5, 0xa7, 0xa8, + 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xaf, 0xb0, 0xb0, 0xb1, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3 }, + { 0x80, 0x84, 0x87, 0x8b, 0x8e, 0x91, 0x94, 0x97, 0x9a, 0x9c, 0x9e, 0xa0, 0xa2, 0xa3, 0xa5, 0xa6, + 0xa7, 0xa9, 0xaa, 0xab, 0xac, 0xac, 0xad, 0xae, 0xae, 0xaf, 0xb0, 0xb0, 0xb1, 0xb1, 0xb2, 0xb2 }, + { 0x80, 0x83, 0x87, 0x8a, 0x8d, 0x90, 0x93, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 0xa0, 0xa2, 0xa3, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xac, 0xad, 0xae, 0xae, 0xaf, 0xb0, 0xb0, 0xb0, 0xb1 }, + { 0x80, 0x83, 0x86, 0x89, 0x8c, 0x8f, 0x92, 0x94, 0x96, 0x99, 0x9b, 0x9d, 0x9e, 0xa0, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa7, 0xa8, 0xa9, 0xa9, 0xaa, 0xab, 0xac, 0xac, 0xad, 0xae, 0xae, 0xaf, 0xaf, 0xb0 }, + { 0x80, 0x83, 0x86, 0x89, 0x8b, 0x8e, 0x90, 0x93, 0x95, 0x97, 0x99, 0x9b, 0x9d, 0x9e, 0xa0, 0xa1, + 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xaa, 0xab, 0xac, 0xad, 0xad, 0xae, 0xae, 0xaf }, + { 0x80, 0x83, 0x85, 0x88, 0x8b, 0x8d, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9b, 0x9d, 0x9f, 0xa0, + 0xa1, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa8, 0xa9, 0xaa, 0xab, 0xab, 0xac, 0xad, 0xad, 0xae }, + { 0x80, 0x83, 0x85, 0x88, 0x8a, 0x8c, 0x8f, 0x91, 0x93, 0x95, 0x97, 0x99, 0x9a, 0x9c, 0x9d, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa5, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xaa, 0xab, 0xab, 0xac, 0xad }, + { 0x80, 0x82, 0x85, 0x87, 0x89, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x97, 0x99, 0x9b, 0x9c, 0x9d, + 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa8, 0xa9, 0xaa, 0xaa, 0xab, 0xac }, + { 0x80, 0x82, 0x85, 0x87, 0x89, 0x8b, 0x8d, 0x8f, 0x91, 0x93, 0x95, 0x96, 0x98, 0x99, 0x9b, 0x9c, + 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa7, 0xa8, 0xa9, 0xa9, 0xaa, 0xab }, + { 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x95, 0x97, 0x98, 0x9a, 0x9b, + 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa6, 0xa7, 0xa8, 0xa8, 0xa9, 0xaa }, + { 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x91, 0x93, 0x94, 0x96, 0x97, 0x99, 0x9a, + 0x9b, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa5, 0xa6, 0xa7, 0xa7, 0xa8, 0xa9 }, + { 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8b, 0x8d, 0x8f, 0x90, 0x92, 0x94, 0x95, 0x97, 0x98, 0x99, + 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa4, 0xa5, 0xa6, 0xa6, 0xa7, 0xa8 }, + { 0x80, 0x82, 0x84, 0x86, 0x87, 0x89, 0x8b, 0x8d, 0x8e, 0x90, 0x91, 0x93, 0x94, 0x96, 0x97, 0x98, + 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa3, 0xa4, 0xa5, 0xa6, 0xa6, 0xa7 }, + { 0x80, 0x82, 0x84, 0x85, 0x87, 0x89, 0x8a, 0x8c, 0x8e, 0x8f, 0x91, 0x92, 0x94, 0x95, 0x96, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa2, 0xa3, 0xa4, 0xa5, 0xa5, 0xa6 }, + { 0x80, 0x82, 0x83, 0x85, 0x87, 0x88, 0x8a, 0x8c, 0x8d, 0x8f, 0x90, 0x92, 0x93, 0x94, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa2, 0xa3, 0xa4, 0xa5, 0xa5 }, + { 0x80, 0x82, 0x83, 0x85, 0x86, 0x88, 0x8a, 0x8b, 0x8d, 0x8e, 0x90, 0x91, 0x92, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa2, 0xa3, 0xa4, 0xa4 }, + { 0x80, 0x82, 0x83, 0x85, 0x86, 0x88, 0x89, 0x8b, 0x8c, 0x8e, 0x8f, 0x90, 0x92, 0x93, 0x94, 0x95, + 0x96, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa2, 0xa3, 0xa4 }, + { 0x80, 0x82, 0x83, 0x85, 0x86, 0x87, 0x89, 0x8a, 0x8c, 0x8d, 0x8e, 0x90, 0x91, 0x92, 0x93, 0x95, + 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9e, 0x9f, 0xa0, 0xa1, 0xa1, 0xa2, 0xa3 }, + { 0x80, 0x81, 0x83, 0x84, 0x86, 0x87, 0x89, 0x8a, 0x8b, 0x8d, 0x8e, 0x8f, 0x90, 0x92, 0x93, 0x94, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9e, 0x9f, 0xa0, 0xa1, 0xa1, 0xa2 }, + { 0x80, 0x81, 0x83, 0x84, 0x86, 0x87, 0x88, 0x8a, 0x8b, 0x8c, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0x9f, 0xa0, 0xa1, 0xa1 }, + { 0x80, 0x81, 0x83, 0x84, 0x85, 0x87, 0x88, 0x89, 0x8b, 0x8c, 0x8d, 0x8e, 0x90, 0x91, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0x9f, 0xa0, 0xa1 }, + { 0x80, 0x81, 0x83, 0x84, 0x85, 0x87, 0x88, 0x89, 0x8a, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, + 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9c, 0x9d, 0x9e, 0x9f, 0x9f, 0xa0 } +}; + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/opcodes.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/opcodes.cpp new file mode 100644 index 0000000000..57a54975a5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/opcodes.cpp @@ -0,0 +1,301 @@ +#ifdef ST0010_CPP + +int16 ST0010::sin(int16 theta) { + return sin_table[(theta >> 8) & 0xff]; +} + +int16 ST0010::cos(int16 theta) { + return sin_table[((theta + 0x4000) >> 8) & 0xff]; +} + +uint8 ST0010::readb(uint16 addr) { + return ram[addr & 0xfff]; +} + +uint16 ST0010::readw(uint16 addr) { + return (readb(addr + 0) << 0) | + (readb(addr + 1) << 8); +} + +uint32 ST0010::readd(uint16 addr) { + return (readb(addr + 0) << 0) | + (readb(addr + 1) << 8) | + (readb(addr + 2) << 16) | + (readb(addr + 3) << 24); +} + +void ST0010::writeb(uint16 addr, uint8 data) { + ram[addr & 0xfff] = data; +} + +void ST0010::writew(uint16 addr, uint16 data) { + writeb(addr + 0, data >> 0); + writeb(addr + 1, data >> 8); +} + +void ST0010::writed(uint16 addr, uint32 data) { + writeb(addr + 0, data >> 0); + writeb(addr + 1, data >> 8); + writeb(addr + 2, data >> 16); + writeb(addr + 3, data >> 24); +} + +//ST-0010 emulation code - Copyright (C) 2003 The Dumper, Matthew Kendora, Overload, Feather +//bsnes port - Copyright (C) 2007 byuu + +void ST0010::op_01(int16 x0, int16 y0, int16 &x1, int16 &y1, int16 &quadrant, int16 &theta) { + if((x0 < 0) && (y0 < 0)) { + x1 = -x0; + y1 = -y0; + quadrant = -0x8000; + } else if(x0 < 0) { + x1 = y0; + y1 = -x0; + quadrant = -0x4000; + } else if(y0 < 0) { + x1 = -y0; + y1 = x0; + quadrant = 0x4000; + } else { + x1 = x0; + y1 = y0; + quadrant = 0x0000; + } + + while((x1 > 0x1f) || (y1 > 0x1f)) { + if(x1 > 1) { x1 >>= 1; } + if(y1 > 1) { y1 >>= 1; } + } + + if(y1 == 0) { quadrant += 0x4000; } + + theta = (arctan[y1][x1] << 8) ^ quadrant; +} + +// + +void ST0010::op_01() { + int16 x0 = readw(0x0000); + int16 y0 = readw(0x0002); + int16 x1, y1, quadrant, theta; + + op_01(x0, y0, x1, y1, quadrant, theta); + + writew(0x0000, x1); + writew(0x0002, y1); + writew(0x0004, quadrant); +//writew(0x0006, y0); //Overload's docs note this write occurs, SNES9x disagrees + writew(0x0010, theta); +} + +void ST0010::op_02() { + int16 positions = readw(0x0024); + uint16 *places = (uint16*)(ram + 0x0040); + uint16 *drivers = (uint16*)(ram + 0x0080); + + bool sorted; + uint16 temp; + if(positions > 1) { + do { + sorted = true; + for(int i = 0; i < positions - 1; i++) { + if(places[i] < places[i + 1]) { + temp = places[i + 1]; + places[i + 1] = places[i]; + places[i] = temp; + + temp = drivers[i + 1]; + drivers[i + 1] = drivers[i]; + drivers[i] = temp; + + sorted = false; + } + } + positions--; + } while(!sorted); + } +} + +void ST0010::op_03() { + int16 x0 = readw(0x0000); + int16 y0 = readw(0x0002); + int16 multiplier = readw(0x0004); + int32 x1, y1; + + x1 = x0 * multiplier << 1; + y1 = y0 * multiplier << 1; + + writed(0x0010, x1); + writed(0x0014, y1); +} + +void ST0010::op_04() { + int16 x = readw(0x0000); + int16 y = readw(0x0002); + int16 square; + //calculate the vector length of (x,y) + square = (int16)sqrt((double)(y * y + x * x)); + + writew(0x0010, square); +} + +void ST0010::op_05() { + int32 dx, dy; + int16 a1, b1, c1; + uint16 o1; + bool wrap = false; + + //target (x,y) coordinates + int16 ypos_max = readw(0x00c0); + int16 xpos_max = readw(0x00c2); + + //current coordinates and direction + int32 ypos = readd(0x00c4); + int32 xpos = readd(0x00c8); + uint16 rot = readw(0x00cc); + + //physics + uint16 speed = readw(0x00d4); + uint16 accel = readw(0x00d6); + uint16 speed_max = readw(0x00d8); + + //special condition acknowledgement + int16 system = readw(0x00da); + int16 flags = readw(0x00dc); + + //new target coordinates + int16 ypos_new = readw(0x00de); + int16 xpos_new = readw(0x00e0); + + //mask upper bit + xpos_new &= 0x7fff; + + //get the current distance + dx = xpos_max - (xpos >> 16); + dy = ypos_max - (ypos >> 16); + + //quirk: clear and move in9 + writew(0x00d2, 0xffff); + writew(0x00da, 0x0000); + + //grab the target angle + op_01(dy, dx, a1, b1, c1, (int16&)o1); + + //check for wrapping + if(abs(o1 - rot) > 0x8000) { + o1 += 0x8000; + rot += 0x8000; + wrap = true; + } + + uint16 old_speed = speed; + + //special case + if(abs(o1 - rot) == 0x8000) { + speed = 0x100; + } + + //slow down for sharp curves + else if(abs(o1 - rot) >= 0x1000) { + uint32 slow = abs(o1 - rot); + slow >>= 4; //scaling + speed -= slow; + } + + //otherwise accelerate + else { + speed += accel; + if(speed > speed_max) { + speed = speed_max; //clip speed + } + } + + //prevent negative/positive overflow + if(abs(old_speed - speed) > 0x8000) { + if(old_speed < speed) { speed = 0; } + else speed = 0xff00; + } + + //adjust direction by so many degrees + //be careful of negative adjustments + if((o1 > rot && (o1 - rot) > 0x80) || (o1 < rot && (rot - o1) >= 0x80)) { + if(o1 < rot) { rot -= 0x280; } + else if(o1 > rot) { rot += 0x280; } + } + + //turn off wrapping + if(wrap) { rot -= 0x8000; } + + //now check the distances (store for later) + dx = (xpos_max << 16) - xpos; + dy = (ypos_max << 16) - ypos; + dx >>= 16; + dy >>= 16; + + //if we're in so many units of the target, signal it + if((system && (dy <= 6 && dy >= -8) && (dx <= 126 && dx >= -128)) || (!system && (dx <= 6 && dx >= -8) && (dy <= 126 && dy >= -128))) { + //announce our new destination and flag it + xpos_max = xpos_new & 0x7fff; + ypos_max = ypos_new; + flags |= 0x08; + } + + //update position + xpos -= (cos(rot) * 0x400 >> 15) * (speed >> 8) << 1; + ypos -= (sin(rot) * 0x400 >> 15) * (speed >> 8) << 1; + + //quirk: mask upper byte + xpos &= 0x1fffffff; + ypos &= 0x1fffffff; + + writew(0x00c0, ypos_max); + writew(0x00c2, xpos_max); + writed(0x00c4, ypos); + writed(0x00c8, xpos); + writew(0x00cc, rot); + writew(0x00d4, speed); + writew(0x00dc, flags); +} + +void ST0010::op_06() { + int16 multiplicand = readw(0x0000); + int16 multiplier = readw(0x0002); + int32 product; + + product = multiplicand * multiplier << 1; + + writed(0x0010, product); +} + +void ST0010::op_07() { + int16 theta = readw(0x0000); + + int16 data; + for(int i = 0, offset = 0; i < 176; i++) { + data = mode7_scale[i] * cos(theta) >> 15; + writew(0x00f0 + offset, data); + writew(0x0510 + offset, data); + + data = mode7_scale[i] * sin(theta) >> 15; + writew(0x0250 + offset, data); + if(data) { data = ~data; } + writew(0x03b0 + offset, data); + + offset += 2; + } +} + +void ST0010::op_08() { + int16 x0 = readw(0x0000); + int16 y0 = readw(0x0002); + int16 theta = readw(0x0004); + int16 x1, y1; + + x1 = (y0 * sin(theta) >> 15) + (x0 * cos(theta) >> 15); + y1 = (y0 * cos(theta) >> 15) - (x0 * sin(theta) >> 15); + + writew(0x0010, x1); + writew(0x0012, y1); +} + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/serialization.cpp new file mode 100644 index 0000000000..1c75634b42 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/serialization.cpp @@ -0,0 +1,3 @@ +auto ST0010::serialize(serializer& s) -> void { + s.array(ram); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/st0010.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/st0010.cpp new file mode 100644 index 0000000000..4d14e531db --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/st0010.cpp @@ -0,0 +1,39 @@ +#include + +namespace SuperFamicom { + +#define ST0010_CPP +#include "data.hpp" +#include "opcodes.cpp" + +ST0010 st0010; +#include "serialization.cpp" + +auto ST0010::power() -> void { + memset(ram, 0x00, sizeof ram); +} + +auto ST0010::read(uint addr, uint8 data) -> uint8 { + return readb(addr); +} + +auto ST0010::write(uint addr, uint8 data) -> void { + writeb(addr, data); + + if((addr & 0xfff) == 0x0021 && (data & 0x80)) { + switch(ram[0x0020]) { + case 0x01: op_01(); break; + case 0x02: op_02(); break; + case 0x03: op_03(); break; + case 0x04: op_04(); break; + case 0x05: op_05(); break; + case 0x06: op_06(); break; + case 0x07: op_07(); break; + case 0x08: op_08(); break; + } + + ram[0x0021] &= ~0x80; + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/st0010.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/st0010.hpp new file mode 100644 index 0000000000..2514a5d4c4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/st0010/st0010.hpp @@ -0,0 +1,39 @@ +struct ST0010 { + auto power() -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + + auto serialize(serializer&) -> void; + + uint8 ram[0x1000]; + static const int16 sin_table[256]; + static const int16 mode7_scale[176]; + static const uint8 arctan[32][32]; + + //interfaces to sin table + int16 sin(int16 theta); + int16 cos(int16 theta); + + //interfaces to ram buffer + uint8 readb (uint16 addr); + uint16 readw (uint16 addr); + uint32 readd (uint16 addr); + void writeb(uint16 addr, uint8 data); + void writew(uint16 addr, uint16 data); + void writed(uint16 addr, uint32 data); + + //opcodes + void op_01(); + void op_02(); + void op_03(); + void op_04(); + void op_05(); + void op_06(); + void op_07(); + void op_08(); + + void op_01(int16 x0, int16 y0, int16 &x1, int16 &y1, int16 &quadrant, int16 &theta); +}; + +extern ST0010 st0010; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/bus.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/bus.cpp new file mode 100644 index 0000000000..416383bc09 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/bus.cpp @@ -0,0 +1,41 @@ +//ROM / RAM access from the S-CPU + +auto SuperFX::CPUROM::data() -> uint8* { + return superfx.rom.data(); +} + +auto SuperFX::CPUROM::size() const -> uint { + return superfx.rom.size(); +} + +auto SuperFX::CPUROM::read(uint addr, uint8 data) -> uint8 { + if(superfx.regs.sfr.g && superfx.regs.scmr.ron) { + static const uint8 vector[16] = { + 0x00, 0x01, 0x00, 0x01, 0x04, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x08, 0x01, 0x00, 0x01, 0x0c, 0x01, + }; + return vector[addr & 15]; + } + return superfx.rom.read(addr, data); +} + +auto SuperFX::CPUROM::write(uint addr, uint8 data) -> void { + superfx.rom.write(addr, data); +} + +auto SuperFX::CPURAM::data() -> uint8* { + return superfx.ram.data(); +} + +auto SuperFX::CPURAM::size() const -> uint { + return superfx.ram.size(); +} + +auto SuperFX::CPURAM::read(uint addr, uint8 data) -> uint8 { + if(superfx.regs.sfr.g && superfx.regs.scmr.ran) return data; + return superfx.ram.read(addr, data); +} + +auto SuperFX::CPURAM::write(uint addr, uint8 data) -> void { + superfx.ram.write(addr, data); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/core.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/core.cpp new file mode 100644 index 0000000000..eb44044a6b --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/core.cpp @@ -0,0 +1,103 @@ +auto SuperFX::stop() -> void { + cpu.irq(1); +} + +auto SuperFX::color(uint8 source) -> uint8 { + if(regs.por.highnibble) return (regs.colr & 0xf0) | (source >> 4); + if(regs.por.freezehigh) return (regs.colr & 0xf0) | (source & 0x0f); + return source; +} + +auto SuperFX::plot(uint8 x, uint8 y) -> void { + if(!regs.por.transparent) { + if(regs.scmr.md == 3) { + if(regs.por.freezehigh) { + if((regs.colr & 0x0f) == 0) return; + } else { + if(regs.colr == 0) return; + } + } else { + if((regs.colr & 0x0f) == 0) return; + } + } + + uint8 color = regs.colr; + if(regs.por.dither && regs.scmr.md != 3) { + if((x ^ y) & 1) color >>= 4; + color &= 0x0f; + } + + uint16 offset = (y << 5) + (x >> 3); + if(offset != pixelcache[0].offset) { + flushPixelCache(pixelcache[1]); + pixelcache[1] = pixelcache[0]; + pixelcache[0].bitpend = 0x00; + pixelcache[0].offset = offset; + } + + x = (x & 7) ^ 7; + pixelcache[0].data[x] = color; + pixelcache[0].bitpend |= 1 << x; + if(pixelcache[0].bitpend == 0xff) { + flushPixelCache(pixelcache[1]); + pixelcache[1] = pixelcache[0]; + pixelcache[0].bitpend = 0x00; + } +} + +auto SuperFX::rpix(uint8 x, uint8 y) -> uint8 { + flushPixelCache(pixelcache[1]); + flushPixelCache(pixelcache[0]); + + uint cn; //character number + switch(regs.por.obj ? 3 : regs.scmr.ht) { + case 0: cn = ((x & 0xf8) << 1) + ((y & 0xf8) >> 3); break; + case 1: cn = ((x & 0xf8) << 1) + ((x & 0xf8) >> 1) + ((y & 0xf8) >> 3); break; + case 2: cn = ((x & 0xf8) << 1) + ((x & 0xf8) << 0) + ((y & 0xf8) >> 3); break; + case 3: cn = ((y & 0x80) << 2) + ((x & 0x80) << 1) + ((y & 0x78) << 1) + ((x & 0x78) >> 3); break; + } + uint bpp = 2 << (regs.scmr.md - (regs.scmr.md >> 1)); // = [regs.scmr.md]{ 2, 4, 4, 8 }; + uint addr = 0x700000 + (cn * (bpp << 3)) + (regs.scbr << 10) + ((y & 0x07) * 2); + uint8 data = 0x00; + x = (x & 7) ^ 7; + + for(uint n : range(bpp)) { + uint byte = ((n >> 1) << 4) + (n & 1); // = [n]{ 0, 1, 16, 17, 32, 33, 48, 49 }; + step(regs.clsr ? 5 : 6); + data |= ((read(addr + byte) >> x) & 1) << n; + } + + return data; +} + +auto SuperFX::flushPixelCache(PixelCache& cache) -> void { + if(cache.bitpend == 0x00) return; + + uint8 x = cache.offset << 3; + uint8 y = cache.offset >> 5; + + uint cn; //character number + switch(regs.por.obj ? 3 : regs.scmr.ht) { + case 0: cn = ((x & 0xf8) << 1) + ((y & 0xf8) >> 3); break; + case 1: cn = ((x & 0xf8) << 1) + ((x & 0xf8) >> 1) + ((y & 0xf8) >> 3); break; + case 2: cn = ((x & 0xf8) << 1) + ((x & 0xf8) << 0) + ((y & 0xf8) >> 3); break; + case 3: cn = ((y & 0x80) << 2) + ((x & 0x80) << 1) + ((y & 0x78) << 1) + ((x & 0x78) >> 3); break; + } + uint bpp = 2 << (regs.scmr.md - (regs.scmr.md >> 1)); // = [regs.scmr.md]{ 2, 4, 4, 8 }; + uint addr = 0x700000 + (cn * (bpp << 3)) + (regs.scbr << 10) + ((y & 0x07) * 2); + + for(uint n : range(bpp)) { + uint byte = ((n >> 1) << 4) + (n & 1); // = [n]{ 0, 1, 16, 17, 32, 33, 48, 49 }; + uint8 data = 0x00; + for(uint x : range(8)) data |= ((cache.data[x] >> n) & 1) << x; + if(cache.bitpend != 0xff) { + step(regs.clsr ? 5 : 6); + data &= cache.bitpend; + data |= read(addr + byte) & ~cache.bitpend; + } + step(regs.clsr ? 5 : 6); + write(addr + byte, data); + } + + cache.bitpend = 0x00; +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/io.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/io.cpp new file mode 100644 index 0000000000..e3808e7f0e --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/io.cpp @@ -0,0 +1,113 @@ +auto SuperFX::readIO(uint addr, uint8) -> uint8 { + cpu.synchronizeCoprocessors(); + addr = 0x3000 | addr & 0x3ff; + + if(addr >= 0x3100 && addr <= 0x32ff) { + return readCache(addr - 0x3100); + } + + if(addr >= 0x3000 && addr <= 0x301f) { + return regs.r[(addr >> 1) & 15] >> ((addr & 1) << 3); + } + + switch(addr) { + case 0x3030: { + return regs.sfr >> 0; + } + + case 0x3031: { + uint8 r = regs.sfr >> 8; + regs.sfr.irq = 0; + cpu.irq(0); + return r; + } + + case 0x3034: { + return regs.pbr; + } + + case 0x3036: { + return regs.rombr; + } + + case 0x303b: { + return regs.vcr; + } + + case 0x303c: { + return regs.rambr; + } + + case 0x303e: { + return regs.cbr >> 0; + } + + case 0x303f: { + return regs.cbr >> 8; + } + } + + return 0x00; +} + +auto SuperFX::writeIO(uint addr, uint8 data) -> void { + cpu.synchronizeCoprocessors(); + addr = 0x3000 | addr & 0x3ff; + + if(addr >= 0x3100 && addr <= 0x32ff) { + return writeCache(addr - 0x3100, data); + } + + if(addr >= 0x3000 && addr <= 0x301f) { + uint n = (addr >> 1) & 15; + if((addr & 1) == 0) { + regs.r[n] = (regs.r[n] & 0xff00) | data; + } else { + regs.r[n] = (data << 8) | (regs.r[n] & 0xff); + } + if(n == 14) updateROMBuffer(); + + if(addr == 0x301f) regs.sfr.g = 1; + return; + } + + switch(addr) { + case 0x3030: { + bool g = regs.sfr.g; + regs.sfr = (regs.sfr & 0xff00) | (data << 0); + if(g == 1 && regs.sfr.g == 0) { + regs.cbr = 0x0000; + flushCache(); + } + } break; + + case 0x3031: { + regs.sfr = (data << 8) | (regs.sfr & 0x00ff); + } break; + + case 0x3033: { + regs.bramr = data & 0x01; + } break; + + case 0x3034: { + regs.pbr = data & 0x7f; + flushCache(); + } break; + + case 0x3037: { + regs.cfgr = data; + } break; + + case 0x3038: { + regs.scbr = data; + } break; + + case 0x3039: { + regs.clsr = data & 0x01; + } break; + + case 0x303a: { + regs.scmr = data; + } break; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/memory.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/memory.cpp new file mode 100644 index 0000000000..01a8c8210f --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/memory.cpp @@ -0,0 +1,100 @@ +auto SuperFX::read(uint addr, uint8 data) -> uint8 { + if((addr & 0xc00000) == 0x000000) { //$00-3f:0000-7fff,:8000-ffff + while(!regs.scmr.ron) { + step(6); + synchronizeCPU(); + if(synchronizing()) break; + } + return rom.read((((addr & 0x3f0000) >> 1) | (addr & 0x7fff)) & romMask); + } + + if((addr & 0xe00000) == 0x400000) { //$40-5f:0000-ffff + while(!regs.scmr.ron) { + step(6); + synchronizeCPU(); + if(synchronizing()) break; + } + return rom.read(addr & romMask); + } + + if((addr & 0xe00000) == 0x600000) { //$60-7f:0000-ffff + while(!regs.scmr.ran) { + step(6); + synchronizeCPU(); + if(synchronizing()) break; + } + return ram.read(addr & ramMask); + } + + return data; +} + +auto SuperFX::write(uint addr, uint8 data) -> void { + if((addr & 0xe00000) == 0x600000) { //$60-7f:0000-ffff + while(!regs.scmr.ran) { + step(6); + synchronizeCPU(); + if(synchronizing()) break; + } + return ram.write(addr & ramMask, data); + } +} + +auto SuperFX::readOpcode(uint16 addr) -> uint8 { + uint16 offset = addr - regs.cbr; + if(offset < 512) { + if(cache.valid[offset >> 4] == false) { + uint dp = offset & 0xfff0; + uint sp = (regs.pbr << 16) + ((regs.cbr + dp) & 0xfff0); + for(uint n : range(16)) { + step(regs.clsr ? 5 : 6); + cache.buffer[dp++] = read(sp++); + } + cache.valid[offset >> 4] = true; + } else { + step(regs.clsr ? 1 : 2); + } + return cache.buffer[offset]; + } + + if(regs.pbr <= 0x5f) { + //$00-5f:0000-ffff ROM + syncROMBuffer(); + step(regs.clsr ? 5 : 6); + return read(regs.pbr << 16 | addr); + } else { + //$60-7f:0000-ffff RAM + syncRAMBuffer(); + step(regs.clsr ? 5 : 6); + return read(regs.pbr << 16 | addr); + } +} + +auto SuperFX::peekpipe() -> uint8 { + uint8 result = regs.pipeline; + regs.pipeline = readOpcode(regs.r[15]); + regs.r[15].modified = false; + return result; +} + +auto SuperFX::pipe() -> uint8 { + uint8 result = regs.pipeline; + regs.pipeline = readOpcode(++regs.r[15]); + regs.r[15].modified = false; + return result; +} + +auto SuperFX::flushCache() -> void { + for(uint n : range(32)) cache.valid[n] = false; +} + +auto SuperFX::readCache(uint16 addr) -> uint8 { + addr = (addr + regs.cbr) & 511; + return cache.buffer[addr]; +} + +auto SuperFX::writeCache(uint16 addr, uint8 data) -> void { + addr = (addr + regs.cbr) & 511; + cache.buffer[addr] = data; + if((addr & 15) == 15) cache.valid[addr >> 4] = true; +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/serialization.cpp new file mode 100644 index 0000000000..214f5dd35f --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/serialization.cpp @@ -0,0 +1,6 @@ +auto SuperFX::serialize(serializer& s) -> void { + GSU::serialize(s); + Thread::serialize(s); + + s.array(ram.data(), ram.size()); +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/superfx.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/superfx.cpp new file mode 100644 index 0000000000..847f6a060e --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/superfx.cpp @@ -0,0 +1,71 @@ +#include +#include + +namespace SuperFamicom { + +#include "bus.cpp" +#include "core.cpp" +#include "memory.cpp" +#include "io.cpp" +#include "timing.cpp" +#include "serialization.cpp" +SuperFX superfx; + +auto SuperFX::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto SuperFX::Enter() -> void { + while(true) { + scheduler.synchronize(); + superfx.main(); + } +} + +auto SuperFX::main() -> void { + if(regs.sfr.g == 0) return step(6); + + instruction(peekpipe()); + + if(regs.r[14].modified) { + regs.r[14].modified = false; + updateROMBuffer(); + } + + if(regs.r[15].modified) { + regs.r[15].modified = false; + } else { + regs.r[15]++; + } +} + +auto SuperFX::unload() -> void { + rom.reset(); + ram.reset(); +} + +auto SuperFX::power() -> void { + double overclock = max(1.0, min(8.0, configuration.hacks.superfx.overclock / 100.0)); + + GSU::power(); + create(SuperFX::Enter, Frequency * overclock); + + romMask = rom.size() - 1; + ramMask = ram.size() - 1; + + for(uint n : range(512)) cache.buffer[n] = 0x00; + for(uint n : range(32)) cache.valid[n] = false; + for(uint n : range(2)) { + pixelcache[n].offset = ~0; + pixelcache[n].bitpend = 0x00; + } + + regs.romcl = 0; + regs.romdr = 0; + + regs.ramcl = 0; + regs.ramar = 0; + regs.ramdr = 0; +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/superfx.hpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/superfx.hpp new file mode 100644 index 0000000000..97df3dfc33 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/superfx.hpp @@ -0,0 +1,77 @@ +struct SuperFX : Processor::GSU, Thread { + ReadableMemory rom; + WritableMemory ram; + + inline auto synchronizing() const -> bool { return scheduler.synchronizing(); } + + //superfx.cpp + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto unload() -> void; + auto power() -> void; + + //bus.cpp + struct CPUROM : Memory { + auto data() -> uint8* override; + auto size() const -> uint override; + auto read(uint, uint8) -> uint8 override; + auto write(uint, uint8) -> void override; + }; + + struct CPURAM : Memory { + auto data() -> uint8* override; + auto size() const -> uint override; + auto read(uint, uint8) -> uint8 override; + auto write(uint, uint8) -> void override; + }; + + //core.cpp + auto stop() -> void override; + auto color(uint8 source) -> uint8 override; + auto plot(uint8 x, uint8 y) -> void override; + auto rpix(uint8 x, uint8 y) -> uint8 override; + + auto flushPixelCache(PixelCache& cache) -> void; + + //memory.cpp + auto read(uint addr, uint8 data = 0x00) -> uint8 override; + auto write(uint addr, uint8 data) -> void override; + + auto readOpcode(uint16 addr) -> uint8; + alwaysinline auto peekpipe() -> uint8; + alwaysinline auto pipe() -> uint8 override; + + auto flushCache() -> void override; + auto readCache(uint16 addr) -> uint8; + auto writeCache(uint16 addr, uint8 data) -> void; + + //io.cpp + auto readIO(uint addr, uint8 data) -> uint8; + auto writeIO(uint addr, uint8 data) -> void; + + //timing.cpp + auto step(uint clocks) -> void override; + + auto syncROMBuffer() -> void override; + auto readROMBuffer() -> uint8 override; + auto updateROMBuffer() -> void; + + auto syncRAMBuffer() -> void override; + auto readRAMBuffer(uint16 addr) -> uint8 override; + auto writeRAMBuffer(uint16 addr, uint8 data) -> void override; + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint Frequency; + + CPUROM cpurom; + CPURAM cpuram; + +private: + uint romMask; + uint ramMask; +}; + +extern SuperFX superfx; diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/timing.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/timing.cpp new file mode 100644 index 0000000000..0b4bf653ad --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/superfx/timing.cpp @@ -0,0 +1,49 @@ +auto SuperFX::step(uint clocks) -> void { + if(regs.romcl) { + regs.romcl -= min(clocks, regs.romcl); + if(regs.romcl == 0) { + regs.sfr.r = 0; + regs.romdr = read((regs.rombr << 16) + regs.r[14]); + } + } + + if(regs.ramcl) { + regs.ramcl -= min(clocks, regs.ramcl); + if(regs.ramcl == 0) { + write(0x700000 + (regs.rambr << 16) + regs.ramar, regs.ramdr); + } + } + + clock += clocks * (uint64_t)cpu.frequency; + synchronizeCPU(); +} + +auto SuperFX::syncROMBuffer() -> void { + if(regs.romcl) step(regs.romcl); +} + +auto SuperFX::readROMBuffer() -> uint8 { + syncROMBuffer(); + return regs.romdr; +} + +auto SuperFX::updateROMBuffer() -> void { + regs.sfr.r = 1; + regs.romcl = regs.clsr ? 5 : 6; +} + +auto SuperFX::syncRAMBuffer() -> void { + if(regs.ramcl) step(regs.ramcl); +} + +auto SuperFX::readRAMBuffer(uint16 addr) -> uint8 { + syncRAMBuffer(); + return read(0x700000 + (regs.rambr << 16) + addr); +} + +auto SuperFX::writeRAMBuffer(uint16 addr, uint8 data) -> void { + syncRAMBuffer(); + regs.ramcl = regs.clsr ? 5 : 6; + regs.ramar = addr; + regs.ramdr = data; +} diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/cpu.cpp b/waterbox/bsnescore/bsnes/sfc/cpu/cpu.cpp new file mode 100644 index 0000000000..fbd59a139a --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cpu/cpu.cpp @@ -0,0 +1,130 @@ +#include + +namespace SuperFamicom { + +CPU cpu; +#include "dma.cpp" +#include "memory.cpp" +#include "io.cpp" +#include "timing.cpp" +#include "irq.cpp" +#include "serialization.cpp" + +auto CPU::synchronizeSMP() -> void { + if(smp.clock < 0) scheduler.resume(smp.thread); +} + +auto CPU::synchronizePPU() -> void { + if(ppu.clock < 0) scheduler.resume(ppu.thread); +} + +auto CPU::synchronizeCoprocessors() -> void { + for(auto coprocessor : coprocessors) { + if(coprocessor->clock < 0) scheduler.resume(coprocessor->thread); + } +} + +auto CPU::Enter() -> void { + while(true) { + scheduler.synchronize(); + cpu.main(); + } +} + +auto CPU::main() -> void { + if(r.wai) return instructionWait(); + if(r.stp) return instructionStop(); + if(!status.interruptPending) { + if (platform->traceEnabled) { + vector disassembly = disassemble(); + disassembly[1].append(" V:", hex(cpu.vcounter(), 3), " H:", hex(cpu.hdot(), 3)); + platform->cpuTrace(disassembly); + } + return instruction(); + } + + if(status.nmiPending) { + status.nmiPending = 0; + r.vector = r.e ? 0xfffa : 0xffea; + return interrupt(); + } + + if(status.irqPending) { + status.irqPending = 0; + r.vector = r.e ? 0xfffe : 0xffee; + return interrupt(); + } + + if(status.resetPending) { + status.resetPending = 0; + for(uint repeat : range(22)) step<6,0>(); //step(132); + r.vector = 0xfffc; + return interrupt(); + } + + status.interruptPending = 0; +} + +auto CPU::load() -> bool { + version = configuration.system.cpu.version; + if(version < 1) version = 1; + if(version > 2) version = 2; + return true; +} + +auto CPU::power(bool reset) -> void { + WDC65816::power(); + Thread::create(Enter, system.cpuFrequency()); + coprocessors.reset(); + PPUcounter::reset(); + PPUcounter::scanline = {&CPU::scanline, this}; + + function reader; + function writer; + + reader = {&CPU::readRAM, this}; + writer = {&CPU::writeRAM, this}; + bus.map(reader, writer, "00-3f,80-bf:0000-1fff", 0x2000); + bus.map(reader, writer, "7e-7f:0000-ffff", 0x20000); + + reader = {&CPU::readAPU, this}; + writer = {&CPU::writeAPU, this}; + bus.map(reader, writer, "00-3f,80-bf:2140-217f"); + + reader = {&CPU::readCPU, this}; + writer = {&CPU::writeCPU, this}; + bus.map(reader, writer, "00-3f,80-bf:2180-2183,4016-4017,4200-421f"); + + reader = {&CPU::readDMA, this}; + writer = {&CPU::writeDMA, this}; + bus.map(reader, writer, "00-3f,80-bf:4300-437f"); + + if(!reset) random.array(wram, sizeof(wram)); + + if(configuration.hacks.hotfixes) { + //Dirt Racer (Europe) relies on uninitialized memory containing certain values to boot without freezing. + //the game itself is broken and will fail to run sometimes on real hardware, but for the sake of expedience, + //WRAM is initialized to a constant value that will allow this game to always boot in successfully. + if(cartridge.headerTitle() == "DIRT RACER") { + for(auto& byte : wram) byte = 0xff; + } + } + + for(uint n : range(8)) { + channels[n] = {}; + if(n != 7) channels[n].next = channels[n + 1]; + } + + counter = {}; + io = {}; + alu = {}; + + status = {}; + status.dramRefreshPosition = (version == 1 ? 530 : 538); + status.hdmaSetupPosition = (version == 1 ? 12 + 8 - dmaCounter() : 12 + dmaCounter()); + status.hdmaPosition = 1104; + status.resetPending = 1; + status.interruptPending = 1; +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/cpu.hpp b/waterbox/bsnescore/bsnes/sfc/cpu/cpu.hpp new file mode 100644 index 0000000000..19a486beb6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cpu/cpu.hpp @@ -0,0 +1,242 @@ +struct CPU : Processor::WDC65816, Thread, PPUcounter { + inline auto interruptPending() const -> bool override { return status.interruptPending; } + inline auto pio() const -> uint8 { return io.pio; } + inline auto refresh() const -> bool { return status.dramRefresh == 1; } + inline auto synchronizing() const -> bool override { return scheduler.synchronizing(); } + + //cpu.cpp + auto synchronizeSMP() -> void; + auto synchronizePPU() -> void; + auto synchronizeCoprocessors() -> void; + static auto Enter() -> void; + auto main() -> void; + auto load() -> bool; + auto power(bool reset) -> void; + + //dma.cpp + inline auto dmaEnable() -> bool; + inline auto hdmaEnable() -> bool; + inline auto hdmaActive() -> bool; + + auto dmaRun() -> void; + auto hdmaReset() -> void; + auto hdmaSetup() -> void; + auto hdmaRun() -> void; + + //memory.cpp + auto idle() -> void override; + auto read(uint addr) -> uint8 override; + auto write(uint addr, uint8 data) -> void override; + auto readDisassembler(uint addr) -> uint8 override; + + //io.cpp + auto readRAM(uint address, uint8 data) -> uint8; + auto readAPU(uint address, uint8 data) -> uint8; + auto readCPU(uint address, uint8 data) -> uint8; + auto readDMA(uint address, uint8 data) -> uint8; + auto writeRAM(uint address, uint8 data) -> void; + auto writeAPU(uint address, uint8 data) -> void; + auto writeCPU(uint address, uint8 data) -> void; + auto writeDMA(uint address, uint8 data) -> void; + + //timing.cpp + inline auto dmaCounter() const -> uint; + inline auto joypadCounter() const -> uint; + + alwaysinline auto stepOnce() -> void; + alwaysinline auto step(uint clocks) -> void; + template auto step() -> void; + auto scanline() -> void; + + alwaysinline auto aluEdge() -> void; + alwaysinline auto dmaEdge() -> void; + + //irq.cpp + alwaysinline auto nmiPoll() -> void; + alwaysinline auto irqPoll() -> void; + auto nmitimenUpdate(uint8 data) -> void; + auto rdnmi() -> bool; + auto timeup() -> bool; + + alwaysinline auto nmiTest() -> bool; + alwaysinline auto irqTest() -> bool; + alwaysinline auto lastCycle() -> void; + + //joypad.cpp + auto joypadEdge() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint8 wram[128 * 1024]; + vector coprocessors; + + struct Overclocking { + uint counter = 0; + uint target = 0; + } overclocking; + +private: + uint version = 2; //allowed: 1, 2 + + struct Counter { + uint cpu = 0; + uint dma = 0; + } counter; + + struct Status { + uint clockCount = 0; + + bool irqLock = 0; + + uint dramRefreshPosition = 0; + uint dramRefresh = 0; //0 = not refreshed; 1 = refresh active; 2 = refresh inactive + + uint hdmaSetupPosition = 0; + bool hdmaSetupTriggered = 0; + + uint hdmaPosition = 0; + bool hdmaTriggered = 0; + + boolean nmiValid = 0; + boolean nmiLine = 0; + boolean nmiTransition = 0; + boolean nmiPending = 0; + boolean nmiHold = 0; + + boolean irqValid = 0; + boolean irqLine = 0; + boolean irqTransition = 0; + boolean irqPending = 0; + boolean irqHold = 0; + + bool resetPending = 0; + bool interruptPending = 0; + + bool dmaActive = 0; + bool dmaPending = 0; + bool hdmaPending = 0; + bool hdmaMode = 0; //0 = init, 1 = run + + uint autoJoypadCounter = 33; //state machine; 4224 / 128 = 33 (inactive) + } status; + + struct IO { + //$2181-$2183 + uint17 wramAddress = 0; + + //$4200 + boolean hirqEnable = 0; + boolean virqEnable = 0; + boolean irqEnable = 0; + boolean nmiEnable = 0; + boolean autoJoypadPoll = 0; + + //$4201 + uint8 pio = 0xff; + + //$4202-$4203 + uint8 wrmpya = 0xff; + uint8 wrmpyb = 0xff; + + //$4204-$4206 + uint16 wrdiva = 0xffff; + uint8 wrdivb = 0xff; + + //$4207-$420a + uint12 htime = 0x1ff + 1 << 2; + uint9 vtime = 0x1ff; + + //$420d + uint1 fastROM = 0; + + //$4214-$4217 + uint16 rddiv = 0; + uint16 rdmpy = 0; + + //$4218-$421f + uint16 joy1 = 0; + uint16 joy2 = 0; + uint16 joy3 = 0; + uint16 joy4 = 0; + } io; + + struct ALU { + uint mpyctr = 0; + uint divctr = 0; + uint shift = 0; + } alu; + + struct Channel { + //dma.cpp + template inline auto step() -> void; + inline auto edge() -> void; + + inline auto validA(uint24 address) -> bool; + inline auto readA(uint24 address) -> uint8; + inline auto readB(uint8 address, bool valid) -> uint8; + inline auto writeA(uint24 address, uint8 data) -> void; + inline auto writeB(uint8 address, uint8 data, bool valid) -> void; + inline auto transfer(uint24 address, uint2 index) -> void; + + inline auto dmaRun() -> void; + inline auto hdmaActive() -> bool; + inline auto hdmaFinished() -> bool; + inline auto hdmaReset() -> void; + inline auto hdmaSetup() -> void; + inline auto hdmaReload() -> void; + inline auto hdmaTransfer() -> void; + inline auto hdmaAdvance() -> void; + + //$420b + uint1 dmaEnable = 0; + + //$420c + uint1 hdmaEnable = 0; + + //$43x0 + uint3 transferMode = 7; + uint1 fixedTransfer = 1; + uint1 reverseTransfer = 1; + uint1 unused = 1; + uint1 indirect = 1; + uint1 direction = 1; + + //$43x1 + uint8 targetAddress = 0xff; + + //$43x2-$43x3 + uint16 sourceAddress = 0xffff; + + //$43x4 + uint8 sourceBank = 0xff; + + //$43x5-$43x6 + union { + uint16 transferSize; + uint16 indirectAddress; + }; + + //$43x7 + uint8 indirectBank = 0xff; + + //$43x8-$43x9 + uint16 hdmaAddress = 0xffff; + + //$43xa + uint8 lineCounter = 0xff; + + //$43xb/$43xf + uint8 unknown = 0xff; + + //internal state + uint1 hdmaCompleted = 0; + uint1 hdmaDoTransfer = 0; + + maybe next; + + Channel() : transferSize(0xffff) {} + } channels[8]; +}; + +extern CPU cpu; diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/dma.cpp b/waterbox/bsnescore/bsnes/sfc/cpu/dma.cpp new file mode 100644 index 0000000000..9f4a10f84c --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cpu/dma.cpp @@ -0,0 +1,189 @@ +auto CPU::dmaEnable() -> bool { + for(auto& channel : channels) if(channel.dmaEnable) return true; + return false; +} + +auto CPU::hdmaEnable() -> bool { + for(auto& channel : channels) if(channel.hdmaEnable) return true; + return false; +} + +auto CPU::hdmaActive() -> bool { + for(auto& channel : channels) if(channel.hdmaActive()) return true; + return false; +} + +auto CPU::dmaRun() -> void { + counter.dma += 8; + step<8,0>(); + dmaEdge(); + for(auto& channel : channels) channel.dmaRun(); + status.irqLock = true; +} + +auto CPU::hdmaReset() -> void { + for(auto& channel : channels) channel.hdmaReset(); +} + +auto CPU::hdmaSetup() -> void { + counter.dma += 8; + step<8,0>(); + for(auto& channel : channels) channel.hdmaSetup(); + status.irqLock = true; +} + +auto CPU::hdmaRun() -> void { + counter.dma += 8; + step<8,0>(); + for(auto& channel : channels) channel.hdmaTransfer(); + for(auto& channel : channels) channel.hdmaAdvance(); + status.irqLock = true; +} + +// + +template +auto CPU::Channel::step() -> void { + cpu.counter.dma += Clocks; + cpu.step(); +} + +auto CPU::Channel::edge() -> void { + cpu.dmaEdge(); +} + +auto CPU::Channel::validA(uint24 address) -> bool { + //A-bus cannot access the B-bus or CPU I/O registers + if((address & 0x40ff00) == 0x2100) return false; //00-3f,80-bf:2100-21ff + if((address & 0x40fe00) == 0x4000) return false; //00-3f,80-bf:4000-41ff + if((address & 0x40ffe0) == 0x4200) return false; //00-3f,80-bf:4200-421f + if((address & 0x40ff80) == 0x4300) return false; //00-3f,80-bf:4300-437f + return true; +} + +auto CPU::Channel::readA(uint24 address) -> uint8 { + step<4,1>(); + cpu.r.mdr = validA(address) ? bus.read(address, cpu.r.mdr) : (uint8)0x00; + step<4,1>(); + return cpu.r.mdr; +} + +auto CPU::Channel::readB(uint8 address, bool valid) -> uint8 { + step<4,1>(); + cpu.r.mdr = valid ? bus.read(0x2100 | address, cpu.r.mdr) : (uint8)0x00; + step<4,1>(); + return cpu.r.mdr; +} + +auto CPU::Channel::writeA(uint24 address, uint8 data) -> void { + if(validA(address)) bus.write(address, data); +} + +auto CPU::Channel::writeB(uint8 address, uint8 data, bool valid) -> void { + if(valid) bus.write(0x2100 | address, data); +} + +auto CPU::Channel::transfer(uint24 addressA, uint2 index) -> void { + uint8 addressB = targetAddress; + switch(transferMode) { + case 1: case 5: addressB += index.bit(0); break; + case 3: case 7: addressB += index.bit(1); break; + case 4: addressB += index; break; + } + + //transfers from WRAM to WRAM are invalid + bool valid = addressB != 0x80 || ((addressA & 0xfe0000) != 0x7e0000 && (addressA & 0x40e000) != 0x0000); + + cpu.r.mar = addressA; + if(direction == 0) { + auto data = readA(addressA); + writeB(addressB, data, valid); + } else { + auto data = readB(addressB, valid); + writeA(addressA, data); + } +} + +auto CPU::Channel::dmaRun() -> void { + if(!dmaEnable) return; + + step<8,0>(); + edge(); + + uint2 index = 0; + do { + transfer(sourceBank << 16 | sourceAddress, index++); + if(!fixedTransfer) !reverseTransfer ? sourceAddress++ : sourceAddress--; + edge(); + } while(dmaEnable && --transferSize); + + dmaEnable = false; +} + +auto CPU::Channel::hdmaActive() -> bool { + return hdmaEnable && !hdmaCompleted; +} + +auto CPU::Channel::hdmaFinished() -> bool { + auto channel = next; + while(channel) { + if(channel->hdmaActive()) return false; + channel = channel->next; + } + return true; +} + +auto CPU::Channel::hdmaReset() -> void { + hdmaCompleted = false; + hdmaDoTransfer = false; +} + +auto CPU::Channel::hdmaSetup() -> void { + hdmaDoTransfer = true; //note: needs hardware verification + if(!hdmaEnable) return; + + dmaEnable = false; //HDMA will stop active DMA mid-transfer + hdmaAddress = sourceAddress; + lineCounter = 0; + hdmaReload(); +} + +auto CPU::Channel::hdmaReload() -> void { + auto data = readA(cpu.r.mar = sourceBank << 16 | hdmaAddress); + + if((uint7)lineCounter == 0) { + lineCounter = data; + hdmaAddress++; + + hdmaCompleted = lineCounter == 0; + hdmaDoTransfer = !hdmaCompleted; + + if(indirect) { + data = readA(cpu.r.mar = sourceBank << 16 | hdmaAddress++); + indirectAddress = data << 8 | 0x00; //todo: should 0x00 be indirectAddress >> 8 ? + if(hdmaCompleted && hdmaFinished()) return; + + data = readA(cpu.r.mar = sourceBank << 16 | hdmaAddress++); + indirectAddress = data << 8 | indirectAddress >> 8; + } + } +} + +auto CPU::Channel::hdmaTransfer() -> void { + if(!hdmaActive()) return; + dmaEnable = false; //HDMA will stop active DMA mid-transfer + if(!hdmaDoTransfer) return; + + static const uint lengths[8] = {1, 2, 2, 4, 4, 4, 2, 4}; + for(uint2 index : range(lengths[transferMode])) { + uint24 address = !indirect ? sourceBank << 16 | hdmaAddress++ : indirectBank << 16 | indirectAddress++; + transfer(address, index); + } +} + +auto CPU::Channel::hdmaAdvance() -> void { + if(!hdmaActive()) return; + lineCounter--; + hdmaDoTransfer = bool(lineCounter & 0x80); + hdmaReload(); +} diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/io.cpp b/waterbox/bsnescore/bsnes/sfc/cpu/io.cpp new file mode 100644 index 0000000000..b6490ff3f5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cpu/io.cpp @@ -0,0 +1,300 @@ +auto CPU::readRAM(uint addr, uint8 data) -> uint8 { + return wram[addr]; +} + +auto CPU::readAPU(uint addr, uint8 data) -> uint8 { + synchronizeSMP(); + return smp.portRead(addr & 3); +} + +auto CPU::readCPU(uint addr, uint8 data) -> uint8 { + switch(addr & 0xffff) { + case 0x2180: //WMDATA + return bus.read(0x7e0000 | io.wramAddress++, data); + + //todo: it is not known what happens when reading from this register during auto-joypad polling + case 0x4016: //JOYSER0 + data &= 0xfc; + data |= controllerPort1.device->data(); + if (!io.autoJoypadPoll) platform->notify("NOTIFY NO_LAG"); + return data; + + //todo: it is not known what happens when reading from this register during auto-joypad polling + case 0x4017: //JOYSER1 + data &= 0xe0; + data |= 0x1c; //pins are connected to GND + data |= controllerPort2.device->data(); + if (!io.autoJoypadPoll) platform->notify("NOTIFY NO_LAG"); + return data; + + case 0x4210: //RDNMI + data &= 0x70; + data |= rdnmi() << 7; + data |= (uint4)version; + return data; + + case 0x4211: //TIMEUP + data &= 0x7f; + data |= timeup() << 7; + return data; + + case 0x4212: //HVBJOY + data &= 0x3e; + data |= io.autoJoypadPoll && status.autoJoypadCounter < 33; + data |= (hcounter() <= 2 || hcounter() >= 1096) << 6; //hblank + data |= (vcounter() >= ppu.vdisp()) << 7; //vblank + return data; + + case 0x4213: return io.pio; //RDIO + + case 0x4214: return io.rddiv >> 0; //RDDIVL + case 0x4215: return io.rddiv >> 8; //RDDIVH + case 0x4216: return io.rdmpy >> 0; //RDMPYL + case 0x4217: return io.rdmpy >> 8; //RDMPYH + + //todo: it is not known what happens when reading from these registers during auto-joypad polling + case 0x4218: platform->notify("NOTIFY NO_LAG"); return io.joy1 >> 0; //JOY1L + case 0x4219: platform->notify("NOTIFY NO_LAG"); return io.joy1 >> 8; //JOY1H + case 0x421a: platform->notify("NOTIFY NO_LAG"); return io.joy2 >> 0; //JOY2L + case 0x421b: platform->notify("NOTIFY NO_LAG"); return io.joy2 >> 8; //JOY2H + case 0x421c: platform->notify("NOTIFY NO_LAG"); return io.joy3 >> 0; //JOY3L + case 0x421d: platform->notify("NOTIFY NO_LAG"); return io.joy3 >> 8; //JOY3H + case 0x421e: platform->notify("NOTIFY NO_LAG"); return io.joy4 >> 0; //JOY4L + case 0x421f: platform->notify("NOTIFY NO_LAG"); return io.joy4 >> 8; //JOY4H + + } + + return data; +} + +auto CPU::readDMA(uint addr, uint8 data) -> uint8 { + auto& channel = this->channels[addr >> 4 & 7]; + + switch(addr & 0xff8f) { + + case 0x4300: //DMAPx + return ( + channel.transferMode << 0 + | channel.fixedTransfer << 3 + | channel.reverseTransfer << 4 + | channel.unused << 5 + | channel.indirect << 6 + | channel.direction << 7 + ); + + case 0x4301: return channel.targetAddress; //BBADx + case 0x4302: return channel.sourceAddress >> 0; //A1TxL + case 0x4303: return channel.sourceAddress >> 8; //A1TxH + case 0x4304: return channel.sourceBank; //A1Bx + case 0x4305: return channel.transferSize >> 0; //DASxL + case 0x4306: return channel.transferSize >> 8; //DASxH + case 0x4307: return channel.indirectBank; //DASBx + case 0x4308: return channel.hdmaAddress >> 0; //A2AxL + case 0x4309: return channel.hdmaAddress >> 8; //A2AxH + case 0x430a: return channel.lineCounter; //NTRLx + case 0x430b: return channel.unknown; //???x + case 0x430f: return channel.unknown; //???x ($43xb mirror) + + } + + return data; +} + +auto CPU::writeRAM(uint addr, uint8 data) -> void { + wram[addr] = data; +} + +auto CPU::writeAPU(uint addr, uint8 data) -> void { + synchronizeSMP(); + return smp.portWrite(addr & 3, data); +} + +auto CPU::writeCPU(uint addr, uint8 data) -> void { + switch(addr & 0xffff) { + + case 0x2180: //WMDATA + return bus.write(0x7e0000 | io.wramAddress++, data); + + case 0x2181: //WMADDL + io.wramAddress = io.wramAddress & 0x1ff00 | data << 0; + return; + + case 0x2182: //WMADDM + io.wramAddress = io.wramAddress & 0x100ff | data << 8; + return; + + case 0x2183: //WMADDH + io.wramAddress = io.wramAddress & 0x0ffff | (data & 1) << 16; + return; + + //todo: it is not known what happens when writing to this register during auto-joypad polling + case 0x4016: //JOYSER0 + //bit 0 is shared between JOYSER0 and JOYSER1: + //strobing $4016.d0 affects both controller port latches. + //$4017 bit 0 writes are ignored. + controllerPort1.device->latch(data & 1); + controllerPort2.device->latch(data & 1); + return; + + case 0x4200: //NMITIMEN + io.autoJoypadPoll = data & 1; + if(!io.autoJoypadPoll) status.autoJoypadCounter = 33; // Disable auto-joypad read + nmitimenUpdate(data); + return; + + case 0x4201: //WRIO + if((io.pio & 0x80) && !(data & 0x80)) ppu.latchCounters(); + io.pio = data; + return; + + case 0x4202: //WRMPYA + io.wrmpya = data; + return; + + case 0x4203: //WRMPYB + io.rdmpy = 0; + if(alu.mpyctr || alu.divctr) return; + + io.wrmpyb = data; + io.rddiv = io.wrmpyb << 8 | io.wrmpya; + + if(!configuration.hacks.cpu.fastMath) { + alu.mpyctr = 8; //perform multiplication over the next eight cycles + alu.shift = io.wrmpyb; + } else { + io.rdmpy = io.wrmpya * io.wrmpyb; + } + return; + + case 0x4204: //WRDIVL + io.wrdiva = io.wrdiva & 0xff00 | data << 0; + return; + + case 0x4205: //WRDIVH + io.wrdiva = io.wrdiva & 0x00ff | data << 8; + return; + + case 0x4206: //WRDIVB + io.rdmpy = io.wrdiva; + if(alu.mpyctr || alu.divctr) return; + + io.wrdivb = data; + + if(!configuration.hacks.cpu.fastMath) { + alu.divctr = 16; //perform division over the next sixteen cycles + alu.shift = io.wrdivb << 16; + } else { + if(io.wrdivb) { + io.rddiv = io.wrdiva / io.wrdivb; + io.rdmpy = io.wrdiva % io.wrdivb; + } else { + io.rddiv = 0xffff; + io.rdmpy = io.wrdiva; + } + } + return; + + case 0x4207: //HTIMEL + io.htime = (io.htime >> 2) - 1; + io.htime = io.htime & 0x100 | data << 0; + io.htime = (io.htime + 1) << 2; + irqPoll(); //unverified + return; + + case 0x4208: //HTIMEH + io.htime = (io.htime >> 2) - 1; + io.htime = io.htime & 0x0ff | (data & 1) << 8; + io.htime = (io.htime + 1) << 2; + irqPoll(); //unverified + return; + + case 0x4209: //VTIMEL + io.vtime = io.vtime & 0x100 | data << 0; + irqPoll(); //unverified + return; + + case 0x420a: //VTIMEH + io.vtime = io.vtime & 0x0ff | (data & 1) << 8; + irqPoll(); //unverified + return; + + case 0x420b: //DMAEN + for(auto n : range(8)) channels[n].dmaEnable = bool(data & 1 << n); + if(data) status.dmaPending = true; + return; + + case 0x420c: //HDMAEN + for(auto n : range(8)) channels[n].hdmaEnable = bool(data & 1 << n); + return; + + case 0x420d: //MEMSEL + io.fastROM = data & 1; + return; + + } +} + +auto CPU::writeDMA(uint addr, uint8 data) -> void { + auto& channel = this->channels[addr >> 4 & 7]; + + switch(addr & 0xff8f) { + + case 0x4300: //DMAPx + channel.transferMode = data >> 0 & 7; + channel.fixedTransfer = data >> 3 & 1; + channel.reverseTransfer = data >> 4 & 1; + channel.unused = data >> 5 & 1; + channel.indirect = data >> 6 & 1; + channel.direction = data >> 7 & 1; + return; + + case 0x4301: //BBADx + channel.targetAddress = data; + return; + + case 0x4302: //A1TxL + channel.sourceAddress = channel.sourceAddress & 0xff00 | data << 0; + return; + + case 0x4303: //A1TxH + channel.sourceAddress = channel.sourceAddress & 0x00ff | data << 8; + return; + + case 0x4304: //A1Bx + channel.sourceBank = data; + return; + + case 0x4305: //DASxL + channel.transferSize = channel.transferSize & 0xff00 | data << 0; + return; + + case 0x4306: //DASxH + channel.transferSize = channel.transferSize & 0x00ff | data << 8; + return; + + case 0x4307: //DASBx + channel.indirectBank = data; + return; + + case 0x4308: //A2AxL + channel.hdmaAddress = channel.hdmaAddress & 0xff00 | data << 0; + return; + + case 0x4309: //A2AxH + channel.hdmaAddress = channel.hdmaAddress & 0x00ff | data << 8; + return; + + case 0x430a: //NTRLx + channel.lineCounter = data; + return; + + case 0x430b: //???x + channel.unknown = data; + return; + + case 0x430f: //???x ($43xb mirror) + channel.unknown = data; + return; + + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/irq.cpp b/waterbox/bsnescore/bsnes/sfc/cpu/irq.cpp new file mode 100644 index 0000000000..05deed3869 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cpu/irq.cpp @@ -0,0 +1,94 @@ +//nmiPoll() and irqPoll() are called once every four clock cycles; +//as NMI steps by scanlines (divisible by 4) and IRQ by PPU 4-cycle dots. +// +//ppu.(vh)counter(n) returns the value of said counters n-clocks before current time; +//it is used to emulate hardware communication delay between opcode and interrupt units. + +auto CPU::nmiPoll() -> void { + //NMI hold + if(status.nmiHold.lower() && io.nmiEnable) { + status.nmiTransition = 1; + } + + //NMI test + if(status.nmiValid.flip(vcounter(2) >= ppu.vdisp())) { + if(status.nmiLine = status.nmiValid) status.nmiHold = 1; //hold /NMI for four cycles + } +} + +auto CPU::irqPoll() -> void { + //IRQ hold + status.irqHold = 0; + if(status.irqLine && io.irqEnable) { + status.irqTransition = 1; + } + + //IRQ test + if(status.irqValid.raise(io.irqEnable + && (!io.virqEnable || vcounter(10) == io.vtime) + && (!io.hirqEnable || hcounter(10) == io.htime) + && (vcounter(6) || hcounter(6)) //IRQs cannot trigger on last dot of fields + )) status.irqLine = status.irqHold = 1; //hold /IRQ for four cycles +} + +auto CPU::nmitimenUpdate(uint8 data) -> void { + io.hirqEnable = data & 0x10; + io.virqEnable = data & 0x20; + io.irqEnable = io.hirqEnable || io.virqEnable; + + if(io.virqEnable && !io.hirqEnable && status.irqLine) { + status.irqTransition = 1; + } else if(!io.irqEnable) { + status.irqLine = 0; + status.irqTransition = 0; + } + + if(io.nmiEnable.raise(data & 0x80) && status.nmiLine) { + status.nmiTransition = 1; + } + + status.irqLock = 1; +} + +auto CPU::rdnmi() -> bool { + bool result = status.nmiLine; + if(!status.nmiHold) { + status.nmiLine = 0; + } + return result; +} + +auto CPU::timeup() -> bool { + bool result = status.irqLine; + if(!status.irqHold) { + status.irqLine = 0; + status.irqTransition = 0; + } + return result; +} + +auto CPU::nmiTest() -> bool { + if(!status.nmiTransition) return 0; + status.nmiTransition = 0; + r.wai = 0; + return 1; +} + +auto CPU::irqTest() -> bool { + if(!status.irqTransition && !r.irq) return 0; + status.irqTransition = 0; + r.wai = 0; + return !r.p.i; +} + +//used to test for NMI/IRQ, which can trigger on the edge of every opcode. +//test one cycle early to simulate two-stage pipeline of the 65816 CPU. +// +//status.irqLock is used to simulate hardware delay before interrupts can +//trigger during certain events (immediately after DMA, writes to $4200, etc) +auto CPU::lastCycle() -> void { + if(!status.irqLock) { + if(nmiTest()) status.nmiPending = 1, status.interruptPending = 1; + if(irqTest()) status.irqPending = 1, status.interruptPending = 1; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/memory.cpp b/waterbox/bsnescore/bsnes/sfc/cpu/memory.cpp new file mode 100644 index 0000000000..c63de17a44 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cpu/memory.cpp @@ -0,0 +1,86 @@ +auto CPU::idle() -> void { + status.clockCount = 6; + dmaEdge(); + step<6,0>(); + status.irqLock = 0; + aluEdge(); +} + +auto CPU::read(uint address) -> uint8 { + if(address & 0x408000) { + if(address & 0x800000 && io.fastROM) { + status.clockCount = 6; + dmaEdge(); + r.mar = address; + step<2,1>(); + } else { + status.clockCount = 8; + dmaEdge(); + r.mar = address; + step<4,1>(); + } + } else if(address + 0x6000 & 0x4000) { + status.clockCount = 8; + dmaEdge(); + r.mar = address; + step<4,1>(); + } else if(address - 0x4000 & 0x7e00) { + status.clockCount = 6; + dmaEdge(); + r.mar = address; + step<2,1>(); + } else { + status.clockCount = 12; + dmaEdge(); + r.mar = address; + step<8,1>(); + } + + status.irqLock = 0; + auto data = bus.read(address, r.mdr); + step<4,0>(); + aluEdge(); + //$00-3f,80-bf:4000-43ff reads are internal to CPU, and do not update the MDR + if((address & 0x40fc00) != 0x4000) r.mdr = data; + return data; +} + +auto CPU::write(uint address, uint8 data) -> void { + aluEdge(); + + if(address & 0x408000) { + if(address & 0x800000 && io.fastROM) { + status.clockCount = 6; + dmaEdge(); + r.mar = address; + step<6,1>(); + } else { + status.clockCount = 8; + dmaEdge(); + r.mar = address; + step<8,1>(); + } + } else if(address + 0x6000 & 0x4000) { + status.clockCount = 8; + dmaEdge(); + r.mar = address; + step<8,1>(); + } else if(address - 0x4000 & 0x7e00) { + status.clockCount = 6; + dmaEdge(); + r.mar = address; + step<6,1>(); + } else { + status.clockCount = 12; + dmaEdge(); + r.mar = address; + step<12,1>(); + } + + status.irqLock = 0; + bus.write(address, r.mdr = data); +} + +auto CPU::readDisassembler(uint address) -> uint8 { + return bus.read(address, r.mdr); +} diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/cpu/serialization.cpp new file mode 100644 index 0000000000..7a1c240100 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cpu/serialization.cpp @@ -0,0 +1,101 @@ +auto CPU::serialize(serializer& s) -> void { + WDC65816::serialize(s); + Thread::serialize(s); + PPUcounter::serialize(s); + + s.array(wram); + + s.integer(version); + + s.integer(counter.cpu); + s.integer(counter.dma); + + s.integer(status.clockCount); + + s.integer(status.irqLock); + + s.integer(status.dramRefreshPosition); + s.integer(status.dramRefresh); + + s.integer(status.hdmaSetupPosition); + s.integer(status.hdmaSetupTriggered); + + s.integer(status.hdmaPosition); + s.integer(status.hdmaTriggered); + + s.boolean(status.nmiValid); + s.boolean(status.nmiLine); + s.boolean(status.nmiTransition); + s.boolean(status.nmiPending); + s.boolean(status.nmiHold); + + s.boolean(status.irqValid); + s.boolean(status.irqLine); + s.boolean(status.irqTransition); + s.boolean(status.irqPending); + s.boolean(status.irqHold); + + s.integer(status.resetPending); + s.integer(status.interruptPending); + + s.integer(status.dmaActive); + s.integer(status.dmaPending); + s.integer(status.hdmaPending); + s.integer(status.hdmaMode); + + s.integer(status.autoJoypadCounter); + + s.integer(io.wramAddress); + + s.boolean(io.hirqEnable); + s.boolean(io.virqEnable); + s.boolean(io.irqEnable); + s.boolean(io.nmiEnable); + s.boolean(io.autoJoypadPoll); + + s.integer(io.pio); + + s.integer(io.wrmpya); + s.integer(io.wrmpyb); + + s.integer(io.wrdiva); + s.integer(io.wrdivb); + + s.integer(io.htime); + s.integer(io.vtime); + + s.integer(io.fastROM); + + s.integer(io.rddiv); + s.integer(io.rdmpy); + + s.integer(io.joy1); + s.integer(io.joy2); + s.integer(io.joy3); + s.integer(io.joy4); + + s.integer(alu.mpyctr); + s.integer(alu.divctr); + s.integer(alu.shift); + + for(auto& channel : channels) { + s.integer(channel.dmaEnable); + s.integer(channel.hdmaEnable); + s.integer(channel.direction); + s.integer(channel.indirect); + s.integer(channel.unused); + s.integer(channel.reverseTransfer); + s.integer(channel.fixedTransfer); + s.integer(channel.transferMode); + s.integer(channel.targetAddress); + s.integer(channel.sourceAddress); + s.integer(channel.sourceBank); + s.integer(channel.transferSize); + s.integer(channel.indirectBank); + s.integer(channel.hdmaAddress); + s.integer(channel.lineCounter); + s.integer(channel.unknown); + s.integer(channel.hdmaCompleted); + s.integer(channel.hdmaDoTransfer); + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/timing.cpp b/waterbox/bsnescore/bsnes/sfc/cpu/timing.cpp new file mode 100644 index 0000000000..e443e5a5fa --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/cpu/timing.cpp @@ -0,0 +1,246 @@ +//DMA clock divider +auto CPU::dmaCounter() const -> uint { + return counter.cpu & 7; +} + +//joypad auto-poll clock divider +auto CPU::joypadCounter() const -> uint { + return counter.cpu & 127; +} + +auto CPU::stepOnce() -> void { + counter.cpu += 2; + tick(); + if(hcounter() & 2) nmiPoll(), irqPoll(); + if(joypadCounter() == 0) joypadEdge(); +} + +template +auto CPU::step() -> void { + static_assert(Clocks == 2 || Clocks == 4 || Clocks == 6 || Clocks == 8 || Clocks == 10 || Clocks == 12); + + for(auto coprocessor : coprocessors) { + if(coprocessor == &icd || coprocessor == &msu1) continue; + coprocessor->clock -= Clocks * (uint64)coprocessor->frequency; + } + + if(overclocking.target) { + overclocking.counter += Clocks; + if(overclocking.counter < overclocking.target) { + if constexpr(Synchronize) { + if(configuration.hacks.coprocessor.delayedSync) return; + synchronizeCoprocessors(); + } + return; + } + } + + if constexpr(Clocks >= 2) stepOnce(); + if constexpr(Clocks >= 4) stepOnce(); + if constexpr(Clocks >= 6) stepOnce(); + if constexpr(Clocks >= 8) stepOnce(); + if constexpr(Clocks >= 10) stepOnce(); + if constexpr(Clocks >= 12) stepOnce(); + + smp.clock -= Clocks * (uint64)smp.frequency; + ppu.clock -= Clocks; + for(auto coprocessor : coprocessors) { + if(coprocessor != &icd && coprocessor != &msu1) continue; + coprocessor->clock -= Clocks * (uint64)coprocessor->frequency; + } + + if(!status.dramRefresh && hcounter() >= status.dramRefreshPosition) { + //note: pattern should technically be 5-3, 5-3, 5-3, 5-3, 5-3 per logic analyzer + //result averages out the same as no coprocessor polls refresh() at > frequency()/2 + status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); + status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); + status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); + status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); + status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); + } + + if(!status.hdmaSetupTriggered && hcounter() >= status.hdmaSetupPosition) { + status.hdmaSetupTriggered = true; + hdmaReset(); + if(hdmaEnable()) { + status.hdmaPending = true; + status.hdmaMode = 0; + } + } + + if(!status.hdmaTriggered && hcounter() >= status.hdmaPosition) { + status.hdmaTriggered = true; + if(hdmaActive()) { + status.hdmaPending = true; + status.hdmaMode = 1; + } + } + + if constexpr(Synchronize) { + if(configuration.hacks.coprocessor.delayedSync) return; + synchronizeCoprocessors(); + } +} + +auto CPU::step(uint clocks) -> void { + switch(clocks) { + case 2: return step< 2,1>(); + case 4: return step< 4,1>(); + case 6: return step< 6,1>(); + case 8: return step< 8,1>(); + case 10: return step<10,1>(); + case 12: return step<12,1>(); + } +} + +//called by ppu.tick() when Hcounter=0 +auto CPU::scanline() -> void { + //forcefully sync S-CPU to other processors, in case chips are not communicating + synchronizeSMP(); + synchronizePPU(); + synchronizeCoprocessors(); + + if(vcounter() == 0) { + //HDMA setup triggers once every frame + status.hdmaSetupPosition = (version == 1 ? 12 + 8 - dmaCounter() : 12 + dmaCounter()); + status.hdmaSetupTriggered = false; + + status.autoJoypadCounter = 33; //33 = inactive + } + + //DRAM refresh occurs once every scanline + if(version == 2) status.dramRefreshPosition = 530 + 8 - dmaCounter(); + status.dramRefresh = 0; + + //HDMA triggers once every visible scanline + if(vcounter() < ppu.vdisp()) { + status.hdmaPosition = 1104; + status.hdmaTriggered = false; + } + + //overclocking + if(vcounter() == (Region::NTSC() ? 261 : 311)) { + overclocking.counter = 0; + overclocking.target = 0; + double overclock = configuration.hacks.cpu.overclock / 100.0; + if(overclock > 1.0) { + int clocks = (Region::NTSC() ? 262 : 312) * 1364; + overclocking.target = clocks * overclock - clocks; + } + } + + //handle video frame events from the CPU core to prevent a race condition between + //games polling inputs during NMI and the PPU thread not being 100% synchronized. + if(vcounter() == ppu.vdisp()) { + if(auto device = controllerPort2.device) device->latch(); //light guns + synchronizePPU(); + if(system.fastPPU()) PPUfast::Line::flush(); + scheduler.leave(Scheduler::Event::Frame); + } +} + +auto CPU::aluEdge() -> void { + if(alu.mpyctr) { + alu.mpyctr--; + if(io.rddiv & 1) io.rdmpy += alu.shift; + io.rddiv >>= 1; + alu.shift <<= 1; + } + + if(alu.divctr) { + alu.divctr--; + io.rddiv <<= 1; + alu.shift >>= 1; + if(io.rdmpy >= alu.shift) { + io.rdmpy -= alu.shift; + io.rddiv |= 1; + } + } +} + +auto CPU::dmaEdge() -> void { + //H/DMA pending && DMA inactive? + //.. Run one full CPU cycle + //.. HDMA pending && HDMA enabled ? DMA sync + HDMA run + //.. DMA pending && DMA enabled ? DMA sync + DMA run + //.... HDMA during DMA && HDMA enabled ? DMA sync + HDMA run + //.. Run one bus CPU cycle + //.. CPU sync + + if(status.dmaActive) { + if(status.hdmaPending) { + status.hdmaPending = false; + if(hdmaEnable()) { + if(!dmaEnable()) { + step(counter.dma = 8 - dmaCounter()); + } + status.hdmaMode == 0 ? hdmaSetup() : hdmaRun(); + if(!dmaEnable()) { + step(status.clockCount - counter.dma % status.clockCount); + status.dmaActive = false; + } + } + } + + if(status.dmaPending) { + status.dmaPending = false; + if(dmaEnable()) { + step(counter.dma = 8 - dmaCounter()); + dmaRun(); + step(status.clockCount - counter.dma % status.clockCount); + status.dmaActive = false; + } + } + } + + if(!status.dmaActive) { + if(status.dmaPending || status.hdmaPending) { + status.dmaActive = true; + } + } +} + +//called every 128 clocks from inside the CPU::stepOnce() function +auto CPU::joypadEdge() -> void { + //it is not yet confirmed if polling can be stopped early and/or (re)started later + if(!io.autoJoypadPoll) return; + + if(vcounter() == ppu.vdisp() && hcounter() >= 130 && hcounter() <= 256) { + //begin new polling sequence + status.autoJoypadCounter = 0; + } + + //stop after polling has been completed for this frame + if(status.autoJoypadCounter >= 33) return; + + if(status.autoJoypadCounter == 0) { + //latch controller states on the first polling cycle + controllerPort1.device->latch(1); + controllerPort2.device->latch(1); + } + + if(status.autoJoypadCounter == 1) { + //release latch and begin reading on the second cycle + controllerPort1.device->latch(0); + controllerPort2.device->latch(0); + + //shift registers are cleared to zero at start of auto-joypad polling + io.joy1 = 0; + io.joy2 = 0; + io.joy3 = 0; + io.joy4 = 0; + } + + if(status.autoJoypadCounter >= 2 && !(status.autoJoypadCounter & 1)) { + //sixteen bits are shifted into joy{1-4}, one bit per 256 clocks + uint2 port0 = controllerPort1.device->data(); + uint2 port1 = controllerPort2.device->data(); + + io.joy1 = io.joy1 << 1 | port0.bit(0); + io.joy2 = io.joy2 << 1 | port1.bit(0); + io.joy3 = io.joy3 << 1 | port0.bit(1); + io.joy4 = io.joy4 << 1 | port1.bit(1); + } + + status.autoJoypadCounter++; +} diff --git a/waterbox/bsnescore/bsnes/sfc/dsp/SPC_DSP.cpp b/waterbox/bsnescore/bsnes/sfc/dsp/SPC_DSP.cpp new file mode 100644 index 0000000000..a89d491f95 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/dsp/SPC_DSP.cpp @@ -0,0 +1,1048 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SPC_DSP.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +#if INT_MAX < 0x7FFFFFFF + #error "Requires that int type have at least 32 bits" +#endif + +// TODO: add to blargg_endian.h +#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) +#define GET_LE16A( addr ) GET_LE16( addr ) +#define SET_LE16A( addr, data ) SET_LE16( addr, data ) + +static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = +{ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +// 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, +// 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, +// 0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A, +// 0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF, +// 0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67, +// 0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF, +// 0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F, +// 0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF +}; + +// if ( io < -32768 ) io = -32768; +// if ( io > 32767 ) io = 32767; +#define CLAMP16( io )\ +{\ + if ( (int16_t) io != io )\ + io = (io >> 31) ^ 0x7FFF;\ +} + +// Access global DSP register +#define REG(n) m.regs [r_##n] + +// Access voice DSP register +#define VREG(r,n) r [v_##n] + +#define WRITE_SAMPLES( l, r, out ) \ +{\ + out [0] = l;\ + out [1] = r;\ + out += 2;\ + if ( out >= m.out_end )\ + {\ + check( out == m.out_end );\ + check( m.out_end != &m.extra [extra_size] || \ + (m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\ + out = m.extra;\ + m.out_end = &m.extra [extra_size];\ + }\ +}\ + +void SPC_DSP::set_output( sample_t* out, int size ) +{ + require( (size & 1) == 0 ); // must be even + if ( !out ) + { + out = m.extra; + size = extra_size; + } + m.out_begin = out; + m.out = out; + m.out_end = out + size; +} + +// Volume registers and efb are signed! Easy to forget int8_t cast. +// Prefixes are to avoid accidental use of locals with same names. + +// Gaussian interpolation + +static short const gauss [512] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, + 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, + 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77, + 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102, + 104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132, + 134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168, + 171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210, + 212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257, + 260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311, + 314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370, + 374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434, + 439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504, + 508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577, + 582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654, + 659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732, + 737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811, + 816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889, + 894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965, + 969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036, +1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102, +1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160, +1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210, +1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251, +1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280, +1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298, +1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, +}; + +inline int SPC_DSP::interpolate( voice_t const* v ) +{ + int out; + int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; + //in[3] is the newest sample, in[0] is the oldest sample + + if(configuration.hacks.dsp.cubic) { + float a = in[0] / 32768.0; + float b = in[1] / 32768.0; + float c = in[2] / 32768.0; + float d = in[3] / 32768.0; + + float A = d - c - a + b; + float B = a - b - A; + float C = c - a; + float D = b; + + float P = (v->interp_pos & 0xFFF) / 4096.0; + float O = (A * P * P * P) + (B * P * P) + (C * P) + (D); + + out = O * 32768.0; + } else { + // Make pointers into gaussian based on fractional position between samples + int offset = v->interp_pos >> 4 & 0xFF; + short const* fwd = gauss + 255 - offset; + short const* rev = gauss + offset; // mirror left half of gaussian + + out = (fwd [ 0] * in [0]) >> 11; + out += (fwd [256] * in [1]) >> 11; + out += (rev [256] * in [2]) >> 11; + out = (int16_t) out; + out += (rev [ 0] * in [3]) >> 11; + } + + CLAMP16( out ); + out &= ~1; + return out; +} + + +//// Counters + +int const simple_counter_range = 2048 * 5 * 3; // 30720 + +static unsigned const counter_rates [32] = +{ + simple_counter_range + 1, // never fires + 2048, 1536, + 1280, 1024, 768, + 640, 512, 384, + 320, 256, 192, + 160, 128, 96, + 80, 64, 48, + 40, 32, 24, + 20, 16, 12, + 10, 8, 6, + 5, 4, 3, + 2, + 1 +}; + +static unsigned const counter_offsets [32] = +{ + 1, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 0, + 0 +}; + +inline void SPC_DSP::init_counter() +{ + m.counter = 0; +} + +inline void SPC_DSP::run_counters() +{ + if ( --m.counter < 0 ) + m.counter = simple_counter_range - 1; +} + +inline unsigned SPC_DSP::read_counter( int rate ) +{ + return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate]; +} + + +//// Envelope + +inline void SPC_DSP::run_envelope( voice_t* const v ) +{ + int env = v->env; + if ( v->env_mode == env_release ) // 60% + { + if ( (env -= 0x8) < 0 ) + env = 0; + v->env = env; + } + else + { + int rate; + int env_data = VREG(v->regs,adsr1); + if ( m.t_adsr0 & 0x80 ) // 99% ADSR + { + if ( v->env_mode >= env_decay ) // 99% + { + env--; + env -= env >> 8; + rate = env_data & 0x1F; + if ( v->env_mode == env_decay ) // 1% + rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10; + } + else // env_attack + { + rate = (m.t_adsr0 & 0x0F) * 2 + 1; + env += rate < 31 ? 0x20 : 0x400; + } + } + else // GAIN + { + int mode; + env_data = VREG(v->regs,gain); + mode = env_data >> 5; + if ( mode < 4 ) // direct + { + env = env_data * 0x10; + rate = 31; + } + else + { + rate = env_data & 0x1F; + if ( mode == 4 ) // 4: linear decrease + { + env -= 0x20; + } + else if ( mode < 6 ) // 5: exponential decrease + { + env--; + env -= env >> 8; + } + else // 6,7: linear increase + { + env += 0x20; + if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) + env += 0x8 - 0x20; // 7: two-slope linear increase + } + } + } + + // Sustain level + if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) + v->env_mode = env_sustain; + + v->hidden_env = env; + + // unsigned cast because linear decrease going negative also triggers this + if ( (unsigned) env > 0x7FF ) + { + env = (env < 0 ? 0 : 0x7FF); + if ( v->env_mode == env_attack ) + v->env_mode = env_decay; + } + + if ( !read_counter( rate ) ) + v->env = env; // nothing else is controlled by the counter + } +} + + +//// BRR Decoding + +inline void SPC_DSP::decode_brr( voice_t* v ) +{ + // Arrange the four input nybbles in 0xABCD order for easy decoding + int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; + + int const header = m.t_brr_header; + + // Write to next four samples in circular buffer + int* pos = &v->buf [v->buf_pos]; + int* end; + if ( (v->buf_pos += 4) >= brr_buf_size ) + v->buf_pos = 0; + + // Decode four samples + for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) + { + // Extract nybble and sign-extend + int s = (int16_t) nybbles >> 12; + + // Shift sample based on header + int const shift = header >> 4; + s = (s << shift) >> 1; + if ( shift >= 0xD ) // handle invalid range + s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0) + + // Apply IIR filter (8 is the most commonly used) + int const filter = header & 0x0C; + int const p1 = pos [brr_buf_size - 1]; + int const p2 = pos [brr_buf_size - 2] >> 1; + if ( filter >= 8 ) + { + s += p1; + s -= p2; + if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875 + { + s += p2 >> 4; + s += (p1 * -3) >> 6; + } + else // s += p1 * 0.8984375 - p2 * 0.40625 + { + s += (p1 * -13) >> 7; + s += (p2 * 3) >> 4; + } + } + else if ( filter ) // s += p1 * 0.46875 + { + s += p1 >> 1; + s += (-p1) >> 5; + } + + // Adjust and write sample + CLAMP16( s ); + s = (int16_t) (s * 2); + pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around + } +} + + +//// Misc + +#define MISC_CLOCK( n ) inline void SPC_DSP::misc_##n() + +MISC_CLOCK( 27 ) +{ + m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON +} +MISC_CLOCK( 28 ) +{ + m.t_non = REG(non); + m.t_eon = REG(eon); + m.t_dir = REG(dir); +} +MISC_CLOCK( 29 ) +{ + if ( (m.every_other_sample ^= 1) != 0 ) + m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read +} +MISC_CLOCK( 30 ) +{ + if ( m.every_other_sample ) + { + m.kon = m.new_kon; + m.t_koff = REG(koff) | m.mute_mask; + } + + run_counters(); + + // Noise + if ( !read_counter( REG(flg) & 0x1F ) ) + { + int feedback = (m.noise << 13) ^ (m.noise << 14); + m.noise = (feedback & 0x4000) ^ (m.noise >> 1); + } +} + + +//// Voices + +#define VOICE_CLOCK( n ) void SPC_DSP::voice_##n( voice_t* const v ) + +inline VOICE_CLOCK( V1 ) +{ + m.t_dir_addr = (m.t_dir * 0x100 + m.t_srcn * 4) & 0xffff; + m.t_srcn = VREG(v->regs,srcn); +} +inline VOICE_CLOCK( V2 ) +{ + // Read sample pointer (ignored if not needed) + uint8_t const* entry = &m.ram [m.t_dir_addr]; + if ( !v->kon_delay ) + entry += 2; + m.t_brr_next_addr = GET_LE16A( entry ); + + m.t_adsr0 = VREG(v->regs,adsr0); + + // Read pitch, spread over two clocks + m.t_pitch = VREG(v->regs,pitchl); +} +inline VOICE_CLOCK( V3a ) +{ + m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8; +} +inline VOICE_CLOCK( V3b ) +{ + // Read BRR header and byte + m.t_brr_byte = m.ram [(v->brr_addr + v->brr_offset) & 0xFFFF]; + m.t_brr_header = m.ram [v->brr_addr]; // brr_addr doesn't need masking +} +VOICE_CLOCK( V3c ) +{ + // Pitch modulation using previous voice's output + if ( m.t_pmon & v->vbit ) + m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10; + + if ( v->kon_delay ) + { + // Get ready to start BRR decoding on next sample + if ( v->kon_delay == 5 ) + { + v->brr_addr = m.t_brr_next_addr; + v->brr_offset = 1; + v->buf_pos = 0; + m.t_brr_header = 0; // header is ignored on this sample + m.kon_check = true; + } + + // Envelope is never run during KON + v->env = 0; + v->hidden_env = 0; + + // Disable BRR decoding until last three samples + v->interp_pos = 0; + if ( --v->kon_delay & 3 ) + v->interp_pos = 0x4000; + + // Pitch is never added during KON + m.t_pitch = 0; + } + + // Gaussian interpolation + { + int output = interpolate( v ); + + // Noise + if ( m.t_non & v->vbit ) + output = (int16_t) (m.noise * 2); + + // Apply envelope + m.t_output = (output * v->env) >> 11 & ~1; + v->t_envx_out = (uint8_t) (v->env >> 4); + } + + // Immediate silence due to end of sample or soft reset + if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) + { + v->env_mode = env_release; + v->env = 0; + } + + if ( m.every_other_sample ) + { + // KOFF + if ( m.t_koff & v->vbit ) + v->env_mode = env_release; + + // KON + if ( m.kon & v->vbit ) + { + v->kon_delay = 5; + v->env_mode = env_attack; + } + } + + // Run envelope for next sample + if ( !v->kon_delay ) + run_envelope( v ); +} +inline void SPC_DSP::voice_output( voice_t const* v, int ch ) +{ + // Apply left/right volume + int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7; + + // Add to output total + m.t_main_out [ch] += amp; + CLAMP16( m.t_main_out [ch] ); + + // Optionally add to echo total + if ( m.t_eon & v->vbit ) + { + m.t_echo_out [ch] += amp; + CLAMP16( m.t_echo_out [ch] ); + } +} +VOICE_CLOCK( V4 ) +{ + // Decode BRR + m.t_looped = 0; + if ( v->interp_pos >= 0x4000 ) + { + decode_brr( v ); + + if ( (v->brr_offset += 2) >= brr_block_size ) + { + // Start decoding next BRR block + assert( v->brr_offset == brr_block_size ); + v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; + if ( m.t_brr_header & 1 ) + { + v->brr_addr = m.t_brr_next_addr; + m.t_looped = v->vbit; + } + v->brr_offset = 1; + } + } + + // Apply pitch + v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; + + // Keep from getting too far ahead (when using pitch modulation) + if ( v->interp_pos > 0x7FFF ) + v->interp_pos = 0x7FFF; + + // Output left + voice_output( v, 0 ); +} +inline VOICE_CLOCK( V5 ) +{ + // Output right + voice_output( v, 1 ); + + // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier + int endx_buf = REG(endx) | m.t_looped; + + // Clear bit in ENDX if KON just began + if ( v->kon_delay == 5 ) + endx_buf &= ~v->vbit; + m.endx_buf = (uint8_t) endx_buf; +} +inline VOICE_CLOCK( V6 ) +{ + (void) v; // avoid compiler warning about unused v + m.outx_buf = (uint8_t) (m.t_output >> 8); +} +inline VOICE_CLOCK( V7 ) +{ + // Update ENDX + REG(endx) = m.endx_buf; + + m.envx_buf = v->t_envx_out; +} +inline VOICE_CLOCK( V8 ) +{ + // Update OUTX + VREG(v->regs,outx) = m.outx_buf; +} +inline VOICE_CLOCK( V9 ) +{ + // Update ENVX + VREG(v->regs,envx) = m.envx_buf; +} + +// Most voices do all these in one clock, so make a handy composite +inline VOICE_CLOCK( V3 ) +{ + voice_V3a( v ); + voice_V3b( v ); + voice_V3c( v ); +} + +// Common combinations of voice steps on different voices. This greatly reduces +// code size and allows everything to be inlined in these functions. +VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); } +VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); } +VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); } + + +//// Echo + +// Current echo buffer pointer for left/right channel +#define ECHO_PTR( ch ) (&m.echo [m.t_echo_ptr + ch * 2]) + +// Sample in echo history buffer, where 0 is the oldest +#define ECHO_FIR( i ) (m.echo_hist_pos [i]) + +// Calculate FIR point for left/right channel +#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6) + +#define ECHO_CLOCK( n ) inline void SPC_DSP::echo_##n() + +inline void SPC_DSP::echo_read( int ch ) +{ + int s = GET_LE16SA( ECHO_PTR( ch ) ); + // second copy simplifies wrap-around handling + ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; +} + +ECHO_CLOCK( 22 ) +{ + // History + if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) + m.echo_hist_pos = m.echo_hist; + + m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; + echo_read( 0 ); + + // FIR (using l and r temporaries below helps compiler optimize) + int l = CALC_FIR( 0, 0 ); + int r = CALC_FIR( 0, 1 ); + + m.t_echo_in [0] = l; + m.t_echo_in [1] = r; +} +ECHO_CLOCK( 23 ) +{ + int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); + int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; + + echo_read( 1 ); +} +ECHO_CLOCK( 24 ) +{ + int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); + int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; +} +ECHO_CLOCK( 25 ) +{ + int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); + int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); + + l = (int16_t) l; + r = (int16_t) r; + + l += (int16_t) CALC_FIR( 7, 0 ); + r += (int16_t) CALC_FIR( 7, 1 ); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_in [0] = l & ~1; + m.t_echo_in [1] = r & ~1; +} +inline int SPC_DSP::echo_output( int ch ) +{ + int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) + + (int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7); + CLAMP16( out ); + return out; +} +ECHO_CLOCK( 26 ) +{ + // Left output volumes + // (save sample for next clock so we can output both together) + m.t_main_out [0] = echo_output( 0 ); + + // Echo feedback + int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); + int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_out [0] = l & ~1; + m.t_echo_out [1] = r & ~1; +} +ECHO_CLOCK( 27 ) +{ + // Output + int l = m.t_main_out [0]; + int r = echo_output( 1 ); + m.t_main_out [0] = 0; + m.t_main_out [1] = 0; + + // TODO: global muting isn't this simple (turns DAC on and off + // or something, causing small ~37-sample pulse when first muted) + if ( REG(flg) & 0x40 ) + { + l = 0; + r = 0; + } + + // Output sample to DAC + #ifdef SPC_DSP_OUT_HOOK + SPC_DSP_OUT_HOOK( l, r ); + #else + sample_t* out = m.out; + WRITE_SAMPLES( l, r, out ); + m.out = out; + #endif +} +ECHO_CLOCK( 28 ) +{ + m.t_echo_enabled = REG(flg); +} +inline void SPC_DSP::echo_write( int ch ) +{ + if ( !(m.t_echo_enabled & 0x20) ) + SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] ); + m.t_echo_out [ch] = 0; +} +ECHO_CLOCK( 29 ) +{ + m.t_esa = REG(esa); + + if ( !m.echo_offset ) + m.echo_length = (REG(edl) & 0x0F) * 0x800; + + m.echo_offset += 4; + if ( m.echo_offset >= m.echo_length ) + m.echo_offset = 0; + + // Write left echo + echo_write( 0 ); + + m.t_echo_enabled = REG(flg); +} +ECHO_CLOCK( 30 ) +{ + // Write right echo + echo_write( 1 ); +} + + +//// Timing + +// Execute clock for a particular voice +#define V( clock, voice ) voice_##clock( &m.voices [voice] ); + +/* The most common sequence of clocks uses composite operations +for efficiency. For example, the following are equivalent to the +individual steps on the right: + +V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5) +V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4) +V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */ + +// Voice 0 1 2 3 4 5 6 7 +#define GEN_DSP_TIMING \ +PHASE( 0) V(V5,0)V(V2,1)\ +PHASE( 1) V(V6,0)V(V3,1)\ +PHASE( 2) V(V7_V4_V1,0)\ +PHASE( 3) V(V8_V5_V2,0)\ +PHASE( 4) V(V9_V6_V3,0)\ +PHASE( 5) V(V7_V4_V1,1)\ +PHASE( 6) V(V8_V5_V2,1)\ +PHASE( 7) V(V9_V6_V3,1)\ +PHASE( 8) V(V7_V4_V1,2)\ +PHASE( 9) V(V8_V5_V2,2)\ +PHASE(10) V(V9_V6_V3,2)\ +PHASE(11) V(V7_V4_V1,3)\ +PHASE(12) V(V8_V5_V2,3)\ +PHASE(13) V(V9_V6_V3,3)\ +PHASE(14) V(V7_V4_V1,4)\ +PHASE(15) V(V8_V5_V2,4)\ +PHASE(16) V(V9_V6_V3,4)\ +PHASE(17) V(V1,0) V(V7,5)V(V4,6)\ +PHASE(18) V(V8_V5_V2,5)\ +PHASE(19) V(V9_V6_V3,5)\ +PHASE(20) V(V1,1) V(V7,6)V(V4,7)\ +PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\ +PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\ +PHASE(23) V(V7,7) echo_23();\ +PHASE(24) V(V8,7) echo_24();\ +PHASE(25) V(V3b,0) V(V9,7) echo_25();\ +PHASE(26) echo_26();\ +PHASE(27) misc_27(); echo_27();\ +PHASE(28) misc_28(); echo_28();\ +PHASE(29) misc_29(); echo_29();\ +PHASE(30) misc_30();V(V3c,0) echo_30();\ +PHASE(31) V(V4,0) V(V1,2)\ + +#if !SPC_DSP_CUSTOM_RUN + +void SPC_DSP::run( int clocks_remain ) +{ + require( clocks_remain > 0 ); + + int const phase = m.phase; + m.phase = (phase + clocks_remain) & 31; + switch ( phase ) + { + loop: + + #define PHASE( n ) if ( n && !--clocks_remain ) break; case n: + GEN_DSP_TIMING + #undef PHASE + + if ( --clocks_remain ) + goto loop; + } +} + +#endif + + +//// Setup + +void SPC_DSP::init( void* ram_64k, void* echo_64k ) +{ + m.ram = (uint8_t*) ram_64k; + m.echo = (uint8_t*) echo_64k; + mute_voices( 0 ); + disable_surround( false ); + set_output( 0, 0 ); + reset(); + + #ifndef NDEBUG + // be sure this sign-extends + assert( (int16_t) 0x8000 == -0x8000 ); + + // be sure right shift preserves sign + assert( (-1 >> 1) == -1 ); + + // check clamp macro + int i; + i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF ); + i = -0x8001; CLAMP16( i ); assert( i == -0x8000 ); + + blargg_verify_byte_order(); + #endif +} + +void SPC_DSP::soft_reset_common() +{ + require( m.ram ); // init() must have been called already + require( m.echo ); + + m.noise = 0x4000; + m.echo_hist_pos = m.echo_hist; + m.every_other_sample = 1; + m.echo_offset = 0; + m.phase = 0; + + init_counter(); +} + +void SPC_DSP::soft_reset() +{ + REG(flg) = 0xE0; + soft_reset_common(); +} + +void SPC_DSP::load( uint8_t const regs [register_count] ) +{ + memcpy( m.regs, regs, sizeof m.regs ); + memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); + + // Internal state + for ( int i = voice_count; --i >= 0; ) + { + voice_t* v = &m.voices [i]; + v->brr_offset = 1; + v->vbit = 1 << i; + v->regs = &m.regs [i * 0x10]; + } + m.new_kon = REG(kon); + m.t_dir = REG(dir); + m.t_esa = REG(esa); + + soft_reset_common(); +} + +void SPC_DSP::reset() { load( initial_regs ); } + + +//// State save/load + +#if !SPC_NO_COPY_STATE_FUNCS + +void SPC_State_Copier::copy( void* state, size_t size ) +{ + func( buf, state, size ); +} + +int SPC_State_Copier::copy_int( int state, int size ) +{ + BOOST::uint8_t s [2]; + SET_LE16( s, state ); + func( buf, &s, size ); + return GET_LE16( s ); +} + +void SPC_State_Copier::skip( int count ) +{ + if ( count > 0 ) + { + char temp [64]; + memset( temp, 0, sizeof temp ); + do + { + int n = sizeof temp; + if ( n > count ) + n = count; + count -= n; + func( buf, temp, n ); + } + while ( count ); + } +} + +void SPC_State_Copier::extra() +{ + int n = 0; + SPC_State_Copier& copier = *this; + SPC_COPY( uint8_t, n ); + skip( n ); +} + +void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) +{ + SPC_State_Copier copier( io, copy ); + + // DSP registers + copier.copy( m.regs, register_count ); + + // Internal state + + // Voices + int i; + for ( i = 0; i < voice_count; i++ ) + { + voice_t* v = &m.voices [i]; + + // BRR buffer + int i; + for ( i = 0; i < brr_buf_size; i++ ) + { + int s = v->buf [i]; + SPC_COPY( int16_t, s ); + v->buf [i] = v->buf [i + brr_buf_size] = s; + } + + SPC_COPY( uint16_t, v->interp_pos ); + SPC_COPY( uint16_t, v->brr_addr ); + SPC_COPY( uint16_t, v->env ); + SPC_COPY( int16_t, v->hidden_env ); + SPC_COPY( uint8_t, v->buf_pos ); + SPC_COPY( uint8_t, v->brr_offset ); + SPC_COPY( uint8_t, v->kon_delay ); + { + int m = v->env_mode; + SPC_COPY( uint8_t, m ); + v->env_mode = (enum env_mode_t) m; + } + SPC_COPY( uint8_t, v->t_envx_out ); + + copier.extra(); + } + + // Echo history + for ( i = 0; i < echo_hist_size; i++ ) + { + int j; + for ( j = 0; j < 2; j++ ) + { + int s = m.echo_hist_pos [i] [j]; + SPC_COPY( int16_t, s ); + m.echo_hist [i] [j] = s; // write back at offset 0 + } + } + m.echo_hist_pos = m.echo_hist; + memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] ); + + // Misc + SPC_COPY( uint8_t, m.every_other_sample ); + SPC_COPY( uint8_t, m.kon ); + + SPC_COPY( uint16_t, m.noise ); + SPC_COPY( uint16_t, m.counter ); + SPC_COPY( uint16_t, m.echo_offset ); + SPC_COPY( uint16_t, m.echo_length ); + SPC_COPY( uint8_t, m.phase ); + + SPC_COPY( uint8_t, m.new_kon ); + SPC_COPY( uint8_t, m.endx_buf ); + SPC_COPY( uint8_t, m.envx_buf ); + SPC_COPY( uint8_t, m.outx_buf ); + + SPC_COPY( uint8_t, m.t_pmon ); + SPC_COPY( uint8_t, m.t_non ); + SPC_COPY( uint8_t, m.t_eon ); + SPC_COPY( uint8_t, m.t_dir ); + SPC_COPY( uint8_t, m.t_koff ); + + SPC_COPY( uint16_t, m.t_brr_next_addr ); + SPC_COPY( uint8_t, m.t_adsr0 ); + SPC_COPY( uint8_t, m.t_brr_header ); + SPC_COPY( uint8_t, m.t_brr_byte ); + SPC_COPY( uint8_t, m.t_srcn ); + SPC_COPY( uint8_t, m.t_esa ); + SPC_COPY( uint8_t, m.t_echo_enabled ); + + SPC_COPY( int16_t, m.t_main_out [0] ); + SPC_COPY( int16_t, m.t_main_out [1] ); + SPC_COPY( int16_t, m.t_echo_out [0] ); + SPC_COPY( int16_t, m.t_echo_out [1] ); + SPC_COPY( int16_t, m.t_echo_in [0] ); + SPC_COPY( int16_t, m.t_echo_in [1] ); + + SPC_COPY( uint16_t, m.t_dir_addr ); + SPC_COPY( uint16_t, m.t_pitch ); + SPC_COPY( int16_t, m.t_output ); + SPC_COPY( uint16_t, m.t_echo_ptr ); + SPC_COPY( uint8_t, m.t_looped ); + + copier.extra(); +} +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/dsp/SPC_DSP.h b/waterbox/bsnescore/bsnes/sfc/dsp/SPC_DSP.h new file mode 100644 index 0000000000..50fd7beab1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/dsp/SPC_DSP.h @@ -0,0 +1,308 @@ +// Highly accurate SNES SPC-700 DSP emulator + +// snes_spc 0.9.0 +#ifndef SPC_DSP_H +#define SPC_DSP_H + +#include "blargg_common.h" + +extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); } + +class SPC_DSP { +public: + typedef BOOST::uint8_t uint8_t; + +// Setup + + // Initializes DSP and has it use the 64K RAM provided + void init( void* ram_64k, void* echo_64k ); + + // Sets destination for output samples. If out is NULL or out_size is 0, + // doesn't generate any. + typedef short sample_t; + void set_output( sample_t* out, int out_size ); + + // Number of samples written to output since it was last set, always + // a multiple of 2. Undefined if more samples were generated than + // output buffer could hold. + int sample_count() const; + +// Emulation + + // Resets DSP to power-on state + void reset(); + + // Emulates pressing reset switch on SNES + void soft_reset(); + + // Reads/writes DSP registers. For accuracy, you must first call run() + // to catch the DSP up to present. + int read ( int addr ) const; + void write( int addr, int data ); + + // Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks + // a pair of samples is be generated. + void run( int clock_count ); + +// Sound control + + // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). + // Reduces emulation accuracy. + enum { voice_count = 8 }; + void mute_voices( int mask ); + +// State + + // Resets DSP and uses supplied values to initialize registers + enum { register_count = 128 }; + void load( uint8_t const regs [register_count] ); + + // Saves/loads exact emulator state + enum { state_size = 640 }; // maximum space needed when saving + typedef dsp_copy_func_t copy_func_t; + void copy_state( unsigned char** io, copy_func_t ); + + // Returns non-zero if new key-on events occurred since last call + bool check_kon(); + +// DSP register addresses + + // Global registers + enum { + r_mvoll = 0x0C, r_mvolr = 0x1C, + r_evoll = 0x2C, r_evolr = 0x3C, + r_kon = 0x4C, r_koff = 0x5C, + r_flg = 0x6C, r_endx = 0x7C, + r_efb = 0x0D, r_pmon = 0x2D, + r_non = 0x3D, r_eon = 0x4D, + r_dir = 0x5D, r_esa = 0x6D, + r_edl = 0x7D, + r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F + }; + + // Voice registers + enum { + v_voll = 0x00, v_volr = 0x01, + v_pitchl = 0x02, v_pitchh = 0x03, + v_srcn = 0x04, v_adsr0 = 0x05, + v_adsr1 = 0x06, v_gain = 0x07, + v_envx = 0x08, v_outx = 0x09 + }; + +public: + enum { extra_size = 16 }; + sample_t* extra() { return m.extra; } + sample_t const* out_pos() const { return m.out; } + void disable_surround( bool ) { } // not supported +public: + BLARGG_DISABLE_NOTHROW + + typedef BOOST::int8_t int8_t; + typedef BOOST::int16_t int16_t; + + enum { echo_hist_size = 8 }; + + enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; + enum { brr_buf_size = 12 }; + struct voice_t + { + int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling) + int buf_pos; // place in buffer where next samples will be decoded + int interp_pos; // relative fractional position in sample (0x1000 = 1.0) + int brr_addr; // address of current BRR block + int brr_offset; // current decoding offset in BRR block + uint8_t* regs; // pointer to voice's DSP registers + int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc. + int kon_delay; // KON delay/current setup phase + env_mode_t env_mode; + int env; // current envelope level + int hidden_env; // used by GAIN mode 7, very obscure quirk + uint8_t t_envx_out; + }; +private: + enum { brr_block_size = 9 }; + + struct state_t + { + uint8_t regs [register_count]; + + // Echo history keeps most recent 8 samples (twice the size to simplify wrap handling) + int echo_hist [echo_hist_size * 2] [2]; + int (*echo_hist_pos) [2]; // &echo_hist [0 to 7] + + int every_other_sample; // toggles every sample + int kon; // KON value when last checked + int noise; + int counter; + int echo_offset; // offset from ESA in echo buffer + int echo_length; // number of bytes that echo_offset will stop at + int phase; // next clock cycle to run (0-31) + bool kon_check; // set when a new KON occurs + + // Hidden registers also written to when main register is written to + int new_kon; + uint8_t endx_buf; + uint8_t envx_buf; + uint8_t outx_buf; + + // Temporary state between clocks + + // read once per sample + int t_pmon; + int t_non; + int t_eon; + int t_dir; + int t_koff; + + // read a few clocks ahead then used + int t_brr_next_addr; + int t_adsr0; + int t_brr_header; + int t_brr_byte; + int t_srcn; + int t_esa; + int t_echo_enabled; + + // internal state that is recalculated every sample + int t_dir_addr; + int t_pitch; + int t_output; + int t_looped; + int t_echo_ptr; + + // left/right sums + int t_main_out [2]; + int t_echo_out [2]; + int t_echo_in [2]; + + voice_t voices [voice_count]; + + // non-emulation state + uint8_t* ram; // 64K shared RAM between DSP and SMP + uint8_t* echo; // should point at the same memory as ram; used for older hack compatibility + int mute_mask; + sample_t* out; + sample_t* out_end; + sample_t* out_begin; + sample_t extra [extra_size]; + }; + state_t m; + + void init_counter(); + void run_counters(); + unsigned read_counter( int rate ); + + int interpolate( voice_t const* v ); + void run_envelope( voice_t* const v ); + void decode_brr( voice_t* v ); + + void misc_27(); + void misc_28(); + void misc_29(); + void misc_30(); + + void voice_output( voice_t const* v, int ch ); + void voice_V1( voice_t* const ); + void voice_V2( voice_t* const ); + void voice_V3( voice_t* const ); + void voice_V3a( voice_t* const ); + void voice_V3b( voice_t* const ); + void voice_V3c( voice_t* const ); + void voice_V4( voice_t* const ); + void voice_V5( voice_t* const ); + void voice_V6( voice_t* const ); + void voice_V7( voice_t* const ); + void voice_V8( voice_t* const ); + void voice_V9( voice_t* const ); + void voice_V7_V4_V1( voice_t* const ); + void voice_V8_V5_V2( voice_t* const ); + void voice_V9_V6_V3( voice_t* const ); + + void echo_read( int ch ); + int echo_output( int ch ); + void echo_write( int ch ); + void echo_22(); + void echo_23(); + void echo_24(); + void echo_25(); + void echo_26(); + void echo_27(); + void echo_28(); + void echo_29(); + void echo_30(); + + void soft_reset_common(); + +public: + bool mute() { return m.regs[r_flg] & 0x40; } +}; + +#include + +inline int SPC_DSP::sample_count() const { return m.out - m.out_begin; } + +inline int SPC_DSP::read( int addr ) const +{ + assert( (unsigned) addr < register_count ); + return m.regs [addr]; +} + +inline void SPC_DSP::write( int addr, int data ) +{ + assert( (unsigned) addr < register_count ); + + m.regs [addr] = (uint8_t) data; + switch ( addr & 0x0F ) + { + case v_envx: + m.envx_buf = (uint8_t) data; + break; + + case v_outx: + m.outx_buf = (uint8_t) data; + break; + + case 0x0C: + if ( addr == r_kon ) + m.new_kon = (uint8_t) data; + + if ( addr == r_endx ) // always cleared, regardless of data written + { + m.endx_buf = 0; + m.regs [r_endx] = 0; + } + break; + } +} + +inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; } + +inline bool SPC_DSP::check_kon() +{ + bool old = m.kon_check; + m.kon_check = 0; + return old; +} + +#if !SPC_NO_COPY_STATE_FUNCS + +class SPC_State_Copier { + SPC_DSP::copy_func_t func; + unsigned char** buf; +public: + SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; } + void copy( void* state, size_t size ); + int copy_int( int state, int size ); + void skip( int count ); + void extra(); +}; + +#define SPC_COPY( type, state )\ +{\ + state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\ + assert( (BOOST::type) state == state );\ +} + +#endif + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/dsp/blargg_common.h b/waterbox/bsnescore/bsnes/sfc/dsp/blargg_common.h new file mode 100644 index 0000000000..75edff3914 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/dsp/blargg_common.h @@ -0,0 +1,186 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +// snes_spc 0.9.0 +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include +#include + +#undef BLARGG_COMMON_H +// allow blargg_config.h to #include blargg_common.h +#include "blargg_config.h" +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// BLARGG_RESTRICT: equivalent to restrict, where supported +#if defined (__GNUC__) || _MSC_VER >= 1100 + #define BLARGG_RESTRICT __restrict +#else + #define BLARGG_RESTRICT +#endif + +// STATIC_CAST(T,expr): Used in place of static_cast (expr) +#ifndef STATIC_CAST + #define STATIC_CAST(T,expr) ((T) (expr)) +#endif + +// blargg_err_t (0 on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; +#endif + +// blargg_vector - very lightweight vector of POD types (no constructor/destructor) +template +class blargg_vector { + T* begin_; + size_t size_; +public: + blargg_vector() : begin_( 0 ), size_( 0 ) { } + ~blargg_vector() { free( begin_ ); } + size_t size() const { return size_; } + T* begin() const { return begin_; } + T* end() const { return begin_ + size_; } + blargg_err_t resize( size_t n ) + { + // TODO: blargg_common.cpp to hold this as an outline function, ugh + void* p = realloc( begin_, n * sizeof (T) ); + if ( p ) + begin_ = (T*) p; + else if ( n > size_ ) // realloc failure only a problem if expanding + return "Out of memory"; + size_ = n; + return 0; + } + void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); } + T& operator [] ( size_t n ) const + { + assert( n <= size_ ); // <= to allow past-the-end value + return begin_ [n]; + } +}; + +#ifndef BLARGG_DISABLE_NOTHROW + // throw spec mandatory in ISO C++ if operator new can return NULL + #if __cplusplus >= 199711 || defined (__GNUC__) + #define BLARGG_THROWS( spec ) throw spec + #else + #define BLARGG_THROWS( spec ) + #endif + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\ + void operator delete ( void* p ) { free( p ); } + #define BLARGG_NEW new +#else + #include + #define BLARGG_NEW new (std::nothrow) +#endif + +// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant) +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF)) + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] ) + #else + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) + #endif +#endif + +// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1, +// compiler is assumed to support bool. If undefined, availability is determined. +#ifndef BLARGG_COMPILER_HAS_BOOL + #if defined (__MWERKS__) + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (__GNUC__) + // supports bool + #elif __cplusplus < 199711 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif +#endif +#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + // If you get errors here, modify your blargg_config.h file + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough + +#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF + typedef long blargg_long; +#else + typedef int blargg_long; +#endif + +#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF + typedef unsigned long blargg_ulong; +#else + typedef unsigned blargg_ulong; +#endif + +// BOOST::int8_t etc. + +// HAVE_STDINT_H: If defined, use for int8_t etc. +#if defined (HAVE_STDINT_H) + #include + #define BOOST + +// HAVE_INTTYPES_H: If defined, use for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include + #define BOOST + +#else + struct BOOST + { + #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F + typedef signed char int8_t; + typedef unsigned char uint8_t; + #else + // No suitable 8-bit type available + typedef struct see_blargg_common_h int8_t; + typedef struct see_blargg_common_h uint8_t; + #endif + + #if USHRT_MAX == 0xFFFF + typedef short int16_t; + typedef unsigned short uint16_t; + #else + // No suitable 16-bit type available + typedef struct see_blargg_common_h int16_t; + typedef struct see_blargg_common_h uint16_t; + #endif + + #if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; + #elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; + #else + // No suitable 32-bit type available + typedef struct see_blargg_common_h int32_t; + typedef struct see_blargg_common_h uint32_t; + #endif + }; +#endif + +#endif +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/dsp/blargg_config.h b/waterbox/bsnescore/bsnes/sfc/dsp/blargg_config.h new file mode 100644 index 0000000000..94570ca0ba --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/dsp/blargg_config.h @@ -0,0 +1,26 @@ +// snes_spc 0.9.0 user configuration file. Don't replace when updating library. + +// snes_spc 0.9.0 +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to disable debugging checks +#ifndef NDEBUG + #define NDEBUG 1 +#endif + +// Uncomment to enable platform-specific (and possibly non-portable) optimizations +//#define BLARGG_NONPORTABLE 1 + +// Uncomment if automatic byte-order determination doesn't work +//#define BLARGG_BIG_ENDIAN 1 + +// Uncomment if you get errors in the bool section of blargg_common.h +//#define BLARGG_COMPILER_HAS_BOOL 1 + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/dsp/blargg_endian.h b/waterbox/bsnescore/bsnes/sfc/dsp/blargg_endian.h new file mode 100644 index 0000000000..f2daca6416 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/dsp/blargg_endian.h @@ -0,0 +1,185 @@ +// CPU Byte Order Utilities + +// snes_spc 0.9.0 +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + #define BLARGG_CPU_RISC 1 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one may be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else + +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif + +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__sparc__) || BLARGG_CPU_POWERPC || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#elif !defined (__mips__) + // No endian specified; assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +inline void blargg_verify_byte_order() +{ + #ifndef NDEBUG + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif + #endif +} + +inline unsigned get_le16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; +} + +inline unsigned get_be16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 8 | + (unsigned) ((unsigned char const*) p) [1]; +} + +inline blargg_ulong get_le32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [3] << 24 | + (blargg_ulong) ((unsigned char const*) p) [2] << 16 | + (blargg_ulong) ((unsigned char const*) p) [1] << 8 | + (blargg_ulong) ((unsigned char const*) p) [0]; +} + +inline blargg_ulong get_be32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [0] << 24 | + (blargg_ulong) ((unsigned char const*) p) [1] << 16 | + (blargg_ulong) ((unsigned char const*) p) [2] << 8 | + (blargg_ulong) ((unsigned char const*) p) [3]; +} + +inline void set_le16( void* p, unsigned n ) +{ + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} + +inline void set_be16( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} + +inline void set_le32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [0] = (unsigned char) n; + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); +} + +inline void set_be32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [3] = (unsigned char) n; + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); +} + +#if BLARGG_NONPORTABLE + // Optimized implementation if byte order is known + #if BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif BLARGG_BIG_ENDIAN + #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + + #if BLARGG_CPU_POWERPC + // PowerPC has special byte-reversed instructions + #if defined (__MWERKS__) + #define GET_LE16( addr ) (__lhbrx( addr, 0 )) + #define GET_LE32( addr ) (__lwbrx( addr, 0 )) + #define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 )) + #define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 )) + #elif defined (__GNUC__) + #define GET_LE16( addr ) ({unsigned ppc_lhbrx_; asm( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr), "0" (ppc_lhbrx_) ); ppc_lhbrx_;}) + #define GET_LE32( addr ) ({unsigned ppc_lwbrx_; asm( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr), "0" (ppc_lwbrx_) ); ppc_lwbrx_;}) + #define SET_LE16( addr, in ) ({asm( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #define SET_LE32( addr, in ) ({asm( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #endif + #endif + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) +#endif + +#ifndef GET_LE32 + #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) +#endif + +#ifndef GET_BE32 + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); } +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); } +inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } +inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } +inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } +inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/dsp/blargg_source.h b/waterbox/bsnescore/bsnes/sfc/dsp/blargg_source.h new file mode 100644 index 0000000000..5e45c4fb42 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/dsp/blargg_source.h @@ -0,0 +1,100 @@ +/* Included at the beginning of library source files, after all other #include lines. +Sets up helpful macros and services used in my source code. They don't need +module an annoying module prefix on their names since they are defined after +all other #include lines. */ + +// snes_spc 0.9.0 +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#undef require +#define require( expr ) assert( expr ) + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments). +// void dprintf( const char* format, ... ); +static inline void blargg_dprintf_( const char*, ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#define check( expr ) ((void) 0) + +// If expr yields error string, return it from current function, otherwise continue. +#undef RETURN_ERR +#define RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is 0, return out of memory error string. +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) + +// Avoid any macros which evaluate their arguments multiple times +#undef min +#undef max + +#define DEF_MIN_MAX( type ) \ + static inline type min( type x, type y ) { if ( x < y ) return x; return y; }\ + static inline type max( type x, type y ) { if ( y < x ) return x; return y; } + +DEF_MIN_MAX( int ) +DEF_MIN_MAX( unsigned ) +DEF_MIN_MAX( long ) +DEF_MIN_MAX( unsigned long ) +DEF_MIN_MAX( float ) +DEF_MIN_MAX( double ) + +#undef DEF_MIN_MAX + +/* +// using const references generates crappy code, and I am currenly only using these +// for built-in types, so they take arguments by value + +// TODO: remove +inline int min( int x, int y ) +template +inline T min( T x, T y ) +{ + if ( x < y ) + return x; + return y; +} + +template +inline T max( T x, T y ) +{ + if ( x < y ) + return y; + return x; +} +*/ + +// TODO: good idea? bad idea? +#undef byte +#define byte byte_ +typedef unsigned char byte; + +// deprecated +#define BLARGG_CHECK_ALLOC CHECK_ALLOC +#define BLARGG_RETURN_ERR RETURN_ERR + +// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif diff --git a/waterbox/bsnescore/bsnes/sfc/dsp/dsp.cpp b/waterbox/bsnescore/bsnes/sfc/dsp/dsp.cpp new file mode 100644 index 0000000000..10ad61dd82 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/dsp/dsp.cpp @@ -0,0 +1,80 @@ +#include + +namespace SuperFamicom { + +DSP dsp; + +#include "serialization.cpp" +#include "SPC_DSP.cpp" + +auto DSP::main() -> void { + if(!configuration.hacks.dsp.fast) { + spc_dsp.run(1); + clock += 2; + } else { + spc_dsp.run(32); + clock += 2 * 32; + } + + int count = spc_dsp.sample_count(); + if(count > 0) { + if(!system.runAhead && system.renderAudio) + for(uint n = 0; n < count; n += 2) { + float left = samplebuffer[n + 0] / 32768.0f; + float right = samplebuffer[n + 1] / 32768.0f; + stream->sample(left, right); + } + spc_dsp.set_output(samplebuffer, 8192); + } +} + +auto DSP::read(uint8 address) -> uint8 { + return spc_dsp.read(address); +} + +auto DSP::write(uint8 address, uint8 data) -> void { + if(configuration.hacks.dsp.echoShadow) { + if(address == 0x6c && (data & 0x20)) { + memset(echoram, 0x00, 65536); + } + } + + spc_dsp.write(address, data); +} + +auto DSP::load() -> bool { + return true; +} + +auto DSP::power(bool reset) -> void { + clock = 0; + stream = Emulator::audio.createStream(2, system.apuFrequency() / 768.0); + + if(!reset) { + if(!configuration.hacks.dsp.echoShadow) { + spc_dsp.init(apuram, apuram); + } else { + memset(echoram, 0x00, 65536); + spc_dsp.init(apuram, echoram); + } + spc_dsp.reset(); + spc_dsp.set_output(samplebuffer, 8192); + } else { + spc_dsp.soft_reset(); + spc_dsp.set_output(samplebuffer, 8192); + } + + if(configuration.hacks.hotfixes) { + //Magical Drop (Japan) does not initialize the DSP registers at startup: + //tokoton mode will hang forever in some instances even on real hardware. + if(cartridge.headerTitle() == "MAGICAL DROP") { + for(uint address : range(0x80)) spc_dsp.write(address, 0xff); + } + } +} + +auto DSP::mute() -> bool { + return spc_dsp.mute(); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/dsp/dsp.hpp b/waterbox/bsnescore/bsnes/sfc/dsp/dsp.hpp new file mode 100644 index 0000000000..1cc97f052e --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/dsp/dsp.hpp @@ -0,0 +1,28 @@ +#include "SPC_DSP.h" + +struct DSP { + shared_pointer stream; + uint8 apuram[64 * 1024] = {}; + + auto main() -> void; + auto read(uint8 address) -> uint8; + auto write(uint8 address, uint8 data) -> void; + + auto load() -> bool; + auto power(bool reset) -> void; + auto mute() -> bool; + + auto serialize(serializer&) -> void; + + int64 clock = 0; + +private: + bool fastDSP = false; + SPC_DSP spc_dsp; + int16 samplebuffer[8192]; + +//unserialized: + uint8 echoram[64 * 1024] = {}; +}; + +extern DSP dsp; diff --git a/waterbox/bsnescore/bsnes/sfc/dsp/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/dsp/serialization.cpp new file mode 100644 index 0000000000..ec45155e3b --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/dsp/serialization.cpp @@ -0,0 +1,28 @@ +static auto dsp_state_save(unsigned char** out, void* in, size_t size) -> void { + memcpy(*out, in, size); + *out += size; +} + +static auto dsp_state_load(unsigned char** in, void* out, size_t size) -> void { + memcpy(out, *in, size); + *in += size; +} + +auto DSP::serialize(serializer& s) -> void { + s.array(apuram); + s.array(samplebuffer); + s.integer(clock); + + unsigned char state[SPC_DSP::state_size]; + unsigned char* p = state; + memset(&state, 0, SPC_DSP::state_size); + if(s.mode() == serializer::Save) { + spc_dsp.copy_state(&p, dsp_state_save); + s.array(state); + } else if(s.mode() == serializer::Load) { + s.array(state); + spc_dsp.copy_state(&p, dsp_state_load); + } else { + s.array(state); + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/expansion/21fx/21fx.cpp b/waterbox/bsnescore/bsnes/sfc/expansion/21fx/21fx.cpp new file mode 100644 index 0000000000..dbf891c931 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/expansion/21fx/21fx.cpp @@ -0,0 +1,145 @@ +#include + +namespace SuperFamicom { + +S21FX::S21FX() { + create(S21FX::Enter, 10'000'000); + + resetVector.byte(0) = bus.read(0xfffc, 0x00); + resetVector.byte(1) = bus.read(0xfffd, 0x00); + + bus.map({&S21FX::read, this}, {&S21FX::write, this}, "00-3f,80-bf:2184-21ff"); + bus.map({&S21FX::read, this}, {&S21FX::write, this}, "00:fffc-fffd"); + + booted = false; + + for(auto& byte : ram) byte = 0xdb; //stp + ram[0] = 0x6c; //jmp ($fffc) + ram[1] = 0xfc; + ram[2] = 0xff; + + if(auto buffer = file::read({platform->path(ID::System), "21fx.rom"})) { + memory::copy(ram, sizeof(ram), buffer.data(), buffer.size()); + } + + string filename{platform->path(ID::SuperFamicom), "21fx.so"}; + if(link.openAbsolute(filename)) { + linkInit = link.sym("fx_init"); + linkMain = link.sym("fx_main"); + } +} + +S21FX::~S21FX() { + scheduler.remove(*this); + bus.unmap("00-3f,80-bf:2184-21ff"); + bus.unmap("00:fffc-fffd"); + + //note: this is an awful hack ... + //since the bus maps are lambdas, we can't safely restore the original reset vector handler + //as such, we install a basic read-only lambda that simply returns the known reset vector + //the downside is that if 00:fffc-fffd were anything but ROM; it will now only act as ROM + //given that this is the only device that hooks the reset vector like this, + //it's not worth the added complexity to support some form of reversible bus mapping hooks + uint vector = resetVector; + bus.map([vector](uint24 addr, uint8) -> uint8 { + return vector >> addr * 8; + }, [](uint24, uint8) -> void { + }, "00:fffc-fffd", 2); + + if(link.open()) link.close(); + linkInit.reset(); + linkMain.reset(); +} + +auto S21FX::Enter() -> void { + while(true) scheduler.synchronize(), expansionPort.device->main(); +} + +auto S21FX::step(uint clocks) -> void { + Thread::step(clocks); + synchronize(cpu); +} + +auto S21FX::main() -> void { + if(linkInit) linkInit( + {&S21FX::quit, this}, + {&S21FX::usleep, this}, + {&S21FX::readable, this}, + {&S21FX::writable, this}, + {&S21FX::read, this}, + {&S21FX::write, this} + ); + if(linkMain) linkMain({}); + while(true) step(10'000'000); +} + +auto S21FX::read(uint addr, uint8 data) -> uint8 { + addr &= 0x40ffff; + + if(addr == 0xfffc) return booted ? resetVector.byte(0) : (uint8)0x84; + if(addr == 0xfffd) return booted ? resetVector.byte(1) : (booted = true, (uint8)0x21); + + if(addr >= 0x2184 && addr <= 0x21fd) return ram[addr - 0x2184]; + + if(addr == 0x21fe) return !link.open() ? 0 : ( + (linkBuffer.size() > 0) << 7 //1 = readable + | (snesBuffer.size() < 1024) << 6 //1 = writable + | (link.open()) << 5 //1 = connected + ); + + if(addr == 0x21ff) { + if(linkBuffer.size() > 0) { + return linkBuffer.takeLeft(); + } + } + + return data; +} + +auto S21FX::write(uint addr, uint8 data) -> void { + addr &= 0x40ffff; + + if(addr == 0x21ff) { + if(snesBuffer.size() < 1024) { + snesBuffer.append(data); + } + } +} + +auto S21FX::quit() -> bool { + step(1); + return false; +} + +auto S21FX::usleep(uint microseconds) -> void { + step(10 * microseconds); +} + +auto S21FX::readable() -> bool { + step(1); + return snesBuffer.size() > 0; +} + +auto S21FX::writable() -> bool { + step(1); + return linkBuffer.size() < 1024; +} + +//SNES -> Link +auto S21FX::read() -> uint8 { + step(1); + if(snesBuffer.size() > 0) { + return snesBuffer.takeLeft(); + } + return 0x00; +} + +//Link -> SNES +auto S21FX::write(uint8 data) -> void { + step(1); + if(linkBuffer.size() < 1024) { + linkBuffer.append(data); + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/expansion/21fx/21fx.hpp b/waterbox/bsnescore/bsnes/sfc/expansion/21fx/21fx.hpp new file mode 100644 index 0000000000..63e7c9dd2d --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/expansion/21fx/21fx.hpp @@ -0,0 +1,37 @@ +struct S21FX : Expansion { + S21FX(); + ~S21FX(); + + static auto Enter() -> void; + auto step(uint clocks) -> void; + auto main() -> void; + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + +private: + auto quit() -> bool; + auto usleep(uint) -> void; + auto readable() -> bool; + auto writable() -> bool; + auto read() -> uint8; + auto write(uint8) -> void; + + bool booted = false; + uint16 resetVector; + uint8 ram[122]; + + nall::library link; + function, //quit + function, //usleep + function, //readable + function, //writable + function, //read + function //write + )> linkInit; + function)> linkMain; + + vector snesBuffer; //SNES -> Link + vector linkBuffer; //Link -> SNES +}; diff --git a/waterbox/bsnescore/bsnes/sfc/expansion/expansion.cpp b/waterbox/bsnescore/bsnes/sfc/expansion/expansion.cpp new file mode 100644 index 0000000000..497a8f13cb --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/expansion/expansion.cpp @@ -0,0 +1,39 @@ +#include +#include +//#include + +namespace SuperFamicom { + +ExpansionPort expansionPort; + +Expansion::Expansion() { +} + +Expansion::~Expansion() { +} + +// + +auto ExpansionPort::connect(uint deviceID) -> void { + if(!system.loaded()) return; + delete device; + + switch(deviceID) { default: + case ID::Device::None: device = new Expansion; break; + case ID::Device::Satellaview: device = new Satellaview; break; +//case ID::Device::S21FX: device = new S21FX; break; + } +} + +auto ExpansionPort::power() -> void { +} + +auto ExpansionPort::unload() -> void { + delete device; + device = nullptr; +} + +auto ExpansionPort::serialize(serializer& s) -> void { +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/expansion/expansion.hpp b/waterbox/bsnescore/bsnes/sfc/expansion/expansion.hpp new file mode 100644 index 0000000000..78e0e0e35b --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/expansion/expansion.hpp @@ -0,0 +1,19 @@ +struct Expansion : Thread { + Expansion(); + virtual ~Expansion(); +}; + +struct ExpansionPort { + auto connect(uint deviceID) -> void; + + auto power() -> void; + auto unload() -> void; + auto serialize(serializer&) -> void; + + Expansion* device = nullptr; +}; + +extern ExpansionPort expansionPort; + +#include +//#include diff --git a/waterbox/bsnescore/bsnes/sfc/expansion/satellaview/satellaview.cpp b/waterbox/bsnescore/bsnes/sfc/expansion/satellaview/satellaview.cpp new file mode 100644 index 0000000000..74ffa9f259 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/expansion/satellaview/satellaview.cpp @@ -0,0 +1,129 @@ +#include + +namespace SuperFamicom { + +Satellaview::Satellaview() { + bus.map({&Satellaview::read, this}, {&Satellaview::write, this}, "00-3f,80-bf:2188-219f"); + regs = {}; +} + +Satellaview::~Satellaview() { + bus.unmap("00-3f,80-bf:2188-219f"); +} + +auto Satellaview::read(uint addr, uint8 data) -> uint8 { + switch(addr &= 0xffff) { + case 0x2188: return regs.r2188; + case 0x2189: return regs.r2189; + case 0x218a: return regs.r218a; + case 0x218c: return regs.r218c; + case 0x218e: return regs.r218e; + case 0x218f: return regs.r218f; + case 0x2190: return regs.r2190; + + case 0x2192: { + uint counter = regs.rtcCounter++; + if(regs.rtcCounter >= 18) regs.rtcCounter = 0; + + if(counter == 0) { + time_t rawtime; + time(&rawtime); + tm* t = localtime(&rawtime); + + regs.rtcHour = t->tm_hour; + regs.rtcMinute = t->tm_min; + regs.rtcSecond = t->tm_sec; + } + + switch(counter) { + case 0: return 0x00; //??? + case 1: return 0x00; //??? + case 2: return 0x00; //??? + case 3: return 0x00; //??? + case 4: return 0x00; //??? + case 5: return 0x01; + case 6: return 0x01; + case 7: return 0x00; + case 8: return 0x00; + case 9: return 0x00; + case 10: return regs.rtcSecond; + case 11: return regs.rtcMinute; + case 12: return regs.rtcHour; + case 13: return 0x00; //??? + case 14: return 0x00; //??? + case 15: return 0x00; //??? + case 16: return 0x00; //??? + case 17: return 0x00; //??? + } + } break; + + case 0x2193: return regs.r2193 & ~0x0c; + case 0x2194: return regs.r2194; + case 0x2196: return regs.r2196; + case 0x2197: return regs.r2197; + case 0x2199: return regs.r2199; + } + + return data; +} + +auto Satellaview::write(uint addr, uint8 data) -> void { + switch(addr &= 0xffff) { + case 0x2188: { + regs.r2188 = data; + } break; + + case 0x2189: { + regs.r2189 = data; + } break; + + case 0x218a: { + regs.r218a = data; + } break; + + case 0x218b: { + regs.r218b = data; + } break; + + case 0x218c: { + regs.r218c = data; + } break; + + case 0x218e: { + regs.r218e = data; + } break; + + case 0x218f: { + regs.r218e >>= 1; + regs.r218e = regs.r218f - regs.r218e; + regs.r218f >>= 1; + } break; + + case 0x2191: { + regs.r2191 = data; + regs.rtcCounter = 0; + } break; + + case 0x2192: { + regs.r2190 = 0x80; + } break; + + case 0x2193: { + regs.r2193 = data; + } break; + + case 0x2194: { + regs.r2194 = data; + } break; + + case 0x2197: { + regs.r2197 = data; + } break; + + case 0x2199: { + regs.r2199 = data; + } break; + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/expansion/satellaview/satellaview.hpp b/waterbox/bsnescore/bsnes/sfc/expansion/satellaview/satellaview.hpp new file mode 100644 index 0000000000..57c355ca51 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/expansion/satellaview/satellaview.hpp @@ -0,0 +1,22 @@ +struct Satellaview : Expansion { + Satellaview(); + ~Satellaview(); + + auto read(uint addr, uint8 data) -> uint8; + auto write(uint addr, uint8 data) -> void; + +private: + struct { + uint8 r2188, r2189, r218a, r218b; + uint8 r218c, r218d, r218e, r218f; + uint8 r2190, r2191, r2192, r2193; + uint8 r2194, r2195, r2196, r2197; + uint8 r2198, r2199, r219a, r219b; + uint8 r219c, r219d, r219e, r219f; + + uint8 rtcCounter; + uint8 rtcHour; + uint8 rtcMinute; + uint8 rtcSecond; + } regs; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/interface/configuration.cpp b/waterbox/bsnescore/bsnes/sfc/interface/configuration.cpp new file mode 100644 index 0000000000..769b8b0c48 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/interface/configuration.cpp @@ -0,0 +1,75 @@ +Configuration configuration; + +auto Configuration::process(Markup::Node document, bool load) -> void { + #define bind(type, path, name) \ + if(load) { \ + if(auto node = document[path]) name = node.type(); \ + } else { \ + document(path).setValue(name); \ + } \ + + bind(natural, "System/CPU/Version", system.cpu.version); + bind(natural, "System/PPU1/Version", system.ppu1.version); + bind(natural, "System/PPU1/VRAM/Size", system.ppu1.vram.size); + bind(natural, "System/PPU2/Version", system.ppu2.version); + bind(text, "System/Serialization/Method", system.serialization.method); + + bind(boolean, "Video/BlurEmulation", video.blurEmulation); + bind(boolean, "Video/ColorEmulation", video.colorEmulation); + + bind(boolean, "Hacks/Hotfixes", hacks.hotfixes); + bind(text, "Hacks/Entropy", hacks.entropy); + bind(natural, "Hacks/CPU/Overclock", hacks.cpu.overclock); + bind(boolean, "Hacks/CPU/FastMath", hacks.cpu.fastMath); + bind(boolean, "Hacks/PPU/Fast", hacks.ppu.fast); + bind(boolean, "Hacks/PPU/Deinterlace", hacks.ppu.deinterlace); + bind(natural, "Hacks/PPU/RenderCycle", hacks.ppu.renderCycle); + bind(boolean, "Hacks/PPU/NoSpriteLimit", hacks.ppu.noSpriteLimit); + bind(boolean, "Hacks/PPU/NoVRAMBlocking", hacks.ppu.noVRAMBlocking); + bind(natural, "Hacks/PPU/Mode7/Scale", hacks.ppu.mode7.scale); + bind(boolean, "Hacks/PPU/Mode7/Perspective", hacks.ppu.mode7.perspective); + bind(boolean, "Hacks/PPU/Mode7/Supersample", hacks.ppu.mode7.supersample); + bind(boolean, "Hacks/PPU/Mode7/Mosaic", hacks.ppu.mode7.mosaic); + bind(boolean, "Hacks/DSP/Fast", hacks.dsp.fast); + bind(boolean, "Hacks/DSP/Cubic", hacks.dsp.cubic); + bind(boolean, "Hacks/DSP/EchoShadow", hacks.dsp.echoShadow); + bind(boolean, "Hacks/Coprocessor/DelayedSync", hacks.coprocessor.delayedSync); + bind(boolean, "Hacks/Coprocessor/PreferHLE", hacks.coprocessor.preferHLE); + bind(natural, "Hacks/SA1/Overclock", hacks.sa1.overclock); + bind(natural, "Hacks/SuperFX/Overclock", hacks.superfx.overclock); + + #undef bind +} + +auto Configuration::read() -> string { + Markup::Node document; + process(document, false); + return BML::serialize(document, " "); +} + +auto Configuration::read(string name) -> string { + auto document = BML::unserialize(read()); + return document[name].text(); +} + +auto Configuration::write(string configuration) -> bool { + *this = {}; + + if(auto document = BML::unserialize(configuration)) { + return process(document, true), true; + } + + return false; +} + +auto Configuration::write(string name, string value) -> bool { + if(SuperFamicom::system.loaded() && name.beginsWith("System/")) return false; + + auto document = BML::unserialize(read()); + if(auto node = document[name]) { + node.setValue(value); + return process(document, true), true; + } + + return false; +} diff --git a/waterbox/bsnescore/bsnes/sfc/interface/configuration.hpp b/waterbox/bsnescore/bsnes/sfc/interface/configuration.hpp new file mode 100644 index 0000000000..5f4bd683c2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/interface/configuration.hpp @@ -0,0 +1,71 @@ +struct Configuration { + auto read() -> string; + auto read(string) -> string; + auto write(string) -> bool; + auto write(string, string) -> bool; + + struct System { + struct CPU { + uint version = 2; + } cpu; + struct PPU1 { + uint version = 1; + struct VRAM { + uint size = 0x10000; + } vram; + } ppu1; + struct PPU2 { + uint version = 3; + } ppu2; + struct Serialization { + string method = "Fast"; + } serialization; + } system; + + struct Video { + bool blurEmulation = true; + bool colorEmulation = true; + } video; + + struct Hacks { + bool hotfixes = true; + string entropy = "Low"; + struct CPU { + uint overclock = 100; + bool fastMath = false; + } cpu; + struct PPU { + bool fast = true; + bool deinterlace = true; + bool noSpriteLimit = false; + bool noVRAMBlocking = false; + uint renderCycle = 512; + struct Mode7 { + uint scale = 1; + bool perspective = true; + bool supersample = false; + bool mosaic = true; + } mode7; + } ppu; + struct DSP { + bool fast = true; + bool cubic = false; + bool echoShadow = false; + } dsp; + struct Coprocessor { + bool delayedSync = true; + bool preferHLE = false; + } coprocessor; + struct SA1 { + uint overclock = 100; + } sa1; + struct SuperFX { + uint overclock = 100; + } superfx; + } hacks; + +private: + auto process(Markup::Node document, bool load) -> void; +}; + +extern Configuration configuration; diff --git a/waterbox/bsnescore/bsnes/sfc/interface/interface.cpp b/waterbox/bsnescore/bsnes/sfc/interface/interface.cpp new file mode 100644 index 0000000000..51d3b9b1b3 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/interface/interface.cpp @@ -0,0 +1,344 @@ +#include + +namespace SuperFamicom { + +Settings settings; +#include "configuration.cpp" + +auto Interface::information() -> Information { + Information information; + information.manufacturer = "Nintendo"; + information.name = "Super Famicom"; + information.extension = "sfc"; + information.resettable = true; + return information; +} + +auto Interface::display() -> Display { + Display display; + display.type = Display::Type::CRT; + display.colors = 1 << 19; + display.width = 256; + display.height = 240; + display.internalWidth = 512; + display.internalHeight = 480; + display.aspectCorrection = 8.0 / 7.0; + return display; +} + +auto Interface::color(uint32 color) -> uint64 { + uint r = color >> 0 & 31; + uint g = color >> 5 & 31; + uint b = color >> 10 & 31; + uint l = color >> 15 & 15; + + //luma=0 is not 100% black; but it's much darker than normal linear scaling + //exact effect seems to be analog; requires > 24-bit color depth to represent accurately + double L = (1.0 + l) / 16.0 * (l ? 1.0 : 0.25); + uint64 R = L * image::normalize(r, 5, 16); + uint64 G = L * image::normalize(g, 5, 16); + uint64 B = L * image::normalize(b, 5, 16); + + if(SuperFamicom::configuration.video.colorEmulation) { + static const uint8 gammaRamp[32] = { + 0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c, + 0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78, + 0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0, + 0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0xff, + }; + R = L * gammaRamp[r] * 0x0101; + G = L * gammaRamp[g] * 0x0101; + B = L * gammaRamp[b] * 0x0101; + } + + return R << 32 | G << 16 | B << 0; +} + +auto Interface::loaded() -> bool { + return system.loaded(); +} + +auto Interface::hashes() -> vector { + return cartridge.hashes(); +} + +auto Interface::manifests() -> vector { + return cartridge.manifests(); +} + +auto Interface::titles() -> vector { + return cartridge.titles(); +} + +auto Interface::title() -> string { + return cartridge.title(); +} + +auto Interface::load() -> bool { + return system.load(this); +} + +auto Interface::save() -> void { + system.save(); +} + +auto Interface::unload() -> void { + save(); + system.unload(); +} + +auto Interface::ports() -> vector { return { + {ID::Port::Controller1, "Controller Port 1"}, + {ID::Port::Controller2, "Controller Port 2"}, + {ID::Port::Expansion, "Expansion Port" }}; +} + +auto Interface::devices(uint port) -> vector { + if(port == ID::Port::Controller1) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad"}, + {ID::Device::Mouse, "Mouse" } + }; + + if(port == ID::Port::Controller2) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad" }, + {ID::Device::Mouse, "Mouse" }, + {ID::Device::SuperMultitap, "Super Multitap"}, + {ID::Device::SuperScope, "Super Scope" }, + {ID::Device::Justifier, "Justifier" }, + {ID::Device::Justifiers, "Justifiers" } + }; + + if(port == ID::Port::Expansion) return { + {ID::Device::None, "None" }, + {ID::Device::Satellaview, "Satellaview"}, + {ID::Device::S21FX, "21fx" } + }; + + return {}; +} + +auto Interface::inputs(uint device) -> vector { + using Type = Input::Type; + + if(device == ID::Device::None) return { + }; + + if(device == ID::Device::Gamepad) return { + {Type::Hat, "Up" }, + {Type::Hat, "Down" }, + {Type::Hat, "Left" }, + {Type::Hat, "Right" }, + {Type::Button, "B" }, + {Type::Button, "A" }, + {Type::Button, "Y" }, + {Type::Button, "X" }, + {Type::Trigger, "L" }, + {Type::Trigger, "R" }, + {Type::Control, "Select"}, + {Type::Control, "Start" } + }; + + if(device == ID::Device::Mouse) return { + {Type::Axis, "X-axis"}, + {Type::Axis, "Y-axis"}, + {Type::Button, "Left" }, + {Type::Button, "Right" } + }; + + if(device == ID::Device::SuperMultitap) { + vector inputs; + for(uint p = 2; p <= 5; p++) inputs.append({ + {Type::Hat, {"Port ", p, " - ", "Up" }}, + {Type::Hat, {"Port ", p, " - ", "Down" }}, + {Type::Hat, {"Port ", p, " - ", "Left" }}, + {Type::Hat, {"Port ", p, " - ", "Right" }}, + {Type::Button, {"Port ", p, " - ", "B" }}, + {Type::Button, {"Port ", p, " - ", "A" }}, + {Type::Button, {"Port ", p, " - ", "Y" }}, + {Type::Button, {"Port ", p, " - ", "X" }}, + {Type::Trigger, {"Port ", p, " - ", "L" }}, + {Type::Trigger, {"Port ", p, " - ", "R" }}, + {Type::Control, {"Port ", p, " - ", "Select"}}, + {Type::Control, {"Port ", p, " - ", "Start" }} + }); + return inputs; + } + + if(device == ID::Device::SuperScope) return { + {Type::Axis, "X-axis" }, + {Type::Axis, "Y-axis" }, + {Type::Control, "Trigger"}, + {Type::Control, "Cursor" }, + {Type::Control, "Turbo" }, + {Type::Control, "Pause" } + }; + + if(device == ID::Device::Justifier) return { + {Type::Axis, "X-axis" }, + {Type::Axis, "Y-axis" }, + {Type::Control, "Trigger"}, + {Type::Control, "Start" } + }; + + if(device == ID::Device::Justifiers) return { + {Type::Axis, "Port 1 - X-axis" }, + {Type::Axis, "Port 1 - Y-axis" }, + {Type::Control, "Port 1 - Trigger"}, + {Type::Control, "Port 1 - Start" }, + {Type::Axis, "Port 2 - X-axis" }, + {Type::Axis, "Port 2 - Y-axis" }, + {Type::Control, "Port 2 - Trigger"}, + {Type::Control, "Port 2 - Start" } + }; + + if(device == ID::Device::Satellaview) return { + }; + + if(device == ID::Device::S21FX) return { + }; + + return {}; +} + +auto Interface::connected(uint port) -> uint { + if(port == ID::Port::Controller1) return settings.controllerPort1; + if(port == ID::Port::Controller2) return settings.controllerPort2; + if(port == ID::Port::Expansion) return settings.expansionPort; + return 0; +} + +auto Interface::connect(uint port, uint device) -> void { + if(port == ID::Port::Controller1) controllerPort1.connect(settings.controllerPort1 = device); + if(port == ID::Port::Controller2) controllerPort2.connect(settings.controllerPort2 = device); + if(port == ID::Port::Expansion) expansionPort.connect(settings.expansionPort = device); +} + +auto Interface::power() -> void { + system.power(/* reset = */ false); +} + +auto Interface::reset() -> void { + system.power(/* reset = */ true); +} + +auto Interface::run() -> void { + system.run(); +} + +auto Interface::rtc() -> bool { + if(cartridge.has.EpsonRTC) return true; + if(cartridge.has.SharpRTC) return true; + return false; +} + +auto Interface::synchronize(uint64 timestamp) -> void { + if(!timestamp) timestamp = chrono::timestamp(); + if(cartridge.has.EpsonRTC) epsonrtc.synchronize(timestamp); + if(cartridge.has.SharpRTC) sharprtc.synchronize(timestamp); +} + +auto Interface::serialize(bool synchronize) -> serializer { + return system.serialize(synchronize); +} + +auto Interface::unserialize(serializer& s) -> bool { + return system.unserialize(s); +} + +auto Interface::read(uint24 address) -> uint8 { + return cpu.readDisassembler(address); +} + +auto Interface::cheats(const vector& list) -> void { + if(cartridge.has.ICD) { + icd.cheats.assign(list); + return; + } + + //make all ROM data writable temporarily + Memory::GlobalWriteEnable = true; + + Cheat oldCheat = cheat; + Cheat newCheat; + newCheat.assign(list); + + //determine all old codes to remove + for(auto& oldCode : oldCheat.codes) { + bool found = false; + for(auto& newCode : newCheat.codes) { + if(oldCode == newCode) { + found = true; + break; + } + } + if(!found) { + //remove old cheat + if(oldCode.enable) { + bus.write(oldCode.address, oldCode.restore); + } + } + } + + //determine all new codes to create + for(auto& newCode : newCheat.codes) { + bool found = false; + for(auto& oldCode : oldCheat.codes) { + if(newCode == oldCode) { + found = true; + break; + } + } + if(!found) { + //create new cheat + newCode.restore = bus.read(newCode.address); + if(!newCode.compare || newCode.compare() == newCode.restore) { + newCode.enable = true; + bus.write(newCode.address, newCode.data); + } else { + newCode.enable = false; + } + } + } + + cheat = newCheat; + + //restore ROM write protection + Memory::GlobalWriteEnable = false; +} + +auto Interface::configuration() -> string { + return SuperFamicom::configuration.read(); +} + +auto Interface::configuration(string name) -> string { + return SuperFamicom::configuration.read(name); +} + +auto Interface::configure(string configuration) -> bool { + return SuperFamicom::configuration.write(configuration); +} + +auto Interface::configure(string name, string value) -> bool { + return SuperFamicom::configuration.write(name, value); +} + +auto Interface::frameSkip() -> uint { + return system.frameSkip; +} + +auto Interface::setFrameSkip(uint frameSkip) -> void { + system.frameSkip = frameSkip; + system.frameCounter = frameSkip; +} + +auto Interface::runAhead() -> bool { + return system.runAhead; +} + +auto Interface::setRunAhead(bool runAhead) -> void { + system.runAhead = runAhead; +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/interface/interface.hpp b/waterbox/bsnescore/bsnes/sfc/interface/interface.hpp new file mode 100644 index 0000000000..d9437487a8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/interface/interface.hpp @@ -0,0 +1,90 @@ +namespace SuperFamicom { + +struct ID { + enum : uint { + System, + SuperFamicom, + GameBoy, + BSMemory, + SufamiTurboA, + SufamiTurboB, + }; + + struct Port { enum : uint { + Controller1, + Controller2, + Expansion, + };}; + + struct Device { enum : uint { + None, + Gamepad, + Mouse, + SuperMultitap, + SuperScope, + Justifier, + Justifiers, + + Satellaview, + S21FX, + };}; +}; + +struct Interface : Emulator::Interface { + auto information() -> Information; + + auto display() -> Display override; + auto color(uint32 color) -> uint64 override; + + auto loaded() -> bool override; + auto hashes() -> vector override; + auto manifests() -> vector override; + auto titles() -> vector override; + auto title() -> string override; + auto load() -> bool override; + auto save() -> void override; + auto unload() -> void override; + + auto ports() -> vector override; + auto devices(uint port) -> vector override; + auto inputs(uint device) -> vector override; + + auto connected(uint port) -> uint override; + auto connect(uint port, uint device) -> void override; + auto power() -> void override; + auto reset() -> void override; + auto run() -> void override; + + auto rtc() -> bool override; + auto synchronize(uint64 timestamp) -> void override; + + auto serialize(bool synchronize = true) -> serializer override; + auto unserialize(serializer&) -> bool override; + + auto read(uint24 address) -> uint8 override; + auto cheats(const vector&) -> void override; + + auto configuration() -> string override; + auto configuration(string name) -> string override; + auto configure(string configuration) -> bool override; + auto configure(string name, string value) -> bool override; + + auto frameSkip() -> uint override; + auto setFrameSkip(uint frameSkip) -> void override; + + auto runAhead() -> bool override; + auto setRunAhead(bool runAhead) -> void override; +}; + +#include "configuration.hpp" + +struct Settings { + uint controllerPort1 = ID::Device::Gamepad; + uint controllerPort2 = ID::Device::Gamepad; + uint expansionPort = ID::Device::None; + bool random = true; +}; + +extern Settings settings; + +} diff --git a/waterbox/bsnescore/bsnes/sfc/memory/memory-inline.hpp b/waterbox/bsnescore/bsnes/sfc/memory/memory-inline.hpp new file mode 100644 index 0000000000..c20d0eca49 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/memory/memory-inline.hpp @@ -0,0 +1,32 @@ +auto Bus::mirror(uint addr, uint size) -> uint { + if(size == 0) return 0; + uint base = 0; + uint mask = 1 << 23; + while(addr >= size) { + while(!(addr & mask)) mask >>= 1; + addr -= mask; + if(size > mask) { + size -= mask; + base += mask; + } + mask >>= 1; + } + return base + addr; +} + +auto Bus::reduce(uint addr, uint mask) -> uint { + while(mask) { + uint bits = (mask & -mask) - 1; + addr = ((addr >> 1) & ~bits) | (addr & bits); + mask = (mask & (mask - 1)) >> 1; + } + return addr; +} + +auto Bus::read(uint addr, uint8 data) -> uint8 { + return reader[lookup[addr]](target[addr], data); +} + +auto Bus::write(uint addr, uint8 data) -> void { + return writer[lookup[addr]](target[addr], data); +} diff --git a/waterbox/bsnescore/bsnes/sfc/memory/memory.cpp b/waterbox/bsnescore/bsnes/sfc/memory/memory.cpp new file mode 100644 index 0000000000..4b76009681 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/memory/memory.cpp @@ -0,0 +1,106 @@ +#include + +namespace SuperFamicom { + +bool Memory::GlobalWriteEnable = false; +Bus bus; + +Bus::~Bus() { + if(lookup) delete[] lookup; + if(target) delete[] target; +} + +auto Bus::reset() -> void { + for(uint id : range(256)) { + reader[id].reset(); + writer[id].reset(); + counter[id] = 0; + } + + if(lookup) delete[] lookup; + if(target) delete[] target; + + lookup = new uint8 [16 * 1024 * 1024](); + target = new uint32[16 * 1024 * 1024](); + + reader[0] = [](uint, uint8 data) -> uint8 { return data; }; + writer[0] = [](uint, uint8) -> void {}; +} + +auto Bus::map( + const function& read, + const function& write, + const string& addr, uint size, uint base, uint mask +) -> uint { + uint id = 1; + while(counter[id]) { + if(++id >= 256) return print("SFC error: bus map exhausted\n"), 0; + } + + reader[id] = read; + writer[id] = write; + + auto p = addr.split(":", 1L); + auto banks = p(0).split(","); + auto addrs = p(1).split(","); + for(auto& bank : banks) { + for(auto& addr : addrs) { + auto bankRange = bank.split("-", 1L); + auto addrRange = addr.split("-", 1L); + uint bankLo = bankRange(0).hex(); + uint bankHi = bankRange(1, bankRange(0)).hex(); + uint addrLo = addrRange(0).hex(); + uint addrHi = addrRange(1, addrRange(0)).hex(); + + for(uint bank = bankLo; bank <= bankHi; bank++) { + for(uint addr = addrLo; addr <= addrHi; addr++) { + uint pid = lookup[bank << 16 | addr]; + if(pid && --counter[pid] == 0) { + reader[pid].reset(); + writer[pid].reset(); + } + + uint offset = reduce(bank << 16 | addr, mask); + if(size) base = mirror(base, size); + if(size) offset = base + mirror(offset, size - base); + lookup[bank << 16 | addr] = id; + target[bank << 16 | addr] = offset; + counter[id]++; + } + } + } + } + + return id; +} + +auto Bus::unmap(const string& addr) -> void { + auto p = addr.split(":", 1L); + auto banks = p(0).split(","); + auto addrs = p(1).split(","); + for(auto& bank : banks) { + for(auto& addr : addrs) { + auto bankRange = bank.split("-", 1L); + auto addrRange = addr.split("-", 1L); + uint bankLo = bankRange(0).hex(); + uint bankHi = bankRange(1, bankRange(0)).hex(); + uint addrLo = addrRange(0).hex(); + uint addrHi = addrRange(1, addrRange(1)).hex(); + + for(uint bank = bankLo; bank <= bankHi; bank++) { + for(uint addr = addrLo; addr <= addrHi; addr++) { + uint pid = lookup[bank << 16 | addr]; + if(pid && --counter[pid] == 0) { + reader[pid].reset(); + writer[pid].reset(); + } + + lookup[bank << 16 | addr] = 0; + target[bank << 16 | addr] = 0; + } + } + } + } +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/memory/memory.hpp b/waterbox/bsnescore/bsnes/sfc/memory/memory.hpp new file mode 100644 index 0000000000..fa028309d8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/memory/memory.hpp @@ -0,0 +1,49 @@ +struct Memory { + static bool GlobalWriteEnable; + + virtual ~Memory() { reset(); } + inline explicit operator bool() const { return size() > 0; } + + virtual auto reset() -> void {} + virtual auto allocate(uint, uint8 = 0xff) -> void {} + + virtual auto data() -> uint8* = 0; + virtual auto size() const -> uint = 0; + + virtual auto read(uint address, uint8 data = 0) -> uint8 = 0; + virtual auto write(uint address, uint8 data) -> void = 0; + + uint id = 0; +}; + +#include "readable.hpp" +#include "writable.hpp" +#include "protectable.hpp" + +struct Bus { + alwaysinline static auto mirror(uint address, uint size) -> uint; + alwaysinline static auto reduce(uint address, uint mask) -> uint; + + ~Bus(); + + alwaysinline auto read(uint address, uint8 data = 0) -> uint8; + alwaysinline auto write(uint address, uint8 data) -> void; + + auto reset() -> void; + auto map( + const function& read, + const function& write, + const string& address, uint size = 0, uint base = 0, uint mask = 0 + ) -> uint; + auto unmap(const string& address) -> void; + +private: + uint8* lookup = nullptr; + uint32* target = nullptr; + + function reader[256]; + function writer[256]; + uint counter[256]; +}; + +extern Bus bus; diff --git a/waterbox/bsnescore/bsnes/sfc/memory/protectable.hpp b/waterbox/bsnescore/bsnes/sfc/memory/protectable.hpp new file mode 100644 index 0000000000..a6f8a5b384 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/memory/protectable.hpp @@ -0,0 +1,54 @@ +struct ProtectableMemory : Memory { + inline auto reset() -> void override { + delete[] self.data; + self.data = nullptr; + self.size = 0; + } + + inline auto allocate(uint size, uint8 fill = 0xff) -> void override { + if(self.size != size) { + delete[] self.data; + self.data = new uint8[self.size = size]; + } + for(uint address : range(size)) { + self.data[address] = fill; + } + } + + inline auto data() -> uint8* override { + return self.data; + } + + inline auto size() const -> uint override { + return self.size; + } + + inline auto writable() const -> bool { + return self.writable; + } + + inline auto writable(bool writable) -> void { + self.writable = writable; + } + + inline auto read(uint address, uint8 data = 0) -> uint8 override { + return self.data[address]; + } + + inline auto write(uint address, uint8 data) -> void override { + if(self.writable || Memory::GlobalWriteEnable) { + self.data[address] = data; + } + } + + inline auto operator[](uint address) const -> uint8 { + return self.data[address]; + } + +private: + struct { + uint8* data = nullptr; + uint size = 0; + bool writable = false; + } self; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/memory/readable.hpp b/waterbox/bsnescore/bsnes/sfc/memory/readable.hpp new file mode 100644 index 0000000000..fcaf14b334 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/memory/readable.hpp @@ -0,0 +1,45 @@ +struct ReadableMemory : Memory { + inline auto reset() -> void override { + delete[] self.data; + self.data = nullptr; + self.size = 0; + } + + inline auto allocate(uint size, uint8 fill = 0xff) -> void override { + if(self.size != size) { + delete[] self.data; + self.data = new uint8[self.size = size]; + } + for(uint address : range(size)) { + self.data[address] = fill; + } + } + + inline auto data() -> uint8* override { + return self.data; + } + + inline auto size() const -> uint override { + return self.size; + } + + inline auto read(uint address, uint8 data = 0) -> uint8 override { + return self.data[address]; + } + + inline auto write(uint address, uint8 data) -> void override { + if(Memory::GlobalWriteEnable) { + self.data[address] = data; + } + } + + inline auto operator[](uint address) const -> uint8 { + return self.data[address]; + } + +private: + struct { + uint8* data = nullptr; + uint size = 0; + } self; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/memory/writable.hpp b/waterbox/bsnescore/bsnes/sfc/memory/writable.hpp new file mode 100644 index 0000000000..7d51303fc5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/memory/writable.hpp @@ -0,0 +1,43 @@ +struct WritableMemory : Memory { + inline auto reset() -> void override { + delete[] self.data; + self.data = nullptr; + self.size = 0; + } + + inline auto allocate(uint size, uint8 fill = 0xff) -> void override { + if(self.size != size) { + delete[] self.data; + self.data = new uint8[self.size = size]; + } + for(uint address : range(size)) { + self.data[address] = fill; + } + } + + inline auto data() -> uint8* override { + return self.data; + } + + inline auto size() const -> uint override { + return self.size; + } + + inline auto read(uint address, uint8 data = 0) -> uint8 override { + return self.data[address]; + } + + inline auto write(uint address, uint8 data) -> void override { + self.data[address] = data; + } + + inline auto operator[](uint address) -> uint8& { + return self.data[address]; + } + +private: + struct { + uint8* data = nullptr; + uint size = 0; + } self; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/background.cpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/background.cpp new file mode 100644 index 0000000000..4516628816 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/background.cpp @@ -0,0 +1,158 @@ +auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> void { + if(!self.aboveEnable && !self.belowEnable) return; + if(self.tileMode == TileMode::Mode7) return renderMode7(self, source); + if(self.tileMode == TileMode::Inactive) return; + + bool windowAbove[256]; + bool windowBelow[256]; + renderWindow(self.window, self.window.aboveEnable, windowAbove); + renderWindow(self.window, self.window.belowEnable, windowBelow); + + bool hires = io.bgMode == 5 || io.bgMode == 6; + bool offsetPerTileMode = io.bgMode == 2 || io.bgMode == 4 || io.bgMode == 6; + bool directColorMode = io.col.directColor && source == Source::BG1 && (io.bgMode == 3 || io.bgMode == 4); + uint colorShift = 3 + self.tileMode; + int width = 256 << hires; + + uint tileHeight = 3 + self.tileSize; + uint tileWidth = !hires ? tileHeight : 4; + uint tileMask = 0x0fff >> self.tileMode; + uint tiledataIndex = self.tiledataAddress >> 3 + self.tileMode; + + uint paletteBase = io.bgMode == 0 ? source << 5 : 0; + uint paletteShift = 2 << self.tileMode; + + uint hscroll = self.hoffset; + uint vscroll = self.voffset; + uint hmask = (width << self.tileSize << !!(self.screenSize & 1)) - 1; + uint vmask = (width << self.tileSize << !!(self.screenSize & 2)) - 1; + + uint y = this->y; + + if(hires) { + hscroll <<= 1; + if(io.interlace) y = y << 1 | (field() && !self.mosaicEnable); + } + if(self.mosaicEnable) { + y -= (io.mosaic.size - io.mosaic.counter) << (hires && io.interlace); + } + + uint mosaicCounter = 1; + uint mosaicPalette = 0; + uint8 mosaicPriority = 0; + uint16 mosaicColor = 0; + + int x = 0 - (hscroll & 7); + while(x < width) { + uint hoffset = x + hscroll; + uint voffset = y + vscroll; + if(offsetPerTileMode) { + uint validBit = 0x2000 << source; + uint offsetX = x + (hscroll & 7); + if(offsetX >= 8) { //first column is exempt + uint hlookup = getTile(io.bg3, (offsetX - 8) + (io.bg3.hoffset & ~7), io.bg3.voffset + 0); + if(io.bgMode == 4) { + if(hlookup & validBit) { + if(!(hlookup & 0x8000)) { + hoffset = offsetX + (hlookup & ~7); + } else { + voffset = y + hlookup; + } + } + } else { + uint vlookup = getTile(io.bg3, (offsetX - 8) + (io.bg3.hoffset & ~7), io.bg3.voffset + 8); + if(hlookup & validBit) { + hoffset = offsetX + (hlookup & ~7); + } + if(vlookup & validBit) { + voffset = y + vlookup; + } + } + } + } + hoffset &= hmask; + voffset &= vmask; + + uint tileNumber = getTile(self, hoffset, voffset); + uint mirrorY = tileNumber & 0x8000 ? 7 : 0; + uint mirrorX = tileNumber & 0x4000 ? 7 : 0; + uint8 tilePriority = self.priority_enabled[bool(tileNumber & 0x2000)] ? self.priority[bool(tileNumber & 0x2000)] : 0; + uint paletteNumber = tileNumber >> 10 & 7; + uint paletteIndex = paletteBase + (paletteNumber << paletteShift) & 0xff; + + if(tileWidth == 4 && (bool(hoffset & 8) ^ bool(mirrorX))) tileNumber += 1; + if(tileHeight == 4 && (bool(voffset & 8) ^ bool(mirrorY))) tileNumber += 16; + tileNumber = (tileNumber & 0x03ff) + tiledataIndex & tileMask; + + uint16 address; + address = (tileNumber << colorShift) + (voffset & 7 ^ mirrorY) & 0x7fff; + + uint64 data; + data = (uint64)ppu.vram[address + 0] << 0; + data |= (uint64)ppu.vram[address + 8] << 16; + data |= (uint64)ppu.vram[address + 16] << 32; + data |= (uint64)ppu.vram[address + 24] << 48; + + for(uint tileX = 0; tileX < 8; tileX++, x++) { + if(x & width) continue; //x < 0 || x >= width + if(--mosaicCounter == 0) { + uint color, shift = mirrorX ? tileX : 7 - tileX; + /*if(self.tileMode >= TileMode::BPP2)*/ { + color = data >> shift + 0 & 1; + color += data >> shift + 7 & 2; + } + if(self.tileMode >= TileMode::BPP4) { + color += data >> shift + 14 & 4; + color += data >> shift + 21 & 8; + } + if(self.tileMode >= TileMode::BPP8) { + color += data >> shift + 28 & 16; + color += data >> shift + 35 & 32; + color += data >> shift + 42 & 64; + color += data >> shift + 49 & 128; + } + + mosaicCounter = self.mosaicEnable ? io.mosaic.size << hires : 1; + mosaicPalette = color; + mosaicPriority = tilePriority; + if(directColorMode) { + mosaicColor = directColor(paletteNumber, mosaicPalette); + } else { + mosaicColor = cgram[paletteIndex + mosaicPalette]; + } + } + if(!mosaicPalette) continue; + + if(!hires) { + if(self.aboveEnable && !windowAbove[x]) plotAbove(x, source, mosaicPriority, mosaicColor); + if(self.belowEnable && !windowBelow[x]) plotBelow(x, source, mosaicPriority, mosaicColor); + } else { + uint X = x >> 1; + if(!ppu.hd()) { + if(x & 1) { + if(self.aboveEnable && !windowAbove[X]) plotAbove(X, source, mosaicPriority, mosaicColor); + } else { + if(self.belowEnable && !windowBelow[X]) plotBelow(X, source, mosaicPriority, mosaicColor); + } + } else { + if(self.aboveEnable && !windowAbove[X]) plotHD(above, X, source, mosaicPriority, mosaicColor, true, x & 1); + if(self.belowEnable && !windowBelow[X]) plotHD(below, X, source, mosaicPriority, mosaicColor, true, x & 1); + } + } + } + } +} + +auto PPU::Line::getTile(PPU::IO::Background& self, uint hoffset, uint voffset) -> uint { + bool hires = io.bgMode == 5 || io.bgMode == 6; + uint tileHeight = 3 + self.tileSize; + uint tileWidth = !hires ? tileHeight : 4; + uint screenX = self.screenSize & 1 ? 32 << 5 : 0; + uint screenY = self.screenSize & 2 ? 32 << 5 + (self.screenSize & 1) : 0; + uint tileX = hoffset >> tileWidth; + uint tileY = voffset >> tileHeight; + uint offset = (tileY & 0x1f) << 5 | (tileX & 0x1f); + if(tileX & 0x20) offset += screenX; + if(tileY & 0x20) offset += screenY; + return ppu.vram[self.screenAddress + offset & 0x7fff]; +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/io.cpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/io.cpp new file mode 100644 index 0000000000..4e445a0336 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/io.cpp @@ -0,0 +1,699 @@ +auto PPU::latchCounters(uint hcounter, uint vcounter) -> void { + io.hcounter = hcounter; + io.vcounter = vcounter; + latch.counters = 1; +} + +auto PPU::latchCounters() -> void { + io.hcounter = cpu.hdot(); + io.vcounter = cpu.vcounter(); + latch.counters = 1; +} + +auto PPU::vramAddress() const -> uint { + uint address = io.vramAddress; + switch(io.vramMapping) { + case 0: return address & 0x7fff; + case 1: return address & 0x7f00 | address << 3 & 0x00f8 | address >> 5 & 7; + case 2: return address & 0x7e00 | address << 3 & 0x01f8 | address >> 6 & 7; + case 3: return address & 0x7c00 | address << 3 & 0x03f8 | address >> 7 & 7; + } + unreachable; +} + +auto PPU::readVRAM() -> uint16 { + if(!io.displayDisable && cpu.vcounter() < vdisp()) return 0x0000; + auto address = vramAddress(); + return vram[address]; +} + +template +auto PPU::writeVRAM(uint8 data) -> void { + if(!io.displayDisable && cpu.vcounter() < vdisp() && !noVRAMBlocking()) return; + Line::flush(); + auto address = vramAddress(); + if constexpr(Byte == 0) { + vram[address] = vram[address] & 0xff00 | data << 0; + } + if constexpr(Byte == 1) { + vram[address] = vram[address] & 0x00ff | data << 8; + } +} + +auto PPU::readOAM(uint10 address) -> uint8 { + if(!io.displayDisable && cpu.vcounter() < vdisp()) address = latch.oamAddress; + return readObject(address); +} + +auto PPU::writeOAM(uint10 address, uint8 data) -> void { + Line::flush(); + //0x0218: Uniracers (2-player mode) hack; requires cycle timing for latch.oamAddress to be correct + if(!io.displayDisable && cpu.vcounter() < vdisp()) address = 0x0218; //latch.oamAddress; + return writeObject(address, data); +} + +template +auto PPU::readCGRAM(uint8 address) -> uint8 { + if(!io.displayDisable + && cpu.vcounter() > 0 && cpu.vcounter() < vdisp() + && cpu.hcounter() >= 88 && cpu.hcounter() < 1096 + ) address = latch.cgramAddress; + if constexpr(Byte == 0) { + return cgram[address] >> 0; + } + if constexpr(Byte == 1) { + return cgram[address] >> 8; + } +} + +auto PPU::writeCGRAM(uint8 address, uint15 data) -> void { + if(!io.displayDisable + && cpu.vcounter() > 0 && cpu.vcounter() < vdisp() + && cpu.hcounter() >= 88 && cpu.hcounter() < 1096 + ) address = latch.cgramAddress; + cgram[address] = data; +} + +auto PPU::readIO(uint address, uint8 data) -> uint8 { + cpu.synchronizePPU(); + + switch(address & 0xffff) { + + case 0x2104: case 0x2105: case 0x2106: case 0x2108: + case 0x2109: case 0x210a: case 0x2114: case 0x2115: + case 0x2116: case 0x2118: case 0x2119: case 0x211a: + case 0x2124: case 0x2125: case 0x2126: case 0x2128: + case 0x2129: case 0x212a: { + return latch.ppu1.mdr; + } + + case 0x2134: { //MPYL + uint result = (int16)io.mode7.a * (int8)(io.mode7.b >> 8); + return latch.ppu1.mdr = result >> 0; + } + + case 0x2135: { //MPYM + uint result = (int16)io.mode7.a * (int8)(io.mode7.b >> 8); + return latch.ppu1.mdr = result >> 8; + } + + case 0x2136: { //MPYH + uint result = (int16)io.mode7.a * (int8)(io.mode7.b >> 8); + return latch.ppu1.mdr = result >> 16; + } + + case 0x2137: { //SLHV + if(cpu.pio() & 0x80) latchCounters(); + return data; //CPU MDR + } + + case 0x2138: { //OAMDATAREAD + data = readOAM(io.oamAddress); + io.oamAddress = io.oamAddress + 1 & 0x3ff; + oamSetFirstObject(); + return latch.ppu1.mdr = data; + } + + case 0x2139: { //VMDATALREAD + data = latch.vram >> 0; + if(io.vramIncrementMode == 0) { + latch.vram = readVRAM(); + io.vramAddress += io.vramIncrementSize; + } + return latch.ppu1.mdr = data; + } + + case 0x213a: { //VMDATAHREAD + data = latch.vram >> 8; + if(io.vramIncrementMode == 1) { + latch.vram = readVRAM(); + io.vramAddress += io.vramIncrementSize; + } + return latch.ppu1.mdr = data; + } + + case 0x213b: { //CGDATAREAD + if(io.cgramAddressLatch == 0) { + io.cgramAddressLatch = 1; + latch.ppu2.mdr = readCGRAM<0>(io.cgramAddress); + } else { + io.cgramAddressLatch = 0; + latch.ppu2.mdr = readCGRAM<1>(io.cgramAddress++) & 0x7f | latch.ppu2.mdr & 0x80; + } + return latch.ppu2.mdr; + } + + case 0x213c: { //OPHCT + if(latch.hcounter == 0) { + latch.hcounter = 1; + latch.ppu2.mdr = io.hcounter; + } else { + latch.hcounter = 0; + latch.ppu2.mdr = io.hcounter >> 8 | latch.ppu2.mdr & 0xfe; + } + return latch.ppu2.mdr; + } + + case 0x213d: { //OPVCT + if(latch.vcounter == 0) { + latch.vcounter = 1; + latch.ppu2.mdr = io.vcounter; + } else { + latch.vcounter = 0; + latch.ppu2.mdr = io.vcounter >> 8 | latch.ppu2.mdr & 0xfe; + } + return latch.ppu2.mdr; + } + + case 0x213e: { //STAT77 + latch.ppu1.mdr = 0x01 | io.obj.rangeOver << 6 | io.obj.timeOver << 7; + return latch.ppu1.mdr; + } + + case 0x213f: { //STAT78 + latch.hcounter = 0; + latch.vcounter = 0; + latch.ppu2.mdr &= 1 << 5; + latch.ppu2.mdr |= 0x03 | Region::PAL() << 4 | field() << 7; + if(!(cpu.pio() & 0x80)) { + latch.ppu2.mdr |= 1 << 6; + } else { + latch.ppu2.mdr |= latch.counters << 6; + latch.counters = 0; + } + return latch.ppu2.mdr; + } + + } + + return data; +} + +auto PPU::writeIO(uint address, uint8 data) -> void { + cpu.synchronizePPU(); + + switch(address & 0xffff) { + + case 0x2100: { //INIDISP + if(io.displayDisable && cpu.vcounter() == vdisp()) oamAddressReset(); + io.displayBrightness = data >> 0 & 15; + io.displayDisable = data >> 7 & 1; + return; + } + + case 0x2101: { //OBSEL + io.obj.tiledataAddress = data << 13 & 0x6000; + io.obj.nameselect = data >> 3 & 3; + io.obj.baseSize = data >> 5 & 7; + return; + } + + case 0x2102: { //OAMADDL + io.oamBaseAddress = (io.oamBaseAddress & 0x0200) | data << 1; + oamAddressReset(); + return; + } + + case 0x2103: { //OAMADDH + io.oamBaseAddress = (data & 1) << 9 | io.oamBaseAddress & 0x01fe; + io.oamPriority = data >> 7 & 1; + oamAddressReset(); + return; + } + + case 0x2104: { //OAMDATA + bool latchBit = io.oamAddress & 1; + uint address = io.oamAddress; + io.oamAddress = io.oamAddress + 1 & 0x3ff; + if(latchBit == 0) latch.oam = data; + if(address & 0x200) { + writeOAM(address, data); + } else if(latchBit == 1) { + writeOAM((address & ~1) + 0, latch.oam); + writeOAM((address & ~1) + 1, data); + } + oamSetFirstObject(); + return; + } + + case 0x2105: { //BGMODE + io.bgMode = data >> 0 & 7; + io.bgPriority = data >> 3 & 1; + io.bg1.tileSize = data >> 4 & 1; + io.bg2.tileSize = data >> 5 & 1; + io.bg3.tileSize = data >> 6 & 1; + io.bg4.tileSize = data >> 7 & 1; + updateVideoMode(); + return; + } + + case 0x2106: { //MOSAIC + bool mosaicEnable = io.bg1.mosaicEnable || io.bg2.mosaicEnable || io.bg3.mosaicEnable || io.bg4.mosaicEnable; + io.bg1.mosaicEnable = data >> 0 & 1; + io.bg2.mosaicEnable = data >> 1 & 1; + io.bg3.mosaicEnable = data >> 2 & 1; + io.bg4.mosaicEnable = data >> 3 & 1; + io.mosaic.size = (data >> 4 & 15) + 1; + if(!mosaicEnable && (data >> 0 & 15)) { + //mosaic vcounter is reloaded when mosaic becomes enabled + io.mosaic.counter = io.mosaic.size + 1; + } + return; + } + + case 0x2107: { //BG1SC + io.bg1.screenSize = data >> 0 & 3; + io.bg1.screenAddress = data << 8 & 0x7c00; + return; + } + + case 0x2108: { //BG2SC + io.bg2.screenSize = data >> 0 & 3; + io.bg2.screenAddress = data << 8 & 0x7c00; + return; + } + + case 0x2109: { //BG3SC + io.bg3.screenSize = data >> 0 & 3; + io.bg3.screenAddress = data << 8 & 0x7c00; + return; + } + + case 0x210a: { //BG4SC + io.bg4.screenSize = data >> 0 & 3; + io.bg4.screenAddress = data << 8 & 0x7c00; + return; + } + + case 0x210b: { //BG12NBA + io.bg1.tiledataAddress = data << 12 & 0x7000; + io.bg2.tiledataAddress = data << 8 & 0x7000; + return; + } + + case 0x210c: { //BG34NBA + io.bg3.tiledataAddress = data << 12 & 0x7000; + io.bg4.tiledataAddress = data << 8 & 0x7000; + return; + } + + case 0x210d: { //BG1HOFS + io.mode7.hoffset = data << 8 | latch.mode7; + latch.mode7 = data; + + io.bg1.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7); + latch.ppu1.bgofs = data; + latch.ppu2.bgofs = data; + return; + } + + case 0x210e: { //BG1VOFS + io.mode7.voffset = data << 8 | latch.mode7; + latch.mode7 = data; + + io.bg1.voffset = data << 8 | latch.ppu1.bgofs; + latch.ppu1.bgofs = data; + return; + } + + case 0x210f: { //BG2HOFS + io.bg2.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7); + latch.ppu1.bgofs = data; + latch.ppu2.bgofs = data; + return; + } + + case 0x2110: { //BG2VOFS + io.bg2.voffset = data << 8 | latch.ppu1.bgofs; + latch.ppu1.bgofs = data; + return; + } + + case 0x2111: { //BG3HOFS + io.bg3.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7); + latch.ppu1.bgofs = data; + latch.ppu2.bgofs = data; + return; + } + + case 0x2112: { //BG3VOFS + io.bg3.voffset = data << 8 | latch.ppu1.bgofs; + latch.ppu1.bgofs = data; + return; + } + + case 0x2113: { //BG4HOFS + io.bg4.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7); + latch.ppu1.bgofs = data; + latch.ppu2.bgofs = data; + return; + } + + case 0x2114: { //BG4VOFS + io.bg4.voffset = data << 8 | latch.ppu1.bgofs; + latch.ppu1.bgofs = data; + return; + } + + case 0x2115: { //VMAIN + static const uint size[4] = {1, 32, 128, 128}; + io.vramIncrementSize = size[data & 3]; + io.vramMapping = data >> 2 & 3; + io.vramIncrementMode = data >> 7 & 1; + return; + } + + case 0x2116: { //VMADDL + io.vramAddress = io.vramAddress & 0xff00 | data << 0; + latch.vram = readVRAM(); + return; + } + + case 0x2117: { //VMADDH + io.vramAddress = io.vramAddress & 0x00ff | data << 8; + latch.vram = readVRAM(); + return; + } + + case 0x2118: { //VMDATAL + writeVRAM<0>(data); + if(io.vramIncrementMode == 0) io.vramAddress += io.vramIncrementSize; + return; + } + + case 0x2119: { //VMDATAH + writeVRAM<1>(data); + if(io.vramIncrementMode == 1) io.vramAddress += io.vramIncrementSize; + return; + } + + case 0x211a: { //M7SEL + io.mode7.hflip = data >> 0 & 1; + io.mode7.vflip = data >> 1 & 1; + io.mode7.repeat = data >> 6 & 3; + return; + } + + case 0x211b: { //M7A + io.mode7.a = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + case 0x211c: { //M7B + io.mode7.b = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + case 0x211d: { //M7C + io.mode7.c = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + case 0x211e: { //M7D + io.mode7.d = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + case 0x211f: { //M7X + io.mode7.x = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + case 0x2120: { //M7Y + io.mode7.y = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + case 0x2121: { //CGADD + io.cgramAddress = data; + io.cgramAddressLatch = 0; + return; + } + + case 0x2122: { //CGDATA + if(io.cgramAddressLatch == 0) { + io.cgramAddressLatch = 1; + latch.cgram = data; + } else { + io.cgramAddressLatch = 0; + writeCGRAM(io.cgramAddress++, (data & 0x7f) << 8 | latch.cgram); + } + return; + } + + case 0x2123: { //W12SEL + io.bg1.window.oneInvert = data >> 0 & 1; + io.bg1.window.oneEnable = data >> 1 & 1; + io.bg1.window.twoInvert = data >> 2 & 1; + io.bg1.window.twoEnable = data >> 3 & 1; + io.bg2.window.oneInvert = data >> 4 & 1; + io.bg2.window.oneEnable = data >> 5 & 1; + io.bg2.window.twoInvert = data >> 6 & 1; + io.bg2.window.twoEnable = data >> 7 & 1; + return; + } + + case 0x2124: { //W34SEL + io.bg3.window.oneInvert = data >> 0 & 1; + io.bg3.window.oneEnable = data >> 1 & 1; + io.bg3.window.twoInvert = data >> 2 & 1; + io.bg3.window.twoEnable = data >> 3 & 1; + io.bg4.window.oneInvert = data >> 4 & 1; + io.bg4.window.oneEnable = data >> 5 & 1; + io.bg4.window.twoInvert = data >> 6 & 1; + io.bg4.window.twoEnable = data >> 7 & 1; + return; + } + + case 0x2125: { //WOBJSEL + io.obj.window.oneInvert = data >> 0 & 1; + io.obj.window.oneEnable = data >> 1 & 1; + io.obj.window.twoInvert = data >> 2 & 1; + io.obj.window.twoEnable = data >> 3 & 1; + io.col.window.oneInvert = data >> 4 & 1; + io.col.window.oneEnable = data >> 5 & 1; + io.col.window.twoInvert = data >> 6 & 1; + io.col.window.twoEnable = data >> 7 & 1; + return; + } + + case 0x2126: { //WH0 + io.window.oneLeft = data; + return; + } + + case 0x2127: { //WH1 + io.window.oneRight = data; + return; + } + + case 0x2128: { //WH2 + io.window.twoLeft = data; + return; + } + + case 0x2129: { //WH3 + io.window.twoRight = data; + return; + } + + case 0x212a: { //WBGLOG + io.bg1.window.mask = data >> 0 & 3; + io.bg2.window.mask = data >> 2 & 3; + io.bg3.window.mask = data >> 4 & 3; + io.bg4.window.mask = data >> 6 & 3; + return; + } + + case 0x212b: { //WOBJLOG + io.obj.window.mask = data >> 0 & 3; + io.col.window.mask = data >> 2 & 3; + return; + } + + case 0x212c: { //TM + io.bg1.aboveEnable = data >> 0 & 1; + io.bg2.aboveEnable = data >> 1 & 1; + io.bg3.aboveEnable = data >> 2 & 1; + io.bg4.aboveEnable = data >> 3 & 1; + io.obj.aboveEnable = data >> 4 & 1; + return; + } + + case 0x212d: { //TS + io.bg1.belowEnable = data >> 0 & 1; + io.bg2.belowEnable = data >> 1 & 1; + io.bg3.belowEnable = data >> 2 & 1; + io.bg4.belowEnable = data >> 3 & 1; + io.obj.belowEnable = data >> 4 & 1; + return; + } + + case 0x212e: { //TMW + io.bg1.window.aboveEnable = data >> 0 & 1; + io.bg2.window.aboveEnable = data >> 1 & 1; + io.bg3.window.aboveEnable = data >> 2 & 1; + io.bg4.window.aboveEnable = data >> 3 & 1; + io.obj.window.aboveEnable = data >> 4 & 1; + return; + } + + case 0x212f: { //TSW + io.bg1.window.belowEnable = data >> 0 & 1; + io.bg2.window.belowEnable = data >> 1 & 1; + io.bg3.window.belowEnable = data >> 2 & 1; + io.bg4.window.belowEnable = data >> 3 & 1; + io.obj.window.belowEnable = data >> 4 & 1; + return; + } + + case 0x2130: { //CGWSEL + io.col.directColor = data >> 0 & 1; + io.col.blendMode = data >> 1 & 1; + io.col.window.belowMask = data >> 4 & 3; + io.col.window.aboveMask = data >> 6 & 3; + return; + } + + case 0x2131: { //CGADDSUB + io.col.enable[Source::BG1 ] = data >> 0 & 1; + io.col.enable[Source::BG2 ] = data >> 1 & 1; + io.col.enable[Source::BG3 ] = data >> 2 & 1; + io.col.enable[Source::BG4 ] = data >> 3 & 1; + io.col.enable[Source::OBJ1] = 0; + io.col.enable[Source::OBJ2] = data >> 4 & 1; + io.col.enable[Source::COL ] = data >> 5 & 1; + io.col.halve = data >> 6 & 1; + io.col.mathMode = data >> 7 & 1; + return; + } + + case 0x2132: { //COLDATA + if(data & 0x20) io.col.fixedColor = io.col.fixedColor & 0b11111'11111'00000 | (data & 31) << 0; + if(data & 0x40) io.col.fixedColor = io.col.fixedColor & 0b11111'00000'11111 | (data & 31) << 5; + if(data & 0x80) io.col.fixedColor = io.col.fixedColor & 0b00000'11111'11111 | (data & 31) << 10; + return; + } + + case 0x2133: { //SETINI + io.interlace = data >> 0 & 1; + io.obj.interlace = data >> 1 & 1; + io.overscan = data >> 2 & 1; + io.pseudoHires = data >> 3 & 1; + io.extbg = data >> 6 & 1; + updateVideoMode(); + return; + } + + } +} + +auto PPU::updateVideoMode() -> void { + ppubase.display.vdisp = !io.overscan ? 225 : 240; + + switch(io.bgMode) { + case 0: + io.bg1.tileMode = TileMode::BPP2; + io.bg2.tileMode = TileMode::BPP2; + io.bg3.tileMode = TileMode::BPP2; + io.bg4.tileMode = TileMode::BPP2; + memory::assign(io.bg1.priority, 8, 11); + memory::assign(io.bg2.priority, 7, 10); + memory::assign(io.bg3.priority, 2, 5); + memory::assign(io.bg4.priority, 1, 4); + memory::assign(io.obj.priority, 3, 6, 9, 12); + break; + + case 1: + io.bg1.tileMode = TileMode::BPP4; + io.bg2.tileMode = TileMode::BPP4; + io.bg3.tileMode = TileMode::BPP2; + io.bg4.tileMode = TileMode::Inactive; + if(io.bgPriority) { + memory::assign(io.bg1.priority, 5, 8); + memory::assign(io.bg2.priority, 4, 7); + memory::assign(io.bg3.priority, 1, 10); + memory::assign(io.obj.priority, 2, 3, 6, 9); + } else { + memory::assign(io.bg1.priority, 6, 9); + memory::assign(io.bg2.priority, 5, 8); + memory::assign(io.bg3.priority, 1, 3); + memory::assign(io.obj.priority, 2, 4, 7, 10); + } + break; + + case 2: + io.bg1.tileMode = TileMode::BPP4; + io.bg2.tileMode = TileMode::BPP4; + io.bg3.tileMode = TileMode::Inactive; + io.bg4.tileMode = TileMode::Inactive; + memory::assign(io.bg1.priority, 3, 7); + memory::assign(io.bg2.priority, 1, 5); + memory::assign(io.obj.priority, 2, 4, 6, 8); + break; + + case 3: + io.bg1.tileMode = TileMode::BPP8; + io.bg2.tileMode = TileMode::BPP4; + io.bg3.tileMode = TileMode::Inactive; + io.bg4.tileMode = TileMode::Inactive; + memory::assign(io.bg1.priority, 3, 7); + memory::assign(io.bg2.priority, 1, 5); + memory::assign(io.obj.priority, 2, 4, 6, 8); + break; + + case 4: + io.bg1.tileMode = TileMode::BPP8; + io.bg2.tileMode = TileMode::BPP2; + io.bg3.tileMode = TileMode::Inactive; + io.bg4.tileMode = TileMode::Inactive; + memory::assign(io.bg1.priority, 3, 7); + memory::assign(io.bg2.priority, 1, 5); + memory::assign(io.obj.priority, 2, 4, 6, 8); + break; + + case 5: + io.bg1.tileMode = TileMode::BPP4; + io.bg2.tileMode = TileMode::BPP2; + io.bg3.tileMode = TileMode::Inactive; + io.bg4.tileMode = TileMode::Inactive; + memory::assign(io.bg1.priority, 3, 7); + memory::assign(io.bg2.priority, 1, 5); + memory::assign(io.obj.priority, 2, 4, 6, 8); + break; + + case 6: + io.bg1.tileMode = TileMode::BPP4; + io.bg2.tileMode = TileMode::Inactive; + io.bg3.tileMode = TileMode::Inactive; + io.bg4.tileMode = TileMode::Inactive; + memory::assign(io.bg1.priority, 2, 5); + memory::assign(io.obj.priority, 1, 3, 4, 6); + break; + + case 7: + if(!io.extbg) { + io.bg1.tileMode = TileMode::Mode7; + io.bg2.tileMode = TileMode::Inactive; + io.bg3.tileMode = TileMode::Inactive; + io.bg4.tileMode = TileMode::Inactive; + memory::assign(io.bg1.priority, 2); + memory::assign(io.obj.priority, 1, 3, 4, 5); + } else { + io.bg1.tileMode = TileMode::Mode7; + io.bg2.tileMode = TileMode::Mode7; + io.bg3.tileMode = TileMode::Inactive; + io.bg4.tileMode = TileMode::Inactive; + memory::assign(io.bg1.priority, 3); + memory::assign(io.bg2.priority, 1, 5); + memory::assign(io.obj.priority, 2, 4, 6, 7); + } + break; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/line.cpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/line.cpp new file mode 100644 index 0000000000..959b5db9a7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/line.cpp @@ -0,0 +1,171 @@ +uint PPU::Line::start = 0; +uint PPU::Line::count = 0; + +auto PPU::Line::flush() -> void { + if(Line::count) { + if(ppu.hdScale() > 1) cacheMode7HD(); + // #pragma omp parallel for if(Line::count >= 8) // we do not have openmp support in waterbox + for(uint y = 0; y < Line::count; y++) { + if(ppu.deinterlace()) { + if(!ppu.interlace()) { + //some games enable interlacing in 240p mode, just force these to even fields + ppu.lines[Line::start + y].render(0); + } else { + //for actual interlaced frames, render both fields every farme for 480i -> 480p + ppu.lines[Line::start + y].render(0); + ppu.lines[Line::start + y].render(1); + } + } else { + //standard 240p (progressive) and 480i (interlaced) rendering + ppu.lines[Line::start + y].render(ppu.field()); + } + } + Line::start = 0; + Line::count = 0; + } +} + +auto PPU::Line::cache() -> void { + uint y = ppu.vcounter(); + if(ppu.io.displayDisable || y >= ppu.vdisp()) { + io.displayDisable = true; + } else { + memcpy(&io, &ppu.io, sizeof(io)); + memcpy(&cgram, &ppu.cgram, sizeof(cgram)); + } + if(!Line::count) Line::start = y; + Line::count++; +} + +auto PPU::Line::render(bool fieldID) -> void { + this->fieldID = fieldID; + uint y = this->y + (!ppu.latch.overscan ? 7 : 0); + + auto hd = ppu.hd(); + auto ss = ppu.ss(); + auto scale = ppu.hdScale(); + auto output = ppu.output + (!hd + ? (y * 1024 + (ppu.interlace() && field() ? 512 : 0)) + : (y * 256 * scale * scale) + ); + auto width = (!hd + ? (!ppu.hires() ? 256 : 512) + : (256 * scale * scale)); + + if(io.displayDisable) { + memory::fill(output, width); + return; + } + + bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; + uint16 aboveColor = cgram[0]; + uint16 belowColor = hires ? cgram[0] : io.col.fixedColor; + uint xa = (hd || ss) && ppu.interlace() && field() ? 256 * scale * scale / 2 : 0; + uint xb = !(hd || ss) ? 256 : ppu.interlace() && !field() ? 256 * scale * scale / 2 : 256 * scale * scale; + for(uint x = xa; x < xb; x++) { + above[x] = {Source::COL, 0, aboveColor}; + below[x] = {Source::COL, 0, belowColor}; + } + + //hack: generally, renderBackground/renderObject ordering do not matter. + //but for HD mode 7, a larger grid of pixels are generated, and so ordering ends up mattering. + //as a hack for Mohawk & Headphone Jack, we reorder things for BG2 to render properly. + //longer-term, we need to devise a better solution that can work for every game. + renderBackground(io.bg1, Source::BG1); + if(io.extbg == 0) renderBackground(io.bg2, Source::BG2); + renderBackground(io.bg3, Source::BG3); + renderBackground(io.bg4, Source::BG4); + renderObject(io.obj); + if(io.extbg == 1) renderBackground(io.bg2, Source::BG2); + renderWindow(io.col.window, io.col.window.aboveMask, windowAbove); + renderWindow(io.col.window, io.col.window.belowMask, windowBelow); + + auto luma = ppu.lightTable[io.displayBrightness]; + uint curr = 0, prev = 0; + if(hd) for(uint x : range(256 * scale * scale)) { + *output++ = luma[pixel(x / scale & 255, above[x], below[x])]; + } else if(width == 256) for(uint x : range(256)) { + *output++ = luma[pixel(x, above[x], below[x])]; + } else if(!hires) for(uint x : range(256)) { + auto color = luma[pixel(x, above[x], below[x])]; + *output++ = color; + *output++ = color; + } else if(!configuration.video.blurEmulation) for(uint x : range(256)) { + *output++ = luma[pixel(x, below[x], above[x])]; + *output++ = luma[pixel(x, above[x], below[x])]; + } else for(uint x : range(256)) { + curr = luma[pixel(x, below[x], above[x])]; + *output++ = (prev + curr - ((prev ^ curr) & 0x0421)) >> 1; + prev = curr; + curr = luma[pixel(x, above[x], below[x])]; + *output++ = (prev + curr - ((prev ^ curr) & 0x0421)) >> 1; + prev = curr; + } +} + +auto PPU::Line::pixel(uint x, Pixel above, Pixel below) const -> uint16 { + if(!windowAbove[x]) above.color = 0x0000; + if(!windowBelow[x]) return above.color; + if(!io.col.enable[above.source]) return above.color; + if(!io.col.blendMode) return blend(above.color, io.col.fixedColor, io.col.halve && windowAbove[x]); + return blend(above.color, below.color, io.col.halve && windowAbove[x] && below.source != Source::COL); +} + +auto PPU::Line::blend(uint x, uint y, bool halve) const -> uint16 { + if(!io.col.mathMode) { //add + if(!halve) { + uint sum = x + y; + uint carry = (sum - ((x ^ y) & 0x0421)) & 0x8420; + return (sum - carry) | (carry - (carry >> 5)); + } else { + return (x + y - ((x ^ y) & 0x0421)) >> 1; + } + } else { //sub + uint diff = x - y + 0x8420; + uint borrow = (diff - ((x ^ y) & 0x8420)) & 0x8420; + if(!halve) { + return (diff - borrow) & (borrow - (borrow >> 5)); + } else { + return (((diff - borrow) & (borrow - (borrow >> 5))) & 0x7bde) >> 1; + } + } +} + +auto PPU::Line::directColor(uint paletteIndex, uint paletteColor) const -> uint16 { + //paletteIndex = bgr + //paletteColor = BBGGGRRR + //output = 0 BBb00 GGGg0 RRRr0 + return (paletteColor << 2 & 0x001c) + (paletteIndex << 1 & 0x0002) //R + + (paletteColor << 4 & 0x0380) + (paletteIndex << 5 & 0x0040) //G + + (paletteColor << 7 & 0x6000) + (paletteIndex << 10 & 0x1000); //B +} + +auto PPU::Line::plotAbove(uint x, uint8 source, uint8 priority, uint16 color) -> void { + if(ppu.hd()) return plotHD(above, x, source, priority, color, false, false); + if(priority > above[x].priority) above[x] = {source, priority, color}; +} + +auto PPU::Line::plotBelow(uint x, uint8 source, uint8 priority, uint16 color) -> void { + if(ppu.hd()) return plotHD(below, x, source, priority, color, false, false); + if(priority > below[x].priority) below[x] = {source, priority, color}; +} + +//todo: name these variables more clearly ... +auto PPU::Line::plotHD(Pixel* pixel, uint x, uint8 source, uint8 priority, uint16 color, bool hires, bool subpixel) -> void { + auto scale = ppu.hdScale(); + int xss = hires && subpixel ? scale / 2 : 0; + int ys = ppu.interlace() && field() ? scale / 2 : 0; + if(priority > pixel[x * scale + xss + ys * 256 * scale].priority) { + Pixel p = {source, priority, color}; + int xsm = hires && !subpixel ? scale / 2 : scale; + int ysm = ppu.interlace() && !field() ? scale / 2 : scale; + for(int xs = xss; xs < xsm; xs++) { + pixel[x * scale + xs + ys * 256 * scale] = p; + } + int size = sizeof(Pixel) * (xsm - xss); + Pixel* source = &pixel[x * scale + xss + ys * 256 * scale]; + for(int yst = ys + 1; yst < ysm; yst++) { + memcpy(&pixel[x * scale + xss + yst * 256 * scale], source, size); + } + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/mode7.cpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/mode7.cpp new file mode 100644 index 0000000000..01f9f9ab9f --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/mode7.cpp @@ -0,0 +1,69 @@ +auto PPU::Line::renderMode7(PPU::IO::Background& self, uint8 source) -> void { + //HD mode 7 support + if(!ppu.hdMosaic() || !self.mosaicEnable || io.mosaic.size == 1) { + if(ppu.hdScale() > 1) return renderMode7HD(self, source); + } + + int Y = this->y; + if(self.mosaicEnable) Y -= io.mosaic.size - io.mosaic.counter; + int y = !io.mode7.vflip ? Y : 255 - Y; + + int a = (int16)io.mode7.a; + int b = (int16)io.mode7.b; + int c = (int16)io.mode7.c; + int d = (int16)io.mode7.d; + int hcenter = (int13)io.mode7.x; + int vcenter = (int13)io.mode7.y; + int hoffset = (int13)io.mode7.hoffset; + int voffset = (int13)io.mode7.voffset; + + uint mosaicCounter = 1; + uint mosaicPalette = 0; + uint8 mosaicPriority = 0; + uint16 mosaicColor = 0; + + auto clip = [](int n) -> int { return n & 0x2000 ? (n | ~1023) : (n & 1023); }; + int originX = (a * clip(hoffset - hcenter) & ~63) + (b * clip(voffset - vcenter) & ~63) + (b * y & ~63) + (hcenter << 8); + int originY = (c * clip(hoffset - hcenter) & ~63) + (d * clip(voffset - vcenter) & ~63) + (d * y & ~63) + (vcenter << 8); + + bool windowAbove[256]; + bool windowBelow[256]; + renderWindow(self.window, self.window.aboveEnable, windowAbove); + renderWindow(self.window, self.window.belowEnable, windowBelow); + + for(int X : range(256)) { + int x = !io.mode7.hflip ? X : 255 - X; + int pixelX = originX + a * x >> 8; + int pixelY = originY + c * x >> 8; + int tileX = pixelX >> 3 & 127; + int tileY = pixelY >> 3 & 127; + bool outOfBounds = (pixelX | pixelY) & ~1023; + uint15 tileAddress = tileY * 128 + tileX; + uint15 paletteAddress = ((pixelY & 7) << 3) + (pixelX & 7); + uint8 tile = io.mode7.repeat == 3 && outOfBounds ? 0 : ppu.vram[tileAddress] >> 0; + uint8 palette = io.mode7.repeat == 2 && outOfBounds ? 0 : ppu.vram[tile << 6 | paletteAddress] >> 8; + + uint8 priority; + if(source == Source::BG1) { + priority = self.priority_enabled[0] ? self.priority[0] : 0; + } else if(source == Source::BG2) { + priority = self.priority_enabled[palette >> 7] ? self.priority[palette >> 7] : 0; + palette &= 0x7f; + } + + if(--mosaicCounter == 0) { + mosaicCounter = self.mosaicEnable ? io.mosaic.size : 1; + mosaicPalette = palette; + mosaicPriority = priority; + if(io.col.directColor && source == Source::BG1) { + mosaicColor = directColor(0, palette); + } else { + mosaicColor = cgram[palette]; + } + } + if(!mosaicPalette) continue; + + if(self.aboveEnable && !windowAbove[X]) plotAbove(X, source, mosaicPriority, mosaicColor); + if(self.belowEnable && !windowBelow[X]) plotBelow(X, source, mosaicPriority, mosaicColor); + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/mode7hd.cpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/mode7hd.cpp new file mode 100644 index 0000000000..7a453e0cf4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/mode7hd.cpp @@ -0,0 +1,253 @@ +//determine mode 7 line groups for perspective correction +auto PPU::Line::cacheMode7HD() -> void { + ppu.mode7LineGroups.count = 0; + if(ppu.hdPerspective()) { + #define isLineMode7(line) (line.io.bg1.tileMode == TileMode::Mode7 && !line.io.displayDisable && ( \ + (line.io.bg1.aboveEnable || line.io.bg1.belowEnable) \ + )) + bool state = false; + uint y; + //find the moe 7 groups + for(y = 0; y < Line::count; y++) { + if(state != isLineMode7(ppu.lines[Line::start + y])) { + state = !state; + if(state) { + ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count] = ppu.lines[Line::start + y].y; + } else { + ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] = ppu.lines[Line::start + y].y - 1; + //the lines at the edges of mode 7 groups may be erroneous, so start and end lines for interpolation are moved inside + int offset = (ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] - ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count]) / 8; + ppu.mode7LineGroups.startLerpLine[ppu.mode7LineGroups.count] = ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count] + offset; + ppu.mode7LineGroups.endLerpLine[ppu.mode7LineGroups.count] = ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] - offset; + ppu.mode7LineGroups.count++; + } + } + } + #undef isLineMode7 + if(state) { + //close the last group if necessary + ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] = ppu.lines[Line::start + y].y - 1; + int offset = (ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] - ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count]) / 8; + ppu.mode7LineGroups.startLerpLine[ppu.mode7LineGroups.count] = ppu.mode7LineGroups.startLine[ppu.mode7LineGroups.count] + offset; + ppu.mode7LineGroups.endLerpLine[ppu.mode7LineGroups.count] = ppu.mode7LineGroups.endLine[ppu.mode7LineGroups.count] - offset; + ppu.mode7LineGroups.count++; + } + + //detect groups that do not have perspective + for(int i : range(ppu.mode7LineGroups.count)) { + int a = -1, b = -1, c = -1, d = -1; //the mode 7 scale factors of the current line + int aPrev = -1, bPrev = -1, cPrev = -1, dPrev = -1; //the mode 7 scale factors of the previous line + bool aVar = false, bVar = false, cVar = false, dVar = false; //has a varying value been found for the factors? + bool aInc = false, bInc = false, cInc = false, dInc = false; //has the variation been an increase or decrease? + for(y = ppu.mode7LineGroups.startLerpLine[i]; y <= ppu.mode7LineGroups.endLerpLine[i]; y++) { + a = ((int)((int16)(ppu.lines[y].io.mode7.a))); + b = ((int)((int16)(ppu.lines[y].io.mode7.b))); + c = ((int)((int16)(ppu.lines[y].io.mode7.c))); + d = ((int)((int16)(ppu.lines[y].io.mode7.d))); + //has the value of 'a' changed compared to the last line? + //(and is the factor larger than zero, which happens sometimes and seems to be game-specific, mostly at the edges of the screen) + if(aPrev > 0 && a > 0 && a != aPrev) { + if(!aVar) { + //if there has been no variation yet, store that there is one and store if it is an increase or decrease + aVar = true; + aInc = a > aPrev; + } else if(aInc != a > aPrev) { + //if there has been an increase and now we have a decrease, or vice versa, set the interpolation lines to -1 + //to deactivate perspective correction for this group and stop analyzing it further + ppu.mode7LineGroups.startLerpLine[i] = -1; + ppu.mode7LineGroups.endLerpLine[i] = -1; + break; + } + } + if(bPrev > 0 && b > 0 && b != bPrev) { + if(!bVar) { + bVar = true; + bInc = b > bPrev; + } else if(bInc != b > bPrev) { + ppu.mode7LineGroups.startLerpLine[i] = -1; + ppu.mode7LineGroups.endLerpLine[i] = -1; + break; + } + } + if(cPrev > 0 && c > 0 && c != cPrev) { + if(!cVar) { + cVar = true; + cInc = c > cPrev; + } else if(cInc != c > cPrev) { + ppu.mode7LineGroups.startLerpLine[i] = -1; + ppu.mode7LineGroups.endLerpLine[i] = -1; + break; + } + } + if(dPrev > 0 && d > 0 && d != bPrev) { + if(!dVar) { + dVar = true; + dInc = d > dPrev; + } else if(dInc != d > dPrev) { + ppu.mode7LineGroups.startLerpLine[i] = -1; + ppu.mode7LineGroups.endLerpLine[i] = -1; + break; + } + } + aPrev = a, bPrev = b, cPrev = c, dPrev = d; + } + } + } +} + +auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint8 source) -> void { + const bool extbg = source == Source::BG2; + const uint scale = ppu.hdScale(); + + Pixel pixel; + Pixel* above = &this->above[-1]; + Pixel* below = &this->below[-1]; + + //find the first and last scanline for interpolation + int y_a = -1; + int y_b = -1; + #define isLineMode7(n) (ppu.lines[n].io.bg1.tileMode == TileMode::Mode7 && !ppu.lines[n].io.displayDisable && ( \ + (ppu.lines[n].io.bg1.aboveEnable || ppu.lines[n].io.bg1.belowEnable) \ + )) + if(ppu.hdPerspective()) { + //find the mode 7 line group this line is in and use its interpolation lines + for(int i : range(ppu.mode7LineGroups.count)) { + if(y >= ppu.mode7LineGroups.startLine[i] && y <= ppu.mode7LineGroups.endLine[i]) { + y_a = ppu.mode7LineGroups.startLerpLine[i]; + y_b = ppu.mode7LineGroups.endLerpLine[i]; + break; + } + } + } + if(y_a == -1 || y_b == -1) { + //if perspective correction is disabled or the group was detected as non-perspective, use the neighboring lines + y_a = y; + y_b = y; + if(y_a > 1 && isLineMode7(y_a)) y_a--; + if(y_b < 239 && isLineMode7(y_b)) y_b++; + } + #undef isLineMode7 + + Line line_a = ppu.lines[y_a]; + float a_a = (int16)line_a.io.mode7.a; + float b_a = (int16)line_a.io.mode7.b; + float c_a = (int16)line_a.io.mode7.c; + float d_a = (int16)line_a.io.mode7.d; + + Line line_b = ppu.lines[y_b]; + float a_b = (int16)line_b.io.mode7.a; + float b_b = (int16)line_b.io.mode7.b; + float c_b = (int16)line_b.io.mode7.c; + float d_b = (int16)line_b.io.mode7.d; + + int hcenter = (int13)io.mode7.x; + int vcenter = (int13)io.mode7.y; + int hoffset = (int13)io.mode7.hoffset; + int voffset = (int13)io.mode7.voffset; + + if(io.mode7.vflip) { + y_a = 255 - y_a; + y_b = 255 - y_b; + } + + bool windowAbove[256]; + bool windowBelow[256]; + renderWindow(self.window, self.window.aboveEnable, windowAbove); + renderWindow(self.window, self.window.belowEnable, windowBelow); + + int pixelYp = INT_MIN; + for(int ys : range(scale)) { + float yf = y + ys * 1.0 / scale - 0.5; + if(io.mode7.vflip) yf = 255 - yf; + + float a = 1.0 / lerp(y_a, 1.0 / a_a, y_b, 1.0 / a_b, yf); + float b = 1.0 / lerp(y_a, 1.0 / b_a, y_b, 1.0 / b_b, yf); + float c = 1.0 / lerp(y_a, 1.0 / c_a, y_b, 1.0 / c_b, yf); + float d = 1.0 / lerp(y_a, 1.0 / d_a, y_b, 1.0 / d_b, yf); + + int ht = (hoffset - hcenter) % 1024; + float vty = ((voffset - vcenter) % 1024) + yf; + float originX = (a * ht) + (b * vty) + (hcenter << 8); + float originY = (c * ht) + (d * vty) + (vcenter << 8); + + int pixelXp = INT_MIN; + for(int x : range(256)) { + bool doAbove = self.aboveEnable && !windowAbove[x]; + bool doBelow = self.belowEnable && !windowBelow[x]; + + for(int xs : range(scale)) { + float xf = x + xs * 1.0 / scale - 0.5; + if(io.mode7.hflip) xf = 255 - xf; + + int pixelX = (originX + a * xf) / 256; + int pixelY = (originY + c * xf) / 256; + + above++; + below++; + + //only compute color again when coordinates have changed + if(pixelX != pixelXp || pixelY != pixelYp) { + uint tile = io.mode7.repeat == 3 && ((pixelX | pixelY) & ~1023) ? 0 : (ppu.vram[(pixelY >> 3 & 127) * 128 + (pixelX >> 3 & 127)] & 0xff); + uint palette = io.mode7.repeat == 2 && ((pixelX | pixelY) & ~1023) ? 0 : (ppu.vram[(((pixelY & 7) << 3) + (pixelX & 7)) + (tile << 6)] >> 8); + + uint8 priority; + if(!extbg) { + priority = self.priority_enabled[0] ? self.priority[0] : 0; + } else { + priority = self.priority_enabled[palette >> 7] ? self.priority[palette >> 7] : 0; + palette &= 0x7f; + } + if(!palette) continue; + + uint16 color; + if(io.col.directColor && !extbg) { + color = directColor(0, palette); + } else { + color = cgram[palette]; + } + + pixel = {source, priority, color}; + pixelXp = pixelX; + pixelYp = pixelY; + } + + if(doAbove && (!extbg || pixel.priority > above->priority)) *above = pixel; + if(doBelow && (!extbg || pixel.priority > below->priority)) *below = pixel; + } + } + } + + if(ppu.ss()) { + uint divisor = scale * scale; + for(uint p : range(256)) { + uint ab = 0, bb = 0; + uint ag = 0, bg = 0; + uint ar = 0, br = 0; + for(uint y : range(scale)) { + auto above = &this->above[p * scale]; + auto below = &this->below[p * scale]; + for(uint x : range(scale)) { + uint a = above[x].color; + uint b = below[x].color; + ab += a >> 0 & 31; + ag += a >> 5 & 31; + ar += a >> 10 & 31; + bb += b >> 0 & 31; + bg += b >> 5 & 31; + br += b >> 10 & 31; + } + } + uint16 aboveColor = ab / divisor << 0 | ag / divisor << 5 | ar / divisor << 10; + uint16 belowColor = bb / divisor << 0 | bg / divisor << 5 | br / divisor << 10; + this->above[p] = {source, this->above[p * scale].priority, aboveColor}; + this->below[p] = {source, this->below[p * scale].priority, belowColor}; + } + } +} + +//interpolation and extrapolation +auto PPU::Line::lerp(float pa, float va, float pb, float vb, float pr) -> float { + if(va == vb || pr == pa) return va; + if(pr == pb) return vb; + return va + (vb - va) / (pb - pa) * (pr - pa); +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/object.cpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/object.cpp new file mode 100644 index 0000000000..49bea69d1c --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/object.cpp @@ -0,0 +1,192 @@ +auto PPU::Line::renderObject(PPU::IO::Object& self) -> void { + if(!self.aboveEnable && !self.belowEnable) return; + + bool windowAbove[256]; + bool windowBelow[256]; + renderWindow(self.window, self.window.aboveEnable, windowAbove); + renderWindow(self.window, self.window.belowEnable, windowBelow); + + uint itemCount = 0; + uint tileCount = 0; + for(uint n : range(ppu.ItemLimit)) items[n].valid = false; + for(uint n : range(ppu.TileLimit)) tiles[n].valid = false; + + for(uint n : range(128)) { + ObjectItem item{true, uint8_t(self.first + n & 127)}; + const auto& object = ppu.objects[item.index]; + + if(object.size == 0) { + static const uint widths[] = { 8, 8, 8, 16, 16, 32, 16, 16}; + static const uint heights[] = { 8, 8, 8, 16, 16, 32, 32, 32}; + item.width = widths [self.baseSize]; + item.height = heights[self.baseSize]; + if(self.interlace && self.baseSize >= 6) item.height = 16; //hardware quirk + } else { + static const uint widths[] = {16, 32, 64, 32, 64, 64, 32, 32}; + static const uint heights[] = {16, 32, 64, 32, 64, 64, 64, 32}; + item.width = widths [self.baseSize]; + item.height = heights[self.baseSize]; + } + + if(object.x > 256 && object.x + item.width - 1 < 512) continue; + uint height = item.height >> self.interlace; + if((y >= object.y && y < object.y + height) + || (object.y + height >= 256 && y < (object.y + height & 255)) + ) { + if(itemCount++ >= ppu.ItemLimit) break; + items[itemCount - 1] = item; + } + } + + for(int n : reverse(range(ppu.ItemLimit))) { + const auto& item = items[n]; + if(!item.valid) continue; + + const auto& object = ppu.objects[item.index]; + uint tileWidth = item.width >> 3; + int x = object.x; + int y = this->y - object.y & 0xff; + if(self.interlace) y <<= 1; + + if(object.vflip) { + if(item.width == item.height) { + y = item.height - 1 - y; + } else if(y < item.width) { + y = item.width - 1 - y; + } else { + y = item.width + (item.width - 1) - (y - item.width); + } + } + + if(self.interlace) { + y = !object.vflip ? y + field() : y - field(); + } + + x &= 511; + y &= 255; + + uint16 tiledataAddress = self.tiledataAddress; + if(object.nameselect) tiledataAddress += 1 + self.nameselect << 12; + uint16 characterX = (object.character & 15); + uint16 characterY = ((object.character >> 4) + (y >> 3) & 15) << 4; + + for(uint tileX : range(tileWidth)) { + uint objectX = x + (tileX << 3) & 511; + if(x != 256 && objectX >= 256 && objectX + 7 < 512) continue; + + ObjectTile tile{true}; + tile.x = objectX; + tile.y = y; + tile.priority = object.priority; + tile.palette = 128 + (object.palette << 4); + tile.hflip = object.hflip; + + uint mirrorX = !object.hflip ? tileX : tileWidth - 1 - tileX; + uint address = tiledataAddress + ((characterY + (characterX + mirrorX & 15)) << 4); + address = (address & 0x7ff0) + (y & 7); + tile.data = ppu.vram[address + 0] << 0; + tile.data |= ppu.vram[address + 8] << 16; + + if(tileCount++ >= ppu.TileLimit) break; + tiles[tileCount - 1] = tile; + } + } + + ppu.io.obj.rangeOver |= itemCount > ppu.ItemLimit; + ppu.io.obj.timeOver |= tileCount > ppu.TileLimit; + + uint8_t palette[256] = {}; + uint8_t priority[256] = {}; + + for(uint n : range(ppu.TileLimit)) { + auto& tile = tiles[n]; + if(!tile.valid) continue; + + uint tileX = tile.x; + for(uint x : range(8)) { + tileX &= 511; + if(tileX < 256) { + uint color, shift = tile.hflip ? x : 7 - x; + color = tile.data >> shift + 0 & 1; + color += tile.data >> shift + 7 & 2; + color += tile.data >> shift + 14 & 4; + color += tile.data >> shift + 21 & 8; + if(color) { + palette[tileX] = tile.palette + color; + priority[tileX] = self.priority_enabled[tile.priority] ? self.priority[tile.priority] : 0; + } + } + tileX++; + } + } + + for(uint x : range(256)) { + if(!priority[x]) continue; + uint8 source = palette[x] < 192 ? Source::OBJ1 : Source::OBJ2; + if(self.aboveEnable && !windowAbove[x]) plotAbove(x, source, priority[x], cgram[palette[x]]); + if(self.belowEnable && !windowBelow[x]) plotBelow(x, source, priority[x], cgram[palette[x]]); + } +} + +auto PPU::oamAddressReset() -> void { + io.oamAddress = io.oamBaseAddress; + oamSetFirstObject(); +} + +auto PPU::oamSetFirstObject() -> void { + io.obj.first = !io.oamPriority ? 0 : io.oamAddress >> 2 & 0x7f; +} + +auto PPU::readObject(uint10 address) -> uint8 { + if(!(address & 0x200)) { + uint n = address >> 2; //object# + address &= 3; + if(address == 0) return objects[n].x; + if(address == 1) return objects[n].y - 1; //-1 => rendering happens one scanline late + if(address == 2) return objects[n].character; + return ( + objects[n].nameselect << 0 + | objects[n].palette << 1 + | objects[n].priority << 4 + | objects[n].hflip << 6 + | objects[n].vflip << 7 + ); + } else { + uint n = (address & 0x1f) << 2; //object# + return ( + objects[n + 0].x >> 8 << 0 + | objects[n + 0].size << 1 + | objects[n + 1].x >> 8 << 2 + | objects[n + 1].size << 3 + | objects[n + 2].x >> 8 << 4 + | objects[n + 2].size << 5 + | objects[n + 3].x >> 8 << 6 + | objects[n + 3].size << 7 + ); + } +} + +auto PPU::writeObject(uint10 address, uint8 data) -> void { + if(!(address & 0x200)) { + uint n = address >> 2; //object# + address &= 3; + if(address == 0) { objects[n].x = objects[n].x & 0x100 | data; return; } + if(address == 1) { objects[n].y = data + 1; return; } //+1 => rendering happens one scanline late + if(address == 2) { objects[n].character = data; return; } + objects[n].nameselect = data >> 0 & 1; + objects[n].palette = data >> 1 & 7; + objects[n].priority = data >> 4 & 3; + objects[n].hflip = data >> 6 & 1; + objects[n].vflip = data >> 7 & 1; + } else { + uint n = (address & 0x1f) << 2; //object# + objects[n + 0].x = objects[n + 0].x & 0xff | data << 8 & 0x100; + objects[n + 1].x = objects[n + 1].x & 0xff | data << 6 & 0x100; + objects[n + 2].x = objects[n + 2].x & 0xff | data << 4 & 0x100; + objects[n + 3].x = objects[n + 3].x & 0xff | data << 2 & 0x100; + objects[n + 0].size = data >> 1 & 1; + objects[n + 1].size = data >> 3 & 1; + objects[n + 2].size = data >> 5 & 1; + objects[n + 3].size = data >> 7 & 1; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/ppu.cpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/ppu.cpp new file mode 100644 index 0000000000..53d1212d78 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/ppu.cpp @@ -0,0 +1,206 @@ +#include + +namespace SuperFamicom { + +PPU& ppubase = ppu; + +#define PPU PPUfast +#define ppu ppufast + +PPU ppu; +#include "io.cpp" +#include "line.cpp" +#include "background.cpp" +#include "mode7.cpp" +#include "mode7hd.cpp" +#include "object.cpp" +#include "window.cpp" +#include "serialization.cpp" + +auto PPU::interlace() const -> bool { return ppubase.display.interlace; } +auto PPU::overscan() const -> bool { return ppubase.display.overscan; } +auto PPU::vdisp() const -> uint { return ppubase.display.vdisp; } +auto PPU::hires() const -> bool { return latch.hires; } +auto PPU::hd() const -> bool { return latch.hd; } +auto PPU::ss() const -> bool { return latch.ss; } +#undef ppu +auto PPU::hdScale() const -> uint { return configuration.hacks.ppu.mode7.scale; } +auto PPU::hdPerspective() const -> bool { return configuration.hacks.ppu.mode7.perspective; } +auto PPU::hdSupersample() const -> bool { return configuration.hacks.ppu.mode7.supersample; } +auto PPU::hdMosaic() const -> bool { return configuration.hacks.ppu.mode7.mosaic; } +auto PPU::deinterlace() const -> bool { return configuration.hacks.ppu.deinterlace; } +auto PPU::renderCycle() const -> uint { return configuration.hacks.ppu.renderCycle; } +auto PPU::noVRAMBlocking() const -> bool { return configuration.hacks.ppu.noVRAMBlocking; } +#define ppu ppufast + +PPU::PPU() { + output = new uint16_t[2304 * 2160](); + + for(uint l : range(16)) { + lightTable[l] = new uint16_t[32768]; + for(uint r : range(32)) { + for(uint g : range(32)) { + for(uint b : range(32)) { + double luma = (double)l / 15.0; + uint ar = (luma * r + 0.5); + uint ag = (luma * g + 0.5); + uint ab = (luma * b + 0.5); + lightTable[l][r << 10 | g << 5 | b << 0] = ab << 10 | ag << 5 | ar << 0; + } + } + } + } + + for(uint y : range(240)) { + lines[y].y = y; + } +} + +PPU::~PPU() { + delete[] output; + for(uint l : range(16)) delete[] lightTable[l]; +} + +auto PPU::synchronizeCPU() -> void { + if(ppubase.clock >= 0) scheduler.resume(cpu.thread); +} + +auto PPU::Enter() -> void { + while(true) { + scheduler.synchronize(); + ppu.main(); + } +} + +auto PPU::step(uint clocks) -> void { + tick(clocks); + ppubase.clock += clocks; + synchronizeCPU(); +} + +auto PPU::main() -> void { + scanline(); + + if(system.frameCounter == 0 && !system.runAhead && system.renderVideo) { + uint y = vcounter(); + if(y >= 1 && y <= 239) { + step(renderCycle()); + bool mosaicEnable = io.bg1.mosaicEnable || io.bg2.mosaicEnable || io.bg3.mosaicEnable || io.bg4.mosaicEnable; + if(y == 1) { + io.mosaic.counter = mosaicEnable ? io.mosaic.size + 1 : 0; + } + if(io.mosaic.counter && !--io.mosaic.counter) { + io.mosaic.counter = mosaicEnable ? io.mosaic.size + 0 : 0; + } + lines[y].cache(); + } + } + + step(hperiod() - hcounter()); +} + +auto PPU::scanline() -> void { + if(vcounter() == 0) { + if(latch.overscan && !io.overscan) { + //when disabling overscan, clear the overscan area that won't be rendered to: + for(uint y = 1; y <= 240; y++) { + if(y >= 8 && y <= 231) continue; + auto output = ppu.output + y * 1024; + memory::fill(output, 1024); + } + } + + ppubase.display.interlace = io.interlace; + ppubase.display.overscan = io.overscan; + latch.overscan = io.overscan; + latch.hires = false; + latch.hd = false; + latch.ss = false; + io.obj.timeOver = false; + io.obj.rangeOver = false; + } + + if(vcounter() > 0 && vcounter() < vdisp()) { + latch.hires |= io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; + //supersampling and EXTBG mode are not compatible, so disable supersampling in EXTBG mode + latch.hd |= io.bgMode == 7 && hdScale() > 1 && (hdSupersample() == 0 || io.extbg == 1); + latch.ss |= io.bgMode == 7 && hdScale() > 1 && (hdSupersample() == 1 && io.extbg == 0); + } + + if(vcounter() == vdisp()) { + if(!io.displayDisable) oamAddressReset(); + } + + if(vcounter() == 240) { + Line::flush(); + } +} + +auto PPU::refresh() -> void { + if(system.frameCounter == 0 && !system.runAhead && system.renderVideo) { + auto output = this->output; + uint pitch, width, height; + if(!hd()) { + pitch = 512 << !interlace(); + width = 256 << hires(); + height = 240 << interlace(); + } else { + pitch = 256 * hdScale(); + width = 256 * hdScale(); + height = 240 * hdScale(); + } + + //clear the areas of the screen that won't be rendered: + //previous video frames may have drawn data here that would now be stale otherwise. + if(!latch.overscan && pitch != frame.pitch && width != frame.width && height != frame.height) { + for(uint y : range(240)) { + if(y >= 8 && y <= 230) continue; //these scanlines are always rendered. + auto output = this->output + (!hd() ? (y * 1024 + (interlace() && field() ? 512 : 0)) : (y * 256 * hdScale() * hdScale())); + auto width = (!hd() ? (!hires() ? 256 : 512) : (256 * hdScale() * hdScale())); + memory::fill(output, width); + } + } + + if(auto device = controllerPort2.device) device->draw(output, pitch * sizeof(uint16), width, height); + platform->videoFrame(output, pitch * sizeof(uint16), width, height, hd() ? hdScale() : 1); + + frame.pitch = pitch; + frame.width = width; + frame.height = height; + } + if(system.frameCounter++ >= system.frameSkip) system.frameCounter = 0; +} + +auto PPU::load() -> bool { + return true; +} + +auto PPU::power(bool reset) -> void { + PPUcounter::reset(); + memory::fill(output, 1024 * 960); + + function reader{&PPU::readIO, this}; + function writer{&PPU::writeIO, this}; + bus.map(reader, writer, "00-3f,80-bf:2100-213f"); + + if(!reset) { + for(auto& word : vram) word = 0x0000; + for(auto& color : cgram) color = 0x0000; + for(auto& object : objects) object = {}; + } + + latch = {}; + io = {}; + updateVideoMode(); + + #undef ppu + ItemLimit = !configuration.hacks.ppu.noSpriteLimit ? 32 : 128; + TileLimit = !configuration.hacks.ppu.noSpriteLimit ? 34 : 128; + + Line::start = 0; + Line::count = 0; + + frame = {}; +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/ppu.hpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/ppu.hpp new file mode 100644 index 0000000000..3a076083cc --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/ppu.hpp @@ -0,0 +1,374 @@ +//performance-focused, scanline-based, parallelized implementation of PPU + +//limitations: +//* mid-scanline effects not support +//* vertical mosaic coordinates are not exact +//* (hardware-mod) 128KB VRAM mode not supported + +#define PPU PPUfast + +struct PPU : PPUcounter { + alwaysinline auto interlace() const -> bool; + alwaysinline auto overscan() const -> bool; + alwaysinline auto vdisp() const -> uint; + alwaysinline auto hires() const -> bool; + alwaysinline auto hd() const -> bool; + alwaysinline auto ss() const -> bool; + alwaysinline auto hdScale() const -> uint; + alwaysinline auto hdPerspective() const -> bool; + alwaysinline auto hdSupersample() const -> bool; + alwaysinline auto hdMosaic() const -> bool; + alwaysinline auto deinterlace() const -> bool; + alwaysinline auto renderCycle() const -> uint; + alwaysinline auto noVRAMBlocking() const -> bool; + + //ppu.cpp + PPU(); + ~PPU(); + + auto synchronizeCPU() -> void; + static auto Enter() -> void; + alwaysinline auto step(uint clocks) -> void; + auto main() -> void; + auto scanline() -> void; + auto refresh() -> void; + auto load() -> bool; + auto power(bool reset) -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + +public: + struct Source { enum : uint8 { BG1, BG2, BG3, BG4, OBJ1, OBJ2, COL }; }; + struct TileMode { enum : uint8 { BPP2, BPP4, BPP8, Mode7, Inactive }; }; + struct ScreenMode { enum : uint8 { Above, Below }; }; + + struct Latch { + //serialization.cpp + auto serialize(serializer&) -> void; + + bool interlace = 0; + bool overscan = 0; + bool hires = 0; + bool hd = 0; + bool ss = 0; + + uint16 vram = 0; + uint8 oam = 0; + uint8 cgram = 0; + + uint16 oamAddress = 0; + uint8 cgramAddress = 0; + + uint8 mode7 = 0; + bool counters = 0; + bool hcounter = 0; //hdot + bool vcounter = 0; + + struct PPUstate { + //serialization.cpp + auto serialize(serializer&) -> void; + + uint8 mdr = 0; + uint8 bgofs = 0; + } ppu1, ppu2; + }; + + struct IO { + //serialization.cpp + auto serialize(serializer&) -> void; + + bool displayDisable = 1; + uint8 displayBrightness = 0; + uint16 oamBaseAddress = 0; + uint16 oamAddress = 0; + bool oamPriority = 0; + bool bgPriority = 0; + uint8 bgMode = 0; + bool vramIncrementMode = 0; + uint8 vramMapping = 0; + uint8 vramIncrementSize = 0; + uint16 vramAddress = 0; + uint8 cgramAddress = 0; + bool cgramAddressLatch = 0; + uint16 hcounter = 0; //hdot + uint16 vcounter = 0; + bool interlace = 0; + bool overscan = 0; + bool pseudoHires = 0; + bool extbg = 0; + + struct Mosaic { + //serialization.cpp + auto serialize(serializer&) -> void; + + uint8 size = 1; + uint8 counter = 0; + } mosaic; + + struct Mode7 { + //serialization.cpp + auto serialize(serializer&) -> void; + + bool hflip = 0; + bool vflip = 0; + uint repeat = 0; + uint16 a = 0; + uint16 b = 0; + uint16 c = 0; + uint16 d = 0; + uint16 x = 0; + uint16 y = 0; + uint16 hoffset = 0; + uint16 voffset = 0; + } mode7; + + struct Window { + //serialization.cpp + auto serialize(serializer&) -> void; + + uint8 oneLeft = 0; + uint8 oneRight = 0; + uint8 twoLeft = 0; + uint8 twoRight = 0; + } window; + + struct WindowLayer { + //serialization.cpp + auto serialize(serializer&) -> void; + + bool oneEnable = 0; + bool oneInvert = 0; + bool twoEnable = 0; + bool twoInvert = 0; + uint mask = 0; + bool aboveEnable = 0; + bool belowEnable = 0; + }; + + struct WindowColor { + //serialization.cpp + auto serialize(serializer&) -> void; + + bool oneEnable = 0; + bool oneInvert = 0; + bool twoEnable = 0; + bool twoInvert = 0; + uint mask = 0; + uint aboveMask = 0; + uint belowMask = 0; + }; + + struct Background { + //serialization.cpp + auto serialize(serializer&) -> void; + + WindowLayer window; + + bool aboveEnable = 0; + bool belowEnable = 0; + bool mosaicEnable = 0; + uint16 tiledataAddress = 0; + uint16 screenAddress = 0; + uint8 screenSize = 0; + bool tileSize = 0; + uint16 hoffset = 0; + uint16 voffset = 0; + uint8 tileMode = 0; + uint8 priority[2] = {}; + + bool priority_enabled[2] = {true, true}; + } bg1, bg2, bg3, bg4; + + struct Object { + //serialization.cpp + auto serialize(serializer&) -> void; + + WindowLayer window; + + bool aboveEnable = 0; + bool belowEnable = 0; + bool interlace = 0; + uint8 baseSize = 0; + uint8 nameselect = 0; + uint16 tiledataAddress = 0; + uint8 first = 0; + bool rangeOver = 0; + bool timeOver = 0; + uint8 priority[4] = {}; + + bool priority_enabled[4] = {true, true, true, true}; + } obj; + + struct Color { + //serialization.cpp + auto serialize(serializer&) -> void; + + WindowColor window; + + bool enable[7] = {}; + bool directColor = 0; + bool blendMode = 0; //0 = fixed; 1 = pixel + bool halve = 0; + bool mathMode = 0; //0 = add; 1 = sub + uint16 fixedColor = 0; + } col; + }; + + struct Object { + //serialization.cpp + auto serialize(serializer&) -> void; + + uint16 x = 0; + uint8 y = 0; + uint8 character = 0; + bool nameselect = 0; + bool vflip = 0; + bool hflip = 0; + uint8 priority = 0; + uint8 palette = 0; + bool size = 0; + }; + + struct ObjectItem { + bool valid = 0; + uint8 index = 0; + uint8 width = 0; + uint8 height = 0; + }; + + struct ObjectTile { + bool valid = 0; + uint16 x = 0; + uint8 y = 0; + uint8 priority = 0; + uint8 palette = 0; + bool hflip = 0; + uint32 data = 0; + }; + + struct Pixel { + uint8 source = 0; + uint8 priority = 0; + uint16 color = 0; + }; + + //io.cpp + auto latchCounters(uint hcounter, uint vcounter) -> void; + auto latchCounters() -> void; + alwaysinline auto vramAddress() const -> uint; + alwaysinline auto readVRAM() -> uint16; + template alwaysinline auto writeVRAM(uint8 data) -> void; + alwaysinline auto readOAM(uint10 address) -> uint8; + alwaysinline auto writeOAM(uint10 address, uint8 data) -> void; + template alwaysinline auto readCGRAM(uint8 address) -> uint8; + alwaysinline auto writeCGRAM(uint8 address, uint15 data) -> void; + auto readIO(uint address, uint8 data) -> uint8; + auto writeIO(uint address, uint8 data) -> void; + auto updateVideoMode() -> void; + + //object.cpp + auto oamAddressReset() -> void; + auto oamSetFirstObject() -> void; + auto readObject(uint10 address) -> uint8; + auto writeObject(uint10 address, uint8 data) -> void; + +//serialized: + Latch latch; + IO io; + + uint16 vram[32 * 1024] = {}; + uint16 cgram[256] = {}; + Object objects[128] = {}; + + //[unserialized] + uint16* output = {}; + uint16* lightTable[16] = {}; + + uint ItemLimit = 0; + uint TileLimit = 0; + + struct Line { + //line.cpp + inline auto field() const -> bool { return fieldID; } + static auto flush() -> void; + auto cache() -> void; + auto render(bool field) -> void; + auto pixel(uint x, Pixel above, Pixel below) const -> uint16; + auto blend(uint x, uint y, bool halve) const -> uint16; + alwaysinline auto directColor(uint paletteIndex, uint paletteColor) const -> uint16; + alwaysinline auto plotAbove(uint x, uint8 source, uint8 priority, uint16 color) -> void; + alwaysinline auto plotBelow(uint x, uint8 source, uint8 priority, uint16 color) -> void; + alwaysinline auto plotHD(Pixel*, uint x, uint8 source, uint8 priority, uint16 color, bool hires, bool subpixel) -> void; + + //background.cpp + auto renderBackground(PPU::IO::Background&, uint8 source) -> void; + auto getTile(PPU::IO::Background&, uint hoffset, uint voffset) -> uint; + + //mode7.cpp + auto renderMode7(PPU::IO::Background&, uint8 source) -> void; + + //mode7hd.cpp + static auto cacheMode7HD() -> void; + auto renderMode7HD(PPU::IO::Background&, uint8 source) -> void; + alwaysinline auto lerp(float pa, float va, float pb, float vb, float pr) -> float; + + //mode7hd-avx2.cpp + auto renderMode7HD_AVX2( + PPU::IO::Background&, uint8 source, + Pixel* above, Pixel* below, + bool* windowAbove, bool* windowBelow, + float originX, float a, + float originY, float c + ) -> void; + + //object.cpp + auto renderObject(PPU::IO::Object&) -> void; + + //window.cpp + auto renderWindow(PPU::IO::WindowLayer&, bool enable, bool output[256]) -> void; + auto renderWindow(PPU::IO::WindowColor&, uint mask, bool output[256]) -> void; + + //unserialized: + uint y; //constant + bool fieldID; + + IO io; + uint16 cgram[256]; + + ObjectItem items[128]; //32 on real hardware + ObjectTile tiles[128]; //34 on real hardware; 1024 max (128 * 64-width tiles) + + Pixel above[256 * 9 * 9]; + Pixel below[256 * 9 * 9]; + + bool windowAbove[256]; + bool windowBelow[256]; + + //flush() + static uint start; + static uint count; + }; + +//unserialized: + Line lines[240]; + + //used to help detect when the video output size changes between frames to clear overscan area. + struct Frame { + uint pitch = 0; + uint width = 0; + uint height = 0; + } frame; + + struct Mode7LineGroups { + int count = -1; + int startLine[32]; + int endLine[32]; + int startLerpLine[32]; + int endLerpLine[32]; + } mode7LineGroups; +}; + +extern PPU ppufast; + +#undef PPU diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/serialization.cpp new file mode 100644 index 0000000000..ff6b41711f --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/serialization.cpp @@ -0,0 +1,166 @@ +auto PPU::serialize(serializer& s) -> void { + ppubase.Thread::serialize(s); + PPUcounter::serialize(s); + + latch.serialize(s); + io.serialize(s); + s.array(vram); + s.array(cgram); + for(auto& object : objects) object.serialize(s); + + Line::start = 0; + Line::count = 0; +} + +auto PPU::Latch::serialize(serializer& s) -> void { + s.integer(interlace); + s.integer(overscan); + s.integer(hires); + s.integer(hd); + s.integer(ss); + s.integer(vram); + s.integer(oam); + s.integer(cgram); + s.integer(oamAddress); + s.integer(cgramAddress); + s.integer(mode7); + s.integer(counters); + s.integer(hcounter); + s.integer(vcounter); + ppu1.serialize(s); + ppu2.serialize(s); +} + +auto PPU::Latch::PPUstate::serialize(serializer& s) -> void { + s.integer(mdr); + s.integer(bgofs); +} + +auto PPU::IO::serialize(serializer& s) -> void { + s.integer(displayDisable); + s.integer(displayBrightness); + s.integer(oamBaseAddress); + s.integer(oamAddress); + s.integer(oamPriority); + s.integer(bgPriority); + s.integer(bgMode); + s.integer(vramIncrementMode); + s.integer(vramMapping); + s.integer(vramIncrementSize); + s.integer(vramAddress); + s.integer(cgramAddress); + s.integer(cgramAddressLatch); + s.integer(hcounter); + s.integer(vcounter); + s.integer(interlace); + s.integer(overscan); + s.integer(pseudoHires); + s.integer(extbg); + + mosaic.serialize(s); + mode7.serialize(s); + window.serialize(s); + bg1.serialize(s); + bg2.serialize(s); + bg3.serialize(s); + bg4.serialize(s); + obj.serialize(s); + col.serialize(s); +} + +auto PPU::IO::Mosaic::serialize(serializer& s) -> void { + s.integer(size); + s.integer(counter); +} + +auto PPU::IO::Mode7::serialize(serializer& s) -> void { + s.integer(hflip); + s.integer(vflip); + s.integer(repeat); + s.integer(a); + s.integer(b); + s.integer(c); + s.integer(d); + s.integer(x); + s.integer(y); + s.integer(hoffset); + s.integer(voffset); +} + +auto PPU::IO::Window::serialize(serializer& s) -> void { + s.integer(oneLeft); + s.integer(oneRight); + s.integer(twoLeft); + s.integer(twoRight); +} + +auto PPU::IO::WindowLayer::serialize(serializer& s) -> void { + s.integer(oneEnable); + s.integer(oneInvert); + s.integer(twoEnable); + s.integer(twoInvert); + s.integer(mask); + s.integer(aboveEnable); + s.integer(belowEnable); +} + +auto PPU::IO::WindowColor::serialize(serializer& s) -> void { + s.integer(oneEnable); + s.integer(oneInvert); + s.integer(twoEnable); + s.integer(twoInvert); + s.integer(mask); + s.integer(aboveMask); + s.integer(belowMask); +} + +auto PPU::IO::Background::serialize(serializer& s) -> void { + window.serialize(s); + s.integer(aboveEnable); + s.integer(belowEnable); + s.integer(mosaicEnable); + s.integer(tiledataAddress); + s.integer(screenAddress); + s.integer(screenSize); + s.integer(tileSize); + s.integer(hoffset); + s.integer(voffset); + s.integer(tileMode); + s.array(priority); +} + +auto PPU::IO::Object::serialize(serializer& s) -> void { + window.serialize(s); + s.integer(aboveEnable); + s.integer(belowEnable); + s.integer(interlace); + s.integer(baseSize); + s.integer(nameselect); + s.integer(tiledataAddress); + s.integer(first); + s.integer(rangeOver); + s.integer(timeOver); + s.array(priority); +} + +auto PPU::IO::Color::serialize(serializer& s) -> void { + window.serialize(s); + s.array(enable); + s.integer(directColor); + s.integer(blendMode); + s.integer(halve); + s.integer(mathMode); + s.integer(fixedColor); +} + +auto PPU::Object::serialize(serializer& s) -> void { + s.integer(x); + s.integer(y); + s.integer(character); + s.integer(nameselect); + s.integer(vflip); + s.integer(hflip); + s.integer(priority); + s.integer(palette); + s.integer(size); +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu-fast/window.cpp b/waterbox/bsnescore/bsnes/sfc/ppu-fast/window.cpp new file mode 100644 index 0000000000..1794b99979 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu-fast/window.cpp @@ -0,0 +1,75 @@ +auto PPU::Line::renderWindow(PPU::IO::WindowLayer& self, bool enable, bool output[256]) -> void { + if(!enable || (!self.oneEnable && !self.twoEnable)) { + memory::fill(output, 256, 0); + return; + } + + if(self.oneEnable && !self.twoEnable) { + bool set = 1 ^ self.oneInvert, clear = !set; + for(uint x : range(256)) { + output[x] = x >= io.window.oneLeft && x <= io.window.oneRight ? set : clear; + } + return; + } + + if(self.twoEnable && !self.oneEnable) { + bool set = 1 ^ self.twoInvert, clear = !set; + for(uint x : range(256)) { + output[x] = x >= io.window.twoLeft && x <= io.window.twoRight ? set : clear; + } + return; + } + + for(uint x : range(256)) { + bool oneMask = (x >= io.window.oneLeft && x <= io.window.oneRight) ^ self.oneInvert; + bool twoMask = (x >= io.window.twoLeft && x <= io.window.twoRight) ^ self.twoInvert; + switch(self.mask) { + case 0: output[x] = (oneMask | twoMask) == 1; break; + case 1: output[x] = (oneMask & twoMask) == 1; break; + case 2: output[x] = (oneMask ^ twoMask) == 1; break; + case 3: output[x] = (oneMask ^ twoMask) == 0; break; + } + } +} + +auto PPU::Line::renderWindow(PPU::IO::WindowColor& self, uint mask, bool output[256]) -> void { + bool set, clear; + switch(mask) { + case 0: memory::fill(output, 256, 1); return; //always + case 1: set = 1, clear = 0; break; //inside + case 2: set = 0, clear = 1; break; //outside + case 3: memory::fill(output, 256, 0); return; //never + } + + if(!self.oneEnable && !self.twoEnable) { + memory::fill(output, 256, clear); + return; + } + + if(self.oneEnable && !self.twoEnable) { + if(self.oneInvert) set ^= 1, clear ^= 1; + for(uint x : range(256)) { + output[x] = x >= io.window.oneLeft && x <= io.window.oneRight ? set : clear; + } + return; + } + + if(self.twoEnable && !self.oneEnable) { + if(self.twoInvert) set ^= 1, clear ^= 1; + for(uint x : range(256)) { + output[x] = x >= io.window.twoLeft && x <= io.window.twoRight ? set : clear; + } + return; + } + + for(uint x : range(256)) { + bool oneMask = (x >= io.window.oneLeft && x <= io.window.oneRight) ^ self.oneInvert; + bool twoMask = (x >= io.window.twoLeft && x <= io.window.twoRight) ^ self.twoInvert; + switch(self.mask) { + case 0: output[x] = (oneMask | twoMask) == 1 ? set : clear; break; + case 1: output[x] = (oneMask & twoMask) == 1 ? set : clear; break; + case 2: output[x] = (oneMask ^ twoMask) == 1 ? set : clear; break; + case 3: output[x] = (oneMask ^ twoMask) == 0 ? set : clear; break; + } + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/background.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/background.cpp new file mode 100644 index 0000000000..e9d3dcb3b9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/background.cpp @@ -0,0 +1,232 @@ +#include "mode7.cpp" + +auto PPU::Background::hires() const -> bool { + return ppu.io.bgMode == 5 || ppu.io.bgMode == 6; +} + +//V = 0, H = 0 +auto PPU::Background::frame() -> void { +} + +//H = 0 +auto PPU::Background::scanline() -> void { + mosaic.hcounter = ppu.mosaic.size; + mosaic.hoffset = 0; + + renderingIndex = 0; + pixelCounter = (io.hoffset & 7) << hires(); + + opt.hoffset = 0; + opt.voffset = 0; +} + +//H = 56 +auto PPU::Background::begin() -> void { + //remove partial tile columns that have been scrolled offscreen + for(auto& data : tiles[0].data) data >>= pixelCounter << 1; +} + +auto PPU::Background::fetchNameTable() -> void { + if(ppu.vcounter() == 0) return; + + uint nameTableIndex = ppu.hcounter() >> 5 << hires(); + int x = (ppu.hcounter() & ~31) >> 2; + + uint hpixel = x << hires(); + uint vpixel = ppu.vcounter(); + uint hscroll = io.hoffset; + uint vscroll = io.voffset; + + if(hires()) { + hscroll <<= 1; + if(ppu.io.interlace) vpixel = vpixel << 1 | (ppu.field() && !mosaic.enable); + } + if(mosaic.enable) { + vpixel -= ppu.mosaic.voffset() << (hires() && ppu.io.interlace); + } + + bool repeated = false; + repeat: + + uint hoffset = hpixel + hscroll; + uint voffset = vpixel + vscroll; + + if(ppu.io.bgMode == 2 || ppu.io.bgMode == 4 || ppu.io.bgMode == 6) { + auto hlookup = ppu.bg3.opt.hoffset; + auto vlookup = ppu.bg3.opt.voffset; + uint valid = 1 << 13 + id; + + if(ppu.io.bgMode == 4) { + if(hlookup & valid) { + if(!(hlookup & 0x8000)) { + hoffset = hpixel + (hlookup & ~7) + (hscroll & 7); + } else { + voffset = vpixel + (vlookup); + } + } + } else { + if(hlookup & valid) hoffset = hpixel + (hlookup & ~7) + (hscroll & 7); + if(vlookup & valid) voffset = vpixel + (vlookup); + } + } + + uint width = 256 << hires(); + uint hsize = width << io.tileSize << io.screenSize.bit(0); + uint vsize = width << io.tileSize << io.screenSize.bit(1); + + hoffset &= hsize - 1; + voffset &= vsize - 1; + + uint vtiles = 3 + io.tileSize; + uint htiles = !hires() ? vtiles : 4; + + uint htile = hoffset >> htiles; + uint vtile = voffset >> vtiles; + + uint hscreen = io.screenSize.bit(0) ? 32 << 5 : 0; + uint vscreen = io.screenSize.bit(1) ? 32 << 5 + io.screenSize.bit(0) : 0; + + uint16 offset = (uint5)htile << 0 | (uint5)vtile << 5; + if(htile & 0x20) offset += hscreen; + if(vtile & 0x20) offset += vscreen; + + uint16 address = io.screenAddress + offset; + uint16 attributes = ppu.vram[address]; + + auto& tile = tiles[nameTableIndex]; + tile.character = attributes & 0x03ff; + tile.paletteGroup = attributes >> 10 & 7; + tile.priority = io.priority[attributes >> 13 & 1]; + tile.hmirror = bool(attributes & 0x4000); + tile.vmirror = bool(attributes & 0x8000); + + if(htiles == 4 && bool(hoffset & 8) != tile.hmirror) tile.character += 1; + if(vtiles == 4 && bool(voffset & 8) != tile.vmirror) tile.character += 16; + + uint characterMask = ppu.vram.mask >> 3 + io.mode; + uint characterIndex = io.tiledataAddress >> 3 + io.mode; + uint16 origin = tile.character + characterIndex & characterMask; + + if(tile.vmirror) voffset ^= 7; + tile.address = (origin << 3 + io.mode) + (voffset & 7); + + uint paletteOffset = ppu.io.bgMode == 0 ? id << 5 : 0; + uint paletteSize = 2 << io.mode; + tile.palette = paletteOffset + (tile.paletteGroup << paletteSize); + + nameTableIndex++; + if(hires() && !repeated) { + repeated = true; + hpixel += 8; + goto repeat; + } +} + +auto PPU::Background::fetchOffset(uint y) -> void { + if(ppu.vcounter() == 0) return; + + uint characterIndex = ppu.hcounter() >> 5 << hires(); + uint x = characterIndex << 3; + + uint hoffset = x + (io.hoffset & ~7); + uint voffset = y + (io.voffset); + + uint vtiles = 3 + io.tileSize; + uint htiles = !hires() ? vtiles : 4; + + uint htile = hoffset >> htiles; + uint vtile = voffset >> vtiles; + + uint hscreen = io.screenSize.bit(0) ? 32 << 5 : 0; + uint vscreen = io.screenSize.bit(1) ? 32 << 5 + io.screenSize.bit(0) : 0; + + uint16 offset = (uint5)htile << 0 | (uint5)vtile << 5; + if(htile & 0x20) offset += hscreen; + if(vtile & 0x20) offset += vscreen; + + uint16 address = io.screenAddress + offset; + if(y == 0) opt.hoffset = ppu.vram[address]; + if(y == 8) opt.voffset = ppu.vram[address]; +} + +auto PPU::Background::fetchCharacter(uint index, bool half) -> void { + if(ppu.vcounter() == 0) return; + + uint characterIndex = (ppu.hcounter() >> 5 << hires()) + half; + + auto& tile = tiles[characterIndex]; + uint16 data = ppu.vram[tile.address + (index << 3)]; + + //reverse bits so that the lowest bit is the left-most pixel + if(!tile.hmirror) { + data = data >> 4 & 0x0f0f | data << 4 & 0xf0f0; + data = data >> 2 & 0x3333 | data << 2 & 0xcccc; + data = data >> 1 & 0x5555 | data << 1 & 0xaaaa; + } + + //interleave two bitplanes for faster planar decoding later + tile.data[index] = ( + ((uint8(data >> 0) * 0x0101010101010101ull & 0x8040201008040201ull) * 0x0102040810204081ull >> 49) & 0x5555 + | ((uint8(data >> 8) * 0x0101010101010101ull & 0x8040201008040201ull) * 0x0102040810204081ull >> 48) & 0xaaaa + ); +} + +auto PPU::Background::run(bool screen) -> void { + if(ppu.vcounter() == 0) return; + + if(screen == Screen::Below) { + output.above.priority = 0; + output.below.priority = 0; + if(!hires()) return; + } + + if(io.mode == Mode::Mode7) return runMode7(); + + auto& tile = tiles[renderingIndex]; + uint8 color = 0; + if(io.mode >= Mode::BPP2) color |= (tile.data[0] & 3) << 0; tile.data[0] >>= 2; + if(io.mode >= Mode::BPP4) color |= (tile.data[1] & 3) << 2; tile.data[1] >>= 2; + if(io.mode >= Mode::BPP8) color |= (tile.data[2] & 3) << 4; tile.data[2] >>= 2; + if(io.mode >= Mode::BPP8) color |= (tile.data[3] & 3) << 6; tile.data[3] >>= 2; + + Pixel pixel; + pixel.priority = tile.priority; + pixel.palette = color ? uint(tile.palette + color) : 0; + pixel.paletteGroup = tile.paletteGroup; + if(++pixelCounter == 0) renderingIndex++; + + uint x = ppu.hcounter() - 56 >> 2; + + if(x == 0 && (!hires() || screen == Screen::Below)) { + mosaic.hcounter = ppu.mosaic.size; + mosaic.pixel = pixel; + } else if((!hires() || screen == Screen::Below) && --mosaic.hcounter == 0) { + mosaic.hcounter = ppu.mosaic.size; + mosaic.pixel = pixel; + } else if(mosaic.enable) { + pixel = mosaic.pixel; + } + if(screen == Screen::Above) x++; + if(pixel.palette == 0) return; + + if(!hires() || screen == Screen::Above) if(io.aboveEnable) output.above = pixel; + if(!hires() || screen == Screen::Below) if(io.belowEnable) output.below = pixel; +} + +auto PPU::Background::power() -> void { + io = {}; + io.tiledataAddress = (random() & 0x0f) << 12; + io.screenAddress = (random() & 0xfc) << 8; + io.screenSize = random(); + io.tileSize = random(); + io.aboveEnable = random(); + io.belowEnable = random(); + io.hoffset = random(); + io.voffset = random(); + + output.above = {}; + output.below = {}; + + mosaic = {}; + mosaic.enable = random(); +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/background.hpp b/waterbox/bsnescore/bsnes/sfc/ppu/background.hpp new file mode 100644 index 0000000000..5df47dc4da --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/background.hpp @@ -0,0 +1,85 @@ +struct Background { + Background(uint id) : id(id) {} + + alwaysinline auto hires() const -> bool; + + //background.cpp + auto frame() -> void; + auto scanline() -> void; + auto begin() -> void; + auto fetchNameTable() -> void; + auto fetchOffset(uint y) -> void; + auto fetchCharacter(uint index, bool half = 0) -> void; + auto run(bool screen) -> void; + auto power() -> void; + + //mode7.cpp + alwaysinline auto clip(int n) -> int; + auto runMode7() -> void; + + auto serialize(serializer&) -> void; + + struct ID { enum : uint { BG1, BG2, BG3, BG4 }; }; + const uint id; + + struct Mode { enum : uint { BPP2, BPP4, BPP8, Mode7, Inactive }; }; + struct ScreenSize { enum : uint { Size32x32, Size32x64, Size64x32, Size64x64 }; }; + struct TileSize { enum : uint { Size8x8, Size16x16 }; }; + struct Screen { enum : uint { Above, Below }; }; + + struct IO { + uint16 tiledataAddress; + uint16 screenAddress; + uint2 screenSize; + uint1 tileSize; + + uint8 mode; + uint8 priority[2]; + + uint1 aboveEnable; + uint1 belowEnable; + + uint16 hoffset; + uint16 voffset; + } io; + + struct Pixel { + uint8 priority; //0 = none (transparent) + uint8 palette; + uint3 paletteGroup; + } above, below; + + struct Output { + Pixel above; + Pixel below; + } output; + + struct Mosaic { + uint1 enable; + uint16 hcounter; + uint16 hoffset; + Pixel pixel; + } mosaic; + + struct OffsetPerTile { + //set in BG3 only; used by BG1 and BG2 + uint16 hoffset; + uint16 voffset; + } opt; + + struct Tile { + uint16 address; + uint10 character; + uint8 palette; + uint3 paletteGroup; + uint8 priority; + uint1 hmirror; + uint1 vmirror; + uint16 data[4]; + } tiles[66]; + + uint7 renderingIndex; + uint3 pixelCounter; + + friend class PPU; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/counter/counter-inline.hpp b/waterbox/bsnescore/bsnes/sfc/ppu/counter/counter-inline.hpp new file mode 100644 index 0000000000..687a7fad88 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/counter/counter-inline.hpp @@ -0,0 +1,84 @@ +auto PPUcounter::tick() -> void { + time.hcounter += 2; //increment by smallest unit of time. + if(time.hcounter == hperiod()) { + last.hperiod = hperiod(); + time.hcounter = 0; + tickScanline(); + } +} + +auto PPUcounter::tick(uint clocks) -> void { + time.hcounter += clocks; + if(time.hcounter >= hperiod()) { + last.hperiod = hperiod(); + time.hcounter -= hperiod(); + tickScanline(); + } +} + +auto PPUcounter::tickScanline() -> void { + if(++time.vcounter == 128) { + //it's not important when this is captured: it is only needed at V=240 or V=311. + time.interlace = ppu.interlace(); + time.vperiod += interlace() && !field(); + } + + if(vcounter() == vperiod()) { + last.vperiod = vperiod(); + //this may be off by one until V=128, hence why vperiod() is a private function. + time.vperiod = Region::NTSC() ? 262 : 312; + time.vcounter = 0; + time.field ^= 1; + } + + time.hperiod = 1364; + //NTSC and PAL scanline rates would not match up with color clocks if every scanline were 1364 clocks. + //to offset for this error, NTSC has one short scanline, and PAL has one long scanline. + if(Region::NTSC() && interlace() == 0 && field() == 1 && vcounter() == 240) time.hperiod -= 4; + if(Region::PAL() && interlace() == 1 && field() == 1 && vcounter() == 311) time.hperiod += 4; + if(scanline) scanline(); +} + +auto PPUcounter::interlace() const -> bool { return time.interlace; } +auto PPUcounter::field() const -> bool { return time.field; } +auto PPUcounter::vcounter() const -> uint { return time.vcounter; } +auto PPUcounter::hcounter() const -> uint { return time.hcounter; } +auto PPUcounter::vperiod() const -> uint { return time.vperiod; } +auto PPUcounter::hperiod() const -> uint { return time.hperiod; } + +auto PPUcounter::vcounter(uint offset) const -> uint { + if(offset <= hcounter()) return vcounter(); + if(vcounter() > 0) return vcounter() - 1; + return last.vperiod - 1; +} + +auto PPUcounter::hcounter(uint offset) const -> uint { + if(offset <= hcounter()) return hcounter() - offset; + return hcounter() + last.hperiod - offset; +} + +//one PPU dot = 4 CPU clocks. +// +//PPU dots 323 and 327 are 6 CPU clocks long. +//this does not apply to NTSC non-interlace scanline 240 on odd fields. this is +//because the PPU skips one dot to alter the color burst phase of the video signal. +//it is not known what happens for PAL 1368 clock scanlines. +// +//dot 323 range = {1292, 1294, 1296} +//dot 327 range = {1310, 1312, 1314} + +auto PPUcounter::hdot() const -> uint { + if(hperiod() == 1360) { + return hcounter() >> 2; + } else { + return hcounter() - ((hcounter() > 1292) << 1) - ((hcounter() > 1310) << 1) >> 2; + } +} + +auto PPUcounter::reset() -> void { + time = {}; + last = {}; + + time.vperiod = last.vperiod = Region::NTSC() ? 262 : 312; + time.hperiod = last.hperiod = 1364; +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/counter/counter.hpp b/waterbox/bsnescore/bsnes/sfc/ppu/counter/counter.hpp new file mode 100644 index 0000000000..d4e5371031 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/counter/counter.hpp @@ -0,0 +1,48 @@ +//PPUcounter emulates the H/V latch counters of the S-PPU2. +// +//real hardware has the S-CPU maintain its own copy of these counters that are +//updated based on the state of the S-PPU Vblank and Hblank pins. emulating this +//would require full lock-step synchronization for every clock tick. +//to bypass this and allow the two to run out-of-order, both the CPU and PPU +//classes inherit PPUcounter and keep their own counters. +//the timers are kept in sync, as the only differences occur on V=240 and V=261, +//based on interlace. thus, we need only synchronize and fetch interlace at any +//point before this in the frame, which is handled internally by this class at +//V=128. + +struct PPUcounter { + alwaysinline auto tick() -> void; + alwaysinline auto tick(uint clocks) -> void; private: + alwaysinline auto tickScanline() -> void; public: + + alwaysinline auto interlace() const -> bool; + alwaysinline auto field() const -> bool; + alwaysinline auto vcounter() const -> uint; + alwaysinline auto hcounter() const -> uint; + alwaysinline auto hdot() const -> uint; private: + alwaysinline auto vperiod() const -> uint; public: + alwaysinline auto hperiod() const -> uint; + + alwaysinline auto vcounter(uint offset) const -> uint; + alwaysinline auto hcounter(uint offset) const -> uint; + + inline auto reset() -> void; + auto serialize(serializer&) -> void; + + function scanline; + +private: + struct { + bool interlace = 0; + bool field = 0; + uint vperiod = 0; + uint hperiod = 0; + uint vcounter = 0; + uint hcounter = 0; + } time; + + struct { + uint vperiod = 0; + uint hperiod = 0; + } last; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/counter/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/counter/serialization.cpp new file mode 100644 index 0000000000..aa798afd79 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/counter/serialization.cpp @@ -0,0 +1,11 @@ +auto PPUcounter::serialize(serializer& s) -> void { + s.integer(time.interlace); + s.integer(time.field); + s.integer(time.vperiod); + s.integer(time.hperiod); + s.integer(time.vcounter); + s.integer(time.hcounter); + + s.integer(last.vperiod); + s.integer(last.hperiod); +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/io.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/io.cpp new file mode 100644 index 0000000000..745b333107 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/io.cpp @@ -0,0 +1,755 @@ +auto PPU::latchCounters(uint hcounter, uint vcounter) -> void { + if(system.fastPPU()) { + return ppufast.latchCounters(hcounter, vcounter); + } + + io.hcounter = hcounter; + io.vcounter = vcounter; + latch.counters = 1; +} + +auto PPU::latchCounters() -> void { + if(system.fastPPU()) { + return ppufast.latchCounters(); + } + + cpu.synchronizePPU(); + io.hcounter = hdot(); + io.vcounter = vcounter(); + latch.counters = 1; +} + +auto PPU::addressVRAM() const -> uint16 { + uint16 address = io.vramAddress; + switch(io.vramMapping) { + case 0: return address; + case 1: return address & 0xff00 | address << 3 & 0x00f8 | address >> 5 & 7; + case 2: return address & 0xfe00 | address << 3 & 0x01f8 | address >> 6 & 7; + case 3: return address & 0xfc00 | address << 3 & 0x03f8 | address >> 7 & 7; + } + unreachable; +} + +auto PPU::readVRAM() -> uint16 { + if(!io.displayDisable && vcounter() < vdisp()) return 0x0000; + auto address = addressVRAM(); + return vram[address]; +} + +auto PPU::writeVRAM(bool byte, uint8 data) -> void { + if(!io.displayDisable && vcounter() < vdisp()) return; + auto address = addressVRAM(); + if(byte == 0) vram[address] = vram[address] & 0xff00 | data << 0; + if(byte == 1) vram[address] = vram[address] & 0x00ff | data << 8; +} + +auto PPU::readOAM(uint10 addr) -> uint8 { + if(!io.displayDisable && vcounter() < vdisp()) addr = latch.oamAddress; + return obj.oam.read(addr); +} + +auto PPU::writeOAM(uint10 addr, uint8 data) -> void { + if(!io.displayDisable && vcounter() < vdisp()) addr = latch.oamAddress; + obj.oam.write(addr, data); +} + +auto PPU::readCGRAM(bool byte, uint8 addr) -> uint8 { + if(!io.displayDisable + && vcounter() > 0 && vcounter() < vdisp() + && hcounter() >= 88 && hcounter() < 1096 + ) addr = latch.cgramAddress; + return screen.cgram[addr].byte(byte); +} + +auto PPU::writeCGRAM(uint8 addr, uint15 data) -> void { + if(!io.displayDisable + && vcounter() > 0 && vcounter() < vdisp() + && hcounter() >= 88 && hcounter() < 1096 + ) addr = latch.cgramAddress; + screen.cgram[addr] = data; +} + +auto PPU::readIO(uint addr, uint8 data) -> uint8 { + cpu.synchronizePPU(); + + switch(addr & 0xffff) { + + case 0x2104: case 0x2105: case 0x2106: case 0x2108: + case 0x2109: case 0x210a: case 0x2114: case 0x2115: + case 0x2116: case 0x2118: case 0x2119: case 0x211a: + case 0x2124: case 0x2125: case 0x2126: case 0x2128: + case 0x2129: case 0x212a: { + return ppu1.mdr; + } + + //MPYL + case 0x2134: { + uint24 result = (int16)io.m7a * (int8)(io.m7b >> 8); + return ppu1.mdr = result.byte(0); + } + + //MPYM + case 0x2135: { + uint24 result = (int16)io.m7a * (int8)(io.m7b >> 8); + return ppu1.mdr = result.byte(1); + } + + //MPYH + case 0x2136: { + uint24 result = (int16)io.m7a * (int8)(io.m7b >> 8); + return ppu1.mdr = result.byte(2); + } + + //SLHV + case 0x2137: { + if(cpu.pio() & 0x80) latchCounters(); + return data; //CPU MDR + } + + //OAMDATAREAD + case 0x2138: { + ppu1.mdr = readOAM(io.oamAddress++); + obj.setFirstSprite(); + return ppu1.mdr; + } + + //VMDATALREAD + case 0x2139: { + ppu1.mdr = latch.vram >> 0; + if(io.vramIncrementMode == 0) { + latch.vram = readVRAM(); + io.vramAddress += io.vramIncrementSize; + } + return ppu1.mdr; + } + + //VMDATAHREAD + case 0x213a: { + ppu1.mdr = latch.vram >> 8; + if(io.vramIncrementMode == 1) { + latch.vram = readVRAM(); + io.vramAddress += io.vramIncrementSize; + } + return ppu1.mdr; + } + + //CGDATAREAD + case 0x213b: { + if(io.cgramAddressLatch++ == 0) { + ppu2.mdr = readCGRAM(0, io.cgramAddress); + } else { + ppu2.mdr &= 0x80; + ppu2.mdr |= readCGRAM(1, io.cgramAddress++) & 0x7f; + } + return ppu2.mdr; + } + + //OPHCT + case 0x213c: { + if(latch.hcounter++ == 0) { + ppu2.mdr = io.hcounter >> 0; + } else { + ppu2.mdr &= 0xfe; + ppu2.mdr |= io.hcounter >> 8 & 1; + } + return ppu2.mdr; + } + + //OPVCT + case 0x213d: { + if(latch.vcounter++ == 0) { + ppu2.mdr = io.vcounter >> 0; + } else { + ppu2.mdr &= 0xfe; + ppu2.mdr |= io.vcounter >> 8 & 1; + } + return ppu2.mdr; + } + + //STAT77 + case 0x213e: { + ppu1.mdr &= 1 << 4; + ppu1.mdr |= ppu1.version << 0; + ppu1.mdr |= obj.io.rangeOver << 6; + ppu1.mdr |= obj.io.timeOver << 7; + return ppu1.mdr; + } + + //STAT78 + case 0x213f: { + latch.hcounter = 0; + latch.vcounter = 0; + ppu2.mdr &= 1 << 5; + ppu2.mdr |= ppu2.version; + ppu2.mdr |= Region::PAL() << 4; //0 = NTSC, 1 = PAL + if(!(cpu.pio() & 0x80)) { + ppu2.mdr |= 1 << 6; + } else { + ppu2.mdr |= latch.counters << 6; + latch.counters = 0; + } + ppu2.mdr |= field() << 7; + return ppu2.mdr; + } + + } + + return data; +} + +auto PPU::writeIO(uint addr, uint8 data) -> void { + cpu.synchronizePPU(); + + switch(addr & 0xffff) { + + //INIDISP + case 0x2100: { + if(io.displayDisable && vcounter() == vdisp()) obj.addressReset(); + io.displayBrightness = data >> 0 & 15; + io.displayDisable = data >> 7 & 1; + return; + } + + //OBSEL + case 0x2101: { + obj.io.tiledataAddress = (data & 7) << 13; + obj.io.nameselect = data >> 3 & 3; + obj.io.baseSize = data >> 5 & 7; + return; + } + + //OAMADDL + case 0x2102: { + io.oamBaseAddress = (io.oamBaseAddress & 0x0200) | data << 1; + obj.addressReset(); + return; + } + + //OAMADDH + case 0x2103: { + io.oamBaseAddress = (data & 1) << 9 | (io.oamBaseAddress & 0x01fe); + io.oamPriority = bool(data & 0x80); + obj.addressReset(); + return; + } + + //OAMDATA + case 0x2104: { + uint1 latchBit = io.oamAddress & 1; + uint10 address = io.oamAddress++; + if(latchBit == 0) latch.oam = data; + if(address & 0x200) { + writeOAM(address, data); + } else if(latchBit == 1) { + writeOAM((address & ~1) + 0, latch.oam); + writeOAM((address & ~1) + 1, data); + } + obj.setFirstSprite(); + return; + } + + //BGMODE + case 0x2105: { + io.bgMode = data >> 0 & 7; + io.bgPriority = data >> 3 & 1; + bg1.io.tileSize = data >> 4 & 1; + bg2.io.tileSize = data >> 5 & 1; + bg3.io.tileSize = data >> 6 & 1; + bg4.io.tileSize = data >> 7 & 1; + updateVideoMode(); + return; + } + + //MOSAIC + case 0x2106: { + bool mosaicEnable = mosaic.enable(); + bg1.mosaic.enable = data >> 0 & 1; + bg2.mosaic.enable = data >> 1 & 1; + bg3.mosaic.enable = data >> 2 & 1; + bg4.mosaic.enable = data >> 3 & 1; + mosaic.size = (data >> 4 & 15) + 1; + if(!mosaicEnable && mosaic.enable()) { + //mosaic vcounter is reloaded when mosaic becomes enabled + mosaic.vcounter = mosaic.size + 1; + } + return; + } + + //BG1SC + case 0x2107: { + bg1.io.screenSize = data & 3; + bg1.io.screenAddress = data >> 2 << 10; + return; + } + + //BG2SC + case 0x2108: { + bg2.io.screenSize = data & 3; + bg2.io.screenAddress = data >> 2 << 10; + return; + } + + //BG3SC + case 0x2109: { + bg3.io.screenSize = data & 3; + bg3.io.screenAddress = data >> 2 << 10; + return; + } + + //BG4SC + case 0x210a: { + bg4.io.screenSize = data & 3; + bg4.io.screenAddress = data >> 2 << 10; + return; + } + + //BG12NBA + case 0x210b: { + bg1.io.tiledataAddress = (data >> 0 & 15) << 12; + bg2.io.tiledataAddress = (data >> 4 & 15) << 12; + return; + } + + //BG34NBA + case 0x210c: { + bg3.io.tiledataAddress = (data >> 0 & 15) << 12; + bg4.io.tiledataAddress = (data >> 4 & 15) << 12; + return; + } + + //BG1HOFS + case 0x210d: { + io.hoffsetMode7 = data << 8 | latch.mode7; + latch.mode7 = data; + + bg1.io.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7); + latch.bgofsPPU1 = data; + latch.bgofsPPU2 = data; + return; + } + + //BG1VOFS + case 0x210e: { + io.voffsetMode7 = data << 8 | latch.mode7; + latch.mode7 = data; + + bg1.io.voffset = data << 8 | latch.bgofsPPU1; + latch.bgofsPPU1 = data; + return; + } + + //BG2HOFS + case 0x210f: { + bg2.io.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7); + latch.bgofsPPU1 = data; + latch.bgofsPPU2 = data; + return; + } + + //BG2VOFS + case 0x2110: { + bg2.io.voffset = data << 8 | latch.bgofsPPU1; + latch.bgofsPPU1 = data; + return; + } + + //BG3HOFS + case 0x2111: { + bg3.io.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7); + latch.bgofsPPU1 = data; + latch.bgofsPPU2 = data; + return; + } + + //BG3VOFS + case 0x2112: { + bg3.io.voffset = data << 8 | latch.bgofsPPU1; + latch.bgofsPPU1 = data; + return; + } + + //BG4HOFS + case 0x2113: { + bg4.io.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7); + latch.bgofsPPU1 = data; + latch.bgofsPPU2 = data; + return; + } + + //BG4VOFS + case 0x2114: { + bg4.io.voffset = data << 8 | latch.bgofsPPU1; + latch.bgofsPPU1 = data; + return; + } + + //VMAIN + case 0x2115: { + static const uint size[4] = {1, 32, 128, 128}; + io.vramIncrementSize = size[data & 3]; + io.vramMapping = data >> 2 & 3; + io.vramIncrementMode = data >> 7 & 1; + return; + } + + //VMADDL + case 0x2116: { + io.vramAddress = io.vramAddress & 0xff00 | data << 0; + latch.vram = readVRAM(); + return; + } + + //VMADDH + case 0x2117: { + io.vramAddress = io.vramAddress & 0x00ff | data << 8; + latch.vram = readVRAM(); + return; + } + + //VMDATAL + case 0x2118: { + writeVRAM(0, data); + if(io.vramIncrementMode == 0) io.vramAddress += io.vramIncrementSize; + return; + } + + //VMDATAH + case 0x2119: { + writeVRAM(1, data); + if(io.vramIncrementMode == 1) io.vramAddress += io.vramIncrementSize; + return; + } + + //M7SEL + case 0x211a: { + io.hflipMode7 = data >> 0 & 1; + io.vflipMode7 = data >> 1 & 1; + io.repeatMode7 = data >> 6 & 3; + return; + } + + //M7A + case 0x211b: { + io.m7a = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + //M7B + case 0x211c: { + io.m7b = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + //M7C + case 0x211d: { + io.m7c = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + //M7D + case 0x211e: { + io.m7d = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + //M7X + case 0x211f: { + io.m7x = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + //M7Y + case 0x2120: { + io.m7y = data << 8 | latch.mode7; + latch.mode7 = data; + return; + } + + //CGADD + case 0x2121: { + io.cgramAddress = data; + io.cgramAddressLatch = 0; + return; + } + + //CGDATA + case 0x2122: { + if(io.cgramAddressLatch++ == 0) { + latch.cgram = data; + } else { + writeCGRAM(io.cgramAddress++, (data & 0x7f) << 8 | latch.cgram); + } + return; + } + + //W12SEL + case 0x2123: { + window.io.bg1.oneInvert = data >> 0 & 1; + window.io.bg1.oneEnable = data >> 1 & 1; + window.io.bg1.twoInvert = data >> 2 & 1; + window.io.bg1.twoEnable = data >> 3 & 1; + window.io.bg2.oneInvert = data >> 4 & 1; + window.io.bg2.oneEnable = data >> 5 & 1; + window.io.bg2.twoInvert = data >> 6 & 1; + window.io.bg2.twoEnable = data >> 7 & 1; + return; + } + + //W34SEL + case 0x2124: { + window.io.bg3.oneInvert = data >> 0 & 1; + window.io.bg3.oneEnable = data >> 1 & 1; + window.io.bg3.twoInvert = data >> 2 & 1; + window.io.bg3.twoEnable = data >> 3 & 1; + window.io.bg4.oneInvert = data >> 4 & 1; + window.io.bg4.oneEnable = data >> 5 & 1; + window.io.bg4.twoInvert = data >> 6 & 1; + window.io.bg4.twoEnable = data >> 7 & 1; + return; + } + + //WOBJSEL + case 0x2125: { + window.io.obj.oneInvert = data >> 0 & 1; + window.io.obj.oneEnable = data >> 1 & 1; + window.io.obj.twoInvert = data >> 2 & 1; + window.io.obj.twoEnable = data >> 3 & 1; + window.io.col.oneInvert = data >> 4 & 1; + window.io.col.oneEnable = data >> 5 & 1; + window.io.col.twoInvert = data >> 6 & 1; + window.io.col.twoEnable = data >> 7 & 1; + return; + } + + //WH0 + case 0x2126: { + window.io.oneLeft = data; + return; + } + + //WH1 + case 0x2127: { + window.io.oneRight = data; + return; + } + + //WH2 + case 0x2128: { + window.io.twoLeft = data; + return; + } + + //WH3 + case 0x2129: { + window.io.twoRight = data; + return; + } + + //WBGLOG + case 0x212a: { + window.io.bg1.mask = data >> 0 & 3; + window.io.bg2.mask = data >> 2 & 3; + window.io.bg3.mask = data >> 4 & 3; + window.io.bg4.mask = data >> 6 & 3; + return; + } + + //WOBJLOG + case 0x212b: { + window.io.obj.mask = data >> 0 & 3; + window.io.col.mask = data >> 2 & 3; + return; + } + + //TM + case 0x212c: { + bg1.io.aboveEnable = data >> 0 & 1; + bg2.io.aboveEnable = data >> 1 & 1; + bg3.io.aboveEnable = data >> 2 & 1; + bg4.io.aboveEnable = data >> 3 & 1; + obj.io.aboveEnable = data >> 4 & 1; + return; + } + + //TS + case 0x212d: { + bg1.io.belowEnable = data >> 0 & 1; + bg2.io.belowEnable = data >> 1 & 1; + bg3.io.belowEnable = data >> 2 & 1; + bg4.io.belowEnable = data >> 3 & 1; + obj.io.belowEnable = data >> 4 & 1; + return; + } + + //TMW + case 0x212e: { + window.io.bg1.aboveEnable = data >> 0 & 1; + window.io.bg2.aboveEnable = data >> 1 & 1; + window.io.bg3.aboveEnable = data >> 2 & 1; + window.io.bg4.aboveEnable = data >> 3 & 1; + window.io.obj.aboveEnable = data >> 4 & 1; + return; + } + + //TSW + case 0x212f: { + window.io.bg1.belowEnable = data >> 0 & 1; + window.io.bg2.belowEnable = data >> 1 & 1; + window.io.bg3.belowEnable = data >> 2 & 1; + window.io.bg4.belowEnable = data >> 3 & 1; + window.io.obj.belowEnable = data >> 4 & 1; + return; + } + + //CGWSEL + case 0x2130: { + screen.io.directColor = data >> 0 & 1; + screen.io.blendMode = data >> 1 & 1; + window.io.col.belowMask = data >> 4 & 3; + window.io.col.aboveMask = data >> 6 & 3; + return; + } + + //CGADDSUB + case 0x2131: { + screen.io.bg1.colorEnable = data >> 0 & 1; + screen.io.bg2.colorEnable = data >> 1 & 1; + screen.io.bg3.colorEnable = data >> 2 & 1; + screen.io.bg4.colorEnable = data >> 3 & 1; + screen.io.obj.colorEnable = data >> 4 & 1; + screen.io.back.colorEnable = data >> 5 & 1; + screen.io.colorHalve = data >> 6 & 1; + screen.io.colorMode = data >> 7 & 1; + return; + } + + //COLDATA + case 0x2132: { + if(data & 0x20) screen.io.colorRed = data & 0x1f; + if(data & 0x40) screen.io.colorGreen = data & 0x1f; + if(data & 0x80) screen.io.colorBlue = data & 0x1f; + return; + } + + //SETINI + case 0x2133: { + io.interlace = data >> 0 & 1; + obj.io.interlace = data >> 1 & 1; + io.overscan = data >> 2 & 1; + io.pseudoHires = data >> 3 & 1; + io.extbg = data >> 6 & 1; + updateVideoMode(); + return; + } + + } +} + +auto PPU::updateVideoMode() -> void { + display.vdisp = !io.overscan ? 225 : 240; + + switch(io.bgMode) { + case 0: + bg1.io.mode = Background::Mode::BPP2; + bg2.io.mode = Background::Mode::BPP2; + bg3.io.mode = Background::Mode::BPP2; + bg4.io.mode = Background::Mode::BPP2; + memory::assign(bg1.io.priority, 8, 11); + memory::assign(bg2.io.priority, 7, 10); + memory::assign(bg3.io.priority, 2, 5); + memory::assign(bg4.io.priority, 1, 4); + memory::assign(obj.io.priority, 3, 6, 9, 12); + break; + + case 1: + bg1.io.mode = Background::Mode::BPP4; + bg2.io.mode = Background::Mode::BPP4; + bg3.io.mode = Background::Mode::BPP2; + bg4.io.mode = Background::Mode::Inactive; + if(io.bgPriority) { + memory::assign(bg1.io.priority, 5, 8); + memory::assign(bg2.io.priority, 4, 7); + memory::assign(bg3.io.priority, 1, 10); + memory::assign(obj.io.priority, 2, 3, 6, 9); + } else { + memory::assign(bg1.io.priority, 6, 9); + memory::assign(bg2.io.priority, 5, 8); + memory::assign(bg3.io.priority, 1, 3); + memory::assign(obj.io.priority, 2, 4, 7, 10); + } + break; + + case 2: + bg1.io.mode = Background::Mode::BPP4; + bg2.io.mode = Background::Mode::BPP4; + bg3.io.mode = Background::Mode::Inactive; + bg4.io.mode = Background::Mode::Inactive; + memory::assign(bg1.io.priority, 3, 7); + memory::assign(bg2.io.priority, 1, 5); + memory::assign(obj.io.priority, 2, 4, 6, 8); + break; + + case 3: + bg1.io.mode = Background::Mode::BPP8; + bg2.io.mode = Background::Mode::BPP4; + bg3.io.mode = Background::Mode::Inactive; + bg4.io.mode = Background::Mode::Inactive; + memory::assign(bg1.io.priority, 3, 7); + memory::assign(bg2.io.priority, 1, 5); + memory::assign(obj.io.priority, 2, 4, 6, 8); + break; + + case 4: + bg1.io.mode = Background::Mode::BPP8; + bg2.io.mode = Background::Mode::BPP2; + bg3.io.mode = Background::Mode::Inactive; + bg4.io.mode = Background::Mode::Inactive; + memory::assign(bg1.io.priority, 3, 7); + memory::assign(bg2.io.priority, 1, 5); + memory::assign(obj.io.priority, 2, 4, 6, 8); + break; + + case 5: + bg1.io.mode = Background::Mode::BPP4; + bg2.io.mode = Background::Mode::BPP2; + bg3.io.mode = Background::Mode::Inactive; + bg4.io.mode = Background::Mode::Inactive; + memory::assign(bg1.io.priority, 3, 7); + memory::assign(bg2.io.priority, 1, 5); + memory::assign(obj.io.priority, 2, 4, 6, 8); + break; + + case 6: + bg1.io.mode = Background::Mode::BPP4; + bg2.io.mode = Background::Mode::Inactive; + bg3.io.mode = Background::Mode::Inactive; + bg4.io.mode = Background::Mode::Inactive; + memory::assign(bg1.io.priority, 2, 5); + memory::assign(obj.io.priority, 1, 3, 4, 6); + break; + + case 7: + if(!io.extbg) { + bg1.io.mode = Background::Mode::Mode7; + bg2.io.mode = Background::Mode::Inactive; + bg3.io.mode = Background::Mode::Inactive; + bg4.io.mode = Background::Mode::Inactive; + memory::assign(bg1.io.priority, 2); + memory::assign(obj.io.priority, 1, 3, 4, 5); + } else { + bg1.io.mode = Background::Mode::Mode7; + bg2.io.mode = Background::Mode::Mode7; + bg3.io.mode = Background::Mode::Inactive; + bg4.io.mode = Background::Mode::Inactive; + memory::assign(bg1.io.priority, 3); + memory::assign(bg2.io.priority, 1, 5); + memory::assign(obj.io.priority, 2, 4, 6, 7); + } + break; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/main.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/main.cpp new file mode 100644 index 0000000000..68f994ca83 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/main.cpp @@ -0,0 +1,195 @@ +auto PPU::main() -> void { + if(vcounter() == 0) { + if(display.overscan && !io.overscan) { + //when disabling overscan, clear the overscan area that won't be rendered to: + for(uint y = 1; y <= 240; y++) { + if(y >= 8 && y <= 231) continue; + auto output = ppu.output + y * 1024; + memory::fill(output, 1024); + } + } + display.interlace = io.interlace; + display.overscan = io.overscan; + bg1.frame(); + bg2.frame(); + bg3.frame(); + bg4.frame(); + obj.frame(); + } + + mosaic.scanline(); + bg1.scanline(); + bg2.scanline(); + bg3.scanline(); + bg4.scanline(); + obj.scanline(); + window.scanline(); + screen.scanline(); + + if(vcounter() > 240) { + step(hperiod()); + return; + } + + #define cycles02(index) cycle() + #define cycles04(index) cycles02(index); cycles02(index + 2) + #define cycles08(index) cycles04(index); cycles04(index + 4) + #define cycles16(index) cycles08(index); cycles08(index + 8) + #define cycles32(index) cycles16(index); cycles16(index + 16) + #define cycles64(index) cycles32(index); cycles32(index + 32) + cycles16( 0); + cycles04( 16); + //H = 20 + cycles04( 20); + cycles04( 24); + //H = 28 + cycles04( 28); + cycles32( 32); + cycles64( 64); + cycles64( 128); + cycles64( 192); + cycles64( 256); + cycles64( 320); + cycles64( 384); + cycles64( 448); + cycles64( 512); + cycles64( 576); + cycles64( 640); + cycles64( 704); + cycles64( 768); + cycles64( 832); + cycles64( 896); + cycles64( 960); + cycles32(1024); + cycles16(1056); + cycles08(1072); + //H = 1080 + obj.fetch(); + //H = 1352 (max) + step(hperiod() - hcounter()); +} + +//it would be lovely if we could put these functions inside cycle(), +//but due to the multiple template instantiations, that destroys L1 cache. +//it's a performance penalty of about 25% for the entire(!!) emulator. + +auto PPU::cycleObjectEvaluate() -> void { + obj.evaluate(hcounter() >> 3); +} + +template +auto PPU::cycleBackgroundFetch() -> void { + switch(io.bgMode) { + case 0: + if constexpr(Cycle == 0) bg4.fetchNameTable(); + if constexpr(Cycle == 1) bg3.fetchNameTable(); + if constexpr(Cycle == 2) bg2.fetchNameTable(); + if constexpr(Cycle == 3) bg1.fetchNameTable(); + if constexpr(Cycle == 4) bg4.fetchCharacter(0); + if constexpr(Cycle == 5) bg3.fetchCharacter(0); + if constexpr(Cycle == 6) bg2.fetchCharacter(0); + if constexpr(Cycle == 7) bg1.fetchCharacter(0); + break; + case 1: + if constexpr(Cycle == 0) bg3.fetchNameTable(); + if constexpr(Cycle == 1) bg2.fetchNameTable(); + if constexpr(Cycle == 2) bg1.fetchNameTable(); + if constexpr(Cycle == 3) bg3.fetchCharacter(0); + if constexpr(Cycle == 4) bg2.fetchCharacter(0); + if constexpr(Cycle == 5) bg2.fetchCharacter(1); + if constexpr(Cycle == 6) bg1.fetchCharacter(0); + if constexpr(Cycle == 7) bg1.fetchCharacter(1); + break; + case 2: + if constexpr(Cycle == 0) bg2.fetchNameTable(); + if constexpr(Cycle == 1) bg1.fetchNameTable(); + if constexpr(Cycle == 2) bg3.fetchOffset(0); + if constexpr(Cycle == 3) bg3.fetchOffset(8); + if constexpr(Cycle == 4) bg2.fetchCharacter(0); + if constexpr(Cycle == 5) bg2.fetchCharacter(1); + if constexpr(Cycle == 6) bg1.fetchCharacter(0); + if constexpr(Cycle == 7) bg1.fetchCharacter(1); + break; + case 3: + if constexpr(Cycle == 0) bg2.fetchNameTable(); + if constexpr(Cycle == 1) bg1.fetchNameTable(); + if constexpr(Cycle == 2) bg2.fetchCharacter(0); + if constexpr(Cycle == 3) bg2.fetchCharacter(1); + if constexpr(Cycle == 4) bg1.fetchCharacter(0); + if constexpr(Cycle == 5) bg1.fetchCharacter(1); + if constexpr(Cycle == 6) bg1.fetchCharacter(2); + if constexpr(Cycle == 7) bg1.fetchCharacter(3); + break; + case 4: + if constexpr(Cycle == 0) bg2.fetchNameTable(); + if constexpr(Cycle == 1) bg1.fetchNameTable(); + if constexpr(Cycle == 2) bg3.fetchOffset(0); + if constexpr(Cycle == 3) bg2.fetchCharacter(0); + if constexpr(Cycle == 4) bg1.fetchCharacter(0); + if constexpr(Cycle == 5) bg1.fetchCharacter(1); + if constexpr(Cycle == 6) bg1.fetchCharacter(2); + if constexpr(Cycle == 7) bg1.fetchCharacter(3); + break; + case 5: + if constexpr(Cycle == 0) bg2.fetchNameTable(); + if constexpr(Cycle == 1) bg1.fetchNameTable(); + if constexpr(Cycle == 2) bg2.fetchCharacter(0, 0); + if constexpr(Cycle == 3) bg2.fetchCharacter(0, 1); + if constexpr(Cycle == 4) bg1.fetchCharacter(0, 0); + if constexpr(Cycle == 5) bg1.fetchCharacter(1, 0); + if constexpr(Cycle == 6) bg1.fetchCharacter(0, 1); + if constexpr(Cycle == 7) bg1.fetchCharacter(1, 1); + break; + case 6: + if constexpr(Cycle == 0) bg2.fetchNameTable(); + if constexpr(Cycle == 1) bg1.fetchNameTable(); + if constexpr(Cycle == 2) bg3.fetchOffset(0); + if constexpr(Cycle == 3) bg3.fetchOffset(8); + if constexpr(Cycle == 4) bg1.fetchCharacter(0, 0); + if constexpr(Cycle == 5) bg1.fetchCharacter(1, 0); + if constexpr(Cycle == 6) bg1.fetchCharacter(0, 1); + if constexpr(Cycle == 7) bg1.fetchCharacter(1, 1); + break; + case 7: + //handled separately by mode7.cpp + break; + } +} + +auto PPU::cycleBackgroundBegin() -> void { + bg1.begin(); + bg2.begin(); + bg3.begin(); + bg4.begin(); +} + +auto PPU::cycleBackgroundBelow() -> void { + bg1.run(1); + bg2.run(1); + bg3.run(1); + bg4.run(1); +} + +auto PPU::cycleBackgroundAbove() -> void { + bg1.run(0); + bg2.run(0); + bg3.run(0); + bg4.run(0); +} + +auto PPU::cycleRenderPixel() -> void { + obj.run(); + window.run(); + screen.run(); +} + +template +auto PPU::cycle() -> void { + if constexpr(Cycle >= 0 && Cycle <= 1016 && (Cycle - 0) % 8 == 0) cycleObjectEvaluate(); + if constexpr(Cycle >= 0 && Cycle <= 1054 && (Cycle - 0) % 4 == 0) cycleBackgroundFetch<(Cycle - 0) / 4 & 7>(); + if constexpr(Cycle == 56 ) cycleBackgroundBegin(); + if constexpr(Cycle >= 56 && Cycle <= 1078 && (Cycle - 56) % 4 == 0) cycleBackgroundBelow(); + if constexpr(Cycle >= 56 && Cycle <= 1078 && (Cycle - 56) % 4 == 2) cycleBackgroundAbove(); + if constexpr(Cycle >= 56 && Cycle <= 1078 && (Cycle - 56) % 4 == 2) cycleRenderPixel(); + step(); +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/mode7.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/mode7.cpp new file mode 100644 index 0000000000..4b4413b9fe --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/mode7.cpp @@ -0,0 +1,68 @@ +auto PPU::Background::clip(int n) -> int { + //13-bit sign extend: --s---nnnnnnnnnn -> ssssssnnnnnnnnnn + return n & 0x2000 ? (n | ~1023) : (n & 1023); +} + +auto PPU::Background::runMode7() -> void { + int a = (int16)ppu.io.m7a; + int b = (int16)ppu.io.m7b; + int c = (int16)ppu.io.m7c; + int d = (int16)ppu.io.m7d; + + int hcenter = (int13)ppu.io.m7x; + int vcenter = (int13)ppu.io.m7y; + int hoffset = (int13)ppu.io.hoffsetMode7; + int voffset = (int13)ppu.io.voffsetMode7; + + uint x = mosaic.hoffset; + uint y = ppu.vcounter(); + if(ppu.bg1.mosaic.enable) y -= ppu.mosaic.voffset(); //BG2 vertical mosaic uses BG1 mosaic enable + + if(!mosaic.enable) { + mosaic.hoffset += 1; + } else if(--mosaic.hcounter == 0) { + mosaic.hcounter = ppu.mosaic.size; + mosaic.hoffset += ppu.mosaic.size; + } + + if(ppu.io.hflipMode7) x = 255 - x; + if(ppu.io.vflipMode7) y = 255 - y; + + int originX = (a * clip(hoffset - hcenter) & ~63) + (b * clip(voffset - vcenter) & ~63) + (b * y & ~63) + (hcenter << 8); + int originY = (c * clip(hoffset - hcenter) & ~63) + (d * clip(voffset - vcenter) & ~63) + (d * y & ~63) + (vcenter << 8); + + int pixelX = originX + a * x >> 8; + int pixelY = originY + c * x >> 8; + uint16 paletteAddress = (uint3)pixelY << 3 | (uint3)pixelX; + + uint7 tileX = pixelX >> 3; + uint7 tileY = pixelY >> 3; + uint16 tileAddress = tileY << 7 | tileX; + + bool outOfBounds = (pixelX | pixelY) & ~1023; + + uint8 tile = ppu.io.repeatMode7 == 3 && outOfBounds ? 0 : ppu.vram[tileAddress] >> 0; + uint8 palette = ppu.io.repeatMode7 == 2 && outOfBounds ? 0 : ppu.vram[tile << 6 | paletteAddress] >> 8; + + uint priority; + if(id == ID::BG1) { + priority = io.priority[0]; + } else if(id == ID::BG2) { + priority = io.priority[palette >> 7]; + palette &= 0x7f; + } + + if(palette == 0) return; + + if(io.aboveEnable) { + output.above.priority = priority; + output.above.palette = palette; + output.above.paletteGroup = 0; + } + + if(io.belowEnable) { + output.below.priority = priority; + output.below.palette = palette; + output.below.paletteGroup = 0; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/mosaic.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/mosaic.cpp new file mode 100644 index 0000000000..79fe4d5106 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/mosaic.cpp @@ -0,0 +1,26 @@ +auto PPU::Mosaic::enable() const -> bool { + if(ppu.bg1.mosaic.enable) return true; + if(ppu.bg2.mosaic.enable) return true; + if(ppu.bg3.mosaic.enable) return true; + if(ppu.bg4.mosaic.enable) return true; + return false; +} + +auto PPU::Mosaic::voffset() const -> uint { + return size - vcounter; +} + +//H = 0 +auto PPU::Mosaic::scanline() -> void { + if(ppu.vcounter() == 1) { + vcounter = enable() ? size + 1 : 0; + } + if(vcounter && !--vcounter) { + vcounter = enable() ? size + 0 : 0; + } +} + +auto PPU::Mosaic::power() -> void { + size = (random() & 15) + 1; + vcounter = 0; +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/mosaic.hpp b/waterbox/bsnescore/bsnes/sfc/ppu/mosaic.hpp new file mode 100644 index 0000000000..261739e685 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/mosaic.hpp @@ -0,0 +1,13 @@ +struct Mosaic { + //mosaic.cpp + alwaysinline auto enable() const -> bool; + alwaysinline auto voffset() const -> uint; + auto scanline() -> void; + auto power() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint5 size; + uint5 vcounter; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/oam.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/oam.cpp new file mode 100644 index 0000000000..b3d59fd987 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/oam.cpp @@ -0,0 +1,74 @@ +auto PPU::OAM::read(uint10 address) -> uint8 { + if(!(address & 0x200)) { + uint n = address >> 2; //object# + address &= 3; + if(address == 0) return object[n].x & 0xff; + if(address == 1) return object[n].y; + if(address == 2) return object[n].character; + return ( + object[n].nameselect << 0 + | object[n].palette << 1 + | object[n].priority << 4 + | object[n].hflip << 6 + | object[n].vflip << 7 + ); + } else { + uint n = (address & 0x1f) << 2; //object# + return ( + object[n + 0].x >> 8 << 0 + | object[n + 0].size << 1 + | object[n + 1].x >> 8 << 2 + | object[n + 1].size << 3 + | object[n + 2].x >> 8 << 4 + | object[n + 2].size << 5 + | object[n + 3].x >> 8 << 6 + | object[n + 3].size << 7 + ); + } +} + +auto PPU::OAM::write(uint10 address, uint8 data) -> void { + if(!(address & 0x200)) { + uint n = address >> 2; //object# + address &= 3; + if(address == 0) { object[n].x = object[n].x & 0x100 | data; return; } + if(address == 1) { object[n].y = data; return; } + if(address == 2) { object[n].character = data; return; } + object[n].nameselect = data >> 0 & 1; + object[n].palette = data >> 1 & 7; + object[n].priority = data >> 4 & 3; + object[n].hflip = data >> 6 & 1; + object[n].vflip = data >> 7 & 1; + } else { + uint n = (address & 0x1f) << 2; //object# + object[n + 0].x = object[n + 0].x & 0xff | bool(data & 0x01) << 8; + object[n + 0].size = bool(data & 0x02); + object[n + 1].x = object[n + 1].x & 0xff | bool(data & 0x04) << 8; + object[n + 1].size = bool(data & 0x08); + object[n + 2].x = object[n + 2].x & 0xff | bool(data & 0x10) << 8; + object[n + 2].size = bool(data & 0x20); + object[n + 3].x = object[n + 3].x & 0xff | bool(data & 0x40) << 8; + object[n + 3].size = bool(data & 0x80); + } +} + +auto PPU::OAM::Object::width() const -> uint { + if(size == 0) { + static const uint width[] = { 8, 8, 8, 16, 16, 32, 16, 16}; + return width[ppu.obj.io.baseSize]; + } else { + static const uint width[] = {16, 32, 64, 32, 64, 64, 32, 32}; + return width[ppu.obj.io.baseSize]; + } +} + +auto PPU::OAM::Object::height() const -> uint { + if(size == 0) { + if(ppu.obj.io.interlace && ppu.obj.io.baseSize >= 6) return 16; //hardware quirk + static const uint height[] = { 8, 8, 8, 16, 16, 32, 32, 32}; + return height[ppu.obj.io.baseSize]; + } else { + static const uint height[] = {16, 32, 64, 32, 64, 64, 64, 32}; + return height[ppu.obj.io.baseSize]; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/object.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/object.cpp new file mode 100644 index 0000000000..d22068b2be --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/object.cpp @@ -0,0 +1,221 @@ +#include "oam.cpp" + +auto PPU::Object::addressReset() -> void { + ppu.io.oamAddress = ppu.io.oamBaseAddress; + setFirstSprite(); +} + +auto PPU::Object::setFirstSprite() -> void { + io.firstSprite = !ppu.io.oamPriority ? 0 : uint(ppu.io.oamAddress >> 2); +} + +auto PPU::Object::frame() -> void { + io.timeOver = false; + io.rangeOver = false; +} + +auto PPU::Object::scanline() -> void { + latch.firstSprite = io.firstSprite; + + t.x = 0; + t.y = ppu.vcounter(); + t.itemCount = 0; + t.tileCount = 0; + + t.active = !t.active; + auto oamItem = t.item[t.active]; + auto oamTile = t.tile[t.active]; + + for(uint n : range(32)) oamItem[n].valid = false; + for(uint n : range(34)) oamTile[n].valid = false; + + if(t.y == ppu.vdisp() && !ppu.io.displayDisable) addressReset(); + if(t.y >= ppu.vdisp() - 1 || ppu.io.displayDisable) return; +} + +auto PPU::Object::evaluate(uint7 index) -> void { + if(ppu.io.displayDisable) return; + if(t.itemCount > 32) return; + + auto oamItem = t.item[t.active]; + auto oamTile = t.tile[t.active]; + + uint7 sprite = latch.firstSprite + index; + if(!onScanline(oam.object[sprite])) return; + ppu.latch.oamAddress = sprite; + + if(t.itemCount++ < 32) { + oamItem[t.itemCount - 1] = {true, sprite}; + } +} + +auto PPU::Object::onScanline(PPU::OAM::Object& sprite) -> bool { + if(sprite.x > 256 && sprite.x + sprite.width() - 1 < 512) return false; + uint height = sprite.height() >> io.interlace; + if(t.y >= sprite.y && t.y < sprite.y + height) return true; + if(sprite.y + height >= 256 && t.y < (sprite.y + height & 255)) return true; + return false; +} + +auto PPU::Object::run() -> void { + output.above.priority = 0; + output.below.priority = 0; + + auto oamTile = t.tile[!t.active]; + uint x = t.x++; + + for(uint n : range(34)) { + const auto& tile = oamTile[n]; + if(!tile.valid) break; + + int px = x - (int9)tile.x; + if(px & ~7) continue; + + uint color = 0, shift = tile.hflip ? px : 7 - px; + color += tile.data >> shift + 0 & 1; + color += tile.data >> shift + 7 & 2; + color += tile.data >> shift + 14 & 4; + color += tile.data >> shift + 21 & 8; + + if(color) { + if(io.aboveEnable) { + output.above.palette = tile.palette + color; + output.above.priority = io.priority[tile.priority]; + } + + if(io.belowEnable) { + output.below.palette = tile.palette + color; + output.below.priority = io.priority[tile.priority]; + } + } + } +} + +auto PPU::Object::fetch() -> void { + auto oamItem = t.item[t.active]; + auto oamTile = t.tile[t.active]; + + for(uint i : reverse(range(32))) { + if(!oamItem[i].valid) continue; + + if(ppu.io.displayDisable || ppu.vcounter() >= ppu.vdisp() - 1) { + ppu.step(8); + continue; + } + + ppu.latch.oamAddress = 0x0200 + (oamItem[i].index >> 2); + const auto& sprite = oam.object[oamItem[i].index]; + + uint tileWidth = sprite.width() >> 3; + int x = sprite.x; + int y = t.y - sprite.y & 0xff; + if(io.interlace) y <<= 1; + + if(sprite.vflip) { + if(sprite.width() == sprite.height()) { + y = sprite.height() - 1 - y; + } else if(y < sprite.width()) { + y = sprite.width() - 1 - y; + } else { + y = sprite.width() + (sprite.width() - 1) - (y - sprite.width()); + } + } + + if(io.interlace) { + y = !sprite.vflip ? y + ppu.field() : y - ppu.field(); + } + + x &= 511; + y &= 255; + + uint16 tiledataAddress = io.tiledataAddress; + if(sprite.nameselect) tiledataAddress += 1 + io.nameselect << 12; + uint16 chrx = (sprite.character & 15); + uint16 chry = ((sprite.character >> 4) + (y >> 3) & 15) << 4; + + for(uint tx : range(tileWidth)) { + uint sx = x + (tx << 3) & 511; + if(x != 256 && sx >= 256 && sx + 7 < 512) continue; + if(t.tileCount++ >= 34) break; + + uint n = t.tileCount - 1; + oamTile[n].valid = true; + oamTile[n].x = sx; + oamTile[n].priority = sprite.priority; + oamTile[n].palette = 128 + (sprite.palette << 4); + oamTile[n].hflip = sprite.hflip; + + uint mx = !sprite.hflip ? tx : tileWidth - 1 - tx; + uint pos = tiledataAddress + ((chry + (chrx + mx & 15)) << 4); + uint16 address = (pos & 0xfff0) + (y & 7); + + if(!ppu.io.displayDisable) + oamTile[n].data = ppu.vram[address + 0] << 0; + ppu.step(4); + + if(!ppu.io.displayDisable) + oamTile[n].data |= ppu.vram[address + 8] << 16; + ppu.step(4); + } + } + + io.timeOver |= (t.tileCount > 34); + io.rangeOver |= (t.itemCount > 32); +} + +auto PPU::Object::power() -> void { + for(auto& object : oam.object) { + object.x = 0; + object.y = 0; + object.character = 0; + object.nameselect = 0; + object.vflip = 0; + object.hflip = 0; + object.priority = 0; + object.palette = 0; + object.size = 0; + } + + t.x = 0; + t.y = 0; + + t.itemCount = 0; + t.tileCount = 0; + + t.active = 0; + for(uint p : range(2)) { + for(uint n : range(32)) { + t.item[p][n].valid = false; + t.item[p][n].index = 0; + } + for(uint n : range(34)) { + t.tile[p][n].valid = false; + t.tile[p][n].x = 0; + t.tile[p][n].priority = 0; + t.tile[p][n].palette = 0; + t.tile[p][n].hflip = 0; + t.tile[p][n].data = 0; + } + } + + io.aboveEnable = random(); + io.belowEnable = random(); + io.interlace = random(); + + io.baseSize = random(); + io.nameselect = random(); + io.tiledataAddress = (random() & 7) << 13; + io.firstSprite = 0; + + for(auto& p : io.priority) p = 0; + + io.timeOver = false; + io.rangeOver = false; + + latch = {}; + + output.above.palette = 0; + output.above.priority = 0; + output.below.palette = 0; + output.below.priority = 0; +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/object.hpp b/waterbox/bsnescore/bsnes/sfc/ppu/object.hpp new file mode 100644 index 0000000000..a3687849a9 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/object.hpp @@ -0,0 +1,91 @@ +struct OAM { + auto read(uint10 address) -> uint8; + auto write(uint10 address, uint8 data) -> void; + + struct Object { + alwaysinline auto width() const -> uint; + alwaysinline auto height() const -> uint; + + uint9 x; + uint8 y; + uint8 character; + uint1 nameselect; + uint1 vflip; + uint1 hflip; + uint2 priority; + uint3 palette; + uint1 size; + } object[128]; +}; + +struct Object { + alwaysinline auto addressReset() -> void; + alwaysinline auto setFirstSprite() -> void; + auto frame() -> void; + auto scanline() -> void; + auto evaluate(uint7 index) -> void; + auto run() -> void; + auto fetch() -> void; + auto power() -> void; + + auto onScanline(PPU::OAM::Object&) -> bool; + + auto serialize(serializer&) -> void; + + OAM oam; + + struct IO { + uint1 aboveEnable; + uint1 belowEnable; + uint1 interlace; + + uint3 baseSize; + uint2 nameselect; + uint16 tiledataAddress; + uint7 firstSprite; + + uint8 priority[4]; + + uint1 timeOver; + uint1 rangeOver; + } io; + + struct Latch { + uint7 firstSprite; + } latch; + + struct Item { + uint1 valid; + uint7 index; + }; + + struct Tile { + uint1 valid; + uint9 x; + uint2 priority; + uint8 palette; + uint1 hflip; + uint32 data; + }; + + struct State { + uint x; + uint y; + + uint itemCount; + uint tileCount; + + bool active; + Item item[2][32]; + Tile tile[2][34]; + } t; + + struct Output { + struct Pixel { + uint8 priority; //0 = none (transparent) + uint8 palette; + } above, below; + } output; + + friend class PPU; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/ppu.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/ppu.cpp new file mode 100644 index 0000000000..dc8d135fce --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/ppu.cpp @@ -0,0 +1,214 @@ +#include + +namespace SuperFamicom { + +PPU ppu; +#include "main.cpp" +#include "io.cpp" +#include "mosaic.cpp" +#include "background.cpp" +#include "object.cpp" +#include "window.cpp" +#include "screen.cpp" +#include "serialization.cpp" +#include "counter/serialization.cpp" + +PPU::PPU() : +bg1(Background::ID::BG1), +bg2(Background::ID::BG2), +bg3(Background::ID::BG3), +bg4(Background::ID::BG4) { + ppu1.version = 1; //allowed values: 1 + ppu2.version = 3; //allowed values: 1, 2, 3 + + for(uint l = 0; l < 16; l++) { + for(uint r = 0; r < 32; r++) { + for(uint g = 0; g < 32; g++) { + for(uint b = 0; b < 32; b++) { + double luma = (double)l / 15.0; + uint ar = (luma * r + 0.5); + uint ag = (luma * g + 0.5); + uint ab = (luma * b + 0.5); + lightTable[l][(r << 10) + (g << 5) + b] = (ab << 10) + (ag << 5) + ar; + } + } + } + } +} + +PPU::~PPU() { +} + +auto PPU::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto PPU::step() -> void { + tick(2); + clock += 2; + synchronizeCPU(); +} + +auto PPU::step(uint clocks) -> void { + clocks >>= 1; + while(clocks--) { + tick(2); + clock += 2; + synchronizeCPU(); + } +} + +auto PPU::Enter() -> void { + while(true) { + scheduler.synchronize(); + ppu.main(); + } +} + +auto PPU::load() -> bool { + ppu1.version = max(1, min(1, configuration.system.ppu1.version)); + ppu2.version = max(1, min(3, configuration.system.ppu2.version)); + vram.mask = configuration.system.ppu1.vram.size / sizeof(uint16) - 1; + if(vram.mask != 0xffff) vram.mask = 0x7fff; + return true && ppufast.load(); +} + +auto PPU::power(bool reset) -> void { + if(system.fastPPU()) { + create(PPUfast::Enter, system.cpuFrequency()); + ppufast.power(reset); + return; + } + + create(Enter, system.cpuFrequency()); + PPUcounter::reset(); + memory::fill(output, 512 * 480); + + function reader{&PPU::readIO, this}; + function writer{&PPU::writeIO, this}; + bus.map(reader, writer, "00-3f,80-bf:2100-213f"); + + if(!reset) random.array((uint8*)vram.data, sizeof(vram.data)); + + ppu1.mdr = random.bias(0xff); + ppu2.mdr = random.bias(0xff); + + latch.vram = random(); + latch.oam = random(); + latch.cgram = random(); + latch.bgofsPPU1 = random(); + latch.bgofsPPU2 = random(); + latch.mode7 = random(); + latch.counters = false; + latch.hcounter = 0; + latch.vcounter = 0; + + latch.oamAddress = 0x0000; + latch.cgramAddress = 0x00; + + //$2100 INIDISP + io.displayDisable = true; + io.displayBrightness = 0; + + //$2102 OAMADDL + //$2103 OAMADDH + io.oamBaseAddress = random() & ~1; + io.oamAddress = random(); + io.oamPriority = random(); + + //$2105 BGMODE + io.bgPriority = false; + io.bgMode = 0; + + //$210d BG1HOFS + io.hoffsetMode7 = random(); + + //$210e BG1VOFS + io.voffsetMode7 = random(); + + //$2115 VMAIN + io.vramIncrementMode = random.bias(1); + io.vramMapping = random(); + io.vramIncrementSize = 1; + + //$2116 VMADDL + //$2117 VMADDH + io.vramAddress = random(); + + //$211a M7SEL + io.repeatMode7 = random(); + io.vflipMode7 = random(); + io.hflipMode7 = random(); + + //$211b M7A + io.m7a = random(); + + //$211c M7B + io.m7b = random(); + + //$211d M7C + io.m7c = random(); + + //$211e M7D + io.m7d = random(); + + //$211f M7X + io.m7x = random(); + + //$2120 M7Y + io.m7y = random(); + + //$2121 CGADD + io.cgramAddress = random(); + io.cgramAddressLatch = random(); + + //$2133 SETINI + io.extbg = random(); + io.pseudoHires = random(); + io.overscan = false; + io.interlace = false; + + //$213c OPHCT + io.hcounter = 0; + + //$213d OPVCT + io.vcounter = 0; + + mosaic.power(); + bg1.power(); + bg2.power(); + bg3.power(); + bg4.power(); + obj.power(); + window.power(); + screen.power(); + + updateVideoMode(); +} + +auto PPU::refresh() -> void { + if(system.fastPPU()) { + return ppufast.refresh(); + } + + if(system.runAhead) return; + + auto output = this->output; + auto pitch = 512; + auto width = 512; + auto height = 480; + if(configuration.video.blurEmulation) { + for(uint y : range(height)) { + auto data = output + y * pitch; + for(uint x : range(width - 1)) { + auto a = data[x + 0]; + auto b = data[x + 1]; + data[x] = (a + b - ((a ^ b) & 0x0421)) >> 1; + } + } + } + if(auto device = controllerPort2.device) device->draw(output, pitch * sizeof(uint16), width, height); + platform->videoFrame(output, pitch * sizeof(uint16), width, height, /* scale = */ 1); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/ppu.hpp b/waterbox/bsnescore/bsnes/sfc/ppu/ppu.hpp new file mode 100644 index 0000000000..353acf2709 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/ppu.hpp @@ -0,0 +1,179 @@ +struct PPU : Thread, PPUcounter { + alwaysinline auto interlace() const -> bool { return display.interlace; } + alwaysinline auto overscan() const -> bool { return display.overscan; } + alwaysinline auto vdisp() const -> uint { return display.vdisp; } + + //ppu.cpp + PPU(); + ~PPU(); + + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto load() -> bool; + auto power(bool reset) -> void; + + //main.cpp + auto main() -> void; + noinline auto cycleObjectEvaluate() -> void; + template noinline auto cycleBackgroundFetch() -> void; + noinline auto cycleBackgroundBegin() -> void; + noinline auto cycleBackgroundBelow() -> void; + noinline auto cycleBackgroundAbove() -> void; + noinline auto cycleRenderPixel() -> void; + template auto cycle() -> void; + + //io.cpp + auto latchCounters(uint hcounter, uint vcounter) -> void; + auto latchCounters() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + +private: + //ppu.cpp + alwaysinline auto step() -> void; + alwaysinline auto step(uint clocks) -> void; + + //io.cpp + alwaysinline auto addressVRAM() const -> uint16; + alwaysinline auto readVRAM() -> uint16; + alwaysinline auto writeVRAM(bool byte, uint8 data) -> void; + alwaysinline auto readOAM(uint10 address) -> uint8; + alwaysinline auto writeOAM(uint10 address, uint8 data) -> void; + alwaysinline auto readCGRAM(bool byte, uint8 address) -> uint8; + alwaysinline auto writeCGRAM(uint8 address, uint15 data) -> void; + auto readIO(uint address, uint8 data) -> uint8; + auto writeIO(uint address, uint8 data) -> void; + auto updateVideoMode() -> void; + + struct VRAM { + auto& operator[](uint address) { return data[address & mask]; } + uint16 data[64 * 1024]; + uint16 mask = 0x7fff; + } vram; + + uint16 output[512 * 480]; + uint16 lightTable[16][32768]; + + struct { + bool interlace; + bool overscan; + uint vdisp; + } display; + + auto refresh() -> void; + + struct { + uint4 version; + uint8 mdr; + } ppu1, ppu2; + + struct Latch { + uint16 vram; + uint8 oam; + uint8 cgram; + uint8 bgofsPPU1; + uint3 bgofsPPU2; + uint8 mode7; + uint1 counters; + uint1 hcounter; + uint1 vcounter; + + uint10 oamAddress; + uint8 cgramAddress; + } latch; + + struct IO { + //$2100 INIDISP + uint1 displayDisable; + uint4 displayBrightness; + + //$2102 OAMADDL + //$2103 OAMADDH + uint10 oamBaseAddress; + uint10 oamAddress; + uint1 oamPriority; + + //$2105 BGMODE + uint1 bgPriority; + uint8 bgMode; + + //$210d BG1HOFS + uint16 hoffsetMode7; + + //$210e BG1VOFS + uint16 voffsetMode7; + + //$2115 VMAIN + uint1 vramIncrementMode; + uint2 vramMapping; + uint8 vramIncrementSize; + + //$2116 VMADDL + //$2117 VMADDH + uint16 vramAddress; + + //$211a M7SEL + uint2 repeatMode7; + uint1 vflipMode7; + uint1 hflipMode7; + + //$211b M7A + uint16 m7a; + + //$211c M7B + uint16 m7b; + + //$211d M7C + uint16 m7c; + + //$211e M7D + uint16 m7d; + + //$211f M7X + uint16 m7x; + + //$2120 M7Y + uint16 m7y; + + //$2121 CGADD + uint8 cgramAddress; + uint1 cgramAddressLatch; + + //$2133 SETINI + uint1 extbg; + uint1 pseudoHires; + uint1 overscan; + uint1 interlace; + + //$213c OPHCT + uint16 hcounter; + + //$213d OPVCT + uint16 vcounter; + } io; + + #include "mosaic.hpp" + #include "background.hpp" + #include "object.hpp" + #include "window.hpp" + #include "screen.hpp" + + Mosaic mosaic; + Background bg1; + Background bg2; + Background bg3; + Background bg4; + Object obj; + Window window; + Screen screen; + + friend class PPU::Background; + friend class PPU::Object; + friend class PPU::Window; + friend class PPU::Screen; + friend class System; + friend class PPUfast; +}; + +extern PPU ppu; diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/screen.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/screen.cpp new file mode 100644 index 0000000000..7455effc30 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/screen.cpp @@ -0,0 +1,182 @@ +auto PPU::Screen::scanline() -> void { + auto y = ppu.vcounter() + (!ppu.display.overscan ? 7 : 0); + + lineA = ppu.output + y * 1024; + lineB = lineA + (ppu.display.interlace ? 0 : 512); + if(ppu.display.interlace && ppu.field()) lineA += 512, lineB += 512; + + //the first hires pixel of each scanline is transparent + //note: exact value initializations are not confirmed on hardware + math.above.color = paletteColor(0); + math.below.color = math.above.color; + + math.above.colorEnable = false; + math.below.colorEnable = false; + + math.transparent = true; + math.blendMode = false; + math.colorHalve = io.colorHalve && !io.blendMode && math.above.colorEnable; +} + +auto PPU::Screen::run() -> void { + if(ppu.vcounter() == 0) return; + + bool hires = ppu.io.pseudoHires || ppu.io.bgMode == 5 || ppu.io.bgMode == 6; + auto belowColor = below(hires); + auto aboveColor = above(); + + *lineA++ = *lineB++ = ppu.lightTable[ppu.io.displayBrightness][hires ? belowColor : aboveColor]; + *lineA++ = *lineB++ = ppu.lightTable[ppu.io.displayBrightness][aboveColor]; +} + +auto PPU::Screen::below(bool hires) -> uint16 { + if(ppu.io.displayDisable || (!ppu.io.overscan && ppu.vcounter() >= 225)) return 0; + + uint priority = 0; + if(ppu.bg1.output.below.priority) { + priority = ppu.bg1.output.below.priority; + if(io.directColor && (ppu.io.bgMode == 3 || ppu.io.bgMode == 4 || ppu.io.bgMode == 7)) { + math.below.color = directColor(ppu.bg1.output.below.palette, ppu.bg1.output.below.paletteGroup); + } else { + math.below.color = paletteColor(ppu.bg1.output.below.palette); + } + } + if(ppu.bg2.output.below.priority > priority) { + priority = ppu.bg2.output.below.priority; + math.below.color = paletteColor(ppu.bg2.output.below.palette); + } + if(ppu.bg3.output.below.priority > priority) { + priority = ppu.bg3.output.below.priority; + math.below.color = paletteColor(ppu.bg3.output.below.palette); + } + if(ppu.bg4.output.below.priority > priority) { + priority = ppu.bg4.output.below.priority; + math.below.color = paletteColor(ppu.bg4.output.below.palette); + } + if(ppu.obj.output.below.priority > priority) { + priority = ppu.obj.output.below.priority; + math.below.color = paletteColor(ppu.obj.output.below.palette); + } + if(math.transparent = (priority == 0)) math.below.color = paletteColor(0); + + if(!hires) return 0; + if(!math.below.colorEnable) return math.above.colorEnable ? math.below.color : (uint15)0; + + return blend( + math.above.colorEnable ? math.below.color : (uint15)0, + math.blendMode ? math.above.color : fixedColor() + ); +} + +auto PPU::Screen::above() -> uint16 { + if(ppu.io.displayDisable || (!ppu.io.overscan && ppu.vcounter() >= 225)) return 0; + + uint priority = 0; + if(ppu.bg1.output.above.priority) { + priority = ppu.bg1.output.above.priority; + if(io.directColor && (ppu.io.bgMode == 3 || ppu.io.bgMode == 4 || ppu.io.bgMode == 7)) { + math.above.color = directColor(ppu.bg1.output.above.palette, ppu.bg1.output.above.paletteGroup); + } else { + math.above.color = paletteColor(ppu.bg1.output.above.palette); + } + math.below.colorEnable = io.bg1.colorEnable; + } + if(ppu.bg2.output.above.priority > priority) { + priority = ppu.bg2.output.above.priority; + math.above.color = paletteColor(ppu.bg2.output.above.palette); + math.below.colorEnable = io.bg2.colorEnable; + } + if(ppu.bg3.output.above.priority > priority) { + priority = ppu.bg3.output.above.priority; + math.above.color = paletteColor(ppu.bg3.output.above.palette); + math.below.colorEnable = io.bg3.colorEnable; + } + if(ppu.bg4.output.above.priority > priority) { + priority = ppu.bg4.output.above.priority; + math.above.color = paletteColor(ppu.bg4.output.above.palette); + math.below.colorEnable = io.bg4.colorEnable; + } + if(ppu.obj.output.above.priority > priority) { + priority = ppu.obj.output.above.priority; + math.above.color = paletteColor(ppu.obj.output.above.palette); + math.below.colorEnable = io.obj.colorEnable && ppu.obj.output.above.palette >= 192; + } + if(priority == 0) { + math.above.color = paletteColor(0); + math.below.colorEnable = io.back.colorEnable; + } + + if(!ppu.window.output.below.colorEnable) math.below.colorEnable = false; + math.above.colorEnable = ppu.window.output.above.colorEnable; + if(!math.below.colorEnable) return math.above.colorEnable ? math.above.color : (uint15)0; + + if(io.blendMode && math.transparent) { + math.blendMode = false; + math.colorHalve = false; + } else { + math.blendMode = io.blendMode; + math.colorHalve = io.colorHalve && math.above.colorEnable; + } + + return blend( + math.above.colorEnable ? math.above.color : (uint15)0, + math.blendMode ? math.below.color : fixedColor() + ); +} + +auto PPU::Screen::blend(uint x, uint y) const -> uint15 { + if(!io.colorMode) { //add + if(!math.colorHalve) { + uint sum = x + y; + uint carry = (sum - ((x ^ y) & 0x0421)) & 0x8420; + return (sum - carry) | (carry - (carry >> 5)); + } else { + return (x + y - ((x ^ y) & 0x0421)) >> 1; + } + } else { //sub + uint diff = x - y + 0x8420; + uint borrow = (diff - ((x ^ y) & 0x8420)) & 0x8420; + if(!math.colorHalve) { + return (diff - borrow) & (borrow - (borrow >> 5)); + } else { + return (((diff - borrow) & (borrow - (borrow >> 5))) & 0x7bde) >> 1; + } + } +} + +auto PPU::Screen::paletteColor(uint8 palette) const -> uint15 { + ppu.latch.cgramAddress = palette; + return cgram[palette]; +} + +auto PPU::Screen::directColor(uint8 palette, uint3 paletteGroup) const -> uint15 { + //palette = -------- BBGGGRRR + //group = -------- -----bgr + //output = 0BBb00GG Gg0RRRr0 + return (palette << 7 & 0x6000) + (paletteGroup << 10 & 0x1000) + + (palette << 4 & 0x0380) + (paletteGroup << 5 & 0x0040) + + (palette << 2 & 0x001c) + (paletteGroup << 1 & 0x0002); +} + +auto PPU::Screen::fixedColor() const -> uint15 { + return io.colorBlue << 10 | io.colorGreen << 5 | io.colorRed << 0; +} + +auto PPU::Screen::power() -> void { + random.array((uint8*)cgram, sizeof(cgram)); + for(auto& word : cgram) word &= 0x7fff; + + io.blendMode = random(); + io.directColor = random(); + io.colorMode = random(); + io.colorHalve = random(); + io.bg1.colorEnable = random(); + io.bg2.colorEnable = random(); + io.bg3.colorEnable = random(); + io.bg4.colorEnable = random(); + io.obj.colorEnable = random(); + io.back.colorEnable = random(); + io.colorBlue = random(); + io.colorGreen = random(); + io.colorRed = random(); +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/screen.hpp b/waterbox/bsnescore/bsnes/sfc/ppu/screen.hpp new file mode 100644 index 0000000000..87dac3c898 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/screen.hpp @@ -0,0 +1,47 @@ +struct Screen { + auto scanline() -> void; + auto run() -> void; + auto power() -> void; + + auto below(bool hires) -> uint16; + auto above() -> uint16; + + auto blend(uint x, uint y) const -> uint15; + alwaysinline auto paletteColor(uint8 palette) const -> uint15; + alwaysinline auto directColor(uint8 palette, uint3 paletteGroup) const -> uint15; + alwaysinline auto fixedColor() const -> uint15; + + auto serialize(serializer&) -> void; + + uint16* lineA; + uint16* lineB; + + uint15 cgram[256]; + + struct IO { + uint1 blendMode; + uint1 directColor; + + uint1 colorMode; + uint1 colorHalve; + struct Layer { + uint1 colorEnable; + } bg1, bg2, bg3, bg4, obj, back; + + uint5 colorBlue; + uint5 colorGreen; + uint5 colorRed; + } io; + + struct Math { + struct Screen { + uint15 color; + uint1 colorEnable; + } above, below; + uint1 transparent; + uint1 blendMode; + uint1 colorHalve; + } math; + + friend class PPU; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/serialization.cpp new file mode 100644 index 0000000000..9a022211e4 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/serialization.cpp @@ -0,0 +1,281 @@ +auto PPU::serialize(serializer& s) -> void { + s.integer(display.interlace); + s.integer(display.overscan); + s.integer(display.vdisp); + + if(system.fastPPU()) { + return ppufast.serialize(s); + } + + Thread::serialize(s); + PPUcounter::serialize(s); + + s.integer(vram.mask); + s.array(vram.data, vram.mask + 1); + + s.integer(ppu1.version); + s.integer(ppu1.mdr); + + s.integer(ppu2.version); + s.integer(ppu2.mdr); + + s.integer(latch.vram); + s.integer(latch.oam); + s.integer(latch.cgram); + s.integer(latch.bgofsPPU1); + s.integer(latch.bgofsPPU2); + s.integer(latch.mode7); + s.integer(latch.counters); + s.integer(latch.hcounter); + s.integer(latch.vcounter); + + s.integer(latch.oamAddress); + s.integer(latch.cgramAddress); + + s.integer(io.displayDisable); + s.integer(io.displayBrightness); + + s.integer(io.oamBaseAddress); + s.integer(io.oamAddress); + s.integer(io.oamPriority); + + s.integer(io.bgMode); + s.integer(io.bgPriority); + + s.integer(io.hoffsetMode7); + s.integer(io.voffsetMode7); + + s.integer(io.vramIncrementMode); + s.integer(io.vramMapping); + s.integer(io.vramIncrementSize); + + s.integer(io.vramAddress); + + s.integer(io.repeatMode7); + s.integer(io.vflipMode7); + s.integer(io.hflipMode7); + + s.integer(io.m7a); + s.integer(io.m7b); + s.integer(io.m7c); + s.integer(io.m7d); + s.integer(io.m7x); + s.integer(io.m7y); + + s.integer(io.cgramAddress); + s.integer(io.cgramAddressLatch); + + s.integer(io.extbg); + s.integer(io.pseudoHires); + s.integer(io.overscan); + s.integer(io.interlace); + + s.integer(io.hcounter); + s.integer(io.vcounter); + + mosaic.serialize(s); + bg1.serialize(s); + bg2.serialize(s); + bg3.serialize(s); + bg4.serialize(s); + obj.serialize(s); + window.serialize(s); + screen.serialize(s); +} + +auto PPU::Mosaic::serialize(serializer& s) -> void { + s.integer(size); + s.integer(vcounter); +} + +auto PPU::Background::serialize(serializer& s) -> void { + s.integer(io.tiledataAddress); + s.integer(io.screenAddress); + s.integer(io.screenSize); + s.integer(io.tileSize); + s.integer(io.mode); + s.array(io.priority); + s.integer(io.aboveEnable); + s.integer(io.belowEnable); + s.integer(io.hoffset); + s.integer(io.voffset); + + s.integer(output.above.priority); + s.integer(output.above.palette); + s.integer(output.above.paletteGroup); + + s.integer(output.below.priority); + s.integer(output.below.palette); + s.integer(output.below.paletteGroup); + + s.integer(mosaic.enable); + s.integer(mosaic.hcounter); + s.integer(mosaic.hoffset); + + s.integer(mosaic.pixel.priority); + s.integer(mosaic.pixel.palette); + s.integer(mosaic.pixel.paletteGroup); + + s.integer(opt.hoffset); + s.integer(opt.voffset); + + for(auto& tile : tiles) { + s.integer(tile.address); + s.integer(tile.character); + s.integer(tile.palette); + s.integer(tile.paletteGroup); + s.integer(tile.priority); + s.integer(tile.hmirror); + s.integer(tile.vmirror); + s.array(tile.data); + } + + s.integer(renderingIndex); + s.integer(pixelCounter); +} + +auto PPU::Object::serialize(serializer& s) -> void { + for(auto& object : oam.object) { + s.integer(object.x); + s.integer(object.y); + s.integer(object.character); + s.integer(object.nameselect); + s.integer(object.vflip); + s.integer(object.hflip); + s.integer(object.priority); + s.integer(object.palette); + s.integer(object.size); + } + + s.integer(io.aboveEnable); + s.integer(io.belowEnable); + s.integer(io.interlace); + + s.integer(io.baseSize); + s.integer(io.nameselect); + s.integer(io.tiledataAddress); + s.integer(io.firstSprite); + + s.array(io.priority); + + s.integer(io.timeOver); + s.integer(io.rangeOver); + + s.integer(latch.firstSprite); + + s.integer(t.x); + s.integer(t.y); + + s.integer(t.itemCount); + s.integer(t.tileCount); + + s.integer(t.active); + for(auto p : range(2)) { + for(auto n : range(32)) { + s.integer(t.item[p][n].valid); + s.integer(t.item[p][n].index); + } + for(auto n : range(34)) { + s.integer(t.tile[p][n].valid); + s.integer(t.tile[p][n].x); + s.integer(t.tile[p][n].priority); + s.integer(t.tile[p][n].palette); + s.integer(t.tile[p][n].hflip); + s.integer(t.tile[p][n].data); + } + } + + s.integer(output.above.priority); + s.integer(output.above.palette); + + s.integer(output.below.priority); + s.integer(output.below.palette); +} + +auto PPU::Window::serialize(serializer& s) -> void { + s.integer(io.bg1.oneEnable); + s.integer(io.bg1.oneInvert); + s.integer(io.bg1.twoEnable); + s.integer(io.bg1.twoInvert); + s.integer(io.bg1.mask); + s.integer(io.bg1.aboveEnable); + s.integer(io.bg1.belowEnable); + + s.integer(io.bg2.oneEnable); + s.integer(io.bg2.oneInvert); + s.integer(io.bg2.twoEnable); + s.integer(io.bg2.twoInvert); + s.integer(io.bg2.mask); + s.integer(io.bg2.aboveEnable); + s.integer(io.bg2.belowEnable); + + s.integer(io.bg3.oneEnable); + s.integer(io.bg3.oneInvert); + s.integer(io.bg3.twoEnable); + s.integer(io.bg3.twoInvert); + s.integer(io.bg3.mask); + s.integer(io.bg3.aboveEnable); + s.integer(io.bg3.belowEnable); + + s.integer(io.bg4.oneEnable); + s.integer(io.bg4.oneInvert); + s.integer(io.bg4.twoEnable); + s.integer(io.bg4.twoInvert); + s.integer(io.bg4.mask); + s.integer(io.bg4.aboveEnable); + s.integer(io.bg4.belowEnable); + + s.integer(io.obj.oneEnable); + s.integer(io.obj.oneInvert); + s.integer(io.obj.twoEnable); + s.integer(io.obj.twoInvert); + s.integer(io.obj.mask); + s.integer(io.obj.aboveEnable); + s.integer(io.obj.belowEnable); + + s.integer(io.col.oneEnable); + s.integer(io.col.oneInvert); + s.integer(io.col.twoEnable); + s.integer(io.col.twoInvert); + s.integer(io.col.mask); + s.integer(io.col.aboveMask); + s.integer(io.col.belowMask); + + s.integer(io.oneLeft); + s.integer(io.oneRight); + s.integer(io.twoLeft); + s.integer(io.twoRight); + + s.integer(output.above.colorEnable); + s.integer(output.below.colorEnable); + + s.integer(x); +} + +auto PPU::Screen::serialize(serializer& s) -> void { + s.array(cgram); + + s.integer(io.blendMode); + s.integer(io.directColor); + + s.integer(io.colorMode); + s.integer(io.colorHalve); + s.integer(io.bg1.colorEnable); + s.integer(io.bg2.colorEnable); + s.integer(io.bg3.colorEnable); + s.integer(io.bg4.colorEnable); + s.integer(io.obj.colorEnable); + s.integer(io.back.colorEnable); + + s.integer(io.colorBlue); + s.integer(io.colorGreen); + s.integer(io.colorRed); + + s.integer(math.above.color); + s.integer(math.above.colorEnable); + s.integer(math.below.color); + s.integer(math.below.colorEnable); + s.integer(math.transparent); + s.integer(math.blendMode); + s.integer(math.colorHalve); +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/window.cpp b/waterbox/bsnescore/bsnes/sfc/ppu/window.cpp new file mode 100644 index 0000000000..96d83d513e --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/window.cpp @@ -0,0 +1,107 @@ +auto PPU::Window::scanline() -> void { + x = 0; +} + +auto PPU::Window::run() -> void { + bool one = (x >= io.oneLeft && x <= io.oneRight); + bool two = (x >= io.twoLeft && x <= io.twoRight); + x++; + + if(test(io.bg1.oneEnable, one ^ io.bg1.oneInvert, io.bg1.twoEnable, two ^ io.bg1.twoInvert, io.bg1.mask)) { + if(io.bg1.aboveEnable) ppu.bg1.output.above.priority = 0; + if(io.bg1.belowEnable) ppu.bg1.output.below.priority = 0; + } + + if(test(io.bg2.oneEnable, one ^ io.bg2.oneInvert, io.bg2.twoEnable, two ^ io.bg2.twoInvert, io.bg2.mask)) { + if(io.bg2.aboveEnable) ppu.bg2.output.above.priority = 0; + if(io.bg2.belowEnable) ppu.bg2.output.below.priority = 0; + } + + if(test(io.bg3.oneEnable, one ^ io.bg3.oneInvert, io.bg3.twoEnable, two ^ io.bg3.twoInvert, io.bg3.mask)) { + if(io.bg3.aboveEnable) ppu.bg3.output.above.priority = 0; + if(io.bg3.belowEnable) ppu.bg3.output.below.priority = 0; + } + + if(test(io.bg4.oneEnable, one ^ io.bg4.oneInvert, io.bg4.twoEnable, two ^ io.bg4.twoInvert, io.bg4.mask)) { + if(io.bg4.aboveEnable) ppu.bg4.output.above.priority = 0; + if(io.bg4.belowEnable) ppu.bg4.output.below.priority = 0; + } + + if(test(io.obj.oneEnable, one ^ io.obj.oneInvert, io.obj.twoEnable, two ^ io.obj.twoInvert, io.obj.mask)) { + if(io.obj.aboveEnable) ppu.obj.output.above.priority = 0; + if(io.obj.belowEnable) ppu.obj.output.below.priority = 0; + } + + bool value = test(io.col.oneEnable, one ^ io.col.oneInvert, io.col.twoEnable, two ^ io.col.twoInvert, io.col.mask); + bool array[] = {true, value, !value, false}; + output.above.colorEnable = array[io.col.aboveMask]; + output.below.colorEnable = array[io.col.belowMask]; +} + +auto PPU::Window::test(bool oneEnable, bool one, bool twoEnable, bool two, uint mask) -> bool { + if(!oneEnable) return two && twoEnable; + if(!twoEnable) return one; + if(mask == 0) return (one | two); + if(mask == 1) return (one & two); + return (one ^ two) == 3 - mask; +} + +auto PPU::Window::power() -> void { + io.bg1.oneEnable = random(); + io.bg1.oneInvert = random(); + io.bg1.twoEnable = random(); + io.bg1.twoInvert = random(); + io.bg1.mask = random(); + io.bg1.aboveEnable = random(); + io.bg1.belowEnable = random(); + + io.bg2.oneEnable = random(); + io.bg2.oneInvert = random(); + io.bg2.twoEnable = random(); + io.bg2.twoInvert = random(); + io.bg2.mask = random(); + io.bg2.aboveEnable = random(); + io.bg2.belowEnable = random(); + + io.bg3.oneEnable = random(); + io.bg3.oneInvert = random(); + io.bg3.twoEnable = random(); + io.bg3.twoInvert = random(); + io.bg3.mask = random(); + io.bg3.aboveEnable = random(); + io.bg3.belowEnable = random(); + + io.bg4.oneEnable = random(); + io.bg4.oneInvert = random(); + io.bg4.twoEnable = random(); + io.bg4.twoInvert = random(); + io.bg4.mask = random(); + io.bg4.aboveEnable = random(); + io.bg4.belowEnable = random(); + + io.obj.oneEnable = random(); + io.obj.oneInvert = random(); + io.obj.twoEnable = random(); + io.obj.twoInvert = random(); + io.obj.mask = random(); + io.obj.aboveEnable = random(); + io.obj.belowEnable = random(); + + io.col.oneEnable = random(); + io.col.oneInvert = random(); + io.col.twoEnable = random(); + io.col.twoInvert = random(); + io.col.mask = random(); + io.col.aboveMask = random(); + io.col.belowMask = random(); + + io.oneLeft = random(); + io.oneRight = random(); + io.twoLeft = random(); + io.twoRight = random(); + + output.above.colorEnable = 0; + output.below.colorEnable = 0; + + x = 0; +} diff --git a/waterbox/bsnescore/bsnes/sfc/ppu/window.hpp b/waterbox/bsnescore/bsnes/sfc/ppu/window.hpp new file mode 100644 index 0000000000..ff3d8453a7 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/ppu/window.hpp @@ -0,0 +1,47 @@ +struct Window { + auto scanline() -> void; + auto run() -> void; + auto test(bool oneEnable, bool one, bool twoEnable, bool two, uint mask) -> bool; + auto power() -> void; + + auto serialize(serializer&) -> void; + + struct IO { + struct Layer { + bool oneEnable; + bool oneInvert; + bool twoEnable; + bool twoInvert; + uint2 mask; + bool aboveEnable; + bool belowEnable; + } bg1, bg2, bg3, bg4, obj; + + struct Color { + bool oneEnable; + bool oneInvert; + bool twoEnable; + bool twoInvert; + uint2 mask; + uint2 aboveMask; + uint2 belowMask; + } col; + + uint8 oneLeft; + uint8 oneRight; + uint8 twoLeft; + uint8 twoRight; + } io; + + struct Output { + struct Pixel { + bool colorEnable; + } above, below; + } output; + + struct { + uint x; + }; + + friend class PPU; +}; diff --git a/waterbox/bsnescore/bsnes/sfc/sfc.hpp b/waterbox/bsnescore/bsnes/sfc/sfc.hpp new file mode 100644 index 0000000000..a3f003aad2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/sfc.hpp @@ -0,0 +1,147 @@ +#pragma once + +//license: GPLv3 +//started: 2004-10-14 + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +extern "C" { + #include + #include +} + +namespace SuperFamicom { + #define platform Emulator::platform + namespace File = Emulator::File; + using Random = Emulator::Random; + using Cheat = Emulator::Cheat; + extern Random random; + extern Cheat cheat; + + struct Scheduler { + enum class Mode : uint { Run, Synchronize } mode; + enum class Event : uint { Frame, Synchronized, Desynchronized } event; + + cothread_t host = nullptr; + cothread_t active = nullptr; + bool desynchronized = false; + + auto enter() -> void { + host = co_active(); + co_switch(active); + } + + auto leave(Event event_) -> void { + event = event_; + active = co_active(); + co_switch(host); + } + + auto resume(cothread_t thread) -> void { + if(mode == Mode::Synchronize) desynchronized = true; + co_switch(thread); + } + + inline auto synchronizing() const -> bool { + return mode == Mode::Synchronize; + } + + inline auto synchronize() -> void { + if(mode == Mode::Synchronize) { + if(desynchronized) { + desynchronized = false; + leave(Event::Desynchronized); + } else { + leave(Event::Synchronized); + } + } + } + + inline auto desynchronize() -> void { + desynchronized = true; + } + }; + extern Scheduler scheduler; + + struct Thread { + enum : uint { Size = 4_KiB * sizeof(void*) }; + + auto create(auto (*entrypoint)() -> void, uint frequency_) -> void { + if(thread) co_delete(thread); + thread = co_create(Thread::Size, entrypoint); + frequency = frequency_; + clock = 0; + } + + auto active() const -> bool { + return thread == co_active(); + } + + auto serialize(serializer& s) -> void { + s.integer(frequency); + s.integer(clock); + } + + auto serializeStack(serializer& s) -> void { + static uint8_t stack[Thread::Size]; + bool active = co_active() == thread; + + if(s.mode() == serializer::Size) { + s.array(stack, Thread::Size); + s.boolean(active); + } + + if(s.mode() == serializer::Load) { + s.array(stack, Thread::Size); + s.boolean(active); + memory::copy(thread, stack, Thread::Size); + if(active) scheduler.active = thread; + } + + if(s.mode() == serializer::Save) { + memory::copy(stack, thread, Thread::Size); + s.array(stack, Thread::Size); + s.boolean(active); + } + } + + cothread_t thread = nullptr; + uint32_t frequency = 0; + int64_t clock = 0; + }; + + struct Region { + static inline auto NTSC() -> bool; + static inline auto PAL() -> bool; + }; + + #include + #include + #include + + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include + + #include + #include +} + +#include diff --git a/waterbox/bsnescore/bsnes/sfc/slot/bsmemory/bsmemory.cpp b/waterbox/bsnescore/bsnes/sfc/slot/bsmemory/bsmemory.cpp new file mode 100644 index 0000000000..3adf0750af --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/slot/bsmemory/bsmemory.cpp @@ -0,0 +1,577 @@ +#include + +namespace SuperFamicom { + +BSMemory bsmemory; +#include "serialization.cpp" + +BSMemory::BSMemory() { + page.self = this; + uint blockID = 0; + for(auto& block : blocks) block.self = this, block.id = blockID++; + block.self = this; +} + +auto BSMemory::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto BSMemory::Enter() -> void { + while(true) { + scheduler.synchronize(); + bsmemory.main(); + } +} + +auto BSMemory::main() -> void { + if(ROM) return step(1'000'000); //1 second + + for(uint6 id : range(block.count())) { + if(block(id).erasing) return block(id).erase(); + block(id).status.ready = 1; + } + + compatible.status.ready = 1; + global.status.ready = 1; + step(10'000); //10 milliseconds +} + +auto BSMemory::step(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; + synchronizeCPU(); +} + +auto BSMemory::load() -> bool { + if(ROM) return true; + + if(size() != 0x100000 && size() != 0x200000 && size() != 0x400000) { + memory.reset(); + return false; + } + + chip.vendor = 0x00'b0; //Sharp + if(size() == 0x100000) chip.device = 0x66'a8; //LH28F800SU + if(size() == 0x200000) chip.device = 0x66'88; //LH28F016SU + if(size() == 0x400000) chip.device = 0x66'88; //LH28F032SU (same device ID as LH28F016SU per datasheet) + chip.serial = 0x00'01'23'45'67'89ull; //serial# should be unique for every cartridge ... + + //page buffer values decay to random noise upon losing power to the flash chip + //the randomness is high entropy (at least compared to SNES SRAM/DRAM chips) + for(auto& byte : page.buffer[0]) byte = random(); + for(auto& byte : page.buffer[1]) byte = random(); + + for(auto& block : blocks) { + block.erased = 1; + block.locked = 1; + } + + if(auto fp = platform->open(pathID, "metadata.bml", File::Read, File::Optional)) { + auto document = BML::unserialize(fp->reads()); + if(auto node = document["flash/vendor"]) { + chip.vendor = node.natural(); + } + if(auto node = document["flash/device"]) { + chip.device = node.natural(); + } + if(auto node = document["flash/serial"]) { + chip.serial = node.natural(); + } + for(uint id : range(block.count())) { + if(auto node = document[{"flash/block(id=", id, ")"}]) { + if(auto erased = node["erased"]) { + block(id).erased = erased.natural(); + } + if(auto locked = node["locked"]) { + block(id).locked = locked.boolean(); + } + } + } + } + + return true; +} + +auto BSMemory::unload() -> void { + if(ROM) return memory.reset(); + + if(auto fp = platform->open(pathID, "metadata.bml", File::Write, File::Optional)) { + string manifest; + manifest.append("flash\n"); + manifest.append(" vendor: 0x", hex(chip.vendor, 4L), "\n"); + manifest.append(" device: 0x", hex(chip.device, 4L), "\n"); + manifest.append(" serial: 0x", hex(chip.serial, 12L), "\n"); + for(uint6 id : range(block.count())) { + manifest.append(" block\n"); + manifest.append(" id: ", id, "\n"); + manifest.append(" erased: ", (uint)block(id).erased, "\n"); + manifest.append(" locked: ", (bool)block(id).locked, "\n"); + } + fp->writes(manifest); + } + + memory.reset(); +} + +auto BSMemory::power() -> void { + create(Enter, 1'000'000); //microseconds + + for(auto& block : blocks) { + block.erasing = 0; + block.status = {}; + } + compatible.status = {}; + global.status = {}; + mode = Mode::Flash; + readyBusyMode = ReadyBusyMode::Disable; + queue.flush(); +} + +auto BSMemory::data() -> uint8* { + return memory.data(); +} + +auto BSMemory::size() const -> uint { + return memory.size(); +} + +auto BSMemory::read(uint address, uint8 data) -> uint8 { + if(!size()) return data; + if(ROM) return memory.read(bus.mirror(address, size())); + + if(mode == Mode::Chip) { + if(address == 0) return chip.vendor; //only appears once + if(address == 1) return chip.device; //only appears once + if((uint3)address == 2) return 0x63; //unknown constant: repeats every eight bytes + return 0x20; //unknown constant: fills in all remaining bytes + } + + if(mode == Mode::Page) { + return page.read(address); + } + + if(mode == Mode::CompatibleStatus) { + return compatible.status(); + } + + if(mode == Mode::ExtendedStatus) { + if((uint16)address == 0x0002) return block(address >> block.bitCount()).status(); + if((uint16)address == 0x0004) return global.status(); + return 0x00; //reserved: always zero + } + + return block(address >> block.bitCount()).read(address); //Mode::Flash +} + +auto BSMemory::write(uint address, uint8 data) -> void { + if(!size() || ROM) return; + queue.push(address, data); + + //write page to flash + if(queue.data(0) == 0x0c) { + if(queue.size() < 3) return; + uint16 count; //1 - 65536 + count = queue.data(!(queue.address(1) & 1) ? 1 : 2) << 0; + count |= queue.data(!(queue.address(1) & 1) ? 2 : 1) << 8; + uint address = queue.address(2); + do { + block(address >> block.bitCount()).write(address, page.read(address)); + address++; + } while(count--); + page.swap(); + mode = Mode::CompatibleStatus; + return queue.flush(); + } + + //write byte + if(queue.data(0) == 0x10) { + if(queue.size() < 2) return; + block(queue.address(1) >> block.bitCount()).write(queue.address(1), queue.data(1)); + mode = Mode::CompatibleStatus; + return queue.flush(); + } + + //erase block + if(queue.data(0) == 0x20) { + if(queue.size() < 2) return; + if(queue.data(1) != 0xd0) return failed(), queue.flush(); + block(queue.address(1) >> block.bitCount()).erase(); + mode = Mode::CompatibleStatus; + return queue.flush(); + } + + //LH28F800SUT-ZI specific? (undocumented / unavailable? for the LH28F800SU) + //write signature, identifier, serial# into current page buffer, then swap page buffers + if(queue.data(0) == 0x38) { + if(queue.size() < 2) return; + if(queue.data(1) != 0xd0) return failed(), queue.flush(); + page.write(0x00, 0x4d); //'M' (memory) + page.write(0x02, 0x50); //'P' (pack) + page.write(0x04, 0x04); //unknown constant (maybe block count? eg 1<<4 = 16 blocks) + page.write(0x06, 0x10 | (uint4)log2(size() >> 10)); //d0-d3 = size; d4-d7 = type (1) + page.write(0x08, chip.serial.byte(5)); //serial# (big endian; BCD format) + page.write(0x0a, chip.serial.byte(4)); //smallest observed value: + page.write(0x0c, chip.serial.byte(3)); // 0x00'00'10'62'62'39 + page.write(0x0e, chip.serial.byte(2)); //largest observed value: + page.write(0x10, chip.serial.byte(1)); // 0x00'91'90'70'31'03 + page.write(0x12, chip.serial.byte(0)); //most values are: 0x00'0x'xx'xx'xx'xx + page.swap(); + return queue.flush(); + } + + //write byte + if(queue.data(0) == 0x40) { + if(queue.size() < 2) return; + block(queue.address(1) >> block.bitCount()).write(queue.address(1), queue.data(1)); + mode = Mode::CompatibleStatus; + return queue.flush(); + } + + //clear status register + if(queue.data(0) == 0x50) { + for(uint6 id : range(block.count())) { + block(id).status.vppLow = 0; + block(id).status.failed = 0; + } + compatible.status.vppLow = 0; + compatible.status.writeFailed = 0; + compatible.status.eraseFailed = 0; + global.status.failed = 0; + return queue.flush(); + } + + //read compatible status register + if(queue.data(0) == 0x70) { + mode = Mode::CompatibleStatus; + return queue.flush(); + } + + //read extended status registers + if(queue.data(0) == 0x71) { + mode = Mode::ExtendedStatus; + return queue.flush(); + } + + //page buffer swap + if(queue.data(0) == 0x72) { + page.swap(); + return queue.flush(); + } + + //single load to page buffer + if(queue.data(0) == 0x74) { + if(queue.size() < 2) return; + page.write(queue.address(1), queue.data(1)); + return queue.flush(); + } + + //read page buffer + if(queue.data(0) == 0x75) { + mode = Mode::Page; + return queue.flush(); + } + + //lock block + if(queue.data(0) == 0x77) { + if(queue.size() < 2) return; + if(queue.data(1) != 0xd0) return failed(), queue.flush(); + block(queue.address(1) >> block.bitCount()).lock(); + return queue.flush(); + } + + //abort + //(unsupported) + if(queue.data(0) == 0x80) { + global.status.sleeping = 1; //abort seems to put the chip into sleep mode + return queue.flush(); + } + + //read chip identifiers + if(queue.data(0) == 0x90) { + mode = Mode::Chip; + return queue.flush(); + } + + //update ry/by mode + //(unsupported) + if(queue.data(0) == 0x96) { + if(queue.size() < 2) return; + if(queue.data(1) == 0x01) readyBusyMode = ReadyBusyMode::EnableToLevelMode; + if(queue.data(1) == 0x02) readyBusyMode = ReadyBusyMode::PulseOnWrite; + if(queue.data(1) == 0x03) readyBusyMode = ReadyBusyMode::PulseOnErase; + if(queue.data(1) == 0x04) readyBusyMode = ReadyBusyMode::Disable; + return queue.flush(); + } + + //upload lock status bits + if(queue.data(0) == 0x97) { + if(queue.size() < 2) return; + if(queue.data(1) != 0xd0) return failed(), queue.flush(); + for(uint6 id : range(block.count())) block(id).update(); + return queue.flush(); + } + + //upload device information (number of erase cycles per block) + if(queue.data(0) == 0x99) { + if(queue.size() < 2) return; + if(queue.data(1) != 0xd0) return failed(), queue.flush(); + page.write(0x06, 0x06); //unknown constant + page.write(0x07, 0x00); //unknown constant + for(uint6 id : range(block.count())) { + uint8 address; + address += (id >> 0 & 3) * 0x08; //verified for LH28F800SUT-ZI + address += (id >> 2 & 3) * 0x40; //verified for LH28F800SUT-ZI + address += (id >> 4 & 1) * 0x20; //guessed for LH28F016SU + address += (id >> 5 & 1) * 0x04; //guessed for LH28F032SU; will overwrite unknown constants + uint32 erased = 1 << 31 | block(id).erased; //unknown if d31 is set when erased == 0 + for(uint2 byte : range(4)) { + page.write(address + byte, erased >> byte * 8); //little endian + } + } + page.swap(); + return queue.flush(); + } + + //erase all blocks + if(queue.data(0) == 0xa7) { + if(queue.size() < 2) return; + if(queue.data(1) != 0xd0) return failed(), queue.flush(); + for(uint6 id : range(block.count())) block(id).erase(); + mode = Mode::CompatibleStatus; + return queue.flush(); + } + + //erase suspend/resume + //(unsupported) + if(queue.data(0) == 0xb0) { + if(queue.size() < 2) return; + if(queue.data(1) != 0xd0) return failed(), queue.flush(); + mode = Mode::CompatibleStatus; + return queue.flush(); + } + + //sequential load to page buffer + if(queue.data(0) == 0xe0) { + if(queue.size() < 4) return; //command length = 3 + count + uint16 count; //1 - 65536 + count = queue.data(1) << 0; //endian order not affected by queue.address(1).bit(0) + count |= queue.data(2) << 8; + page.write(queue.address(3), queue.data(3)); + if(count--) { + queue.history[1].data = count >> 0; + queue.history[2].data = count >> 8; + return queue.pop(); //hack to avoid needing a 65539-entry queue + } else { + return queue.flush(); + } + } + + //sleep + //(unsupported) + if(queue.data(0) == 0xf0) { + //it is currently unknown how to exit sleep mode; other than via chip reset + global.status.sleeping = 1; + return queue.flush(); + } + + //write word + if(queue.data(0) == 0xfb) { + if(queue.size() < 3) return; + uint16 value; + value = queue.data(!(queue.address(1) & 1) ? 1 : 2) << 0; + value |= queue.data(!(queue.address(1) & 1) ? 2 : 1) << 8; + //writes are always word-aligned: a0 toggles, rather than increments + block(queue.address(2) >> block.bitCount()).write(queue.address(2) ^ 0, value >> 0); + block(queue.address(2) >> block.bitCount()).write(queue.address(2) ^ 1, value >> 8); + mode = Mode::CompatibleStatus; + return queue.flush(); + } + + //read flash memory + if(queue.data(0) == 0xff) { + mode = Mode::Flash; + return queue.flush(); + } + + //unknown command + return queue.flush(); +} + +// + +auto BSMemory::failed() -> void { + compatible.status.writeFailed = 1; //datasheet specifies these are for write/erase failures + compatible.status.eraseFailed = 1; //yet all errors seem to set both of these bits ... + global.status.failed = 1; +} + +// + +auto BSMemory::Page::swap() -> void { + self->global.status.page ^= 1; +} + +auto BSMemory::Page::read(uint8 address) -> uint8 { + return buffer[self->global.status.page][address]; +} + +auto BSMemory::Page::write(uint8 address, uint8 data) -> void { + buffer[self->global.status.page][address] = data; +} + +// + +auto BSMemory::BlockInformation::bitCount() const -> uint { return 16; } +auto BSMemory::BlockInformation::byteCount() const -> uint { return 1 << bitCount(); } +auto BSMemory::BlockInformation::count() const -> uint { return self->size() >> bitCount(); } + +// + +auto BSMemory::Block::read(uint address) -> uint8 { + address &= byteCount() - 1; + return self->memory.read(id << bitCount() | address); +} + +auto BSMemory::Block::write(uint address, uint8 data) -> void { + if(!self->writable() && status.locked) { + status.failed = 1; + return self->failed(); + } + + //writes to flash can only clear bits + address &= byteCount() - 1; + data &= self->memory.read(id << bitCount() | address); + self->memory.write(id << bitCount() | address, data); +} + +auto BSMemory::Block::erase() -> void { + if(cpu.active()) { + //erase command runs even if the block is not currently writable + erasing = 1; + status.ready = 0; + self->compatible.status.ready = 0; + self->global.status.ready = 0; + return; + } + + self->step(300'000); //300 milliseconds are required to erase one block + erasing = 0; + + if(!self->writable() && status.locked) { + //does not set any failure bits when unsuccessful ... + return; + } + + for(uint address : range(byteCount())) { + self->memory.write(id << bitCount() | address, 0xff); + } + + erased++; + locked = 0; + status.locked = 0; +} + +auto BSMemory::Block::lock() -> void { + if(!self->writable()) { + //produces a failure result even if the page was already locked + status.failed = 1; + return self->failed(); + } + + locked = 1; + status.locked = 1; +} + +//at reset, the locked status bit is set +//this command refreshes the true locked status bit from the device +auto BSMemory::Block::update() -> void { + status.locked = locked; +} + +// + +auto BSMemory::Blocks::operator()(uint6 id) -> Block& { + return self->blocks[id & count() - 1]; +} + +// + +auto BSMemory::Block::Status::operator()() -> uint8 { + return ( //d0-d1 are reserved; always return zero + vppLow << 2 + | queueFull << 3 + | aborted << 4 + | failed << 5 + |!locked << 6 //note: technically the unlocked flag; so locked is inverted here + | ready << 7 + ); +} + +// + +auto BSMemory::Compatible::Status::operator()() -> uint8 { + return ( //d0-d2 are reserved; always return zero + vppLow << 3 + | writeFailed << 4 + | eraseFailed << 5 + | eraseSuspended << 6 + | ready << 7 + ); +} + +// + +auto BSMemory::Global::Status::operator()() -> uint8 { + return ( + page << 0 + | pageReady << 1 + | pageAvailable << 2 + | queueFull << 3 + | sleeping << 4 + | failed << 5 + | suspended << 6 + | ready << 7 + ); +} + +// + +auto BSMemory::Queue::flush() -> void { + history[0] = {}; + history[1] = {}; + history[2] = {}; + history[3] = {}; +} + +auto BSMemory::Queue::pop() -> void { + if(history[3].valid) { history[3] = {}; return; } + if(history[2].valid) { history[2] = {}; return; } + if(history[1].valid) { history[1] = {}; return; } + if(history[0].valid) { history[0] = {}; return; } +} + +auto BSMemory::Queue::push(uint24 address, uint8 data) -> void { + if(!history[0].valid) { history[0] = {true, address, data}; return; } + if(!history[1].valid) { history[1] = {true, address, data}; return; } + if(!history[2].valid) { history[2] = {true, address, data}; return; } + if(!history[3].valid) { history[3] = {true, address, data}; return; } +} + +auto BSMemory::Queue::size() -> uint { + if(history[3].valid) return 4; + if(history[2].valid) return 3; + if(history[1].valid) return 2; + if(history[0].valid) return 1; + return 0; +} + +auto BSMemory::Queue::address(uint index) -> uint24 { + if(index > 3 || !history[index].valid) return 0; + return history[index].address; +} + +auto BSMemory::Queue::data(uint index) -> uint8 { + if(index > 3 || !history[index].valid) return 0; + return history[index].data; +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/slot/bsmemory/bsmemory.hpp b/waterbox/bsnescore/bsnes/sfc/slot/bsmemory/bsmemory.hpp new file mode 100644 index 0000000000..efa12731b2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/slot/bsmemory/bsmemory.hpp @@ -0,0 +1,168 @@ +//MaskROMs supported: +// Sharp LH5S4TNI (MaskROM 512K x 8-bit) [BSMC-CR-01: BSMC-ZS5J-JPN, BSMC-YS5J-JPN] +// Sharp LH534VNF (MaskROM 512K x 8-bit) [BSMC-BR-01: BSMC-ZX3J-JPN] + +//Flash chips supported: (16-bit modes unsupported) +// Sharp LH28F800SUT-ZI (Flash 16 x 65536 x 8-bit) [BSMC-AF-01: BSMC-HM-JPN] +// Sharp LH28F016SU ??? (Flash 32 x 65536 x 8-bit) [unreleased: experimental] +// Sharp LH28F032SU ??? (Flash 64 x 65536 x 8-bit) [unreleased: experimental] + +//unsupported: +// Sharp LH28F400SU ??? (Flash 32 x 16384 x 8-bit) [unreleased] {vendor ID: 0x00'b0; device ID: 0x66'21} + +//notes: +//timing emulation is only present for block erase commands +//other commands generally complete so quickly that it's unnecessary (eg 70-120ns for writes) +//suspend, resume, abort, ready/busy modes are not supported + +struct BSMemory : Thread, Memory { + uint pathID = 0; + uint ROM = 1; + + auto writable() const { return pin.writable; } + auto writable(bool writable) { pin.writable = !ROM && writable; } + + //bsmemory.cpp + BSMemory(); + auto synchronizeCPU() -> void; + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; + + auto load() -> bool; + auto unload() -> void; + auto power() -> void; + + auto data() -> uint8* override; + auto size() const -> uint override; + auto read(uint address, uint8 data) -> uint8 override; + auto write(uint address, uint8 data) -> void override; + + //serialization.cpp + auto serialize(serializer&) -> void; + + WritableMemory memory; + +private: + struct Pin { + uint1 writable; // => /WP + } pin; + + struct Chip { + uint16 vendor; + uint16 device; + uint48 serial; + } chip; + + struct Page { + BSMemory* self = nullptr; + + auto swap() -> void; + auto read(uint8 address) -> uint8; + auto write(uint8 address, uint8 data) -> void; + + uint8 buffer[2][256]; + } page; + + struct BlockInformation { + BSMemory* self = nullptr; + + inline auto bitCount() const -> uint; + inline auto byteCount() const -> uint; + inline auto count() const -> uint; + }; + + struct Block : BlockInformation { + auto read(uint address) -> uint8; + auto write(uint address, uint8 data) -> void; + auto erase() -> void; + auto lock() -> void; + auto update() -> void; + + uint4 id; + uint32 erased; + uint1 locked; + uint1 erasing; + + struct Status { + auto operator()() -> uint8; + + uint1 vppLow; + uint1 queueFull; + uint1 aborted; + uint1 failed; + uint1 locked = 1; + uint1 ready = 1; + } status; + } blocks[64]; //8mbit = 16; 16mbit = 32; 32mbit = 64 + + struct Blocks : BlockInformation { + auto operator()(uint6 id) -> Block&; + } block; + + struct Compatible { + struct Status { + auto operator()() -> uint8; + + uint1 vppLow; + uint1 writeFailed; + uint1 eraseFailed; + uint1 eraseSuspended; + uint1 ready = 1; + } status; + } compatible; + + struct Global { + struct Status { + auto operator()() -> uint8; + + uint1 page; + uint1 pageReady = 1; + uint1 pageAvailable = 1; + uint1 queueFull; + uint1 sleeping; + uint1 failed; + uint1 suspended; + uint1 ready = 1; + } status; + } global; + + struct Mode { enum : uint { + Flash, + Chip, + Page, + CompatibleStatus, + ExtendedStatus, + };}; + uint3 mode; + + struct ReadyBusyMode { enum : uint { + EnableToLevelMode, + PulseOnWrite, + PulseOnErase, + Disable, + };}; + uint2 readyBusyMode; + + struct Queue { + auto flush() -> void; + auto pop() -> void; + auto push(uint24 address, uint8 data) -> void; + auto size() -> uint; + auto address(uint index) -> uint24; + auto data(uint index) -> uint8; + + //serialization.cpp + auto serialize(serializer&) -> void; + + struct History { + uint1 valid; + uint24 address; + uint8 data; + } history[4]; + } queue; + + auto failed() -> void; +}; + +extern BSMemory bsmemory; diff --git a/waterbox/bsnescore/bsnes/sfc/slot/bsmemory/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/slot/bsmemory/serialization.cpp new file mode 100644 index 0000000000..45e3e212e2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/slot/bsmemory/serialization.cpp @@ -0,0 +1,67 @@ +auto BSMemory::serialize(serializer& s) -> void { + if(ROM) return; + Thread::serialize(s); + + s.array(memory.data(), memory.size()); + + s.integer(pin.writable); + + s.integer(chip.vendor); + s.integer(chip.device); + s.integer(chip.serial); + + s.array(page.buffer[0]); + s.array(page.buffer[1]); + + for(auto& block : blocks) { + s.integer(block.id); + s.integer(block.erased); + s.integer(block.locked); + s.integer(block.erasing); + s.integer(block.status.vppLow); + s.integer(block.status.queueFull); + s.integer(block.status.aborted); + s.integer(block.status.failed); + s.integer(block.status.locked); + s.integer(block.status.ready); + } + + s.integer(compatible.status.vppLow); + s.integer(compatible.status.writeFailed); + s.integer(compatible.status.eraseFailed); + s.integer(compatible.status.eraseSuspended); + s.integer(compatible.status.ready); + + s.integer(global.status.page); + s.integer(global.status.pageReady); + s.integer(global.status.pageAvailable); + s.integer(global.status.queueFull); + s.integer(global.status.sleeping); + s.integer(global.status.failed); + s.integer(global.status.suspended); + s.integer(global.status.ready); + + s.integer(mode); + + s.integer(readyBusyMode); + + queue.serialize(s); +} + +auto BSMemory::Queue::serialize(serializer& s) -> void { + s.integer(history[0].valid); + s.integer(history[0].address); + s.integer(history[0].data); + + s.integer(history[1].valid); + s.integer(history[1].address); + s.integer(history[1].data); + + s.integer(history[2].valid); + s.integer(history[2].address); + s.integer(history[2].data); + + s.integer(history[3].valid); + s.integer(history[3].address); + s.integer(history[3].data); +} diff --git a/waterbox/bsnescore/bsnes/sfc/slot/slot.cpp b/waterbox/bsnescore/bsnes/sfc/slot/slot.cpp new file mode 100644 index 0000000000..0e1d4ab678 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/slot/slot.cpp @@ -0,0 +1,2 @@ +#include +#include diff --git a/waterbox/bsnescore/bsnes/sfc/slot/slot.hpp b/waterbox/bsnescore/bsnes/sfc/slot/slot.hpp new file mode 100644 index 0000000000..1e28ccb1a6 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/slot/slot.hpp @@ -0,0 +1,2 @@ +#include +#include diff --git a/waterbox/bsnescore/bsnes/sfc/slot/sufamiturbo/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/slot/sufamiturbo/serialization.cpp new file mode 100644 index 0000000000..0f97d98c49 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/slot/sufamiturbo/serialization.cpp @@ -0,0 +1,3 @@ +auto SufamiTurboCartridge::serialize(serializer& s) -> void { + s.array(ram.data(), ram.size()); +} diff --git a/waterbox/bsnescore/bsnes/sfc/slot/sufamiturbo/sufamiturbo.cpp b/waterbox/bsnescore/bsnes/sfc/slot/sufamiturbo/sufamiturbo.cpp new file mode 100644 index 0000000000..5c6fc058d2 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/slot/sufamiturbo/sufamiturbo.cpp @@ -0,0 +1,17 @@ +#include + +namespace SuperFamicom { + +#include "serialization.cpp" +SufamiTurboCartridge sufamiturboA; +SufamiTurboCartridge sufamiturboB; + +auto SufamiTurboCartridge::unload() -> void { + rom.reset(); + ram.reset(); +} + +auto SufamiTurboCartridge::power() -> void { +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/slot/sufamiturbo/sufamiturbo.hpp b/waterbox/bsnescore/bsnes/sfc/slot/sufamiturbo/sufamiturbo.hpp new file mode 100644 index 0000000000..6f196bf77a --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/slot/sufamiturbo/sufamiturbo.hpp @@ -0,0 +1,12 @@ +struct SufamiTurboCartridge { + auto unload() -> void; + auto power() -> void; + auto serialize(serializer&) -> void; + + uint pathID = 0; + ReadableMemory rom; + WritableMemory ram; +}; + +extern SufamiTurboCartridge sufamiturboA; +extern SufamiTurboCartridge sufamiturboB; diff --git a/waterbox/bsnescore/bsnes/sfc/smp/io.cpp b/waterbox/bsnescore/bsnes/sfc/smp/io.cpp new file mode 100644 index 0000000000..b154010ce8 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/smp/io.cpp @@ -0,0 +1,182 @@ +auto SMP::portRead(uint2 port) const -> uint8 { + if(port == 0) return io.cpu0; + if(port == 1) return io.cpu1; + if(port == 2) return io.cpu2; + if(port == 3) return io.cpu3; + unreachable; +} + +auto SMP::portWrite(uint2 port, uint8 data) -> void { + if(port == 0) io.apu0 = data; + if(port == 1) io.apu1 = data; + if(port == 2) io.apu2 = data; + if(port == 3) io.apu3 = data; +} + +auto SMP::readIO(uint16 address) -> uint8 { + uint8 data; + + switch(address) { + case 0xf0: //TEST (write-only register) + return 0x00; + + case 0xf1: //CONTROL (write-only register) + return 0x00; + + case 0xf2: //DSPADDR + return io.dspAddr; + + case 0xf3: //DSPDATA + //0x80-0xff are read-only mirrors of 0x00-0x7f + return dsp.read(io.dspAddr & 0x7f); + + case 0xf4: //CPUIO0 + synchronizeCPU(); + return io.apu0; + + case 0xf5: //CPUIO1 + synchronizeCPU(); + return io.apu1; + + case 0xf6: //CPUIO2 + synchronizeCPU(); + return io.apu2; + + case 0xf7: //CPUIO3 + synchronizeCPU(); + return io.apu3; + + case 0xf8: //AUXIO4 + return io.aux4; + + case 0xf9: //AUXIO5 + return io.aux5; + + case 0xfa: //T0TARGET + case 0xfb: //T1TARGET + case 0xfc: //T2TARGET (write-only registers) + return 0x00; + + case 0xfd: //T0OUT (4-bit counter value) + data = timer0.stage3; + timer0.stage3 = 0; + return data; + + case 0xfe: //T1OUT (4-bit counter value) + data = timer1.stage3; + timer1.stage3 = 0; + return data; + + case 0xff: //T2OUT (4-bit counter value) + data = timer2.stage3; + timer2.stage3 = 0; + return data; + } + + unreachable; +} + +auto SMP::writeIO(uint16 address, uint8 data) -> void { + switch(address) { + case 0xf0: //TEST + if(r.p.p) break; //writes only valid when P flag is clear + + io.timersDisable = data >> 0 & 1; + io.ramWritable = data >> 1 & 1; + io.ramDisable = data >> 2 & 1; + io.timersEnable = data >> 3 & 1; + io.externalWaitStates = data >> 4 & 3; + io.internalWaitStates = data >> 6 & 3; + + timer0.synchronizeStage1(); + timer1.synchronizeStage1(); + timer2.synchronizeStage1(); + break; + + case 0xf1: //CONTROL + //0->1 transistion resets timers + if(timer0.enable.raise(data & 0x01)) { + timer0.stage2 = 0; + timer0.stage3 = 0; + } + + if(timer1.enable.raise(data & 0x02)) { + timer1.stage2 = 0; + timer1.stage3 = 0; + } + + if(!timer2.enable.raise(data & 0x04)) { + timer2.stage2 = 0; + timer2.stage3 = 0; + } + + if(data & 0x10) { + synchronizeCPU(); + io.apu0 = 0x00; + io.apu1 = 0x00; + } + + if(data & 0x20) { + synchronizeCPU(); + io.apu2 = 0x00; + io.apu3 = 0x00; + } + + io.iplromEnable = bool(data & 0x80); + break; + + case 0xf2: //DSPADDR + io.dspAddr = data; + break; + + case 0xf3: //DSPDATA + if(io.dspAddr & 0x80) break; //0x80-0xff are read-only mirrors of 0x00-0x7f + dsp.write(io.dspAddr & 0x7f, data); + break; + + case 0xf4: //CPUIO0 + synchronizeCPU(); + io.cpu0 = data; + break; + + case 0xf5: //CPUIO1 + synchronizeCPU(); + io.cpu1 = data; + break; + + case 0xf6: //CPUIO2 + synchronizeCPU(); + io.cpu2 = data; + break; + + case 0xf7: //CPUIO3 + synchronizeCPU(); + io.cpu3 = data; + break; + + case 0xf8: //AUXIO4 + io.aux4 = data; + break; + + case 0xf9: //AUXIO5 + io.aux5 = data; + break; + + case 0xfa: //T0TARGET + timer0.target = data; + break; + + case 0xfb: //T1TARGET + timer1.target = data; + break; + + case 0xfc: //T2TARGET + timer2.target = data; + break; + + case 0xfd: //T0OUT + case 0xfe: //T1OUT + case 0xff: //T2OUT (read-only registers) + break; + } +} diff --git a/waterbox/bsnescore/bsnes/sfc/smp/memory.cpp b/waterbox/bsnescore/bsnes/sfc/smp/memory.cpp new file mode 100644 index 0000000000..32af649e33 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/smp/memory.cpp @@ -0,0 +1,42 @@ +auto SMP::readRAM(uint16 address) -> uint8 { + if(address >= 0xffc0 && io.iplromEnable) return iplrom[address & 0x3f]; + if(io.ramDisable) return 0x5a; //0xff on mini-SNES + return dsp.apuram[address]; +} + +auto SMP::writeRAM(uint16 address, uint8 data) -> void { + //writes to $ffc0-$ffff always go to apuram, even if the iplrom is enabled + if(io.ramWritable && !io.ramDisable) dsp.apuram[address] = data; +} + +auto SMP::idle() -> void { + waitIdle(); +} + +auto SMP::read(uint16 address) -> uint8 { + //Kishin Douji Zenki - Tenchi Meidou requires bus hold delays on CPU I/O reads. + //smp_mem_access_times requires no bus hold delays on APU RAM reads. + if((address & 0xfffc) == 0x00f4) { + wait(address, 1); + uint8 data = readRAM(address); + if((address & 0xfff0) == 0x00f0) data = readIO(address); + wait(address, 1); + return data; + } else { + wait(address, 0); + uint8 data = readRAM(address); + if((address & 0xfff0) == 0x00f0) data = readIO(address); + return data; + } +} + +auto SMP::write(uint16 address, uint8 data) -> void { + wait(address); + writeRAM(address, data); //even IO writes affect underlying RAM + if((address & 0xfff0) == 0x00f0) writeIO(address, data); +} + +auto SMP::readDisassembler(uint16 address) -> uint8 { + if((address & 0xfff0) == 0x00f0) return 0x00; + return readRAM(address); +} diff --git a/waterbox/bsnescore/bsnes/sfc/smp/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/smp/serialization.cpp new file mode 100644 index 0000000000..caaec3aba0 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/smp/serialization.cpp @@ -0,0 +1,55 @@ +auto SMP::serialize(serializer& s) -> void { + SPC700::serialize(s); + Thread::serialize(s); + + s.integer(io.clockCounter); + s.integer(io.dspCounter); + + s.integer(io.apu0); + s.integer(io.apu1); + s.integer(io.apu2); + s.integer(io.apu3); + + s.integer(io.timersDisable); + s.integer(io.ramWritable); + s.integer(io.ramDisable); + s.integer(io.timersEnable); + s.integer(io.externalWaitStates); + s.integer(io.internalWaitStates); + + s.integer(io.iplromEnable); + + s.integer(io.dspAddr); + + s.integer(io.cpu0); + s.integer(io.cpu1); + s.integer(io.cpu2); + s.integer(io.cpu3); + + s.integer(io.aux4); + s.integer(io.aux5); + + s.integer(timer0.stage0); + s.integer(timer0.stage1); + s.integer(timer0.stage2); + s.integer(timer0.stage3); + s.boolean(timer0.line); + s.boolean(timer0.enable); + s.integer(timer0.target); + + s.integer(timer1.stage0); + s.integer(timer1.stage1); + s.integer(timer1.stage2); + s.integer(timer1.stage3); + s.boolean(timer1.line); + s.boolean(timer1.enable); + s.integer(timer1.target); + + s.integer(timer2.stage0); + s.integer(timer2.stage1); + s.integer(timer2.stage2); + s.integer(timer2.stage3); + s.boolean(timer2.line); + s.boolean(timer2.enable); + s.integer(timer2.target); +} diff --git a/waterbox/bsnescore/bsnes/sfc/smp/smp.cpp b/waterbox/bsnescore/bsnes/sfc/smp/smp.cpp new file mode 100644 index 0000000000..4002855a75 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/smp/smp.cpp @@ -0,0 +1,53 @@ +#include + +namespace SuperFamicom { + +SMP smp; +#include "memory.cpp" +#include "io.cpp" +#include "timing.cpp" +#include "serialization.cpp" + +auto SMP::synchronizeCPU() -> void { + if(clock >= 0) scheduler.resume(cpu.thread); +} + +auto SMP::synchronizeDSP() -> void { + while(dsp.clock < 0) dsp.main(); +} + +auto SMP::Enter() -> void { + while(true) { + scheduler.synchronize(); + smp.main(); + } +} + +auto SMP::main() -> void { + if(r.wait) return instructionWait(); + if(r.stop) return instructionStop(); + instruction(); +} + +auto SMP::load() -> bool { + if(auto fp = platform->open(ID::System, "ipl.rom", File::Read, File::Required)) { + fp->read(iplrom, 64); + return true; + } + return false; +} + +auto SMP::power(bool reset) -> void { + SPC700::power(); + create(Enter, system.apuFrequency() / 12.0); + + r.pc.byte.l = iplrom[62]; + r.pc.byte.h = iplrom[63]; + + io = {}; + timer0 = {}; + timer1 = {}; + timer2 = {}; +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/smp/smp.hpp b/waterbox/bsnescore/bsnes/sfc/smp/smp.hpp new file mode 100644 index 0000000000..4b13dc6ae1 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/smp/smp.hpp @@ -0,0 +1,100 @@ +//Sony CXP1100Q-1 + +struct SMP : Processor::SPC700, Thread { + inline auto synchronizing() const -> bool override { return scheduler.synchronizing(); } + + //io.cpp + auto portRead(uint2 port) const -> uint8; + auto portWrite(uint2 port, uint8 data) -> void; + + //smp.cpp + auto synchronizeCPU() -> void; + auto synchronizeDSP() -> void; + static auto Enter() -> void; + auto main() -> void; + auto load() -> bool; + auto power(bool reset) -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint8 iplrom[64]; + +private: + struct IO { + //timing + uint clockCounter = 0; + uint dspCounter = 0; + + //external + uint8 apu0 = 0; + uint8 apu1 = 0; + uint8 apu2 = 0; + uint8 apu3 = 0; + + //$00f0 + uint1 timersDisable = 0; + uint1 ramWritable = 1; + uint1 ramDisable = 0; + uint1 timersEnable = 1; + uint2 externalWaitStates = 0; + uint2 internalWaitStates = 0; + + //$00f1 + uint1 iplromEnable = 1; + + //$00f2 + uint8 dspAddr = 0; + + //$00f4-00f7 + uint8 cpu0 = 0; + uint8 cpu1 = 0; + uint8 cpu2 = 0; + uint8 cpu3 = 0; + + //$00f8-00f9 + uint8 aux4 = 0; + uint8 aux5 = 0; + } io; + + //memory.cpp + inline auto readRAM(uint16 address) -> uint8; + inline auto writeRAM(uint16 address, uint8 data) -> void; + + auto idle() -> void override; + auto read(uint16 address) -> uint8 override; + auto write(uint16 address, uint8 data) -> void override; + + auto readDisassembler(uint16 address) -> uint8 override; + + //io.cpp + inline auto readIO(uint16 address) -> uint8; + inline auto writeIO(uint16 address, uint8 data) -> void; + + //timing.cpp + template + struct Timer { + uint8 stage0 = 0; + uint8 stage1 = 0; + uint8 stage2 = 0; + uint4 stage3 = 0; + boolean line = 0; + boolean enable = 0; + uint8 target = 0; + + auto step(uint clocks) -> void; + auto synchronizeStage1() -> void; + }; + + Timer<128> timer0; + Timer<128> timer1; + Timer< 16> timer2; + + inline auto wait(maybe address = nothing, bool half = false) -> void; + inline auto waitIdle(maybe address = nothing, bool half = false) -> void; + inline auto step(uint clocks) -> void; + inline auto stepIdle(uint clocks) -> void; + inline auto stepTimers(uint clocks) -> void; +}; + +extern SMP smp; diff --git a/waterbox/bsnescore/bsnes/sfc/smp/timing.cpp b/waterbox/bsnescore/bsnes/sfc/smp/timing.cpp new file mode 100644 index 0000000000..4f70d561ff --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/smp/timing.cpp @@ -0,0 +1,78 @@ +//DSP clock (~24576khz) / 12 (~2048khz) is fed into the SMP +//from here, the wait states value is really a clock divider of {2, 4, 8, 16} +//due to an unknown hardware issue, clock dividers of 8 and 16 are glitchy +//the SMP ends up consuming 10 and 20 clocks per opcode cycle instead +//this causes unpredictable behavior on real hardware +//sometimes the SMP will run far slower than expected +//other times (and more likely), the SMP will deadlock until the system is reset +//the timers are not affected by this and advance by their expected values +auto SMP::wait(maybe addr, bool half) -> void { + static const uint cycleWaitStates[4] = {2, 4, 10, 20}; + static const uint timerWaitStates[4] = {2, 4, 8, 16}; + + uint waitStates = io.externalWaitStates; + if(!addr) waitStates = io.internalWaitStates; //idle cycles + else if((*addr & 0xfff0) == 0x00f0) waitStates = io.internalWaitStates; //IO registers + else if(*addr >= 0xffc0 && io.iplromEnable) waitStates = io.internalWaitStates; //IPLROM + + step(cycleWaitStates[waitStates] >> half); + stepTimers(timerWaitStates[waitStates] >> half); +} + +auto SMP::waitIdle(maybe addr, bool half) -> void { + static const uint cycleWaitStates[4] = {2, 4, 10, 20}; + static const uint timerWaitStates[4] = {2, 4, 8, 16}; + + uint waitStates = io.externalWaitStates; + if(!addr) waitStates = io.internalWaitStates; //idle cycles + else if((*addr & 0xfff0) == 0x00f0) waitStates = io.internalWaitStates; //IO registers + else if(*addr >= 0xffc0 && io.iplromEnable) waitStates = io.internalWaitStates; //IPLROM + + stepIdle(cycleWaitStates[waitStates] >> half); + stepTimers(timerWaitStates[waitStates] >> half); +} + +auto SMP::step(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; + dsp.clock -= clocks; + synchronizeDSP(); + //forcefully sync SMP to CPU in case chips are not communicating + if(clock > 768 * 24 * (int64_t)24'000'000) synchronizeCPU(); +} + +auto SMP::stepIdle(uint clocks) -> void { + clock += clocks * (uint64_t)cpu.frequency; + dsp.clock -= clocks; +} + +auto SMP::stepTimers(uint clocks) -> void { + timer0.step(clocks); + timer1.step(clocks); + timer2.step(clocks); +} + +template auto SMP::Timer::step(uint clocks) -> void { + //stage 0 increment + stage0 += clocks; + if(stage0 < Frequency) return; + stage0 -= Frequency; + + //stage 1 increment + stage1 ^= 1; + synchronizeStage1(); +} + +template auto SMP::Timer::synchronizeStage1() -> void { + bool level = stage1; + if(!smp.io.timersEnable) level = false; + if(smp.io.timersDisable) level = false; + if(!line.lower(level)) return; //only pulse on 1->0 transition + + //stage 2 increment + if(!enable) return; + if(++stage2 != target) return; + + //stage 3 increment + stage2 = 0; + stage3++; +} diff --git a/waterbox/bsnescore/bsnes/sfc/system/serialization.cpp b/waterbox/bsnescore/bsnes/sfc/system/serialization.cpp new file mode 100644 index 0000000000..75c0e5d945 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/system/serialization.cpp @@ -0,0 +1,119 @@ +auto System::serialize(bool synchronize) -> serializer { + //deterministic serialization (synchronize=false) is only possible with select libco methods + if(!co_serializable()) synchronize = true; + + if(!information.serializeSize[synchronize]) return {}; //should never occur + if(synchronize) runToSave(); + + uint signature = 0x31545342; + uint serializeSize = information.serializeSize[synchronize]; + char version[16] = {}; + char description[512] = {}; + memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size()); + + serializer s(serializeSize); + s.integer(signature); + s.integer(serializeSize); + s.array(version); + s.array(description); + s.boolean(synchronize); + s.boolean(hacks.fastPPU); + serializeAll(s, synchronize); + return s; +} + +auto System::unserialize(serializer& s) -> bool { + uint signature = 0; + uint serializeSize = 0; + char version[16] = {}; + char description[512] = {}; + bool synchronize = false; + bool fastPPU = false; + + s.integer(signature); + s.integer(serializeSize); + s.array(version); + s.array(description); + s.boolean(synchronize); + s.boolean(fastPPU); + + if(signature != 0x31545342) return false; + if(serializeSize != information.serializeSize[synchronize]) return false; + if(string{version} != Emulator::SerializerVersion) return false; + if(fastPPU != hacks.fastPPU) return false; + + if(synchronize) power(/* reset = */ false); + serializeAll(s, synchronize); + return true; +} + +//internal + +auto System::serializeAll(serializer& s, bool synchronize) -> void { + random.serialize(s); + cartridge.serialize(s); + cpu.serialize(s); + smp.serialize(s); + ppu.serialize(s); + dsp.serialize(s); + + if(cartridge.has.ICD) icd.serialize(s); + if(cartridge.has.MCC) mcc.serialize(s); + if(cartridge.has.DIP) dip.serialize(s); + if(cartridge.has.Event) event.serialize(s); + if(cartridge.has.SA1) sa1.serialize(s); + if(cartridge.has.SuperFX) superfx.serialize(s); + if(cartridge.has.ARMDSP) armdsp.serialize(s); + if(cartridge.has.HitachiDSP) hitachidsp.serialize(s); + if(cartridge.has.NECDSP) necdsp.serialize(s); + if(cartridge.has.EpsonRTC) epsonrtc.serialize(s); + if(cartridge.has.SharpRTC) sharprtc.serialize(s); + if(cartridge.has.SPC7110) spc7110.serialize(s); + if(cartridge.has.SDD1) sdd1.serialize(s); + if(cartridge.has.OBC1) obc1.serialize(s); + if(cartridge.has.MSU1) msu1.serialize(s); + + if(cartridge.has.Cx4) cx4.serialize(s); + if(cartridge.has.DSP1) dsp1.serialize(s); + if(cartridge.has.DSP2) dsp2.serialize(s); + if(cartridge.has.DSP4) dsp4.serialize(s); + if(cartridge.has.ST0010) st0010.serialize(s); + + if(cartridge.has.BSMemorySlot) bsmemory.serialize(s); + if(cartridge.has.SufamiTurboSlotA) sufamiturboA.serialize(s); + if(cartridge.has.SufamiTurboSlotB) sufamiturboB.serialize(s); + + controllerPort1.serialize(s); + controllerPort2.serialize(s); + expansionPort.serialize(s); + + if(!synchronize) { + cpu.serializeStack(s); + smp.serializeStack(s); + ppu.serializeStack(s); + for(auto coprocessor : cpu.coprocessors) { + coprocessor->serializeStack(s); + } + } +} + +//perform dry-run state save: +//determines exactly how many bytes are needed to save state for this cartridge, +//as amount varies per game (eg different RAM sizes, special chips, etc.) +auto System::serializeInit(bool synchronize) -> uint { + serializer s; + + uint signature = 0; + uint serializeSize = 0; + char version[16] = {}; + char description[512] = {}; + + s.integer(signature); + s.integer(serializeSize); + s.array(version); + s.array(description); + s.boolean(synchronize); + s.boolean(hacks.fastPPU); + serializeAll(s, synchronize); + return s.size(); +} diff --git a/waterbox/bsnescore/bsnes/sfc/system/system.cpp b/waterbox/bsnescore/bsnes/sfc/system/system.cpp new file mode 100644 index 0000000000..27897f50f5 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/system/system.cpp @@ -0,0 +1,242 @@ +#include + +namespace SuperFamicom { + +System system; +Scheduler scheduler; +Random random; +Cheat cheat; +#include "serialization.cpp" + +auto System::run() -> void { + scheduler.mode = Scheduler::Mode::Run; + scheduler.enter(); + if(scheduler.event == Scheduler::Event::Frame) frameEvent(); +} + +auto System::runToSave() -> void { + auto method = configuration.system.serialization.method; + + //these games will periodically deadlock when using "Fast" synchronization + if(cartridge.headerTitle() == "Star Ocean") method = "Strict"; + if(cartridge.headerTitle() == "TALES OF PHANTASIA") method = "Strict"; + + //fallback in case of unrecognized method specified + if(method != "Fast" && method != "Strict") method = "Fast"; + + scheduler.mode = Scheduler::Mode::Synchronize; + if(method == "Fast") runToSaveFast(); + if(method == "Strict") runToSaveStrict(); + + scheduler.mode = Scheduler::Mode::Run; + scheduler.active = cpu.thread; +} + +auto System::runToSaveFast() -> void { + //run the emulator normally until the CPU thread naturally hits a synchronization point + while(true) { + scheduler.enter(); + if(scheduler.event == Scheduler::Event::Frame) frameEvent(); + if(scheduler.event == Scheduler::Event::Synchronized) { + if(scheduler.active != cpu.thread) continue; + break; + } + if(scheduler.event == Scheduler::Event::Desynchronized) continue; + } + + //ignore any desynchronization events to force all other threads to their synchronization points + auto synchronize = [&](cothread_t thread) -> void { + scheduler.active = thread; + while(true) { + scheduler.enter(); + if(scheduler.event == Scheduler::Event::Frame) frameEvent(); + if(scheduler.event == Scheduler::Event::Synchronized) break; + if(scheduler.event == Scheduler::Event::Desynchronized) continue; + } + }; + + synchronize(smp.thread); + synchronize(ppu.thread); + for(auto coprocessor : cpu.coprocessors) { + synchronize(coprocessor->thread); + } +} + +auto System::runToSaveStrict() -> void { + //run every thread until it cleanly hits a synchronization point + //if it fails, start resynchronizing every thread again + auto synchronize = [&](cothread_t thread) -> bool { + scheduler.active = thread; + while(true) { + scheduler.enter(); + if(scheduler.event == Scheduler::Event::Frame) frameEvent(); + if(scheduler.event == Scheduler::Event::Synchronized) break; + if(scheduler.event == Scheduler::Event::Desynchronized) return false; + } + return true; + }; + + while(true) { + //SMP thread is synchronized twice to ensure the CPU and SMP are closely aligned: + //this is extremely critical for Tales of Phantasia and Star Ocean. + if(!synchronize(smp.thread)) continue; + if(!synchronize(cpu.thread)) continue; + if(!synchronize(smp.thread)) continue; + if(!synchronize(ppu.thread)) continue; + + bool synchronized = true; + for(auto coprocessor : cpu.coprocessors) { + if(!synchronize(coprocessor->thread)) { synchronized = false; break; } + } + if(!synchronized) continue; + + break; + } +} + +auto System::frameEvent() -> void { + ppu.refresh(); + + //refresh all cheat codes once per frame + Memory::GlobalWriteEnable = true; + for(auto& code : cheat.codes) { + if(code.enable) { + bus.write(code.address, code.data); + } + } + Memory::GlobalWriteEnable = false; +} + +auto System::load(Emulator::Interface* interface) -> bool { + information = {}; + + bus.reset(); + if(!cpu.load()) return false; + if(!smp.load()) return false; + if(!ppu.load()) return false; + if(!dsp.load()) return false; + if(!cartridge.load()) return false; + + if(cartridge.region() == "NTSC") { + information.region = Region::NTSC; + information.cpuFrequency = Emulator::Constants::Colorburst::NTSC * 6.0; + } + if(cartridge.region() == "PAL") { + information.region = Region::PAL; + information.cpuFrequency = Emulator::Constants::Colorburst::PAL * 4.8; + } + + if(configuration.hacks.hotfixes) { + //due to poor programming, Rendering Ranger R2 will rarely lock up at 32040 * 768hz. + if(cartridge.headerTitle() == "RENDERING RANGER R2") { + information.apuFrequency = 32000.0 * 768.0; + } + } + + if(cartridge.has.ICD) { + if(!icd.load()) return false; + } + if(cartridge.has.BSMemorySlot) bsmemory.load(); + + this->interface = interface; + return information.loaded = true; +} + +auto System::save() -> void { + if(!loaded()) return; + + cartridge.save(); +} + +auto System::unload() -> void { + if(!loaded()) return; + + controllerPort1.unload(); + controllerPort2.unload(); + expansionPort.unload(); + + if(cartridge.has.ICD) icd.unload(); + if(cartridge.has.MCC) mcc.unload(); + if(cartridge.has.Event) event.unload(); + if(cartridge.has.SA1) sa1.unload(); + if(cartridge.has.SuperFX) superfx.unload(); + if(cartridge.has.HitachiDSP) hitachidsp.unload(); + if(cartridge.has.SPC7110) spc7110.unload(); + if(cartridge.has.SDD1) sdd1.unload(); + if(cartridge.has.OBC1) obc1.unload(); + if(cartridge.has.MSU1) msu1.unload(); + if(cartridge.has.BSMemorySlot) bsmemory.unload(); + if(cartridge.has.SufamiTurboSlotA) sufamiturboA.unload(); + if(cartridge.has.SufamiTurboSlotB) sufamiturboB.unload(); + + cartridge.unload(); + information.loaded = false; +} + +auto System::power(bool reset) -> void { + hacks.fastPPU = configuration.hacks.ppu.fast; + + Emulator::audio.reset(interface); + + random.entropy(Random::Entropy::Low); //fallback case + if(configuration.hacks.entropy == "None") random.entropy(Random::Entropy::None); + if(configuration.hacks.entropy == "Low" ) random.entropy(Random::Entropy::Low ); + if(configuration.hacks.entropy == "High") random.entropy(Random::Entropy::High); + + cpu.power(reset); + smp.power(reset); + dsp.power(reset); + ppu.power(reset); + + if(cartridge.has.ICD) icd.power(); + if(cartridge.has.MCC) mcc.power(); + if(cartridge.has.DIP) dip.power(); + if(cartridge.has.Event) event.power(); + if(cartridge.has.SA1) sa1.power(); + if(cartridge.has.SuperFX) superfx.power(); + if(cartridge.has.ARMDSP) armdsp.power(); + if(cartridge.has.HitachiDSP) hitachidsp.power(); + if(cartridge.has.NECDSP) necdsp.power(); + if(cartridge.has.EpsonRTC) epsonrtc.power(); + if(cartridge.has.SharpRTC) sharprtc.power(); + if(cartridge.has.SPC7110) spc7110.power(); + if(cartridge.has.SDD1) sdd1.power(); + if(cartridge.has.OBC1) obc1.power(); + if(cartridge.has.MSU1) msu1.power(); + if(cartridge.has.Cx4) cx4.power(); + if(cartridge.has.DSP1) dsp1.power(); + if(cartridge.has.DSP2) dsp2.power(); + if(cartridge.has.DSP4) dsp4.power(); + if(cartridge.has.ST0010) st0010.power(); + if(cartridge.has.BSMemorySlot) bsmemory.power(); + if(cartridge.has.SufamiTurboSlotA) sufamiturboA.power(); + if(cartridge.has.SufamiTurboSlotB) sufamiturboB.power(); + + if(cartridge.has.ICD) cpu.coprocessors.append(&icd); + if(cartridge.has.Event) cpu.coprocessors.append(&event); + if(cartridge.has.SA1) cpu.coprocessors.append(&sa1); + if(cartridge.has.SuperFX) cpu.coprocessors.append(&superfx); + if(cartridge.has.ARMDSP) cpu.coprocessors.append(&armdsp); + if(cartridge.has.HitachiDSP) cpu.coprocessors.append(&hitachidsp); + if(cartridge.has.NECDSP) cpu.coprocessors.append(&necdsp); + if(cartridge.has.EpsonRTC) cpu.coprocessors.append(&epsonrtc); + if(cartridge.has.SharpRTC) cpu.coprocessors.append(&sharprtc); + if(cartridge.has.SPC7110) cpu.coprocessors.append(&spc7110); + if(cartridge.has.MSU1) cpu.coprocessors.append(&msu1); + if(cartridge.has.BSMemorySlot) cpu.coprocessors.append(&bsmemory); + + scheduler.active = cpu.thread; + + controllerPort1.power(ID::Port::Controller1); + controllerPort2.power(ID::Port::Controller2); + expansionPort.power(); + + controllerPort1.connect(settings.controllerPort1); + controllerPort2.connect(settings.controllerPort2); + expansionPort.connect(settings.expansionPort); + + information.serializeSize[0] = serializeInit(0); + information.serializeSize[1] = serializeInit(1); +} + +} diff --git a/waterbox/bsnescore/bsnes/sfc/system/system.hpp b/waterbox/bsnescore/bsnes/sfc/system/system.hpp new file mode 100644 index 0000000000..b19fcdf477 --- /dev/null +++ b/waterbox/bsnescore/bsnes/sfc/system/system.hpp @@ -0,0 +1,56 @@ +struct System { + enum class Region : uint { NTSC, PAL }; + + inline auto loaded() const -> bool { return information.loaded; } + inline auto region() const -> Region { return information.region; } + inline auto cpuFrequency() const -> double { return information.cpuFrequency; } + inline auto apuFrequency() const -> double { return information.apuFrequency; } + + inline auto fastPPU() const -> bool { return hacks.fastPPU; } + + auto run() -> void; + auto runToSave() -> void; + auto runToSaveFast() -> void; + auto runToSaveStrict() -> void; + auto frameEvent() -> void; + + auto load(Emulator::Interface*) -> bool; + auto save() -> void; + auto unload() -> void; + auto power(bool reset) -> void; + + //serialization.cpp + auto serialize(bool synchronize) -> serializer; + auto unserialize(serializer&) -> bool; + + uint frameSkip = 0; + uint frameCounter = 0; + bool runAhead = 0; + bool renderVideo = true; + bool renderAudio = true; + +private: + Emulator::Interface* interface = nullptr; + + struct Information { + bool loaded = false; + Region region = Region::NTSC; + double cpuFrequency = Emulator::Constants::Colorburst::NTSC * 6.0; + double apuFrequency = 32040.0 * 768.0; + uint serializeSize[2] = {0, 0}; + } information; + + struct Hacks { + bool fastPPU = false; + } hacks; + + auto serializeAll(serializer&, bool synchronize) -> void; + auto serializeInit(bool synchronize) -> uint; + + friend class Cartridge; +}; + +extern System system; + +auto Region::NTSC() -> bool { return system.region() == System::Region::NTSC; } +auto Region::PAL() -> bool { return system.region() == System::Region::PAL; } diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp new file mode 100644 index 0000000000..892ed23acb --- /dev/null +++ b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp @@ -0,0 +1,363 @@ +#include "bsnescore.hpp" +#include "callbacks.h" +#include +#include +#include + +using namespace nall; +using namespace SuperFamicom; + +#include "program.cpp" + + +//zero 05-sep-2012 +// currently unused; was only used in the graphics debugger as far as i can see +int snes_peek_logical_register(int reg) +{ + if (emulator->configuration("Hacks/PPU/Fast") == "true") + switch(reg) + { + //zero 17-may-2014 + // 3-may-2021 above timestamp left for reference because i like it + + //$2105 + case SNES_REG_BG_MODE: return ppufast.io.bgMode; + case SNES_REG_BG3_PRIORITY: return ppufast.io.bgPriority; + case SNES_REG_BG1_TILESIZE: return ppufast.io.bg1.tileSize; + case SNES_REG_BG2_TILESIZE: return ppufast.io.bg2.tileSize; + case SNES_REG_BG3_TILESIZE: return ppufast.io.bg3.tileSize; + case SNES_REG_BG4_TILESIZE: return ppufast.io.bg4.tileSize; + //$2107 + case SNES_REG_BG1_SCADDR: return ppufast.io.bg1.screenAddress >> 8; + case SNES_REG_BG1_SCSIZE: return ppufast.io.bg1.screenSize; + //$2108 + case SNES_REG_BG2_SCADDR: return ppufast.io.bg2.screenAddress >> 8; + case SNES_REG_BG2_SCSIZE: return ppufast.io.bg2.screenSize; + //$2109 + case SNES_REG_BG3_SCADDR: return ppufast.io.bg3.screenAddress >> 8; + case SNES_REG_BG3_SCSIZE: return ppufast.io.bg3.screenSize; + //$210A + case SNES_REG_BG4_SCADDR: return ppufast.io.bg4.screenAddress >> 8; + case SNES_REG_BG4_SCSIZE: return ppufast.io.bg4.screenSize; + //$210B + case SNES_REG_BG1_TDADDR: return ppufast.io.bg1.tiledataAddress >> 12; + case SNES_REG_BG2_TDADDR: return ppufast.io.bg2.tiledataAddress >> 12; + //$210C + case SNES_REG_BG3_TDADDR: return ppufast.io.bg3.tiledataAddress >> 12; + case SNES_REG_BG4_TDADDR: return ppufast.io.bg4.tiledataAddress >> 12; + //$2133 SETINI + case SNES_REG_SETINI_MODE7_EXTBG: return ppufast.io.extbg; + case SNES_REG_SETINI_HIRES: return ppufast.io.pseudoHires; + case SNES_REG_SETINI_OVERSCAN: return ppufast.io.overscan; + case SNES_REG_SETINI_OBJ_INTERLACE: return ppufast.io.obj.interlace; + case SNES_REG_SETINI_SCREEN_INTERLACE: return ppufast.io.interlace; + //$2130 CGWSEL + case SNES_REG_CGWSEL_COLORMASK: return ppufast.io.col.window.aboveMask; + case SNES_REG_CGWSEL_COLORSUBMASK: return ppufast.io.col.window.belowMask; + case SNES_REG_CGWSEL_ADDSUBMODE: return ppufast.io.col.blendMode; + case SNES_REG_CGWSEL_DIRECTCOLOR: return ppufast.io.col.directColor; + //$2101 OBSEL + case SNES_REG_OBSEL_NAMEBASE: return ppufast.io.obj.tiledataAddress >> 13; // TODO: figure out why these shifts are only in specific places + case SNES_REG_OBSEL_NAMESEL: return ppufast.io.obj.nameselect; + case SNES_REG_OBSEL_SIZE: return ppufast.io.obj.baseSize; + //$2131 CGADDSUB + //enum { BG1 = 0, BG2 = 1, BG3 = 2, BG4 = 3, OAM = 4, BACK = 5, COL = 5 }; + case SNES_REG_CGADDSUB_BG1: return ppufast.io.col.enable[PPUfast::Source::BG1]; + case SNES_REG_CGADDSUB_BG2: return ppufast.io.col.enable[PPUfast::Source::BG2]; + case SNES_REG_CGADDSUB_BG3: return ppufast.io.col.enable[PPUfast::Source::BG3]; + case SNES_REG_CGADDSUB_BG4: return ppufast.io.col.enable[PPUfast::Source::BG4]; + case SNES_REG_CGADDSUB_OBJ: return ppufast.io.col.enable[PPUfast::Source::OBJ2]; + case SNES_REG_CGADDSUB_BACKDROP: return ppufast.io.col.enable[PPUfast::Source::COL]; + case SNES_REG_CGADDSUB_HALF: return ppufast.io.col.halve; + case SNES_REG_CGADDSUB_MODE: return ppufast.io.col.mathMode; + //$212C TM + case SNES_REG_TM_BG1: return ppufast.io.bg1.aboveEnable; + case SNES_REG_TM_BG2: return ppufast.io.bg2.aboveEnable; + case SNES_REG_TM_BG3: return ppufast.io.bg3.aboveEnable; + case SNES_REG_TM_BG4: return ppufast.io.bg4.aboveEnable; + case SNES_REG_TM_OBJ: return ppufast.io.obj.aboveEnable; + //$212D TS + case SNES_REG_TS_BG1: return ppufast.io.bg1.belowEnable; + case SNES_REG_TS_BG2: return ppufast.io.bg2.belowEnable; + case SNES_REG_TS_BG3: return ppufast.io.bg3.belowEnable; + case SNES_REG_TS_BG4: return ppufast.io.bg4.belowEnable; + case SNES_REG_TS_OBJ: return ppufast.io.obj.belowEnable; + //Mode7 regs + case SNES_REG_M7SEL_HFLIP: return ppufast.io.mode7.hflip; + case SNES_REG_M7SEL_VFLIP: return ppufast.io.mode7.vflip; + case SNES_REG_M7SEL_REPEAT: return ppufast.io.mode7.repeat; + case SNES_REG_M7A: return ppufast.io.mode7.a; + case SNES_REG_M7B: return ppufast.io.mode7.b; + case SNES_REG_M7C: return ppufast.io.mode7.c; + case SNES_REG_M7D: return ppufast.io.mode7.d; + case SNES_REG_M7X: return ppufast.io.mode7.x; + case SNES_REG_M7Y: return ppufast.io.mode7.y; + //BG scroll regs + case SNES_REG_BG1HOFS: return ppufast.io.bg1.hoffset; + case SNES_REG_BG1VOFS: return ppufast.io.bg1.voffset; + case SNES_REG_BG2HOFS: return ppufast.io.bg2.hoffset; + case SNES_REG_BG2VOFS: return ppufast.io.bg2.voffset; + case SNES_REG_BG3HOFS: return ppufast.io.bg3.hoffset; + case SNES_REG_BG3VOFS: return ppufast.io.bg3.voffset; + case SNES_REG_BG4HOFS: return ppufast.io.bg4.hoffset; + case SNES_REG_BG4VOFS: return ppufast.io.bg4.voffset; + case SNES_REG_M7HOFS: return ppufast.io.mode7.hoffset; // TODO figure out what that comment means .regs.m7_hofs & 0x1FFF; //rememebr to make these signed with <<19>>19 + case SNES_REG_M7VOFS: return ppufast.io.mode7.voffset; //rememebr to make these signed with <<19>>19 + } + else; // no fast ppu + // TODO: potentially provide register values even in this case? currently all those are private in ppu.hpp + return 0; +} + + +// +// fresh dll interface functions +// + +// callbacks, set initially by the frontend +SnesCallbacks snesCallbacks; + +EXPORT void snes_set_callbacks(SnesCallbacks* callbacks) +{ + snesCallbacks = SnesCallbacks(*callbacks); +} + +EXPORT void snes_init(int entropy, uint left_port, uint right_port, uint16_t merged_bools)// bool hotfixes, bool fast_ppu) +{ + bool hotfixes = merged_bools >> 8; + bool fast_ppu = merged_bools & 1; + fprintf(stderr, "snes_init was called!\n"); + emulator = new SuperFamicom::Interface; + program = new Program; + // memset(&cdlInfo,0,sizeof(cdlInfo)); + + string entropy_string; + switch (entropy) + { + case 0: entropy_string = "None"; break; + case 1: entropy_string = "Low"; break; + case 2: entropy_string = "High"; break; + } + emulator->configure("Hacks/Entropy", entropy_string); + + emulator->connect(ID::Port::Controller1, left_port); + emulator->connect(ID::Port::Controller2, right_port); + + emulator->configure("Hacks/Hotfixes", hotfixes); + emulator->configure("Hacks/PPU/Fast", fast_ppu); + + emulator->configure("Video/BlurEmulation", false); // blurs the video when not using fast ppu. I don't like it so I disable it here :) + // needed in order to get audio sync working. should probably figure out what exactly this does or how to change that properly + Emulator::audio.setFrequency(SAMPLE_RATE); +} + +EXPORT void snes_power(void) +{ + emulator->power(); +} + +// unused currently? should it be? +EXPORT void snes_term(void) +{ + emulator->unload(); +} + +EXPORT void snes_reset(void) +{ + emulator->reset(); +} + +// note: run with runahead doesn't work yet, i suspect it's due to the serialize thing breaking (cause of libco) +EXPORT void snes_run(void) +{ + snesCallbacks.snes_input_poll(); + + // TODO: I currently have implemented separate poll and state calls, where poll updates the state and the state call just receives this + // based on the way this is implemented this approach might be useless in terms of reducing polling load, will need confirmation here + // the runahead feature should also be considered in case this is ever implemented and works + + emulator->run(); +} + +// not used, but would probably be nice +void snes_hd_scale(int scale) +{ + emulator->configure("Hacks/PPU/Mode7/Scale", scale); +} + +EXPORT int snes_serialized_size() +{ + return emulator->serialize().size(); +} + +// waiting for libco update in order to be able to use this deterministically (no synchronize) +EXPORT void snes_serialize(uint8_t* data, int size) +{ + auto serializer = emulator->serialize(); + memcpy(data, serializer.data(), size); +} + +EXPORT void snes_unserialize(const uint8_t* data, int size) +{ + serializer s(data, size); + emulator->unserialize(s); +} + +EXPORT void snes_load_cartridge_normal( + const char* base_rom_path, const uint8_t* rom_data, int rom_size +) { + program->superFamicom.location = base_rom_path; + + program->superFamicom.raw_data.resize(rom_size); + memcpy(program->superFamicom.raw_data.data(), rom_data, rom_size); + + program->load(); +} + +// TODO: merged_rom_sizes is bad, fix this +EXPORT void snes_load_cartridge_super_gameboy( + const char* base_rom_path, const uint8_t* rom_data, const uint8_t* sgb_rom_data, uint64_t merged_rom_sizes +) { + int rom_size = merged_rom_sizes >> 32; + int sgb_rom_size = merged_rom_sizes & 0xffffffff; + program->superFamicom.location = base_rom_path; + + program->superFamicom.raw_data.resize(rom_size); + memcpy(program->superFamicom.raw_data.data(), rom_data, rom_size); + + program->gameBoy.program.resize(sgb_rom_size); + memcpy(program->gameBoy.program.data(), sgb_rom_data, sgb_rom_size); + + program->load(); +} +// Note that bsmemory and sufamiturbo (a and b) are never loaded +// I have no idea what that is but it probably should be supported frontend + + +EXPORT void snes_set_layer_enables(LayerEnables* layerEnables) +{ + if (emulator->configuration("Hacks/PPU/Fast") == "true") { + ppufast.io.bg1.priority_enabled[0] = layerEnables->BG1_Prio0; + ppufast.io.bg1.priority_enabled[1] = layerEnables->BG1_Prio1; + ppufast.io.bg2.priority_enabled[0] = layerEnables->BG2_Prio0; + ppufast.io.bg2.priority_enabled[1] = layerEnables->BG2_Prio1; + ppufast.io.bg3.priority_enabled[0] = layerEnables->BG3_Prio0; + ppufast.io.bg3.priority_enabled[1] = layerEnables->BG3_Prio1; + ppufast.io.bg4.priority_enabled[0] = layerEnables->BG4_Prio0; + ppufast.io.bg4.priority_enabled[1] = layerEnables->BG4_Prio1; + ppufast.io.obj.priority_enabled[0] = layerEnables->Obj_Prio0; + ppufast.io.obj.priority_enabled[1] = layerEnables->Obj_Prio1; + ppufast.io.obj.priority_enabled[2] = layerEnables->Obj_Prio2; + ppufast.io.obj.priority_enabled[3] = layerEnables->Obj_Prio3; + } +} + +EXPORT void snes_set_audio_enabled(bool enable) +{ + SuperFamicom::system.renderAudio = enable; +} + +EXPORT void snes_set_video_enabled(bool enable) +{ + SuperFamicom::system.renderVideo = enable; +} + +EXPORT void snes_set_trace_enabled(bool enabled) +{ + platform->traceEnabled = enabled; +} + + +EXPORT int snes_get_region(void) { + return Region::PAL(); +} + +EXPORT char snes_get_mapper(void) { + string board = program->superFamicom.document["game/board"].text(); + string mapper = board.split('-', 1)[0]; + if (mapper == "LOROM") return 0; + if (mapper == "HIROM") return 1; + if (mapper == "EXLOROM") return 2; + if (mapper == "EXHIROM") return 3; + if (mapper == "SUPERFXROM") return 4; + if (mapper == "SA1ROM") return 5; + if (mapper == "SPC7110ROM") return 6; + if (mapper == "BSCLOROM") return 7; + if (mapper == "BSCHIROM") return 8; + if (mapper == "BSXROM") return 9; + if (mapper == "STROM") return 10; + + return -1; +} + +EXPORT void* snes_get_memory_region(int id, int* size, int* word_size) +{ + if(!emulator->loaded()) return nullptr; + bool fast_ppu = emulator->configuration("Hacks/PPU/Fast") == "true"; + + switch(id) + { + case SNES_MEMORY::CARTRIDGE_RAM: + *size = cartridge.ram.size(); + *word_size = 1; + return cartridge.ram.data(); + case SNES_MEMORY::BSX_RAM: + if (!cartridge.has.BSMemorySlot) break; + *size = mcc.rom.size(); + *word_size = 1; + return mcc.rom.data(); + case SNES_MEMORY::BSX_PRAM: + if (!cartridge.has.BSMemorySlot) break; + *size = mcc.psram.size(); + *word_size = 1; + return mcc.psram.data(); + case SNES_MEMORY::SUFAMI_TURBO_A_RAM: + if (!cartridge.has.SufamiTurboSlotA) break; + *size = sufamiturboA.ram.size(); + *word_size = 1; + return sufamiturboA.ram.data(); + case SNES_MEMORY::SUFAMI_TURBO_B_RAM: + if (!cartridge.has.SufamiTurboSlotB) break; + *size = sufamiturboB.ram.size(); + *word_size = 1; + return sufamiturboB.ram.data(); + + case SNES_MEMORY::WRAM: + *size = sizeof(cpu.wram); + *word_size = 1; + return cpu.wram; + case SNES_MEMORY::APURAM: + *size = sizeof(dsp.apuram); + *word_size = 1; + return dsp.apuram; + case SNES_MEMORY::VRAM: + if (!fast_ppu) break; + *size = sizeof(ppufast.vram); + *word_size = sizeof(*ppufast.vram); + return ppufast.vram; + // case SNES_MEMORY::OAM: // probably weird since bsnes uses "object"s instead of bytes for oam rn + // return (uint8_t*) ppufast.objects; + case SNES_MEMORY::CGRAM: + if (!fast_ppu) break; + *size = sizeof(ppufast.cgram); + *word_size = sizeof(*ppufast.cgram); + return ppufast.cgram; + + case SNES_MEMORY::CARTRIDGE_ROM: + *size = cartridge.rom.size(); + *word_size = 1; + return cartridge.rom.data(); + } + + return nullptr; +} + +EXPORT uint8_t snes_bus_read(unsigned addr) +{ + return bus.read(addr); +} + +EXPORT void snes_bus_write(unsigned addr, uint8_t value) +{ + bus.write(addr, value); +} diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp new file mode 100644 index 0000000000..1c76d18e06 --- /dev/null +++ b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp @@ -0,0 +1,125 @@ +#ifndef BSNESCORE_HPP +#define BSNESCORE_HPP + +#include +#include + +#define EXPORT ECL_EXPORT + +#define SAMPLE_RATE 32040 + +enum SNES_MEMORY { + CARTRIDGE_RAM, + BSX_RAM, + BSX_PRAM, + SUFAMI_TURBO_A_RAM, + SUFAMI_TURBO_B_RAM, + + WRAM, + APURAM, + VRAM, + CGRAM, + + CARTRIDGE_ROM +}; + + +struct LayerEnables +{ + bool BG1_Prio0, BG1_Prio1; + bool BG2_Prio0, BG2_Prio1; + bool BG3_Prio0, BG3_Prio1; + bool BG4_Prio0, BG4_Prio1; + bool Obj_Prio0, Obj_Prio1, Obj_Prio2, Obj_Prio3; +}; + + +// below code unused; would be useful for the graphics debugger +//$2105 +#define SNES_REG_BG_MODE 0 +#define SNES_REG_BG3_PRIORITY 1 +#define SNES_REG_BG1_TILESIZE 2 +#define SNES_REG_BG2_TILESIZE 3 +#define SNES_REG_BG3_TILESIZE 4 +#define SNES_REG_BG4_TILESIZE 5 +//$2107 +#define SNES_REG_BG1_SCADDR 10 +#define SNES_REG_BG1_SCSIZE 11 +//$2108 +#define SNES_REG_BG2_SCADDR 12 +#define SNES_REG_BG2_SCSIZE 13 +//$2109 +#define SNES_REG_BG3_SCADDR 14 +#define SNES_REG_BG3_SCSIZE 15 +//$210A +#define SNES_REG_BG4_SCADDR 16 +#define SNES_REG_BG4_SCSIZE 17 +//$210B +#define SNES_REG_BG1_TDADDR 20 +#define SNES_REG_BG2_TDADDR 21 +//$210C +#define SNES_REG_BG3_TDADDR 22 +#define SNES_REG_BG4_TDADDR 23 +//$2133 SETINI +#define SNES_REG_SETINI_MODE7_EXTBG 30 +#define SNES_REG_SETINI_HIRES 31 +#define SNES_REG_SETINI_OVERSCAN 32 +#define SNES_REG_SETINI_OBJ_INTERLACE 33 +#define SNES_REG_SETINI_SCREEN_INTERLACE 34 +//$2130 CGWSEL +#define SNES_REG_CGWSEL_COLORMASK 40 +#define SNES_REG_CGWSEL_COLORSUBMASK 41 +#define SNES_REG_CGWSEL_ADDSUBMODE 42 +#define SNES_REG_CGWSEL_DIRECTCOLOR 43 +//$2101 OBSEL +#define SNES_REG_OBSEL_NAMEBASE 50 +#define SNES_REG_OBSEL_NAMESEL 51 +#define SNES_REG_OBSEL_SIZE 52 +//$2131 CGADSUB +#define SNES_REG_CGADDSUB_MODE 60 +#define SNES_REG_CGADDSUB_HALF 61 +#define SNES_REG_CGADDSUB_BG4 62 +#define SNES_REG_CGADDSUB_BG3 63 +#define SNES_REG_CGADDSUB_BG2 64 +#define SNES_REG_CGADDSUB_BG1 65 +#define SNES_REG_CGADDSUB_OBJ 66 +#define SNES_REG_CGADDSUB_BACKDROP 67 +//$212C TM +#define SNES_REG_TM_BG1 70 +#define SNES_REG_TM_BG2 71 +#define SNES_REG_TM_BG3 72 +#define SNES_REG_TM_BG4 73 +#define SNES_REG_TM_OBJ 74 +//$212D TM +#define SNES_REG_TS_BG1 80 +#define SNES_REG_TS_BG2 81 +#define SNES_REG_TS_BG3 82 +#define SNES_REG_TS_BG4 83 +#define SNES_REG_TS_OBJ 84 +//Mode7 regs +#define SNES_REG_M7SEL_REPEAT 90 +#define SNES_REG_M7SEL_HFLIP 91 +#define SNES_REG_M7SEL_VFLIP 92 +#define SNES_REG_M7A 93 +#define SNES_REG_M7B 94 +#define SNES_REG_M7C 95 +#define SNES_REG_M7D 96 +#define SNES_REG_M7X 97 +#define SNES_REG_M7Y 98 +//BG scroll regs +#define SNES_REG_BG1HOFS 100 +#define SNES_REG_BG1VOFS 101 +#define SNES_REG_BG2HOFS 102 +#define SNES_REG_BG2VOFS 103 +#define SNES_REG_BG3HOFS 104 +#define SNES_REG_BG3VOFS 105 +#define SNES_REG_BG4HOFS 106 +#define SNES_REG_BG4VOFS 107 +#define SNES_REG_M7HOFS 108 +#define SNES_REG_M7VOFS 109 + + +int snes_peek_logical_register(int reg); + + +#endif diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h b/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h new file mode 100644 index 0000000000..6d35f91780 --- /dev/null +++ b/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h @@ -0,0 +1,26 @@ +#ifndef CALLBACKS_H +#define CALLBACKS_H + +#include + +typedef void (*snes_input_poll_t)(void); +typedef int16_t (*snes_input_state_t)(int port, int index, int id); +typedef void (*snes_no_lag_t)(void); +typedef void (*snes_video_frame_t)(const uint16_t* data, int width, int height, int pitch); +typedef void (*snes_audio_sample_t)(int16_t left, int16_t right); +typedef char* (*snes_path_request_t)(int slot, const char* hint, int required); +typedef void (*snes_trace_t)(const char* disassembly, const char* register_info); + +struct SnesCallbacks { + snes_input_poll_t snes_input_poll; + snes_input_state_t snes_input_state; + snes_no_lag_t snes_no_lag; + snes_video_frame_t snes_video_frame; + snes_audio_sample_t snes_audio_sample; + snes_path_request_t snes_path_request; + snes_trace_t snes_trace; +}; + +extern SnesCallbacks snesCallbacks; + +#endif diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp b/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp new file mode 100644 index 0000000000..e7eb7ef287 --- /dev/null +++ b/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp @@ -0,0 +1,485 @@ +// blatantly stolen from target-libretro + +#include +#include +#include +#include +#include + +#include "resources.hpp" + +static Emulator::Interface *emulator; + +struct Program : Emulator::Platform +{ + Program(); + + auto open(uint id, string name, vfs::file::mode mode, bool required) -> shared_pointer override; + auto load(uint id, string name, string type, vector options = {}) -> Emulator::Platform::Load override; + auto videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void override; + auto audioFrame(const double* samples, uint channels) -> void override; + auto inputPoll(uint port, uint device, uint input) -> int16 override; + auto inputRumble(uint port, uint device, uint input, bool enable) -> void override; + auto notify(string text) -> void override; + auto getBackdropColor() -> uint16 override; + auto cpuTrace(vector) -> void override; + + auto load() -> void; + auto loadFile(string location) -> vector; + auto loadSuperFamicom() -> bool; + auto loadGameBoy() -> bool; + auto loadBSMemory() -> bool; + + auto save() -> void; + + auto openFileSuperFamicom(string name, vfs::file::mode mode, bool required) -> shared_pointer; + auto openFileGameBoy(string name, vfs::file::mode mode, bool required) -> shared_pointer; + + auto hackPatchMemory(vector& data) -> void; + + bool overscan = false; + uint16_t backdropColor; + +public: + struct Game { + explicit operator bool() const { return (bool)location; } + + string option; + string location; + string manifest; + Markup::Node document; + boolean patched; + boolean verified; + }; + + struct SuperFamicom : Game { + vector raw_data; + string title; + string region; + vector program; + vector data; + vector expansion; + vector firmware; + } superFamicom; + + struct GameBoy : Game { + vector program; + } gameBoy; + + struct BSMemory : Game { + vector program; + } bsMemory; +}; + +static Program *program = nullptr; + +Program::Program() +{ + platform = this; +} + +auto Program::save() -> void +{ + if(!emulator->loaded()) return; + emulator->save(); +} + +auto Program::open(uint id, string name, vfs::file::mode mode, bool required) -> shared_pointer +{ + fprintf(stderr, "name \"%s\" was requested\n", name.data()); + + shared_pointer result; + + if (name == "ipl.rom" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(iplrom, sizeof(iplrom)); + } + + if (name == "boards.bml" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(Boards, sizeof(Boards)); + } + + if (id == 1) { //Super Famicom + if (name == "manifest.bml" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(superFamicom.manifest.data(), superFamicom.manifest.size()); + } + else if (name == "program.rom" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(superFamicom.program.data(), superFamicom.program.size()); + } + else if (name == "data.rom" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(superFamicom.data.data(), superFamicom.data.size()); + } + else if (name == "expansion.rom" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(superFamicom.expansion.data(), superFamicom.expansion.size()); + } + else { + result = openFileSuperFamicom(name, mode, required); + } + } + else if (id == 2) { //Game Boy + if (name == "manifest.bml" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(gameBoy.manifest.data(), gameBoy.manifest.size()); + } + else if (name == "program.rom" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(gameBoy.program.data(), gameBoy.program.size()); + } + else { + result = openFileGameBoy(name, mode, required); + } + } + else if (id == 3) { //BS Memory + if (name == "manifest.bml" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(bsMemory.manifest.data(), bsMemory.manifest.size()); + } + else if (name == "program.rom" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(bsMemory.program.data(), bsMemory.program.size()); + } + else if(name == "program.flash") { + //writes are not flushed to disk in bsnes + result = vfs::memory::file::open(bsMemory.program.data(), bsMemory.program.size()); + } + else { + result = {}; + } + // sufami turbo would be id 4 and 5 and is ignored for reasons? do we support it in bizhawk? TODO check this + } + return result; +} + +auto Program::openFileSuperFamicom(string name, vfs::file::mode mode, bool required) -> shared_pointer +{ + // TODO: the original bsnes code handles a lot more paths; *.data.ram, time.rtc and download.ram + // I believe none of these can currently be correctly served by bizhawk and I therefor ignore them here + // This should probably be changed? Not sure how much can break from not having them + if(name == "msu1/data.rom" || name.match("msu1/track*.pcm") || name == "save.ram") + { + return vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, name, required), mode); + } + + if(name == "arm6.program.rom" && mode == vfs::file::mode::read) { + if(superFamicom.firmware.size() == 0x28000) { + return vfs::memory::file::open(&superFamicom.firmware.data()[0x00000], 0x20000); + } + if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Program,architecture=ARM6)"]) { + return vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, memory["identifier"].text().downcase(), required), mode); + } + } + + if(name == "arm6.data.rom" && mode == vfs::file::mode::read) { + if(superFamicom.firmware.size() == 0x28000) { + return vfs::memory::file::open(&superFamicom.firmware.data()[0x20000], 0x08000); + } + if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Data,architecture=ARM6)"]) { + auto file = vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, memory["identifier"].text().downcase(), required), mode); + if (file) file->seek(0x20000, vfs::file::index::absolute); + return file; + } + } + + if(name == "hg51bs169.data.rom" && mode == vfs::file::mode::read) { + if(superFamicom.firmware.size() == 0xc00) { + return vfs::memory::file::open(superFamicom.firmware.data(), superFamicom.firmware.size()); + } + if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Data,architecture=HG51BS169)"]) { + return vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, memory["identifier"].text().downcase(), required), mode); + } + } + + if(name == "lr35902.boot.rom" && mode == vfs::file::mode::read) { + if(superFamicom.firmware.size() == 0x100) { + return vfs::memory::file::open(superFamicom.firmware.data(), superFamicom.firmware.size()); + } + if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]) { + return vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, memory["identifier"].text().downcase(), required), mode); + } + } + + if(name == "upd7725.program.rom" && mode == vfs::file::mode::read) { + if(superFamicom.firmware.size() == 0x2000) { + return vfs::memory::file::open(&superFamicom.firmware.data()[0x0000], 0x1800); + } + if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Program,architecture=uPD7725)"]) { + auto file = vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, memory["identifier"].text().downcase(), required), mode); + return file; + } + } + + if(name == "upd7725.data.rom" && mode == vfs::file::mode::read) { + if(superFamicom.firmware.size() == 0x2000) { + return vfs::memory::file::open(&superFamicom.firmware.data()[0x1800], 0x0800); + } + if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Data,architecture=uPD7725)"]) { + auto file = vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, memory["identifier"].text().downcase(), required), mode); + if (file) file->seek(0x1800, vfs::file::index::absolute); + return file; + } + } + + if(name == "upd96050.program.rom" && mode == vfs::file::mode::read) { + if(superFamicom.firmware.size() == 0xd000) { + return vfs::memory::file::open(&superFamicom.firmware.data()[0x0000], 0xc000); + } + if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Program,architecture=uPD96050)"]) { + auto file = vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, memory["identifier"].text().downcase(), required), mode); + return file; + } + } + + if(name == "upd96050.data.rom" && mode == vfs::file::mode::read) { + if(superFamicom.firmware.size() == 0xd000) { + return vfs::memory::file::open(&superFamicom.firmware.data()[0xc000], 0x1000); + } + if(auto memory = superFamicom.document["game/board/memory(type=ROM,content=Data,architecture=uPD96050)"]) { + auto file = vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, memory["identifier"].text().downcase(), required), mode); + if (file) file->seek(0xc000, vfs::file::index::absolute); + return file; + } + } + + return {}; +} + +auto Program::openFileGameBoy(string name, vfs::file::mode mode, bool required) -> shared_pointer +{ + if(name == "save.ram") + { + return vfs::fs::file::open(snesCallbacks.snes_path_request(ID::GameBoy, name, required), mode); + } + + if(name == "time.rtc") + { + return vfs::fs::file::open(snesCallbacks.snes_path_request(ID::GameBoy, name, required), mode); + } + + return {}; +} + +auto Program::load() -> void { + emulator->unload(); + emulator->load(); + + // per-game hack overrides + auto title = superFamicom.title; + auto region = superFamicom.region; + + //relies on mid-scanline rendering techniques + if(title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER") emulator->configure("Hacks/PPU/Fast", false); + + //the dialogue text is blurry due to an issue in the scanline-based renderer's color math support + if(title == "マーヴェラス") emulator->configure("Hacks/PPU/Fast", false); + + //stage 2 uses pseudo-hires in a way that's not compatible with the scanline-based renderer + if(title == "SFC クレヨンシンチャン") emulator->configure("Hacks/PPU/Fast", false); + + //title screen game select (after choosing a game) changes OAM tiledata address mid-frame + //this is only supported by the cycle-based PPU renderer + if(title == "Winter olympics") emulator->configure("Hacks/PPU/Fast", false); + + //title screen shows remnants of the flag after choosing a language with the scanline-based renderer + if(title == "WORLD CUP STRIKER") emulator->configure("Hacks/PPU/Fast", false); + + //relies on cycle-accurate writes to the echo buffer + if(title == "KOUSHIEN_2") emulator->configure("Hacks/DSP/Fast", false); + + //will hang immediately + if(title == "RENDERING RANGER R2") emulator->configure("Hacks/DSP/Fast", false); + + //will hang sometimes in the "Bach in Time" stage + if(title == "BUBSY II" && region == "PAL") emulator->configure("Hacks/DSP/Fast", false); + + //fixes an errant scanline on the title screen due to writing to PPU registers too late + if(title == "ADVENTURES OF FRANKEN" && region == "PAL") emulator->configure("Hacks/PPU/RenderCycle", 32); + + //fixes an errant scanline on the title screen due to writing to PPU registers too late + if(title == "FIREPOWER 2000" || title == "SUPER SWIV") emulator->configure("Hacks/PPU/RenderCycle", 32); + + //fixes an errant scanline on the title screen due to writing to PPU registers too late + if(title == "NHL '94" || title == "NHL PROHOCKEY'94") emulator->configure("Hacks/PPU/RenderCycle", 32); + + //fixes an errant scanline on the title screen due to writing to PPU registers too late + if(title == "Sugoro Quest++") emulator->configure("Hacks/PPU/RenderCycle", 128); + + if (emulator->configuration("Hacks/Hotfixes")) { + //this game transfers uninitialized memory into video RAM: this can cause a row of invalid tiles + //to appear in the background of stage 12. this one is a bug in the original game, so only enable + //it if the hotfixes option has been enabled. + if(title == "The Hurricanes") emulator->configure("Hacks/Entropy", "None"); + + //Frisky Tom attract sequence sometimes hangs when WRAM is initialized to pseudo-random patterns + if (title == "ニチブツ・アーケード・クラシックス") emulator->configure("Hacks/Entropy", "None"); + } + + emulator->power(); +} + +auto Program::load(uint id, string name, string type, vector options) -> Emulator::Platform::Load { + + if (id == 1) + { + if (loadSuperFamicom()) + { + return {id, superFamicom.region}; + } + } + else if (id == 2) + { + if (loadGameBoy()) + { + return { id, NULL }; + } + } + else if (id == 3) + { + if (loadBSMemory()) + { + return { id, NULL }; + } + } + return { id, options(0) }; +} + +auto Program::loadSuperFamicom() -> bool +{ + vector& rom = superFamicom.raw_data; + fprintf(stderr, "location: \"%s\"\n", superFamicom.location.data()); + fprintf(stderr, "rom size: %ld\n", rom.size()); + + if(rom.size() < 0x8000) return false; + + auto heuristics = Heuristics::SuperFamicom(rom, superFamicom.location); + + superFamicom.title = heuristics.title(); + superFamicom.region = heuristics.videoRegion(); + superFamicom.manifest = heuristics.manifest(); + + hackPatchMemory(rom); + superFamicom.document = BML::unserialize(superFamicom.manifest); + fprintf(stderr, "loaded game manifest: \"\n%s\"\n", superFamicom.manifest.data()); + + uint offset = 0; + if(auto size = heuristics.programRomSize()) { + superFamicom.program.acquire(rom.data() + offset, size); + offset += size; + } + if(auto size = heuristics.dataRomSize()) { + superFamicom.data.acquire(rom.data() + offset, size); + offset += size; + } + if(auto size = heuristics.expansionRomSize()) { + superFamicom.expansion.acquire(rom.data() + offset, size); + offset += size; + } + if(auto size = heuristics.firmwareRomSize()) { + superFamicom.firmware.acquire(rom.data() + offset, size); + offset += size; + } + return true; +} + +auto Program::loadGameBoy() -> bool { + if (gameBoy.program.size() < 0x4000) return false; + + auto heuristics = Heuristics::GameBoy(gameBoy.program, gameBoy.location); + + gameBoy.manifest = heuristics.manifest(); + gameBoy.document = BML::unserialize(gameBoy.manifest); + + return true; +} + +auto Program::loadBSMemory() -> bool { + if (bsMemory.program.size() < 0x8000) return false; + + auto heuristics = Heuristics::BSMemory(bsMemory.program, gameBoy.location); + + bsMemory.manifest = heuristics.manifest(); + bsMemory.document = BML::unserialize(bsMemory.manifest); + + return true; +} + +auto Program::videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void { + + // note: scale is not used currently, but as bsnes has builtin scaling support (something something mode 7) + // we might actually wanna make use of that? also overscan might always be false rn, will need to check + pitch >>= 1; + if (!overscan) + { + uint multiplier = height / 240; + data += 8 * pitch * multiplier; + height -= 16 * multiplier; + } + + fprintf(stderr, "got a video frame with dimensions h: %d, w: %d, p: %d, overscan: %d, scale: %d\n", height, width, pitch, overscan, scale); + + snesCallbacks.snes_video_frame(data, width, height, pitch); +} + +// Double the fun! +static int16_t d2i16(double v) +{ + v *= 0x8000; + if (v > 0x7fff) + v = 0x7fff; + else if (v < -0x8000) + v = -0x8000; + return int16_t(floor(v + 0.5)); +} + +auto Program::audioFrame(const double* samples, uint channels) -> void +{ + int16_t left = d2i16(samples[0]); + int16_t right = d2i16(samples[1]); + return snesCallbacks.snes_audio_sample(left, right); +} + +auto Program::notify(string message) -> void +{ + if (message == "NOTIFY NO_LAG"); + snesCallbacks.snes_no_lag(); +} + +auto Program::cpuTrace(vector parts) -> void +{ + snesCallbacks.snes_trace(parts[0], parts[1]); +} + +auto Program::getBackdropColor() -> uint16 +{ + return backdropColor; +} + +auto Program::inputPoll(uint port, uint device, uint input) -> int16 +{ + int index = 0; + int id = input; + if (device == ID::Device::SuperMultitap) { + index = input / 12; + id = input % 12; + } else if (device == ID::Device::Justifiers) { + index = input / 4; + id = input % 4; + } + + return snesCallbacks.snes_input_state(port, index, id); +} + +auto Program::inputRumble(uint port, uint device, uint input, bool enable) -> void +{ +} + + +auto Program::hackPatchMemory(vector& data) -> void +{ + auto title = superFamicom.title; + + if(title == "Satellaview BS-X" && data.size() >= 0x100000) { + //BS-X: Sore wa Namae o Nusumareta Machi no Monogatari (JPN) (1.1) + //disable limited play check for BS Memory flash cartridges + //benefit: allow locked out BS Memory flash games to play without manual header patching + //detriment: BS Memory ROM cartridges will cause the game to hang in the load menu + if(data[0x4a9b] == 0x10) data[0x4a9b] = 0x80; + if(data[0x4d6d] == 0x10) data[0x4d6d] = 0x80; + if(data[0x4ded] == 0x10) data[0x4ded] = 0x80; + if(data[0x4e9a] == 0x10) data[0x4e9a] = 0x80; + } +} diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/resources.hpp b/waterbox/bsnescore/bsnes/target-bsnescore/resources.hpp new file mode 100644 index 0000000000..7f4dfac898 --- /dev/null +++ b/waterbox/bsnescore/bsnes/target-bsnescore/resources.hpp @@ -0,0 +1,1015 @@ +const unsigned char Boards[32230] = { + 100,97,116,97,98,97,115,101,10,32,32,114,101,118,105,115,105,111,110,58,32,50,48,50,48,45,48,54,45,48,55,10, + 10,47,47,66,111,97,114,100,115,32,40,80,114,111,100,117,99,116,105,111,110,41,10,10,100,97,116,97,98,97,115,101, + 10,32,32,114,101,118,105,115,105,111,110,58,32,50,48,50,48,45,48,49,45,50,49,10,10,98,111,97,114,100,58,32, + 66,65,78,68,65,73,45,80,84,45,57,50,51,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32, + 99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61, + 48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48, + 48,10,32,32,115,108,111,116,32,116,121,112,101,61,83,117,102,97,109,105,84,117,114,98,111,10,32,32,32,32,114,111, + 109,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58, + 56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,114,97,109,10,32,32, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,54,48,45,54,102,44,101,48,45,101,102,58,48,48,48,48, + 45,102,102,102,102,10,32,32,115,108,111,116,32,116,121,112,101,61,83,117,102,97,109,105,84,117,114,98,111,10,32,32, + 32,32,114,111,109,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,44,99,48, + 45,100,102,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,114,97, + 109,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58, + 48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,66,83,67,45,49,65,53,66,57,80,45,48,49,10, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32, + 32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,49,48,45,49,55,58,53,48,48,48,45,53,102,102,102,32,109, + 97,115,107,61,48,120,102,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110,116,105,102,105,101, + 114,61,77,67,67,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,48,102,58,53,48,48,48, + 45,53,102,102,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61, + 48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48, + 45,55,102,102,102,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116, + 101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65, + 77,32,99,111,110,116,101,110,116,61,68,111,119,110,108,111,97,100,10,32,32,32,32,32,32,115,108,111,116,32,116,121, + 112,101,61,66,83,77,101,109,111,114,121,10,10,98,111,97,114,100,58,32,66,83,67,45,49,65,53,77,45,48,50,10, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97, + 109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102,58,56,48,48,48,45,102,102,102, + 102,32,109,97,115,107,61,48,120,101,48,56,48,48,48,32,98,97,115,101,61,48,120,48,48,48,48,48,48,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,58,56,48,48,48,45,102,102,102,102,32,109,97, + 115,107,61,48,120,101,48,56,48,48,48,32,98,97,115,101,61,48,120,49,48,48,48,48,48,10,32,32,32,32,109,97, + 112,32,97,100,100,114,101,115,115,61,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48, + 120,101,48,56,48,48,48,32,98,97,115,101,61,48,120,50,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100, + 100,114,101,115,115,61,97,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,101,48,56, + 48,48,48,32,98,97,115,101,61,48,120,49,48,48,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115, + 61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48, + 48,48,10,32,32,115,108,111,116,32,116,121,112,101,61,66,83,77,101,109,111,114,121,10,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,99,48,45,101,102,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32, + 66,83,67,45,49,65,55,77,45,40,48,49,44,49,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,49,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,101,48,56,48,48,48, + 32,98,97,115,101,61,48,120,48,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50, + 48,45,51,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,101,48,56,48,48,48,32,98,97,115, + 101,61,48,120,49,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,56,48,45,57,102, + 58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,101,48,56,48,48,48,32,98,97,115,101,61,48,120, + 50,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,97,48,45,98,102,58,56,48,48, + 48,45,102,102,102,102,32,109,97,115,107,61,48,120,101,48,56,48,48,48,32,98,97,115,101,61,48,120,49,48,48,48, + 48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118, + 101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48, + 48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,115,108,111,116,32,116,121,112,101,61, + 66,83,77,101,109,111,114,121,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,101,102,58,48, + 48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,66,83,67,45,49,74,51,77,45,48,49,10,32,32,109, + 101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32, + 32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45, + 102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,44,99,48,45,100,102, + 58,48,48,48,48,45,102,102,102,102,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110, + 116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44, + 97,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,32,32,115,108, + 111,116,32,116,121,112,101,61,66,83,77,101,109,111,114,121,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115, + 61,50,48,45,51,102,44,97,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,54,48,45,55,100,44,101,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,10,98,111, + 97,114,100,58,32,66,83,67,45,49,74,53,77,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,52,48,45,53,102,44,99,48,45,100,102,58,48,48,48,48,45,102,102,102,102,10,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48,45,55, + 102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,32,32,115,108,111,116,32,116,121,112,101,61,66,83,77,101, + 109,111,114,121,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102, + 58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,54,48,45,55,100, + 44,101,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,66,83,67,45,49,76,51, + 66,45,48,49,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,87,54, + 53,67,56,49,54,83,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45, + 98,102,58,50,50,48,48,45,50,51,102,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115, + 107,61,48,120,52,48,56,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45, + 102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,115,108,111,116,32,116,121, + 112,101,61,66,83,77,101,109,111,114,121,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32, + 99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48, + 48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,115,105,122,101,61,48,120,50,48,48,48, + 10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,52,102,58,48,48,48,48,45,102,102, + 102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,73, + 110,116,101,114,110,97,108,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44, + 56,48,45,98,102,58,51,48,48,48,45,51,55,102,102,32,115,105,122,101,61,48,120,56,48,48,10,10,98,111,97,114, + 100,58,32,66,83,67,45,49,76,53,66,45,48,49,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105, + 116,101,99,116,117,114,101,61,87,54,53,67,56,49,54,83,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115, + 61,48,48,45,51,102,44,56,48,45,98,102,58,50,50,48,48,45,50,51,102,102,10,32,32,32,32,109,99,117,10,32, + 32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48, + 48,45,102,102,102,102,32,109,97,115,107,61,48,120,52,48,56,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32, + 32,32,32,115,108,111,116,32,116,121,112,101,61,66,83,77,101,109,111,114,121,10,32,32,32,32,109,101,109,111,114,121, + 32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,115, + 105,122,101,61,48,120,50,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45, + 52,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77, + 32,99,111,110,116,101,110,116,61,73,110,116,101,114,110,97,108,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,51,48,48,48,45,51,55,102,102,32,115,105,122,101,61,48, + 120,56,48,48,10,10,98,111,97,114,100,58,32,83,71,66,45,82,45,49,48,10,32,32,109,101,109,111,114,121,32,116, + 121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97, + 115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44, + 99,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,112,114, + 111,99,101,115,115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,73,67,68,32,114,101,118,105,115,105,111,110,61, + 50,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48, + 48,48,45,54,55,102,102,44,55,48,48,48,45,55,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112, + 101,61,82,79,77,32,99,111,110,116,101,110,116,61,66,111,111,116,32,97,114,99,104,105,116,101,99,116,117,114,101,61, + 83,77,56,51,10,32,32,32,32,115,108,111,116,32,116,121,112,101,61,71,97,109,101,66,111,121,10,10,98,111,97,114, + 100,58,32,83,72,86,67,45,49,65,48,78,45,40,48,49,44,48,50,44,49,48,44,50,48,44,51,48,41,10,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48, + 45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56, + 48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,65,49,66,45,40,48,52,44,48,53,44,48,54,41, + 10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114, + 97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102,44,56,48,45,57,102,58,56, + 48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100, + 58,32,83,72,86,67,45,49,65,49,77,45,40,48,49,44,49,48,44,49,49,44,50,48,41,10,32,32,109,101,109,111, + 114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102, + 102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32, + 99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45, + 55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10, + 98,111,97,114,100,58,32,83,72,86,67,45,49,65,51,66,45,40,49,49,44,49,50,44,49,51,41,10,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45,102, + 102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65, + 77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55, + 48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,83,72,86, + 67,45,49,65,51,66,45,50,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116, + 101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55, + 100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32, + 32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102, + 102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,65,51,77, + 45,40,49,48,44,50,48,44,50,49,44,51,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77, + 32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115, + 61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48, + 48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118, + 101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48, + 48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67, + 45,49,65,53,66,45,40,48,50,44,48,52,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32, + 99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61, + 48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48, + 48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101, + 10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48, + 48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,65,53,77,45,40,48,49,44,49,49,44, + 50,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114, + 111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102, + 102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,109,101,109,111,114,121, + 32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115, + 107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,66,48,78,45,40,48,50,44,48, + 51,44,49,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61, + 80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102,44,56,48, + 45,57,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,112,114,111,99, + 101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,51,48,45,51,102,44,98,48,45,98,102,58,56,48,48,48,45,102,102,102,102, + 32,109,97,115,107,61,48,120,51,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77, + 32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80, + 68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110, + 116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99, + 104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114, + 10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,66,53,66,45,48,50,10,32,32,109,101,109,111,114,121,32,116, + 121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,32,109,97, + 115,107,61,48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116, + 101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102, + 48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105, + 116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61, + 50,48,45,51,102,44,97,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,51,102,102, + 102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114, + 111,103,114,97,109,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109, + 101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104, + 105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101, + 61,82,65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117, + 80,68,55,55,50,53,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,83,72, + 86,67,45,49,67,48,78,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101, + 61,71,83,85,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102, + 58,51,48,48,48,45,51,52,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99, + 111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115, + 61,48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48, + 48,48,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83, + 97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,54,48,45,55,100,44,101,48,45,102, + 102,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,67,48,78,53,83,45, + 48,49,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,71,83,85,10, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,51,48,48,48, + 45,51,52,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110, + 116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49, + 102,44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32, + 32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,54,48,45,55,100,44,101,48,45,102,102,58,48,48,48, + 48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,67,65,48,78,53,83,45,48,49,10,32, + 32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,71,83,85,10,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,51,48,48,48,45,51,52,102, + 102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114, + 111,103,114,97,109,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48, + 45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,44,99,48,45,100,102,58,48,48,48,48,45,102,102,102, + 102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97, + 118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102, + 58,54,48,48,48,45,55,102,102,102,32,115,105,122,101,61,48,120,50,48,48,48,10,32,32,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,55,48,45,55,49,44,102,48,45,102,49,58,48,48,48,48,45,102,102,102,102,10,10, + 98,111,97,114,100,58,32,83,72,86,67,45,49,67,65,48,78,54,83,45,48,49,10,32,32,112,114,111,99,101,115,115, + 111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,71,83,85,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,51,48,48,48,45,51,52,102,102,10,32,32,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48, + 45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,52,48,45,53,102,44,99,48,45,100,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,32, + 32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102, + 102,102,32,115,105,122,101,61,48,120,50,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115, + 61,55,48,45,55,49,44,102,48,45,102,49,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,83, + 72,86,67,45,49,67,65,54,66,45,48,49,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101, + 99,116,117,114,101,61,71,83,85,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44, + 56,48,45,98,102,58,51,48,48,48,45,51,52,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,97,112,32,97,100, + 100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107, + 61,48,120,56,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,44, + 99,48,45,100,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,115,105,122,101,61,48,120, + 50,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,49,44,102,48,45, + 102,49,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,67,66,48,78,55, + 83,45,48,49,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,71,83, + 85,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,51,48, + 48,48,45,51,52,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116, + 101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48, + 45,51,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32, + 32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45, + 55,102,102,102,32,115,105,122,101,61,48,120,50,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,55,48,45,55,49,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,83,72,86,67,45, + 49,67,66,53,66,45,40,48,49,44,50,48,41,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116, + 101,99,116,117,114,101,61,71,83,85,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102, + 44,56,48,45,98,102,58,51,48,48,48,45,51,52,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101, + 61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,48,48,45,51,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48, + 48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,58,48,48,48,48,45, + 102,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116, + 61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48, + 45,98,102,58,54,48,48,48,45,55,102,102,102,32,115,105,122,101,61,48,120,50,48,48,48,10,32,32,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,49,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97, + 114,100,58,32,83,72,86,67,45,49,67,66,55,66,45,48,49,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114, + 99,104,105,116,101,99,116,117,114,101,61,71,83,85,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48, + 48,45,51,102,44,56,48,45,98,102,58,51,48,48,48,45,51,52,102,102,10,32,32,32,32,109,101,109,111,114,121,32, + 116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61, + 48,120,56,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,58,48, + 48,48,48,45,102,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110, + 116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51, + 102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,115,105,122,101,61,48,120,50,48,48,48,10,32,32, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,49,58,48,48,48,48,45,102,102,102,102,10, + 10,98,111,97,114,100,58,32,83,72,86,67,45,49,68,67,48,78,45,48,49,10,32,32,112,114,111,99,101,115,115,111, + 114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,72,71,53,49,66,83,49,54,57,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,99,48,48,45,54,102,102,102,44,55, + 99,48,48,45,55,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110, + 116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48, + 48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48, + 10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118, + 101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,55,58,48,48,48,48,45,55, + 102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61, + 68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,72,71,53,49,66,83,49,54,57,10,32,32,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99, + 104,105,116,101,99,116,117,114,101,61,72,71,53,49,66,83,49,54,57,10,32,32,32,32,32,32,109,97,112,32,97,100, + 100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,54,98,102,102,44,55,48,48,48, + 45,55,98,102,102,32,109,97,115,107,61,48,120,102,48,48,48,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114, + 10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,68,83,48,66,45,50,48,10,32,32,109,101,109,111,114,121,32, + 116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109, + 97,115,107,61,48,120,56,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116, + 117,114,101,61,117,80,68,57,54,48,53,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,54,48,45, + 54,55,44,101,48,45,101,55,58,48,48,48,48,45,51,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,32,97,114,99,104,105,116,101,99,116, + 117,114,101,61,117,80,68,57,54,48,53,48,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77, + 32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,57,54, + 48,53,48,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61, + 68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,57,54,48,53,48,10,32,32,32,32,32, + 32,109,97,112,32,97,100,100,114,101,115,115,61,54,56,45,54,102,44,101,56,45,101,102,58,48,48,48,48,45,55,102, + 102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98, + 111,97,114,100,58,32,83,72,86,67,45,49,74,48,78,45,40,48,49,44,49,48,44,50,48,41,10,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32, + 32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102, + 102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48, + 48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,74,49,77,45,40,49,49,44,50, + 48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111, + 103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102, + 58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100, + 44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61, + 50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48, + 48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,74,51,66,45,48,49,10,32,32,109,101,109,111,114,121,32, + 116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32, + 32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45, + 102,102,102,102,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83, + 97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58, + 54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,10,98,111,97,114,100,58,32,83,72, + 86,67,45,49,74,51,77,45,40,48,49,44,49,49,44,50,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101, + 61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100, + 114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102, + 10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48, + 45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49, + 74,53,77,45,40,48,49,44,49,49,44,50,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77, + 32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115, + 61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,109, + 101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48,45,55,102,102, + 102,32,109,97,115,107,61,48,120,101,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,75,48,78,45, + 48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111, + 103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102, + 58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100, + 44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99, + 104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,48,48,45,49,102,44,56,48,45,57,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,102, + 102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80, + 114,111,103,114,97,109,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99, + 104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112, + 101,61,82,65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61, + 117,80,68,55,55,50,53,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,83, + 72,86,67,45,49,75,49,66,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111, + 110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48, + 45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,109,101,109,111, + 114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109, + 97,115,107,61,48,120,101,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116, + 117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49, + 102,44,56,48,45,57,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,102,102,102,10,32,32,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109, + 32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121, + 32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116, + 117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32, + 99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50, + 53,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,75, + 49,88,45,49,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61, + 80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48, + 45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48, + 45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,109,101,109,111,114,121,32,116,121,112, + 101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120, + 101,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80, + 68,55,55,50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102,44,56,48,45,57, + 102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,102,102,102,10,32,32,32,32,109,101,109,111,114, + 121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,32,97,114,99,104,105, + 116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80, + 68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110, + 116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32, + 111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,76,48,78,51,83,45,48, + 49,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,87,54,53,67,56, + 49,54,83,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58, + 50,50,48,48,45,50,51,102,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48, + 120,52,48,56,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102,102,58, + 48,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32, + 99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,115,105,122,101,61,48,120, + 50,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,52,102,58,48,48,48, + 48,45,102,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101, + 110,116,61,73,110,116,101,114,110,97,108,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48, + 45,51,102,44,56,48,45,98,102,58,51,48,48,48,45,51,55,102,102,32,115,105,122,101,61,48,120,56,48,48,10,10, + 98,111,97,114,100,58,32,83,72,86,67,45,49,76,51,66,45,40,48,50,44,49,49,41,10,32,32,112,114,111,99,101, + 115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,87,54,53,67,56,49,54,83,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,50,50,48,48,45,50,51,102,102, + 10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102, + 44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,52,48,56,48,48,48,10,32, + 32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102, + 10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80, + 114,111,103,114,97,109,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101, + 110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44, + 56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,115,105,122,101,61,48,120,50,48,48,48,10,32,32,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,52,102,58,48,48,48,48,45,102,102,102,102,10,32,32, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,73,110,116,101,114,110, + 97,108,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102, + 58,51,48,48,48,45,51,55,102,102,32,115,105,122,101,61,48,120,56,48,48,10,10,98,111,97,114,100,58,32,83,72, + 86,67,45,49,76,53,66,45,40,49,49,44,50,48,41,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104, + 105,116,101,99,116,117,114,101,61,87,54,53,67,56,49,54,83,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,48,48,45,51,102,44,56,48,45,98,102,58,50,50,48,48,45,50,51,102,102,10,32,32,32,32,109,99,117,10, + 32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48, + 48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,52,48,56,48,48,48,10,32,32,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32, + 32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48, + 48,45,55,102,102,102,32,115,105,122,101,61,48,120,50,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100, + 114,101,115,115,61,52,48,45,52,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32, + 116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,73,110,116,101,114,110,97,108,10,32,32,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,51,48,48,48,45,51,55,102, + 102,32,115,105,122,101,61,48,120,56,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,49,78,48,78,45,40, + 48,49,44,49,48,41,10,32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,83,68, + 68,49,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,52, + 56,48,48,45,52,56,48,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109, + 10,10,98,111,97,114,100,58,32,83,72,86,67,45,50,65,48,78,45,48,49,35,65,10,32,32,109,101,109,111,114,121, + 32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97, + 112,32,97,100,100,114,101,115,115,61,48,48,45,50,102,44,56,48,45,97,102,58,56,48,48,48,45,102,102,102,102,32, + 109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,54, + 102,44,99,48,45,101,102,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98, + 111,97,114,100,58,32,83,72,86,67,45,50,65,48,78,45,40,48,49,44,49,48,44,49,49,44,50,48,41,10,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48, + 45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56, + 48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,50,65,49,77,45,48,49,10,32,32,109,101,109,111,114, + 121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102, + 32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99, + 111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55, + 100,44,102,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98, + 111,97,114,100,58,32,83,72,86,67,45,50,65,51,66,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101, + 61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100, + 114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61, + 48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116, + 61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102, + 102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32, + 83,72,86,67,45,50,65,51,77,45,48,49,35,65,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77, + 32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115, + 61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48, + 48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118, + 101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48, + 48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67, + 45,50,65,51,77,45,40,48,49,44,49,49,44,50,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120, + 56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83, + 97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58, + 48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,83,72, + 86,67,45,50,65,53,77,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110, + 116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45, + 55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55, + 102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,50,66,51, + 66,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80, + 114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45, + 98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,109,101,109,111,114, + 121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97, + 115,107,61,48,120,56,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117, + 114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,54,48,45,54,102, + 44,101,48,45,101,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,51,102,102,102,10,32,32,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109, + 32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121, + 32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116, + 117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32, + 99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50, + 53,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,83,72,86,67,45,50,68, + 67,48,78,45,48,49,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61, + 72,71,53,49,66,83,49,54,57,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44, + 56,48,45,98,102,58,54,99,48,48,45,54,102,102,102,44,55,99,48,48,45,55,102,102,102,10,32,32,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48, + 45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112, + 101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100, + 114,101,115,115,61,55,48,45,55,55,58,48,48,48,48,45,55,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32, + 116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117, + 114,101,61,72,71,53,49,66,83,49,54,57,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77, + 32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,72,71,53,49,66, + 83,49,54,57,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45, + 98,102,58,54,48,48,48,45,54,98,102,102,44,55,48,48,48,45,55,98,102,102,32,109,97,115,107,61,48,120,102,48, + 48,48,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,83,72,86,67,45,50, + 69,51,77,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116, + 61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56, + 48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,112,114,111, + 99,101,115,115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,79,66,67,49,10,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115, + 107,61,48,120,101,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,49,44,102, + 48,45,102,49,58,54,48,48,48,45,55,102,102,102,44,101,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120, + 101,48,48,48,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116, + 61,83,97,118,101,10,10,98,111,97,114,100,58,32,83,72,86,67,45,50,74,48,78,45,40,48,49,44,49,48,44,49, + 49,44,50,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61, + 80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48, + 45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48, + 45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,83,72,86,67, + 45,50,74,51,77,45,40,48,49,44,49,49,44,50,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,49,48,45,49,102,44,51,48,45,51,102,44,57,48,45,57,102,44, + 98,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,10,98,111,97, + 114,100,58,32,83,72,86,67,45,50,74,53,77,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,49,48,45,49,102,44,51,48,45,51,102,44,57,48,45,57,102,44, + 98,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,10,98,111,97, + 114,100,58,32,83,72,86,67,45,51,74,48,78,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,50,102,44,56,48,45,97,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,52,48,45,54,102,44,99,48,45,101,102,58,48,48,48,48,45,102,102,102,102,10,10, + 98,111,97,114,100,58,32,83,72,86,67,45,66,65,48,78,45,40,48,49,44,49,48,41,10,32,32,109,101,109,111,114, + 121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102, + 32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45, + 55,100,44,99,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10, + 98,111,97,114,100,58,32,83,72,86,67,45,66,65,49,77,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112, + 101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100, + 100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107, + 61,48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110, + 116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45, + 102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58, + 32,83,72,86,67,45,66,65,51,77,45,40,48,49,44,49,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101, + 61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100, + 114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61, + 48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116, + 61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102, + 102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32, + 83,72,86,67,45,66,74,48,78,45,40,48,49,44,50,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97, + 112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10, + 10,98,111,97,114,100,58,32,83,72,86,67,45,66,74,49,77,45,40,49,48,44,50,48,41,10,32,32,109,101,109,111, + 114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102, + 102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48, + 48,48,45,102,102,102,102,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110, + 116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45, + 98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,10,98,111,97,114,100,58, + 32,83,72,86,67,45,66,74,51,77,45,40,49,48,44,50,48,41,10,32,32,109,101,109,111,114,121,32,116,121,112,101, + 61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100, + 114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102, + 10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48, + 45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,76, + 68,72,51,67,45,48,49,10,32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,83, + 80,67,55,49,49,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45, + 98,102,58,52,56,48,48,45,52,56,51,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,53,48,44, + 53,56,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115, + 107,61,48,120,56,48,48,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45, + 102,102,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,99,48,48,48,48,48,10,32,32,32,32,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109, + 10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68, + 97,116,97,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61, + 83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45, + 98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,32,32,114,116,99,32,109, + 97,110,117,102,97,99,116,117,114,101,114,61,69,112,115,111,110,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,48,48,45,51,102,44,56,48,45,98,102,58,52,56,52,48,45,52,56,52,50,10,32,32,32,32,109,101,109,111, + 114,121,32,116,121,112,101,61,82,84,67,32,99,111,110,116,101,110,116,61,84,105,109,101,32,109,97,110,117,102,97,99, + 116,117,114,101,114,61,69,112,115,111,110,10,10,98,111,97,114,100,58,32,83,72,86,67,45,76,74,51,77,45,48,49, + 10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114, + 97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,58,56,48,48,48,45,102,102, + 102,102,32,98,97,115,101,61,48,120,52,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115, + 61,52,48,45,55,100,58,48,48,48,48,45,102,102,102,102,32,98,97,115,101,61,48,120,52,48,48,48,48,48,10,32, + 32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109, + 97,115,107,61,48,120,99,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45, + 102,102,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,99,48,48,48,48,48,10,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97, + 112,32,97,100,100,114,101,115,115,61,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48, + 120,101,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,76,78,51,66,45,48,49,10,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97, + 112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32, + 109,97,115,107,61,48,120,101,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55, + 51,58,48,48,48,48,45,102,102,102,102,10,32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110,116,105,102,105, + 101,114,61,83,68,68,49,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48, + 45,98,102,58,52,56,48,48,45,52,56,48,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10, + 32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114, + 111,103,114,97,109,10,10,98,111,97,114,100,58,32,83,72,86,67,45,83,71,66,50,45,48,49,10,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32, + 32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102, + 102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52, + 48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48, + 10,32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,73,67,68,32,114,101,118,105, + 115,105,111,110,61,50,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45, + 98,102,58,54,48,48,48,45,54,55,102,102,44,55,48,48,48,45,55,102,102,102,10,32,32,32,32,109,101,109,111,114, + 121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,66,111,111,116,32,97,114,99,104,105,116,101,99, + 116,117,114,101,61,83,77,56,51,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,32,32,32,32,115,108,111, + 116,32,116,121,112,101,61,71,97,109,101,66,111,121,10,10,98,111,97,114,100,58,32,83,72,86,67,45,89,65,48,78, + 45,48,49,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114, + 111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102, + 102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97, + 115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,83,72,86,67,45,89,74,48,78,45,48,49,10,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109, + 10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48, + 48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45, + 102,102,58,48,48,48,48,45,102,102,102,102,10,10,47,47,66,111,97,114,100,115,32,40,80,114,111,116,111,116,121,112, + 101,115,41,10,10,98,111,97,114,100,58,32,83,72,86,67,45,50,80,51,66,45,48,49,10,32,32,109,101,109,111,114, + 121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102, + 32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45, + 55,100,44,99,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10, + 98,111,97,114,100,58,32,83,72,86,67,45,52,80,86,53,66,45,48,49,10,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115, + 107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99, + 48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,47,47,66,111, + 97,114,100,115,32,40,71,101,110,101,114,105,99,41,10,10,100,97,116,97,98,97,115,101,10,32,32,114,101,118,105,115, + 105,111,110,58,32,50,48,50,48,45,48,54,45,48,55,10,10,98,111,97,114,100,58,32,65,82,77,45,76,79,82,79, + 77,45,82,65,77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61, + 80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48, + 45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97, + 112,32,97,100,100,114,101,115,115,61,52,48,45,54,102,44,99,48,45,101,102,58,48,48,48,48,45,55,102,102,102,32, + 109,97,115,107,61,48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111, + 110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100, + 44,102,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99, + 104,105,116,101,99,116,117,114,101,61,65,82,77,54,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48, + 48,45,51,102,44,56,48,45,98,102,58,51,56,48,48,45,51,56,102,102,10,32,32,32,32,109,101,109,111,114,121,32, + 116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,32,97,114,99,104,105,116,101, + 99,116,117,114,101,61,65,82,77,54,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99, + 111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,65,82,77,54,10,32,32, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97, + 114,99,104,105,116,101,99,116,117,114,101,61,65,82,77,54,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10, + 10,98,111,97,114,100,58,32,66,83,45,72,73,82,79,77,45,82,65,77,10,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32, + 32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,44,99,48,45,100,102,58,48,48,48,48,45,102,102, + 102,102,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118, + 101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48, + 48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,32,32,115,108,111,116,32,116,121,112,101,61, + 66,83,77,101,109,111,114,121,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97, + 48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,54, + 48,45,55,100,44,101,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,10,98,111,97,114,100,58,32,66,83,45, + 76,79,82,79,77,45,82,65,77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116, + 101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49, + 102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,101,48,56,48,48,48,32,98,97,115,101,61,48, + 120,48,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,58,56,48, + 48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,101,48,56,48,48,48,32,98,97,115,101,61,48,120,49,48,48, + 48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,56,48,45,57,102,58,56,48,48,48,45,102, + 102,102,102,32,109,97,115,107,61,48,120,101,48,56,48,48,48,32,98,97,115,101,61,48,120,50,48,48,48,48,48,10, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,97,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32, + 109,97,115,107,61,48,120,101,48,56,48,48,48,32,98,97,115,101,61,48,120,49,48,48,48,48,48,10,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102,102,102, + 32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,115,108,111,116,32,116,121,112,101,61,66,83,77,101,109,111,114, + 121,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,101,102,58,48,48,48,48,45,102,102,102, + 102,10,10,98,111,97,114,100,58,32,66,83,45,77,67,67,45,82,65,77,10,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,49,48,45,49,55,58,53,48,48,48,45,53,102,102,102,32,109,97,115,107,61,48,120,102,48,48,48,10, + 32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,77,67,67,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,48,48,45,48,102,58,53,48,48,48,45,53,102,102,102,10,32,32,32,32,109, + 99,117,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102, + 58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45, + 55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,97,112,32,97,100,100, + 114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48,45,55,102,102,102,10,32,32,32,32,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109, + 10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,68, + 111,119,110,108,111,97,100,10,32,32,32,32,32,32,115,108,111,116,32,116,121,112,101,61,66,83,77,101,109,111,114,121, + 10,10,98,111,97,114,100,58,32,66,83,45,83,65,49,45,82,65,77,10,32,32,112,114,111,99,101,115,115,111,114,32, + 97,114,99,104,105,116,101,99,116,117,114,101,61,87,54,53,67,56,49,54,83,10,32,32,32,32,109,97,112,32,97,100, + 100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,50,50,48,48,45,50,51,102,102,10,32,32,32,32, + 109,99,117,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98, + 102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,52,48,56,48,48,48,10,32,32,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97, + 109,10,32,32,32,32,32,32,115,108,111,116,32,116,121,112,101,61,66,83,77,101,109,111,114,121,10,32,32,32,32,109, + 101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55, + 102,102,102,32,115,105,122,101,61,48,120,50,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,52,48,45,52,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112, + 101,61,82,65,77,32,99,111,110,116,101,110,116,61,73,110,116,101,114,110,97,108,10,32,32,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,51,48,48,48,45,51,55,102,102,32,115, + 105,122,101,61,48,120,56,48,48,10,10,98,111,97,114,100,58,32,69,86,69,78,84,45,67,67,57,50,10,32,32,109, + 101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102,102, + 102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,109,97,110,117,102,97, + 99,116,117,114,101,114,61,78,69,67,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,56,50,49,52, + 10,32,32,32,32,105,100,101,110,116,105,102,105,101,114,58,32,67,97,109,112,117,115,32,67,104,97,108,108,101,110,103, + 101,32,39,57,50,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,44,101,48,58,48,48,48,48, + 10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102, + 44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,76,101,118,101,108,45,49,10,32,32,32, + 32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,76,101,118,101,108, + 45,50,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116, + 61,76,101,118,101,108,45,51,10,32,32,32,32,100,105,112,10,32,32,112,114,111,99,101,115,115,111,114,32,109,97,110, + 117,102,97,99,116,117,114,101,114,61,78,69,67,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55, + 50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,56, + 48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,55,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32, + 116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,32,97,114,99,104,105,116,101, + 99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79, + 77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55, + 55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61, + 68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,111,115, + 99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,69,86,69,78,84,45,80,70,57,52,10,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,51,48,45,51,102,44,98,48,45,98,102,58,54,48,48,48,45,55,102,102,102, + 32,109,97,115,107,61,48,120,101,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,109,97,110,117,102,97,99, + 116,117,114,101,114,61,78,69,67,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,56,50,49,52,10, + 32,32,32,32,105,100,101,110,116,105,102,105,101,114,58,32,80,111,119,101,114,70,101,115,116,32,39,57,52,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,49,48,44,50,48,58,54,48,48,48,10,32,32,32,32,109,99,117, + 10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56, + 48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102,102, + 58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77, + 32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,76,101,118,101,108,45,49,10,32,32,32,32,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,76,101,118,101,108,45,50,10,32,32,32, + 32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,76,101,118,101,108, + 45,51,10,32,32,32,32,100,105,112,10,32,32,112,114,111,99,101,115,115,111,114,32,109,97,110,117,102,97,99,116,117, + 114,101,114,61,78,69,67,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32, + 32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,48,102,44,56,48,45,56,102,58,54,48,48,48,45,55,102, + 102,102,32,109,97,115,107,61,48,120,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79, + 77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117, + 80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101, + 110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114, + 99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,111,115,99,105,108,108,97,116,111, + 114,10,10,98,111,97,114,100,58,32,69,88,72,73,82,79,77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,48,48,45,51,102,58,56,48,48,48,45,102,102,102,102,32,98,97,115,101,61,48,120,52,48,48,48,48, + 48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,58,48,48,48,48,45,102,102,102, + 102,32,98,97,115,101,61,48,120,52,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61, + 56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,99,48,48,48,48,48,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,32,109,97, + 115,107,61,48,120,99,48,48,48,48,48,10,10,98,111,97,114,100,58,32,69,88,72,73,82,79,77,45,82,65,77,10, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97, + 109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,58,56,48,48,48,45,102,102,102, + 102,32,98,97,115,101,61,48,120,52,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61, + 52,48,45,55,100,58,48,48,48,48,45,102,102,102,102,32,98,97,115,101,61,48,120,52,48,48,48,48,48,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97, + 115,107,61,48,120,99,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102, + 102,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,99,48,48,48,48,48,10,32,32,109,101,109,111, + 114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109, + 97,115,107,61,48,120,101,48,48,48,10,10,98,111,97,114,100,58,32,69,88,72,73,82,79,77,45,82,65,77,45,83, + 72,65,82,80,82,84,67,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110, + 116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,58, + 56,48,48,48,45,102,102,102,102,32,98,97,115,101,61,48,120,52,48,48,48,48,48,10,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,52,48,45,55,100,58,48,48,48,48,45,102,102,102,102,32,98,97,115,101,61,48,120,52, + 48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,56,48,45,98,102,58,56,48,48,48, + 45,102,102,102,102,32,109,97,115,107,61,48,120,99,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,99,48,48,48,48, + 48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101, + 10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48, + 48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,32,32,114,116,99,32,109,97,110,117,102,97,99, + 116,117,114,101,114,61,83,104,97,114,112,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51, + 102,44,56,48,45,98,102,58,50,56,48,48,45,50,56,48,49,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112, + 101,61,82,84,67,32,99,111,110,116,101,110,116,61,84,105,109,101,32,109,97,110,117,102,97,99,116,117,114,101,114,61, + 83,104,97,114,112,10,10,98,111,97,114,100,58,32,69,88,76,79,82,79,77,10,32,32,109,101,109,111,114,121,32,116, + 121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,48,48,45,55,100,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56, + 48,56,48,48,48,32,98,97,115,101,61,48,120,52,48,48,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,56,48,48, + 48,32,98,97,115,101,61,48,120,48,48,48,48,48,48,10,10,98,111,97,114,100,58,32,69,88,76,79,82,79,77,45, + 82,65,77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114, + 111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,58,56,48,48,48, + 45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,56,48,48,48,32,98,97,115,101,61,48,120,52,48,48,48,48, + 48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,56,48,45,102,102,58,56,48,48,48,45,102,102,102, + 102,32,109,97,115,107,61,48,120,56,48,56,48,48,48,32,98,97,115,101,61,48,120,48,48,48,48,48,48,10,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32, + 32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102, + 102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,69,88,78,69,67,45,76,79,82, + 79,77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111, + 103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102, + 58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,112,114,111,99,101,115,115, + 111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,57,54,48,53,48,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,54,48,45,54,55,44,101,48,45,101,55,58,48,48,48,48,45,51,102,102,102,10,32, + 32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114, + 97,109,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,57,54,48,53,48,10,32,32,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116, + 101,99,116,117,114,101,61,117,80,68,57,54,48,53,48,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80, + 68,57,54,48,53,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,54,56,45,54,102,44,101, + 56,45,101,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,111, + 115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,69,88,83,80,67,55,49,49,48,45,82,65,77,45, + 69,80,83,79,78,82,84,67,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101, + 110,116,61,69,120,112,97,110,115,105,111,110,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45, + 52,102,58,48,48,48,48,45,102,102,102,102,10,32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110,116,105,102, + 105,101,114,61,83,80,67,55,49,49,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51, + 102,44,56,48,45,98,102,58,52,56,48,48,45,52,56,51,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,53,48,44,53,56,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102, + 102,32,109,97,115,107,61,48,120,56,48,48,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,99,48,48,48,48,48,10, + 32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114, + 111,103,114,97,109,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116, + 101,110,116,61,68,97,116,97,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110, + 116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51, + 102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,32,32, + 114,116,99,32,109,97,110,117,102,97,99,116,117,114,101,114,61,69,112,115,111,110,10,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,52,56,52,48,45,52,56,52,50,10,32,32,32, + 32,109,101,109,111,114,121,32,116,121,112,101,61,82,84,67,32,99,111,110,116,101,110,116,61,84,105,109,101,32,109,97, + 110,117,102,97,99,116,117,114,101,114,61,69,112,115,111,110,10,10,98,111,97,114,100,58,32,71,66,45,76,79,82,79, + 77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103, + 114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58, + 56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,97,112,32,97,100, + 100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107, + 61,48,120,56,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,73, + 67,68,32,114,101,118,105,115,105,111,110,61,50,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48, + 45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,54,55,102,102,44,55,48,48,48,45,55,102,102,102,10,32,32, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,66,111,111,116,32,97, + 114,99,104,105,116,101,99,116,117,114,101,61,83,77,56,51,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10, + 32,32,32,32,115,108,111,116,32,116,121,112,101,61,71,97,109,101,66,111,121,10,10,98,111,97,114,100,58,32,71,83, + 85,45,82,65,77,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,71, + 83,85,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,51, + 48,48,48,45,51,52,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110, + 116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48, + 48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48, + 10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,44,99,48,45,100,102,58,48, + 48,48,48,45,102,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110, + 116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51, + 102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,115,105,122,101,61,48,120,50,48,48,48,10,32,32, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,49,44,102,48,45,102,49,58,48,48,48,48, + 45,102,102,102,102,10,10,98,111,97,114,100,58,32,72,73,82,79,77,10,32,32,109,101,109,111,114,121,32,116,121,112, + 101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100, + 100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102, + 102,10,10,98,111,97,114,100,58,32,72,73,82,79,77,45,82,65,77,10,32,32,109,101,109,111,114,121,32,116,121,112, + 101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100, + 100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48,45,102,102,102, + 102,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101, + 10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,54,48,48, + 48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,10,98,111,97,114,100,58,32,72,73,84,65,67, + 72,73,45,76,79,82,79,77,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114, + 101,61,72,71,53,49,66,83,49,54,57,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51, + 102,44,56,48,45,98,102,58,54,99,48,48,45,54,102,102,102,44,55,99,48,48,45,55,102,102,102,10,32,32,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10, + 32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48, + 48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,109,101,109,111,114,121,32,116, + 121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,55,48,45,55,55,58,48,48,48,48,45,55,102,102,102,10,32,32,32,32,109,101,109,111,114, + 121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99, + 116,117,114,101,61,72,71,53,49,66,83,49,54,57,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,72,71,53, + 49,66,83,49,54,57,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56, + 48,45,98,102,58,54,48,48,48,45,54,98,102,102,44,55,48,48,48,45,55,98,102,102,32,109,97,115,107,61,48,120, + 102,48,48,48,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,76,79,82,79, + 77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103, + 114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44,56,48,45,102,102,58, + 56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,76,79, + 82,79,77,45,82,65,77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110, + 116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,55,100,44, + 56,48,45,102,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102,102,102, + 32,109,97,115,107,61,48,120,56,48,48,48,10,10,98,111,97,114,100,58,32,76,79,82,79,77,45,82,65,77,35,65, + 10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114, + 97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56, + 48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48, + 120,56,48,48,48,10,10,98,111,97,114,100,58,32,78,69,67,45,72,73,82,79,77,10,32,32,109,101,109,111,114,121, + 32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97, + 112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,10, + 32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44,99,48,45,102,102,58,48,48,48,48, + 45,102,102,102,102,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117, + 80,68,55,55,50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102,44,56,48,45, + 57,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,102,102,102,10,32,32,32,32,109,101,109,111, + 114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,32,97,114,99,104, + 105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101, + 61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117, + 80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101, + 110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32, + 32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,78,69,67,45,72,73,82,79,77,45,82,65, + 77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103, + 114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58, + 56,48,48,48,45,102,102,102,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,55,100,44, + 99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65, + 77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50, + 48,45,51,102,44,97,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48, + 10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50, + 53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102,44,56,48,45,57,102,58,54,48, + 48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,32,97,114,99,104,105,116,101,99,116, + 117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32, + 99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50, + 53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,68,97, + 116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,111,115,99,105, + 108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,78,69,67,45,76,79,82,79,77,10,32,32,109,101,109,111,114, + 121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102, + 32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101, + 99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,51,48, + 45,51,102,44,98,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,51,102,102,102,10, + 32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103, + 114,97,109,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116, + 101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68, + 55,55,50,53,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,78,69,67,45, + 76,79,82,79,77,45,82,65,77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116, + 101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51, + 102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32, + 109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32, + 32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45,55,102, + 102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105, + 116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61, + 54,48,45,54,102,44,101,48,45,101,102,58,48,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,51,102,102, + 102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114, + 111,103,114,97,109,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109, + 101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104, + 105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101, + 61,82,65,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117, + 80,68,55,55,50,53,10,32,32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,78,69, + 67,45,76,79,82,79,77,45,82,65,77,35,65,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32, + 99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61, + 48,48,45,49,102,44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48, + 48,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101, + 10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48, + 48,45,102,102,102,102,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61, + 117,80,68,55,55,50,53,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48, + 45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,51,102,102,102,10,32,32,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,32,97,114, + 99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121, + 112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101, + 61,117,80,68,55,55,50,53,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110, + 116,101,110,116,61,68,97,116,97,32,97,114,99,104,105,116,101,99,116,117,114,101,61,117,80,68,55,55,50,53,10,32, + 32,32,32,111,115,99,105,108,108,97,116,111,114,10,10,98,111,97,114,100,58,32,79,66,67,49,45,76,79,82,79,77, + 45,82,65,77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80, + 114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45, + 98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,112,114,111,99,101, + 115,115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,79,66,67,49,10,32,32,32,32,109,97,112,32,97,100,100, + 114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61, + 48,120,101,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,49,44,102,48,45, + 102,49,58,54,48,48,48,45,55,102,102,102,44,101,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,101,48, + 48,48,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83, + 97,118,101,10,10,98,111,97,114,100,58,32,83,65,49,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104, + 105,116,101,99,116,117,114,101,61,87,54,53,67,56,49,54,83,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,48,48,45,51,102,44,56,48,45,98,102,58,50,50,48,48,45,50,51,102,102,10,32,32,32,32,109,99,117,10, + 32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48, + 48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,52,48,56,48,48,48,10,32,32,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,101, + 109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32, + 32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,73,110,116,101,114,110, + 97,108,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102, + 58,51,48,48,48,45,51,55,102,102,32,115,105,122,101,61,48,120,56,48,48,10,10,98,111,97,114,100,58,32,83,65, + 49,45,82,65,77,10,32,32,112,114,111,99,101,115,115,111,114,32,97,114,99,104,105,116,101,99,116,117,114,101,61,87, + 54,53,67,56,49,54,83,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48, + 45,98,102,58,50,50,48,48,45,50,51,102,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32, + 97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97, + 115,107,61,48,120,52,48,56,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48, + 45,102,102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,101,109,111,114,121,32,116, + 121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97, + 100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,115,105,122, + 101,61,48,120,50,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,52,102, + 58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99, + 111,110,116,101,110,116,61,73,110,116,101,114,110,97,108,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115, + 115,61,48,48,45,51,102,44,56,48,45,98,102,58,51,48,48,48,45,51,55,102,102,32,115,105,122,101,61,48,120,56, + 48,48,10,10,98,111,97,114,100,58,32,83,68,68,49,10,32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110, + 116,105,102,105,101,114,61,83,68,68,49,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51, + 102,44,56,48,45,98,102,58,52,56,48,48,45,52,56,48,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32, + 109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102, + 102,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102, + 102,102,102,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110, + 116,61,80,114,111,103,114,97,109,10,10,98,111,97,114,100,58,32,83,68,68,49,45,82,65,77,10,32,32,109,101,109, + 111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,109,97, + 112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32, + 109,97,115,107,61,48,120,101,48,48,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55, + 51,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,112,114,111,99,101,115, + 115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,83,68,68,49,10,32,32,32,32,109,97,112,32,97,100,100,114, + 101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,52,56,48,48,45,52,56,48,102,10,32,32,32,32,109,99, + 117,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58, + 56,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102, + 102,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79, + 77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,10,98,111,97,114,100,58,32,83,80,67,55,49,49, + 48,45,82,65,77,10,32,32,112,114,111,99,101,115,115,111,114,32,105,100,101,110,116,105,102,105,101,114,61,83,80,67, + 55,49,49,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102, + 58,52,56,48,48,45,52,56,51,102,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,53,48,44,53,56, + 58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,99,117,10,32,32,32,32,32,32,109,97,112,32,97,100,100, + 114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61, + 48,120,56,48,48,48,48,48,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,99,48,45,102,102, + 58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,99,48,48,48,48,48,10,32,32,32,32,32,32,109, + 101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,80,114,111,103,114,97,109,10,32, + 32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101,110,116,61,68,97,116, + 97,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,65,77,32,99,111,110,116,101,110,116,61,83,97, + 118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102, + 58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120,101,48,48,48,10,10,98,111,97,114,100,58,32,83, + 80,67,55,49,49,48,45,82,65,77,45,69,80,83,79,78,82,84,67,10,32,32,112,114,111,99,101,115,115,111,114,32, + 105,100,101,110,116,105,102,105,101,114,61,83,80,67,55,49,49,48,10,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,52,56,48,48,45,52,56,51,102,10,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,53,48,44,53,56,58,48,48,48,48,45,102,102,102,102,10,32,32,32,32,109,99,117, + 10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,56, + 48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,48,48,10,32,32,32,32,32,32,109,97,112, + 32,97,100,100,114,101,115,115,61,99,48,45,102,102,58,48,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120, + 99,48,48,48,48,48,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110, + 116,101,110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82, + 79,77,32,99,111,110,116,101,110,116,61,68,97,116,97,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61, + 82,65,77,32,99,111,110,116,101,110,116,61,83,97,118,101,10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101, + 115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,54,48,48,48,45,55,102,102,102,32,109,97,115,107,61,48,120, + 101,48,48,48,10,32,32,114,116,99,32,109,97,110,117,102,97,99,116,117,114,101,114,61,69,112,115,111,110,10,32,32, + 32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,51,102,44,56,48,45,98,102,58,52,56,52,48,45,52, + 56,52,50,10,32,32,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,84,67,32,99,111,110,116,101,110,116,61, + 84,105,109,101,32,109,97,110,117,102,97,99,116,117,114,101,114,61,69,112,115,111,110,10,10,98,111,97,114,100,58,32, + 83,84,45,76,79,82,79,77,10,32,32,109,101,109,111,114,121,32,116,121,112,101,61,82,79,77,32,99,111,110,116,101, + 110,116,61,80,114,111,103,114,97,109,10,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,48,48,45,49,102, + 44,56,48,45,57,102,58,56,48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,115, + 108,111,116,32,116,121,112,101,61,83,117,102,97,109,105,84,117,114,98,111,10,32,32,32,32,114,111,109,10,32,32,32, + 32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,50,48,45,51,102,44,97,48,45,98,102,58,56,48,48,48,45, + 102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,114,97,109,10,32,32,32,32,32,32,109, + 97,112,32,97,100,100,114,101,115,115,61,54,48,45,54,102,44,101,48,45,101,102,58,48,48,48,48,45,102,102,102,102, + 10,32,32,115,108,111,116,32,116,121,112,101,61,83,117,102,97,109,105,84,117,114,98,111,10,32,32,32,32,114,111,109, + 10,32,32,32,32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,52,48,45,53,102,44,99,48,45,100,102,58,48, + 48,48,48,45,102,102,102,102,32,109,97,115,107,61,48,120,56,48,48,48,10,32,32,32,32,114,97,109,10,32,32,32, + 32,32,32,109,97,112,32,97,100,100,114,101,115,115,61,55,48,45,55,100,44,102,48,45,102,102,58,48,48,48,48,45, + 102,102,102,102,10,10, +}; + +const unsigned char iplrom[64] = { + 205,239,189,232,0,198,29,208,252,143,170,244,143,187,245,120,204,244,208,251,47,25,235,244,208,252,126,244,208,11,228,245, + 203,244,215,0,252,208,243,171,1,16,239,126,244,16,235,186,246,218,0,186,244,196,244,221,93,208,219,31,0,0,192,255, +}; diff --git a/waterbox/bsnescore/v115 b/waterbox/bsnescore/v115 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/waterbox/libco/amd64.c b/waterbox/libco/amd64.c index 702ff365a2..c59197c431 100644 --- a/waterbox/libco/amd64.c +++ b/waterbox/libco/amd64.c @@ -71,8 +71,6 @@ static cothread_impl* alloc_thread(uint64_t size) return co; } -extern void co_swap(cothread_impl*, cothread_impl*); - static void crash(void) { __asm__("int3"); // called only if cothread_t entrypoint returns @@ -150,3 +148,13 @@ void co_switch(cothread_t handle) "memory" ); } + +cothread_t co_derive(void* memory, unsigned sz, void (*entrypoint)(void)) +{ + return NULL; +} + +int co_serializable(void) +{ + return 0; +} diff --git a/waterbox/libco/libco.h b/waterbox/libco/libco.h index 31672077da..4a01ecd15a 100644 --- a/waterbox/libco/libco.h +++ b/waterbox/libco/libco.h @@ -17,6 +17,8 @@ cothread_t co_active(void); cothread_t co_create(unsigned int, void(*)(void)); void co_delete(cothread_t); void co_switch(cothread_t); +cothread_t co_derive(void* memory, unsigned int, void(*)(void)); +int co_serializable(void); #ifdef __cplusplus } diff --git a/waterbox/libsnes/.vscode/settings.json b/waterbox/libsnes/.vscode/settings.json deleted file mode 100644 index 320669d7d4..0000000000 --- a/waterbox/libsnes/.vscode/settings.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "files.associations": { - "ostream": "cpp", - "algorithm": "cpp", - "cmath": "cpp", - "cstddef": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "cwchar": "cpp", - "deque": "cpp", - "exception": "cpp", - "initializer_list": "cpp", - "ios": "cpp", - "iosfwd": "cpp", - "istream": "cpp", - "limits": "cpp", - "memory": "cpp", - "new": "cpp", - "queue": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "string": "cpp", - "system_error": "cpp", - "type_traits": "cpp", - "typeinfo": "cpp", - "utility": "cpp", - "vector": "cpp", - "xfacet": "cpp", - "xiosbase": "cpp", - "xlocale": "cpp", - "xlocinfo": "cpp", - "xlocnum": "cpp", - "xmemory": "cpp", - "xmemory0": "cpp", - "xstddef": "cpp", - "xstring": "cpp", - "xtr1common": "cpp", - "xutility": "cpp" - } -} \ No newline at end of file diff --git a/waterbox/libsnes/bsnes/nall/dsp/buffer.hpp b/waterbox/libsnes/bsnes/nall/dsp/buffer.hpp index 718ee98f76..58632d2f83 100644 --- a/waterbox/libsnes/bsnes/nall/dsp/buffer.hpp +++ b/waterbox/libsnes/bsnes/nall/dsp/buffer.hpp @@ -1,51 +1,51 @@ -#ifdef NALL_DSP_INTERNAL_HPP - -struct Buffer { - double **sample; - uint16_t rdoffset; - uint16_t wroffset; - unsigned channels; - - void setChannels(unsigned channels) { - for(unsigned c = 0; c < this->channels; c++) { - if(sample[c]) abort(); - } - if(sample) abort(); - - this->channels = channels; - if(channels == 0) return; - - sample = (double**)alloc_invisible(channels * sizeof(*sample)); - for(unsigned c = 0; c < channels; c++) { - sample[c] = (double*)alloc_invisible(65536 * sizeof(**sample)); - } - } - - inline double& read(unsigned channel, signed offset = 0) { - return sample[channel][(uint16_t)(rdoffset + offset)]; - } - - inline double& write(unsigned channel, signed offset = 0) { - return sample[channel][(uint16_t)(wroffset + offset)]; - } - - inline void clear() { - for(unsigned c = 0; c < channels; c++) { - for(unsigned n = 0; n < 65536; n++) { - sample[c][n] = 0; - } - } - rdoffset = 0; - wroffset = 0; - } - - Buffer() { - channels = 0; - } - - ~Buffer() { - setChannels(0); - } -}; - -#endif +#ifdef NALL_DSP_INTERNAL_HPP + +struct Buffer { + double **sample; + uint16_t rdoffset; + uint16_t wroffset; + unsigned channels; + + void setChannels(unsigned channels) { + for(unsigned c = 0; c < this->channels; c++) { + if(sample[c]) abort(); + } + if(sample) abort(); + + this->channels = channels; + if(channels == 0) return; + + sample = (double**)alloc_invisible(channels * sizeof(*sample)); + for(unsigned c = 0; c < channels; c++) { + sample[c] = (double*)alloc_invisible(65536 * sizeof(**sample)); + } + } + + inline double& read(unsigned channel, signed offset = 0) { + return sample[channel][(uint16_t)(rdoffset + offset)]; + } + + inline double& write(unsigned channel, signed offset = 0) { + return sample[channel][(uint16_t)(wroffset + offset)]; + } + + inline void clear() { + for(unsigned c = 0; c < channels; c++) { + for(unsigned n = 0; n < 65536; n++) { + sample[c][n] = 0; + } + } + rdoffset = 0; + wroffset = 0; + } + + Buffer() { + channels = 0; + } + + ~Buffer() { + setChannels(0); + } +}; + +#endif diff --git a/waterbox/libsnes/bsnes/nall/dsp/core.hpp b/waterbox/libsnes/bsnes/nall/dsp/core.hpp index cbf9b97b25..a7683db6e1 100644 --- a/waterbox/libsnes/bsnes/nall/dsp/core.hpp +++ b/waterbox/libsnes/bsnes/nall/dsp/core.hpp @@ -1,168 +1,168 @@ -#ifdef NALL_DSP_INTERNAL_HPP - -#include -#include -#include - -namespace nall { - -//precision: can be float, double or long double -#define real float - -struct DSP; - -struct Resampler { - DSP &dsp; - real frequency; - - virtual void setFrequency() = 0; - virtual void clear() = 0; - virtual void sample() = 0; - Resampler(DSP &dsp) : dsp(dsp) {} -}; - -struct DSP { - enum class ResampleEngine : unsigned { - Nearest, - Linear, - Cosine, - Cubic, - Hermite, - Average, - Sinc, - }; - - inline void setChannels(unsigned channels); - inline void setPrecision(unsigned precision); - inline void setFrequency(real frequency); //inputFrequency - inline void setVolume(real volume); - inline void setBalance(real balance); - - inline void setResampler(ResampleEngine resamplingEngine); - inline void setResamplerFrequency(real frequency); //outputFrequency - - inline void sample(signed channel[]); - inline bool pending(); - inline void read(signed channel[]); - - inline void clear(); - inline DSP(); - inline ~DSP(); - -protected: - friend class ResampleNearest; - friend class ResampleLinear; - friend class ResampleCosine; - friend class ResampleCubic; - friend class ResampleAverage; - friend class ResampleHermite; - friend class ResampleSinc; - - struct Settings { - unsigned channels; - unsigned precision; - real frequency; - real volume; - real balance; - - //internal - real intensity; - real intensityInverse; - } settings; - - Resampler *resampler; - inline void write(real channel[]); - - #include "buffer.hpp" - Buffer buffer; - Buffer output; - - inline void adjustVolume(); - inline void adjustBalance(); - inline signed clamp(const unsigned bits, const signed x); -}; - -#include "resample/nearest.hpp" -#include "resample/linear.hpp" -#include "resample/cosine.hpp" -#include "resample/cubic.hpp" -#include "resample/hermite.hpp" -#include "resample/average.hpp" -#include "resample/sinc.hpp" -#include "settings.hpp" - -void DSP::sample(signed channel[]) { - for(unsigned c = 0; c < settings.channels; c++) { - buffer.write(c) = (real)channel[c] * settings.intensityInverse; - } - buffer.wroffset++; - resampler->sample(); -} - -bool DSP::pending() { - return output.rdoffset != output.wroffset; -} - -void DSP::read(signed channel[]) { - adjustVolume(); - adjustBalance(); - - for(unsigned c = 0; c < settings.channels; c++) { - channel[c] = clamp(settings.precision, output.read(c) * settings.intensity); - } - output.rdoffset++; -} - -void DSP::write(real channel[]) { - for(unsigned c = 0; c < settings.channels; c++) { - output.write(c) = channel[c]; - } - output.wroffset++; -} - -void DSP::adjustVolume() { - for(unsigned c = 0; c < settings.channels; c++) { - output.read(c) *= settings.volume; - } -} - -void DSP::adjustBalance() { - if(settings.channels != 2) return; //TODO: support > 2 channels - if(settings.balance < 0.0) output.read(1) *= 1.0 + settings.balance; - if(settings.balance > 0.0) output.read(0) *= 1.0 - settings.balance; -} - -signed DSP::clamp(const unsigned bits, const signed x) { - const signed b = 1U << (bits - 1); - const signed m = (1U << (bits - 1)) - 1; - return (x > m) ? m : (x < -b) ? -b : x; -} - -void DSP::clear() { - buffer.clear(); - output.clear(); - resampler->clear(); -} - -DSP::DSP() { - setResampler(ResampleEngine::Hermite); - setResamplerFrequency(44100.0); - - setChannels(2); - setPrecision(16); - setFrequency(44100.0); - setVolume(1.0); - setBalance(0.0); - - clear(); -} - -DSP::~DSP() { - if(resampler) delete resampler; -} - -#undef real - -} - -#endif +#ifdef NALL_DSP_INTERNAL_HPP + +#include +#include +#include + +namespace nall { + +//precision: can be float, double or long double +#define real float + +struct DSP; + +struct Resampler { + DSP &dsp; + real frequency; + + virtual void setFrequency() = 0; + virtual void clear() = 0; + virtual void sample() = 0; + Resampler(DSP &dsp) : dsp(dsp) {} +}; + +struct DSP { + enum class ResampleEngine : unsigned { + Nearest, + Linear, + Cosine, + Cubic, + Hermite, + Average, + Sinc, + }; + + inline void setChannels(unsigned channels); + inline void setPrecision(unsigned precision); + inline void setFrequency(real frequency); //inputFrequency + inline void setVolume(real volume); + inline void setBalance(real balance); + + inline void setResampler(ResampleEngine resamplingEngine); + inline void setResamplerFrequency(real frequency); //outputFrequency + + inline void sample(signed channel[]); + inline bool pending(); + inline void read(signed channel[]); + + inline void clear(); + inline DSP(); + inline ~DSP(); + +protected: + friend class ResampleNearest; + friend class ResampleLinear; + friend class ResampleCosine; + friend class ResampleCubic; + friend class ResampleAverage; + friend class ResampleHermite; + friend class ResampleSinc; + + struct Settings { + unsigned channels; + unsigned precision; + real frequency; + real volume; + real balance; + + //internal + real intensity; + real intensityInverse; + } settings; + + Resampler *resampler; + inline void write(real channel[]); + + #include "buffer.hpp" + Buffer buffer; + Buffer output; + + inline void adjustVolume(); + inline void adjustBalance(); + inline signed clamp(const unsigned bits, const signed x); +}; + +#include "resample/nearest.hpp" +#include "resample/linear.hpp" +#include "resample/cosine.hpp" +#include "resample/cubic.hpp" +#include "resample/hermite.hpp" +#include "resample/average.hpp" +#include "resample/sinc.hpp" +#include "settings.hpp" + +void DSP::sample(signed channel[]) { + for(unsigned c = 0; c < settings.channels; c++) { + buffer.write(c) = (real)channel[c] * settings.intensityInverse; + } + buffer.wroffset++; + resampler->sample(); +} + +bool DSP::pending() { + return output.rdoffset != output.wroffset; +} + +void DSP::read(signed channel[]) { + adjustVolume(); + adjustBalance(); + + for(unsigned c = 0; c < settings.channels; c++) { + channel[c] = clamp(settings.precision, output.read(c) * settings.intensity); + } + output.rdoffset++; +} + +void DSP::write(real channel[]) { + for(unsigned c = 0; c < settings.channels; c++) { + output.write(c) = channel[c]; + } + output.wroffset++; +} + +void DSP::adjustVolume() { + for(unsigned c = 0; c < settings.channels; c++) { + output.read(c) *= settings.volume; + } +} + +void DSP::adjustBalance() { + if(settings.channels != 2) return; //TODO: support > 2 channels + if(settings.balance < 0.0) output.read(1) *= 1.0 + settings.balance; + if(settings.balance > 0.0) output.read(0) *= 1.0 - settings.balance; +} + +signed DSP::clamp(const unsigned bits, const signed x) { + const signed b = 1U << (bits - 1); + const signed m = (1U << (bits - 1)) - 1; + return (x > m) ? m : (x < -b) ? -b : x; +} + +void DSP::clear() { + buffer.clear(); + output.clear(); + resampler->clear(); +} + +DSP::DSP() { + setResampler(ResampleEngine::Hermite); + setResamplerFrequency(44100.0); + + setChannels(2); + setPrecision(16); + setFrequency(44100.0); + setVolume(1.0); + setBalance(0.0); + + clear(); +} + +DSP::~DSP() { + if(resampler) delete resampler; +} + +#undef real + +} + +#endif diff --git a/waterbox/libsnes/bsnes/nall/platform.hpp b/waterbox/libsnes/bsnes/nall/platform.hpp index c55617743a..3bbffd9ab9 100644 --- a/waterbox/libsnes/bsnes/nall/platform.hpp +++ b/waterbox/libsnes/bsnes/nall/platform.hpp @@ -1,155 +1,155 @@ -#ifndef NALL_PLATFORM_HPP -#define NALL_PLATFORM_HPP - -#if defined(_WIN32) - //minimum version needed for _wstat64, etc - #undef __MSVCRT_VERSION__ - #define __MSVCRT_VERSION__ 0x0601 - #include -#endif - -#ifdef _MSC_VER -#define _USE_MATH_DEFINES 1 -#endif - -//========================= -//standard platform headers -//========================= - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#if defined(_WIN32) - #include - #include - //#include //bizhawk chokes? - #include - #undef interface - #define dllexport __declspec(dllexport) - //bad things happen without these here -#else - #include - #include - #define dllexport -#endif - -//================== -//warning supression -//================== - -//Visual C++ -#if defined(_MSC_VER) - //disable libc "deprecation" warnings - #pragma warning(disable:4996) -#endif - -//================ -//POSIX compliance -//================ - -#if defined(_MSC_VER) - #define PATH_MAX _MAX_PATH - #define va_copy(dest, src) ((dest) = (src)) -#endif - -#if defined(_WIN32) - #define getcwd _getcwd - #define ftruncate _chsize - #define mkdir(n, m) _wmkdir(nall::utf16_t(n)) - #define putenv _putenv - #define rmdir _rmdir - #define vsnprintf _vsnprintf - inline void usleep(unsigned milliseconds) { Sleep(milliseconds / 1000); } -#endif - -//================ -//inline expansion -//================ - -#if defined(__GNUC__) - #define noinline __attribute__((noinline)) - #define inline inline - #define alwaysinline inline __attribute__((always_inline)) -#elif defined(_MSC_VER) - #define noinline __declspec(noinline) - #define inline inline - #define alwaysinline inline __forceinline -#else - #define noinline - #define inline inline - #define alwaysinline inline -#endif - -//========================= -//file system functionality -//========================= - -#if defined(_WIN32) - inline char* realpath(const char *filename, char *resolvedname) { - wchar_t fn[_MAX_PATH] = L""; - _wfullpath(fn, nall::utf16_t(filename), _MAX_PATH); - strcpy(resolvedname, nall::utf8_t(fn)); - for(unsigned n = 0; resolvedname[n]; n++) if(resolvedname[n] == '\\') resolvedname[n] = '/'; - return resolvedname; - } - - inline char* userpath(char *path) { - //TODO BIZHAWK - return nullptr; - //wchar_t fp[_MAX_PATH] = L""; - //SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, fp); - //strcpy(path, nall::utf8_t(fp)); - //for(unsigned n = 0; path[n]; n++) if(path[n] == '\\') path[n] = '/'; - //unsigned length = strlen(path); - //if(path[length] != '/') strcpy(path + length, "/"); - //return path; - } - - inline char* getcwd(char *path) { - //TODO BIZHAWK - return nullptr; - //wchar_t fp[_MAX_PATH] = L""; - //_wgetcwd(fp, _MAX_PATH); - //strcpy(path, nall::utf8_t(fp)); - //for(unsigned n = 0; path[n]; n++) if(path[n] == '\\') path[n] = '/'; - //unsigned length = strlen(path); - //if(path[length] != '/') strcpy(path + length, "/"); - //return path; - } -#else - //realpath() already exists - - inline char* userpath(char *path) { - *path = 0; - struct passwd *userinfo = getpwuid(getuid()); - if(userinfo) strcpy(path, userinfo->pw_dir); - unsigned length = strlen(path); - if(path[length] != '/') strcpy(path + length, "/"); - return path; - } - - inline char *getcwd(char *path) { - auto unused = getcwd(path, PATH_MAX); - unsigned length = strlen(path); - if(path[length] != '/') strcpy(path + length, "/"); - return path; - } -#endif - -#endif - +#ifndef NALL_PLATFORM_HPP +#define NALL_PLATFORM_HPP + +#if defined(_WIN32) + //minimum version needed for _wstat64, etc + #undef __MSVCRT_VERSION__ + #define __MSVCRT_VERSION__ 0x0601 + #include +#endif + +#ifdef _MSC_VER +#define _USE_MATH_DEFINES 1 +#endif + +//========================= +//standard platform headers +//========================= + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#if defined(_WIN32) + #include + #include + //#include //bizhawk chokes? + #include + #undef interface + #define dllexport __declspec(dllexport) + //bad things happen without these here +#else + #include + #include + #define dllexport +#endif + +//================== +//warning supression +//================== + +//Visual C++ +#if defined(_MSC_VER) + //disable libc "deprecation" warnings + #pragma warning(disable:4996) +#endif + +//================ +//POSIX compliance +//================ + +#if defined(_MSC_VER) + #define PATH_MAX _MAX_PATH + #define va_copy(dest, src) ((dest) = (src)) +#endif + +#if defined(_WIN32) + #define getcwd _getcwd + #define ftruncate _chsize + #define mkdir(n, m) _wmkdir(nall::utf16_t(n)) + #define putenv _putenv + #define rmdir _rmdir + #define vsnprintf _vsnprintf + inline void usleep(unsigned milliseconds) { Sleep(milliseconds / 1000); } +#endif + +//================ +//inline expansion +//================ + +#if defined(__GNUC__) + #define noinline __attribute__((noinline)) + #define inline inline + #define alwaysinline inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define noinline __declspec(noinline) + #define inline inline + #define alwaysinline inline __forceinline +#else + #define noinline + #define inline inline + #define alwaysinline inline +#endif + +//========================= +//file system functionality +//========================= + +#if defined(_WIN32) + inline char* realpath(const char *filename, char *resolvedname) { + wchar_t fn[_MAX_PATH] = L""; + _wfullpath(fn, nall::utf16_t(filename), _MAX_PATH); + strcpy(resolvedname, nall::utf8_t(fn)); + for(unsigned n = 0; resolvedname[n]; n++) if(resolvedname[n] == '\\') resolvedname[n] = '/'; + return resolvedname; + } + + inline char* userpath(char *path) { + //TODO BIZHAWK + return nullptr; + //wchar_t fp[_MAX_PATH] = L""; + //SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, fp); + //strcpy(path, nall::utf8_t(fp)); + //for(unsigned n = 0; path[n]; n++) if(path[n] == '\\') path[n] = '/'; + //unsigned length = strlen(path); + //if(path[length] != '/') strcpy(path + length, "/"); + //return path; + } + + inline char* getcwd(char *path) { + //TODO BIZHAWK + return nullptr; + //wchar_t fp[_MAX_PATH] = L""; + //_wgetcwd(fp, _MAX_PATH); + //strcpy(path, nall::utf8_t(fp)); + //for(unsigned n = 0; path[n]; n++) if(path[n] == '\\') path[n] = '/'; + //unsigned length = strlen(path); + //if(path[length] != '/') strcpy(path + length, "/"); + //return path; + } +#else + //realpath() already exists + + inline char* userpath(char *path) { + *path = 0; + struct passwd *userinfo = getpwuid(getuid()); + if(userinfo) strcpy(path, userinfo->pw_dir); + unsigned length = strlen(path); + if(path[length] != '/') strcpy(path + length, "/"); + return path; + } + + inline char *getcwd(char *path) { + auto unused = getcwd(path, PATH_MAX); + unsigned length = strlen(path); + if(path[length] != '/') strcpy(path + length, "/"); + return path; + } +#endif + +#endif + diff --git a/waterbox/libsnes/bsnes/snes/system/video.cpp b/waterbox/libsnes/bsnes/snes/system/video.cpp index dd817c88da..4500be24c6 100644 --- a/waterbox/libsnes/bsnes/snes/system/video.cpp +++ b/waterbox/libsnes/bsnes/snes/system/video.cpp @@ -1,103 +1,103 @@ -#ifdef SYSTEM_CPP - -Video video; - -//internal - -const uint8_t Video::cursor[15 * 15] = { - 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0, - 0,0,0,0,1,1,2,2,2,1,1,0,0,0,0, - 0,0,0,1,2,2,1,2,1,2,2,1,0,0,0, - 0,0,1,2,1,1,0,1,0,1,1,2,1,0,0, - 0,1,2,1,0,0,0,1,0,0,0,1,2,1,0, - 0,1,2,1,0,0,1,2,1,0,0,1,2,1,0, - 1,2,1,0,0,1,1,2,1,1,0,0,1,2,1, - 1,2,2,1,1,2,2,2,2,2,1,1,2,2,1, - 1,2,1,0,0,1,1,2,1,1,0,0,1,2,1, - 0,1,2,1,0,0,1,2,1,0,0,1,2,1,0, - 0,1,2,1,0,0,0,1,0,0,0,1,2,1,0, - 0,0,1,2,1,1,0,1,0,1,1,2,1,0,0, - 0,0,0,1,2,2,1,2,1,2,2,1,0,0,0, - 0,0,0,0,1,1,2,2,2,1,1,0,0,0,0, - 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0, -}; - -void Video::draw_cursor(uint16_t color, int x, int y) { - uint32_t *data = (uint32_t*)ppu.output; - if(ppu.interlace() && ppu.field()) data += 512; - - for(int cy = 0; cy < 15; cy++) { - int vy = y + cy - 7; - if(vy <= 0 || vy >= 240) continue; //do not draw offscreen - - bool hires = (line_width[vy] == 512); - for(int cx = 0; cx < 15; cx++) { - int vx = x + cx - 7; - if(vx < 0 || vx >= 256) continue; //do not draw offscreen - uint8_t pixel = cursor[cy * 15 + cx]; - if(pixel == 0) continue; - uint32_t pixelcolor = (15 << 15) | ((pixel == 1) ? 0 : color); - - if(hires == false) { - *((uint32_t*)data + vy * 1024 + vx) = pixelcolor; - } else { - *((uint32_t*)data + vy * 1024 + vx * 2 + 0) = pixelcolor; - *((uint32_t*)data + vy * 1024 + vx * 2 + 1) = pixelcolor; - } - } - } -} - -void Video::update() { - switch(config.controller_port2) { - case Input::Device::SuperScope: - if(dynamic_cast(input.port2)) { - SuperScope &device = (SuperScope&)*input.port2; - draw_cursor(0x7c00, device.x, device.y); - } - break; - case Input::Device::Justifier: - case Input::Device::Justifiers: - if(dynamic_cast(input.port2)) { - Justifier &device = (Justifier&)*input.port2; - draw_cursor(0x001f, device.player1.x, device.player1.y); - if(device.chained == false) break; - draw_cursor(0x02e0, device.player2.x, device.player2.y); - } - break; - } - - uint32_t *data = (uint32_t*)ppu.output; - if(ppu.interlace() && ppu.field()) data += 512; - - if(hires) { - //normalize line widths - for(unsigned y = 0; y < 240; y++) { - if(line_width[y] == 512) continue; - uint32_t *buffer = data + y * 1024; - for(signed x = 255; x >= 0; x--) { - buffer[(x * 2) + 0] = buffer[(x * 2) + 1] = buffer[x]; - } - } - } - - interface()->videoRefresh(ppu.surface, hires, ppu.interlace(), ppu.overscan()); - - hires = false; -} - -void Video::scanline() { - unsigned y = cpu.vcounter(); - if(y >= 240) return; - - hires |= ppu.hires(); - unsigned width = (ppu.hires() == false ? 256 : 512); - line_width[y] = width; -} - -void Video::init() { - hires = false; - for(auto &n : line_width) n = 256; -} - -#endif +#ifdef SYSTEM_CPP + +Video video; + +//internal + +const uint8_t Video::cursor[15 * 15] = { + 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0, + 0,0,0,0,1,1,2,2,2,1,1,0,0,0,0, + 0,0,0,1,2,2,1,2,1,2,2,1,0,0,0, + 0,0,1,2,1,1,0,1,0,1,1,2,1,0,0, + 0,1,2,1,0,0,0,1,0,0,0,1,2,1,0, + 0,1,2,1,0,0,1,2,1,0,0,1,2,1,0, + 1,2,1,0,0,1,1,2,1,1,0,0,1,2,1, + 1,2,2,1,1,2,2,2,2,2,1,1,2,2,1, + 1,2,1,0,0,1,1,2,1,1,0,0,1,2,1, + 0,1,2,1,0,0,1,2,1,0,0,1,2,1,0, + 0,1,2,1,0,0,0,1,0,0,0,1,2,1,0, + 0,0,1,2,1,1,0,1,0,1,1,2,1,0,0, + 0,0,0,1,2,2,1,2,1,2,2,1,0,0,0, + 0,0,0,0,1,1,2,2,2,1,1,0,0,0,0, + 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0, +}; + +void Video::draw_cursor(uint16_t color, int x, int y) { + uint32_t *data = (uint32_t*)ppu.output; + if(ppu.interlace() && ppu.field()) data += 512; + + for(int cy = 0; cy < 15; cy++) { + int vy = y + cy - 7; + if(vy <= 0 || vy >= 240) continue; //do not draw offscreen + + bool hires = (line_width[vy] == 512); + for(int cx = 0; cx < 15; cx++) { + int vx = x + cx - 7; + if(vx < 0 || vx >= 256) continue; //do not draw offscreen + uint8_t pixel = cursor[cy * 15 + cx]; + if(pixel == 0) continue; + uint32_t pixelcolor = (15 << 15) | ((pixel == 1) ? 0 : color); + + if(hires == false) { + *((uint32_t*)data + vy * 1024 + vx) = pixelcolor; + } else { + *((uint32_t*)data + vy * 1024 + vx * 2 + 0) = pixelcolor; + *((uint32_t*)data + vy * 1024 + vx * 2 + 1) = pixelcolor; + } + } + } +} + +void Video::update() { + switch(config.controller_port2) { + case Input::Device::SuperScope: + if(dynamic_cast(input.port2)) { + SuperScope &device = (SuperScope&)*input.port2; + draw_cursor(0x7c00, device.x, device.y); + } + break; + case Input::Device::Justifier: + case Input::Device::Justifiers: + if(dynamic_cast(input.port2)) { + Justifier &device = (Justifier&)*input.port2; + draw_cursor(0x001f, device.player1.x, device.player1.y); + if(device.chained == false) break; + draw_cursor(0x02e0, device.player2.x, device.player2.y); + } + break; + } + + uint32_t *data = (uint32_t*)ppu.output; + if(ppu.interlace() && ppu.field()) data += 512; + + if(hires) { + //normalize line widths + for(unsigned y = 0; y < 240; y++) { + if(line_width[y] == 512) continue; + uint32_t *buffer = data + y * 1024; + for(signed x = 255; x >= 0; x--) { + buffer[(x * 2) + 0] = buffer[(x * 2) + 1] = buffer[x]; + } + } + } + + interface()->videoRefresh(ppu.surface, hires, ppu.interlace(), ppu.overscan()); + + hires = false; +} + +void Video::scanline() { + unsigned y = cpu.vcounter(); + if(y >= 240) return; + + hires |= ppu.hires(); + unsigned width = (ppu.hires() == false ? 256 : 512); + line_width[y] = width; +} + +void Video::init() { + hires = false; + for(auto &n : line_width) n = 256; +} + +#endif diff --git a/waterbox/libsnes/bsnes/snes/system/video.hpp b/waterbox/libsnes/bsnes/snes/system/video.hpp index 777176f2b5..f0c096832d 100644 --- a/waterbox/libsnes/bsnes/snes/system/video.hpp +++ b/waterbox/libsnes/bsnes/snes/system/video.hpp @@ -1,19 +1,19 @@ -struct Video { - enum class Format : unsigned { RGB30, RGB24, RGB16, RGB15 }; -public: - void update(); - -private: - bool hires; - unsigned line_width[240]; - - void scanline(); - void init(); - - static const uint8_t cursor[15 * 15]; - void draw_cursor(uint16_t color, int x, int y); - - friend class System; -}; - -extern Video video; +struct Video { + enum class Format : unsigned { RGB30, RGB24, RGB16, RGB15 }; +public: + void update(); + +private: + bool hires; + unsigned line_width[240]; + + void scanline(); + void init(); + + static const uint8_t cursor[15 * 15]; + void draw_cursor(uint16_t color, int x, int y); + + friend class System; +}; + +extern Video video;