/// <summary> /// Parses a Sonic Heroes/Shadow ONE Archive from the supplied byte array and returns /// you an instance of Archive. /// </summary> /// <returns></returns> public static unsafe Archive FromONEFile(ref byte[] oneArchive) { // Know if we're dealing with Shadow050 or Shadow060 ONEArchiveType archiveType = ONEArchiveTester.GetArchiveType(ref oneArchive); if (archiveType == ONEArchiveType.Heroes) { return(new ONEArchive(ref oneArchive).GetArchive()); } else { return(new ONEShadowArchive(ref oneArchive).GetArchive()); } }
/// <summary> /// Used to conditionally display a warning on opening Shadow The Hedgehog .ONE files pleading the user /// to retain the file order. /// </summary> /// <param name="oneArchive">Byte array containing a .ONE File.</param> private void CheckShadowWarning(ref byte[] oneArchive) { // Know if we're dealing with Shadow050 or Shadow060 ONEArchiveType archiveType = ONEArchiveTester.GetArchiveType(ref oneArchive); if (archiveType == ONEArchiveType.Shadow050 || archiveType == ONEArchiveType.Shadow060 && _openedShadowArchive == false) { if (Properties.Settings.Default.HideWarnings == false) { _openedShadowArchive = true; MessageBox.Show("Note: You are opening a Shadow The Hedgehog Archive.\n\n" + "For some of the .ONE files (such as shadow.one), Shadow The Hedgehog seems to expect a strict file order.\n\n" + "It is highly recommended you either use the Replace button or reimport the files in the same order as the original when creating new archives and reimporting."); } } }
/// <summary> /// Tries to check the type of .ONE file that the supplied byte array is /// and sets the checkbox hint beside the save buttons appropriately. /// </summary> /// <param name="oneFile"></param> private void SetCheckboxHint(ref byte[] oneFile) { // Update toolstrip save button hint. var archiveType = ONEArchiveTester.GetArchiveType(ref oneFile); saveShadow050ToolStripMenuItem.Checked = false; saveShadow060ToolStripMenuItem.Checked = false; saveToolStripMenuItem.Checked = false; switch (archiveType) { case ONEArchiveType.Heroes: saveToolStripMenuItem.Checked = true; break; case ONEArchiveType.Shadow050: saveShadow050ToolStripMenuItem.Checked = true; break; case ONEArchiveType.Shadow060: saveShadow060ToolStripMenuItem.Checked = true; break; } }
/// <summary> /// Parses a Shadow The Hedgehog ONE file from a byte array and returns a ONE file structure back. /// </summary> /// <returns>The structure of a ONE Archive.</returns> // ReSharper disable once InconsistentNaming public static ONEShadowArchive ParseONEFile(ref byte[] file) { // Know if we're dealing with Shadow050 or Shadow060 ONEArchiveType archiveType = ONEArchiveTester.GetArchiveType(ref file); // Do not accept Heroes archives. if (archiveType == ONEArchiveType.Heroes) { throw new ArgumentException("The supplied .ONE file does not appear to be a Shadow The Hedgehog .ONE file"); } // Instantiate either a Shadow 050 or Shadow 060 archive. ONEShadowArchive oneShadowArchive = new ONEShadowArchive(); // Pointer for our file. int pointer = 0; oneShadowArchive.FileHeader = StructUtilities.ArrayToStructureUnsafe <ONEHeader>(ref file, pointer, ref pointer); oneShadowArchive.OneVersion = StructUtilities.ArrayToStructureUnsafe <ONEFileVersion>(ref file, pointer, ref pointer); oneShadowArchive.FileCount = StructUtilities.ArrayToStructureUnsafe <int>(ref file, pointer, ref pointer); oneShadowArchive.OnePadding = StructUtilities.ArrayToStructureUnsafe <ONEPadding>(ref file, pointer, ref pointer); // Populate the file list. oneShadowArchive.Files = new List <IFileEntry>(); // Populate the individual files. // Here we will filter out our dummies by for (int x = 0; x < oneShadowArchive.FileCount + 2; x++) { if (archiveType == ONEArchiveType.Shadow050) { ONE50FileEntry entry = StructUtilities.ArrayToStructureUnsafe <ONE50FileEntry>(ref file, pointer, ref pointer); if (entry.EndOfBlock == 1) { oneShadowArchive.Files.Add(entry); } } else { ONE60FileEntry entry = StructUtilities.ArrayToStructureUnsafe <ONE60FileEntry>(ref file, pointer, ref pointer); if (entry.EndOfBlock == 1) { oneShadowArchive.Files.Add(entry); } } } // Get individual file data. oneShadowArchive.FileData = new List <byte[]>(oneShadowArchive.FileCount); /* * From observation we can deduce that the first 0xC length header present at the start of the file * is not technically part of the .ONE Archive structure itself for any of the .ONE variations but is * instead, metadata preprended to each of the files once they have been generated. The first always empty * integer is probably simply acting as a reserved value. * * Thus all of the offsets present inside the ONE Files are actually incorrect as they are read here due to this * and offset from the true location inside the actual file by the length of the header in question. * * We must take this into consideration when obtaining the length of the last file and the offset of each of the individual files. */ int headerSize = Marshal.SizeOf <ONEHeader>(); int trueFileSize = file.Length - headerSize; for (int x = 0; x < oneShadowArchive.Files.Count; x++) { // Define our locals int length; int offset; byte[] compressedData; // Determine our locals. if (x != oneShadowArchive.Files.Count - 1) { length = oneShadowArchive.Files[x + 1].FileOffset - oneShadowArchive.Files[x].FileOffset; } else { length = trueFileSize - oneShadowArchive.Files[x].FileOffset; } // Last file needs to count from the end of file. // Set our offset. offset = oneShadowArchive.Files[x].FileOffset + headerSize; // Create managed byte array and copy into it. compressedData = new byte[length]; Array.Copy(file, offset, compressedData, 0, length); // We're done here. oneShadowArchive.FileData.Add(compressedData); } return(oneShadowArchive); }