diff --git a/Assets/gamedb/gamedb.txt b/Assets/gamedb/gamedb.txt index b7e844ec40..7c596bae3d 100644 --- a/Assets/gamedb/gamedb.txt +++ b/Assets/gamedb/gamedb.txt @@ -1,4 +1,5 @@ #include gamedb_a2600.txt +#include gamedb_a7800.txt #include gamedb_appleII.txt #include gamedb_coleco.txt #include gamedb_e_cards.txt diff --git a/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs b/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs index b40ec818a1..0316c0f78c 100644 --- a/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs +++ b/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs @@ -34,6 +34,9 @@ namespace BizHawk.Client.ApiHawk case "A78": return CoreSystem.Atari2600; + case "A7800": + return CoreSystem.Atari7800; + case "Coleco": return CoreSystem.ColecoVision; diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index a417cb6b75..0330eac5e4 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -372,6 +372,12 @@ ColecoControllerSettings.cs + + Form + + + A7800ControllerSettings.cs + Form @@ -1196,6 +1202,7 @@ + @@ -1338,6 +1345,9 @@ ColecoControllerSettings.cs + + A7800ControllerSettings.cs + IntvControllerSettings.cs diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index 33fc42b28a..3d8cd97254 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -290,8 +290,10 @@ this.AutoloadKeypadMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.paletteToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.AtariSubMenu = new System.Windows.Forms.ToolStripMenuItem(); - this.AtariSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.GBSubMenu = new System.Windows.Forms.ToolStripMenuItem(); + this.AtariSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.A7800SubMenu = new System.Windows.Forms.ToolStripMenuItem(); + this.A7800ControllerSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.GBSubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.GBcoreSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.LoadGBInSGBMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator28 = new System.Windows.Forms.ToolStripSeparator(); @@ -456,7 +458,8 @@ this.SMSSubMenu, this.TI83SubMenu, this.AtariSubMenu, - this.GBSubMenu, + this.A7800SubMenu, + this.GBSubMenu, this.GBASubMenu, this.PSXSubMenu, this.SNESSubMenu, @@ -2655,10 +2658,26 @@ this.AtariSettingsToolStripMenuItem.Size = new System.Drawing.Size(125, 22); this.AtariSettingsToolStripMenuItem.Text = "Settings..."; this.AtariSettingsToolStripMenuItem.Click += new System.EventHandler(this.AtariSettingsToolStripMenuItem_Click); - // - // GBSubMenu - // - this.GBSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + // + // A7800SubMenu + // + this.A7800SubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.A7800ControllerSettingsMenuItem}); + this.A7800SubMenu.Name = "A7800SubMenu"; + this.A7800SubMenu.Size = new System.Drawing.Size(56, 19); + this.A7800SubMenu.Text = "&A7800"; + this.A7800SubMenu.DropDownOpened += new System.EventHandler(this.A7800SubMenu_DropDownOpened); + // + // A7800SettingsToolStripMenuItem + // + this.A7800ControllerSettingsMenuItem.Name = "A7800ControllerSettingsMenuItem"; + this.A7800ControllerSettingsMenuItem.Size = new System.Drawing.Size(125, 22); + this.A7800ControllerSettingsMenuItem.Text = "Settings..."; + this.A7800ControllerSettingsMenuItem.Click += new System.EventHandler(this.A7800SettingsToolStripMenuItem_Click); + // + // GBSubMenu + // + this.GBSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.GBcoreSettingsToolStripMenuItem, this.LoadGBInSGBMenuItem, this.toolStripSeparator28, @@ -4097,6 +4116,7 @@ private System.Windows.Forms.ToolStripMenuItem PCEBGViewerMenuItem; private System.Windows.Forms.ToolStripMenuItem ScreenshotContextMenuItem; private System.Windows.Forms.ToolStripMenuItem AtariSubMenu; + private System.Windows.Forms.ToolStripMenuItem A7800SubMenu; private System.Windows.Forms.ToolStripMenuItem NESSoundChannelsMenuItem; private System.Windows.Forms.ToolStripMenuItem SNESSubMenu; private System.Windows.Forms.ToolStripMenuItem SnesGfxDebuggerMenuItem; @@ -4167,6 +4187,7 @@ private System.Windows.Forms.ToolStripMenuItem GenesisSubMenu; private System.Windows.Forms.ToolStripMenuItem GenesisSettingsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem AtariSettingsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem A7800ControllerSettingsMenuItem; private System.Windows.Forms.ToolStripMenuItem MovieSettingsMenuItem; private System.Windows.Forms.ToolStripMenuItem CoresSubMenu; private System.Windows.Forms.ToolStripMenuItem GBInSGBMenuItem; diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 84c74537e3..404f07c535 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -1212,7 +1212,6 @@ namespace BizHawk.Client.EmuHawk private void CoresSubMenu_DropDownOpened(object sender, EventArgs e) { Atari7800WithEmu7800MenuItem.Visible = VersionInfo.DeveloperBuild; // Don't expose Atari7800Hawk in releases yet - GBInSGBMenuItem.Checked = Global.Config.GB_AsSGB; NesInQuickNESMenuItem.Checked = Global.Config.NES_InQuickNES; gBAWithMGBAToolStripMenuItem.Checked = Global.Config.GBA_UsemGBA; @@ -1981,6 +1980,20 @@ namespace BizHawk.Client.EmuHawk #endregion + #region Atari7800 + + private void A7800SubMenu_DropDownOpened(object sender, EventArgs e) + { + A7800ControllerSettingsMenuItem.Enabled = !Global.MovieSession.Movie.IsActive; + } + + private void A7800SettingsToolStripMenuItem_Click(object sender, EventArgs e) + { + new A7800ControllerSettings().ShowDialog(); + } + + #endregion + #region GB private void GBSubMenu_DropDownOpened(object sender, EventArgs e) diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 05367e71bc..21dff09bae 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -1785,6 +1785,7 @@ namespace BizHawk.Client.EmuHawk GBSubMenu.Visible = false; GBASubMenu.Visible = false; AtariSubMenu.Visible = false; + A7800SubMenu.Visible = false; SNESSubMenu.Visible = false; PSXSubMenu.Visible = false; ColecoSubMenu.Visible = false; @@ -1835,6 +1836,9 @@ namespace BizHawk.Client.EmuHawk case "A26": AtariSubMenu.Visible = true; break; + case "A7800": + A7800SubMenu.Visible = true; + break; case "PSX": PSXSubMenu.Visible = true; break; diff --git a/BizHawk.Client.EmuHawk/config/A7800/A7800ControllerSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/A7800/A7800ControllerSettings.Designer.cs new file mode 100644 index 0000000000..882cbaea8c --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/A7800/A7800ControllerSettings.Designer.cs @@ -0,0 +1,147 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class A7800ControllerSettings + { + /// + /// 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() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(A7800ControllerSettings)); + this.OkBtn = new System.Windows.Forms.Button(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.label5 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.Port2ComboBox = new System.Windows.Forms.ComboBox(); + this.Port1ComboBox = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + 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, 249); + this.OkBtn.Name = "OkBtn"; + this.OkBtn.Size = new System.Drawing.Size(60, 23); + this.OkBtn.TabIndex = 3; + 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, 249); + this.CancelBtn.Name = "CancelBtn"; + this.CancelBtn.Size = new System.Drawing.Size(60, 23); + this.CancelBtn.TabIndex = 4; + this.CancelBtn.Text = "&Cancel"; + this.CancelBtn.UseVisualStyleBackColor = true; + this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(9, 94); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(38, 13); + this.label5.TabIndex = 16; + this.label5.Text = "Port 2:"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(12, 44); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(38, 13); + this.label4.TabIndex = 15; + this.label4.Text = "Port 1:"; + // + // 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.Location = new System.Drawing.Point(12, 110); + this.Port2ComboBox.Name = "Port2ComboBox"; + this.Port2ComboBox.Size = new System.Drawing.Size(284, 21); + this.Port2ComboBox.TabIndex = 14; + // + // 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.Location = new System.Drawing.Point(12, 60); + this.Port1ComboBox.Name = "Port1ComboBox"; + this.Port1ComboBox.Size = new System.Drawing.Size(284, 21); + this.Port1ComboBox.TabIndex = 13; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 14); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(146, 13); + this.label1.TabIndex = 17; + this.label1.Text = "A7800 Controller Settings"; + // + // A7800ControllerSettings + // + 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, 284); + this.Controls.Add(this.label1); + this.Controls.Add(this.label5); + this.Controls.Add(this.label4); + this.Controls.Add(this.Port2ComboBox); + this.Controls.Add(this.Port1ComboBox); + this.Controls.Add(this.CancelBtn); + this.Controls.Add(this.OkBtn); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "A7800ControllerSettings"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Controller Settings"; + this.Load += new System.EventHandler(this.IntvControllerSettings_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button OkBtn; + private System.Windows.Forms.Button CancelBtn; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.ComboBox Port2ComboBox; + private System.Windows.Forms.ComboBox Port1ComboBox; + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/A7800/A7800ControllerSettings.cs b/BizHawk.Client.EmuHawk/config/A7800/A7800ControllerSettings.cs new file mode 100644 index 0000000000..d087d2558c --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/A7800/A7800ControllerSettings.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using System.Windows.Forms; + +using BizHawk.Client.Common; +using BizHawk.Emulation.Cores.Atari.A7800Hawk; + +namespace BizHawk.Client.EmuHawk +{ + public partial class A7800ControllerSettings : Form + { + private A7800Hawk.A7800SyncSettings _syncSettings; + + public A7800ControllerSettings() + { + InitializeComponent(); + } + + private void IntvControllerSettings_Load(object sender, EventArgs e) + { + _syncSettings = ((A7800Hawk)Global.Emulator).GetSyncSettings().Clone(); + + var possibleControllers = A7800HawkControllerDeck.ValidControllerTypes.Select(t => t.Key); + + foreach (var val in possibleControllers) + { + Port1ComboBox.Items.Add(val); + Port2ComboBox.Items.Add(val); + } + + Port1ComboBox.SelectedItem = _syncSettings.Port1; + Port2ComboBox.SelectedItem = _syncSettings.Port2; + } + + private void OkBtn_Click(object sender, EventArgs e) + { + bool changed = + _syncSettings.Port1 != Port1ComboBox.SelectedItem.ToString() + || _syncSettings.Port2 != Port2ComboBox.SelectedItem.ToString(); + + if (changed) + { + _syncSettings.Port1 = Port1ComboBox.SelectedItem.ToString(); + _syncSettings.Port2 = Port2ComboBox.SelectedItem.ToString(); + + GlobalWin.MainForm.PutCoreSyncSettings(_syncSettings); + } + + DialogResult = DialogResult.OK; + Close(); + } + + private void CancelBtn_Click(object sender, EventArgs e) + { + GlobalWin.OSD.AddMessage("Controller settings aborted"); + DialogResult = DialogResult.Cancel; + Close(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/config/A7800/A7800ControllerSettings.resx b/BizHawk.Client.EmuHawk/config/A7800/A7800ControllerSettings.resx new file mode 100644 index 0000000000..ca821b54f8 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/A7800/A7800ControllerSettings.resx @@ -0,0 +1,624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + AAABAAwAMDAQAAAABABoBgAAxgAAACAgEAAAAAQA6AIAAC4HAAAYGBAAAAAEAOgBAAAWCgAAEBAQAAAA + BAAoAQAA/gsAADAwAAAAAAgAqA4AACYNAAAgIAAAAAAIAKgIAADOGwAAGBgAAAAACADIBgAAdiQAABAQ + AAAAAAgAaAUAAD4rAAAwMAAAAAAgAKglAACmMAAAICAAAAAAIACoEAAATlYAABgYAAAAACAAiAkAAPZm + AAAQEAAAAAAgAGgEAAB+cAAAKAAAADAAAABgAAAAAQAEAAAAAACABAAAAAAAAAAAAAAQAAAAEAAAAAAA + AAAAAIAAAIAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP// + /wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAHR3AAAAAAAAAAAAAAAAAAAAAAAAAAAAdHdEcAAAAAAAAAAAAAAAAA + AAAAAAAAAHd0d3QAAAAAAAAAAAAAAAAAAAAAAAAAAEd8d3UAAAAAAAAAAAAAAAAAAAAAAAAAB3yHfHZw + AAAAAAAAAAAAAAAAAAAAAAAAd3fIyHVwAAAAAAAAAAAAAAAAAAAAAAAAfHh3jIxwAAAAAAAAAAAAAAAA + AAAAAAAHd8jIyHdgAAAAAAAAAAAAAAAAAAAAAAAHd4yHfIdAAAAAAAAAAAAAAAAAAAAAAAAHyMjIyMhQ + AAAAAAAAAAAAAAAAAAAAAAB3d3eMh4dgAAAAAAAAAAAAAAAAAAAAAAB8jIyIfIdQAAAAAAAAAAAAAAAA + AAAAAAB3h4jIiMh3AAAAAAAAAAAAAAAAAAAAAAB8jIeHeIjHAAAAAAAAAAAAAAAAAAAAAAeIiHh4eMiE + AAAAAAAAAAAAB0dHcAAAAAd8h4eIiIiHcAAAAAAAAAB0d3d3RwAAAAeIeIiIiIh3RwAAAAAAAHR3d8h3 + dAAAAAfIh4iIiHiIx0cAAAAAdHh3eIeHhwAAAAeHiIiIiIiId3R3dHR0eHd4h4eHhAAAAAd4eIiIiIiH + x3d2d3eId4iIiIiIhwAAAAd4eIiI+IiIh3d3eHh3iIiIiIeHwAAAAAfIjHeIiIiIyIeHh4iIiIiIiIiI + cAAAAAeIQ0R3h3iIiMiIiIiIiIiIiIiEAAAAAAfIR3d3d0iIiIh4iIeIiIiIiHhAAAAAAAB4d3d3SHiI + h4fTiIi3iIiIeIwAAAAAAAB3h4d3eIeIiHiJiIuIiIh4jHAAAAAAAAAHyId3h3h4iIh4iIiIiIiHeAAA + AAAAAAAAB8iMiMjIiIiIh4h3aMjHAAAAAAAAAAAAAAdYyIeIiIiMjId6d4eAAAAAAAAAAAAAAAAHdsjH + eIeH6MiId3AAAAAAAAAAAAAAAIiIh4V8jIh4eIfHcAAAAAAAAAAAAACIiIh3AAAHd3h3fHcAAAAAAAAA + AAAAAAiIjHgAAAAAAHx8eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC + AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/ + AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAAAAA + AAd0dAAAAAAAAAAAAAAAAAB3x3cAAAAAAAAAAAAAAAAAd3fHcAAAAAAAAAAAAAAAB3yMh3AAAAAAAAAA + AAAAAAfIeMdwAAAAAAAAAAAAAAAHjIyHQAAAAAAAAAAAAAAAfId4yHAAAAAAAAAAAAAAAHjIyIdQAAAA + AAAAAAAAAAB3iId4YAAAAAAAAAdwAAAAjIiIiIUAAAAAAHd3dAAAB4iIiHh8cAAAAHd3x4dwAAd4iIiI + h3Z3d3R3yIh4cAAHh4iIiIfHd3d4iIiIh3AAB3jHiIiIiHeHiIiIiIwAAAh3dXh4iMiIiIiIiIhwAAAA + yGd0d4iIeIi4iIiMAAAAAIeHd4iIh32IiIiIcAAAAAAAd4jIyIiIiHeHyAAAAAAAAAB3h4iIh8h3dwAA + AAAAAAAIh8fIh4eIaAAAAAAAAACIiHAAB8jIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + ////////////////////n////g////wP///8B///+Af///gH///4B///8Af///AH///wB//n8AP/A+AB + /AHgAAAB4AAAAeAAAAPgAAAH8AAAD/AAAB/8AAA//wAA//4AA//weA////////////////////////// + //8oAAAAGAAAADAAAAABAAQAAAAAACABAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA + AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRwAAAAAAAAAAAAB3dAAAAAAAAAAAAA + d8dwAAAAAAAAAAAAfId3AAAAAAAAAAAHeMjHAAAAAAAAAAAHyHh3AAAAAAAAAAAHh3eEAAAAAAAAAAAI + yIiHAAAAAHd2cAAIiIiIQAAAd3d4UACHiIiId3d3eHiIcACHh4iIyHeHiIiIcAAIR3d4iIiIiIiMAAAH + d3eIh3iIiIhwAAAAeMh4iIiHiMAAAAAAAHfIiMh4aAAAAAAAiIgHyIfIAAAAAAAIgAAAAIAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD8f/8A+H//APB/ + /wDwP/8A4D//AOA//wDgP/8A4D/BAOAfAQDAAAEAwAABAOAAAwDgAAcA8AAfAPwAPwDwgP8A5/f/AP// + /wD///8A////ACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACA + AAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAd1AAAAAAAAB8cAAAAAAAB4eAAAAAAAAHyMgAAAAAAAiIhwAAAHcACI + iHcAd3hwAIz4jIeIiIAAd3eIiIiIAACHeIiIiHAAAACMeMh4AAAAiAAIgAAAAAAAAAAAAAAAAAAAAAAA + AAD//wAA//8AAP//AADj/wAA4/8AAMP/AADB/wAAwfkAAMDBAADAAQAAwAMAAMAHAADwDwAAzn8AAP// + AAD//wAAKAAAADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA9OzsAZD8/AGg8 + PABtPj4AQkNDAEZIRwBWQkIAV0REAF5AQABbRkYAVklJAFxPTwBTU1MAXFJSAF5ZWQBkQEAAYUREAGZF + RQBqQkEAYEtLAGNPTwBwQUEAfUZGAHJKSgB2SUkAfU9PAGBRUQBgVFQAZlZWAGZYWABqWVkAclZWAHpU + VAB9W1oAbmJiAGtoaABtaWkAcWdnAHdnZwB8Y2MAe2pqAHJxcQB+dHQAd3l5AHl6egCGT08AiU9PAIFP + UACGU1MAjVFRAIlWVgCMV1cAg1xbAIxaWQCQUlIAlVJSAJFXVgCXVVUAmVVVAJZaWQCSXV0AlV9eAJpZ + WgCeW1sAml5eAKBZWgCgXFwAql9fAIRmZQCIZWQAhWtrAI5ragCTYmEAnGBhAJ9kYwCaZmYAk25uAJ1s + awCFdHQAiXd3AIt+fgCWd3cAmHR0AJV5eQCbfHwAo2JhAKZhYQChZWUApGVkAKplZACsZGQAqmhnAKZr + agCnbGsAqmloAKlubQCsbW0AtGZnALhsbACxb3AAv29wAKVxcACrc3IAr35+ALN0cwC5c3MAvXBxALR4 + dgC1fHsAunt6AMNtbgDGb3AAw3FyAMZwcQDGdXUAyHR1AMp3eADBeXkAxnt7AMB/fgDLensANLBSAEWf + TgBBtFwAPMdnADHkdgDciiIAvoF/AISrdwDln0sA35lhAN2XfADgmmEA8LdlAO61cAArWPIALWT+AEh5 + +gDOf4AAfoCAAHiA1ABZv9wAZrnUAGK+2ABxnv4Ad6P/ADPX/QBw0OcAW+D7AIKEgwCPgoIAjI2NAJuC + ggCUiIgAmYqKAJGSkgCjhIQAqoKCAKKLiwC+hIMAsoqKALaSgQCum5sAsZubALqqlQCdgr4Ar6ytALGh + oAC6pKQAwoSDAMyBggDGiIYAyYiHAMWMigDMjIoA0ISFANKHiADUjIwA2Y6NAMCUjQDIk44A0JCPANaP + kADHlZQAzpSSAMScmwDUkpIA2ZSVANWYlgDampcA2ZeYANWcnADam5sA4p2cAMChjwDeoJ4A5aCFAOaj + jQDlpJoA2p6hAMOkowDOoaEAy62tANegoADdoqEA2aGpANGsrwDdq6kAwbG4ANGysQDdtLQA2ri3AOGk + owDjqKYA66ylAOGnqADjq6oA6a2rAOOwrwDssK4A5K+wAOaztADttLIA57i2AO24tgDmurgA6rq6APC1 + swDyuLYA9Ly5APi+uwD1wL0A+cC9AKKMwACkk8QAqprMALSayACptsEAlaDkAOy/wACRxtQAgOv9AJnr + 9wDEwsoA5sbGAOzCwgDuyMcA7MzMAPPEwgDxy8oA9dPTAPja2gAAAAAAAAAAAP///wAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAADLytDi9tOemQAAAAAAUy9EecLEsa1uPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPj11Mme + VakAAAAAAAAAAAAATS84M0akAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE + AAAAAAAAAAAAAAABAAAAAQAAAAAAAFFNTQBRUlIAU1RUAGJHRwBiT08Aa0lIAGJTUwBrVlYAYllZAGZc + XABpWloAb1xbAHNTUwB7V1YAc1hXAHFbWwBkZWUAaWFhAG5kZABpamkAcGFhAHlubgB2cHAAf3V1AH55 + eQB8fX0AgUpKAI1PTwCLWFcAhlhYAI9ZWQCKXFsAm1ZWAJJZWQCWWVgAmlpbAJtcWwCiXFwAl2BfAIBg + YACAZ2YAgG9vAI9oaACWZWQAmGBhAJ5kZACcaWoAmm9vAIV0dACNcHAAiXZ2AIB8fACac3IAm3V0AJ51 + dQCZfHwAnHx8AKNmZgCnZmYAqmJiAK5jYwCvb24AtWVmALBtbgC5bW0AvmxtAKx+fQCxcnIAtHBwALZz + dACydXQAtnd2ALlwcAC5dnYAt3p5ALh5eAC8fHsAun18ALx+fQDGb3AAxnBxAMdzdADAd3YAyHJzAMlz + dADJdXYAynd4AMd/fwDMe3wAzXx9AHunbwBhvHIAYsN4ANuLOwC2hn4A4Zt5APC3ZABte9sAX47+AHWM + 5QAl0foAY+P8AIeDgwCFhoYAioSEAJOIiACWi4sAmpKRAKGCgQCmhYUAqYGBAKuDhACniooApYyMAKiO + jQCyhYMAvoWEALeNjQCrj5AAr5eXALSVlAC9lJMAmbCEAK6RugDBgYAAwoSCAMWDhADChoQAxYeFAM6A + gQDFiIYAxoqIAMqIiQDMi4oAy4yKAMiPjQDPj44A0ISFANKJigDUi4wA04+NANWNjgDKkY8A0JCOANud + iQDWj5AAzJSTAM2XlgDGm5oA1pGSANOUkgDVl5EA1pOUANiVlgDYmJUA2ZeYANKenADbmpsA3pmYANuc + mgDbn5wA1aacAN6gngDqqZoA3Z+gAMyjowDCra0AxqysAMqpqQDboaAA3qKiAN6logDbp6UA3aWkANer + qgDWsbMA0rW0ANe0tADfs7IA4aSiAOGlpQDkp6UA46imAOWopgDsraIA6qimAOGoqADhrqwA6a2rAOqv + rADpsK4A7LGuAOGzswDlsbEA7bKxAO+1sgDotrYA5rm3AO+4twDot7sA6bq5AOu9uwDrv70A8bazAPG2 + tADxuLUA9Lm2APC9uwD2vboA9L+9APi+uwD4v7wA8sC+APXAvgD5wL0AkILJAKqXzACsu8cAqr/LALLV + 3QDawMIA48XFAOvDwQDswMAA7cTDAO/ExQDgxsgA8cbEAPTGxADwyskA9MvJAPLNzQD21dYA+NjZAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHCEcBQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAayU9PSYbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdQlBSQiJpAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM0pSUlJQPRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnUlJSUlJGFQAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAFJSUlJSUkoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUlJSWVJZfxAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5XWYqKioqGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoqMkpqa + mqAsAAAAAAAAAAAAAAAAAABoNAAAAAAAAACMjJyuvLy2toYHAAAAAAAAAAAAABcOIDouBgAAAAAAc4yc + tsHKysPAriIKAAAAAAAAABYgRk1LTX+DEAAAAABukqXB4ejo4dHPQCIEChcXEwggTXV/k66unKMpAAAA + AG6Srsro6ero0dN/Rk1NRk2Dg4STrsbh4cHAt2sAAAAAbpKuOXPe6ajW15KGg4OGk528yuHo5eHPz882 + AAAAAAB4jCkDAxSoMabXt5yjt8ro3ePo5dbT09HTdAAAAAAAAABGcBFoGgFwdtfDwHxi2dpmZcrX09HP + z0MAAAAAAAAAAHh/qWwaOa6cz9PNZGPYsdzbzc3DwLk2AAAAAAAAAAAAAAAvhpKakoyg19HNyKS5wHtb + orZ/cwAAAAAAAAAAAAAAAAAANkaKWVm5zb1gYV6cXVxfNgAAAAAAAAAAAAAAAAAAALGvlTIuP1K5tqCR + l4xfLwAAAAAAAAAAAAAAAAAAsbPBenkAAAAAcCVYjE0scwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////+f///+D////A////wH + ///4B///+Af///gH///wB///8Af///AH/+fwA/8D4AH8AeAAAAHgAAAB4AAAA+AAAAfwAAAP8AAAH/wA + AD//AAD//gAD//B4D////////////////////////////ygAAAAYAAAAMAAAAAEACAAAAAAAQAIAAAAA + AAAAAAAAAAEAAAABAAAAAAAAWlJSAHBJSQB1SEgAe1dXAHdYWAB5WlkAel1dAGBiYgB1bGwAfWtrAHh2 + dgB9fn4Ag01NAIRXVwCIV1cAhV9eAItbWgCgX14ApV1dAJhgXwCNYGAAnWtqAJhtbQCCdnYAh3x8AI15 + eACeensAqGBgAKhoZwCga2oArGpqALNqagCzb28AtG1tALltbQCxb3AApnVzAKlzcwCqdHMApnp6AKd+ + fgCpensAq3x7ALZ3dgC8dHQAvH59AMZvcADGcHEAxXN0AMhycwDJdncAynh5AMx5egDNfn8Ajo1wAOek + VgDGgH8A4p53AEZ2+gB8u4AAd8PaAIuEhACOh4cAjo6OAJ+DggCejo4Ao4SEAKSIiACsi4sAqo2MAK6P + jgC+gYAAvoaGAL+KiACskJAAtJeXALWenQC5np4At6iOAKmyjgC9nroAwYSDAMaGhADOhoYAxomHAMiK + iQDJjYwA0oeIANOOjwDUjY0A2ZiPANaPkADGkZEAx5eXAMySkADGnZwA1ZOSANeTlADWl5YA2JSVANGZ + mADan50A3J6dAOCcmwDVoJ8A7K2fAMOtrQDXo6IA3aCgAN+kpADVq6oA3ay3AMu0tADPtrYA3L+/AOCi + oQDhpqUA5KelAOinpgDlq6gA46usAOOvrQDqrqwA7LGuAOayswDjtrQA5re1AOqysQDts7EA57y6AO+8 + ugDrvL0A8LOwAPC1sgDwtrQA87q3APS6twD2vboA8b69APi/vAD2wb4A+cC9AJmTzwDHqMMAu8PMAIHf + 8QDByNAA7cLCAO3FwwDvxsQA5cjIAOzOzgDwxcQA9cbEAPPP0AD10tIAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + BQMJAAAAAAAAAAAAAAAAAAAAAAAAAAAPHBMNAAAAAAAAAAAAAAAAAAAAAAAAABojLy8TAAAAAAAAAAAA + AAAAAAAAAAAAAB0wMDAiPgAAAAAAAAAAAAAAAAAAAAAAQjAwMDAtGAAAAAAAAAAAAAAAAAAAAAAAFzIy + NTU5CgAAAAAAAAAAAAAAAAAAAAAAIjZYWFxcBwAAAAAAAAAAAAAAAAAAAAAANlxtdW11JQAAAAAAAAAA + PgcRDgkAAAAAXG1/lISAZgMAAAAAABkVLC5SVhcAAABNY3WWnJuLfB8UBAcQHkhWaX91dSsAAABNY2BM + mJeCiVJSVl9laX+WloSJgEIAAAAAXAEIC0tGjnR0dJaRk5qNjIyJQwAAAAAAJkNADBtdjIaPO1GSPYuJ + hnVEAAAAAAAAAClISWRcd4xwkGp8UE90VwAAAAAAAAAAAAAAKSQ1NYZ7OjhbPDdGAAAAAAAAAAAAAHNv + YGsAKyJoXFYmRwAAAAAAAAAAAAAAcnIAAAAAAAAATgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP// + /wD///8A////APx//wD4f/8A8H//APA//wDgP/8A4D//AOA//wDgP8EA4B8BAMAAAQDAAAEA4AADAOAA + BwDwAB8A/AA/APCA/wDn9/8A////AP///wD///8AKAAAABAAAAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAA + AAAAAQAAAAEAAAAAAABjZGQAdmRjAHtpaQB/eHgAgU9PAKBaWgCFbm0AlWtqAKptbgCwZ2cAsGhoAKxw + cACteHkAvnJyAMZvcADGcHEAy3l5AMx9fgCFmXQAwIB/ANeUfQDhoX8AlIqJAJWMjACYiIgAoIaGAK2K + igCxh4cAvoGAALKKigC4iYgAuJWVAL2cnACss50AuqKhAL+mpgDLgoIAxImHAMeNjADLkI8AxpWTANCS + kQDYlZUA1J6dANqZmgDdnp4A1J+oAMaiogDOr68AzLKyANi5uADhpaIA4qypAOWtqADrrqsA4bKwAOay + sgDtuLYA57++AOy4uADxtLIA8be0APa9ugDswL4A9sG+ALCcxwC5ncIA06zBALnH0QC2ytQA7sPDAPLS + 0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAZBgUAAAAAAAAAAAAAAAAACw8KAAAAAAAAAAAAAAAAGhAQDgAAAAAAAAAAAAAAAAkRESUYAAAA + AAAAAAAAAAAlKy4uBwAAAAAAAAcDAAAAKzlHPCYCAAAYCB0oKgAAAC0wSDs0FB0nLDlAOiwAAAANAQQb + Pi9DRkVBPzUAAAAAJB4cKz5EQjMiNSkAAAAAAAAAHwwRNxYVEyQAAAAAAAAxMgAAACEgAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4/8AAOP/AADD/wAAwf8AAMH5 + AADAwQAAwAEAAMADAADABwAA8A8AAM5/AAD//wAA//8AACgAAAAwAAAAYAAAAAEAIAAAAAAAgCUAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAkAAAAJAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAUAAAAOAEBAVUAAABUAAAANQAAABAAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkFBSUvGRl5TCkpwlYuLtxDJCTQFw0NmQAA + AEkAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGAwMKE8rK6V6RET2klJR/5ZS + U/+OT0//ZDc38B0QEJoAAAAyAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDAwYVzAwoopP + T/ygXVz/oFtb/55ZWf+bWFf/k1NT/1UvL9wGAwNcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AARNKipxhk5O+adkY/+uZWX/tWdo/7VmZ/+qYWH/nltb/3hERPcfERGCAAAAFgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADEZGS1zQ0LXqGdm/7ptbf/Fb3D/x3Bx/8hwcf/BbW7/q2Vl/4hPT/82HR2gAAAAIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gxMYyYXl3/vXFx/8Zwcf/HcHH/x3Bx/8dwcf/HcHH/uG1t/5NY + V/9EJia2AAAAKQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB8fNH1MS+K4cnH/x3Fy/8dwcf/HcHH/x3Bx/8dw + cf/HcHH/wHBx/51gX/9PLCzGAAAAMwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAMAAAAAAAAAAAAAAAAAAAAALhAQFIZXVs/RjIz/1Y2O/9qYmP/eoaL/46qr/+aysv/ot7f/6rm5/+m4 + uf/otbX/5q+v/+uvrf/jqab/wYeF/28/P/QhEhKvAAAAXwAAACgAAAANAAAABQAAAAMAAAACAAAAAwAA + AAUAAAAKAAAAFQAAADAdDg9oSSkptHZHRu2dYmL+t3Z1/758e/+6enn/tnh3/7d5eP+8fn3/w4SD/7Z6 + ef9eODfbBgICTgAAAAgAAAAAAAAAAAAAAAAAAAAAPhwcJJVjYuPXkZH/2JOU/92fn//iqqr/57O0/+u8 + vP/uwsL/78XG/+/Exf/twMD/67i4/+60sv/wtrP/zZKQ/5taWv9xQED2MRsaxAgEBIcAAABaAAAAQQAA + ADcAAAA2AAAAOwAAAEUEAgJZHA4OfUcnJ7l5SkntqGxr/8CAfv/DgoH/vH59/7p+ff/DiIb/zZGP/9GT + kf/UlJP/1peV/9eZl/+GVlbuGQsLVwAAAAcAAAAAAAAAAAAAAAAAAAAARiIiLZ9rauvZk5P/2peY/+Ck + pP/lsLD/6ru7/+/Fxf/yzMz/9NDQ//PPz//xycr/7sDA//K5tv/1u7j/36Kg/6dmZf+mZWX/j1ZW/WM6 + OutDJSXQNBwcvDAaGrQ0HBy1PiIivUwsLMtkPDzfh1VU9a1xcP/EhIP/xIWE/7+Cgf/Ch4b/zZST/9mk + ov/grq3/4a6t/96lo//eoJ7/36Kg/+Cjof+IWVjnGwwMQwAAAAIAAAAAAAAAAAAAAAAAAAAARyQkL6Br + auzZk5P/25qb/+GnqP/ntLT/7cDA//LLy//209T/+NjY//fX1//00ND/8cbG//W9u//4vrz/46ak/7d0 + c/+vb27/s3Jy/7d2df+ucXD/pWpp/6Npaf+nbWz/sHVz/7p9fP/EhYT/yImI/8WIhv/DiIb/ypGP/9eg + n//hr63/57q5/+rCwP/rwsD/6bq4/+evrf/nq6n/6q6r/9qgnv9wRkbDBwAAHgAAAAAAAAAAAAAAAAAA + AAAAAAAASCQkLZ1nZuvYkpP/25uc/+Opqv/qtrf/7cHB//TOzv/52Nj/+tzc//na2v/xz9D/8MfH//fA + vv/6wb7/6a6r/8OBgP/DgoD/vX58/7h7ev+8fn3/woOC/8aHhv/HiYj/xoqJ/8aLif/Ijoz/zZST/9eg + nv/hrav/6Lm3/+zCwf/uyMf/78nH/+/Dwf/uvLr/7ba0/+60sf/vtLL/8ri1/7J+fflMKSltAAAABAAA + AAAAAAAAAAAAAAAAAAAAAAAAQyEhI5JcXOPWj5D/3Juc/8qVlf+BZmb/bl5e/4l4eP/AqKj/8tPT//LO + zv+5p6b/w6qq//fBv//7wr//8LWy/86Ojf/Ojoz/0ZGP/9GSkP/OkY//zpOR/9GamP/VoJ//2qel/+Gv + rf/nt7X/6727/+3Dwf/wycf/8czL//LLyf/yxsT/8cC+//G7uf/yubf/87m3//S7uP/4vrv/1J6c/3JH + RrAdCgsWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANRcXEYJNTcvPiIn/15aW/2VNTf85Ojr/Q0VF/0JF + RP9dXFz/n5GR/+S/v/+bh4f/hXp6/+25uP/7wr//9bu4/9qcmv/Zmpj/252b/96gnf/ipKH/5q+s/+u+ + vP/vycf/8srI/+3Hxv/wysj/9c7M//TNy//0ysj/9MbE//TBv//1vrz/9r26//e9u//4vrv/+L+8//vB + vv/hqqf/g1ZVzDwcHC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW4+Ppq/env/05OT/2ZX + V/9rbm7/fX9//3l6ev99f3//cHJy/5F9ff+ff3//XFhY/9eop//8wr//+L+8/+Wppv/ipaP/5qil/96i + pP/Kmaz/1qi1//LGxP/tyMf/qb3J/23E3P9kw9//vMTN//jDwP/3wb//+MC9//i/vf/5v73/+b+8//i/ + vP/3vrv/+L68/92mo/+IWlnRRSMjOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcv + L0mbX1/y15GS/6GAgP9XV1b/iYuL/4CBgf98fX3/cnR0/1dPT/++j4//km9w/9Sfnv/6wL3/+cC9/+6z + sP/ssK3/0Z+u/4OH1P9YffD/QGPs/7KYyv/Ct7z/Ytrz/3Ts//8s2f//cbvU//m+u//4v7z/+L67//e9 + uv/1vLn/9Lq3//O5tv/zuLX/0puZ/4RVVctGIyM4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADIXFwdrPDySq2ts/diZmf/ApKT/sKur/4CBgP95enr/iYiI/49zdP/do6P/36Ch/96e + nv/zuLX/+sK///W7uP/1ubT/qZC//2qY+/9tnf//MGT6/56FxP/esK//nMbS/57n8/9+z+T/ybG3//a6 + t//zubb/8re0//C1s//utLH/7rKw/+qvrP++iIb9dklJtkMgISoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHIyMSazw8kZ5hYvXNjI3/2aSk/7OMjP+bd3f/sIKC/9KV + lv/cnJz/2peY/9aRkf/koqL/+sG+//nAvf/5v7z/4amw/6qZx/+aouP/qpvP/+mxtv/2urj/6rGv/+S6 + u//ptrX/466n/+Ovqf/ssK7/6q6s/+isqv/oq6n/2J2b/6JubfFoPT2NOxoaFwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBoaCFowMFd7SEjAomZm9sWC + gv/XkZL/25SV/9iSk//Wj5D/1IyN/9KHiP/UiIj/8bOx//rCv//3vbv/9ru4//O3s//xuLX/7q6e/+ej + hf/npIn/7bCp/+Otp/+KsX3/ULdm/1WjWv+7oYz/5KWk/9uenP+4gH79glJRzVYuLlQgCAkGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAA8HBwQVy4uS3FBQaCPV1fjsG5v/cmAgf/ShYb/0YKD/85+f//LeXr/2I2M//e8uf/1vLn/7rOx/+2y + sP/lpJX/5qFY/+6xXP/djS3/35h9/86gl/9SwW7/Nd90/0WxXP+vlH//wYSE/49cW+VlOTmBQR4eHAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGk7OhqIWFd8oG5u8J5qav+eX2D/tmts/8Z0df/KdHX/yXJz/92T + k//3vLn/7LGu/+Snpf/dm5L/4Z1q/+61dP/fmmX/15WM/9eYlv/Bm43/r6uR/6uNgP+WYWDtbkBAnUwn + JzQVAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiFJSBnhC + QgpqNDQJWSUlB08dHQdfKisKfENDFJJWViinbGtRvYOCjtOcm8/pt7X157y6/7eOjfhxRUW7aTk5m4RK + StehWlr6uGdo/8Zwcf/dkpH/8bSx/+OnpP/YmZj/1ZWT/9ealP/Vl5X/0JCP/8eIhv+zdnb/lFtc6nA/ + QKRSKio/JQwNBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADTn6AB2qioDMuUlCHBhYU8voCAWcCBgXTEhoaLzZGQqdeensngrKvn47Sz/NOop/+yiIfyi2Bgs2k+ + PlZXKysPAAAAAUYlJRxcMTFYcj4+pYpMTeWmXF3+xnl5/9+Zl//dnJr/z46M/8KCgf+vc3L/ll9e831L + S8hlOTl/TigoMy0REQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABzQUIDnmprDriGhifHlpZMzp6eeNCgoZ7On5+2yJqaybuPj9WnfHzVj2RkunVJ + SYNbLy8/PRQUCgAAAAAAAAAAAAAAAAAAAAAAAAAAKRUVBU0pKSphNDRtd0BAsotNTd2ZW1vrkVlY4HtJ + Sb5lOTmCUysrQTsbGxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCwsA2Y4OA5xQkImdkhIRHhKSll0R0dibUBAWWI2 + NkNUKCgoOhISDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhkZB0km + Jh5LJiYsRSEhITATFAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAD/+H////8AAP/gH////wAA/8Af//// + AAD/gA////8AAP+AD////wAA/wAP////AAD/AA////8AAP4AB////wAA/gAH////AAD8AAf///8AAPwA + B////wAA/AAH////AAD8AAf///8AAPgAB////wAA+AAH//4HAAD4AAP/8AEAAPgAAf/AAQAA8AAA/wAA + AADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAEAAPAAAAAAAQAA8AAAAAADAADwAAAAAAcAAPAA + AAAADwAA+AAAAAAfAAD4AAAAAD8AAPwAAAAAfwAA/gAAAAD/AAD/gAAAA/8AAP/gAAAH/wAAgAAAAB// + AAAAAAAAf/8AAAAD4AP//wAAgB/8H///AAD///////8AAP///////wAA////////AAD///////8AAP// + /////wAA////////AAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAYAAAAZAAAAGQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAARCQkYOh8fb0ooKK80HByiCQUFTAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIhERFmA2Np2ITUz3lVNT/4dLS/5IKCi9AAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAANjODiBllhY+61kZP+vY2P/pV5e/3xHRvEhEhJfAAAAAgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAASSgoN41VVeS6bW3/xW9w/8dwcf+9bG3/klZW/jogIIEAAAAGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ1RkWcs2xs/8dxcv/HcHH/x3Bx/8Zwcf+iYWH/SSkpmAAA + AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC0tMZtgX+fGcnP/x3Bx/8dwcf/HcHH/x3Fy/61q + av9UMTGqAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxRER1tm9v/8hxcv/HcHH/x3Bx/8dw + cf/HcnP/tnRz/185OboAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAACIxXV7TEdHT/yHJz/8l1 + dv/Kd3j/ynd4/8p4eP/Bf37/bURDywAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNKysjo2Zm4Mt4 + ef/NfH3/z4GC/9GFhf/RhYb/0YWF/82Mi/9+UVHeCAICOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAACwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAGc+ + Pkm1c3P30IGC/9OJiv/XkZL/2ZaW/9mWl//YlJX/2JmY/5hnZfMeEBBrAAAABwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA0FAgItHhAQWzAbG4IqFxeHDQcHWwAAABkAAAAAAAAAAAAA + AAAAAAAAek1MdMN/f//VjI3/2piZ/9+io//hqKn/4qmp/+Clpf/jpqT/wImH/04xMLwAAAA6AAAABQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABEbDg5GRygokW5CQs+MVlbxnGJh/JdfXvxnPz7hHA8PbgAA + AAwAAAAAAAAAAAAAAACMW1qbz4qK/9qXl//gpqb/5rKz/+q6u//rvLz/6La2/+qxr//epKL/j1lZ+DUc + HLACAQFPAAAAHQAAAA8AAAAPAAAAEwAAACIbDg5MVDExnYZUU+SpbWz+uXl4/7x+fP/AgoD/xoeF/72A + f/9fOzu1AAAAHAAAAAAAAAAAAAAABJhkZK/VkZH/3Z+g/+axsf/twMD/8svL//LNzf/vxcX/8Lq4/+6z + sf+1dHP/j1VU+144N9g7IiKqMhwclDcfH5RGKSmiYTw7v4tZWOiydXT+woOC/8aKiP/Ol5X/2aWj/9ui + of/cnpz/2pyb/35TUrgAAAAVAAAAAAAAAAAAAAAFmmVkstaTk//hpaX/7Lm6//TLy//419f/+NnZ//TP + z//1wb//9Lq3/8aGhP+1dHP/s3Rz/6xwb/+pb27+rnNy/7Z7ev/BhIL/yY2L/8+WlP/apqT/5be2/+vB + v//rvrz/6bKw/+uvrf/Um5n/bUVEgAAAAAMAAAAAAAAAAAAAAAOTXV2q1ZGR/9CYmP+dfX7/o4yM/9e8 + vP/z0tL/zLOz/+u8u//5v7z/1peV/8uLif/Ki4r/yoyL/86Ukv/TnJv/2qSi/+Gtq//nuLb/7cPB//DJ + x//xxsT/8b+9//G6t//zubf/77az/6d1dM89Hx8lAAAAAAAAAAAAAAAAAAAAAIJOTojNiIn/jGlp/01O + Tv9UVlb/dnNz/7uhof+Pfn7/xJ+e//zCv//lqKb/3J2b/+Chnv/hpaT/7Ly5/+vHxv/MxMn/0MjN//LK + yf/1x8X/9sLA//a/vP/3vrv/+L+8//S7uP+5hoXhYTo5RwAAAAAAAAAAAAAAAAAAAAAAAAAAaTs7RrVz + dPKmfn7/cXJx/4SGhv97fX3/b2Zm/516ev+7kJD/+sG+//C2s//lqqr/rpbA/3aB2/+ql83/tMHK/2jc + 9P9OzOz/2r3B//q/vP/3vrv/9ry6//a8uf/ss7D/tYGA32c+Pk0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAvEhIHg01Njbp9fvrCn5//nI+P/4R7ev+fgID/2Jyd/9ybnP/ytrT/+b+8/+ewtf+Mld3/ZI36/5eI + zv/Ttrn/sNLc/6/Czv/stLT/8re0/++0sf/tsq//2qCe/6Rxb8phODg+AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABCIB8MeUZGbqRpata8gYH8x4mJ/9eTk//YkpP/04qL/+Cbmv/5wL3/9726/+Sw + t//Zrrn/56qY/+2smf/lr6n/nLWJ/4Gtdf/Pppn/3qGf/7yEg/KJWViYTyoqIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQh0dGXJAQGOXXl7NtnR1/8V7fP/MfH3/znt8/+il + o//0urj/7LCu/+Whg//rq13/35VX/9Kek/9yvXz/ZbNv/6iCdfqYY2O/aj4+TCUJCgcAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACcamsBjFRVB4FERAh9PT0JjU1ND6VnZx+/hINF0JqZiNOjoty0iIf2hFBQw5lX + V8+wY2P4xXR0/+aioP/oq6j/2pqT/92fif/Vlor/yYqJ/7N8efiVZmPGdERFYkEfHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALiFhgXFkJEdx5CQSMqSknbNlZWbz5uaws2cnOXBlJPnqH18r4dc + XFFULy8OSCUlFm07O0+FSUmeoV1d3sF9fPrGhoX/snZ295xkZNiFUlKbbD4+T0UdHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc0JDA5FgYRKdbm46onR0Zp9ycnuWampzhFlZVmY6 + OikvDAwHAAAAAAAAAAAAAAAAAAAAAB0ODgRULCwhbjo7UXhERGVrPDxHTCYmGxAAAQMAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP//////////////////////D////gf///wH///4A///+AP///AD///wA///8AP//+AD + ///gA//D4AH+AeAA+ADgAAAAwAAAAMAAAADAAAAB4AAAA+AAAAfgAAAP8AAAH/wAAD8AAAD/AAAD/wB4 + D//H////////////////////KAAAABgAAAAwAAAAAQAgAAAAAABgCQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAABMAAAAtAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgIO1cwMM1qOjrsHhAQmwAA + ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAATCgogfUhI6ahgYP6lXV3+f0hI9wIBAT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGBgFPLy6kuW1t/sZv + cP/Gb3D/oF9e/hMKCmgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4QECynZmX7xnBx/sdwcf/HcHH/tG1t/h8REYMAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAx + MIzFc3T+xm9w/sdwcf7HcHH+vHR0/jAcHJkAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQ4OAYVSUtfIcnP/yXZ3/st5ef/LeHn/xoB//kQq + KrEAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAJxYWGrNvb/7Nfn//0oeI/tSNjf/UjI3/1ZOS/mE+PtQAAAAXAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAIAAAARAAAALQAAADUAAAARAAAAAAAAAAAAAAAAQyYmUM6Ghv/Wj5D/3J2e/uCl + pf/fpKT/4KOi/qRycPkHBARlAAAABQAAAAAAAAAAAAAAAAAAAAAAAAADAQAAJh8REYBYNTXMhVJR8XxM + TO8gEhKeAAAAEAAAAAAAAAAAbUVEe9aPkP7doKD+5rKz/uu9vv7rvLz+6rKx/tqfnf5iNzfnCAQEcwAA + ACoAAAAbAAAAIQIBATorGBiQhFNT67Z3dv68fn3+wYSD/siKiP6aZmX2AQAAKQAAAAAAAAAAd05Ni9eT + lP/jq6z/7cLC/vXS0v/zz9D/8b69/uyxrv+samr/l15d+2tDQ+NkPz7bdkxL451nZve+gYD/yY2M/tWg + n//jtrT/46+t/uOmpP+mdHPwBQMDFAAAAAAAAAAAdkpJh9iUlf7Hl5f+tJeX/uzOzv7lyMj+57y6/vS6 + t/7HhoX+xYaE/saJh/7MkpD+0ZmY/tejov7mt7X+7cXD/vDFxP7vvLr+8Le0/u2zsf5PMzOMDQcHAQAA + AAAAAAAAYTg4X9OOj/9aUlL/YGJi/nh2dv+skJD/qo2M/vnAvf/dn53/4KKg/+Cnp/7vxsT/u8PM/sHI + 0P/1xsT/9sG+/ve+u//3vrv/87q3/ntVVLkkFhYIAAAAAAAAAAAAAAAAVC8wD6BkZOWjhIT/jo6O/n1+ + fv+eenv/xpGR/vi/vP/wtbL/mZPP/0Z2+v69nrr/gd/x/nfD2v/2vLr/9Lq3/vG2tP/lq6j/elJRrjQg + IAoAAAAAAAAAAAAAAAAAAAAAAAAAAGc7OyeOWVnGv4eH/r2Fhf7YlZb+1Y6P/uinpv74v7z+3ay3/seo + w/7srZ/+7LGv/qmyjv63qI7+5Kel/r2GhPZ1S0p1QCcmAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAd0pKOpReXtKxb3D/yXl6/sx5ev/ws7D/6q6s/+Ked/7npFb/2ZiP/ny7gP+OjW/9h1dWr2I7 + OiMAAAAAAAAAAAAAAAAAAAAAAAAAALSCggSqcXIbo2dnN61xcVS/h4eIzp2c2cKWle2OY2OGbz4+Y4xN + Tr6zaWn84Jyb/9aXlv7Ji4r/p25t9INTUqZlPDw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJJg + YASjcnMorH9/a6h7e4yabm6Df1NTU3VKSgwAAAAAAAAAAAAAAABgNDQgcj8/bntHR4ZnPDxTVTExDQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////APx//wD4P/8A8D//AOA//wDgH/8A4B//AMAf + /wDAH8EAwA8AAMAAAADAAAAAwAAAAMAAAQDAAAMA4AAHAPgAHwAAAH8AAcH/AP///wD///8A////ACgA + AAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQc + HA5LKSlUNBwcSAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsO + DgV/SkqHm1hY+X5HR90tGRkuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAB4SEhCr2Zm7sZwcf+oYWL5UC8vUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAACnl9fnMRwcf/IcXL/tmxs/mI8PGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAa0NCGbRsbdbMenv/zn5//8R9ff9ySkmCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAA + AAkAAAAAAAAAAItYWDvFfn/y2ZWW/92fn//anJv/jWFgvwAAAB0AAAAAAAAAAAAAAAIzHBwiYjs7a3pM + S6pqQkKjLBoaMwAAAACeZ2dZ05KS/em0tP/vxMT/77u6/8CHhfpmPDyvRysqYlExMV1ySEiGnWdn07qB + gPzLkI//w4iG/HJLS3YAAAAAomloXsyRkf/DoKD/48bG/+jAv//hpKL/vX17/7h/fPu/iYj7z5qZ/+Gw + rv/rvLr/77q3/9ScmuR9U1I+AAAAAJZbWz2ndnbxdG9v/4yCgv+4lJP/77Wy/86erP+6nsH/tsXR/8PH + 0P/4wsD/9b26/+Cppu2peXdiAAAAAQAAAABYKCgHn2lqe6eCguSsgoL90pKS//Cxrv/TrcP/s5y+/8i3 + s/+quab/26mh/82UktSgbm1TBAAAAwAAAACud3cEvYGBC7N6ehyyfHtyt39+3bNub9vLgYH05qak/+Kg + g//OlH39jZR04Zd0aYmDT1EiAAAAAAAAAAAAAAAAr3t7D7aCgki5h4Z8uImJgah+fUltPz8ajU1ORq1s + bI6vdHOgm2RkaYxJUiZgCygCAAAAAAAAAAAAAAAAAAAAAGo9PQF9UVEHcEdHCTodHQIAAAAAAAAAAAAA + AAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AADh/wAAwf8AAMH/ + AACB/wAAgfkAAIDAAACAAAAAgAAAAIAAAACAAQAAAAcAAAAPAAAOfwAA//8AAA== + + + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/A7800HawkSchema.cs b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/A7800HawkSchema.cs new file mode 100644 index 0000000000..1c4fa60977 --- /dev/null +++ b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/A7800HawkSchema.cs @@ -0,0 +1,247 @@ +using System.Collections.Generic; +using System.Drawing; + +using BizHawk.Common.ReflectionExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Atari.A7800Hawk; + +namespace BizHawk.Client.EmuHawk +{ + [SchemaAttributes("A7800Hawk")] + public class A7800HawkSchema : IVirtualPadSchema + { + private string UnpluggedControllerName => typeof(UnpluggedController).DisplayName(); + private string StandardControllerName => typeof(StandardController).DisplayName(); + + public IEnumerable GetPadSchemas(IEmulator core) + { + var intvSyncSettings = ((A7800Hawk)core).GetSyncSettings().Clone(); + var port1 = intvSyncSettings.Port1; + var port2 = intvSyncSettings.Port2; + + if (port1 == StandardControllerName) + { + yield return JoystickController(1); + } + + if (port2 == StandardControllerName) + { + yield return JoystickController(2); + } + + } + + private static PadSchema ProLineController(int controller) + { + return new PadSchema + { + DisplayName = "Player " + controller, + IsConsole = false, + DefaultSize = new Size(174, 74), + MaxSize = new Size(174, 74), + Buttons = new[] + { + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Up", + DisplayName = "", + Icon = Properties.Resources.BlueUp, + Location = new Point(23, 15), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Down", + DisplayName = "", + Icon = Properties.Resources.BlueDown, + Location = new Point(23, 36), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Left", + DisplayName = "", + Icon = Properties.Resources.Back, + Location = new Point(2, 24), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Right", + DisplayName = "", + Icon = Properties.Resources.Forward, + Location = new Point(44, 24), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Trigger", + DisplayName = "1", + Location = new Point(120, 24), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Trigger 2", + DisplayName = "2", + Location = new Point(145, 24), + Type = PadSchema.PadInputType.Boolean + } + } + }; + } + + private static PadSchema JoystickController(int controller) + { + return new PadSchema + { + DisplayName = "Player " + controller, + IsConsole = false, + DefaultSize = new Size(174, 74), + MaxSize = new Size(174, 74), + Buttons = new[] + { + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Up", + DisplayName = "", + Icon = Properties.Resources.BlueUp, + Location = new Point(23, 15), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Down", + DisplayName = "", + Icon = Properties.Resources.BlueDown, + Location = new Point(23, 36), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Left", + DisplayName = "", + Icon = Properties.Resources.Back, + Location = new Point(2, 24), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Right", + DisplayName = "", + Icon = Properties.Resources.Forward, + Location = new Point(44, 24), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Trigger", + DisplayName = "1", + Location = new Point(120, 24), + Type = PadSchema.PadInputType.Boolean + } + } + }; + } + + private static PadSchema PaddleController(int controller) + { + return new PadSchema + { + DisplayName = "Player " + controller, + IsConsole = false, + DefaultSize = new Size(250, 74), + Buttons = new[] + { + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Paddle", + DisplayName = "Paddle", + Location = new Point(23, 15), + Type = PadSchema.PadInputType.FloatSingle + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Trigger", + DisplayName = "1", + Location = new Point(12, 90), + Type = PadSchema.PadInputType.Boolean + } + } + }; + } + + private static PadSchema LightGunController(int controller) + { + return new PadSchema + { + DisplayName = "Light Gun", + IsConsole = false, + DefaultSize = new Size(356, 290), + MaxSize = new Size(356, 290), + Buttons = new[] + { + new PadSchema.ButtonSchema + { + Name = "P" + controller + " VPos", + Location = new Point(14, 17), + Type = PadSchema.PadInputType.TargetedPair, + TargetSize = new Size(256, 240), + SecondaryNames = new[] + { + "P" + controller + " HPos", + } + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Trigger", + DisplayName = "Trigger", + Location = new Point(284, 17), + Type = PadSchema.PadInputType.Boolean + } + } + }; + } + + private static PadSchema ConsoleButtons() + { + return new PadSchema + { + DisplayName = "Console", + IsConsole = true, + DefaultSize = new Size(215, 50), + Buttons = new[] + { + new PadSchema.ButtonSchema + { + Name = "Select", + DisplayName = "Select", + Location = new Point(10, 15), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "Reset", + DisplayName = "Reset", + Location = new Point(60, 15), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "Power", + DisplayName = "Power", + Location = new Point(108, 15), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "Pause", + DisplayName = "Pause", + Location = new Point(158, 15), + Type = PadSchema.PadInputType.Boolean + } + } + }; + } + } +} diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index ed3b3663c5..a48dd0cc63 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -208,12 +208,12 @@ C64.cs - C64.cs + C64.cs C64.cs - + @@ -221,7 +221,7 @@ - + @@ -382,10 +382,16 @@ A7800Hawk.cs + + A7800Hawk.cs + + + + + A7800Hawk.cs - @@ -1377,4 +1383,4 @@ --> - + \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IDebuggable.cs index 45809e725e..056eb62a0a 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IDebuggable.cs @@ -54,11 +54,7 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk } } - public IMemoryCallbackSystem MemoryCallbacks - { - [FeatureNotImplemented] - get { throw new NotImplementedException(); } - } + public IMemoryCallbackSystem MemoryCallbacks { get; private set; } public bool CanStep(StepType type) { diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IEmulator.cs index c739349520..09ad452125 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IEmulator.cs @@ -1,4 +1,5 @@ using BizHawk.Emulation.Common; +using System; namespace BizHawk.Emulation.Cores.Atari.A7800Hawk { @@ -6,15 +7,29 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk { public IEmulatorServiceProvider ServiceProvider { get; } - public ControllerDefinition ControllerDefinition { get; private set; } + public ControllerDefinition ControllerDefinition => _controllerDeck.Definition; //Maria related variables public int cycle; public int cpu_cycle; public int scanline; + // there are 4 maria cycles in a CPU cycle (fast access, both NTSC and PAL) + // if the 6532 or TIA are accessed (PC goes to one of those addresses) the next access will be slower by 1/2 a CPU cycle + // i.e. it will take 6 Maria cycles instead of 4 + public bool slow_access = false; + public void FrameAdvance(IController controller, bool render, bool rendersound) { + if (_tracer.Enabled) + { + cpu.TraceCallback = s => _tracer.Put(s); + } + else + { + cpu.TraceCallback = null; + } + _frame++; if (controller.IsPressed("Power")) @@ -29,6 +44,9 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk _lagcount++; } + // read the controller state here for now + GetControllerState(controller); + scanline = 0; // actually execute the frame @@ -37,18 +55,61 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk maria.Execute(cycle, scanline); cycle++; cpu_cycle++; + tia._hsyncCnt++; + + // the time a cpu cycle takes depends on the status of the address bus + // any address in range of the TIA or m6532 takes an extra cycle to complete + if (cpu_cycle==(4 + (slow_access ? 2 : 0))) + { + cpu.ExecuteOne(); + cpu_cycle = 0; + } + + // determine if the next access will be fast or slow + if (cpu.PC < 0x0400) + { + if ((cpu.PC & 0xFF) < 0x20) + { + if ((A7800_control_register & 0x1) == 0 && (cpu.PC < 0x20)) + { + slow_access = false; + } + else + { + slow_access = true; + } + } + else if (cpu.PC < 0x300) + { + slow_access = true; + } + else + { + slow_access = false; + } + } if (cycle == 454) { scanline++; cycle = 0; + tia._hsyncCnt = 0; } } } + private void GetControllerState(IController controller) + { + InputCallbacks.Call(); + + ushort port1 = _controllerDeck.ReadPort1(controller); + + ushort port2 = _controllerDeck.ReadPort2(controller); + } + public int Frame => _frame; public string SystemId => "A7800"; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISettable.cs new file mode 100644 index 0000000000..7f646316af --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISettable.cs @@ -0,0 +1,91 @@ +using System; +using Newtonsoft.Json; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class A7800Hawk : IEmulator, IStatable, ISettable + { + public A7800Settings GetSettings() + { + return _settings.Clone(); + } + + public A7800SyncSettings GetSyncSettings() + { + return _syncSettings.Clone(); + } + + public bool PutSettings(A7800Settings o) + { + _settings = o; + return false; + } + + public bool PutSyncSettings(A7800SyncSettings o) + { + bool ret = A7800SyncSettings.NeedsReboot(_syncSettings, o); + _syncSettings = o; + return ret; + } + + private A7800Settings _settings = new A7800Settings(); + private A7800SyncSettings _syncSettings = new A7800SyncSettings(); + + public class A7800Settings + { + public A7800Settings Clone() + { + return (A7800Settings)MemberwiseClone(); + } + } + + public class A7800SyncSettings + { + private string _port1 = A7800HawkControllerDeck.DefaultControllerName; + private string _port2 = A7800HawkControllerDeck.DefaultControllerName; + + [JsonIgnore] + public string Port1 + { + get { return _port1; } + set + { + if (!A7800HawkControllerDeck.ValidControllerTypes.ContainsKey(value)) + { + throw new InvalidOperationException("Invalid controller type: " + value); + } + + _port1 = value; + } + } + + [JsonIgnore] + public string Port2 + { + get { return _port2; } + set + { + if (!A7800HawkControllerDeck.ValidControllerTypes.ContainsKey(value)) + { + throw new InvalidOperationException("Invalid controller type: " + value); + } + + _port2 = value; + } + } + + public A7800SyncSettings Clone() + { + return (A7800SyncSettings)MemberwiseClone(); + } + + public static bool NeedsReboot(A7800SyncSettings x, A7800SyncSettings y) + { + return !DeepEquality.DeepEquals(x, y); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IStatable.cs index 96356abb5b..872bc6b442 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IStatable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IStatable.cs @@ -53,6 +53,8 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk ser.Sync("Lag", ref _lagcount); ser.Sync("Frame", ref _frame); ser.Sync("IsLag", ref _islag); + _controllerDeck.SyncState(ser); + ser.EndSection(); } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs index 1ea8b6aea8..32c4872461 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs @@ -12,7 +12,8 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk isPorted: false, isReleased: true)] [ServiceNotApplicable(typeof(ISettable<,>), typeof(IDriveLight))] - public partial class A7800Hawk : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable + public partial class A7800Hawk : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable, + ISettable { // this register selects between 2600 and 7800 mode in the A7800 // however, we already have a 2600 emulator so this core will only be loading A7800 games @@ -25,14 +26,20 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk public byte[] Maria_regs = new byte[0x20]; public byte[] RAM = new byte[0x1000]; public byte[] regs_6532 = new byte[0x80]; + public byte[] hs_bios_mem = new byte[0x800]; - private readonly byte[] _rom; - private readonly byte[] _hsbios; - private readonly byte[] _bios; - private readonly byte[] _hsram = new byte[2048]; + public readonly byte[] _rom; + public readonly byte[] _hsbios; + public readonly byte[] _bios; + public readonly byte[] _hsram = new byte[2048]; private int _frame = 0; + public string s_mapper; + public MapperBase mapper; + + private readonly ITraceable _tracer; + public MOS6502X cpu; public Maria maria; private bool _isPAL; @@ -45,10 +52,14 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk maria = new Maria(); tia = new TIA(); - - ser.Register(maria); - ser.Register(tia); - ServiceProvider = ser; + cpu = new MOS6502X + { + ReadMemory = ReadMemory, + WriteMemory = WriteMemory, + PeekMemory = ReadMemory, + DummyReadMemory = ReadMemory, + OnExecFetch = ExecFetch + }; maria = new Maria { @@ -56,6 +67,9 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk }; CoreComm = comm; + + _controllerDeck = new A7800HawkControllerDeck(_syncSettings.Port1, _syncSettings.Port2); + byte[] highscoreBios = comm.CoreFileProvider.GetFirmware("A78", "Bios_HSC", false, "Some functions may not work without the high score BIOS."); byte[] palBios = comm.CoreFileProvider.GetFirmware("A78", "Bios_PAL", false, "The game will not run if the correct region BIOS is not available."); byte[] ntscBios = comm.CoreFileProvider.GetFirmware("A78", "Bios_NTSC", false, "The game will not run if the correct region BIOS is not available."); @@ -74,7 +88,7 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk // if none found default is zero // also check for PAL region string hash_md5 = null; - string s_mapper = null; + s_mapper = null; hash_md5 = "md5:" + rom.HashMD5(0, rom.Length); var gi = Database.CheckDatabase(hash_md5); @@ -82,22 +96,24 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk if (gi != null) { var dict = gi.GetOptionsDict(); - if (!dict.ContainsKey("PAL")) + if (dict.ContainsKey("PAL")) { _isPAL = true; } - if (!dict.ContainsKey("board")) + if (dict.ContainsKey("board")) { s_mapper = dict["board"]; } else - throw new Exception("No Board selected for this mapper"); + throw new Exception("No Board selected for this game"); } else { throw new Exception("ROM not in gamedb"); } + Reset_Mapper(s_mapper); + _rom = rom; _hsbios = highscoreBios; _bios = _isPAL ? palBios : ntscBios; @@ -111,21 +127,31 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk if (_isPAL) { maria._frameHz = 50; + maria._screen_width = 454; + maria._screen_height = 313; maria._palette = PALPalette; } else { maria._frameHz = 60; + maria._screen_width = 454; + maria._screen_height = 263; maria._palette = NTSCPalette; } + ser.Register(maria); + ser.Register(tia); + ServiceProvider = ser; + + _tracer = new TraceBuffer { Header = cpu.TraceHeader }; + ser.Register(_tracer); HardReset(); } public DisplayType Region => _isPAL ? DisplayType.PAL : DisplayType.NTSC; - public A7800HawkControl ControlAdapter { get; private set; } + private readonly A7800HawkControllerDeck _controllerDeck; private void HardReset() { @@ -142,7 +168,24 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk Maria_regs = new byte[0x20]; RAM = new byte[0x1000]; regs_6532 = new byte[0x80]; - } + + cpu_cycle = 0; + } + + private void ExecFetch(ushort addr) + { + //MemoryCallbacks.CallExecutes(addr); + } + + private void Reset_Mapper(string m) + { + if (m=="0") + { + mapper = new MapperDefault(); + } + + mapper.Core = this; + } /* * MariaTables.cs diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControl.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControl.cs deleted file mode 100644 index ddb0815b14..0000000000 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControl.cs +++ /dev/null @@ -1,409 +0,0 @@ -using System; - -using EMU7800.Core; -using BizHawk.Emulation.Common; - -namespace BizHawk.Emulation.Cores.Atari.A7800Hawk -{ - public class A7800HawkControl - { - private static readonly ControllerDefinition Joystick = new ControllerDefinition - { - Name = "Atari 7800 Joystick Controller", - BoolButtons = - { - // hard reset, not passed to EMU7800 - "Power", - - // on the console - "Reset", - "Select", - "BW", // should be "Color"?? - "Left Difficulty", // better not put P# on these as they might not correspond to player numbers - "Right Difficulty", - - // ports - "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Trigger", - "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Trigger" - } - }; - - private static readonly ControllerDefinition Paddles = new ControllerDefinition - { - Name = "Atari 7800 Paddle Controller", - BoolButtons = - { - // hard reset, not passed to EMU7800 - "Power", - - // on the console - "Reset", - "Select", - "BW", // should be "Color"?? - "Left Difficulty", // better not put P# on these as they might not correspond to player numbers - "Right Difficulty", - - // ports - "P1 Trigger", - "P2 Trigger", - "P3 Trigger", - "P4 Trigger" - }, - FloatControls = // should be in [0..700000] - { - "P1 Paddle", - "P2 Paddle", - "P3 Paddle", - "P4 Paddle" - }, - FloatRanges = - { - // what is the center point supposed to be here? - new[] { 0.0f, 0.0f, 700000.0f }, - new[] { 0.0f, 0.0f, 700000.0f }, - new[] { 0.0f, 0.0f, 700000.0f }, - new[] { 0.0f, 0.0f, 700000.0f } - } - }; - - private static readonly ControllerDefinition Keypad = new ControllerDefinition - { - Name = "Atari 7800 Keypad Controller", - BoolButtons = - { - // hard reset, not passed to EMU7800 - "Power", - - // on the console - "Reset", - "Select", - "BW", // should be "Color"?? - "Toggle Left Difficulty", // better not put P# on these as they might not correspond to player numbers - "Toggle Right Difficulty", - - // ports - "P1 Keypad1", "P1 Keypad2", "P1 Keypad3", - "P1 Keypad4", "P1 Keypad5", "P1 Keypad6", - "P1 Keypad7", "P1 Keypad8", "P1 Keypad9", - "P1 KeypadA", "P1 Keypad0", "P1 KeypadP", - "P2 Keypad1", "P2 Keypad2", "P2 Keypad3", - "P2 Keypad4", "P2 Keypad5", "P2 Keypad6", - "P2 Keypad7", "P2 Keypad8", "P2 Keypad9", - "P2 KeypadA", "P2 Keypad0", "P2 KeypadP", - "P3 Keypad1", "P3 Keypad2", "P3 Keypad3", - "P3 Keypad4", "P3 Keypad5", "P3 Keypad6", - "P3 Keypad7", "P3 Keypad8", "P3 Keypad9", - "P3 KeypadA", "P3 Keypad0", "P3 KeypadP", - "P4 Keypad1", "P4 Keypad2", "P4 Keypad3", - "P4 Keypad4", "P4 Keypad5", "P4 Keypad6", - "P4 Keypad7", "P4 Keypad8", "P4 Keypad9", - "P4 KeypadA", "P4 Keypad0", "P4 KeypadP" - } - }; - - private static readonly ControllerDefinition Driving = new ControllerDefinition - { - Name = "Atari 7800 Driving Controller", - BoolButtons = - { - // hard reset, not passed to EMU7800 - "Power", - - // on the console - "Reset", - "Select", - "BW", // should be "Color"?? - "Toggle Left Difficulty", // better not put P# on these as they might not correspond to player numbers - "Toggle Right Difficulty", - - // ports - "P1 Trigger", - "P2 Trigger" - }, - FloatControls = // should be in [0..3] - { - "P1 Driving", - "P2 Driving" - }, - FloatRanges = - { - new[] { 0.0f, 0.0f, 3.0f }, - new[] { 0.0f, 0.0f, 3.0f }, - new[] { 0.0f, 0.0f, 3.0f } - } - }; - - private static readonly ControllerDefinition BoosterGrip = new ControllerDefinition - { - Name = "Atari 7800 Booster Grip Controller", - BoolButtons = - { - // hard reset, not passed to EMU7800 - "Power", - - // on the console - "Reset", - "Select", - "BW", // should be "Color"?? - "Toggle Left Difficulty", // better not put P# on these as they might not correspond to player numbers - "Toggle Right Difficulty", - - // ports - // NB: as referenced by the emu, p1t2 = p1t2, p1t3 = p2t2, p2t2 = p3t2, p2t3 = p4t2 - "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Trigger", "P1 Trigger 2", "P1 Trigger 3", - "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Trigger", "P2 Trigger 2", "P2 Trigger 3" - } - }; - - private static readonly ControllerDefinition ProLineJoystick = new ControllerDefinition - { - Name = "Atari 7800 ProLine Joystick Controller", - BoolButtons = - { - // hard reset, not passed to EMU7800 - "Power", - - // on the console - "Reset", - "Select", - "Pause", - "Toggle Left Difficulty", // better not put P# on these as they might not correspond to player numbers - "Toggle Right Difficulty", - - // ports - "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Trigger", "P1 Trigger 2", - "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Trigger", "P2 Trigger 2" - } - }; - - private static readonly ControllerDefinition Lightgun = new ControllerDefinition - { - Name = "Atari 7800 Light Gun Controller", - BoolButtons = - { - // hard reset, not passed to EMU7800 - "Power", - - // on the console - "Reset", - "Select", - "Pause", - "Left Difficulty", // better not put P# on these as they might not correspond to player numbers - "Right Difficulty", - - // ports - "P1 Trigger", - "P2 Trigger" - }, - FloatControls = // vpos should be actual scanline number. hpos should be in [0..319]?? - { - "P1 VPos", "P1 HPos", - "P2 VPos", "P2 HPos" - }, - FloatRanges = - { - // how many scanlines are there again?? - new[] { 0.0f, 0.0f, 240.0f }, - new[] { 0.0f, 0.0f, 319.0f }, - new[] { 0.0f, 0.0f, 240.0f }, - new[] { 0.0f, 0.0f, 319.0f } - } - }; - - private struct ControlAdapter - { - public readonly ControllerDefinition Type; - public readonly Controller Left; - public readonly Controller Right; - public readonly Action Convert; - - public ControlAdapter(ControllerDefinition type, Controller left, Controller right, Action convert) - { - Type = type; - Left = left; - Right = right; - Convert = convert; - } - } - - private static readonly ControlAdapter[] Adapters = - { - new ControlAdapter(Joystick, Controller.Joystick, Controller.Joystick, ConvertJoystick), - new ControlAdapter(Paddles, Controller.Paddles, Controller.Paddles, ConvertPaddles), - new ControlAdapter(Keypad, Controller.Keypad, Controller.Keypad, ConvertKeypad), - new ControlAdapter(Driving, Controller.Driving, Controller.Driving, ConvertDriving), - new ControlAdapter(BoosterGrip, Controller.BoosterGrip, Controller.BoosterGrip, ConvertBoosterGrip), - new ControlAdapter(ProLineJoystick, Controller.ProLineJoystick, Controller.ProLineJoystick, ConvertProLineJoystick), - new ControlAdapter(Lightgun, Controller.Lightgun, Controller.Lightgun, ConvertLightgun), - }; - - private static void ConvertConsoleButtons(IController c, InputState s) - { - s.RaiseInput(0, MachineInput.Reset, c.IsPressed("Reset")); - s.RaiseInput(0, MachineInput.Select, c.IsPressed("Select")); - s.RaiseInput(0, MachineInput.Color, c.IsPressed("BW")); - if (c.IsPressed("Toggle Left Difficulty")) - { - s.RaiseInput(0, MachineInput.LeftDifficulty, c.IsPressed("Toggle Left Difficulty")); - } - - if (c.IsPressed("Toggle Right Difficulty")) - { - s.RaiseInput(0, MachineInput.RightDifficulty, c.IsPressed("Toggle Right Difficulty")); - } - } - - private static void ConvertConsoleButtons7800(IController c, InputState s) - { - s.RaiseInput(0, MachineInput.Reset, c.IsPressed("Reset")); - s.RaiseInput(0, MachineInput.Select, c.IsPressed("Select")); - s.RaiseInput(0, MachineInput.Color, c.IsPressed("Pause")); - if (c.IsPressed("Toggle Left Difficulty")) - { - s.RaiseInput(0, MachineInput.LeftDifficulty, c.IsPressed("Toggle Left Difficulty")); - } - - if (c.IsPressed("Toggle Right Difficulty")) - { - s.RaiseInput(0, MachineInput.RightDifficulty, c.IsPressed("Toggle Right Difficulty")); - } - } - - private static void ConvertDirections(IController c, InputState s, int p) - { - string ps = $"P{p + 1} "; - s.RaiseInput(p, MachineInput.Up, c.IsPressed(ps + "Up")); - s.RaiseInput(p, MachineInput.Down, c.IsPressed(ps + "Down")); - s.RaiseInput(p, MachineInput.Left, c.IsPressed(ps + "Left")); - s.RaiseInput(p, MachineInput.Right, c.IsPressed(ps + "Right")); - } - - private static void ConvertTrigger(IController c, InputState s, int p) - { - string ps = $"P{p + 1} "; - s.RaiseInput(p, MachineInput.Fire, c.IsPressed(ps + "Trigger")); - } - - private static void ConvertJoystick(IController c, InputState s) - { - s.ClearControllerInput(); - ConvertConsoleButtons(c, s); - ConvertDirections(c, s, 0); - ConvertDirections(c, s, 1); - ConvertTrigger(c, s, 0); - ConvertTrigger(c, s, 1); - } - - private static void ConvertPaddles(IController c, InputState s) - { - s.ClearControllerInput(); - ConvertConsoleButtons(c, s); - for (int i = 0; i < 4; i++) - { - string ps = $"P{i + 1} "; - ConvertTrigger(c, s, i); - s.RaisePaddleInput(i, 700000, (int)c.GetFloat(ps + "Trigger")); - } - } - - private static void ConvertKeypad(IController c, InputState s) - { - s.ClearControllerInput(); - ConvertConsoleButtons(c, s); - for (int i = 0; i < 4; i++) - { - string ps = $"P{i + 1} "; - s.RaiseInput(i, MachineInput.NumPad1, c.IsPressed(ps + "Keypad1")); - s.RaiseInput(i, MachineInput.NumPad2, c.IsPressed(ps + "Keypad2")); - s.RaiseInput(i, MachineInput.NumPad3, c.IsPressed(ps + "Keypad3")); - s.RaiseInput(i, MachineInput.NumPad4, c.IsPressed(ps + "Keypad4")); - s.RaiseInput(i, MachineInput.NumPad5, c.IsPressed(ps + "Keypad5")); - s.RaiseInput(i, MachineInput.NumPad6, c.IsPressed(ps + "Keypad6")); - s.RaiseInput(i, MachineInput.NumPad7, c.IsPressed(ps + "Keypad7")); - s.RaiseInput(i, MachineInput.NumPad8, c.IsPressed(ps + "Keypad8")); - s.RaiseInput(i, MachineInput.NumPad9, c.IsPressed(ps + "Keypad9")); - s.RaiseInput(i, MachineInput.NumPadMult, c.IsPressed(ps + "KeypadA")); - s.RaiseInput(i, MachineInput.NumPad0, c.IsPressed(ps + "Keypad0")); - s.RaiseInput(i, MachineInput.NumPadHash, c.IsPressed(ps + "KeypadP")); - } - } - - private static readonly MachineInput[] Drvlut = - { - MachineInput.Driving0, - MachineInput.Driving1, - MachineInput.Driving2, - MachineInput.Driving3 - }; - - private static void ConvertDriving(IController c, InputState s) - { - s.ClearControllerInput(); - ConvertConsoleButtons(c, s); - ConvertTrigger(c, s, 0); - ConvertTrigger(c, s, 1); - s.RaiseInput(0, Drvlut[(int)c.GetFloat("P1 Driving")], true); - s.RaiseInput(1, Drvlut[(int)c.GetFloat("P2 Driving")], true); - } - - private static void ConvertBoosterGrip(IController c, InputState s) - { - s.ClearControllerInput(); - ConvertConsoleButtons(c, s); - ConvertDirections(c, s, 0); - ConvertDirections(c, s, 1); - - // weird mapping is intentional - s.RaiseInput(0, MachineInput.Fire, c.IsPressed("P1 Trigger")); - s.RaiseInput(0, MachineInput.Fire2, c.IsPressed("P1 Trigger 2")); - s.RaiseInput(1, MachineInput.Fire2, c.IsPressed("P1 Trigger 3")); - s.RaiseInput(1, MachineInput.Fire, c.IsPressed("P2 Trigger")); - s.RaiseInput(2, MachineInput.Fire2, c.IsPressed("P2 Trigger 2")); - s.RaiseInput(3, MachineInput.Fire2, c.IsPressed("P2 Trigger 3")); - } - - private static void ConvertProLineJoystick(IController c, InputState s) - { - s.ClearControllerInput(); - ConvertConsoleButtons7800(c, s); - ConvertDirections(c, s, 0); - ConvertDirections(c, s, 1); - s.RaiseInput(0, MachineInput.Fire, c.IsPressed("P1 Trigger")); - s.RaiseInput(0, MachineInput.Fire2, c.IsPressed("P1 Trigger 2")); - s.RaiseInput(1, MachineInput.Fire, c.IsPressed("P2 Trigger")); - s.RaiseInput(1, MachineInput.Fire2, c.IsPressed("P2 Trigger 2")); - } - - private static void ConvertLightgun(IController c, InputState s) - { - s.ClearControllerInput(); - ConvertConsoleButtons7800(c, s); - ConvertTrigger(c, s, 0); - ConvertTrigger(c, s, 1); - s.RaiseLightgunPos(0, (int)c.GetFloat("P1 VPos"), (int)c.GetFloat("P1 HPos")); - s.RaiseLightgunPos(1, (int)c.GetFloat("P2 VPos"), (int)c.GetFloat("P2 HPos")); - } - - public Action Convert { get; private set; } - - public ControllerDefinition ControlType { get; private set; } - - public A7800HawkControl(MachineBase mac) - { - var l = mac.InputState.LeftControllerJack; - var r = mac.InputState.RightControllerJack; - - foreach (var a in Adapters) - { - if (a.Left == l && a.Right == r) - { - Convert = a.Convert; - ControlType = a.Type; - return; - } - } - - throw new Exception($"Couldn't connect Atari 7800 controls \"{l}\" and \"{r}\""); - } - } -} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControllerDeck.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControllerDeck.cs new file mode 100644 index 0000000000..131ab32082 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControllerDeck.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Common.ReflectionExtensions; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public class A7800HawkControllerDeck + { + public A7800HawkControllerDeck(string controller1Name, string controller2Name) + { + if (!ValidControllerTypes.ContainsKey(controller1Name)) + { + throw new InvalidOperationException("Invalid controller type: " + controller1Name); + } + + if (!ValidControllerTypes.ContainsKey(controller2Name)) + { + throw new InvalidOperationException("Invalid controller type: " + controller2Name); + } + + Port1 = (IPort)Activator.CreateInstance(ValidControllerTypes[controller1Name], 1); + Port2 = (IPort)Activator.CreateInstance(ValidControllerTypes[controller2Name], 2); + + Definition = new ControllerDefinition + { + Name = "A7800 Controller", + BoolButtons = Port1.Definition.BoolButtons + .Concat(Port2.Definition.BoolButtons) + .Concat(new[] + { + "Power", + "Reset", + "Select", + "BW", // should be "Color"?? + "Left Difficulty", // better not put P# on these as they might not correspond to player numbers + "Right Difficulty" + }) + .ToList() + }; + + Definition.FloatControls.AddRange(Port1.Definition.FloatControls); + Definition.FloatControls.AddRange(Port2.Definition.FloatControls); + + Definition.FloatRanges.AddRange(Port1.Definition.FloatRanges); + Definition.FloatRanges.AddRange(Port2.Definition.FloatRanges); + } + + public byte ReadPort1(IController c) + { + return Port1.Read(c); + } + + public byte ReadPort2(IController c) + { + return Port2.Read(c); + } + + public ControllerDefinition Definition { get; } + + public void SyncState(Serializer ser) + { + ser.BeginSection("Port1"); + Port1.SyncState(ser); + ser.EndSection(); + + ser.BeginSection("Port2"); + Port2.SyncState(ser); + ser.EndSection(); + } + + private readonly IPort Port1; + private readonly IPort Port2; + + private static Dictionary _controllerTypes; + + public static Dictionary ValidControllerTypes + { + get + { + if (_controllerTypes == null) + { + _controllerTypes = typeof(A7800HawkControllerDeck).Assembly + .GetTypes() + .Where(t => typeof(IPort).IsAssignableFrom(t)) + .Where(t => !t.IsAbstract && !t.IsInterface) + .ToDictionary(tkey => tkey.DisplayName()); + } + + return _controllerTypes; + } + } + + public static string DefaultControllerName => typeof(StandardController).DisplayName(); + } + +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControllers.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControllers.cs new file mode 100644 index 0000000000..72ca92171d --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControllers.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + /// + /// Represents a controller plugged into a controller port on the intellivision + /// + public interface IPort + { + byte Read(IController c); + + ControllerDefinition Definition { get; } + + void SyncState(Serializer ser); + + int PortNum { get; } + } + + [DisplayName("Unplugged Controller")] + public class UnpluggedController : IPort + { + public UnpluggedController(int portNum) + { + PortNum = portNum; + Definition = new ControllerDefinition + { + BoolButtons = new List() + }; + } + + public byte Read(IController c) + { + return 0; + } + + public ControllerDefinition Definition { get; } + + public void SyncState(Serializer ser) + { + // Do nothing + } + + public int PortNum { get; } + } + + [DisplayName("Joystick Controller")] + public class StandardController : IPort + { + public StandardController(int portNum) + { + PortNum = portNum; + Definition = new ControllerDefinition + { + BoolButtons = BaseDefinition + .Select(b => "P" + PortNum + " " + b) + .ToList() + }; + } + + public int PortNum { get; } + + public byte Read(IController c) + { + byte result = 0; + for (int i = 0; i < 5; i++) + { + if (c.IsPressed(Definition.BoolButtons[i])) + { + result |= HandControllerButtons[i]; + } + } + + return result; + } + + public ControllerDefinition Definition { get; } + + + public void SyncState(Serializer ser) + { + // Nothing todo, I think + } + + private static readonly string[] BaseDefinition = + { + "U", "D", "L", "R", "Fire" + }; + + private static byte[] HandControllerButtons = + { + 0x60, // UP + 0xC0, // Down + 0xA0, // Left + 0x48, // Right + 0x81 // Fire + }; + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Mappers/MapperBase.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Mappers/MapperBase.cs new file mode 100644 index 0000000000..7b67757591 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Mappers/MapperBase.cs @@ -0,0 +1,36 @@ +using BizHawk.Common; +using System; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public class MapperBase + { + public A7800Hawk Core { get; set; } + + public virtual byte ReadMemory(ushort addr) + { + return 0; + } + + public virtual byte PeekMemory(ushort addr) + { + return 0; + } + + public virtual void WriteMemory(ushort addr, byte value) + { + } + + public virtual void PokeMemory(ushort addr, byte value) + { + } + + public virtual void SyncState(Serializer ser) + { + } + + public virtual void Dispose() + { + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Mappers/MapperDefault.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Mappers/MapperDefault.cs new file mode 100644 index 0000000000..a92a98d8d3 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Mappers/MapperDefault.cs @@ -0,0 +1,100 @@ +using BizHawk.Common; +using BizHawk.Common.NumberExtensions; +using System; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + // Default mapper with no bank switching + // Just need to keep track of high score bios stuff + public class MapperDefault : MapperBase + { + public override byte ReadMemory(ushort addr) + { + if (addr < 0x480) + { + // Nothing here + return 0xFF; + } + else if (addr < 0x1800) + { + //could be hsbios RAM here + if (addr >= 0x1000 && Core._hsbios != null) + { + return Core._hsram[addr - 0x1000]; + } + return 0xFF; + } + else if (addr < 0x4000) + { + // could be either RAM mirror or ROM + if (addr >= 0x3000 && Core._hsbios != null) + { + return Core._hsbios[addr - 0x3000]; + } + else + { + return Core.RAM[0x800 + addr & 0x7FF]; + } + } + else + { + // cartridge and other OPSYS + if ((Core._rom.Length >= 0x10000 - addr) && Core.A7800_control_register.Bit(2)) + { + return Core._rom[Core._rom.Length - (0x10000 - addr)]; + } + else if (addr >= 0xF000 && !Core.A7800_control_register.Bit(2)) + { + return Core._bios[addr - 0xF000]; + } + else + { + return 0xFF; + } + } + } + + public override byte PeekMemory(ushort addr) + { + return ReadMemory(addr); + } + + public override void WriteMemory(ushort addr, byte value) + { + if (addr < 0x480) + { + // Nothing here + } + else if (addr < 0x1800) + { + //could be hsbios RAM here + if (addr >= 0x1000 && Core._hsbios != null) + { + Core._hsram[addr - 0x1000] = value; + } + } + else if (addr < 0x4000) + { + // could be either RAM mirror or ROM + if (addr >= 0x3000 && Core._hsbios != null) + { + } + else + { + Core.RAM[0x800 + addr & 0x7FF] = value; + } + } + else + { + // cartridge and other OPSYS + } + } + + public override void PokeMemory(ushort addr, byte value) + { + WriteMemory(addr, value); + } + + + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Maria.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Maria.cs index a142a32bb7..95f136866b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Maria.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Maria.cs @@ -7,7 +7,9 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk // Emulates the Atari 7800 Maria graphics chip public class Maria : IVideoProvider { - public int _frameHz; + public int _frameHz = 60; + public int _screen_width = 454; + public int _screen_height = 263; public int[] _vidbuffer; public int[] _palette; @@ -17,10 +19,10 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk return _vidbuffer; } - public int VirtualWidth => 275; - public int VirtualHeight => BufferHeight; - public int BufferWidth { get; private set; } - public int BufferHeight { get; private set; } + public int VirtualWidth => 454; + public int VirtualHeight => _screen_height; + public int BufferWidth => 454; + public int BufferHeight => _screen_height; public int BackgroundColor => unchecked((int)0xff000000); public int VsyncNumerator => _frameHz; public int VsyncDenominator => 1; @@ -28,11 +30,6 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk // the Maria chip can directly access memory public Func ReadMemory; - // there are 4 maria cycles in a CPU cycle (fast access, both NTSC and PAL) - // if the 6532 or TIA are accessed (PC goes to one of those addresses) the next access will be slower by 1/2 a CPU cycle - // i.e. it will take 6 Maria cycles instead of 4 - public bool slow_access = false; - // each frame contains 263 scanlines // each scanline consists of 113.5 CPU cycles (fast access) which equates to 454 Maria cycles // In total there are 29850.5 CPU cycles (fast access) in a frame @@ -43,7 +40,7 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk public void Reset() { - + _vidbuffer = new int[VirtualWidth * VirtualHeight]; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/MemoryMap.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/MemoryMap.cs index 64f6211966..a7541855c6 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/MemoryMap.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/MemoryMap.cs @@ -55,7 +55,8 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk } else if (addr < 0x480) { - return 0xFF; // cartridge space available + // cartridge space available + return mapper.ReadMemory(addr); } else if (addr < 0x500) { @@ -64,7 +65,8 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk } else if (addr < 0x1800) { - return 0xFF; // cartridge space available + // cartridge space available + return mapper.ReadMemory(addr); } else if (addr < 0x2800) { @@ -72,11 +74,12 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk } else if (addr < 0x4000) { - return RAM[addr - 0x2800 + 0x800]; + // could be either RAM mirror or ROM + return mapper.ReadMemory(addr); } else { - return 0xFF; // cartridge and other OPSYS + return mapper.ReadMemory(addr); } } @@ -125,6 +128,7 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk else if (addr < 0x480) { // cartridge space available + mapper.WriteMemory(addr, value); } else if (addr < 0x500) { @@ -134,6 +138,7 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk else if (addr < 0x1800) { // cartridge space available + mapper.WriteMemory(addr, value); } else if (addr < 0x2800) { @@ -141,13 +146,16 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk } else if (addr < 0x4000) { - RAM[addr - 0x2800 + 0x800] = value; + // could be either RAM mirror or ROM + mapper.WriteMemory(addr, value); } else { // cartridge and other OPSYS + mapper.WriteMemory(addr, value); } } + } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/TIA.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/TIA.cs index 4c6ef5f20d..2fea119618 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/TIA.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/TIA.cs @@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk private bool _doTicks; - private byte _hsyncCnt; + public int _hsyncCnt; private int _capChargeStart; private bool _capCharging; public int AudioClocks; // not savestated @@ -37,7 +37,7 @@ namespace BizHawk.Emulation.Cores.Atari.A7800Hawk public void Execute(int cycles) { // do the audio sampling - if (_hsyncCnt == 36 || _hsyncCnt == 148) + if (_hsyncCnt == 113 || _hsyncCnt == 340) { LocalAudioCycles[AudioClocks] += (short)(AUD[0].Cycle() / 2); LocalAudioCycles[AudioClocks] += (short)(AUD[1].Cycle() / 2);