From f57e9fa7e77c662aeec1b866e1ad0b289079d529 Mon Sep 17 00:00:00 2001 From: Kaphotics Date: Sun, 30 Oct 2016 11:52:19 -0700 Subject: [PATCH] Add GARC Version 6 handling --- pk3DS/3DS/GARC.cs | 108 ++++++++++++++++++++++++++++++--------- pk3DS/Game/GameConfig.cs | 1 + pk3DS/Main.cs | 2 +- pk3DS/Tools/ToolsUI.cs | 8 ++- 4 files changed, 93 insertions(+), 26 deletions(-) diff --git a/pk3DS/3DS/GARC.cs b/pk3DS/3DS/GARC.cs index 6976ff7..27e6a60 100644 --- a/pk3DS/3DS/GARC.cs +++ b/pk3DS/3DS/GARC.cs @@ -8,13 +8,19 @@ namespace CTR { #region GARC Class & Struct - public class GARC + public static class GARC { - internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar pBar1 = null, Label label = null, bool supress = false) + public const ushort VER_6 = 0x0600; + public const ushort VER_4 = 0x0400; + + public static bool garcPackMS(string folderPath, string garcPath, int version, ProgressBar pBar1 = null, Label label = null, bool supress = false) { // Check to see if our input folder exists. if (!new DirectoryInfo(folderPath).Exists) { Util.Error("Folder does not exist."); return false; } + if (version != VER_4 && version != VER_6) + throw new FormatException("Invalid GARC Version: 0x" + version.ToString("X4")); + // Okay some basic proofing is done. Proceed. int filectr = 0; // Get the paths of the files to pack up. @@ -58,12 +64,13 @@ internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar // Set Up the GARC template. GARCFile garc = new GARCFile { + ContentPadToNearest = 4, fato = { // Magic = new[] { 'O', 'T', 'A', 'F' }, Entries = new FATO_Entry[packOrder.Length], - EntryCount = (ushort)packOrder.Length, - HeaderSize = 0xC + packOrder.Length * 4, + EntryCount = (ushort) packOrder.Length, + HeaderSize = 0xC + packOrder.Length*4, Padding = 0xFFFF }, fatb = @@ -73,6 +80,12 @@ internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar FileCount = filectr } }; + if (version == VER_6) + { + // Some files have larger bytes-to-pad values (ex/ 0x80 for a109) + // Hopefully there's no problems defining this with a constant number. + garc.ContentPadToNearest = 4; + } #region Start Reassembling the FAT* tables. { @@ -190,12 +203,18 @@ internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar gw.Write((uint)0x47415243); // GARC gw.Write((uint)0x0000001C); // Header Length gw.Write((ushort)0xFEFF); // Endianness BOM - gw.Write((ushort)0x0400); // Const (4) + gw.Write((ushort)version); // Version gw.Write((uint)0x00000004); // Section Count (4) gw.Write((uint)0x00000000); // Data Offset (temp) gw.Write((uint)0x00000000); // File Length (temp) gw.Write((uint)0x00000000); // Largest File Size (temp) + if (version == VER_6) + { + gw.Write((uint)0x0); + gw.Write((uint)0x0); + } + // Write FATO gw.Write((uint)0x4641544F); // FATO gw.Write(garc.fato.HeaderSize); // Header Size @@ -218,6 +237,7 @@ internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar #region Write Files long largestSize = 0; // Required memory to allocate to handle the largest file + long largestPadded = 0; // Required memory to allocate to handle the largest PADDED file (Ver6 only) foreach (string e in packOrder) { string[] fa = Directory.Exists(e) ? Directory.GetFiles(e) : new[] { e }; @@ -225,16 +245,28 @@ internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar { // Update largest file length if necessary long len = new FileInfo(f).Length; - if (len > largestSize) largestSize = len; + bool largest = len > largestSize; + if (largest) + { + largestSize = len; + largestPadded = len; + } // Write to FIMB using (var input = File.OpenRead(f)) input.CopyTo(GARCdata); // While length is not divisible by 4, pad with FF (unused byte) - while (GARCdata.Length % 4 > 0) GARCdata.WriteByte(0xFF); + while (GARCdata.Length%garc.ContentPadToNearest != 0) + { + GARCdata.WriteByte(0xFF); + if (largest) + largestPadded++; + } } } + garc.ContentLargestUnpadded = (uint)largestSize; + garc.ContentLargestPadded = (uint)largestPadded; #endregion gw.Write((uint)0x46494D42); // FIMB @@ -244,7 +276,18 @@ internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar gw.Seek(0x10, SeekOrigin.Begin); // Goto the start of the un-set 0 data we set earlier and set it. gw.Write((uint)newGARC.Length); // Write Data Offset gw.Write((uint)(newGARC.Length + GARCdata.Length)); // Write total GARC Length - gw.Write((uint)largestSize); // Write Largest File stat (?) + + // Write Handling information + if (version == VER_4) + { + gw.Write(garc.ContentLargestUnpadded); // Write Largest File stat + } + else if (version == VER_6) + { + gw.Write(garc.ContentLargestPadded); // Write Largest With Padding + gw.Write(garc.ContentLargestUnpadded); // Write Largest Without Padding + gw.Write(garc.ContentPadToNearest); + } newGARC.Seek(0, SeekOrigin.End); // Goto the end so we can copy the filedata after the GARC headers. @@ -268,7 +311,7 @@ internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar catch (Exception e) { Util.Error("Packing Failed!", e.ToString()); return false; } } } - internal static bool garcUnpack(string garcPath, string outPath, bool skipDecompression, ProgressBar pBar1 = null, Label label = null, bool supress = false, bool bypassExt = false) + public static bool garcUnpack(string garcPath, string outPath, bool skipDecompression, ProgressBar pBar1 = null, Label label = null, bool supress = false, bool bypassExt = false) { if (!File.Exists(garcPath) && !supress) { Util.Alert("File does not exist"); return false; } @@ -378,7 +421,7 @@ internal static bool garcUnpack(string garcPath, string outPath, bool skipDecomp return true; } - internal static GARCFile unpackGARC(string path) + public static GARCFile unpackGARC(string path) { GARCFile garc = new GARCFile(); using (BinaryReader br = new BinaryReader(File.OpenRead(path))) @@ -387,12 +430,25 @@ internal static GARCFile unpackGARC(string path) garc.Magic = br.ReadChars(4); garc.HeaderSize = br.ReadUInt32(); garc.Endianess = br.ReadUInt16(); - garc.ChunkCount = br.ReadUInt16(); - garc.FileSize = br.ReadUInt32(); + garc.Version = br.ReadUInt16(); + garc.ChunkCount = br.ReadUInt32(); garc.DataOffset = br.ReadUInt32(); garc.FileSize = br.ReadUInt32(); - garc.LastSize = br.ReadUInt32(); + if (garc.Version == VER_4) + { + garc.ContentLargestUnpadded = br.ReadUInt32(); + } + else if (garc.Version == VER_6) + { + garc.ContentLargestPadded = br.ReadUInt32(); + garc.ContentLargestUnpadded = br.ReadUInt32(); + garc.ContentPadToNearest = br.ReadUInt32(); + } + else + throw new FormatException("Invalid GARC Version: 0x" + garc.Version.ToString("X4")); + if (garc.ChunkCount != 4) + throw new FormatException("Invalid GARC Chunk Count: " + garc.ChunkCount); // FATO (File Allocation Table Offsets) garc.fato.Magic = br.ReadChars(4); @@ -442,14 +498,18 @@ internal static GARCFile unpackGARC(string path) public struct GARCFile { - public Char[] Magic; // Always GARC = 0x4E415243 + public char[] Magic; // Always GARC = 0x4E415243 public uint HeaderSize; // Always 0x001C - public UInt16 Endianess; // 0xFFFE - public UInt16 ChunkCount; // Always 0x0400 chunk count + public ushort Endianess; // 0xFFFE + public ushort Version; // 4: gen6, 6: gen7 + public uint ChunkCount; // Always 0x0400 chunk count public uint DataOffset; public uint FileSize; - public uint LastSize; + + public uint ContentLargestPadded; // Format 6 Only + public uint ContentLargestUnpadded; + public uint ContentPadToNearest; // Format 6 Only (4 bytes is standard in VER_4, and is not stored) public FATO fato; public FATB fatb; @@ -458,10 +518,10 @@ public struct GARCFile public struct FATO { - public Char[] Magic; + public char[] Magic; public int HeaderSize; - public UInt16 EntryCount; - public UInt16 Padding; + public ushort EntryCount; + public ushort Padding; public FATO_Entry[] Entries; } @@ -472,7 +532,7 @@ public struct FATO_Entry public struct FATB { - public Char[] Magic; + public char[] Magic; public int HeaderSize; public int FileCount; @@ -481,12 +541,12 @@ public struct FATB public struct FATB_Entry { public uint Vector; - public Boolean IsFolder; + public bool IsFolder; public FATB_SubEntry[] SubEntries; } public struct FATB_SubEntry { - public Boolean Exists; + public bool Exists; public int Start; public int End; public int Length; @@ -494,7 +554,7 @@ public struct FATB_SubEntry public struct FIMG { - public Char[] Magic; + public char[] Magic; public int HeaderSize; public int DataSize; } diff --git a/pk3DS/Game/GameConfig.cs b/pk3DS/Game/GameConfig.cs index b82fdd8..5d998b5 100644 --- a/pk3DS/Game/GameConfig.cs +++ b/pk3DS/Game/GameConfig.cs @@ -83,6 +83,7 @@ private void getGameData(GameVersion game) public bool ORAS => Version == GameVersion.ORAS || Version == GameVersion.ORASDEMO; public bool SM => Version == GameVersion.SM || Version == GameVersion.SMDEMO; public int MaxSpeciesID => XY || ORAS ? 722 : 802; + public int GARCVersion => XY || ORAS ? CTR.GARC.VER_4 : CTR.GARC.VER_6; public bool IsRebuildable(int fileCount) { switch (fileCount) diff --git a/pk3DS/Main.cs b/pk3DS/Main.cs index 6317067..19e723d 100644 --- a/pk3DS/Main.cs +++ b/pk3DS/Main.cs @@ -911,7 +911,7 @@ private bool setGARC(string outfile, string infolder, bool PB) try { - bool success = CTR.GARC.garcPackMS(infolder, outfile, PB ? pBar1 : null, null, true); + bool success = CTR.GARC.garcPackMS(infolder, outfile, Config.GARCVersion, PB ? pBar1 : null, null, true); threads--; updateStatus(string.Format(success ? "Success!" : "Failed!"), false); return success; diff --git a/pk3DS/Tools/ToolsUI.cs b/pk3DS/Tools/ToolsUI.cs index 658c0c3..a103cf1 100644 --- a/pk3DS/Tools/ToolsUI.cs +++ b/pk3DS/Tools/ToolsUI.cs @@ -173,9 +173,15 @@ private void saveARC(string path) case 1: // GARC Pack { if (threads > 0) { Util.Alert("Please wait for all operations to finish first."); return; } + DialogResult dr = Util.Prompt(MessageBoxButtons.YesNoCancel, "Format Selection:", + "Yes: Sun/Moon (Version 6)\nNo: XY/ORAS (Version 4)"); + if (dr == DialogResult.Cancel) + return; + + var version = dr == DialogResult.Yes ? CTR.GARC.VER_6 : CTR.GARC.VER_4; new Thread(() => { - bool r = CTR.GARC.garcPackMS(path, folderName + ".garc", pBar1); + bool r = CTR.GARC.garcPackMS(path, folderName + ".garc", version, pBar1); if (!r) { Util.Alert("Packing failed."); return; } // Delete path after repacking if (CHK_Delete.Checked && Directory.Exists(path))