From d4f8778608003d274ee2459ae571c82134b36e46 Mon Sep 17 00:00:00 2001 From: zeromus Date: Mon, 7 Mar 2011 02:04:42 +0000 Subject: [PATCH] archive file choosing. also, support archive subdirectories. just because i always wanted to. --- BizHawk.Emulation/Util.cs | 34 +++++ .../ArchiveChooser.Designer.cs | 123 ++++++++++++++++ BizHawk.MultiClient/ArchiveChooser.cs | 55 ++++++++ BizHawk.MultiClient/ArchiveChooser.resx | 120 ++++++++++++++++ .../BizHawk.MultiClient.csproj | 11 +- BizHawk.MultiClient/HawkFile.cs | 133 ++++++++++++++---- BizHawk.MultiClient/MainForm.cs | 16 +++ BizHawk.MultiClient/RomGame.cs | 2 - 8 files changed, 460 insertions(+), 34 deletions(-) create mode 100644 BizHawk.MultiClient/ArchiveChooser.Designer.cs create mode 100644 BizHawk.MultiClient/ArchiveChooser.cs create mode 100644 BizHawk.MultiClient/ArchiveChooser.resx diff --git a/BizHawk.Emulation/Util.cs b/BizHawk.Emulation/Util.cs index d5fba37bd3..e7a5da6615 100644 --- a/BizHawk.Emulation/Util.cs +++ b/BizHawk.Emulation/Util.cs @@ -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); + } + } diff --git a/BizHawk.MultiClient/ArchiveChooser.Designer.cs b/BizHawk.MultiClient/ArchiveChooser.Designer.cs new file mode 100644 index 0000000000..4387082b29 --- /dev/null +++ b/BizHawk.MultiClient/ArchiveChooser.Designer.cs @@ -0,0 +1,123 @@ +namespace BizHawk.MultiClient +{ + partial class ArchiveChooser + { + /// + /// 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.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; + } +} \ No newline at end of file diff --git a/BizHawk.MultiClient/ArchiveChooser.cs b/BizHawk.MultiClient/ArchiveChooser.cs new file mode 100644 index 0000000000..64ba9f93ba --- /dev/null +++ b/BizHawk.MultiClient/ArchiveChooser.cs @@ -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(); + } + } +} diff --git a/BizHawk.MultiClient/ArchiveChooser.resx b/BizHawk.MultiClient/ArchiveChooser.resx new file mode 100644 index 0000000000..ff31a6db56 --- /dev/null +++ b/BizHawk.MultiClient/ArchiveChooser.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BizHawk.MultiClient/BizHawk.MultiClient.csproj b/BizHawk.MultiClient/BizHawk.MultiClient.csproj index 5dae115f79..835f4bfe26 100644 --- a/BizHawk.MultiClient/BizHawk.MultiClient.csproj +++ b/BizHawk.MultiClient/BizHawk.MultiClient.csproj @@ -3,7 +3,7 @@ Debug AnyCPU - 9.0.21022 + 9.0.30729 2.0 {DD448B37-BA3F-4544-9754-5406E8094723} Exe @@ -73,6 +73,12 @@ AboutBox.cs + + Form + + + ArchiveChooser.cs + @@ -259,6 +265,9 @@ AboutBox.cs Designer + + ArchiveChooser.cs + InputConfig.cs Designer diff --git a/BizHawk.MultiClient/HawkFile.cs b/BizHawk.MultiClient/HawkFile.cs index fa80f19e32..33ea862ba7 100644 --- a/BizHawk.MultiClient/HawkFile.cs +++ b/BizHawk.MultiClient/HawkFile.cs @@ -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 /// public string Extension { get { return Path.GetExtension(Name); } } - //--- - bool rootExists; - string rootPath; - string memberPath; - Stream rootStream, boundStream; - SevenZip.SevenZipExtractor extractor; + /// + /// Indicates whether this file is an archive + /// + 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 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 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('\\', '/'); + } + + /// + /// binds the selected archive index + /// + 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; } /// @@ -150,6 +189,9 @@ namespace BizHawk.MultiClient memberPath = null; } + /// + /// causes the root to be bound (in the case of non-archive files + /// void BindRoot() { boundStream = rootStream; @@ -166,9 +208,23 @@ namespace BizHawk.MultiClient } /// - /// 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 + /// + public HawkFile BindSoleItemOf(params string[] extensions) + { + return BindByExtensionCore(false, extensions); + } + + /// + /// 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 /// 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(); + 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(); + 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(); } } diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index 87b4da9c06..4b254a24a5 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -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); diff --git a/BizHawk.MultiClient/RomGame.cs b/BizHawk.MultiClient/RomGame.cs index 5b6d2326ab..2e698651b5 100644 --- a/BizHawk.MultiClient/RomGame.cs +++ b/BizHawk.MultiClient/RomGame.cs @@ -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.");