diff --git a/BizHawk.Client.Common/CoreFileProvider.cs b/BizHawk.Client.Common/CoreFileProvider.cs index e03e559e71..2ff417b2f6 100644 --- a/BizHawk.Client.Common/CoreFileProvider.cs +++ b/BizHawk.Client.Common/CoreFileProvider.cs @@ -98,6 +98,7 @@ namespace BizHawk.Client.Common #endregion + // this should go away now public static void SyncCoreCommInputSignals(CoreComm target = null) { if (target == null) diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index d1129f4bcc..45aac8d48d 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -362,7 +362,7 @@ namespace BizHawk.Client.Common } else { - nextEmulator = new QuickNES(nextComm, rom.FileData); + nextEmulator = new QuickNES(nextComm, rom.FileData, GetCoreSettings()); } break; diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index be685f28bd..55e92eedae 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -285,6 +285,12 @@ NESSyncSettingsForm.cs + + Form + + + QuickNesConfig.cs + Form @@ -903,6 +909,9 @@ NESSyncSettingsForm.cs + + QuickNesConfig.cs + PathConfig.cs diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 510391787b..d7599c0c32 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -12,6 +12,8 @@ using BizHawk.Emulation.Cores.Nintendo.NES; using BizHawk.Emulation.Cores.Nintendo.SNES; using BizHawk.Emulation.Cores.PCEngine; using BizHawk.Emulation.Cores.Sega.MasterSystem; +using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES; +using BizHawk.Client.EmuHawk.config.NES; namespace BizHawk.Client.EmuHawk { @@ -1220,8 +1222,10 @@ namespace BizHawk.Client.EmuHawk private void NESGraphicSettingsMenuItem_Click(object sender, EventArgs e) { - new NESGraphicsConfig().ShowDialog(); - CoreFileProvider.SyncCoreCommInputSignals(); + if (Global.Emulator is NES) + new NESGraphicsConfig().ShowDialog(this); + else if (Global.Emulator is QuickNES) + new QuickNesConfig().ShowDialog(this); } private void NESSoundChannelsMenuItem_Click(object sender, EventArgs e) diff --git a/BizHawk.Client.EmuHawk/config/NES/QuickNesConfig.Designer.cs b/BizHawk.Client.EmuHawk/config/NES/QuickNesConfig.Designer.cs new file mode 100644 index 0000000000..c851ad1aa8 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/NES/QuickNesConfig.Designer.cs @@ -0,0 +1,154 @@ +namespace BizHawk.Client.EmuHawk.config.NES +{ + partial class QuickNesConfig + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.buttonOK = new System.Windows.Forms.Button(); + this.buttonCancel = new System.Windows.Forms.Button(); + this.buttonPal = new System.Windows.Forms.Button(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.buttonPalReset = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.SuspendLayout(); + // + // propertyGrid1 + // + this.propertyGrid1.Location = new System.Drawing.Point(12, 12); + this.propertyGrid1.Name = "propertyGrid1"; + this.propertyGrid1.PropertySort = System.Windows.Forms.PropertySort.Alphabetical; + this.propertyGrid1.Size = new System.Drawing.Size(465, 189); + this.propertyGrid1.TabIndex = 0; + this.propertyGrid1.ToolbarVisible = false; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.pictureBox1); + this.groupBox1.Location = new System.Drawing.Point(12, 207); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(465, 156); + this.groupBox1.TabIndex = 1; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Palette Preview:"; + // + // buttonOK + // + this.buttonOK.Location = new System.Drawing.Point(321, 369); + this.buttonOK.Name = "buttonOK"; + this.buttonOK.Size = new System.Drawing.Size(75, 23); + this.buttonOK.TabIndex = 2; + this.buttonOK.Text = "OK"; + this.buttonOK.UseVisualStyleBackColor = true; + this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click); + // + // buttonCancel + // + this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonCancel.Location = new System.Drawing.Point(402, 369); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(75, 23); + this.buttonCancel.TabIndex = 3; + this.buttonCancel.Text = "Cancel"; + this.buttonCancel.UseVisualStyleBackColor = true; + // + // buttonPal + // + this.buttonPal.Location = new System.Drawing.Point(61, 369); + this.buttonPal.Name = "buttonPal"; + this.buttonPal.Size = new System.Drawing.Size(75, 23); + this.buttonPal.TabIndex = 4; + this.buttonPal.Text = "Browse..."; + this.buttonPal.UseVisualStyleBackColor = true; + this.buttonPal.Click += new System.EventHandler(this.buttonPal_Click); + // + // pictureBox1 + // + this.pictureBox1.Location = new System.Drawing.Point(6, 19); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(453, 131); + this.pictureBox1.TabIndex = 0; + this.pictureBox1.TabStop = false; + // + // buttonPalReset + // + this.buttonPalReset.Location = new System.Drawing.Point(142, 369); + this.buttonPalReset.Name = "buttonPalReset"; + this.buttonPalReset.Size = new System.Drawing.Size(75, 23); + this.buttonPalReset.TabIndex = 5; + this.buttonPalReset.Text = "Reset..."; + this.buttonPalReset.UseVisualStyleBackColor = true; + this.buttonPalReset.Click += new System.EventHandler(this.buttonPalReset_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 374); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(43, 13); + this.label1.TabIndex = 6; + this.label1.Text = "Palette:"; + // + // QuickNesConfig + // + this.AcceptButton = this.buttonOK; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.buttonCancel; + this.ClientSize = new System.Drawing.Size(489, 404); + this.Controls.Add(this.label1); + this.Controls.Add(this.buttonPalReset); + this.Controls.Add(this.buttonPal); + this.Controls.Add(this.buttonCancel); + this.Controls.Add(this.buttonOK); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.propertyGrid1); + this.Name = "QuickNesConfig"; + this.Text = "QuickNesConfig"; + this.Load += new System.EventHandler(this.QuickNesConfig_Load); + this.groupBox1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.PropertyGrid propertyGrid1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Button buttonOK; + private System.Windows.Forms.Button buttonCancel; + private System.Windows.Forms.Button buttonPal; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Button buttonPalReset; + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/NES/QuickNesConfig.cs b/BizHawk.Client.EmuHawk/config/NES/QuickNesConfig.cs new file mode 100644 index 0000000000..caf81dbb78 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/NES/QuickNesConfig.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using BizHawk.Emulation.Cores.Nintendo.NES; +using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES; +using BizHawk.Client.Common; +using BizHawk.Common; + +namespace BizHawk.Client.EmuHawk.config.NES +{ + public partial class QuickNesConfig : Form + { + private QuickNES.QuickNESSettings Settings; + + public QuickNesConfig() + { + InitializeComponent(); + } + + private void QuickNesConfig_Load(object sender, EventArgs e) + { + Settings = (QuickNES.QuickNESSettings)Global.Emulator.GetSettings(); + propertyGrid1.SelectedObject = Settings; + SetPaletteImage(); + } + + void SetPaletteImage() + { + int w = pictureBox1.Size.Width; + int h = pictureBox1.Size.Height; + var bmp = new Bitmap(w, h); + var pal = Settings.Palette; + + for (int j = 0; j < h; j++) + { + int iy = j * 12 / h; + int cy = iy / 3; + for (int i = 0; i < w; i++) + { + int ix = i * 112 / w; + int cx = ix / 7; + if (iy % 3 == 2) + { + cx += 64 * (ix % 7 + 1); + } + int cindex = cy * 16 + cx; + Color col = Color.FromArgb(0xff, pal[cindex * 3], pal[cindex * 3 + 1], pal[cindex * 3 + 2]); + bmp.SetPixel(i, j, col); + } + } + pictureBox1.Image = bmp; + } + + private void buttonPal_Click(object sender, EventArgs e) + { + OpenFileDialog ofd = new OpenFileDialog + { + InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.PathEntries["NES", "Palettes"].Path, "NES"), + Filter = "Palette Files (.pal)|*.PAL|All Files (*.*)|*.*", + RestoreDirectory = true + }; + + var result = ofd.ShowDialog(); + if (result != DialogResult.OK) + { + return; + } + HawkFile palette = new HawkFile(ofd.FileName); + + if (palette != null && palette.Exists) + { + var data = BizHawk.Emulation.Cores.Nintendo.NES.NES.Palettes.Load_FCEUX_Palette(HawkFile.ReadAllBytes(palette.Name)); + Settings.SetNesHawkPalette(data); + SetPaletteImage(); + } + } + + private void buttonOK_Click(object sender, EventArgs e) + { + GlobalWin.MainForm.PutCoreSettings(Settings); + DialogResult = DialogResult.OK; + Close(); + } + + private void buttonPalReset_Click(object sender, EventArgs e) + { + Settings.SetDefaultColors(); + SetPaletteImage(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/config/NES/QuickNesConfig.resx b/BizHawk.Client.EmuHawk/config/NES/QuickNesConfig.resx new file mode 100644 index 0000000000..29dcb1b3a3 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/NES/QuickNesConfig.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs index 6449bdb124..32237bcea2 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs @@ -74,8 +74,15 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES /// /// Context /// rgb32 256x240 packed + /// rgb 24 colors, red first, 512 of them (1536 bytes) [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] - public static extern void qn_blit(IntPtr e, IntPtr dest); + public static extern void qn_blit(IntPtr e, IntPtr dest, byte[] colors); + /// + /// get quicknes's default palette + /// + /// 1536 bytes suitable for qn_blit + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_get_default_colors(); /// /// get number of times joypad was read in most recent frame /// diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs index cdfd1d5fe9..33eb2b77c6 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs @@ -5,6 +5,8 @@ using System.Text; using BizHawk.Emulation.Common; using System.Runtime.InteropServices; using BizHawk.Common; +using System.ComponentModel; +using Newtonsoft.Json; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES { @@ -46,7 +48,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES LibQuickNES.qn_setup_mappers(); } - public QuickNES(CoreComm nextComm, byte[] Rom) + public QuickNES(CoreComm nextComm, byte[] Rom, object Settings) { using (FP.Save()) { @@ -71,6 +73,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES BoardName = mappername; CoreComm.VsyncNum = 39375000; CoreComm.VsyncDen = 655171; + PutSettings(Settings ?? QuickNESSettings.GetDefaults()); } catch { @@ -367,9 +370,102 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES #region settings + public class QuickNESSettings + { + [DefaultValue(8)] + [Description("Set the number of sprites visible per line. 0 hides all sprites, 8 behaves like a normal NES, and 64 is maximum.")] + public int NumSprites + { + get { return _NumSprites; } + set { _NumSprites = Math.Min(64, Math.Max(0, value)); } + } + [JsonIgnore] + private int _NumSprites; + + [DefaultValue(false)] + [Description("Clip the left and right 8 pixels of the display, which sometimes contain nametable garbage.")] + public bool ClipLeftAndRight { get; set; } + + [DefaultValue(false)] + [Description("Clip the top and bottom 8 pixels of the display, which sometimes contain nametable garbage.")] + public bool ClipTopAndBottom { get; set; } + + [Browsable(false)] + public byte[] Palette + { + get { return _Palette; } + set + { + if (value == null) + throw new ArgumentNullException(); + else if (value.Length == 64 * 8 * 3) + _Palette = value; + else + throw new ArgumentOutOfRangeException(); + } + } + [JsonIgnore] + private byte[] _Palette; + + public QuickNESSettings Clone() + { + var ret = (QuickNESSettings)MemberwiseClone(); + ret._Palette = (byte[])_Palette.Clone(); + return ret; + } + public static QuickNESSettings GetDefaults() + { + return new QuickNESSettings + { + NumSprites = 8, + ClipLeftAndRight = false, + ClipTopAndBottom = false, + _Palette = GetDefaultColors() + }; + } + + public void SetNesHawkPalette(int[,] pal) + { + if (pal.GetLength(0) != 64 || pal.GetLength(1) != 3) + throw new ArgumentOutOfRangeException(); + for (int c = 0; c < 64; c++) + { + _Palette[c * 3] = (byte)pal[c, 0]; + _Palette[c * 3 + 1] = (byte)pal[c, 1]; + _Palette[c * 3 + 2] = (byte)pal[c, 2]; + } + for (int c = 64; c < 512; c++) + { + int a = c & 63; + int r = _Palette[a * 3]; + int g = _Palette[a * 3 + 1]; + int b = _Palette[a * 3 + 2]; + BizHawk.Emulation.Cores.Nintendo.NES.NES.Palettes.ApplyDeemphasis(ref r, ref g, ref b, c >> 6); + _Palette[c * 3] = (byte)r; + _Palette[c * 3 + 1] = (byte)g; + _Palette[c * 3 + 2] = (byte)b; + } + } + + static byte[] GetDefaultColors() + { + IntPtr src = LibQuickNES.qn_get_default_colors(); + byte[] ret = new byte[1536]; + Marshal.Copy(src, ret, 0, 1536); + return ret; + } + + public void SetDefaultColors() + { + _Palette = GetDefaultColors(); + } + } + + QuickNESSettings Settings = QuickNESSettings.GetDefaults(); + public object GetSettings() { - return null; + return Settings.Clone(); } public object GetSyncSettings() @@ -379,6 +475,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES public bool PutSettings(object o) { + Settings = (QuickNESSettings)o; + LibQuickNES.qn_set_sprite_limit(Context, Settings.NumSprites); return false; } @@ -428,7 +526,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES void Blit() { - LibQuickNES.qn_blit(Context, VideoOutputH.AddrOfPinnedObject()); + LibQuickNES.qn_blit(Context, VideoOutputH.AddrOfPinnedObject(), Settings.Palette); } public IVideoProvider VideoProvider { get { return this; } } diff --git a/output/dll/libquicknes.dll b/output/dll/libquicknes.dll index 5b8ef0eb80..98f992c1ee 100644 Binary files a/output/dll/libquicknes.dll and b/output/dll/libquicknes.dll differ diff --git a/quicknes/bizinterface.cpp b/quicknes/bizinterface.cpp index 7a18b16f24..37eeed66e9 100644 --- a/quicknes/bizinterface.cpp +++ b/quicknes/bizinterface.cpp @@ -67,7 +67,7 @@ EXPORT const char *qn_emulate_frame(Nes_Emu *e, int pad1, int pad2) return e->emulate_frame(pad1, pad2); } -EXPORT void qn_blit(Nes_Emu *e, char *dest) +EXPORT void qn_blit(Nes_Emu *e, char *dest, const Nes_Emu::rgb_t *colors) { // what is the point of the 256 color bitmap and the dynamic color allocation to it? // why not just render directly to a 512 color bitmap with static palette positions? @@ -77,7 +77,6 @@ EXPORT void qn_blit(Nes_Emu *e, char *dest) const unsigned char *srcend = src + e->image_height * srcpitch; const short *lut = e->frame().palette; - const Nes_Emu::rgb_t *colors = e->nes_colors; for (; src < srcend; src += srcpitch) { @@ -92,6 +91,11 @@ EXPORT void qn_blit(Nes_Emu *e, char *dest) } } +EXPORT const Nes_Emu::rgb_t *qn_get_default_colors() +{ + return Nes_Emu::nes_colors; +} + EXPORT int qn_get_joypad_read_count(Nes_Emu *e) { return e->frame().joypad_read_count;