/// <summary> /// Extract IMG archive to directory /// </summary> /// <param name="sourceArchiveFileName">Source archive file name</param> /// <param name="destinationDirectoryName">Destination directory name</param> /// <param name="entryNameEncoding">Entry name encoding</param> public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding) { if ((sourceArchiveFileName != null) && (destinationDirectoryName != null) && (entryNameEncoding != null)) { IMGArchive archive = Open(sourceArchiveFileName, EIMGArchiveMode.Read, entryNameEncoding); if (archive != null) { if (!(Directory.Exists(destinationDirectoryName))) { Directory.CreateDirectory(destinationDirectoryName); } foreach (IMGArchiveEntry entry in archive.Entries) { string file_path = Path.Combine(destinationDirectoryName, entry.FullName); string directory_path = Path.GetDirectoryName(file_path); if (!(Directory.Exists(directory_path))) { Directory.CreateDirectory(directory_path); } using (FileStream file_stream = File.Open(file_path, FileMode.Create)) { using (Stream entry_stream = entry.Open()) { byte[] buffer = new byte[2048]; int read_bytes = 0; while ((read_bytes = entry_stream.Read(buffer, 0, buffer.Length)) > 0) { file_stream.Write(buffer, 0, read_bytes); } } } } } } }
/// <summary> /// Constructor /// </summary> /// <param name="archive">Archive</param> /// <param name="offset">Data offset</param> /// <param name="length">Length in bytes</param> /// <param name="fullName">Full name</param> internal IMGArchiveEntry(IMGArchive archive, long offset, int length, string fullName) { this.archive = archive; this.offset = offset; this.length = length; this.fullName = fullName; }
/// <summary> /// Constructor /// </summary> /// <param name="archive">Archive</param> /// <param name="offset">Data offset</param> /// <param name="length">Length in bytes</param> /// <param name="fullName">Full name</param> /// <param name="isNewEntry">Is new entry</param> internal IMGArchiveEntry(IMGArchive archive, long offset, int length, string fullName, bool isNewEntry) { this.archive = archive; this.offset = offset; this.length = length; this.fullName = fullName; this.isNewEntry = isNewEntry; }
/// <summary> /// Write IMG archive entry /// </summary> /// <param name="tempArchive">Temporary IMG archive</param> /// <param name="newEntry">New IMG archive entry</param> /// <param name="stream">Stream</param> private void WriteEntry(IMGArchive tempArchive, IIMGArchiveEntry newEntry, Stream stream) { using (IIMGArchiveEntryStream temporary_img_archive_entry_stream = tempArchive.InternalEntries[newEntry.FullName.ToLower()].Open()) { byte[] temp_data = new byte[temporary_img_archive_entry_stream.Stream.Length]; temporary_img_archive_entry_stream.Stream.Read(temp_data, 0, temp_data.Length); stream.Write(temp_data, 0, temp_data.Length); } }
/// <summary> /// Write IMG archive entry /// </summary> /// <param name="tempArchive">Temporary IMG archive</param> /// <param name="newEntry">New IMG archive entry</param> /// <param name="stream">Stream</param> private void WriteEntry(IMGArchive tempArchive, IMGArchiveEntry newEntry, Stream stream) { using (Stream temp_stream = tempArchive.entries[newEntry.FullName.ToLower()].Open()) { byte[] temp_data = new byte[temp_stream.Length]; temp_stream.Read(temp_data, 0, temp_data.Length); stream.Write(temp_data, 0, temp_data.Length); } }
/// <summary> /// Extract IMG archive to directory /// </summary> /// <param name="sourceIMGArchiveFilePath">Source IMG archive file path</param> /// <param name="destinationDirectoryPath">Destination directory path</param> /// <param name="entryNameEncoding">Entry name encoding</param> public static void ExtractToDirectory(string sourceIMGArchiveFilePath, string destinationDirectoryPath, Encoding entryNameEncoding) { if (sourceIMGArchiveFilePath == null) { throw new ArgumentNullException(nameof(sourceIMGArchiveFilePath)); } if (destinationDirectoryPath == null) { throw new ArgumentNullException(nameof(destinationDirectoryPath)); } if (entryNameEncoding == null) { throw new ArgumentNullException(nameof(entryNameEncoding)); } IMGArchive img_archive = Open(sourceIMGArchiveFilePath, EIMGArchiveAccessMode.Read, entryNameEncoding) as IMGArchive; if (img_archive != null) { if (!(Directory.Exists(destinationDirectoryPath))) { Directory.CreateDirectory(destinationDirectoryPath); } foreach (IMGArchiveEntry img_archive_entry in img_archive.InternalEntries.Values) { string file_path = Path.Combine(destinationDirectoryPath, img_archive_entry.FullName); string directory_path = Path.GetDirectoryName(file_path); if (!(Directory.Exists(directory_path))) { Directory.CreateDirectory(directory_path); } using (FileStream img_archive_entry_file_stream = File.Open(file_path, FileMode.Create)) { using (IIMGArchiveEntryStream img_archive_entry_stream = img_archive_entry.Open()) { img_archive_entry_file_stream.Position = 0L; img_archive_entry_stream.Stream.Position = 0L; img_archive_entry_file_stream.CopyTo(img_archive_entry_stream.Stream); } } } } }
/// <summary> /// Commit IMG archive entry /// </summary> /// <param name="entry">IMG archive entry</param> /// <param name="entryStream">IMG archive entry stream</param> internal void CommitEntry(IIMGArchiveEntry entry, IIMGArchiveEntryStream entryStream) { try { if (AccessMode != EIMGArchiveAccessMode.Read) { string temporary_img_archive_path = Path.Combine(Path.GetTempPath(), $"{ Guid.NewGuid() }.img"); if (File.Exists(temporary_img_archive_path)) { File.Delete(temporary_img_archive_path); } using (FileStream temporary_img_archive_file_stream = File.Open(temporary_img_archive_path, FileMode.Create)) { Stream.Seek(0L, SeekOrigin.Begin); Stream.CopyTo(temporary_img_archive_file_stream); } using (IMGArchive temporary_img_archive = IMGFile.OpenRead(temporary_img_archive_path) as IMGArchive) { if (temporary_img_archive != null) { InternalEntries.Clear(); Stream.SetLength(0L); using (BinaryWriter stream_binary_writer = new BinaryWriter(Stream, EntryNameEncoding, true)) { int entry_count = temporary_img_archive.InternalEntries.Values.Count + ((entry == null) ? 0 : (entry.IsNewEntry ? 1 : 0)); int first_entry_offset = ((((entry_count * 32) % 2048) == 0) ? ((entry_count * 32) / 2048) : (((entry_count * 32) / 2048) + 1)); int current_entry_offset = first_entry_offset; List <IMGArchiveEntry> new_entries = new List <IMGArchiveEntry>(); Dictionary <string, IIMGArchiveEntry> temporary_entries = new Dictionary <string, IIMGArchiveEntry>(temporary_img_archive.InternalEntries); long missing_byte_count; stream_binary_writer.Write(IMGFile.SupportedIMGArchiveVersion); stream_binary_writer.Write((short)entry_count); if ((entry != null) && entry.IsNewEntry) { temporary_entries.Add(entry.FullName.ToLower(), new IMGArchiveEntry(temporary_img_archive, 0L, (int)(entryStream.Stream.Length), entry.FullName)); } foreach (KeyValuePair <string, IIMGArchiveEntry> temporary_entry in temporary_entries) { int entry_length = (int)((entry == null) ? (((temporary_entry.Value.Length % 2048L) == 0L) ? (temporary_entry.Value.Length / 2048L) : ((temporary_entry.Value.Length / 2048L) + 1L)) : ((entry.FullName.ToLower() == temporary_entry.Key) ? (((entryStream.Stream.Length % 2048L) == 0L) ? (entryStream.Stream.Length / 2048L) : ((entryStream.Stream.Length / 2048L) + 1)) : (((temporary_entry.Value.Length % 2048L) == 0L) ? (temporary_entry.Value.Length / 2048L) : ((temporary_entry.Value.Length / 2048L) + 1L)))); byte[] name_bytes_raw = EntryNameEncoding.GetBytes(temporary_entry.Value.FullName); byte[] name_bytes = new byte[24]; Array.Copy(name_bytes_raw, name_bytes, Math.Min(name_bytes_raw.Length, name_bytes.Length)); stream_binary_writer.Write(current_entry_offset); stream_binary_writer.Write((short)entry_length); stream_binary_writer.Write(name_bytes); IMGArchiveEntry new_entry = new IMGArchiveEntry(this, current_entry_offset * 2048, entry_length * 2048, temporary_entry.Value.FullName); new_entries.Add(new_entry); InternalEntries.Add(new_entry.FullName.ToLower(), new_entry); current_entry_offset += entry_length; } foreach (IMGArchiveEntry new_entry in new_entries) { stream_binary_writer.Flush(); missing_byte_count = new_entry.Offset - Stream.Length; if (missing_byte_count > 0L) { stream_binary_writer.Write(new byte[missing_byte_count]); } if (entry != null) { if (entry.FullName == new_entry.FullName) { byte[] data = new byte[entryStream.Stream.Length]; entryStream.Stream.Seek(0L, SeekOrigin.Begin); entryStream.Stream.Read(data, 0, data.Length); Stream.Write(data, 0, data.Length); } else { WriteEntry(temporary_img_archive, new_entry, Stream); } } else { WriteEntry(temporary_img_archive, new_entry, Stream); } } stream_binary_writer.Flush(); missing_byte_count = current_entry_offset - (Stream.Length / 2048); if (missing_byte_count > 0) { stream_binary_writer.Write(new byte[missing_byte_count]); } stream_binary_writer.Flush(); } } } if (File.Exists(temporary_img_archive_path)) { File.Delete(temporary_img_archive_path); } } } catch (Exception e) { Console.Error.WriteLine(e); } }
/// <summary> /// Open IMG archive /// </summary> /// <param name="archiveFileName">Archive file name</param> /// <param name="archiveMode">Archive mode</param> /// <param name="entryNameEncoding">Entry name encoding</param> /// <returns>IMG archive</returns> /// <exception cref="InvalidOperationException">Return value is null</exception> /// <exception cref="ArgumentNullException">Archive file name is null</exception> /// <exception cref="FileNotFoundException">Archive file not found</exception> /// <exception cref="InvalidDataException">Archive error</exception> public static IMGArchive Open(string archiveFileName, EIMGArchiveMode archiveMode, Encoding entryNameEncoding) { IMGArchive ret = null; try { Encoding encoding = ((entryNameEncoding == null) ? Encoding.UTF8 : entryNameEncoding); if (archiveFileName != null) { if ((archiveMode == EIMGArchiveMode.Create) || File.Exists(archiveFileName)) { ret = new IMGArchive(File.Open(archiveFileName, (archiveMode == EIMGArchiveMode.Create) ? FileMode.Create : FileMode.Open, (archiveMode != EIMGArchiveMode.Read) ? FileAccess.ReadWrite : FileAccess.Read), archiveMode, entryNameEncoding); if (archiveMode == EIMGArchiveMode.Create) { byte[] header_bytes = new byte[] { 0x56, 0x45, 0x52, 0x32, 0x0, 0x0, 0x0, 0x0 }; ret.Stream.Write(header_bytes, 0, header_bytes.Length); while (ret.Stream.Length < 2048) { ret.Stream.WriteByte(0); } } else { byte[] version_bytes = new byte[4]; if (ret.Stream.Read(version_bytes, 0, version_bytes.Length) == version_bytes.Length) { string version = Encoding.UTF8.GetString(version_bytes); byte[] int_bytes = new byte[4]; if (ret.Stream.Read(int_bytes, 0, int_bytes.Length) == int_bytes.Length) { uint num_entries = int_bytes[0] | (((uint)(int_bytes[1])) << 8) | (((uint)(int_bytes[2])) << 16) | (((uint)(int_bytes[3])) << 24); if ((version == "VER2") && (ret.Stream.Length >= (num_entries * 8))) { for (uint num_entry = 0U; num_entry != num_entries; num_entry++) { if (ret.Stream.Read(int_bytes, 0, int_bytes.Length) == int_bytes.Length) { long offset = (int_bytes[0] | (((long)(int_bytes[1])) << 8) | (((long)(int_bytes[2])) << 16) | (((long)(int_bytes[3])) << 24)) * 2048L; byte[] short_bytes = new byte[2]; if (ret.Stream.Read(short_bytes, 0, short_bytes.Length) == short_bytes.Length) { int length = (short_bytes[0] | (short_bytes[1] << 8)) * 2048; if (ret.Stream.Read(short_bytes, 0, short_bytes.Length) == short_bytes.Length) { byte[] full_name_bytes_raw = new byte[24]; if (ret.Stream.Read(full_name_bytes_raw, 0, full_name_bytes_raw.Length) == full_name_bytes_raw.Length) { int full_name_bytes_count = IMGUtils.GetNullTerminatedByteStringLength(full_name_bytes_raw); if (full_name_bytes_count > 0) { string full_name = encoding.GetString(full_name_bytes_raw, 0, full_name_bytes_count); ret.entries.Add(full_name.ToLower(), new IMGArchiveEntry(ret, offset, length, full_name)); } else { throw new InvalidDataException("IMG entry name can't be empty."); } } else { throw new InvalidDataException("IMG entry name is missing."); } } else { throw new InvalidDataException("IMG entry length is missing."); } } else { throw new InvalidDataException("IMG entry length is missing."); } } else { throw new InvalidDataException("IMG entry offset is missing."); } } } else { throw new InvalidDataException("\"" + archiveFileName + "\" is not an IMG file"); } } else { throw new InvalidDataException("\"" + archiveFileName + "\" is not an IMG file"); } } else { throw new InvalidDataException("\"" + archiveFileName + "\" is not an IMG file"); } } } else { throw new FileNotFoundException("Archive not found", archiveFileName); } } else { throw new ArgumentNullException("archiveFileName"); } if (ret == null) { throw new InvalidOperationException("Return value is null"); } } catch (Exception e) { if (ret != null) { ret.Dispose(); ret = null; } throw e; } return(ret); }
/// <summary> /// Open IMG archive /// </summary> /// <param name="imgArchiveFilePath">IMG archive file path</param> /// <param name="accessMode">IMG archive access mode</param> /// <param name="entryNameEncoding">IMG archive entry name encoding</param> /// <returns>IMG archive</returns> /// <exception cref="InvalidOperationException">Return value is null</exception> /// <exception cref="ArgumentNullException">Archive file name is null</exception> /// <exception cref="FileNotFoundException">Archive file not found</exception> /// <exception cref="InvalidDataException">Archive error</exception> public static IIMGArchive Open(string imgArchiveFilePath, EIMGArchiveAccessMode accessMode, Encoding entryNameEncoding) { if (imgArchiveFilePath == null) { throw new ArgumentNullException(nameof(imgArchiveFilePath)); } if (entryNameEncoding == null) { throw new ArgumentNullException(nameof(entryNameEncoding)); } IMGArchive ret = null; try { if ((accessMode != EIMGArchiveAccessMode.Create) && (!File.Exists(imgArchiveFilePath))) { throw new FileNotFoundException("Archive not found", imgArchiveFilePath); } FileStream archive_file_stream = File.Open(imgArchiveFilePath, (accessMode == EIMGArchiveAccessMode.Create) ? FileMode.Create : FileMode.Open, (accessMode != EIMGArchiveAccessMode.Read) ? FileAccess.ReadWrite : FileAccess.Read); if (archive_file_stream == null) { throw new FileNotFoundException("Archive not found", imgArchiveFilePath); } Dictionary <string, IIMGArchiveEntry> img_archive_entries = new Dictionary <string, IIMGArchiveEntry>(); ret = new IMGArchive(archive_file_stream, accessMode, entryNameEncoding, img_archive_entries); if (accessMode == EIMGArchiveAccessMode.Create) { byte[] new_data = new byte[2048]; new_data[0] = 0x56; new_data[1] = 0x45; new_data[2] = 0x52; new_data[3] = 0x32; archive_file_stream.Write(new_data, 0, new_data.Length); } else { using (BinaryReader archive_file_stream_binary_reader = new BinaryReader(archive_file_stream, entryNameEncoding, true)) { int archive_version = archive_file_stream_binary_reader.ReadInt32(); if (archive_version != SupportedIMGArchiveVersion) { throw new InvalidDataException($"Invalid IMG archive version { archive_version }."); } uint num_entries = archive_file_stream_binary_reader.ReadUInt32(); if (archive_file_stream.Length < (num_entries * 8)) { throw new InvalidDataException($"IMG archive file size is smaller than expected."); } for (uint num_entry = 0U; num_entry != num_entries; num_entry++) { long offset = archive_file_stream_binary_reader.ReadInt32() * 2048L; int length = archive_file_stream_binary_reader.ReadInt16() * 2048; short dummy = archive_file_stream_binary_reader.ReadInt16(); byte[] full_name_bytes_raw = new byte[24]; if (archive_file_stream_binary_reader.Read(full_name_bytes_raw, 0, full_name_bytes_raw.Length) != full_name_bytes_raw.Length) { throw new InvalidDataException("IMG entry name is missing."); } int full_name_bytes_count = IMGUtilities.GetNullTerminatedByteStringLength(full_name_bytes_raw); if (full_name_bytes_count <= 0) { throw new InvalidDataException("IMG entry name can't be empty."); } string full_name = entryNameEncoding.GetString(full_name_bytes_raw, 0, full_name_bytes_count); img_archive_entries.Add(full_name.ToLower(), new IMGArchiveEntry(ret, offset, length, full_name)); } } } if (ret == null) { throw new InvalidOperationException("Return value is null"); } } catch (Exception e) { ret?.Dispose(); throw e; } return(ret); }
/// <summary> /// Commit IMG archive entry /// </summary> /// <param name="entry">IMG archive entry</param> /// <param name="stream">IMG archive entry stream</param> internal void CommitEntry(IMGArchiveEntry entry, IMGArchiveEntryStream stream) { try { if (mode != EIMGArchiveMode.Read) { string temp_path = Path.GetTempPath() + Guid.NewGuid().ToString() + ".img"; if (File.Exists(temp_path)) { File.Delete(temp_path); } using (FileStream temp_stream = File.Open(temp_path, FileMode.Create)) { this.stream.Seek(0L, SeekOrigin.Begin); byte[] buffer = new byte[2048]; int read_bytes = 0; while ((read_bytes = this.stream.Read(buffer, 0, buffer.Length)) > 0) { temp_stream.Write(buffer, 0, read_bytes); } } using (IMGArchive temp_archive = IMGFile.OpenRead(temp_path)) { entries.Clear(); this.stream.SetLength(0L); int entry_count = temp_archive.entries.Values.Count + ((entry == null) ? 0 : (entry.IsNewEntry ? 1 : 0)); int first_entry_offset = ((((entry_count * 32) % 2048) == 0) ? ((entry_count * 32) / 2048) : (((entry_count * 32) / 2048) + 1)); int current_entry_offset = first_entry_offset; List <IMGArchiveEntry> new_entries = new List <IMGArchiveEntry>(); byte[] header_bytes = new byte[] { 0x56, 0x45, 0x52, 0x32, (byte)(entry_count & 0xFF), (byte)((entry_count >> 8) & 0xFF), 0x0, 0x0 }; this.stream.Write(header_bytes, 0, header_bytes.Length); Dictionary <string, IMGArchiveEntry> temp_entries = new Dictionary <string, IMGArchiveEntry>(temp_archive.entries); if (entry != null) { if (entry.IsNewEntry) { temp_entries.Add(entry.FullName.ToLower(), new IMGArchiveEntry(temp_archive, 0L, (int)(stream.Length), entry.FullName)); } } foreach (KeyValuePair <string, IMGArchiveEntry> temp_entry in temp_entries) { int entry_length = (int)((entry == null) ? (((temp_entry.Value.Length % 2048L) == 0L) ? (temp_entry.Value.Length / 2048L) : ((temp_entry.Value.Length / 2048L) + 1L)) : ((entry.FullName.ToLower() == temp_entry.Key) ? (((stream.Length % 2048L) == 0L) ? (stream.Length / 2048L) : ((stream.Length / 2048L) + 1)) : (((temp_entry.Value.Length % 2048L) == 0L) ? (temp_entry.Value.Length / 2048L) : ((temp_entry.Value.Length / 2048L) + 1L)))); byte[] entry_offset_bytes = new byte[] { (byte)(current_entry_offset & 0xFF), (byte)((current_entry_offset >> 8) & 0xFF), (byte)((current_entry_offset >> 16) & 0xFF), (byte)((current_entry_offset >> 24) & 0xFF) }; byte[] entry_length_bytes = new byte[] { (byte)(entry_length & 0xFF), (byte)((entry_length >> 8) & 0xFF), 0x0, 0x0 }; byte[] name_bytes_raw = entryNameEncoding.GetBytes(temp_entry.Value.FullName); byte[] name_bytes = new byte[24]; Array.Copy(name_bytes_raw, name_bytes, Math.Min(name_bytes_raw.Length, name_bytes.Length)); this.stream.Write(entry_offset_bytes, 0, entry_offset_bytes.Length); this.stream.Write(entry_length_bytes, 0, entry_length_bytes.Length); this.stream.Write(name_bytes, 0, name_bytes.Length); IMGArchiveEntry new_entry = new IMGArchiveEntry(this, current_entry_offset * 2048, entry_length * 2048, temp_entry.Value.FullName); new_entries.Add(new_entry); entries.Add(new_entry.FullName.ToLower(), new_entry); current_entry_offset += entry_length; } foreach (IMGArchiveEntry new_entry in new_entries) { while (this.stream.Length < new_entry.Offset) { this.stream.WriteByte(0); } if (entry != null) { if (entry.FullName == new_entry.FullName) { byte[] data = new byte[stream.Length]; stream.Seek(0L, SeekOrigin.Begin); stream.Read(data, 0, data.Length); this.stream.Write(data, 0, data.Length); } else { WriteEntry(temp_archive, new_entry, this.stream); } } else { WriteEntry(temp_archive, new_entry, this.stream); } } while ((this.stream.Length / 2048) < current_entry_offset) { this.stream.WriteByte(0); } } if (File.Exists(temp_path)) { File.Delete(temp_path); } } } catch (Exception e) { Console.Error.WriteLine(e.Message); } }