diff --git a/BundleFormat/BundleArchive.cs b/BundleFormat/BundleArchive.cs index 674138a..1d4166a 100644 --- a/BundleFormat/BundleArchive.cs +++ b/BundleFormat/BundleArchive.cs @@ -160,6 +160,45 @@ private void ProcessRST(string rst) } } + private void RebuildRST() + { + XmlDocument doc = new XmlDocument(); + XmlElement root = doc.CreateElement("ResourceStringTable"); + doc.AppendChild(root); + + bool hasNamedResources = false; + + foreach (BundleEntry entry in Entries) + { + string name = entry.DebugInfo.Name?.Trim(); + if (string.IsNullOrEmpty(name)) + continue; + + XmlElement resource = doc.CreateElement("Resource"); + resource.SetAttribute("id", entry.ID.ToString("X8")); + resource.SetAttribute("name", name); + resource.SetAttribute( + "type", + string.IsNullOrWhiteSpace(entry.DebugInfo.TypeName) + ? entry.Type.ToString() + : entry.DebugInfo.TypeName + ); + + root.AppendChild(resource); + hasNamedResources = true; + } + + if (!hasNamedResources) + { + ResourceStringTable = null; + Flags &= ~Flags.HasResourceStringTable; + return; + } + + ResourceStringTable = doc.OuterXml; + Flags |= Flags.HasResourceStringTable; + } + public static BundleArchive Read(string path) { using (Stream s = File.OpenRead(path)) @@ -580,6 +619,8 @@ public void Write(string path) public void Write(BinaryWriter2 bw) { + RebuildRST(); + bw.Write(BND2Magic); bw.Write(Version); bw.Write((int)Platform); diff --git a/BundleManager/ImportResourceForm.Designer.cs b/BundleManager/ImportResourceForm.Designer.cs new file mode 100644 index 0000000..dc23dcb --- /dev/null +++ b/BundleManager/ImportResourceForm.Designer.cs @@ -0,0 +1,270 @@ +namespace BundleManager +{ + partial class ImportResourceForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + importButton = new System.Windows.Forms.Button(); + resourceIDTextBox = new System.Windows.Forms.TextBox(); + label1 = new System.Windows.Forms.Label(); + resourceNameTextBox = new System.Windows.Forms.TextBox(); + label2 = new System.Windows.Forms.Label(); + label3 = new System.Windows.Forms.Label(); + secondaryDataPathSelectorButton = new System.Windows.Forms.Button(); + autoUpdateIDCheckBox = new System.Windows.Forms.CheckBox(); + selectTypeLabel = new System.Windows.Forms.Label(); + resourceTypeComboBox = new System.Windows.Forms.ComboBox(); + tertiaryDataPathSelectorButton = new System.Windows.Forms.Button(); + primaryImportPathLabel = new System.Windows.Forms.Label(); + primaryResourcePathTextBox = new System.Windows.Forms.TextBox(); + secondaryImportPathLabel = new System.Windows.Forms.Label(); + secondaryResourcePathTextBox = new System.Windows.Forms.TextBox(); + tertiaryImportPathLabel = new System.Windows.Forms.Label(); + tertiaryResourcePathTextBox = new System.Windows.Forms.TextBox(); + primaryDataPathSelectorButton = new System.Windows.Forms.Button(); + SuspendLayout(); + // + // importButton + // + importButton.Location = new System.Drawing.Point(562, 258); + importButton.Name = "importButton"; + importButton.Size = new System.Drawing.Size(88, 23); + importButton.TabIndex = 0; + importButton.Text = "Import"; + importButton.UseVisualStyleBackColor = true; + importButton.Click += importButton_Click; + // + // resourceIDTextBox + // + resourceIDTextBox.CharacterCasing = System.Windows.Forms.CharacterCasing.Upper; + resourceIDTextBox.Location = new System.Drawing.Point(563, 27); + resourceIDTextBox.MaxLength = 8; + resourceIDTextBox.Name = "resourceIDTextBox"; + resourceIDTextBox.Size = new System.Drawing.Size(85, 23); + resourceIDTextBox.TabIndex = 1; + resourceIDTextBox.Text = "XXXXXXXX"; + // + // label1 + // + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(563, 9); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(88, 15); + label1.TabIndex = 2; + label1.Text = "ID to import as:"; + // + // resourceNameTextBox + // + resourceNameTextBox.Location = new System.Drawing.Point(12, 27); + resourceNameTextBox.Name = "resourceNameTextBox"; + resourceNameTextBox.Size = new System.Drawing.Size(528, 23); + resourceNameTextBox.TabIndex = 3; + resourceNameTextBox.Text = "bundlemanager://my_really_cool_and_extremely_long_resource_name"; + resourceNameTextBox.TextChanged += resourceNameTextBox_TextChanged; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new System.Drawing.Point(12, 9); + label2.Name = "label2"; + label2.Size = new System.Drawing.Size(242, 15); + label2.TabIndex = 4; + label2.Text = "What would you like to name your resource?"; + // + // label3 + // + label3.AutoSize = true; + label3.Location = new System.Drawing.Point(546, 31); + label3.Name = "label3"; + label3.Size = new System.Drawing.Size(15, 15); + label3.TabIndex = 5; + label3.Text = ">"; + // + // secondaryDataPathSelectorButton + // + secondaryDataPathSelectorButton.Location = new System.Drawing.Point(508, 175); + secondaryDataPathSelectorButton.Name = "secondaryDataPathSelectorButton"; + secondaryDataPathSelectorButton.Size = new System.Drawing.Size(140, 23); + secondaryDataPathSelectorButton.TabIndex = 6; + secondaryDataPathSelectorButton.Text = "Select Secondary Data"; + secondaryDataPathSelectorButton.UseVisualStyleBackColor = true; + secondaryDataPathSelectorButton.Click += secondaryDataPathSelectorButton_Click; + // + // autoUpdateIDCheckBox + // + autoUpdateIDCheckBox.AutoSize = true; + autoUpdateIDCheckBox.Location = new System.Drawing.Point(546, 56); + autoUpdateIDCheckBox.Name = "autoUpdateIDCheckBox"; + autoUpdateIDCheckBox.Size = new System.Drawing.Size(106, 19); + autoUpdateIDCheckBox.TabIndex = 7; + autoUpdateIDCheckBox.Text = "Auto update ID"; + autoUpdateIDCheckBox.UseVisualStyleBackColor = true; + autoUpdateIDCheckBox.CheckedChanged += autoUpdateIDCheckBox_CheckedChanged; + // + // selectTypeLabel + // + selectTypeLabel.AutoSize = true; + selectTypeLabel.Location = new System.Drawing.Point(11, 60); + selectTypeLabel.Name = "selectTypeLabel"; + selectTypeLabel.Size = new System.Drawing.Size(202, 15); + selectTypeLabel.TabIndex = 9; + selectTypeLabel.Text = "Select the resource type to import as:"; + // + // resourceTypeComboBox + // + resourceTypeComboBox.FormattingEnabled = true; + resourceTypeComboBox.Location = new System.Drawing.Point(11, 78); + resourceTypeComboBox.Name = "resourceTypeComboBox"; + resourceTypeComboBox.Size = new System.Drawing.Size(229, 23); + resourceTypeComboBox.TabIndex = 10; + resourceTypeComboBox.Text = "AptData"; + // + // tertiaryDataPathSelectorButton + // + tertiaryDataPathSelectorButton.Location = new System.Drawing.Point(508, 220); + tertiaryDataPathSelectorButton.Name = "tertiaryDataPathSelectorButton"; + tertiaryDataPathSelectorButton.Size = new System.Drawing.Size(140, 23); + tertiaryDataPathSelectorButton.TabIndex = 11; + tertiaryDataPathSelectorButton.Text = "Select Tertiary Data"; + tertiaryDataPathSelectorButton.UseVisualStyleBackColor = true; + tertiaryDataPathSelectorButton.Click += tertiaryDataPathSelectorButton_Click; + // + // primaryImportPathLabel + // + primaryImportPathLabel.AutoSize = true; + primaryImportPathLabel.Location = new System.Drawing.Point(12, 114); + primaryImportPathLabel.Name = "primaryImportPathLabel"; + primaryImportPathLabel.Size = new System.Drawing.Size(104, 15); + primaryImportPathLabel.TabIndex = 13; + primaryImportPathLabel.Text = "Primary data path:"; + primaryImportPathLabel.TextAlign = System.Drawing.ContentAlignment.BottomLeft; + // + // primaryResourcePathTextBox + // + primaryResourcePathTextBox.Location = new System.Drawing.Point(12, 132); + primaryResourcePathTextBox.Name = "primaryResourcePathTextBox"; + primaryResourcePathTextBox.Size = new System.Drawing.Size(490, 23); + primaryResourcePathTextBox.TabIndex = 14; + primaryResourcePathTextBox.Text = "my_really_cool_and_extremely_long_primary_resource_path"; + // + // secondaryImportPathLabel + // + secondaryImportPathLabel.AutoSize = true; + secondaryImportPathLabel.Location = new System.Drawing.Point(12, 158); + secondaryImportPathLabel.Name = "secondaryImportPathLabel"; + secondaryImportPathLabel.Size = new System.Drawing.Size(118, 15); + secondaryImportPathLabel.TabIndex = 15; + secondaryImportPathLabel.Text = "Secondary data path:"; + secondaryImportPathLabel.TextAlign = System.Drawing.ContentAlignment.BottomLeft; + // + // secondaryResourcePathTextBox + // + secondaryResourcePathTextBox.Location = new System.Drawing.Point(12, 176); + secondaryResourcePathTextBox.Name = "secondaryResourcePathTextBox"; + secondaryResourcePathTextBox.Size = new System.Drawing.Size(490, 23); + secondaryResourcePathTextBox.TabIndex = 16; + secondaryResourcePathTextBox.Text = "my_really_cool_and_extremely_long_secondary_resource_path"; + // + // tertiaryImportPathLabel + // + tertiaryImportPathLabel.AutoSize = true; + tertiaryImportPathLabel.Location = new System.Drawing.Point(12, 202); + tertiaryImportPathLabel.Name = "tertiaryImportPathLabel"; + tertiaryImportPathLabel.Size = new System.Drawing.Size(101, 15); + tertiaryImportPathLabel.TabIndex = 17; + tertiaryImportPathLabel.Text = "Tertiary data path:"; + tertiaryImportPathLabel.TextAlign = System.Drawing.ContentAlignment.BottomLeft; + // + // tertiaryResourcePathTextBox + // + tertiaryResourcePathTextBox.Location = new System.Drawing.Point(12, 220); + tertiaryResourcePathTextBox.Name = "tertiaryResourcePathTextBox"; + tertiaryResourcePathTextBox.Size = new System.Drawing.Size(490, 23); + tertiaryResourcePathTextBox.TabIndex = 18; + tertiaryResourcePathTextBox.Text = "my_really_cool_and_extremely_long_tertiary_resource_path"; + // + // primaryDataPathSelectorButton + // + primaryDataPathSelectorButton.Location = new System.Drawing.Point(508, 132); + primaryDataPathSelectorButton.Name = "primaryDataPathSelectorButton"; + primaryDataPathSelectorButton.Size = new System.Drawing.Size(140, 23); + primaryDataPathSelectorButton.TabIndex = 19; + primaryDataPathSelectorButton.Text = "Select Primary Data"; + primaryDataPathSelectorButton.UseVisualStyleBackColor = true; + primaryDataPathSelectorButton.Click += primaryDataPathSelectorButton_Click; + // + // ImportResourceForm + // + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(659, 293); + Controls.Add(primaryDataPathSelectorButton); + Controls.Add(tertiaryResourcePathTextBox); + Controls.Add(tertiaryImportPathLabel); + Controls.Add(secondaryResourcePathTextBox); + Controls.Add(secondaryImportPathLabel); + Controls.Add(primaryResourcePathTextBox); + Controls.Add(primaryImportPathLabel); + Controls.Add(tertiaryDataPathSelectorButton); + Controls.Add(resourceTypeComboBox); + Controls.Add(selectTypeLabel); + Controls.Add(autoUpdateIDCheckBox); + Controls.Add(secondaryDataPathSelectorButton); + Controls.Add(label3); + Controls.Add(label2); + Controls.Add(resourceNameTextBox); + Controls.Add(label1); + Controls.Add(resourceIDTextBox); + Controls.Add(importButton); + Name = "ImportResourceForm"; + Text = "Import Resource into Bundle"; + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Button importButton; + private System.Windows.Forms.TextBox resourceIDTextBox; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox resourceNameTextBox; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Button secondaryDataPathSelectorButton; + private System.Windows.Forms.CheckBox autoUpdateIDCheckBox; + private System.Windows.Forms.Label selectTypeLabel; + private System.Windows.Forms.ComboBox resourceTypeComboBox; + private System.Windows.Forms.Button tertiaryDataPathSelectorButton; + private System.Windows.Forms.Label primaryImportPathLabel; + private System.Windows.Forms.TextBox primaryResourcePathTextBox; + private System.Windows.Forms.Label secondaryImportPathLabel; + private System.Windows.Forms.TextBox secondaryResourcePathTextBox; + private System.Windows.Forms.Label tertiaryImportPathLabel; + private System.Windows.Forms.TextBox tertiaryResourcePathTextBox; + private System.Windows.Forms.Button primaryDataPathSelectorButton; + } +} diff --git a/BundleManager/ImportResourceForm.cs b/BundleManager/ImportResourceForm.cs new file mode 100644 index 0000000..0ac6085 --- /dev/null +++ b/BundleManager/ImportResourceForm.cs @@ -0,0 +1,659 @@ +using BundleFormat; +using BundleUtilities; +using BurnoutImage; +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; + +namespace BundleManager +{ + public partial class ImportResourceForm : Form + { + private const string ResourceNamePrefix = "bundlemanager://"; + + MemoryStream ms; + DataChunkImportProperties Properties = new(); + public bool AutoUpdateID; + public BundleArchive ResultArchive { get; private set; } + + private BundleArchive _workingArchive; + + private BundleEntry _newEntry; + + + string ImportName + { + get => _importName; + set + { + _importName = value ?? string.Empty; + SetResourceNameText(ResourceNamePrefix + _importName, ResourceNamePrefix.Length + _importName.Length); + } + } + private string _importName; + private bool _isUpdatingResourceNameText; + + public ImportResourceForm(BundleArchive destination, string primaryDataPath, uint? resourceID = null) + { + InitializeComponent(); + + _workingArchive = destination; + + if (!Path.Exists(primaryDataPath)) + throw new InvalidDataException("Selected file does not exist or is corrupt!"); + + ms = new(File.ReadAllBytes(primaryDataPath)); + + ImportName = Path.GetFileNameWithoutExtension(primaryDataPath); + + RegisterDataBlockProperties(); + Properties.Entries[0].Path = primaryDataPath; + + ResolveDependencies(); + + Properties.EntryType = InferEntryType(ref ms); + _newEntry.Type = Properties.EntryType; + + if (Properties.EntryType == EntryType.Invalid) + selectTypeLabel.Text = "Unable to auto detect resource type, please select it from the box below:"; + + autoUpdateIDCheckBox.Checked = resourceID == null ? true : false; + + resourceTypeComboBox.DataSource = Enum.GetValues(typeof(EntryType)); + resourceTypeComboBox.SelectedIndexChanged += ResourceTypeComboBox_SelectedIndexChanged; + resourceTypeComboBox.SelectedItem = Properties.EntryType; + + resourceIDTextBox.KeyPress += ResourceIDTextBox_KeyPress; + resourceIDTextBox.Validating += ResourceIDTextBox_Validating; + + primaryResourcePathTextBox.Validated += UpdateDataPathsFromTextBoxes; + secondaryResourcePathTextBox.Validated += UpdateDataPathsFromTextBoxes; + tertiaryResourcePathTextBox.Validated += UpdateDataPathsFromTextBoxes; + } + + #region Event Handlers + + private void ResourceIDTextBox_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + if (!Utilities.IsValidHex(resourceIDTextBox.Text)) + { + e.Cancel = true; + MessageBox.Show("Please enter a valid hexadecimal value."); + } + } + + private void ResourceIDTextBox_KeyPress(object sender, KeyPressEventArgs e) + { + bool isControl = char.IsControl(e.KeyChar); + bool isHex = Uri.IsHexDigit(e.KeyChar); + + if (!isControl && !isHex) + e.Handled = true; + } + + private void ResourceTypeComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + if (resourceTypeComboBox.SelectedItem is EntryType entryType) + { + Properties.EntryType = entryType; + _newEntry.Type = entryType; + } + } + + private void autoUpdateIDCheckBox_CheckedChanged(object sender, EventArgs e) + { + AutoUpdateID = autoUpdateIDCheckBox.Checked; + resourceIDTextBox.ReadOnly = autoUpdateIDCheckBox.Checked; + + if (AutoUpdateID) + RecalculateResourceIDTextBox(); + } + + private void resourceNameTextBox_TextChanged(object sender, EventArgs e) + { + if (_isUpdatingResourceNameText) + return; + + string text = resourceNameTextBox.Text ?? string.Empty; + + _importName = text.StartsWith(ResourceNamePrefix, StringComparison.OrdinalIgnoreCase) + ? text.Substring(ResourceNamePrefix.Length) + : text; + + if (AutoUpdateID) + RecalculateResourceIDTextBox(); + } + + private void primaryDataPathSelectorButton_Click(object sender, EventArgs e) + { + SelectNewDataChunkDialog(0); + } + + private void secondaryDataPathSelectorButton_Click(object sender, EventArgs e) + { + SelectNewDataChunkDialog(1); + } + + private void tertiaryDataPathSelectorButton_Click(object sender, EventArgs e) + { + SelectNewDataChunkDialog(2); + } + + private void importButton_Click(object sender, EventArgs e) + { + RunImportOperation(); + } +#endregion + + void RegisterDataBlockProperties() + { + Properties.Entries[0].PathSelectorControls = + [ + primaryDataPathSelectorButton, + primaryResourcePathTextBox, + primaryImportPathLabel + ]; + Properties.Entries[1].PathSelectorControls = + [ + secondaryDataPathSelectorButton, + secondaryResourcePathTextBox, + secondaryImportPathLabel + ]; + Properties.Entries[2].PathSelectorControls = + [ + tertiaryDataPathSelectorButton, + tertiaryResourcePathTextBox, + tertiaryImportPathLabel + ]; + + for (int i = 0; i < Properties.Entries.Length; i++) + { + Properties.Entries[i].PathChanged = OnAnyDataBlockPathsUpdated; + } + + OnAnyDataBlockPathsUpdated(); + } + + void ResolveDependencies() + { + byte[] primaryData = ms.ToArray(); + + LoadDependencies( + primaryData, + _workingArchive.Platform > BundlePlatform.PC, + Properties.Entries[0].Path, + out List dependencies, + out int dependencyBytes + ); + + int newPrimarySize = primaryData.Length - dependencyBytes; + if (newPrimarySize < 0) newPrimarySize = 0; + + byte[] strippedPrimary = new byte[newPrimarySize]; + if (newPrimarySize > 0) + Buffer.BlockCopy(primaryData, 0, strippedPrimary, 0, newPrimarySize); + + _newEntry = new(_workingArchive) + { + Type = Properties.EntryType + }; + + foreach (Dependency dep in dependencies) + _newEntry.Dependencies.Add(dep); + + _newEntry.DependencyCount = (short)_newEntry.Dependencies.Count; + + ms.SetLength(strippedPrimary.Length); + + MessageBox.Show($"Resolved {_newEntry.Dependencies.Count} dependencies for the imported resource."); + } + + // This really needs to be put in a better spot + EntryType InferEntryType(ref MemoryStream memoryStream) + { + long originalPos = memoryStream.Position; + byte[] data = memoryStream.ToArray(); + + // AttribSysVault + if (_workingArchive.Console switch + { + true => Utilities.ContainsASCII(data, "StrN") + && Utilities.ContainsASCII(data, "Vers"), + false => Utilities.ContainsASCII(data, "NrtS") + && Utilities.ContainsASCII(data, "sreV") + } == true) + { + return EntryType.AttribSysVault; + } + + EntryType type = EntryType.Invalid; + + memoryStream.Seek(0x0, SeekOrigin.Begin); + + // Texture + if (GameImage.DetectPlatform(data) is PlatformType p + && p != PlatformType.Invalid + && _newEntry.Dependencies.Count == 0) + { + type = EntryType.Texture; + } + // Model + else if (memoryStream.Length <= 0x20) + { + // 32 bit model + memoryStream.Seek(0x13, SeekOrigin.Begin); + byte modelCandidate32 = (byte)memoryStream.ReadByte(); + + // 64 bit model (32 bit is technically padded to nearest 0x10, so this would run anyway) + if (memoryStream.Length > 0x14) + { + memoryStream.Seek(0x1F, SeekOrigin.Begin); + + if (memoryStream.ReadByte() == 0x2) + type = EntryType.Model; + } + + if (modelCandidate32 == 0x2) + type = EntryType.Model; + } + // Renderable + else + { + if (_workingArchive.Console switch + { + true => Utilities.ReadUInt16BE(data, 0x10), + false => Utilities.ReadUInt16LE(data, 0x10) + } == 11) // mu16VersionNumber + { + type = EntryType.Renderable; + } + } + + memoryStream.Position = originalPos; + return type; + } + + void OnAnyDataBlockPathsUpdated() + { + primaryResourcePathTextBox.Text = Properties.Entries[0].Path; + secondaryResourcePathTextBox.Text = Properties.Entries[1].Path; + tertiaryResourcePathTextBox.Text = Properties.Entries[2].Path; + } + + private void UpdateDataPathsFromTextBoxes(object sender, EventArgs e) + { + if (sender is not TextBox textBox) + return; + + string path = textBox.Text; + + for (int i = 0; i < Properties.Entries.Length; i++) + { + if (Properties.Entries[i].Path != path) + Properties.Entries[i].Path = path; + } + } + + private void RecalculateResourceIDTextBox() + { + string nameText = resourceNameTextBox.Text ?? string.Empty; + + Utilities.CalculateResourceIDFromName + ( + string.Concat + ( + nameText.ToLowerInvariant(), + ".", + Properties.EntryType.ToString() + ), + out string id + ); + resourceIDTextBox.Text = id; + } + + void SelectNewDataChunkDialog(byte index) + { + using (OpenFileDialog ofd = new()) + { + ofd.Title = $"Please select the {GetDataChunkName(index)} resource to import"; + ofd.Filter = "Binary Files|*.dat;*.bin|Image Files(*.dds,*.bmp;*.gif;*.jpg;*.png;*.tif;*.tga;*.webp)|*.dds;*.bmp;*.gif;*.jpg;*.png;*.tif;*.tga;*.webp|All files (*.*)|*.*"; + + if (ofd.ShowDialog(this) != DialogResult.OK) + return; + + string path = ofd.FileName; + + if (string.IsNullOrEmpty(path) || !File.Exists(path)) + return; + + Properties.Entries[index].Path = path; + } + } + + private void RunImportOperation() + { + for (int i = 1; i < Properties.DataChunkCount; i++) + { + if (!File.Exists(Properties.Entries[i].Path)) + { + MessageBox.Show($"Please select valid {GetDataChunkName(i)} data for the resource you are trying to import."); + return; + } + } + + byte[][] dataBlocks = new byte[Properties.DataChunkCount][]; + + dataBlocks[0] = ms.ToArray(); + if (Properties.DataChunkCount > 1) + { + for (int i = 1; i < Properties.DataChunkCount; i++) + { + dataBlocks[i] = File.ReadAllBytes(Properties.Entries[i].Path); + } + } + + EntryBlock block1 = new() + { + Compressed = false, + Data = dataBlocks[0] + }; + + EntryBlock block2 = new() + { + Compressed = false + }; + + EntryBlock block3 = new() + { + Compressed = false + }; + + if (Properties.DataChunkCount > 1) + { + if (Properties.EntryType == EntryType.Texture) + block2.Compressed = true; + + block2.Data = dataBlocks[1]; + } + + _newEntry.EntryBlocks = + [ + block1, + block2, + block3 + ]; + + _newEntry.Dirty = true; + _newEntry.Type = Properties.EntryType; + + _newEntry.ID = Convert.ToUInt32(resourceIDTextBox.Text, 16); + _newEntry.Index = _workingArchive.Entries.Count; + _newEntry.DebugInfo = new() + { + Name = $"{resourceNameTextBox.Text}.{Properties.EntryType.ToString()}", + TypeName = Properties.EntryType.ToString() + }; + + _workingArchive.Entries.Add(_newEntry); + + ResultArchive = _workingArchive; + DialogResult = DialogResult.OK; + Close(); + } + + #region Dependency Handling + + private void SetResourceNameText(string text, int caretPosition) + { + _isUpdatingResourceNameText = true; + resourceNameTextBox.Text = text; + resourceNameTextBox.SelectionStart = Math.Clamp(caretPosition, 0, resourceNameTextBox.TextLength); + resourceNameTextBox.SelectionLength = 0; + _isUpdatingResourceNameText = false; + } + + private static void LoadDependencies( + byte[] primaryData, + bool isBigEndian, + string primaryPath, + out List dependencies, + out int dependencyBytes) + { + string yamlPath = GetImportsYamlPath(primaryPath); + + if (yamlPath != null && File.Exists(yamlPath)) + { + dependencies = LoadDependenciesFromYaml(yamlPath); + dependencyBytes = dependencies.Count * 0x10; + } + else + { + ScanBinaryDependencies(primaryData, isBigEndian, out dependencies, out dependencyBytes); + } + } + + private static string GetImportsYamlPath(string primaryPath) + { + string dir = Path.GetDirectoryName(primaryPath); + string name = Path.GetFileNameWithoutExtension(primaryPath); + const string primarySuffix = "_primary"; + + if (name.EndsWith(primarySuffix, StringComparison.OrdinalIgnoreCase)) + name = name.Substring(0, name.Length - primarySuffix.Length); + + if (string.IsNullOrEmpty(dir) || string.IsNullOrEmpty(name)) + return null; + + return Path.Combine(dir, name + "_imports.yaml"); + } + + private static List LoadDependenciesFromYaml(string yamlPath) + { + string[] lines = File.ReadAllLines(yamlPath); + List list = []; + + foreach (string line in lines) + { + string trimmed = line.Trim(); + + if (trimmed.Length == 0) + continue; + + if (trimmed.StartsWith("#")) + continue; + + if (trimmed[0] == '-') + trimmed = trimmed.Substring(1).TrimStart(); + + int colonIndex = trimmed.IndexOf(':'); + if (colonIndex <= 0) + continue; + + string offsetPart = trimmed.Substring(0, colonIndex).Trim(); + string idPart = trimmed.Substring(colonIndex + 1).Trim(); + + if (!Utilities.TryParseHexUInt(offsetPart, out uint offset)) + continue; + + if (!Utilities.TryParseHexUInt(idPart, out uint id)) + continue; + + Dependency dep = new() + { + EntryPointerOffset = offset, + ID = id + }; + + list.Add(dep); + } + + return list; + } + + private static void ScanBinaryDependencies( + byte[] primaryData, + bool isBigEndian, + out List dependencies, + out int dependencyBytes) + { + dependencies = []; + dependencyBytes = 0; + + if (primaryData.Length < 0x10) + return; + + int pos = primaryData.Length - 0x10; + + while (pos >= 0) + { + if (!TryReadDependencyEntry(primaryData, pos, isBigEndian, out uint id, out uint location)) + break; + + Dependency dep = new() + { + ID = id, + EntryPointerOffset = location + }; + + dependencies.Add(dep); + dependencyBytes += 0x10; + pos -= 0x10; + } + + if (dependencies.Count > 0) + dependencies.Reverse(); + } + + private static bool TryReadDependencyEntry( + byte[] data, + int offset, + bool isBigEndian, + out uint id, + out uint location) + { + id = 0; + location = 0; + + if (offset < 0 || offset + 0x10 > data.Length) + return false; + + if (isBigEndian) + { + if (data[offset + 0] != 0 || data[offset + 1] != 0 || + data[offset + 2] != 0 || data[offset + 3] != 0) + return false; + + if (data[offset + 8] != 0 || data[offset + 9] != 0 || + data[offset + 10] != 0 || data[offset + 11] != 0) + return false; + + id = Utilities.ReadUInt32BE(data, offset + 4); + location = Utilities.ReadUInt32BE(data, offset + 12); + } + else + { + if (data[offset + 4] != 0 || data[offset + 5] != 0 || + data[offset + 6] != 0 || data[offset + 7] != 0) + return false; + + if (data[offset + 12] != 0 || data[offset + 13] != 0 || + data[offset + 14] != 0 || data[offset + 15] != 0) + return false; + + id = Utilities.ReadUInt32LE(data, offset + 0); + location = Utilities.ReadUInt32LE(data, offset + 8); + } + + if (id == 0) + return false; + + return true; + } + + #endregion + + #region Data Chunks + + private static string GetDataChunkName(int index) + { + return index >= 0 && index < DataChunkNames.Length + ? DataChunkNames[index] + : "additional"; + } + + private static readonly string[] DataChunkNames = + { + "primary", + "secondary", + "tertiary" + }; + + struct DataChunkImportProperties + { + public byte DataChunkCount { get; private set; } + public DataChunkImportEntry[] Entries; + + public EntryType EntryType + { + get => _currentEntryType; + set + { + _currentEntryType = value; + UpdateDataChunkCount(); + UpdateEntryVisibilities(); + } + } + private EntryType _currentEntryType = EntryType.Invalid; + + public DataChunkImportProperties() + { + UpdateDataChunkCount(); + Entries = new DataChunkImportEntry[3]; + } + + void UpdateDataChunkCount() + { + DataChunkCount = BundleEntry.GetMemoryChunkCount(_currentEntryType); + } + + void UpdateEntryVisibilities() + { + for (byte i = 0; i < Entries.Length; i++) + Entries[i].Visible = DataChunkCount > i; + } + } + + struct DataChunkImportEntry + { + public bool Visible + { + get => _visible; + set + { + _visible = value; + if (PathSelectorControls.Count > 0) + foreach (var control in PathSelectorControls) + control.Visible = value; + Path = value == false ? string.Empty : Path; + } + } + private bool _visible; + + public List PathSelectorControls; + public Action? PathChanged; + public string Path + { + get => _path; + set + { + _path = value; + PathChanged?.Invoke(); + } + } + private string _path; + } + + #endregion + } +} diff --git a/BundleManager/ImportResourceForm.resx b/BundleManager/ImportResourceForm.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/BundleManager/ImportResourceForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BundleManager/MainForm.Designer.cs b/BundleManager/MainForm.Designer.cs index bc120ab..8b146e4 100644 --- a/BundleManager/MainForm.Designer.cs +++ b/BundleManager/MainForm.Designer.cs @@ -28,342 +28,323 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); + components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); - this.mnuMain = new System.Windows.Forms.MenuStrip(); - this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.newToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator(); - this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); - this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); - this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.closeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.searchForEntryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStrip1 = new System.Windows.Forms.ToolStrip(); - this.tsbNew = new System.Windows.Forms.ToolStripButton(); - this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); - this.tsbOpen = new System.Windows.Forms.ToolStripButton(); - this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); - this.tsbSave = new System.Windows.Forms.ToolStripButton(); - this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); - this.tsbSwitchMode = new System.Windows.Forms.ToolStripButton(); - this.lstEntries = new BundleManager.BetterListView(); - this.colIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colID = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colSize = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colPreview = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.mnuLst = new System.Windows.Forms.ContextMenuStrip(this.components); - this.previewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem4 = new System.Windows.Forms.ToolStripSeparator(); - this.viewDataToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.viewDebugMenuStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.pluginToolsSeparatorItem = new System.Windows.Forms.ToolStripSeparator(); - this.mnuMain.SuspendLayout(); - this.toolStrip1.SuspendLayout(); - this.mnuLst.SuspendLayout(); - this.SuspendLayout(); + mnuMain = new System.Windows.Forms.MenuStrip(); + fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + newToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator(); + openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); + saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); + exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + closeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + searchForEntryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + pluginToolsSeparatorItem = new System.Windows.Forms.ToolStripSeparator(); + addResourcesToBundleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + toolStrip1 = new System.Windows.Forms.ToolStrip(); + tsbNew = new System.Windows.Forms.ToolStripButton(); + toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + tsbOpen = new System.Windows.Forms.ToolStripButton(); + toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + tsbSave = new System.Windows.Forms.ToolStripButton(); + toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + tsbSwitchMode = new System.Windows.Forms.ToolStripButton(); + lstEntries = new BetterListView(); + colIndex = new System.Windows.Forms.ColumnHeader(); + colName = new System.Windows.Forms.ColumnHeader(); + colID = new System.Windows.Forms.ColumnHeader(); + colType = new System.Windows.Forms.ColumnHeader(); + colSize = new System.Windows.Forms.ColumnHeader(); + colPreview = new System.Windows.Forms.ColumnHeader(); + mnuLst = new System.Windows.Forms.ContextMenuStrip(components); + previewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItem4 = new System.Windows.Forms.ToolStripSeparator(); + viewDataToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + viewDebugMenuStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + mnuMain.SuspendLayout(); + toolStrip1.SuspendLayout(); + mnuLst.SuspendLayout(); + SuspendLayout(); // // mnuMain // - this.mnuMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.fileToolStripMenuItem, - this.toolsToolStripMenuItem}); - this.mnuMain.Location = new System.Drawing.Point(0, 0); - this.mnuMain.Name = "mnuMain"; - this.mnuMain.Size = new System.Drawing.Size(784, 24); - this.mnuMain.TabIndex = 0; - this.mnuMain.Text = "menuStrip1"; + mnuMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { fileToolStripMenuItem, toolsToolStripMenuItem }); + mnuMain.Location = new System.Drawing.Point(0, 0); + mnuMain.Name = "mnuMain"; + mnuMain.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); + mnuMain.Size = new System.Drawing.Size(915, 24); + mnuMain.TabIndex = 0; + mnuMain.Text = "menuStrip1"; // // fileToolStripMenuItem // - this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.newToolStripMenuItem, - this.toolStripMenuItem3, - this.openToolStripMenuItem, - this.toolStripMenuItem1, - this.saveToolStripMenuItem, - this.saveAsToolStripMenuItem, - this.toolStripMenuItem2, - this.exitToolStripMenuItem, - this.closeToolStripMenuItem}); - this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); - this.fileToolStripMenuItem.Text = "File"; + fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { newToolStripMenuItem, toolStripMenuItem3, openToolStripMenuItem, toolStripMenuItem1, saveToolStripMenuItem, saveAsToolStripMenuItem, toolStripMenuItem2, exitToolStripMenuItem, closeToolStripMenuItem }); + fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); + fileToolStripMenuItem.Text = "File"; // // newToolStripMenuItem // - this.newToolStripMenuItem.Image = global::BundleManager.Properties.Resources.NewDocumentHS; - this.newToolStripMenuItem.Name = "newToolStripMenuItem"; - this.newToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.N))); - this.newToolStripMenuItem.Size = new System.Drawing.Size(186, 22); - this.newToolStripMenuItem.Text = "New"; - this.newToolStripMenuItem.Click += new System.EventHandler(this.newToolStripMenuItem_Click); + newToolStripMenuItem.Image = Properties.Resources.NewDocumentHS; + newToolStripMenuItem.Name = "newToolStripMenuItem"; + newToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.N; + newToolStripMenuItem.Size = new System.Drawing.Size(186, 22); + newToolStripMenuItem.Text = "New"; + newToolStripMenuItem.Click += newToolStripMenuItem_Click; // // toolStripMenuItem3 // - this.toolStripMenuItem3.Name = "toolStripMenuItem3"; - this.toolStripMenuItem3.Size = new System.Drawing.Size(183, 6); + toolStripMenuItem3.Name = "toolStripMenuItem3"; + toolStripMenuItem3.Size = new System.Drawing.Size(183, 6); // // openToolStripMenuItem // - this.openToolStripMenuItem.Image = global::BundleManager.Properties.Resources.openHS; - this.openToolStripMenuItem.Name = "openToolStripMenuItem"; - this.openToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); - this.openToolStripMenuItem.Size = new System.Drawing.Size(186, 22); - this.openToolStripMenuItem.Text = "Open"; - this.openToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click); + openToolStripMenuItem.Image = Properties.Resources.openHS; + openToolStripMenuItem.Name = "openToolStripMenuItem"; + openToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O; + openToolStripMenuItem.Size = new System.Drawing.Size(186, 22); + openToolStripMenuItem.Text = "Open"; + openToolStripMenuItem.Click += openToolStripMenuItem_Click; // // toolStripMenuItem1 // - this.toolStripMenuItem1.Name = "toolStripMenuItem1"; - this.toolStripMenuItem1.Size = new System.Drawing.Size(183, 6); + toolStripMenuItem1.Name = "toolStripMenuItem1"; + toolStripMenuItem1.Size = new System.Drawing.Size(183, 6); // // saveToolStripMenuItem // - this.saveToolStripMenuItem.Image = global::BundleManager.Properties.Resources.saveHS; - this.saveToolStripMenuItem.Name = "saveToolStripMenuItem"; - this.saveToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S))); - this.saveToolStripMenuItem.Size = new System.Drawing.Size(186, 22); - this.saveToolStripMenuItem.Text = "Save"; - this.saveToolStripMenuItem.Click += new System.EventHandler(this.saveToolStripMenuItem_Click); + saveToolStripMenuItem.Image = Properties.Resources.saveHS; + saveToolStripMenuItem.Name = "saveToolStripMenuItem"; + saveToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S; + saveToolStripMenuItem.Size = new System.Drawing.Size(186, 22); + saveToolStripMenuItem.Text = "Save"; + saveToolStripMenuItem.Click += saveToolStripMenuItem_Click; // // saveAsToolStripMenuItem // - this.saveAsToolStripMenuItem.Image = global::BundleManager.Properties.Resources.SaveAllHS; - this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; - this.saveAsToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) - | System.Windows.Forms.Keys.S))); - this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(186, 22); - this.saveAsToolStripMenuItem.Text = "Save As"; - this.saveAsToolStripMenuItem.Click += new System.EventHandler(this.saveAsToolStripMenuItem_Click); + saveAsToolStripMenuItem.Image = Properties.Resources.SaveAllHS; + saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; + saveAsToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift | System.Windows.Forms.Keys.S; + saveAsToolStripMenuItem.Size = new System.Drawing.Size(186, 22); + saveAsToolStripMenuItem.Text = "Save As"; + saveAsToolStripMenuItem.Click += saveAsToolStripMenuItem_Click; // // toolStripMenuItem2 // - this.toolStripMenuItem2.Name = "toolStripMenuItem2"; - this.toolStripMenuItem2.Size = new System.Drawing.Size(183, 6); + toolStripMenuItem2.Name = "toolStripMenuItem2"; + toolStripMenuItem2.Size = new System.Drawing.Size(183, 6); // // exitToolStripMenuItem // - this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; - this.exitToolStripMenuItem.Size = new System.Drawing.Size(186, 22); - this.exitToolStripMenuItem.Text = "Exit"; - this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click); + exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + exitToolStripMenuItem.Size = new System.Drawing.Size(186, 22); + exitToolStripMenuItem.Text = "Exit"; + exitToolStripMenuItem.Click += exitToolStripMenuItem_Click; // // closeToolStripMenuItem // - this.closeToolStripMenuItem.Name = "closeToolStripMenuItem"; - this.closeToolStripMenuItem.Size = new System.Drawing.Size(186, 22); - this.closeToolStripMenuItem.Text = "Close"; - this.closeToolStripMenuItem.Visible = false; - this.closeToolStripMenuItem.Click += new System.EventHandler(this.closeToolStripMenuItem_Click); + closeToolStripMenuItem.Name = "closeToolStripMenuItem"; + closeToolStripMenuItem.Size = new System.Drawing.Size(186, 22); + closeToolStripMenuItem.Text = "Close"; + closeToolStripMenuItem.Visible = false; + closeToolStripMenuItem.Click += closeToolStripMenuItem_Click; // // toolsToolStripMenuItem // - this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.searchForEntryToolStripMenuItem, - this.pluginToolsSeparatorItem}); - this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; - this.toolsToolStripMenuItem.Size = new System.Drawing.Size(46, 20); - this.toolsToolStripMenuItem.Text = "Tools"; + toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { searchForEntryToolStripMenuItem, addResourcesToBundleToolStripMenuItem, pluginToolsSeparatorItem }); + toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; + toolsToolStripMenuItem.Size = new System.Drawing.Size(46, 20); + toolsToolStripMenuItem.Text = "Tools"; // // searchForEntryToolStripMenuItem // - this.searchForEntryToolStripMenuItem.Name = "searchForEntryToolStripMenuItem"; - this.searchForEntryToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.F))); - this.searchForEntryToolStripMenuItem.Size = new System.Drawing.Size(197, 22); - this.searchForEntryToolStripMenuItem.Text = "Search for entry"; - this.searchForEntryToolStripMenuItem.Click += new System.EventHandler(this.searchForEntryToolStripMenuItem_Click); + searchForEntryToolStripMenuItem.Name = "searchForEntryToolStripMenuItem"; + searchForEntryToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.F; + searchForEntryToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + searchForEntryToolStripMenuItem.Text = "Search for entry"; + searchForEntryToolStripMenuItem.Click += searchForEntryToolStripMenuItem_Click; + // + // pluginToolsSeparatorItem + // + pluginToolsSeparatorItem.Name = "pluginToolsSeparatorItem"; + pluginToolsSeparatorItem.Size = new System.Drawing.Size(217, 6); + // + // addResourcesToBundleToolStripMenuItem + // + addResourcesToBundleToolStripMenuItem.Name = "addResourcesToBundleToolStripMenuItem"; + addResourcesToBundleToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + addResourcesToBundleToolStripMenuItem.Text = "Add resource(s) to bundle..."; + addResourcesToBundleToolStripMenuItem.Click += addResourcesToBundleToolStripMenuItem_Click; // // toolStrip1 // - this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.tsbNew, - this.toolStripSeparator1, - this.tsbOpen, - this.toolStripSeparator2, - this.tsbSave, - this.toolStripSeparator3, - this.tsbSwitchMode}); - this.toolStrip1.Location = new System.Drawing.Point(0, 24); - this.toolStrip1.Name = "toolStrip1"; - this.toolStrip1.Size = new System.Drawing.Size(784, 25); - this.toolStrip1.TabIndex = 1; - this.toolStrip1.Text = "toolStrip1"; + toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { tsbNew, toolStripSeparator1, tsbOpen, toolStripSeparator2, tsbSave, toolStripSeparator3, tsbSwitchMode }); + toolStrip1.Location = new System.Drawing.Point(0, 24); + toolStrip1.Name = "toolStrip1"; + toolStrip1.Size = new System.Drawing.Size(915, 25); + toolStrip1.TabIndex = 1; + toolStrip1.Text = "toolStrip1"; // // tsbNew // - this.tsbNew.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.tsbNew.Image = global::BundleManager.Properties.Resources.NewDocumentHS; - this.tsbNew.ImageTransparentColor = System.Drawing.Color.Magenta; - this.tsbNew.Name = "tsbNew"; - this.tsbNew.Size = new System.Drawing.Size(23, 22); - this.tsbNew.Text = "New"; - this.tsbNew.Click += new System.EventHandler(this.tsbNew_Click); + tsbNew.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + tsbNew.Image = Properties.Resources.NewDocumentHS; + tsbNew.ImageTransparentColor = System.Drawing.Color.Magenta; + tsbNew.Name = "tsbNew"; + tsbNew.Size = new System.Drawing.Size(23, 22); + tsbNew.Text = "New"; + tsbNew.Click += tsbNew_Click; // // toolStripSeparator1 // - this.toolStripSeparator1.Name = "toolStripSeparator1"; - this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25); + toolStripSeparator1.Name = "toolStripSeparator1"; + toolStripSeparator1.Size = new System.Drawing.Size(6, 25); // // tsbOpen // - this.tsbOpen.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.tsbOpen.Image = global::BundleManager.Properties.Resources.openHS; - this.tsbOpen.ImageTransparentColor = System.Drawing.Color.Magenta; - this.tsbOpen.Name = "tsbOpen"; - this.tsbOpen.Size = new System.Drawing.Size(23, 22); - this.tsbOpen.Text = "Open"; - this.tsbOpen.Click += new System.EventHandler(this.tsbOpen_Click); + tsbOpen.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + tsbOpen.Image = Properties.Resources.openHS; + tsbOpen.ImageTransparentColor = System.Drawing.Color.Magenta; + tsbOpen.Name = "tsbOpen"; + tsbOpen.Size = new System.Drawing.Size(23, 22); + tsbOpen.Text = "Open"; + tsbOpen.Click += tsbOpen_Click; // // toolStripSeparator2 // - this.toolStripSeparator2.Name = "toolStripSeparator2"; - this.toolStripSeparator2.Size = new System.Drawing.Size(6, 25); + toolStripSeparator2.Name = "toolStripSeparator2"; + toolStripSeparator2.Size = new System.Drawing.Size(6, 25); // // tsbSave // - this.tsbSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.tsbSave.Image = global::BundleManager.Properties.Resources.saveHS; - this.tsbSave.ImageTransparentColor = System.Drawing.Color.Magenta; - this.tsbSave.Name = "tsbSave"; - this.tsbSave.Size = new System.Drawing.Size(23, 22); - this.tsbSave.Text = "Save"; - this.tsbSave.Click += new System.EventHandler(this.tsbSave_Click); + tsbSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + tsbSave.Image = Properties.Resources.saveHS; + tsbSave.ImageTransparentColor = System.Drawing.Color.Magenta; + tsbSave.Name = "tsbSave"; + tsbSave.Size = new System.Drawing.Size(23, 22); + tsbSave.Text = "Save"; + tsbSave.Click += tsbSave_Click; // // toolStripSeparator3 // - this.toolStripSeparator3.Name = "toolStripSeparator3"; - this.toolStripSeparator3.Size = new System.Drawing.Size(6, 25); + toolStripSeparator3.Name = "toolStripSeparator3"; + toolStripSeparator3.Size = new System.Drawing.Size(6, 25); // // tsbSwitchMode // - this.tsbSwitchMode.Image = global::BundleManager.Properties.Resources.icon; - this.tsbSwitchMode.ImageTransparentColor = System.Drawing.Color.Magenta; - this.tsbSwitchMode.Name = "tsbSwitchMode"; - this.tsbSwitchMode.Size = new System.Drawing.Size(148, 22); - this.tsbSwitchMode.Text = "Switch To Studio Mode"; - this.tsbSwitchMode.Click += new System.EventHandler(this.tsbSwitchMode_Click); + tsbSwitchMode.Image = Properties.Resources.icon; + tsbSwitchMode.ImageTransparentColor = System.Drawing.Color.Magenta; + tsbSwitchMode.Name = "tsbSwitchMode"; + tsbSwitchMode.Size = new System.Drawing.Size(148, 22); + tsbSwitchMode.Text = "Switch To Studio Mode"; + tsbSwitchMode.Click += tsbSwitchMode_Click; // // lstEntries // - this.lstEntries.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.colIndex, - this.colName, - this.colID, - this.colType, - this.colSize, - this.colPreview}); - this.lstEntries.ContextMenuStrip = this.mnuLst; - this.lstEntries.Dock = System.Windows.Forms.DockStyle.Fill; - this.lstEntries.FullRowSelect = true; - this.lstEntries.GridLines = true; - this.lstEntries.HideSelection = false; - this.lstEntries.Location = new System.Drawing.Point(0, 49); - this.lstEntries.MultiSelect = false; - this.lstEntries.Name = "lstEntries"; - this.lstEntries.Size = new System.Drawing.Size(784, 512); - this.lstEntries.TabIndex = 2; - this.lstEntries.UseCompatibleStateImageBehavior = false; - this.lstEntries.View = System.Windows.Forms.View.Details; - this.lstEntries.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.lstEntries_ColumnClick); - this.lstEntries.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.lstEntries_MouseDoubleClick); + lstEntries.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { colIndex, colName, colID, colType, colSize, colPreview }); + lstEntries.ContextMenuStrip = mnuLst; + lstEntries.Dock = System.Windows.Forms.DockStyle.Fill; + lstEntries.FullRowSelect = true; + lstEntries.GridLines = true; + lstEntries.Location = new System.Drawing.Point(0, 49); + lstEntries.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + lstEntries.MultiSelect = false; + lstEntries.Name = "lstEntries"; + lstEntries.Size = new System.Drawing.Size(915, 598); + lstEntries.TabIndex = 2; + lstEntries.UseCompatibleStateImageBehavior = false; + lstEntries.View = System.Windows.Forms.View.Details; + lstEntries.ColumnClick += lstEntries_ColumnClick; + lstEntries.MouseDoubleClick += lstEntries_MouseDoubleClick; // // colIndex // - this.colIndex.Text = "Index"; + colIndex.Text = "Index"; + colIndex.Width = 70; // // colName // - this.colName.Text = "Name"; - this.colName.Width = 120; + colName.Text = "Name"; + colName.Width = 140; // // colID // - this.colID.Text = "ID"; - this.colID.Width = 100; + colID.Text = "ID"; + colID.Width = 117; // // colType // - this.colType.Text = "Type"; - this.colType.Width = 200; + colType.Text = "Type"; + colType.Width = 233; // // colSize // - this.colSize.Text = "Size"; + colSize.Text = "Size"; + colSize.Width = 70; // // colPreview // - this.colPreview.Text = "Preview"; - this.colPreview.Width = 300; + colPreview.Text = "Preview"; + colPreview.Width = 350; // // mnuLst // - this.mnuLst.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.previewToolStripMenuItem, - this.toolStripMenuItem4, - this.viewDataToolStripMenuItem, this.viewDebugMenuStripMenuItem}); - this.mnuLst.Name = "mnuLst"; - this.mnuLst.Size = new System.Drawing.Size(141, 54); + mnuLst.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { previewToolStripMenuItem, toolStripMenuItem4, viewDataToolStripMenuItem, viewDebugMenuStripMenuItem }); + mnuLst.Name = "mnuLst"; + mnuLst.Size = new System.Drawing.Size(172, 76); // // previewToolStripMenuItem // - this.previewToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.previewToolStripMenuItem.Name = "previewToolStripMenuItem"; - this.previewToolStripMenuItem.Size = new System.Drawing.Size(140, 22); - this.previewToolStripMenuItem.Text = "Preview"; - this.previewToolStripMenuItem.Click += new System.EventHandler(this.previewToolStripMenuItem_Click); + previewToolStripMenuItem.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 0); + previewToolStripMenuItem.Name = "previewToolStripMenuItem"; + previewToolStripMenuItem.Size = new System.Drawing.Size(171, 22); + previewToolStripMenuItem.Text = "Preview"; + previewToolStripMenuItem.Click += previewToolStripMenuItem_Click; // // toolStripMenuItem4 // - this.toolStripMenuItem4.Name = "toolStripMenuItem4"; - this.toolStripMenuItem4.Size = new System.Drawing.Size(137, 6); + toolStripMenuItem4.Name = "toolStripMenuItem4"; + toolStripMenuItem4.Size = new System.Drawing.Size(168, 6); // // viewDataToolStripMenuItem // - this.viewDataToolStripMenuItem.Name = "viewDataToolStripMenuItem"; - this.viewDataToolStripMenuItem.Size = new System.Drawing.Size(140, 22); - this.viewDataToolStripMenuItem.Text = "View Header"; - this.viewDataToolStripMenuItem.Click += new System.EventHandler(this.viewDataToolStripMenuItem_Click); + viewDataToolStripMenuItem.Name = "viewDataToolStripMenuItem"; + viewDataToolStripMenuItem.Size = new System.Drawing.Size(171, 22); + viewDataToolStripMenuItem.Text = "View Header"; + viewDataToolStripMenuItem.Click += viewDataToolStripMenuItem_Click; // // viewDebugMenuStripMenuItem // - this.viewDebugMenuStripMenuItem.Name = "viewDebugMenuStripMenuItem"; - this.viewDebugMenuStripMenuItem.Size = new System.Drawing.Size(140, 22); - this.viewDebugMenuStripMenuItem.Text = "View Debug Menu"; - this.viewDebugMenuStripMenuItem.Click += new System.EventHandler(this.viewDebugMenuStripMenuItem_Click); - - // - // pluginToolsSeparatorItem - // - this.pluginToolsSeparatorItem.Name = "pluginToolsSeparatorItem"; - this.pluginToolsSeparatorItem.Size = new System.Drawing.Size(194, 6); + viewDebugMenuStripMenuItem.Name = "viewDebugMenuStripMenuItem"; + viewDebugMenuStripMenuItem.Size = new System.Drawing.Size(171, 22); + viewDebugMenuStripMenuItem.Text = "View Debug Menu"; + viewDebugMenuStripMenuItem.Click += viewDebugMenuStripMenuItem_Click; // // MainForm // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(784, 561); - this.Controls.Add(this.lstEntries); - this.Controls.Add(this.toolStrip1); - this.Controls.Add(this.mnuMain); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MainMenuStrip = this.mnuMain; - this.Name = "MainForm"; - this.Text = "Burnout Bundle Manager"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); - this.mnuMain.ResumeLayout(false); - this.mnuMain.PerformLayout(); - this.toolStrip1.ResumeLayout(false); - this.toolStrip1.PerformLayout(); - this.mnuLst.ResumeLayout(false); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(915, 647); + Controls.Add(lstEntries); + Controls.Add(toolStrip1); + Controls.Add(mnuMain); + Icon = (System.Drawing.Icon)resources.GetObject("$this.Icon"); + MainMenuStrip = mnuMain; + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + Name = "MainForm"; + Text = "Burnout Bundle Manager"; + FormClosing += MainForm_FormClosing; + mnuMain.ResumeLayout(false); + mnuMain.PerformLayout(); + toolStrip1.ResumeLayout(false); + toolStrip1.PerformLayout(); + mnuLst.ResumeLayout(false); + ResumeLayout(false); + PerformLayout(); } @@ -403,6 +384,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem searchForEntryToolStripMenuItem; private System.Windows.Forms.ColumnHeader colName; private System.Windows.Forms.ToolStripSeparator pluginToolsSeparatorItem; + private System.Windows.Forms.ToolStripMenuItem addResourcesToBundleToolStripMenuItem; } } diff --git a/BundleManager/MainForm.cs b/BundleManager/MainForm.cs index 1e0e699..76514fb 100644 --- a/BundleManager/MainForm.cs +++ b/BundleManager/MainForm.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.ComponentModel; +using System.IO; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -597,6 +599,41 @@ private void searchForEntryToolStripMenuItem_Click(object sender, EventArgs e) Search(); } + private void addResourcesToBundleToolStripMenuItem_Click(object sender, EventArgs e) + { + if (CurrentArchive == null) + return; + + using OpenFileDialog ofd = new(); + + ofd.Title = "Please select the primary resource (or header) to import"; + ofd.Filter = "Binary Files|*.dat;*.bin|Image Files(*.dds,*.bmp;*.gif;*.jpg;*.png;*.tif;*.tga;*.webp)|*.dds;*.bmp;*.gif;*.jpg;*.png;*.tif;*.tga;*.webp|All files (*.*)|*.*"; + + if (ofd.ShowDialog(this) != DialogResult.OK) + return; + + var path = ofd.FileName; + + if (string.IsNullOrEmpty(path) || !File.Exists(path)) + return; + + Match m = ResourceIDPattern().Match(Path.GetFileNameWithoutExtension(path)); + + if (m.Success) + { + string hex = m.Value; + uint id = Convert.ToUInt32(hex, 16); + } + + using ImportResourceForm importResourceForm = new(CurrentArchive, path); + + if (importResourceForm.ShowDialog(this) == DialogResult.OK) + { + CurrentArchive = importResourceForm.ResultArchive; + UpdateDisplay(); + } + } + #endregion #region Utility @@ -761,6 +798,9 @@ public void Swap() } } + [GeneratedRegex(@"\b[0-9A-Fa-f]{8}\b")] + private static partial Regex ResourceIDPattern(); + #endregion } } diff --git a/BundleManager/MainForm.resx b/BundleManager/MainForm.resx index 8f9f8c4..0e585be 100644 --- a/BundleManager/MainForm.resx +++ b/BundleManager/MainForm.resx @@ -1,17 +1,17 @@  - diff --git a/BundleUtilities/Utilities.cs b/BundleUtilities/Utilities.cs index 40b1ba1..e8174e8 100644 --- a/BundleUtilities/Utilities.cs +++ b/BundleUtilities/Utilities.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text; using System.Reflection; +using System.Globalization; namespace BundleUtilities { @@ -97,5 +98,98 @@ public static void Parse(string s, bool forceHex, out T value) TypeConverter converter = TypeDescriptor.GetConverter(typeof(T)); value = (T)converter.ConvertFromString(s); } + + public static bool TryParseHexUInt(string s, out uint value) + { + try + { + Parse(s, true, out value); + return true; + } + catch + { + value = 0; + return false; + } + } + + public static ushort ReadUInt16LE(byte[] data, int offset) + { + return (ushort)(data[offset] + | (data[offset + 1] << 8)); + } + + public static ushort ReadUInt16BE(byte[] data, int offset) + { + return (ushort)((data[offset] << 8) + | data[offset + 1]); + } + + public static uint ReadUInt32LE(byte[] data, int offset) + { + return (uint)(data[offset] + | (data[offset + 1] << 8) + | (data[offset + 2] << 16) + | (data[offset + 3] << 24)); + } + + public static uint ReadUInt32BE(byte[] data, int offset) + { + return (uint)((data[offset] << 24) + | (data[offset + 1] << 16) + | (data[offset + 2] << 8) + | data[offset + 3]); + } + + public static bool IsValidHex(string s) + { + if (string.IsNullOrWhiteSpace(s)) + return false; + + foreach (char c in s) + if (!Uri.IsHexDigit(c)) + return false; + + return true; + } + + public static bool ContainsASCII(byte[] data, string value) + { + if (data.Length < value.Length) + return false; + + byte[] pattern = Encoding.ASCII.GetBytes(value); + + for (int i = 0; i <= data.Length - pattern.Length; i++) + { + bool match = true; + + for (int j = 0; j < pattern.Length; j++) + { + if (data[i + j] != pattern[j]) + { + match = false; + break; + } + } + + if (match) + return true; + } + + return false; + } + + public static void CalculateResourceIDFromName(string resourceName, out uint resourceID) + { + // example: bundlemanager://. + resourceID = Crc32.HashEntryID(resourceName.ToLower()); + } + + public static void CalculateResourceIDFromName(string resourceName, out string resourceID) + { + CalculateResourceIDFromName(resourceName, out uint id); + resourceID = id.ToString("X8"); + } } } diff --git a/BurnoutImage/GameImage.cs b/BurnoutImage/GameImage.cs index 5c894f5..b5411a9 100644 --- a/BurnoutImage/GameImage.cs +++ b/BurnoutImage/GameImage.cs @@ -387,24 +387,29 @@ public static PlatformType DetectPlatform(byte[] data) int len = data.Length; + using MemoryStream ms = new(data); + using BinaryReader2 br = new(ms); + if (len == 0x40 || len == 0x60) { if (len == 0x40) { - using (MemoryStream ms = new(data)) - using (BinaryReader2 br = new(ms)) - { - uint ptr = br.ReadUInt32(); - if (ptr != 0) - return PlatformType.X360; - } + ms.Seek(0x0, SeekOrigin.Begin); + + if (br.ReadUInt32() != 0) + return PlatformType.X360; } return PlatformType.Remastered; } if (len == 0x20) - return PlatformType.PC; + { + ms.Seek(0x4, SeekOrigin.Begin); + + if (br.ReadUInt32() == 0) // Texture interface ptr + return PlatformType.PC; + } if (len == 0x30) return PlatformType.PS3;