BizHawk/BizHawk.Client.EmuHawk/config/FirmwaresConfig.cs

409 lines
13 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Emulation.Common;
//notes: eventually, we intend to have a "firmware acquisition interface" exposed to the emulator cores.
//it will be implemented by the multiclient, and use firmware keys to fetch the firmware content.
//however, for now, the cores are using strings from the config class. so we have the `configMember` which is
//used by reflection to set the configuration for firmwares which were found
//TODO - we may eventually need to add a progress dialog for this. we should have one for other reasons.
//I started making one in Bizhawk.Util as QuickProgressPopup but ran out of time
//IDEA: show current path in tooltip (esp. for custom resolved)
//IDEA: prepop set customization to dir of current custom
//TODO - display some kind if [!] if you have a user-specified file which is known but defined as incompatible by the firmware DB
namespace BizHawk.Client.EmuHawk
{
public partial class FirmwaresConfig : Form
{
//friendlier names than the system Ids
public static readonly Dictionary<string, string> SystemGroupNames = new Dictionary<string, string>()
{
{ "NES", "NES" },
{ "SNES", "SNES" },
{ "PCECD", "PCE-CD" },
{ "SAT", "Saturn" },
{ "A78", "Atari 7800" },
{ "Coleco", "Colecovision" },
{ "GBA", "GBA" },
{ "TI83", "TI-83" },
{ "INTV", "Intellivision" },
{ "C64", "C64" },
{ "GEN", "Genesis" },
{ "SMS", "Sega Master System" },
};
public string TargetSystem = null;
private const int idUnsure = 0;
private const int idMissing = 1;
private const int idOk = 2;
Font fixedFont, boldFont, boldFixedFont;
class ListViewSorter : IComparer
{
public FirmwaresConfig dialog;
public int column;
public int sign;
public ListViewSorter(FirmwaresConfig dialog, int column)
{
this.dialog = dialog;
this.column = column;
}
public int Compare(object a, object b)
{
var lva = (ListViewItem)a;
var lvb = (ListViewItem)b;
return sign * string.Compare(lva.SubItems[column].Text, lvb.SubItems[column].Text);
}
}
string currSelectorDir;
ListViewSorter listviewSorter;
public FirmwaresConfig()
{
InitializeComponent();
//prep imagelist for listview with 3 item states for {idUnsure, idMissing, idOk}
imageList1.Images.AddRange(new[] { EmuHawk.Properties.Resources.RetroQuestion, EmuHawk.Properties.Resources.ExclamationRed, EmuHawk.Properties.Resources.GreenCheck });
listviewSorter = new ListViewSorter(this, -1);
}
//makes sure that the specified SystemId is selected in the list (and that all the firmwares for it are visible)
private void WarpToSystemId(string sysid)
{
bool selectedFirst = false;
foreach (ListViewItem lvi in lvFirmwares.Items)
{
if (lvi.SubItems[1].Text == sysid)
{
if(!selectedFirst) lvi.Selected = true;
lvi.EnsureVisible();
selectedFirst = true;
}
}
}
private void FirmwaresConfig_Load(object sender, EventArgs e)
{
//we'll use this font for displaying the hash, so they dont look all jagged in a long list
fixedFont = new Font(new FontFamily("Courier New"), 8);
boldFont = new Font(lvFirmwares.Font, FontStyle.Bold);
boldFixedFont = new Font(fixedFont, FontStyle.Bold);
//populate listview from firmware DB
var groups = new Dictionary<string, ListViewGroup>();
foreach (var fr in FirmwareDatabase.FirmwareRecords)
{
var lvi = new ListViewItem();
lvi.Tag = fr;
lvi.UseItemStyleForSubItems = false;
lvi.ImageIndex = idUnsure;
lvi.SubItems.Add(fr.systemId);
lvi.SubItems.Add(fr.firmwareId);
lvi.SubItems.Add(fr.descr);
lvi.SubItems.Add(""); //resolved with
lvi.SubItems.Add(""); //location
lvi.SubItems.Add(""); //hash
lvi.SubItems[6].Font = fixedFont; //would be used for hash
lvFirmwares.Items.Add(lvi);
//build the groups in the listview as we go:
if (!groups.ContainsKey(fr.systemId))
{
string name;
if (!SystemGroupNames.TryGetValue(fr.systemId, out name))
name = "FIX ME (FirmwaresConfig.cs)";
lvFirmwares.Groups.Add(fr.systemId, name);
var lvg = lvFirmwares.Groups[lvFirmwares.Groups.Count - 1];
groups[fr.systemId] = lvg;
}
lvi.Group = groups[fr.systemId];
}
//now that we have some items in the listview, we can size some columns to sensible widths
lvFirmwares.AutoResizeColumn(1, ColumnHeaderAutoResizeStyle.ColumnContent);
lvFirmwares.AutoResizeColumn(2, ColumnHeaderAutoResizeStyle.ColumnContent);
lvFirmwares.AutoResizeColumn(3, ColumnHeaderAutoResizeStyle.ColumnContent);
if (TargetSystem != null)
{
WarpToSystemId(TargetSystem);
}
RefreshBasePath();
}
private void FirmwaresConfig_FormClosed(object sender, FormClosedEventArgs e)
{
fixedFont.Dispose();
boldFont.Dispose();
boldFixedFont.Dispose();
}
private void tbbGroup_Click(object sender, EventArgs e)
{
//toggle the grouping state
lvFirmwares.ShowGroups = !lvFirmwares.ShowGroups;
}
private void lvFirmwares_ColumnClick(object sender, ColumnClickEventArgs e)
{
if (listviewSorter.column != e.Column)
{
listviewSorter.column = e.Column;
listviewSorter.sign = 1;
}
else listviewSorter.sign *= -1;
lvFirmwares.ListViewItemSorter = listviewSorter;
lvFirmwares.SetSortIcon(e.Column, listviewSorter.sign == 1 ? SortOrder.Descending : SortOrder.Ascending);
lvFirmwares.Sort();
}
private void tbbScan_Click(object sender, EventArgs e)
{
//user-initiated scan
DoScan();
}
FirmwareManager Manager { get { return Global.FirmwareManager; } }
private void DoScan()
{
lvFirmwares.BeginUpdate();
Manager.DoScanAndResolve();
//for each type of firmware, try resolving and record the result
foreach (ListViewItem lvi in lvFirmwares.Items)
{
var fr = lvi.Tag as FirmwareDatabase.FirmwareRecord;
var ri = Manager.Resolve(fr);
for(int i=4;i<=6;i++)
lvi.SubItems[i].Text = "";
if (ri == null)
{
lvi.ImageIndex = idMissing;
}
else
{
//lazy substring extraction. really should do a better job
var basePath = PathManager.MakeAbsolutePath(Global.Config.PathEntries.FirmwaresPathFragment, null) + Path.DirectorySeparatorChar;
var path = ri.FilePath.Replace(basePath, "");
//bolden the item if the user has specified a path for it
bool bolden = ri.UserSpecified;
//set columns based on whether it was a known file
if (ri.KnownFirmwareFile == null)
{
lvi.ImageIndex = idUnsure;
lvi.SubItems[4].Text = "-custom-";
}
else
{
lvi.ImageIndex = idOk;
lvi.SubItems[4].Text = ri.KnownFirmwareFile.descr;
}
//bolden the item if necessary
if (bolden)
{
foreach (ListViewItem.ListViewSubItem lvsi in lvi.SubItems) lvsi.Font = boldFont;
lvi.SubItems[6].Font = boldFixedFont;
}
else
{
foreach (ListViewItem.ListViewSubItem lvsi in lvi.SubItems) lvsi.Font = lvFirmwares.Font;
lvi.SubItems[6].Font = fixedFont;
}
//if the user specified a file but its missing, mark it as such
if(ri.Missing)
lvi.ImageIndex = idMissing;
//if the user specified a known firmware file but its for some other firmware, it was probably a mistake. mark it as suspicious
if (ri.KnownMismatching)
lvi.ImageIndex = idUnsure;
lvi.SubItems[5].Text = path;
if (ri.Hash != null) lvi.SubItems[6].Text = "sha1:" + ri.Hash;
else lvi.SubItems[6].Text = "";
}
}
lvFirmwares.EndUpdate();
}
private void tbbOrganize_Click(object sender, EventArgs e)
{
if (System.Windows.Forms.MessageBox.Show(this, "This is going to move/rename every automatically-selected firmware file under your configured firmwares directory to match our recommended organizational scheme (which is not super great right now). Proceed?", "Firmwares Organization Confirm", MessageBoxButtons.OKCancel) == System.Windows.Forms.DialogResult.Cancel)
return;
Manager.DoScanAndResolve();
foreach (var fr in FirmwareDatabase.FirmwareRecords)
{
var ri = Manager.Resolve(fr);
if (ri.KnownFirmwareFile == null) continue;
if (ri.UserSpecified) continue;
string fpTarget = PathManager.StandardFirmwareName(ri.KnownFirmwareFile.recommendedName);
string fpSource = ri.FilePath;
try
{
File.Move(fpSource, fpTarget);
}
catch
{
//sometimes moves fail. especially in newer versions of windows with explorers more fragile than your great-grandma.
//I am embarassed that I know that. about windows, not your great-grandma.
}
}
DoScan();
}
private void lvFirmwares_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.C && e.Control && !e.Alt && !e.Shift)
{
PerformListCopy();
}
}
void PerformListCopy()
{
var str = lvFirmwares.CopyItemsAsText();
if (str.Length > 0) Clipboard.SetDataObject(str);
}
private void lvFirmwares_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right && lvFirmwares.GetItemAt(e.X, e.Y) != null)
lvFirmwaresContextMenuStrip.Show(lvFirmwares, e.Location);
}
private void tsmiSetCustomization_Click(object sender, EventArgs e)
{
using (var ofd = new OpenFileDialog())
{
ofd.InitialDirectory = currSelectorDir;
ofd.RestoreDirectory = true;
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
//remember the location we selected this firmware from, maybe there are others
currSelectorDir = Path.GetDirectoryName(ofd.FileName);
//for each selected item, set the user choice (even though multiple selection for this operation is no longer allowed)
foreach (ListViewItem lvi in lvFirmwares.SelectedItems)
{
var fr = lvi.Tag as FirmwareDatabase.FirmwareRecord;
Global.Config.FirmwareUserSpecifications[fr.ConfigKey] = ofd.FileName;
}
DoScan();
}
}
}
private void tsmiClearCustomization_Click(object sender, EventArgs e)
{
//for each selected item, clear the user choice
foreach (ListViewItem lvi in lvFirmwares.SelectedItems)
{
var fr = lvi.Tag as FirmwareDatabase.FirmwareRecord;
Global.Config.FirmwareUserSpecifications.Remove(fr.ConfigKey);
}
DoScan();
}
private void tsmiInfo_Click(object sender, EventArgs e)
{
var lvi = lvFirmwares.SelectedItems[0];
var fr = lvi.Tag as FirmwareDatabase.FirmwareRecord;
//get all options for this firmware (in order)
var options =
from fo in FirmwareDatabase.FirmwareOptions
where fo.systemId == fr.systemId && fo.firmwareId == fr.firmwareId
select fo;
FirmwaresConfigInfo fciDialog = new FirmwaresConfigInfo();
fciDialog.lblFirmware.Text = string.Format("{0} : {1} ({2})", fr.systemId, fr.firmwareId, fr.descr);
foreach (var o in options)
{
ListViewItem olvi = new ListViewItem();
olvi.SubItems.Add(new ListViewItem.ListViewSubItem());
olvi.SubItems.Add(new ListViewItem.ListViewSubItem());
var ff = FirmwareDatabase.FirmwareFilesByHash[o.hash];
olvi.SubItems[0].Text = o.hash;
olvi.SubItems[0].Font = fixedFont;
olvi.SubItems[1].Text = ff.recommendedName;
olvi.SubItems[1].Font = this.Font; //why doesnt this work?
olvi.SubItems[2].Text = ff.descr;
olvi.SubItems[2].Font = this.Font; //why doesnt this work?
fciDialog.lvOptions.Items.Add(olvi);
}
fciDialog.lvOptions.AutoResizeColumn(0, ColumnHeaderAutoResizeStyle.ColumnContent);
fciDialog.lvOptions.AutoResizeColumn(1, ColumnHeaderAutoResizeStyle.ColumnContent);
fciDialog.lvOptions.AutoResizeColumn(2, ColumnHeaderAutoResizeStyle.ColumnContent);
fciDialog.ShowDialog();
}
private void lvFirmwaresContextMenuStrip_Opening(object sender, CancelEventArgs e)
{
//hide menu items that arent appropriate for multi-select
tsmiSetCustomization.Visible = lvFirmwares.SelectedItems.Count == 1;
tsmiInfo.Visible = lvFirmwares.SelectedItems.Count == 1;
}
private void tsmiCopy_Click(object sender, EventArgs e)
{
PerformListCopy();
}
private void linkBasePath_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
if (Owner is PathConfig)
{
MessageBox.Show("C-C-C-Combo Breaker!", "Nice try, but");
return;
}
new PathConfig().ShowDialog(this);
RefreshBasePath();
}
void RefreshBasePath()
{
string oldBasePath = currSelectorDir;
linkBasePath.Text = currSelectorDir = PathManager.MakeAbsolutePath(Global.Config.PathEntries.FirmwaresPathFragment, null);
if (oldBasePath != currSelectorDir)
DoScan();
}
} //class FirmwaresConfig
}