diff --git a/IPFBrowser/Extensions.cs b/IPFBrowser/Extensions.cs new file mode 100644 index 0000000..a006cd7 --- /dev/null +++ b/IPFBrowser/Extensions.cs @@ -0,0 +1,139 @@ +using System; +using System.IO; +using System.Text; + +namespace IPFBrowser +{ + /// + /// Extensions for BinaryReader and BinaryWriter to make working with + /// IES files easier. + /// + public static class BinaryReaderWriterExtensions + { + private const int XorKey = 1; + + /// + /// XORs buffer and returns it as a string in the given encoding. + /// + /// Buffer to XOR. + private static void XorBuffer(ref byte[] buffer) + { + for (var i = 0; i < buffer.Length; ++i) + { + if (buffer[i] == 0) + break; + + buffer[i] = (byte)(buffer[i] ^ XorKey); + } + } + + /// + /// Reads ushort-length-prefixed, XORed UTF8 string from reader and + /// returns it. + /// + /// + /// + public static string ReadXoredLpString(this BinaryReader reader) + { + var length = reader.ReadUInt16(); + return ReadXoredFixedString(reader, length); + } + + /// + /// Reads XORed UTF8 string with given length from reader and + /// returns it. + /// + /// + /// + /// + public static string ReadXoredFixedString(this BinaryReader reader, int length) + { + if (length <= 0) + return ""; + + var buffer = reader.ReadBytes(length); + XorBuffer(ref buffer); + var index = Array.IndexOf(buffer, (byte)0); + + if (index != -1) + length = index; + + return Encoding.UTF8.GetString(buffer, 0, length); + } + + /// + /// Reads XORed UTF8 string with given length from reader and + /// returns it. + /// + /// + /// + /// + public static string ReadFixedString(this BinaryReader reader, int length) + { + if (length <= 0) + return ""; + + var buffer = reader.ReadBytes(length); + var index = Array.IndexOf(buffer, (byte)0); + + if (index != -1) + length = index; + + return Encoding.UTF8.GetString(buffer, 0, length); + } + + /// + /// Writes UTF8 string to the writer, prefixed with a length. + /// The string is XORed. + /// + /// + /// + /// + public static void WriteXoredLpString(this BinaryWriter writer, string value) + { + var length = Encoding.UTF8.GetByteCount(value); + writer.Write((ushort)length); + WriteXoredFixedString(writer, value, length); + } + + /// + /// Writes UTF8 string with a fixed length to the writer. String + /// is cut off if it's to long and extended with null bytes if + /// too short. The actual string is XORed. + /// + /// + /// + /// + public static void WriteXoredFixedString(this BinaryWriter writer, string value, int length) + { + var buffer = Encoding.UTF8.GetBytes(value); + var writeLength = (buffer.Length > length ? length : buffer.Length); + + XorBuffer(ref buffer); + + writer.Write(buffer, 0, writeLength); + + for (; writeLength < length; ++writeLength) + writer.Write((byte)0); + } + + /// + /// Writes UTF8 string with a fixed length to the writer. String + /// is cut off if it's to long and extended with null bytes if + /// too short. + /// + /// + /// + /// + public static void WriteFixedString(this BinaryWriter writer, string value, int length) + { + var buffer = Encoding.UTF8.GetBytes(value); + var writeLength = (buffer.Length > length ? length : buffer.Length); + + writer.Write(buffer, 0, writeLength); + + for (; writeLength < length; ++writeLength) + writer.Write((byte)0); + } + } +} \ No newline at end of file diff --git a/IPFBrowser/FileFormats/IES/IesFile.cs b/IPFBrowser/FileFormats/IES/IesFile.cs index b69b309..714718e 100644 --- a/IPFBrowser/FileFormats/IES/IesFile.cs +++ b/IPFBrowser/FileFormats/IES/IesFile.cs @@ -14,7 +14,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; +using System.Windows.Forms; +using IPFBrowser; namespace IPFBrowser.FileFormats.IES { @@ -24,7 +27,11 @@ public class IesFile : IDisposable private BinaryReader _reader; private byte _xorKey; - public List Columns { get; private set; } + private const int HeaderNameLengths = 0x40; + private const int ColumnSize = 136; + private const int SizesPos = (2 * HeaderNameLengths + 2 * sizeof(short)); + + public List Columns { get; private set; } public IesHeader Header { get; private set; } public List Rows { get; private set; } @@ -73,8 +80,9 @@ private void ReadColumns() item.Name = this.DecryptString(_reader.ReadBytes(0x40), null); item.Name2 = this.DecryptString(_reader.ReadBytes(0x40), null); item.Type = (ColumnType)_reader.ReadUInt16(); - _reader.ReadUInt32(); - item.Position = _reader.ReadUInt16(); + item.Access = (PropertyAccess)_reader.ReadUInt16(); + item.Sync = _reader.ReadUInt16(); + item.Position = _reader.ReadUInt16(); // Duplicates var old = item.Name; @@ -88,14 +96,16 @@ private void ReadColumns() private void ReadHeader() { - this.Header = new IesHeader(); + this.Header = new IesHeader(); this.Header.Name = Encoding.UTF8.GetString(_reader.ReadBytes(0x80)); - _reader.ReadUInt32(); - this.Header.DataOffset = _reader.ReadUInt32(); + this.Header.Version = _reader.ReadUInt16(); + _reader.ReadUInt16(); // padding + this.Header.DataOffset = _reader.ReadUInt32(); this.Header.ResourceOffset = _reader.ReadUInt32(); this.Header.FileSize = _reader.ReadUInt32(); - _reader.ReadUInt16(); - this.Header.RowCount = _reader.ReadUInt16(); + this.Header.UseClassId = (_reader.ReadByte() != 0); + _reader.ReadByte(); // padding + this.Header.RowCount = _reader.ReadUInt16(); this.Header.ColumnCount = _reader.ReadUInt16(); this.Header.NumberColumnCount = _reader.ReadUInt16(); this.Header.StringColumnCount = _reader.ReadUInt16(); @@ -109,11 +119,14 @@ private void ReadRows() this.Rows = new List(); for (int i = 0; i < this.Header.RowCount; ++i) { - _reader.ReadUInt32(); - var count = _reader.ReadUInt16(); - _reader.ReadBytes(count); + var item = new IesRow(); - var item = new IesRow(); + item.ClassId = _reader.ReadInt32(); + item.ClassName = _reader.ReadXoredLpString(); + //var count = _reader.ReadUInt16(); + //_reader.ReadBytes(count); + + for (int j = 0; j < this.Columns.Count; ++j) { var column = this.Columns[j]; @@ -121,20 +134,7 @@ private void ReadRows() if (column.IsNumber) { var nan = _reader.ReadSingle(); - var maxValue = uint.MaxValue; - try - { - maxValue = (uint)nan; - } - catch - { - nan = float.NaN; - } - - if (Math.Abs((float)(nan - maxValue)) < float.Epsilon) - item.Add(column.Name, maxValue); - else - item.Add(column.Name, nan); + item.Add(column.Name, nan); } else { @@ -151,13 +151,122 @@ private void ReadRows() _reader.BaseStream.Seek((long)this.Header.StringColumnCount, SeekOrigin.Current); } } - } + /// + /// Saves this instance's data to IES file. + /// + /// + public byte[] ToBytes() + { + var columns = this.Columns.ToList(); + var sortedColumns = columns.OrderBy(a => a.IsNumber ? 0 : 1).ThenBy(a => a.Position); + var rows = this.Rows; + + var rowCount = rows.Count; + var colCount = columns.Count; + var numberColCount = columns.Count(a => a.IsNumber); + var stringColCount = colCount - numberColCount; + + MemoryStream ms = new MemoryStream(); + + using (var bw = new BinaryWriter(ms)) + { + bw.WriteFixedString(this.Header.Name, HeaderNameLengths * 2); + bw.Write((ushort)this.Header.Version); + bw.Write((ushort)0); // padding + bw.Write((uint)this.Header.DataOffset); + bw.Write((uint)this.Header.ResourceOffset); + bw.Write((uint)this.Header.FileSize); + bw.Write(this.Header.UseClassId ? (byte)1 : (byte)0); + bw.Write((byte)0); // padding + bw.Write((ushort)rowCount); + bw.Write((ushort)colCount); + bw.Write((ushort)numberColCount); + bw.Write((ushort)stringColCount); + bw.Write((ushort)0); // padding + + foreach (var column in columns) + { + bw.WriteXoredFixedString(column.Name, HeaderNameLengths); + bw.WriteXoredFixedString(column.Name2, HeaderNameLengths); + bw.Write((ushort)column.Type); + bw.Write((ushort)column.Access); + bw.Write((ushort)column.Sync); + bw.Write((ushort)column.Position); + } + + var rowsStart = bw.BaseStream.Position; + foreach (var row in rows) + { + // Find the ClassId and ClassName + // If these are part of the table, we write these instead of whatever + // values we might have here, in case the user changed them + if (row.TryGetValue("ClassID", out var classId)) + { + bw.Write((int)(float)classId); + } + else + { + bw.Write(row.ClassId); + } + + if (row.TryGetValue("ClassName", out var className)) + { + bw.WriteXoredLpString((String)className); + } + else + { + bw.WriteXoredLpString(row.ClassName ?? ""); + } + + foreach (var column in sortedColumns) + { + if (!row.TryGetValue(column.Name, out var value)) + { + if (column.IsNumber) + bw.Write(0f); + else + bw.Write((ushort)0); + } + else + { + if (column.IsNumber) + bw.Write((float)value); + else + bw.WriteXoredLpString((string)value); + } + } + + foreach (var column in sortedColumns.Where(a => !a.IsNumber)) + { + if (row.UseScr.TryGetValue(column.Name, out var value)) + bw.Write(value ? (byte)1 : (byte)0); + else + bw.Write((byte)0); + } + } + + this.Header.DataOffset = (uint)(columns.Count * ColumnSize); + this.Header.ResourceOffset = (uint)(bw.BaseStream.Position - rowsStart); + this.Header.FileSize = (uint)bw.BaseStream.Position; + + bw.BaseStream.Seek(SizesPos, SeekOrigin.Begin); + bw.Write((uint)this.Header.DataOffset); + bw.Write((uint)this.Header.ResourceOffset); + bw.Write((uint)this.Header.FileSize); + bw.BaseStream.Seek(0, SeekOrigin.End); + } + + return ms.ToArray(); + } + } public class IesHeader { - public uint DataOffset { get; set; } + public int Version { get; set; } + public uint DataOffset { get; set; } public uint ResourceOffset { get; set; } public uint FileSize { get; set; } + public bool UseClassId { get; set; } public string Name { get; set; } public ushort ColumnCount { get; set; } public ushort RowCount { get; set; } @@ -171,8 +280,9 @@ public class IesColumn : IComparable public string Name2 { get; set; } public ColumnType Type { get; set; } public ushort Position { get; set; } - - public bool IsNumber { get { return (this.Type == ColumnType.Float); } } + public PropertyAccess Access { get; set; } = PropertyAccess.SP; + public int Sync { get; set; } + public bool IsNumber { get { return (this.Type == ColumnType.Float); } } public int CompareTo(IesColumn other) { @@ -193,9 +303,21 @@ public enum ColumnType String2 } - public class IesRow : Dictionary + public enum PropertyAccess : byte + { + EP, + CP, + VP, + SP, + CT, + } + + public class IesRow : Dictionary { - public float GetFloat(string name) + public int ClassId { get; set; } + public string ClassName { get; set; } + public Dictionary UseScr { get; } = new Dictionary(); + public float GetFloat(string name) { if (!ContainsKey(name)) throw new ArgumentException("Unknown field: " + name); diff --git a/IPFBrowser/FileFormats/IPF/Ipf.cs b/IPFBrowser/FileFormats/IPF/Ipf.cs index e46630a..177d714 100644 --- a/IPFBrowser/FileFormats/IPF/Ipf.cs +++ b/IPFBrowser/FileFormats/IPF/Ipf.cs @@ -11,10 +11,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using IPFBrowser.FileFormats.IES; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Windows.Forms; +using System.Xml.Linq; namespace IPFBrowser.FileFormats.IPF { @@ -24,16 +30,16 @@ public class Ipf private readonly Stream _stream; private readonly BinaryReader _br; - public string FilePath { get; private set; } + public string FilePath { get; private set; } public List Files { get; private set; } public IpfFooter Footer { get; private set; } public Ipf(string filePath) { _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - _br = new BinaryReader(_stream); + _br = new BinaryReader(_stream); - this.FilePath = filePath; + this.FilePath = filePath; this.Load(); } @@ -44,7 +50,7 @@ public void Close() if (_br != null) _br.Dispose(); - } + } public void Load() { @@ -52,14 +58,14 @@ public void Load() this.Footer = new IpfFooter(); this.Footer.FileCount = _br.ReadUInt16(); - var fileTableOffset = _br.ReadUInt32(); - _br.ReadUInt16(); - _br.ReadUInt32(); - _br.ReadUInt32(); // compression - this.Footer.PatchVersion = _br.ReadUInt32(); + this.Footer.FileTableOffset = _br.ReadUInt32(); + this.Footer.Val1 = _br.ReadUInt16(); + this.Footer.Val2 = _br.ReadUInt32(); + this.Footer.Compression = _br.ReadUInt32(); + this.Footer.PatchVersion = _br.ReadUInt32(); this.Footer.NewVersion = _br.ReadUInt32(); - _stream.Position = fileTableOffset; + _stream.Position = this.Footer.FileTableOffset; this.Files = new List(); for (int i = 0; i < this.Footer.FileCount; i++) @@ -67,7 +73,7 @@ public void Load() var ipfFile = new IpfFile(this); var pathLength = _br.ReadUInt16(); - _br.ReadUInt32(); // crc32 + ipfFile.Checksum = _br.ReadUInt32(); ipfFile.SizeCompressed = _br.ReadUInt32(); ipfFile.SizeUncompressed = _br.ReadUInt32(); ipfFile.Offset = _br.ReadUInt32(); @@ -78,11 +84,122 @@ public void Load() var path = new string(_br.ReadChars(pathLength)); ipfFile.Path = path.Replace("\\", "/"); ipfFile.FullPath = Path.Combine(ipfFile.PackFileName, path).Replace("\\", "/"); - + this.Files.Add(ipfFile); } } + public bool Save(string filePath) + { + var tempPath = Path.GetDirectoryName(filePath) + "\\~" + Path.GetFileName(filePath); + + if (File.Exists(tempPath)) + { + File.Delete(tempPath); + } + + try + { + var outputStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); + var bw = new BinaryWriter(outputStream); + + uint currentPosition = 0; + + foreach (var file in this.Files) + { + if (!file.isModified) + { + bw.Write(ReadData(file.Offset, (int)file.SizeCompressed)); + bw.Flush(); + + // update the offset for use in writing the file table later, nothing else needs to be changed + file.Offset = currentPosition; + currentPosition += file.SizeCompressed; + } + else + { + file.SizeUncompressed = (uint)file.GetData().Length; + + var compressedData = file.Compress(); + file.SizeCompressed = (uint)compressedData.Length; + file.Checksum = CRC32.crc32(0, compressedData); + + bw.Write(compressedData); + bw.Flush(); + + file.Offset = currentPosition; + currentPosition += file.SizeCompressed; + } + } + + IpfFooter newFooter = new IpfFooter(); + newFooter.FileCount = (ushort)this.Files.Count; + newFooter.FileTableOffset = currentPosition; + newFooter.Val1 = this.Footer.Val1; + newFooter.Val2 = this.Footer.Val2; + newFooter.Compression = this.Footer.Compression; + newFooter.PatchVersion = this.Footer.PatchVersion; + newFooter.NewVersion = this.Footer.NewVersion; + + // Now we write the file table + + foreach (var file in this.Files) + { + bw.Write((ushort)file.Path.Length); + bw.Write((uint)file.Checksum); + bw.Write((uint)file.SizeCompressed); + bw.Write((uint)file.SizeUncompressed); + bw.Write((uint)file.Offset); + bw.Write((ushort)file.PackFileName.Length); + bw.WriteFixedString(file.PackFileName, file.PackFileName.Length); + bw.WriteFixedString(file.Path, file.Path.Length); + bw.Flush(); + } + + // Finally we write the footer + + bw.Write((ushort)newFooter.FileCount); + bw.Write((uint)newFooter.FileTableOffset); + bw.Write((ushort)newFooter.Val1); + bw.Write((uint)newFooter.Val2); + bw.Write((uint)newFooter.Compression); + bw.Write((uint)newFooter.PatchVersion); + bw.Write((uint)newFooter.NewVersion); + bw.Flush(); + bw.Close(); + + if (filePath == this.FilePath) + { + // Overwriting the open file, have to reopen the file + _br.Close(); + _stream.Close(); + } + + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + File.Move(tempPath, filePath); + + return filePath == this.FilePath; + } + catch (IOException ex) + { + MessageBox.Show("Cannot save to this file. This file may be open in another application."); + } + catch (Exception ex) + { + MessageBox.Show("Unable to save the file"); + } + + if (File.Exists(tempPath)) + { + File.Delete(tempPath); + } + + return false; + } + public byte[] ReadData(long offset, int length) { byte[] data; @@ -99,7 +216,7 @@ public byte[] ReadData(long offset, int length) public class IpfFile { - private readonly string[] _noCompression = new[] { ".jpg", ".jpg", ".fsb", ".mp3" }; + private readonly string[] _noCompression = new[] { ".jpg", ".jpeg", ".fsb", ".mp3" }; public Ipf Ipf { get; set; } public string PackFileName { get; set; } @@ -108,24 +225,37 @@ public class IpfFile public uint Offset { get; set; } public uint SizeCompressed { get; set; } public uint SizeUncompressed { get; set; } + public uint Checksum { get; set; } + + public bool isModified; - public IpfFile(Ipf ipf) + public byte[] content; + + public IpfFile(Ipf ipf) { this.Ipf = ipf; + isModified = false; } - public byte[] GetData() { - var ext = System.IO.Path.GetExtension(this.Path); - var data = this.Ipf.ReadData(this.Offset, (int)this.SizeCompressed); - - if (_noCompression.Contains(ext.ToLowerInvariant())) - return data; - - return this.Decompress(data); - } + if (isModified) + { + return content; + } + else + { + var ext = System.IO.Path.GetExtension(this.Path); + var data = this.Ipf.ReadData(this.Offset, (int)this.SizeCompressed); + + if (_noCompression.Contains(ext.ToLowerInvariant())) + { + return data; + } + return Decompress(data); + } + } - private byte[] Decompress(byte[] data) + private byte[] Decompress(byte[] data) { if (this.Ipf.Footer.NewVersion > 11000 || this.Ipf.Footer.NewVersion == 0) { @@ -141,13 +271,122 @@ private byte[] Decompress(byte[] data) return msOut.ToArray(); } } - } - public class IpfFooter + public byte[] Compress() + { + if (!isModified) + { + // data is already compressed, just return the data + return this.Ipf.ReadData(this.Offset, (int)this.SizeCompressed); + } + + var ext = System.IO.Path.GetExtension(this.Path); + if (_noCompression.Contains(ext.ToLowerInvariant())) + { + // does not require compression or encryption + return content; + } + + byte[] bytes = null; + + using (var ms = new MemoryStream()) + { + using (var deflate = new DeflateStream(ms, CompressionMode.Compress, true)) + { + deflate.Write(content, 0, content.Length); + } + + bytes = ms.ToArray(); + } + + byte[] encryptedBytes = null; + + if (this.Ipf.Footer.NewVersion > 11000 || this.Ipf.Footer.NewVersion == 0) + { + var pkw = new PkwareTraditionalEncryptionData("ofO1a0ueXA? [\xFFs h %?"); + encryptedBytes = pkw.Encrypt(bytes, bytes.Length); + } + + return encryptedBytes; + } + } + + + public static class CRC32 + { + static readonly uint[] crc32_tab = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + + /// + /// Calculate the crc32 checksum of the given memory block. + /// + /// The start value for the crc + /// Pointer to the memory block + /// Number of bytes + public static unsafe uint crc32(uint crc, byte[] buf) + { + crc = crc ^ ~0U; //0xFFFFFFFF + foreach (byte b in buf) + crc = crc32_tab[(crc ^ b) & 0xFF] ^ (crc >> 8); + + return crc ^ ~0U; //0xFFFFFFFF + } + } + + public class IpfFooter { public ushort FileCount { get; set; } public uint NewVersion { get; set; } public uint PatchVersion { get; set; } - } + public uint FileTableOffset { get; set; } + public uint Val1 { get; set; } + public uint Val2 { get; set; } + public uint Compression { get; set; } + + } } diff --git a/IPFBrowser/FileFormats/IPF/PkwareTraditionalEncryptionData.cs b/IPFBrowser/FileFormats/IPF/PkwareTraditionalEncryptionData.cs index 235a74a..a1ea394 100644 --- a/IPFBrowser/FileFormats/IPF/PkwareTraditionalEncryptionData.cs +++ b/IPFBrowser/FileFormats/IPF/PkwareTraditionalEncryptionData.cs @@ -60,9 +60,16 @@ public byte[] Encrypt(byte[] plainText, int length) var cipherText = new byte[length]; for (int i = 0; i < length; i++) { - byte C = plainText[i]; - cipherText[i] = (byte)(plainText[i] ^ MagicByte); - UpdateKeys(C); + if ((i % 2) != 0) + { + cipherText[i] = plainText[i]; + } + else + { + byte C = plainText[i]; + cipherText[i] = (byte)(plainText[i] ^ MagicByte); + UpdateKeys(C); + } } return cipherText; } diff --git a/IPFBrowser/FrmMain.Designer.cs b/IPFBrowser/FrmMain.Designer.cs index 9de97bf..ca0a7b5 100644 --- a/IPFBrowser/FrmMain.Designer.cs +++ b/IPFBrowser/FrmMain.Designer.cs @@ -28,389 +28,425 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmMain)); - this.SplMain = new System.Windows.Forms.SplitContainer(); - this.TreeFolders = new System.Windows.Forms.TreeView(); - this.imageList1 = new System.Windows.Forms.ImageList(this.components); - this.SplFiles = new System.Windows.Forms.SplitContainer(); - this.LstFiles = new System.Windows.Forms.ListView(); - this.GridPreview = new System.Windows.Forms.DataGridView(); - this.PnlImagePreview = new System.Windows.Forms.Panel(); - this.ImgPreview = new System.Windows.Forms.PictureBox(); - this.TxtPreview = new ScintillaNET.Scintilla(); - this.LblPreview = new System.Windows.Forms.Label(); - this.OfdIpfFile = new System.Windows.Forms.OpenFileDialog(); - this.StatusStrip = new System.Windows.Forms.StatusStrip(); - this.LblVersion = new System.Windows.Forms.ToolStripStatusLabel(); - this.LblFileName = new System.Windows.Forms.ToolStripStatusLabel(); - this.mainMenu1 = new System.Windows.Forms.MainMenu(this.components); - this.menuItem1 = new System.Windows.Forms.MenuItem(); - this.BtnMenuOpen = new System.Windows.Forms.MenuItem(); - this.BtnExit = new System.Windows.Forms.MenuItem(); - this.menuItem3 = new System.Windows.Forms.MenuItem(); - this.BtnAbout = new System.Windows.Forms.MenuItem(); - this.SavExtractFile = new System.Windows.Forms.SaveFileDialog(); - this.FbdExtractPack = new Ookii.Dialogs.VistaFolderBrowserDialog(); - this.toolStrip1 = new System.Windows.Forms.ToolStrip(); - this.BtnOpen = new System.Windows.Forms.ToolStripButton(); - this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); - this.BtnExtractClient = new System.Windows.Forms.ToolStripButton(); - this.BtnExtractPack = new System.Windows.Forms.ToolStripButton(); - this.BtnExtractFile = new System.Windows.Forms.ToolStripButton(); - this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); - this.BtnPreview = new System.Windows.Forms.ToolStripButton(); - ((System.ComponentModel.ISupportInitialize)(this.SplMain)).BeginInit(); - this.SplMain.Panel1.SuspendLayout(); - this.SplMain.Panel2.SuspendLayout(); - this.SplMain.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.SplFiles)).BeginInit(); - this.SplFiles.Panel1.SuspendLayout(); - this.SplFiles.Panel2.SuspendLayout(); - this.SplFiles.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.GridPreview)).BeginInit(); - this.PnlImagePreview.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.ImgPreview)).BeginInit(); - this.StatusStrip.SuspendLayout(); - this.toolStrip1.SuspendLayout(); - this.SuspendLayout(); - // - // SplMain - // - this.SplMain.Dock = System.Windows.Forms.DockStyle.Fill; - this.SplMain.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; - this.SplMain.IsSplitterFixed = true; - this.SplMain.Location = new System.Drawing.Point(0, 41); - this.SplMain.Name = "SplMain"; - // - // SplMain.Panel1 - // - this.SplMain.Panel1.Controls.Add(this.TreeFolders); - this.SplMain.Panel1MinSize = 250; - // - // SplMain.Panel2 - // - this.SplMain.Panel2.Controls.Add(this.SplFiles); - this.SplMain.Size = new System.Drawing.Size(964, 535); - this.SplMain.SplitterDistance = 250; - this.SplMain.TabIndex = 0; - // - // TreeFolders - // - this.TreeFolders.Dock = System.Windows.Forms.DockStyle.Fill; - this.TreeFolders.HideSelection = false; - this.TreeFolders.ImageIndex = 2; - this.TreeFolders.ImageList = this.imageList1; - this.TreeFolders.Location = new System.Drawing.Point(0, 0); - this.TreeFolders.Name = "TreeFolders"; - this.TreeFolders.PathSeparator = "/"; - this.TreeFolders.SelectedImageIndex = 2; - this.TreeFolders.Size = new System.Drawing.Size(250, 535); - this.TreeFolders.TabIndex = 0; - this.TreeFolders.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.TreeFolders_AfterSelect); - // - // imageList1 - // - this.imageList1.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageList1.ImageStream"))); - this.imageList1.TransparentColor = System.Drawing.Color.Transparent; - this.imageList1.Images.SetKeyName(0, "folder_page_white.png"); - this.imageList1.Images.SetKeyName(1, "folder_page.png"); - this.imageList1.Images.SetKeyName(2, "folder.png"); - this.imageList1.Images.SetKeyName(3, "compress.png"); - this.imageList1.Images.SetKeyName(4, "page_white_text.png"); - this.imageList1.Images.SetKeyName(5, "page_white_code.png"); - this.imageList1.Images.SetKeyName(6, "page_white.png"); - this.imageList1.Images.SetKeyName(7, "image.png"); - this.imageList1.Images.SetKeyName(8, "table.png"); - // - // SplFiles - // - this.SplFiles.Dock = System.Windows.Forms.DockStyle.Fill; - this.SplFiles.Location = new System.Drawing.Point(0, 0); - this.SplFiles.Name = "SplFiles"; - this.SplFiles.Orientation = System.Windows.Forms.Orientation.Horizontal; - // - // SplFiles.Panel1 - // - this.SplFiles.Panel1.Controls.Add(this.LstFiles); - // - // SplFiles.Panel2 - // - this.SplFiles.Panel2.Controls.Add(this.GridPreview); - this.SplFiles.Panel2.Controls.Add(this.PnlImagePreview); - this.SplFiles.Panel2.Controls.Add(this.TxtPreview); - this.SplFiles.Panel2.Controls.Add(this.LblPreview); - this.SplFiles.Size = new System.Drawing.Size(710, 535); - this.SplFiles.SplitterDistance = 231; - this.SplFiles.TabIndex = 0; - // - // LstFiles - // - this.LstFiles.Dock = System.Windows.Forms.DockStyle.Fill; - this.LstFiles.HideSelection = false; - this.LstFiles.Location = new System.Drawing.Point(0, 0); - this.LstFiles.MultiSelect = false; - this.LstFiles.Name = "LstFiles"; - this.LstFiles.Size = new System.Drawing.Size(710, 231); - this.LstFiles.SmallImageList = this.imageList1; - this.LstFiles.TabIndex = 0; - this.LstFiles.UseCompatibleStateImageBehavior = false; - this.LstFiles.View = System.Windows.Forms.View.List; - this.LstFiles.SelectedIndexChanged += new System.EventHandler(this.LstFiles_SelectedIndexChanged); - // - // GridPreview - // - this.GridPreview.AllowUserToDeleteRows = false; - this.GridPreview.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.GridPreview.Location = new System.Drawing.Point(456, 25); - this.GridPreview.Name = "GridPreview"; - this.GridPreview.ReadOnly = true; - this.GridPreview.Size = new System.Drawing.Size(240, 150); - this.GridPreview.TabIndex = 6; - // - // PnlImagePreview - // - this.PnlImagePreview.AutoScroll = true; - this.PnlImagePreview.Controls.Add(this.ImgPreview); - this.PnlImagePreview.Location = new System.Drawing.Point(239, 11); - this.PnlImagePreview.Name = "PnlImagePreview"; - this.PnlImagePreview.Size = new System.Drawing.Size(211, 164); - this.PnlImagePreview.TabIndex = 5; - // - // ImgPreview - // - this.ImgPreview.Location = new System.Drawing.Point(0, 0); - this.ImgPreview.Name = "ImgPreview"; - this.ImgPreview.Size = new System.Drawing.Size(156, 100); - this.ImgPreview.TabIndex = 4; - this.ImgPreview.TabStop = false; - // - // TxtPreview - // - this.TxtPreview.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.TxtPreview.Location = new System.Drawing.Point(33, 75); - this.TxtPreview.Name = "TxtPreview"; - this.TxtPreview.ReadOnly = true; - this.TxtPreview.ScrollWidth = 100; - this.TxtPreview.Size = new System.Drawing.Size(200, 100); - this.TxtPreview.TabIndex = 2; - this.TxtPreview.UseTabs = false; - // - // LblPreview - // - this.LblPreview.Font = new System.Drawing.Font("Microsoft Sans Serif", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.LblPreview.ForeColor = System.Drawing.Color.Silver; - this.LblPreview.Location = new System.Drawing.Point(27, 11); - this.LblPreview.Name = "LblPreview"; - this.LblPreview.Size = new System.Drawing.Size(122, 61); - this.LblPreview.TabIndex = 1; - this.LblPreview.Text = "Preview"; - this.LblPreview.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - // - // OfdIpfFile - // - this.OfdIpfFile.Filter = "IPF-files|*.ipf"; - // - // StatusStrip - // - this.StatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.LblVersion, - this.LblFileName}); - this.StatusStrip.Location = new System.Drawing.Point(0, 576); - this.StatusStrip.Name = "StatusStrip"; - this.StatusStrip.Size = new System.Drawing.Size(964, 24); - this.StatusStrip.TabIndex = 3; - // - // LblVersion - // - this.LblVersion.AutoSize = false; - this.LblVersion.Name = "LblVersion"; - this.LblVersion.Size = new System.Drawing.Size(118, 19); - this.LblVersion.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // LblFileName - // - this.LblFileName.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; - this.LblFileName.BorderStyle = System.Windows.Forms.Border3DStyle.Etched; - this.LblFileName.Name = "LblFileName"; - this.LblFileName.Size = new System.Drawing.Size(4, 19); - this.LblFileName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // mainMenu1 - // - this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { - this.menuItem1, - this.menuItem3}); - // - // menuItem1 - // - this.menuItem1.Index = 0; - this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { - this.BtnMenuOpen, - this.BtnExit}); - this.menuItem1.Text = "File"; - // - // BtnMenuOpen - // - this.BtnMenuOpen.Index = 0; - this.BtnMenuOpen.Text = "Open..."; - this.BtnMenuOpen.Click += new System.EventHandler(this.BtnOpen_Click); - // - // BtnExit - // - this.BtnExit.Index = 1; - this.BtnExit.Text = "Exit"; - // - // menuItem3 - // - this.menuItem3.Index = 1; - this.menuItem3.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { - this.BtnAbout}); - this.menuItem3.Text = "?"; - // - // BtnAbout - // - this.BtnAbout.Index = 0; - this.BtnAbout.Text = "About"; - this.BtnAbout.Click += new System.EventHandler(this.BtnAbout_Click); - // - // SavExtractFile - // - this.SavExtractFile.Filter = "All files|*.*"; - // - // toolStrip1 - // - this.toolStrip1.AutoSize = false; - this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; - this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.BtnOpen, - this.toolStripSeparator1, - this.BtnExtractClient, - this.BtnExtractPack, - this.BtnExtractFile, - this.toolStripSeparator2, - this.BtnPreview}); - this.toolStrip1.Location = new System.Drawing.Point(0, 0); - this.toolStrip1.Name = "toolStrip1"; - this.toolStrip1.RenderMode = System.Windows.Forms.ToolStripRenderMode.System; - this.toolStrip1.Size = new System.Drawing.Size(964, 41); - this.toolStrip1.TabIndex = 1; - this.toolStrip1.Text = "toolStrip1"; - // - // BtnOpen - // - this.BtnOpen.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.BtnOpen.Image = ((System.Drawing.Image)(resources.GetObject("BtnOpen.Image"))); - this.BtnOpen.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; - this.BtnOpen.ImageTransparentColor = System.Drawing.Color.Magenta; - this.BtnOpen.Name = "BtnOpen"; - this.BtnOpen.Size = new System.Drawing.Size(36, 38); - this.BtnOpen.Text = "Open IPF file"; - this.BtnOpen.Click += new System.EventHandler(this.BtnOpen_Click); - // - // toolStripSeparator1 - // - this.toolStripSeparator1.Name = "toolStripSeparator1"; - this.toolStripSeparator1.Size = new System.Drawing.Size(6, 41); - // - // BtnExtractClient - // - this.BtnExtractClient.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.BtnExtractClient.Image = ((System.Drawing.Image)(resources.GetObject("BtnExtractClient.Image"))); - this.BtnExtractClient.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; - this.BtnExtractClient.ImageTransparentColor = System.Drawing.Color.Magenta; - this.BtnExtractClient.Name = "BtnExtractClient"; - this.BtnExtractClient.Size = new System.Drawing.Size(36, 38); - this.BtnExtractClient.Text = "Extract all client IPFs"; - this.BtnExtractClient.Click += new System.EventHandler(this.BtnExtractClient_Click); - // - // BtnExtractPack - // - this.BtnExtractPack.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.BtnExtractPack.Image = ((System.Drawing.Image)(resources.GetObject("BtnExtractPack.Image"))); - this.BtnExtractPack.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; - this.BtnExtractPack.ImageTransparentColor = System.Drawing.Color.Magenta; - this.BtnExtractPack.Name = "BtnExtractPack"; - this.BtnExtractPack.Size = new System.Drawing.Size(36, 38); - this.BtnExtractPack.Text = "Extract open IPF file"; - this.BtnExtractPack.Click += new System.EventHandler(this.BtnExtractPack_Click); - // - // BtnExtractFile - // - this.BtnExtractFile.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.BtnExtractFile.Image = ((System.Drawing.Image)(resources.GetObject("BtnExtractFile.Image"))); - this.BtnExtractFile.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; - this.BtnExtractFile.ImageTransparentColor = System.Drawing.Color.Magenta; - this.BtnExtractFile.Name = "BtnExtractFile"; - this.BtnExtractFile.Size = new System.Drawing.Size(36, 38); - this.BtnExtractFile.Text = "Extract selected file"; - this.BtnExtractFile.Click += new System.EventHandler(this.BtnExtractFile_Click); - // - // toolStripSeparator2 - // - this.toolStripSeparator2.Name = "toolStripSeparator2"; - this.toolStripSeparator2.Size = new System.Drawing.Size(6, 41); - // - // BtnPreview - // - this.BtnPreview.Checked = true; - this.BtnPreview.CheckOnClick = true; - this.BtnPreview.CheckState = System.Windows.Forms.CheckState.Checked; - this.BtnPreview.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.BtnPreview.Image = ((System.Drawing.Image)(resources.GetObject("BtnPreview.Image"))); - this.BtnPreview.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; - this.BtnPreview.ImageTransparentColor = System.Drawing.Color.Magenta; - this.BtnPreview.Name = "BtnPreview"; - this.BtnPreview.Size = new System.Drawing.Size(36, 38); - this.BtnPreview.Text = "Show Preview"; - this.BtnPreview.Click += new System.EventHandler(this.BtnPreview_Click); - // - // FrmMain - // - this.AllowDrop = true; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(964, 600); - this.Controls.Add(this.SplMain); - this.Controls.Add(this.toolStrip1); - this.Controls.Add(this.StatusStrip); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.Menu = this.mainMenu1; - this.Name = "FrmMain"; - this.Text = "IPF Browser"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FrmMain_FormClosing); - this.DragDrop += new System.Windows.Forms.DragEventHandler(this.FrmMain_DragDrop); - this.DragEnter += new System.Windows.Forms.DragEventHandler(this.FrmMain_DragEnter); - this.SplMain.Panel1.ResumeLayout(false); - this.SplMain.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.SplMain)).EndInit(); - this.SplMain.ResumeLayout(false); - this.SplFiles.Panel1.ResumeLayout(false); - this.SplFiles.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.SplFiles)).EndInit(); - this.SplFiles.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.GridPreview)).EndInit(); - this.PnlImagePreview.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.ImgPreview)).EndInit(); - this.StatusStrip.ResumeLayout(false); - this.StatusStrip.PerformLayout(); - this.toolStrip1.ResumeLayout(false); - this.toolStrip1.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmMain)); + this.SplMain = new System.Windows.Forms.SplitContainer(); + this.TreeFolders = new System.Windows.Forms.TreeView(); + this.imageList1 = new System.Windows.Forms.ImageList(this.components); + this.SplFiles = new System.Windows.Forms.SplitContainer(); + this.LstFiles = new System.Windows.Forms.ListView(); + this.GridPreview = new System.Windows.Forms.DataGridView(); + this.PnlImagePreview = new System.Windows.Forms.Panel(); + this.ImgPreview = new System.Windows.Forms.PictureBox(); + this.TxtPreview = new ScintillaNET.Scintilla(); + this.LblPreview = new System.Windows.Forms.Label(); + this.OfdIpfFile = new System.Windows.Forms.OpenFileDialog(); + this.SfdIpfFile = new System.Windows.Forms.SaveFileDialog(); + this.StatusStrip = new System.Windows.Forms.StatusStrip(); + this.LblVersion = new System.Windows.Forms.ToolStripStatusLabel(); + this.LblFileName = new System.Windows.Forms.ToolStripStatusLabel(); + this.mainMenu1 = new System.Windows.Forms.MainMenu(this.components); + this.menuItem1 = new System.Windows.Forms.MenuItem(); + this.BtnMenuOpen = new System.Windows.Forms.MenuItem(); + this.BtnMenuSave = new System.Windows.Forms.MenuItem(); + this.BtnExit = new System.Windows.Forms.MenuItem(); + this.menuItem3 = new System.Windows.Forms.MenuItem(); + this.BtnAbout = new System.Windows.Forms.MenuItem(); + this.SavExtractFile = new System.Windows.Forms.SaveFileDialog(); + this.FbdExtractPack = new Ookii.Dialogs.VistaFolderBrowserDialog(); + this.toolStrip1 = new System.Windows.Forms.ToolStrip(); + this.BtnOpen = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.BtnExtractClient = new System.Windows.Forms.ToolStripButton(); + this.BtnExtractPack = new System.Windows.Forms.ToolStripButton(); + this.BtnExtractFile = new System.Windows.Forms.ToolStripButton(); + this.BtnSavePack = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.BtnPreview = new System.Windows.Forms.ToolStripButton(); + ((System.ComponentModel.ISupportInitialize)(this.SplMain)).BeginInit(); + this.SplMain.Panel1.SuspendLayout(); + this.SplMain.Panel2.SuspendLayout(); + this.SplMain.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.SplFiles)).BeginInit(); + this.SplFiles.Panel1.SuspendLayout(); + this.SplFiles.Panel2.SuspendLayout(); + this.SplFiles.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.GridPreview)).BeginInit(); + this.PnlImagePreview.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.ImgPreview)).BeginInit(); + this.StatusStrip.SuspendLayout(); + this.toolStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // SplMain + // + this.SplMain.Dock = System.Windows.Forms.DockStyle.Fill; + this.SplMain.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.SplMain.IsSplitterFixed = true; + this.SplMain.Location = new System.Drawing.Point(0, 41); + this.SplMain.Name = "SplMain"; + // + // SplMain.Panel1 + // + this.SplMain.Panel1.Controls.Add(this.TreeFolders); + this.SplMain.Panel1MinSize = 250; + // + // SplMain.Panel2 + // + this.SplMain.Panel2.Controls.Add(this.SplFiles); + this.SplMain.Size = new System.Drawing.Size(964, 535); + this.SplMain.SplitterDistance = 250; + this.SplMain.TabIndex = 0; + // + // TreeFolders + // + this.TreeFolders.Dock = System.Windows.Forms.DockStyle.Fill; + this.TreeFolders.HideSelection = false; + this.TreeFolders.ImageIndex = 2; + this.TreeFolders.ImageList = this.imageList1; + this.TreeFolders.Location = new System.Drawing.Point(0, 0); + this.TreeFolders.Name = "TreeFolders"; + this.TreeFolders.PathSeparator = "/"; + this.TreeFolders.SelectedImageIndex = 2; + this.TreeFolders.Size = new System.Drawing.Size(250, 535); + this.TreeFolders.TabIndex = 0; + this.TreeFolders.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.TreeFolders_AfterSelect); + this.TreeFolders.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TreeFolders_KeyDown); + // + // imageList1 + // + this.imageList1.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageList1.ImageStream"))); + this.imageList1.TransparentColor = System.Drawing.Color.Transparent; + this.imageList1.Images.SetKeyName(0, "folder_page_white.png"); + this.imageList1.Images.SetKeyName(1, "folder_page.png"); + this.imageList1.Images.SetKeyName(2, "folder.png"); + this.imageList1.Images.SetKeyName(3, "compress.png"); + this.imageList1.Images.SetKeyName(4, "page_white_text.png"); + this.imageList1.Images.SetKeyName(5, "page_white_code.png"); + this.imageList1.Images.SetKeyName(6, "page_white.png"); + this.imageList1.Images.SetKeyName(7, "image.png"); + this.imageList1.Images.SetKeyName(8, "table.png"); + // + // SplFiles + // + this.SplFiles.Dock = System.Windows.Forms.DockStyle.Fill; + this.SplFiles.Location = new System.Drawing.Point(0, 0); + this.SplFiles.Name = "SplFiles"; + this.SplFiles.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // SplFiles.Panel1 + // + this.SplFiles.Panel1.Controls.Add(this.LstFiles); + // + // SplFiles.Panel2 + // + this.SplFiles.Panel2.Controls.Add(this.GridPreview); + this.SplFiles.Panel2.Controls.Add(this.PnlImagePreview); + this.SplFiles.Panel2.Controls.Add(this.TxtPreview); + this.SplFiles.Panel2.Controls.Add(this.LblPreview); + this.SplFiles.Size = new System.Drawing.Size(710, 535); + this.SplFiles.SplitterDistance = 231; + this.SplFiles.TabIndex = 0; + // + // LstFiles + // + this.LstFiles.AllowDrop = true; + this.LstFiles.Dock = System.Windows.Forms.DockStyle.Fill; + this.LstFiles.HideSelection = false; + this.LstFiles.Location = new System.Drawing.Point(0, 0); + this.LstFiles.MultiSelect = false; + this.LstFiles.Name = "LstFiles"; + this.LstFiles.Size = new System.Drawing.Size(710, 231); + this.LstFiles.SmallImageList = this.imageList1; + this.LstFiles.TabIndex = 0; + this.LstFiles.UseCompatibleStateImageBehavior = false; + this.LstFiles.View = System.Windows.Forms.View.List; + this.LstFiles.SelectedIndexChanged += new System.EventHandler(this.LstFiles_SelectedIndexChanged); + this.LstFiles.DragDrop += new System.Windows.Forms.DragEventHandler(this.LstFiles_DropFile); + this.LstFiles.DragEnter += new System.Windows.Forms.DragEventHandler(this.LstFiles_DragEnter); + this.LstFiles.KeyDown += new System.Windows.Forms.KeyEventHandler(this.LstFiles_KeyDown); + // + // GridPreview + // + this.GridPreview.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.GridPreview.Location = new System.Drawing.Point(456, 25); + this.GridPreview.Name = "GridPreview"; + this.GridPreview.Size = new System.Drawing.Size(240, 150); + this.GridPreview.TabIndex = 6; + this.GridPreview.RowHeaderMouseClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.GridPreview_InsertRow); + this.GridPreview.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.GridPreview_ValueChanged); + this.GridPreview.UserDeletedRow += new System.Windows.Forms.DataGridViewRowEventHandler(this.GridPreview_DeleteRow); + // + // PnlImagePreview + // + this.PnlImagePreview.AutoScroll = true; + this.PnlImagePreview.Controls.Add(this.ImgPreview); + this.PnlImagePreview.Location = new System.Drawing.Point(239, 11); + this.PnlImagePreview.Name = "PnlImagePreview"; + this.PnlImagePreview.Size = new System.Drawing.Size(211, 164); + this.PnlImagePreview.TabIndex = 5; + // + // ImgPreview + // + this.ImgPreview.Location = new System.Drawing.Point(0, 0); + this.ImgPreview.Name = "ImgPreview"; + this.ImgPreview.Size = new System.Drawing.Size(156, 100); + this.ImgPreview.TabIndex = 4; + this.ImgPreview.TabStop = false; + // + // TxtPreview + // + this.TxtPreview.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.TxtPreview.Location = new System.Drawing.Point(33, 75); + this.TxtPreview.Name = "TxtPreview"; + this.TxtPreview.ScrollWidth = 100; + this.TxtPreview.Size = new System.Drawing.Size(200, 100); + this.TxtPreview.TabIndex = 2; + this.TxtPreview.UseTabs = false; + this.TxtPreview.TextChanged += new System.EventHandler(this.TxtPreview_TextChanged); + // + // LblPreview + // + this.LblPreview.Font = new System.Drawing.Font("Microsoft Sans Serif", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.LblPreview.ForeColor = System.Drawing.Color.Silver; + this.LblPreview.Location = new System.Drawing.Point(27, 11); + this.LblPreview.Name = "LblPreview"; + this.LblPreview.Size = new System.Drawing.Size(122, 61); + this.LblPreview.TabIndex = 1; + this.LblPreview.Text = "Preview"; + this.LblPreview.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // OfdIpfFile + // + this.OfdIpfFile.Filter = "IPF-files|*.ipf"; + // + // SfdIpfFile + // + this.SfdIpfFile.Filter = "IPF-files|*.ipf"; + // + // StatusStrip + // + this.StatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.LblVersion, + this.LblFileName}); + this.StatusStrip.Location = new System.Drawing.Point(0, 576); + this.StatusStrip.Name = "StatusStrip"; + this.StatusStrip.Size = new System.Drawing.Size(964, 24); + this.StatusStrip.TabIndex = 3; + // + // LblVersion + // + this.LblVersion.AutoSize = false; + this.LblVersion.Name = "LblVersion"; + this.LblVersion.Size = new System.Drawing.Size(118, 19); + this.LblVersion.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.LblVersion.Click += new System.EventHandler(this.LblVersion_Click); + // + // LblFileName + // + this.LblFileName.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Left; + this.LblFileName.BorderStyle = System.Windows.Forms.Border3DStyle.Etched; + this.LblFileName.Name = "LblFileName"; + this.LblFileName.Size = new System.Drawing.Size(4, 19); + this.LblFileName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // mainMenu1 + // + this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { + this.menuItem1, + this.menuItem3}); + // + // menuItem1 + // + this.menuItem1.Index = 0; + this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { + this.BtnMenuOpen, + this.BtnMenuSave, + this.BtnExit}); + this.menuItem1.Text = "File"; + // + // BtnMenuOpen + // + this.BtnMenuOpen.Index = 0; + this.BtnMenuOpen.Text = "Open..."; + this.BtnMenuOpen.Click += new System.EventHandler(this.BtnOpen_Click); + // + // BtnMenuSave + // + this.BtnMenuSave.Index = 1; + this.BtnMenuSave.Text = "Save As..."; + this.BtnMenuSave.Click += new System.EventHandler(this.BtnSave_Click); + // + // BtnExit + // + this.BtnExit.Index = 2; + this.BtnExit.Text = "Exit"; + this.BtnExit.Click += new System.EventHandler(this.BtnExit_Click); + // + // menuItem3 + // + this.menuItem3.Index = 1; + this.menuItem3.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { + this.BtnAbout}); + this.menuItem3.Text = "Help"; + // + // BtnAbout + // + this.BtnAbout.Index = 0; + this.BtnAbout.Text = "About"; + this.BtnAbout.Click += new System.EventHandler(this.BtnAbout_Click); + // + // SavExtractFile + // + this.SavExtractFile.Filter = "All files|*.*"; + // + // toolStrip1 + // + this.toolStrip1.AutoSize = false; + this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; + this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.BtnOpen, + this.toolStripSeparator1, + this.BtnExtractClient, + this.BtnExtractPack, + this.BtnExtractFile, + this.BtnSavePack, + this.toolStripSeparator2, + this.BtnPreview}); + this.toolStrip1.Location = new System.Drawing.Point(0, 0); + this.toolStrip1.Name = "toolStrip1"; + this.toolStrip1.RenderMode = System.Windows.Forms.ToolStripRenderMode.System; + this.toolStrip1.Size = new System.Drawing.Size(964, 41); + this.toolStrip1.TabIndex = 1; + this.toolStrip1.Text = "toolStrip1"; + // + // BtnOpen + // + this.BtnOpen.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.BtnOpen.Image = ((System.Drawing.Image)(resources.GetObject("BtnOpen.Image"))); + this.BtnOpen.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.BtnOpen.ImageTransparentColor = System.Drawing.Color.Magenta; + this.BtnOpen.Name = "BtnOpen"; + this.BtnOpen.Size = new System.Drawing.Size(36, 38); + this.BtnOpen.Text = "Open IPF file"; + this.BtnOpen.Click += new System.EventHandler(this.BtnOpen_Click); + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(6, 41); + // + // BtnExtractClient + // + this.BtnExtractClient.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.BtnExtractClient.Image = ((System.Drawing.Image)(resources.GetObject("BtnExtractClient.Image"))); + this.BtnExtractClient.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.BtnExtractClient.ImageTransparentColor = System.Drawing.Color.Magenta; + this.BtnExtractClient.Name = "BtnExtractClient"; + this.BtnExtractClient.Size = new System.Drawing.Size(36, 38); + this.BtnExtractClient.Text = "Extract all client IPFs"; + this.BtnExtractClient.Click += new System.EventHandler(this.BtnExtractClient_Click); + // + // BtnExtractPack + // + this.BtnExtractPack.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.BtnExtractPack.Image = ((System.Drawing.Image)(resources.GetObject("BtnExtractPack.Image"))); + this.BtnExtractPack.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.BtnExtractPack.ImageTransparentColor = System.Drawing.Color.Magenta; + this.BtnExtractPack.Name = "BtnExtractPack"; + this.BtnExtractPack.Size = new System.Drawing.Size(36, 38); + this.BtnExtractPack.Text = "Extract open IPF file"; + this.BtnExtractPack.Click += new System.EventHandler(this.BtnExtractPack_Click); + // + // BtnExtractFile + // + this.BtnExtractFile.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.BtnExtractFile.Image = ((System.Drawing.Image)(resources.GetObject("BtnExtractFile.Image"))); + this.BtnExtractFile.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.BtnExtractFile.ImageTransparentColor = System.Drawing.Color.Magenta; + this.BtnExtractFile.Name = "BtnExtractFile"; + this.BtnExtractFile.Size = new System.Drawing.Size(36, 38); + this.BtnExtractFile.Text = "Extract selected file"; + this.BtnExtractFile.Click += new System.EventHandler(this.BtnExtractFile_Click); + // + // BtnSavePack + // + this.BtnSavePack.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.BtnSavePack.Image = ((System.Drawing.Image)(resources.GetObject("BtnSavePack.Image"))); + this.BtnSavePack.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.BtnSavePack.ImageTransparentColor = System.Drawing.Color.Magenta; + this.BtnSavePack.Name = "BtnSavePack"; + this.BtnSavePack.Size = new System.Drawing.Size(36, 38); + this.BtnSavePack.Text = "Save Modified IPF File"; + this.BtnSavePack.ToolTipText = "Save Modified IPF File"; + this.BtnSavePack.Click += new System.EventHandler(this.BtnSave_Click); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(6, 41); + // + // BtnPreview + // + this.BtnPreview.Checked = true; + this.BtnPreview.CheckOnClick = true; + this.BtnPreview.CheckState = System.Windows.Forms.CheckState.Checked; + this.BtnPreview.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.BtnPreview.Image = ((System.Drawing.Image)(resources.GetObject("BtnPreview.Image"))); + this.BtnPreview.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + this.BtnPreview.ImageTransparentColor = System.Drawing.Color.Magenta; + this.BtnPreview.Name = "BtnPreview"; + this.BtnPreview.Size = new System.Drawing.Size(36, 38); + this.BtnPreview.Text = "Show Preview"; + this.BtnPreview.Click += new System.EventHandler(this.BtnPreview_Click); + // + // FrmMain + // + this.AllowDrop = true; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(964, 600); + this.Controls.Add(this.SplMain); + this.Controls.Add(this.toolStrip1); + this.Controls.Add(this.StatusStrip); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Menu = this.mainMenu1; + this.Name = "FrmMain"; + this.Text = "IPF Browser"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FrmMain_FormClosing); + this.DragDrop += new System.Windows.Forms.DragEventHandler(this.FrmMain_DragDrop); + this.DragEnter += new System.Windows.Forms.DragEventHandler(this.FrmMain_DragEnter); + this.SplMain.Panel1.ResumeLayout(false); + this.SplMain.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.SplMain)).EndInit(); + this.SplMain.ResumeLayout(false); + this.SplFiles.Panel1.ResumeLayout(false); + this.SplFiles.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.SplFiles)).EndInit(); + this.SplFiles.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.GridPreview)).EndInit(); + this.PnlImagePreview.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.ImgPreview)).EndInit(); + this.StatusStrip.ResumeLayout(false); + this.StatusStrip.PerformLayout(); + this.toolStrip1.ResumeLayout(false); + this.toolStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); } - #endregion + #endregion - private System.Windows.Forms.SplitContainer SplMain; + private System.Windows.Forms.SplitContainer SplMain; private System.Windows.Forms.TreeView TreeFolders; private System.Windows.Forms.SplitContainer SplFiles; private System.Windows.Forms.ListView LstFiles; private System.Windows.Forms.ToolStrip toolStrip1; private System.Windows.Forms.ToolStripButton BtnOpen; private System.Windows.Forms.OpenFileDialog OfdIpfFile; - private System.Windows.Forms.ImageList imageList1; + private System.Windows.Forms.SaveFileDialog SfdIpfFile; + private System.Windows.Forms.ImageList imageList1; private System.Windows.Forms.StatusStrip StatusStrip; private System.Windows.Forms.ToolStripStatusLabel LblVersion; private System.Windows.Forms.ToolStripStatusLabel LblFileName; @@ -427,12 +463,14 @@ private void InitializeComponent() private System.Windows.Forms.DataGridView GridPreview; private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; private System.Windows.Forms.ToolStripButton BtnExtractPack; - private System.Windows.Forms.ToolStripButton BtnExtractFile; + private System.Windows.Forms.ToolStripButton BtnExtractFile; + private System.Windows.Forms.ToolStripButton BtnSavePack; private System.Windows.Forms.SaveFileDialog SavExtractFile; private Ookii.Dialogs.VistaFolderBrowserDialog FbdExtractPack; private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; private System.Windows.Forms.ToolStripButton BtnExtractClient; private System.Windows.Forms.ToolStripButton BtnPreview; - } + private System.Windows.Forms.MenuItem BtnMenuSave; + } } diff --git a/IPFBrowser/FrmMain.cs b/IPFBrowser/FrmMain.cs index adf5bff..b3f6088 100644 --- a/IPFBrowser/FrmMain.cs +++ b/IPFBrowser/FrmMain.cs @@ -24,10 +24,13 @@ using System.Drawing.Text; using System.IO; using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; +using static System.Net.Mime.MediaTypeNames; namespace IPFBrowser { @@ -40,6 +43,10 @@ public partial class FrmMain : Form private Dictionary _fileTypes = new Dictionary(); + private IpfFile openIpfFile; + private bool openTextFile; + private IesFile openIesFile; + /// /// Initializes form. /// @@ -80,9 +87,11 @@ public FrmMain(string[] args) LblPreview.Dock = DockStyle.Fill; GridPreview.Dock = DockStyle.Fill; - // Disable extract buttons by default + // Disable extract / save buttons by default + BtnMenuSave.Enabled = false; BtnExtractPack.Enabled = false; - BtnExtractFile.Enabled = false; + BtnExtractFile.Enabled = false; + BtnSavePack.Enabled = false; // Hide empty lists SplMain.Visible = false; @@ -160,6 +169,11 @@ private void BtnOpen_Click(object sender, EventArgs e) /// private void Open(string filePath) { + if (_openedIpf != null) + { + _openedIpf.Close(); + } + if (Path.GetExtension(filePath) == ".ies") { this.ResetPreview(); @@ -183,11 +197,15 @@ private void Open(string filePath) foreach (var iesColumn in iesFile.Columns) row.Cells[i++].Value = iesRow[iesColumn.Name]; + row.Tag = iesRow.ClassName; + GridPreview.Rows.Add(row); } GridPreview.ResumeDrawing(); + openIesFile = iesFile; + GridPreview.Visible = true; }); @@ -211,7 +229,6 @@ private void Open(string filePath) try { _openedIpf = new Ipf(filePath); - _openedIpf.Load(); } catch (IOException) { @@ -242,9 +259,11 @@ private void Open(string filePath) TreeFolders.SelectedNode.Toggle(); } - // Show lists and enabled pack extract button - BtnExtractPack.Enabled = true; - SplMain.Visible = true; + // Show lists and enabled pack extract button + BtnMenuSave.Enabled = true; + BtnExtractPack.Enabled = true; + BtnSavePack.Enabled = true; + SplMain.Visible = true; } /// @@ -286,7 +305,7 @@ private void PopulateTreeView(TreeView treeView, IEnumerable paths, char { node = treeView.Nodes.Add(subPathAgg, subPath); insertedPaths.Add(subPathAgg, node); - } + } else { node = node.Nodes.Add(subPathAgg, subPath); @@ -313,23 +332,25 @@ private void LstFiles_SelectedIndexChanged(object sender, EventArgs e) return; } - BtnExtractFile.Enabled = true; + BtnExtractFile.Enabled = true; if (BtnPreview.Checked) Preview(); } - /// - /// Called when selected a node in the tree view, - /// lists files in node's folder in file list. - /// - /// - /// - private void TreeFolders_AfterSelect(object sender, TreeViewEventArgs e) + + /// + /// Called when selected a node in the tree view, + /// lists files in node's folder in file list. + /// + /// + /// + private void TreeFolders_AfterSelect(object sender, TreeViewEventArgs e) { var path = e.Node.FullPath.Replace('\\', '/') + '/'; - LstFiles.BeginUpdate(); + ResetPreview(); + LstFiles.BeginUpdate(); LstFiles.Items.Clear(); List paths; @@ -342,7 +363,10 @@ private void TreeFolders_AfterSelect(object sender, TreeViewEventArgs e) var lvi = LstFiles.Items.Add(fileName); //lvi.SubItems.Add("0 Byte"); - lvi.Tag = filePath; + + lvi.Tag = filePath; + if (_files.TryGetValue(filePath, out var ipfFile) && ipfFile.isModified) + lvi.ForeColor = Color.Blue; FileFormat fileType; if (_fileTypes.TryGetValue(ext, out fileType)) @@ -355,18 +379,74 @@ private void TreeFolders_AfterSelect(object sender, TreeViewEventArgs e) LstFiles.EndUpdate(); } - /// - /// Shows preview for selected file. - /// - private void Preview() + /// + /// Key Pressed on TreeFolders pane, used for deleting folders + /// + /// + /// + private void TreeFolders_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Delete) + { + if (TreeFolders.SelectedNode == null) + return; + + ResetPreview(); + + DeleteFolder(TreeFolders.SelectedNode); + } + } + + + /// + /// Attempt to delete a folder, its associated subfolders, and files + /// + /// + private void DeleteFolder(TreeNode folder) + { + TreeNode[] toRemove = new TreeNode[folder.Nodes.Count]; + folder.Nodes.CopyTo(toRemove, 0); + + foreach (TreeNode subfolder in toRemove) + { + DeleteFolder(subfolder); + } + + // now remove the files + + var path = folder.FullPath.Replace('\\', '/') + '/'; + + if (_folders.TryGetValue(path, out var files)) + { + foreach (var file in files) + { + if (_files.TryGetValue(file, out var ipfFile)) + { + _openedIpf.Files.Remove(ipfFile); + } + _files.Remove(file); + } + } + + _folders.Remove(path); + TreeFolders.Nodes.Remove(folder); + } + + + /// + /// Shows preview for selected file. + /// + private void Preview() { - if (LstFiles.SelectedIndices.Count == 0) - return; + if (LstFiles.SelectedIndices.Count == 0) + return; var selected = LstFiles.SelectedItems[0]; var fileName = (string)selected.Tag; var ipfFile = _files[fileName]; var ext = Path.GetExtension(fileName).ToLowerInvariant(); + + openIpfFile = ipfFile; var previewType = PreviewType.None; var lexer = Lexer.Null; @@ -392,11 +472,10 @@ private void Preview() Invoke((MethodInvoker)delegate { - TxtPreview.ReadOnly = false; TxtPreview.Text = text; - TxtPreview.ReadOnly = true; TxtPreview.Visible = true; }); + openTextFile = true; break; case PreviewType.Image: @@ -405,7 +484,7 @@ private void Preview() Invoke((MethodInvoker)delegate { using (var ms = new MemoryStream(imgData)) - ImgPreview.Image = Image.FromStream(ms); + ImgPreview.Image = System.Drawing.Image.FromStream(ms); ImgPreview.Size = ImgPreview.Image.Size; PnlImagePreview.Visible = true; }); @@ -473,6 +552,8 @@ private void Preview() foreach (var iesColumn in iesFile.Columns) GridPreview.Columns.Add(iesColumn.Name, iesColumn.Name); + int index = 0; + foreach (var iesRow in iesFile.Rows) { var row = new DataGridViewRow(); @@ -482,11 +563,15 @@ private void Preview() foreach (var iesColumn in iesFile.Columns) row.Cells[i++].Value = iesRow[iesColumn.Name]; - GridPreview.Rows.Add(row); + row.Tag = "" + index++; + + GridPreview.Rows.Add(row); } GridPreview.ResumeDrawing(); + openIesFile = iesFile; + GridPreview.Visible = true; }); break; @@ -578,7 +663,7 @@ private void Preview() private void BtnExit_Click(object sender, EventArgs e) { Close(); - } + } /// /// Sets lexer and styles for text preview. @@ -669,7 +754,12 @@ private void BtnAbout_Click(object sender, EventArgs e) /// private void ResetPreview() { - TxtPreview.Visible = false; + if (openIpfFile != null && openIpfFile.isModified) + SaveIpfFile(); + + openTextFile = false; + openIesFile = null; + TxtPreview.Visible = false; TxtPreview.Text = ""; PnlImagePreview.Visible = false; @@ -735,17 +825,18 @@ private void BtnExtractFile_Click(object sender, EventArgs e) File.WriteAllBytes(filePath, file); } - /// - /// Called when clicking Extract Client button, extracts selected - /// TOS client to selected destination. - /// - /// - /// Loads data first, followed by patch, to get the latest version - /// of all files found. - /// - /// - /// - private void BtnExtractClient_Click(object sender, EventArgs e) + + /// + /// Called when clicking Extract Client button, extracts selected + /// TOS client to selected destination. + /// + /// + /// Loads data first, followed by patch, to get the latest version + /// of all files found. + /// + /// + /// + private void BtnExtractClient_Click(object sender, EventArgs e) { FbdExtractPack.Description = "Select TOS folder."; FbdExtractPack.ShowNewFolderButton = false; @@ -871,5 +962,362 @@ private void ExtractFiles(IEnumerable ipfFiles, string extractPath) // blocks the main window. frmProgress.ShowDialog(); } - } + + /// + /// Value changed for Text Preview + /// + /// + /// + private void TxtPreview_TextChanged(object sender, System.EventArgs e) + { + if (openTextFile) { + openIpfFile.isModified = true; + LstFiles.SelectedItems[0].ForeColor = Color.Blue; + } + } + + + /// + /// Value changed for Grid Preview + /// + /// + /// + private void GridPreview_ValueChanged(object sender, DataGridViewCellEventArgs e) + { + if (openIesFile == null) + return; + var newValue = (string)GridPreview[e.ColumnIndex, e.RowIndex].Value; + if (e.RowIndex >= openIesFile.Rows.Count) + GridPreview_AddRow(); + var editedRow = openIesFile.Rows[e.RowIndex]; + var editedCol = openIesFile.Columns[e.ColumnIndex]; + if (editedCol.IsNumber) + { + var newFloat = 0f; + if (float.TryParse(newValue, out newFloat)) + { + editedRow[editedCol.Name] = float.Parse(newValue); + } + else + { + MessageBox.Show("Value must be numeric"); + GridPreview[e.ColumnIndex, e.RowIndex].Value = editedRow[editedCol.Name].ToString(); + } + } + else + { + editedRow[editedCol.Name] = newValue; + } + + openIpfFile.isModified = true; + LstFiles.SelectedItems[0].ForeColor = Color.Blue; + } + + + /// + /// Inserting a row in Grid Preview + /// This is done by right clicking the row header above where you want to insert, which is + /// not ideal, this can probably be moved to a proper context menu eventually + /// + private void GridPreview_InsertRow(object sender, DataGridViewCellMouseEventArgs e) + { + if (e.Button != MouseButtons.Right) + return; + + var afterRow = e.RowIndex; + if (afterRow > openIesFile.Rows.Count) + return; + + GridPreview.Rows.Insert(afterRow + 1, 1); + + // Some files have auto-generated classnames rather than having them in the file + // Need to shift these down to avoid duplicates + // For files which contain the Classid / Classname as part of the file these values aren't used. + for (int i = afterRow + 1; i < openIesFile.Rows.Count; i++) + { + openIesFile.Rows[i].ClassId++; + openIesFile.Rows[i].ClassName = "ClassName" + openIesFile.Rows[i].ClassId; + } + + var newRow = new IesRow(); + newRow.ClassId = openIesFile.Rows[afterRow].ClassId + 1; + newRow.ClassName = "ClassName" + newRow.ClassId; + + openIesFile.Rows.Insert(afterRow + 1, newRow); + + openIpfFile.isModified = true; + LstFiles.SelectedItems[0].ForeColor = Color.Blue; + } + + + /// + /// Added a row in Grid Preview + /// + private void GridPreview_AddRow() + { + var newRow = new IesRow(); + newRow.ClassId = openIesFile.Rows[openIesFile.Rows.Count - 1].ClassId + 1; + newRow.ClassName = "ClassName" + newRow.ClassId; + + openIesFile.Rows.Add(newRow); + + openIpfFile.isModified = true; + LstFiles.SelectedItems[0].ForeColor = Color.Blue; + } + + + /// + /// Deleted a row in Grid Preview + /// + /// + /// + private void GridPreview_DeleteRow(object sender, DataGridViewRowEventArgs e) + { + if (openIesFile == null) + return; + + int deletedIndex = int.Parse((string)e.Row.Tag); + + openIesFile.Rows.RemoveAt(deletedIndex); + + openIpfFile.isModified = true; + LstFiles.SelectedItems[0].ForeColor = Color.Blue; + } + + + /// + /// Saves changes to an IPF file + /// + private void SaveIpfFile() + { + if (openIesFile != null) + { + openIpfFile.content = openIesFile.ToBytes(); + return; + } + + if (openTextFile) + { + int byteCount = Encoding.UTF8.GetByteCount(TxtPreview.Text.ToCharArray(), 0, TxtPreview.TextLength); + openIpfFile.content = new byte[byteCount]; + Encoding.UTF8.GetEncoder().GetBytes(TxtPreview.Text.ToCharArray(), 0, TxtPreview.TextLength, openIpfFile.content, 0, true); + } + } + + + /// + /// Key Pressed on LstFiles window, used for deleting files + /// + /// + /// + private void LstFiles_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Delete) + { + if (LstFiles.SelectedIndices.Count == 0) + return; + + foreach (ListViewItem file in LstFiles.SelectedItems) + { + var fileName = (string)file.Tag; + _openedIpf.Files.Remove(_files[fileName]); + _files.Remove(fileName); + + var folderPath = TreeFolders.SelectedNode.FullPath.Replace('\\', '/') + '/'; + + if (_folders.TryGetValue(folderPath, out var paths)) + { + paths.Remove(fileName); + } + + LstFiles.Items.Remove(file); + } + + openIpfFile = null; + ResetPreview(); + } + } + + + /// + /// Drag Enter method for the LstFiles window + /// + /// + /// + private void LstFiles_DragEnter(object sender, DragEventArgs e) + { + if (TreeFolders.SelectedNode == null) return; + + if (!e.Data.GetDataPresent(DataFormats.FileDrop)) + return; + + e.Effect = DragDropEffects.Copy; + } + + + /// + /// Called when dropping a file in the LstFiles window + /// + /// + /// + private void LstFiles_DropFile(object sender, DragEventArgs e) + { + if (TreeFolders.SelectedNode == null) + return; + + if (!e.Data.GetDataPresent(DataFormats.FileDrop)) + return; + + string[] files = (string[]) e.Data.GetData(DataFormats.FileDrop); + if (files.Length > 1) + { + MessageBox.Show("Please drop only one file at a time"); + return; + } + + var ext = Path.GetExtension(files[0]); + + FileFormat fileType; + if (!_fileTypes.TryGetValue(ext, out fileType)) + { + MessageBox.Show("Can't add this type of file"); + return; + } + + var folderPath = TreeFolders.SelectedNode.FullPath.Replace('\\', '/') + "/"; + var newFilename = Path.GetFileName(files[0]); + var fullFileName = folderPath + newFilename; + + if (_files.Keys.Contains(fullFileName)) + { + var isPreviewedFile = BtnPreview.Checked && openIpfFile != null && _files[fullFileName] == openIpfFile; + + if (isPreviewedFile) + ResetPreview(); + + _files[fullFileName].isModified = true; + _files[fullFileName].content = File.ReadAllBytes(files[0]); + foreach (ListViewItem item in LstFiles.Items) + { + if ((string)item.Tag == fullFileName) + { + item.ForeColor = Color.Blue; + } + } + + if (isPreviewedFile) + Preview(); + + return; + } + + IpfFile newFile = new IpfFile(_openedIpf); + newFile.Path = newFilename; + newFile.PackFileName = folderPath; + newFile.FullPath = fullFileName; + newFile.isModified = true; + newFile.content = File.ReadAllBytes(files[0]); + + if (_folders.TryGetValue(folderPath, out var paths)) + { + paths.Add(fullFileName); + } + _files.Add(fullFileName, newFile); + _openedIpf.Files.Add(newFile); + + var lvi = LstFiles.Items.Add(newFilename); + + lvi.Tag = fullFileName; + lvi.ForeColor = Color.Blue; + lvi.ImageKey = fileType.Icon; + } + + + /// + /// Called when clicking Save, shows save dialog and saves the IPF file + /// + /// + /// + private void BtnSave_Click(object sender, EventArgs e) + { + if (openIpfFile != null && openIpfFile.isModified) + SaveIpfFile(); + + if (SfdIpfFile.ShowDialog() != DialogResult.OK) + return; + + var filePath = SfdIpfFile.FileName; + var reopenRequired = _openedIpf.Save(filePath); + + if (reopenRequired) + { + Open(filePath); + } + } + + + /// + /// Called when the version label is clicked, lets you change the version + /// + /// + /// + private void LblVersion_Click(object sender, EventArgs e) + { + string newVersion = "" + _openedIpf.Footer.NewVersion; + if (ShowInputDialog("New Version", ref newVersion) == DialogResult.OK) + { + if (uint.TryParse(newVersion, out var result)) + { + _openedIpf.Footer.NewVersion = result; + LblVersion.Text = "Version " + newVersion; + } + else + { + MessageBox.Show("Value must be numeric"); + } + } + } + + + private static DialogResult ShowInputDialog(string title, ref string value) + { + System.Drawing.Size size = new System.Drawing.Size(200, 70); + Form inputBox = new Form(); + + inputBox.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + inputBox.ShowInTaskbar = false; + inputBox.StartPosition = FormStartPosition.CenterParent; + inputBox.ClientSize = size; + inputBox.Text = title; + + System.Windows.Forms.TextBox textBox = new TextBox(); + textBox.Size = new System.Drawing.Size(size.Width - 10, 23); + textBox.Location = new System.Drawing.Point(5, 5); + textBox.Text = value; + inputBox.Controls.Add(textBox); + + Button okButton = new Button(); + okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + okButton.Name = "okButton"; + okButton.Size = new System.Drawing.Size(75, 23); + okButton.Text = "&OK"; + okButton.Location = new System.Drawing.Point(size.Width - 80 - 80, 39); + inputBox.Controls.Add(okButton); + + Button cancelButton = new Button(); + cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + cancelButton.Name = "cancelButton"; + cancelButton.Size = new System.Drawing.Size(75, 23); + cancelButton.Text = "&Cancel"; + cancelButton.Location = new System.Drawing.Point(size.Width - 80, 39); + inputBox.Controls.Add(cancelButton); + + inputBox.AcceptButton = okButton; + inputBox.CancelButton = cancelButton; + + DialogResult result = inputBox.ShowDialog(); + value = textBox.Text; + return result; + } + } } diff --git a/IPFBrowser/FrmMain.resx b/IPFBrowser/FrmMain.resx index 490ee25..5326875 100644 --- a/IPFBrowser/FrmMain.resx +++ b/IPFBrowser/FrmMain.resx @@ -118,14 +118,14 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 377, 17 + 331, 17 AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAA2 - EQAAAk1TRnQBSQFMAgEBCQEAAdABAAHQAQABEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo + EQAAAk1TRnQBSQFMAgEBCQEAAYABAQGAAQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo AwABQAMAATADAAEBAQABCAYAAQwYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5 AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA @@ -202,76 +202,79 @@ - 237, 17 + 225, 17 + + + 17, 17 - 487, 17 + 442, 17 - 597, 17 + 552, 17 - 854, 17 + 670, 17 - 990, 17 + 796, 17 - 17, 17 + 120, 17 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 @@ -310,58 +313,92 @@ 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 + /wJuY5fnBqzZIQAAAABJRU5ErkJggg== iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAavSURBVFhHzVZZTJRXFNaHNm3SNqUhPtlaWkMXfWkTW33Q - xgVFi0ubWJOK2mqtiTuyjTA4zCCICwoIKq4IVkWkFVFAlrggyKIsAygg2wwzMDIw+yaIX8/5Z6DGBwSr - Tb/kSy73v+c737n33DuM+T9jbIhEMjVi564MiTTCFhG5CzIXecxzssjoiyKR5BNe6wx5RVi1atX7Mll0 - bXxCIqqqa2A2mfA8eI6/8RpeyzGu8H+FsWKxxEcqi0RVVbUr1YvBazmGY1nDKTV6jA0ODlkcvXsfDAaD - SxoorGnDpqP5+CYwBVODUgXymOcKq9tcqyDEcCxrsJZTchTw8fFxj96zDzpdryBoNNuw4sAVfCfNxJLE - u1h5ph2r0xRYe1GJdRkq+J6shldkFlbSGl7L4FjWYC2X7IjxhlgsrS8rLxeE9CYL5oSnwye2FJKcTtxo - NkOh74NcY0eJ0oo8+vtohQ6hhRr4nqiAlyRdiGGwBmuxplP6xRj7k6/v5JgDcYIAMIDVhwqwMO4O8hpM - 0FqeoEnrQIXKihutZmQ3GXHxvgGpcj1iy3oRer0bv6ZWY01CgRDLYC3WZG1niuHxRnBISFbJnVKKf4J8 - OlfvvYWQZKuF5A8e2VHaYcH1Nkr+0ISMBiPO1BlwrFqH6NIeSIu1CC/uwQ8HbyKvinqCNFiLNVnbmWJ4 - vBskCrGrVWqg34Ggs+VYelSO67TNXHkZJb9ByXOaTfiryYRzD4w4WW9EQo0eUXd12FXRi6hyHfyutiLw - DB1hnwOsxZqs7UwxPNy3i3egv/8JBhxWfB97CytSW9Cu66Ntd1Y+mPx8owmn6VgO3zchRm7EgVojYmoM - 2FtFN+CeAQv238SA3SJosSZrO1MMj3GiEDEcDgf6rSb8eLgcv5xthbzL7jxz2vbB5CnEY41mxD8wI7HJ - IvBggxlxtCMxchOWJJShz2oUtFiTtZ0phse44O2hsNlscJj1WHa8Gr+ltaFYYcFVajg+c952rpyTJ1DS - pFYbjinsAnnMRuLI1NKkSjhMOkGLNVnbmWJ4CAasViushh78nNKAtRfakUeVc7dzw/GZ87bHU7Wc8KTq - MVI0fQJPqBw0ZycTViw7dV/QYK1RGQjaHup4+LAZJp0WEbkK/J7ejqTyHuGqcbdzw/GZc6VcNSdO0z/F - eWJKVx+OKR2IpHdBmt0maLAWa7K2M8XwcN+4cUtBXkEB9L1a5Nd1Yt2fKoiuqRBT0i1ctSgS54Z71gAn - P6cboHE/jnc8xpZcDfJq1dD3aHEtvwDrN20uZG1niuHx3ty5c5fLdkbRU6qD7pEakgItNl3tgii/E+G3 - u4Wrxt3ODcdHcKLDIVR+mniKjmPnXSPE1x5Bp1Gjt7cXO8IjMGnSpLmkPaJr+CbRY+NG/5ac3Gvo1nSh - WaFCQIEJAflahN3UILJUiz2V9PLV0f1vtOBICzWhkpqQGFVphV+uEQ/bVUJs1pWroGIGSPNj4ogeIn4u - 3dzd3WeE0N2trauHplOFptYORBVbEHjdBnGRHpHUE3uq6CjqnTuxu9aGkNsO7Cwyo7FFCY1ahWq5HJv9 - /FFSWob9sQnchFzciMBOx3t7LwgQbRdDLq9Fp6oDXYoW5Nd3I67chsAiDNH/FnCgzIa8ukfopDW8tqam - Btv8g5CaehZ2x2NUVNZid+zxUZl4mzjRy2te0Fa/QGRevoJOtRoqRTtUbc1QtTYRG11sQgfP0Tc1VX4p - 8zK2bPXH4SNHcelyFnp0BujNfWivPImSmGkjNsFH8Q5xopubm/f6DRvawnaEIzMzC/cqK6Hs6IBCqRTI - Y57jb2FhEmz1CxiIO5iI5JQz+CPtIs5lZNNtoN8WUxlQMRNF+2eOygTvxHjiFE9PT9/Va9YUUXUOrnDz - lm0Cecxz/M3Dw2M5rZ0fLApFYtJxJCWno+RcGJDhAfTQj5OphkzMHpUJBvcE/5M5gfgVcQZxFnG2izzm - Of7G3f4pcXqAKByXDm3C0+QPgbTPycSXZOLuS5vg3WAjfJf5QeFX7VnyHH/jNSzKuzY9UzYHDbFfwJ78 - GZmYRCaIQybmkIlZyImaMY3Wsv4rxZCJC7KFUMRPgC2ZdmHIxD3AUgdUzkdZghfvxFsc9KoxZCJNtvg5 - E5Ppn81aoGgmSuIEAx9wwOvAMyYWoT52MuynycQFMpD5NXDLB7fj5rGBEf1QvSyeOY5FUMZ/hL4UT+cu - 5E5Bcfz8126A8c9OhC/EeelCpEl9kC5dwMm/JboRXzsGTXDCwSvMY57jb/8JOBFXO3h9eexKPmbM3zvF - Dx9jcJyVAAAAAElFTkSuQmCC + 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 + Dpt0eOMpAAAAAElFTkSuQmCC diff --git a/IPFBrowser/IPFBrowser.csproj b/IPFBrowser/IPFBrowser.csproj index b82b4f2..e73b7a8 100644 --- a/IPFBrowser/IPFBrowser.csproj +++ b/IPFBrowser/IPFBrowser.csproj @@ -63,6 +63,7 @@ +