archive file choosing. also, support archive subdirectories. just because i always wanted to.

This commit is contained in:
zeromus 2011-03-07 02:04:42 +00:00
parent ff48a8c5ef
commit d4f8778608
8 changed files with 460 additions and 34 deletions

View File

@ -478,6 +478,40 @@ namespace BizHawk
return outStream.ToArray();
}
public static string FormatFileSize(long filesize)
{
Decimal size = (Decimal)filesize;
Decimal OneKiloByte = 1024M;
Decimal OneMegaByte = OneKiloByte * 1024M;
Decimal OneGigaByte = OneMegaByte * 1024M;
string suffix;
if (size > 1024*1024*1024)
{
size /= 1024*1024*1024;
suffix = "GB";
}
else if (size > 1024*1024)
{
size /= 1024*1024;
suffix = "MB";
}
else if (size > 1024)
{
size /= 1024;
suffix = "KB";
}
else
{
suffix = " B";
}
string precision = "2";
return String.Format("{0:N" + precision + "}{1}", size, suffix);
}
}

View File

@ -0,0 +1,123 @@
namespace BizHawk.MultiClient
{
partial class ArchiveChooser
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
this.btnCancel = new System.Windows.Forms.Button();
this.btnOK = new System.Windows.Forms.Button();
this.lvMembers = new System.Windows.Forms.ListView();
this.colSize = new System.Windows.Forms.ColumnHeader();
this.colName = new System.Windows.Forms.ColumnHeader();
this.flowLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// flowLayoutPanel1
//
this.flowLayoutPanel1.AutoSize = true;
this.flowLayoutPanel1.Controls.Add(this.btnCancel);
this.flowLayoutPanel1.Controls.Add(this.btnOK);
this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Bottom;
this.flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
this.flowLayoutPanel1.Location = new System.Drawing.Point(0, 276);
this.flowLayoutPanel1.Name = "flowLayoutPanel1";
this.flowLayoutPanel1.Size = new System.Drawing.Size(528, 29);
this.flowLayoutPanel1.TabIndex = 0;
//
// btnCancel
//
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(450, 3);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(75, 23);
this.btnCancel.TabIndex = 1;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// btnOK
//
this.btnOK.Location = new System.Drawing.Point(369, 3);
this.btnOK.Name = "btnOK";
this.btnOK.Size = new System.Drawing.Size(75, 23);
this.btnOK.TabIndex = 0;
this.btnOK.Text = "OK";
this.btnOK.UseVisualStyleBackColor = true;
this.btnOK.Click += new System.EventHandler(this.btnOK_Click);
//
// lvMembers
//
this.lvMembers.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.colSize,
this.colName});
this.lvMembers.Dock = System.Windows.Forms.DockStyle.Fill;
this.lvMembers.FullRowSelect = true;
this.lvMembers.Location = new System.Drawing.Point(0, 0);
this.lvMembers.Name = "lvMembers";
this.lvMembers.Size = new System.Drawing.Size(528, 276);
this.lvMembers.TabIndex = 0;
this.lvMembers.UseCompatibleStateImageBehavior = false;
this.lvMembers.View = System.Windows.Forms.View.Details;
this.lvMembers.ItemActivate += new System.EventHandler(this.lvMembers_ItemActivate);
//
// colSize
//
this.colSize.Text = "Size";
//
// colName
//
this.colName.Text = "Name";
this.colName.Width = 409;
//
// ArchiveChooser
//
this.AcceptButton = this.btnOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(528, 305);
this.Controls.Add(this.lvMembers);
this.Controls.Add(this.flowLayoutPanel1);
this.Name = "ArchiveChooser";
this.Text = "Choose File From Archive";
this.flowLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
private System.Windows.Forms.ListView lvMembers;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnOK;
private System.Windows.Forms.ColumnHeader colSize;
private System.Windows.Forms.ColumnHeader colName;
}
}

View File

@ -0,0 +1,55 @@
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;
namespace BizHawk.MultiClient
{
public partial class ArchiveChooser : Form
{
public ArchiveChooser(HawkFile hawkfile)
{
InitializeComponent();
foreach (var item in hawkfile.ArchiveItems)
{
var lvi = new ListViewItem();
lvi.Tag = item;
lvi.SubItems.Add(new ListViewItem.ListViewSubItem());
lvi.Text = Util.FormatFileSize(item.size);
lvi.SubItems[1].Text = item.name;
lvMembers.Items.Add(lvi);
}
}
public int SelectedMemberIndex
{
get
{
if (lvMembers.SelectedIndices.Count == 0) return -1;
var ai = lvMembers.SelectedItems[0].Tag as HawkFile.ArchiveItem;
return ai.index;
}
}
private void btnOK_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
}
private void lvMembers_ItemActivate(object sender, EventArgs e)
{
DialogResult = DialogResult.OK;
Close();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{DD448B37-BA3F-4544-9754-5406E8094723}</ProjectGuid>
<OutputType>Exe</OutputType>
@ -73,6 +73,12 @@
<Compile Include="AboutBox.Designer.cs">
<DependentUpon>AboutBox.cs</DependentUpon>
</Compile>
<Compile Include="ArchiveChooser.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="ArchiveChooser.Designer.cs">
<DependentUpon>ArchiveChooser.cs</DependentUpon>
</Compile>
<Compile Include="Config.cs" />
<Compile Include="ConfigService.cs" />
<Compile Include="config\InputConfig.cs">
@ -259,6 +265,9 @@
<DependentUpon>AboutBox.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="ArchiveChooser.resx">
<DependentUpon>ArchiveChooser.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="config\InputConfig.resx">
<DependentUpon>InputConfig.cs</DependentUpon>
<SubType>Designer</SubType>

View File

@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace BizHawk.MultiClient
@ -54,19 +56,41 @@ namespace BizHawk.MultiClient
/// </summary>
public string Extension { get { return Path.GetExtension(Name); } }
//---
bool rootExists;
string rootPath;
string memberPath;
Stream rootStream, boundStream;
SevenZip.SevenZipExtractor extractor;
/// <summary>
/// Indicates whether this file is an archive
/// </summary>
public bool IsArchive { get { return extractor != null; } }
public static bool PathExists(string path)
{
using (var hf = new HawkFile(path))
return hf.Exists;
}
public class ArchiveItem
{
public string name;
public long size;
public int index;
}
public IEnumerable<ArchiveItem> ArchiveItems
{
get
{
if (!IsArchive) throw new InvalidOperationException("Cant get archive items from non-archive");
return archiveItems;
}
}
//---
bool rootExists;
string rootPath;
string memberPath;
Stream rootStream, boundStream;
SevenZip.SevenZipExtractor extractor;
List<ArchiveItem> archiveItems;
public HawkFile(string path)
{
string autobind = null;
@ -89,6 +113,8 @@ namespace BizHawk.MultiClient
if (extractor == null)
{
rootStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
//we could autobind here, but i dont want to
//bind it later with the desired extensions.
}
if (autobind != null)
@ -96,7 +122,7 @@ namespace BizHawk.MultiClient
autobind = autobind.ToUpperInvariant();
for (int i = 0; i < extractor.ArchiveFileData.Count; i++)
{
if (extractor.ArchiveFileNames[i].ToUpperInvariant() == autobind)
if (FixArchiveFilename(extractor.ArchiveFileNames[i]).ToUpperInvariant() == autobind)
{
BindArchiveMember(i);
return;
@ -131,13 +157,26 @@ namespace BizHawk.MultiClient
else return string.Format("{0}|{1}", root, member);
}
void BindArchiveMember(int index)
string FixArchiveFilename(string fn)
{
return fn.Replace('\\', '/');
}
/// <summary>
/// binds the selected archive index
/// </summary>
public HawkFile BindArchiveMember(int archiveIndex)
{
if (!rootExists) return this;
if (boundStream != null) throw new InvalidOperationException("stream already bound!");
boundStream = new MemoryStream();
extractor.ExtractFile(index, boundStream);
extractor.ExtractFile(archiveIndex, boundStream);
boundStream.Position = 0;
memberPath = extractor.ArchiveFileNames[index];
memberPath = FixArchiveFilename(extractor.ArchiveFileNames[archiveIndex]); //TODO - maybe go through our own list of names? maybe not, its indexes dont match..
Console.WriteLine("bound " + CanonicalName);
return this;
}
/// <summary>
@ -150,6 +189,9 @@ namespace BizHawk.MultiClient
memberPath = null;
}
/// <summary>
/// causes the root to be bound (in the case of non-archive files
/// </summary>
void BindRoot()
{
boundStream = rootStream;
@ -166,9 +208,23 @@ namespace BizHawk.MultiClient
}
/// <summary>
/// Binds the first item in the archive (or the file itself) if the extension matches one of the supplied templates
/// binds one of the supplied extensions if there is only one match in the archive
/// </summary>
public HawkFile BindSoleItemOf(params string[] extensions)
{
return BindByExtensionCore(false, extensions);
}
/// <summary>
/// Binds the first item in the archive (or the file itself) if the extension matches one of the supplied templates.
/// You probably should not use this. use BindSoleItemOf or the archive chooser instead
/// </summary>
public HawkFile BindFirstOf(params string[] extensions)
{
return BindByExtensionCore(true, extensions);
}
HawkFile BindByExtensionCore(bool first, params string[] extensions)
{
if (!rootExists) return this;
if (boundStream != null) throw new InvalidOperationException("stream already bound!");
@ -177,44 +233,59 @@ namespace BizHawk.MultiClient
{
//open uncompressed file
string extension = Path.GetExtension(rootPath).Substring(1).ToUpperInvariant();
if (extensions.Length==0 || extension.In(extensions))
if (extensions.Length == 0 || extension.In(extensions))
{
BindRoot();
}
return this;
}
for(int i=0;i<extractor.ArchiveFileData.Count;i++)
var candidates = new List<int>();
for (int i = 0; i < extractor.ArchiveFileData.Count; i++)
{
var e = extractor.ArchiveFileData[i];
var extension = Path.GetExtension(e.FileName).Substring(1).ToUpperInvariant();
if (e.IsDirectory) continue;
var extension = Path.GetExtension(e.FileName).ToUpperInvariant();
extension = extension.TrimStart('.');
if (extensions.Length == 0 || extension.In(extensions))
{
BindArchiveMember(i);
return this;
if (first)
{
BindArchiveMember(i);
return this;
}
candidates.Add(i);
}
}
if (candidates.Count == 1)
BindArchiveMember(candidates[0]);
return this;
}
private void AnalyzeArchive(string path)
{
try
void ScanArchive()
{
archiveItems = new List<ArchiveItem>();
for (int i = 0; i < extractor.ArchiveFileData.Count; i++)
{
SevenZip.FileChecker.ThrowExceptions = false;
int offset;
bool isExecutable;
if (SevenZip.FileChecker.CheckSignature(path, out offset, out isExecutable) != SevenZip.InArchiveFormat.None)
{
extractor = new SevenZip.SevenZipExtractor(path);
//now would be a good time to scan the archive..
}
var afd = extractor.ArchiveFileData[i];
var ai = new ArchiveItem();
ai.name = FixArchiveFilename(afd.FileName);
ai.size = (long)afd.Size; //ulong. obnoxious.
ai.index = i;
archiveItems.Add(ai);
}
catch
}
private void AnalyzeArchive(string path)
{
SevenZip.FileChecker.ThrowExceptions = false;
int offset;
bool isExecutable;
if (SevenZip.FileChecker.CheckSignature(path, out offset, out isExecutable) != SevenZip.InArchiveFormat.None)
{
//must not be an archive. is there a better way to determine this? the exceptions are as annoying as hell
extractor = new SevenZip.SevenZipExtractor(path);
ScanArchive();
}
}

View File

@ -393,8 +393,24 @@ namespace BizHawk.MultiClient
{
using (var file = new HawkFile(path))
{
//if the provided file doesnt even exist, give up!
if (!file.RootExists) return false;
//try binding normal rom extensions first
if (!file.IsBound)
file.BindSoleItemOf("SMS", "PCE", "SGX", "GG", "SG", "BIN", "SMD", "GB", "NES");
//if we have an archive and need to bind something, then pop the dialog
if (file.IsArchive && !file.IsBound)
{
var ac = new ArchiveChooser(file);
if (ac.ShowDialog(this) == DialogResult.OK)
{
file.BindArchiveMember(ac.SelectedMemberIndex);
}
else return false;
}
CloseGame();
var game = new RomGame(file);

View File

@ -18,8 +18,6 @@ namespace BizHawk.MultiClient
public RomGame(HawkFile file, string patch)
{
if(!file.IsBound)
file.BindFirstOf("SMS", "PCE", "SGX", "GG", "SG", "BIN", "SMD", "GB", "NES");
if (!file.Exists)
throw new Exception("The file needs to exist, yo.");