2013-07-31 15:54:08 +00:00
using System ;
2013-08-03 19:34:51 +00:00
using System.Collections ;
2013-07-31 15:54:08 +00:00
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 ;
2013-10-25 00:57:23 +00:00
using BizHawk.Client.Common ;
2013-11-04 01:06:36 +00:00
using BizHawk.Emulation.Common ;
2013-10-25 00:57:23 +00:00
2013-08-02 03:43:58 +00:00
//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
2013-12-19 16:59:23 +00:00
//IDEA: show current path in tooltip (esp. for custom resolved)
//IDEA: prepop set customization to dir of current custom
2013-08-02 03:43:58 +00:00
2013-08-10 01:17:06 +00:00
//TODO - display some kind if [!] if you have a user-specified file which is known but defined as incompatible by the firmware DB
2013-11-03 03:54:37 +00:00
namespace BizHawk.Client.EmuHawk
2013-07-31 15:54:08 +00:00
{
2013-08-02 03:43:58 +00:00
public partial class FirmwaresConfig : Form
{
2013-08-10 01:17:06 +00:00
//friendlier names than the system Ids
2013-08-11 19:05:05 +00:00
public static readonly Dictionary < string , string > SystemGroupNames = new Dictionary < string , string > ( )
2013-08-02 03:43:58 +00:00
{
{ "NES" , "NES" } ,
{ "SNES" , "SNES" } ,
{ "PCECD" , "PCE-CD" } ,
{ "SAT" , "Saturn" } ,
{ "A78" , "Atari 7800" } ,
{ "Coleco" , "Colecovision" } ,
{ "GBA" , "GBA" } ,
{ "TI83" , "TI-83" } ,
{ "INTV" , "Intellivision" } ,
{ "C64" , "C64" } ,
2013-12-16 19:00:05 +00:00
{ "GEN" , "Genesis" } ,
2014-03-04 02:33:39 +00:00
{ "SMS" , "Sega Master System" } ,
2014-06-21 00:48:31 +00:00
{ "PSX" , "Sony PlayStation" }
2013-08-02 03:43:58 +00:00
} ;
2013-08-11 19:05:05 +00:00
public string TargetSystem = null ;
2013-08-10 01:17:06 +00:00
2013-08-02 03:51:33 +00:00
private const int idUnsure = 0 ;
private const int idMissing = 1 ;
private const int idOk = 2 ;
2013-08-10 01:17:06 +00:00
Font fixedFont , boldFont , boldFixedFont ;
2013-08-03 19:34:51 +00:00
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 ;
2013-08-10 01:17:06 +00:00
return sign * string . Compare ( lva . SubItems [ column ] . Text , lvb . SubItems [ column ] . Text ) ;
2013-08-03 19:34:51 +00:00
}
}
2013-08-10 01:17:06 +00:00
string currSelectorDir ;
2013-08-03 19:34:51 +00:00
ListViewSorter listviewSorter ;
2013-08-02 03:43:58 +00:00
public FirmwaresConfig ( )
{
InitializeComponent ( ) ;
2013-08-02 03:51:33 +00:00
//prep imagelist for listview with 3 item states for {idUnsure, idMissing, idOk}
2014-06-29 02:28:48 +00:00
imageList1 . Images . AddRange ( new [ ] { Properties . Resources . RetroQuestion , Properties . Resources . ExclamationRed , Properties . Resources . GreenCheck } ) ;
2013-08-03 19:34:51 +00:00
listviewSorter = new ListViewSorter ( this , - 1 ) ;
2013-08-02 03:43:58 +00:00
}
2013-08-10 01:17:06 +00:00
2013-08-11 15:48:47 +00:00
//makes sure that the specified SystemId is selected in the list (and that all the firmwares for it are visible)
2013-08-11 19:05:05 +00:00
private void WarpToSystemId ( string sysid )
2013-08-11 15:48:47 +00:00
{
bool selectedFirst = false ;
foreach ( ListViewItem lvi in lvFirmwares . Items )
{
if ( lvi . SubItems [ 1 ] . Text = = sysid )
{
if ( ! selectedFirst ) lvi . Selected = true ;
lvi . EnsureVisible ( ) ;
selectedFirst = true ;
}
}
}
2013-08-02 03:43:58 +00:00
private void FirmwaresConfig_Load ( object sender , EventArgs e )
{
2013-08-02 03:51:33 +00:00
//we'll use this font for displaying the hash, so they dont look all jagged in a long list
2013-08-02 03:43:58 +00:00
fixedFont = new Font ( new FontFamily ( "Courier New" ) , 8 ) ;
2013-08-10 01:17:06 +00:00
boldFont = new Font ( lvFirmwares . Font , FontStyle . Bold ) ;
boldFixedFont = new Font ( fixedFont , FontStyle . Bold ) ;
2013-08-02 03:43:58 +00:00
2013-08-02 03:51:33 +00:00
//populate listview from firmware DB
2013-08-02 03:43:58 +00:00
var groups = new Dictionary < string , ListViewGroup > ( ) ;
2013-08-10 01:17:06 +00:00
foreach ( var fr in FirmwareDatabase . FirmwareRecords )
2013-08-02 03:43:58 +00:00
{
var lvi = new ListViewItem ( ) ;
2013-08-10 01:17:06 +00:00
lvi . Tag = fr ;
2013-08-02 03:43:58 +00:00
lvi . UseItemStyleForSubItems = false ;
lvi . ImageIndex = idUnsure ;
2013-08-10 01:17:06 +00:00
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
2013-08-02 03:43:58 +00:00
lvFirmwares . Items . Add ( lvi ) ;
2013-08-02 03:51:33 +00:00
//build the groups in the listview as we go:
2013-08-10 01:17:06 +00:00
if ( ! groups . ContainsKey ( fr . systemId ) )
2013-08-02 03:43:58 +00:00
{
2013-12-16 19:00:05 +00:00
string name ;
if ( ! SystemGroupNames . TryGetValue ( fr . systemId , out name ) )
name = "FIX ME (FirmwaresConfig.cs)" ;
lvFirmwares . Groups . Add ( fr . systemId , name ) ;
2013-08-02 03:43:58 +00:00
var lvg = lvFirmwares . Groups [ lvFirmwares . Groups . Count - 1 ] ;
2013-08-10 01:17:06 +00:00
groups [ fr . systemId ] = lvg ;
2013-08-02 03:43:58 +00:00
}
2013-08-10 01:17:06 +00:00
lvi . Group = groups [ fr . systemId ] ;
2013-08-02 03:43:58 +00:00
}
2013-08-10 01:17:06 +00:00
//now that we have some items in the listview, we can size some columns to sensible widths
2013-08-02 03:51:33 +00:00
lvFirmwares . AutoResizeColumn ( 1 , ColumnHeaderAutoResizeStyle . ColumnContent ) ;
2013-08-02 03:43:58 +00:00
lvFirmwares . AutoResizeColumn ( 2 , ColumnHeaderAutoResizeStyle . ColumnContent ) ;
2013-08-10 01:28:51 +00:00
lvFirmwares . AutoResizeColumn ( 3 , ColumnHeaderAutoResizeStyle . ColumnContent ) ;
2013-08-02 03:43:58 +00:00
2013-08-11 19:05:05 +00:00
if ( TargetSystem ! = null )
{
WarpToSystemId ( TargetSystem ) ;
}
2013-12-19 16:59:23 +00:00
RefreshBasePath ( ) ;
2013-08-02 03:43:58 +00:00
}
private void FirmwaresConfig_FormClosed ( object sender , FormClosedEventArgs e )
{
fixedFont . Dispose ( ) ;
2013-08-10 01:17:06 +00:00
boldFont . Dispose ( ) ;
boldFixedFont . Dispose ( ) ;
2013-08-02 03:43:58 +00:00
}
2013-08-02 03:51:33 +00:00
private void tbbGroup_Click ( object sender , EventArgs e )
{
//toggle the grouping state
lvFirmwares . ShowGroups = ! lvFirmwares . ShowGroups ;
}
private void lvFirmwares_ColumnClick ( object sender , ColumnClickEventArgs e )
2013-08-02 03:43:58 +00:00
{
2013-08-03 19:34:51 +00:00
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 ( ) ;
2013-08-02 03:43:58 +00:00
}
2013-08-02 03:51:33 +00:00
private void tbbScan_Click ( object sender , EventArgs e )
{
//user-initiated scan
DoScan ( ) ;
}
2013-08-02 03:43:58 +00:00
2013-11-01 23:17:30 +00:00
FirmwareManager Manager { get { return Global . FirmwareManager ; } }
2013-08-02 03:43:58 +00:00
private void DoScan ( )
{
2013-08-10 01:17:06 +00:00
lvFirmwares . BeginUpdate ( ) ;
Manager . DoScanAndResolve ( ) ;
//for each type of firmware, try resolving and record the result
foreach ( ListViewItem lvi in lvFirmwares . Items )
2013-08-02 03:43:58 +00:00
{
2013-08-10 01:17:06 +00:00
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 )
2013-08-02 03:43:58 +00:00
{
2013-08-10 01:17:06 +00:00
lvi . ImageIndex = idMissing ;
2013-08-02 03:43:58 +00:00
}
2013-08-10 01:17:06 +00:00
else
{
//lazy substring extraction. really should do a better job
2013-12-13 05:20:50 +00:00
var basePath = PathManager . MakeAbsolutePath ( Global . Config . PathEntries . FirmwaresPathFragment , null ) + Path . DirectorySeparatorChar ;
2013-08-10 01:17:06 +00:00
var path = ri . FilePath . Replace ( basePath , "" ) ;
2013-08-02 03:43:58 +00:00
2013-08-10 01:17:06 +00:00
//bolden the item if the user has specified a path for it
bool bolden = ri . UserSpecified ;
2013-08-02 03:51:33 +00:00
2013-08-10 01:17:06 +00:00
//set columns based on whether it was a known file
if ( ri . KnownFirmwareFile = = null )
2013-08-02 03:43:58 +00:00
{
2013-08-10 01:17:06 +00:00
lvi . ImageIndex = idUnsure ;
lvi . SubItems [ 4 ] . Text = "-custom-" ;
}
else
{
lvi . ImageIndex = idOk ;
lvi . SubItems [ 4 ] . Text = ri . KnownFirmwareFile . descr ;
2013-08-02 03:43:58 +00:00
}
2013-08-10 01:17:06 +00:00
//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 ;
}
2013-08-02 03:43:58 +00:00
2013-08-10 01:17:06 +00:00
//if the user specified a file but its missing, mark it as such
if ( ri . Missing )
lvi . ImageIndex = idMissing ;
2013-08-02 03:43:58 +00:00
2013-08-10 01:17:06 +00:00
//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 ;
2013-08-02 03:43:58 +00:00
2013-08-10 01:17:06 +00:00
lvi . SubItems [ 5 ] . Text = path ;
if ( ri . Hash ! = null ) lvi . SubItems [ 6 ] . Text = "sha1:" + ri . Hash ;
else lvi . SubItems [ 6 ] . Text = "" ;
}
2013-08-02 03:43:58 +00:00
}
2013-08-10 01:17:06 +00:00
lvFirmwares . EndUpdate ( ) ;
2013-08-02 03:43:58 +00:00
}
private void tbbOrganize_Click ( object sender , EventArgs e )
{
2014-06-29 02:28:48 +00:00
if ( 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 ) = = DialogResult . Cancel )
2013-08-10 01:17:06 +00:00
return ;
Manager . DoScanAndResolve ( ) ;
2013-08-02 03:43:58 +00:00
2013-08-10 01:17:06 +00:00
foreach ( var fr in FirmwareDatabase . FirmwareRecords )
2013-08-02 03:43:58 +00:00
{
2013-08-10 01:17:06 +00:00
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 ;
2013-08-02 03:43:58 +00:00
try
{
2013-08-10 01:17:06 +00:00
File . Move ( fpSource , fpTarget ) ;
2013-08-02 03:43:58 +00:00
}
catch
{
2013-08-10 01:17:06 +00:00
//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.
2013-08-02 03:43:58 +00:00
}
}
DoScan ( ) ;
}
2013-08-03 19:34:51 +00:00
private void lvFirmwares_KeyDown ( object sender , KeyEventArgs e )
{
if ( e . KeyCode = = Keys . C & & e . Control & & ! e . Alt & & ! e . Shift )
{
2013-10-25 00:57:23 +00:00
PerformListCopy ( ) ;
2013-08-03 19:34:51 +00:00
}
}
2013-08-02 14:23:59 +00:00
2013-10-25 00:57:23 +00:00
void PerformListCopy ( )
{
var str = lvFirmwares . CopyItemsAsText ( ) ;
if ( str . Length > 0 ) Clipboard . SetDataObject ( str ) ;
}
2013-08-10 01:17:06 +00:00
private void lvFirmwares_MouseClick ( object sender , MouseEventArgs e )
2013-08-03 19:34:51 +00:00
{
2014-06-29 02:28:48 +00:00
if ( e . Button = = MouseButtons . Right & & lvFirmwares . GetItemAt ( e . X , e . Y ) ! = null )
2013-08-10 01:17:06 +00:00
lvFirmwaresContextMenuStrip . Show ( lvFirmwares , e . Location ) ;
}
private void tsmiSetCustomization_Click ( object sender , EventArgs e )
{
using ( var ofd = new OpenFileDialog ( ) )
2013-08-03 19:34:51 +00:00
{
2013-08-10 01:17:06 +00:00
ofd . InitialDirectory = currSelectorDir ;
ofd . RestoreDirectory = true ;
2014-06-29 02:28:48 +00:00
if ( ofd . ShowDialog ( ) = = DialogResult . OK )
2013-08-03 19:34:51 +00:00
{
2013-08-10 01:17:06 +00:00
//remember the location we selected this firmware from, maybe there are others
currSelectorDir = Path . GetDirectoryName ( ofd . FileName ) ;
2013-10-25 00:57:23 +00:00
//for each selected item, set the user choice (even though multiple selection for this operation is no longer allowed)
2013-08-10 01:17:06 +00:00
foreach ( ListViewItem lvi in lvFirmwares . SelectedItems )
{
var fr = lvi . Tag as FirmwareDatabase . FirmwareRecord ;
Global . Config . FirmwareUserSpecifications [ fr . ConfigKey ] = ofd . FileName ;
}
2013-08-03 19:34:51 +00:00
2013-08-10 01:17:06 +00:00
DoScan ( ) ;
}
2013-08-03 19:34:51 +00:00
}
2013-08-10 01:17:06 +00:00
}
2013-08-03 19:34:51 +00:00
2013-08-10 01:17:06 +00:00
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 ) ;
}
2013-08-03 19:34:51 +00:00
2013-08-10 01:17:06 +00:00
DoScan ( ) ;
2013-08-03 19:34:51 +00:00
}
2013-08-02 03:43:58 +00:00
2013-10-25 00:57:23 +00:00
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 ( ) ) ;
2014-06-21 06:40:07 +00:00
olvi . SubItems . Add ( new ListViewItem . ListViewSubItem ( ) ) ;
2013-10-25 00:57:23 +00:00
var ff = FirmwareDatabase . FirmwareFilesByHash [ o . hash ] ;
2014-06-21 06:40:07 +00:00
if ( o . status = = FirmwareDatabase . FirmwareOptionStatus . Ideal )
olvi . ImageIndex = FirmwaresConfigInfo . idIdeal ;
if ( o . status = = FirmwareDatabase . FirmwareOptionStatus . Acceptable )
olvi . ImageIndex = FirmwaresConfigInfo . idAcceptable ;
if ( o . status = = FirmwareDatabase . FirmwareOptionStatus . Unacceptable )
olvi . ImageIndex = FirmwaresConfigInfo . idUnacceptable ;
if ( o . status = = FirmwareDatabase . FirmwareOptionStatus . Bad )
olvi . ImageIndex = FirmwaresConfigInfo . idBad ;
2013-10-25 00:57:23 +00:00
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?
2014-06-21 06:40:07 +00:00
olvi . SubItems [ 3 ] . Text = ff . info ;
olvi . SubItems [ 3 ] . Font = this . Font ; //why doesnt this work?
2013-10-25 00:57:23 +00:00
fciDialog . lvOptions . Items . Add ( olvi ) ;
}
fciDialog . lvOptions . AutoResizeColumn ( 0 , ColumnHeaderAutoResizeStyle . ColumnContent ) ;
fciDialog . lvOptions . AutoResizeColumn ( 1 , ColumnHeaderAutoResizeStyle . ColumnContent ) ;
fciDialog . lvOptions . AutoResizeColumn ( 2 , ColumnHeaderAutoResizeStyle . ColumnContent ) ;
2014-06-21 06:40:07 +00:00
fciDialog . lvOptions . AutoResizeColumn ( 3 , ColumnHeaderAutoResizeStyle . ColumnContent ) ;
2013-10-25 00:57:23 +00:00
2014-06-21 06:40:07 +00:00
fciDialog . ShowDialog ( this ) ;
2013-10-25 00:57:23 +00:00
}
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 ( ) ;
}
2013-12-19 16:59:23 +00:00
private void linkBasePath_LinkClicked ( object sender , LinkLabelLinkClickedEventArgs e )
{
2013-12-19 22:53:06 +00:00
if ( Owner is PathConfig )
{
MessageBox . Show ( "C-C-C-Combo Breaker!" , "Nice try, but" ) ;
return ;
}
2013-12-19 16:59:23 +00:00
new PathConfig ( ) . ShowDialog ( this ) ;
RefreshBasePath ( ) ;
}
void RefreshBasePath ( )
{
string oldBasePath = currSelectorDir ;
linkBasePath . Text = currSelectorDir = PathManager . MakeAbsolutePath ( Global . Config . PathEntries . FirmwaresPathFragment , null ) ;
if ( oldBasePath ! = currSelectorDir )
DoScan ( ) ;
}
2013-08-10 01:17:06 +00:00
} //class FirmwaresConfig
2013-07-31 15:54:08 +00:00
}