From 05c5bfe8f63e7a3d2251c60d4e94aad60dbff607 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 3 May 2015 14:55:39 -0700 Subject: [PATCH] Refactoring & Code Integration from other projects Added DARC Batch by SciresM Added png2bclim conversion by Kaphotics/SciresM Really really old code; just using for some quick tool integration. --- pk3DS/ARCUtil.cs | 940 ++++++++++++++++++++++++++++++++ pk3DS/GARCTool.cs | 2 +- pk3DS/Main.cs | 2 +- pk3DS/Subforms/Moves.cs | 8 +- pk3DS/Subforms/OWSE.cs | 2 +- pk3DS/Tools/ToolsUI.Designer.cs | 52 +- pk3DS/Tools/ToolsUI.cs | 26 +- pk3DS/Util.cs | 179 +----- pk3DS/pk3DS.csproj | 11 + pk3DS/png2bclim.cs | 740 +++++++++++++++++++++++++ 10 files changed, 1764 insertions(+), 198 deletions(-) create mode 100644 pk3DS/ARCUtil.cs create mode 100644 pk3DS/png2bclim.cs diff --git a/pk3DS/ARCUtil.cs b/pk3DS/ARCUtil.cs new file mode 100644 index 0000000..288ff14 --- /dev/null +++ b/pk3DS/ARCUtil.cs @@ -0,0 +1,940 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace pk3DS +{ + public partial class ARC + { + // Multi Type Archive Handling + internal static Boolean onefile = true; + internal static SARC analyzeSARC(string path) + { + SARC sarc = new SARC + { + FileName = Path.GetFileNameWithoutExtension(path), + FilePath = Path.GetDirectoryName(path), + Extension = Path.GetExtension(path) + }; + BinaryReader br = new BinaryReader(File.OpenRead(path)); + sarc.valid = true; + sarc.Signature = new string(br.ReadChars(4)); + if (sarc.Signature != "SARC") + { + sarc.valid = false; + return sarc; + } + sarc.HeaderSize = br.ReadUInt16(); + sarc.Endianness = br.ReadUInt16(); + sarc.FileSize = br.ReadUInt32(); + sarc.DataOffset = br.ReadUInt32(); + sarc.Unknown = br.ReadUInt32(); + sarc.SFat = new SFAT { Signature = new string(br.ReadChars(4)) }; + if (sarc.SFat.Signature != "SFAT") + { + sarc.valid = false; + return sarc; + } + sarc.SFat.HeaderSize = br.ReadUInt16(); + sarc.SFat.EntryCount = br.ReadUInt16(); + sarc.SFat.HashMult = br.ReadUInt32(); + sarc.SFat.Entries = new List(); + for (int i = 0; i < sarc.SFat.EntryCount; i++) + { + SFATEntry s = new SFATEntry + { + FileNameHash = br.ReadUInt32(), + FileNameOffset = br.ReadUInt32(), + FileDataStart = br.ReadUInt32(), + FileDataEnd = br.ReadUInt32() + }; + sarc.SFat.Entries.Add(s); + } + sarc.SFnt = new SFNT { Signature = new string(br.ReadChars(4)) }; + if (sarc.SFnt.Signature != "SFNT") + { + sarc.valid = false; + return sarc; + } + sarc.SFnt.HeaderSize = br.ReadUInt16(); + sarc.SFnt.Unknown = br.ReadUInt16(); + sarc.SFnt.StringOffset = (uint)br.BaseStream.Position; + return sarc; + } + internal static ShuffleARC AnalyzeShuffle(string path) + { + ShuffleARC sharc = new ShuffleARC + { + FileName = Path.GetFileNameWithoutExtension(path), + FilePath = Path.GetDirectoryName(path), + Extension = Path.GetExtension(path) + }; + BinaryReader br = new BinaryReader(File.OpenRead(path)); + if (br.ReadUInt32() != 0xB) + { + br.BaseStream.Seek(0x100, SeekOrigin.Begin); + if (br.ReadUInt32() != 0xB) + { + sharc.valid = false; + return sharc; + } + sharc.add100 = true; + } + uint magic = br.ReadUInt32(); + if (magic.ToString("X8") != sharc.FileName) + { + Console.WriteLine("Sharc mismatch - " + magic.ToString("X8") + "," + sharc.FileName); + sharc.valid = false; + return sharc; + } + sharc.valid = true; + br.ReadUInt32(); + br.ReadUInt32(); + sharc.FileCount = br.ReadUInt32(); + br.ReadUInt32(); + sharc.Files = new List(); + for (int i = 0; i < sharc.FileCount; i++) + { + br.BaseStream.Seek(0x8, SeekOrigin.Current); + ShuffleFile sf = new ShuffleFile + { + Length = br.ReadUInt32(), + Offset = br.ReadUInt32() + (uint)(sharc.add100 ? 0x100 : 0) + }; + br.BaseStream.Seek(0x10, SeekOrigin.Current); + sharc.Files.Add(sf); + } + return sharc; + } + internal static GAR analyzeGAR(string path) + { + GAR gar = new GAR + { + FileName = Path.GetFileNameWithoutExtension(path), + FilePath = Path.GetDirectoryName(path), + Extension = Path.GetExtension(path) + }; + BinaryReader br = new BinaryReader(File.OpenRead(path)); + long len = br.BaseStream.Length; + gar.Magic = br.ReadUInt32(); + gar.FileLength = br.ReadUInt32(); + if (gar.Magic != 0x02524147 || gar.FileLength != len) + { + gar.valid = false; + return gar; + } + gar.valid = true; + gar.Unknown = br.ReadUInt32(); + gar.HeaderLength = br.ReadUInt32(); + gar.FileMetaOffset = br.ReadUInt32(); + gar.FileOffsetsOffset = br.ReadUInt32(); + br.BaseStream.Seek(0x1C, SeekOrigin.Current); + gar.FileCountOffset = br.ReadUInt32(); + gar.CTXBOffset = br.ReadUInt32(); + br.BaseStream.Seek(gar.FileOffsetsOffset, SeekOrigin.Begin); + gar.DataOffset = br.ReadUInt32(); + gar.FileCount = (gar.DataOffset - gar.FileOffsetsOffset) / 4; + br.BaseStream.Seek(gar.FileMetaOffset, SeekOrigin.Begin); + gar.Files = new List(); + for (int i = 0; i < gar.FileCount; i++) + { + GARFile gf = new GARFile + { + Length = br.ReadUInt32(), + NOffset = br.ReadUInt32(), + NWEOffset = br.ReadUInt32() + }; + gar.Files.Add(gf); + } + for (int i = 0; i < gar.FileCount; i++) + { + br.BaseStream.Seek(gar.Files[i].NOffset, SeekOrigin.Begin); + StringBuilder sb = new StringBuilder(); + for (char c = br.ReadChar(); c != (char)0; c = br.ReadChar()) + { + sb.Append(c); + } + gar.Files[i].Name = sb.ToString(); + br.BaseStream.Seek(gar.Files[i].NWEOffset, SeekOrigin.Begin); + sb = new StringBuilder(); + for (char c = br.ReadChar(); c != (char)0; c = br.ReadChar()) + { + sb.Append(c); + } + gar.Files[i].NameWithExtension = sb.ToString(); + } + br.BaseStream.Seek(gar.FileOffsetsOffset, SeekOrigin.Begin); + if (gar.Files.Count > 0) + { + gar.Files[0].Offset = gar.DataOffset; + br.ReadUInt32(); + } + for (int i = 1; i < gar.FileCount; i++) + { + gar.Files[i].Offset = br.ReadUInt32(); + } + return gar; + } + internal static DARC analyze(string path) + { + DARC darc = new DARC + { + FileName = Path.GetFileNameWithoutExtension(path), + FilePath = Path.GetDirectoryName(path), + Extension = Path.GetExtension(path) + }; + BinaryReader br = new BinaryReader(File.OpenRead(path)); + long len = br.BaseStream.Length; + darc.Magic = br.ReadUInt32(); + UInt32 m = darc.Magic; + darc.HeaderOffset = 0; + while (m != 0x63726164 && darc.HeaderOffset < len - 4) + { + m = br.ReadUInt32(); + darc.HeaderOffset += 4; + } + if (darc.HeaderOffset >= len - 4) + { + darc.valid = false; + return darc; + } + darc.Magic = m; + darc.valid = true; + darc.BOM = br.ReadUInt16(); + darc.HeaderLength = br.ReadUInt16(); + darc.Unknown = br.ReadUInt32(); + darc.totalLength = br.ReadUInt32(); + darc.TableOffset = br.ReadUInt32(); //from start of file + darc.TableOffset += darc.HeaderOffset; + darc.TableLength = br.ReadUInt32(); + darc.DataOffset = br.ReadUInt32(); + FileTable ft = new FileTable(); + br.BaseStream.Seek(darc.TableOffset + 8, SeekOrigin.Begin); + int count = br.ReadByte(); + ft.Files = new List(); + ft.FileNames = new List(); + br.BaseStream.Seek(darc.TableOffset, SeekOrigin.Begin); + for (int i = 0; i < count; i++) + { + DarcFile file = new DarcFile + { + NameOffset = br.ReadUInt16(), + Parent = br.ReadByte(), + Folder = br.ReadByte(), + Offset = br.ReadUInt32() + darc.HeaderOffset, + Length = br.ReadUInt32() + }; + DarcFile f2 = file; + ft.Files.Add(f2); + } + + uint NameTableOffset = (uint)br.BaseStream.Position; + for (int i = 0; i < ft.Files.Count; i++) + { + br.BaseStream.Seek(NameTableOffset + ft.Files[i].NameOffset, SeekOrigin.Begin); + MemoryStream stream = new MemoryStream(); + for (byte fb = br.ReadByte(), sb = br.ReadByte(); + fb != 0 || sb != 0; + fb = br.ReadByte(), sb = br.ReadByte()) + { + stream.WriteByte(fb); + stream.WriteByte(sb); + } + ft.FileNames.Add(Encoding.Unicode.GetString(stream.ToArray())); + stream.Close(); + } + darc.Files = ft; + darc.FileName = Path.GetFileNameWithoutExtension(path); + darc.FilePath = Path.GetDirectoryName(path); + darc.Extension = Path.GetExtension(path); + return darc; + } + internal static FARC analyzeFARC(string path) + { + FARC farc = new FARC + { + FileName = Path.GetFileNameWithoutExtension(path), + FilePath = Path.GetDirectoryName(path), + Extension = Path.GetExtension(path) + }; + BinaryReader br = new BinaryReader(File.OpenRead(path)); + long len = br.BaseStream.Length; + farc.Magic = br.ReadUInt32(); + UInt32 m = farc.Magic; + farc.HeaderOffset = 0; + while (m != 0x43524146 && farc.HeaderOffset < len - 4) //FARC + { + m = br.ReadUInt32(); + farc.HeaderOffset += 4; + } + if (farc.HeaderOffset >= len - 4) + { + farc.valid = false; + return farc; + } + farc.Magic = m; + farc.valid = true; + br.BaseStream.Seek(farc.HeaderOffset + 0x24, SeekOrigin.Begin); + farc.SirOffset = br.ReadUInt32() + farc.HeaderOffset; + br.ReadUInt32(); //unk + farc.DataOffset = br.ReadUInt32() + farc.HeaderOffset; + br.BaseStream.Seek(farc.SirOffset, SeekOrigin.Begin); + farc.SirMagic = br.ReadUInt32(); + if (farc.SirMagic != 0x30524953) + { + farc.valid = false; + return farc; + } + farc.MetaPointer = farc.SirOffset + br.ReadUInt32(); + br.BaseStream.Seek(farc.MetaPointer, SeekOrigin.Begin); + farc.TableOffset = farc.SirOffset + br.ReadUInt32(); + farc.FileCount = br.ReadUInt32(); + br.BaseStream.Seek(farc.TableOffset, SeekOrigin.Begin); + FARCFileTable ft = new FARCFileTable + { + Files = new List(), + FileNames = new List() + }; + for (int i = 0; i < farc.FileCount; i++) + { + FARCFile file = new FARCFile + { + NameOffset = br.ReadUInt32(), + Offset = br.ReadUInt32(), + Length = br.ReadUInt32() + }; + br.ReadUInt32(); //align to 0x10 + FARCFile f2 = file; + ft.Files.Add(f2); + } + for (int i = 0; i < farc.FileCount; i++) + { + br.BaseStream.Seek(ft.Files[i].NameOffset + farc.SirOffset, SeekOrigin.Begin); + MemoryStream stream = new MemoryStream(); + int firstByte = 1, secondByte = 1; + while (firstByte != 0 || secondByte != 0) + { + firstByte = br.ReadByte(); + secondByte = br.ReadByte(); + stream.WriteByte((byte) firstByte); + stream.WriteByte((byte) secondByte); + } + ft.FileNames.Add(Encoding.Unicode.GetString(stream.ToArray())); + stream.Close(); + } + farc.Files = ft; + farc.FileName = Path.GetFileNameWithoutExtension(path); + farc.FilePath = Path.GetDirectoryName(path); + farc.Extension = Path.GetExtension(path); + return farc; + } + internal static string Interpret(string path) + { + string fn = Path.GetFileName(path); + if (fn == "save0.bin" || fn == "save1.bin" || fn == "save2.bin") + { + return FixMajoraChecksum(path); + } + if (fn.StartsWith("message") && (fn.EndsWith("_US.bin") || fn.EndsWith("_UK.bin"))) + { + return ParseShuffleText(path); + } + DARC darc = analyze(path); + FARC farc = new FARC(); + GAR gar = new GAR(); + SARC sarc = new SARC(); + ShuffleARC sharc = new ShuffleARC(); + if (!darc.valid) farc = analyzeFARC(path); + if (!farc.valid) gar = analyzeGAR(path); + if (!gar.valid) sharc = AnalyzeShuffle(path); + if (!sharc.valid) sarc = analyzeSARC(path); + + string ret = ""; + if (darc.valid) + { + ret += "Header Offset: " + darc.HeaderOffset + Environment.NewLine + "File Count: " + darc.Files.Files.Count + Environment.NewLine; + int extracted = 0; + int folder = 0; + for (int i = 0; i < darc.Files.Files.Count; i++) + { + if (darc.Files.Files[i].Folder > 0) { folder++; } + else + { + extracted++; + string dir = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + darc.FileName + Path.DirectorySeparatorChar; + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + string outPath = dir + darc.Files.FileNames[i]; + var fs = File.OpenRead(path); + fs.Seek(darc.Files.Files[i].Offset, SeekOrigin.Begin); + byte[] fileBuffer = new byte[darc.Files.Files[i].Length]; + fs.Read(fileBuffer, 0, fileBuffer.Length); + fs.Close(); + File.WriteAllBytes(outPath, fileBuffer); + } + } + ret += "Extracted " + extracted + " files"; + if (folder > 0) + { + ret += ", did not extract " + folder + " folders"; + } + ret += "." + Environment.NewLine + "Open a .DARC/.SARC/.FARC/.GAR/Shuffle Archive file (or drag/drop)."; + } + else if (farc.valid) + { + ret += "Header Offset: " + farc.HeaderOffset + Environment.NewLine; + int extracted = 0; + for (int i = 0; i < farc.Files.Files.Count; i++) + { + extracted++; + string dir = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + "FARC_" + farc.FileName + Path.DirectorySeparatorChar; + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + string outPath = dir + farc.Files.FileNames[i]; + var fs = File.OpenRead(farc.FilePath + "\\" + farc.FileName + farc.Extension); + fs.Seek(farc.Files.Files[i].Offset + farc.DataOffset, SeekOrigin.Begin); + byte[] fileBuffer = new byte[farc.Files.Files[i].Length]; + fs.Read(fileBuffer, 0, fileBuffer.Length); + fs.Close(); + File.WriteAllBytes(outPath, fileBuffer); + } + ret += "Extracted " + extracted + " files"; + ret += "." + Environment.NewLine + ".DARC/.FARC/.SARC/.GAR/Shuffle Archive file (or drag/drop)."; + } + else if (gar.valid) + { + ret += "New GAR with " + gar.FileCount + " files." + Environment.NewLine; + string dir = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + gar.FileName + Path.DirectorySeparatorChar; + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + for (int i = 0; i < gar.FileCount; i++) + { + var fs = File.OpenRead(path); + fs.Seek(gar.Files[i].Offset, SeekOrigin.Begin); + byte[] fileBuffer = new byte[gar.Files[i].Length]; + fs.Read(fileBuffer, 0, fileBuffer.Length); + fs.Close(); + File.WriteAllBytes(dir + gar.Files[i].NameWithExtension, fileBuffer); + ret += "Extracted " + gar.Files[i].NameWithExtension + " (Offset: " + gar.Files[i].Offset.ToString("X8") + ", Len: " + gar.Files[i].Length.ToString("X8") + ")." + Environment.NewLine; + } + ret += Environment.NewLine; + } + else if (sharc.valid) + { + ret += "New Shuffle Archive with " + sharc.FileCount + " files." + Environment.NewLine; + string dir = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + sharc.FileName + "_" + Path.DirectorySeparatorChar; + if (!Directory.Exists(dir)) + { + Console.WriteLine("Making dir: " + dir); + Directory.CreateDirectory(dir); + } + + string diglen = "".PadLeft((int)(Math.Log10(sharc.FileCount) + 1), '0'); + for (int i = 0; i < sharc.FileCount; i++) + { + var fs = File.OpenRead(path); + fs.Seek(sharc.Files[i].Offset, SeekOrigin.Begin); + byte[] fileBuffer = new byte[sharc.Files[i].Length]; + fs.Read(fileBuffer, 0, fileBuffer.Length); + fs.Close(); + uint check = 0; + for (int j = 0; j < fileBuffer.Length; j += 4) + check += BitConverter.ToUInt32(fileBuffer, j); + Console.WriteLine(i.ToString(diglen) + ": " + check.ToString("X8")); + File.WriteAllBytes(dir + i.ToString(diglen) + ".zip", fileBuffer); + ret += "Extracted " + i.ToString(diglen) + " (Offset: " + sharc.Files[i].Offset.ToString("X8") + ", Len: " + sharc.Files[i].Length.ToString("X8") + ")." + Environment.NewLine; + } + ret += Environment.NewLine; + } + else if (sarc.valid) + { + ret = "New SARC with " + sarc.SFat.EntryCount + " files." + Environment.NewLine; + string dir = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + sarc.FileName + "_" + Path.DirectorySeparatorChar; + if (!Directory.Exists(dir)) + { + Console.WriteLine("Making dir: " + dir); + Directory.CreateDirectory(dir); + } + + foreach (SFATEntry t in sarc.SFat.Entries) + { + var fs = File.OpenRead(path); + uint FileLen = t.FileDataEnd - t.FileDataStart; + string FileName = ""; + fs.Seek(t.FileDataStart + sarc.DataOffset, SeekOrigin.Begin); + byte[] fileBuffer = new byte[FileLen]; + fs.Read(fileBuffer, 0, (int)FileLen); + fs.Seek(sarc.SFnt.StringOffset, SeekOrigin.Begin); + fs.Seek((t.FileNameOffset & 0x00FFFFFF) * 4, SeekOrigin.Current); + StringBuilder sb = new StringBuilder(); + for (char c = (char)fs.ReadByte(); c != 0; c = (char)fs.ReadByte()) + { + sb.Append(c); + } + FileName = sb.ToString().Replace('/', Path.DirectorySeparatorChar); + fs.Close(); + string FileDir = Path.GetDirectoryName(dir + FileName) + Path.DirectorySeparatorChar; + if (!Directory.Exists(FileDir)) + { + Console.WriteLine("Making dir: " + FileDir); + Directory.CreateDirectory(FileDir); + } + File.WriteAllBytes(dir + FileName, fileBuffer); + } + } + else + { + ret = "Not a valid .DARC/.FARC/.SARC/.GAR/Shuffle Archive file"; + } + return ret; + } + + // Mini Packing Util + internal static void packMini(string path, string ident, string fileName, string outExt = null, string outFolder = null) + { + if (outFolder == null) outFolder = path; + if (outExt == null) outExt = ".bin"; + // Create new Binary with the relevant header bytes + byte[] data = new byte[4]; + data[0] = (byte)ident[0]; + data[1] = (byte)ident[1]; + string[] files = Directory.GetFiles(path); + Array.Copy(BitConverter.GetBytes((ushort)files.Length), 0, data, 2, 2); + + int count = files.Length; + int dataOffset = 4 + 4 + count * 4; + + // Start the data filling. + using (MemoryStream dataout = new MemoryStream()) + using (MemoryStream offsetMap = new MemoryStream()) + using (BinaryWriter bd = new BinaryWriter(dataout)) + using (BinaryWriter bo = new BinaryWriter(offsetMap)) + { + // For each file... + for (int i = 0; i < count; i++) + { + // Write File Offset + uint fileOffset = (uint)(dataout.Position + dataOffset); + bo.Write(fileOffset); + + // Write File to Stream + bd.Write(File.ReadAllBytes(files[i])); + + // Pad the Data MemoryStream with Zeroes until len%4=0; + while (dataout.Length % 4 != 0) + bd.Write((byte)0); + // File Offset will be updated as the offset is based off of the Data length. + + // Delete the File + File.Delete(files[i]); + } + // Cap the File + bo.Write((uint)(dataout.Position + dataOffset)); + + using (var newPack = File.Create(Path.Combine(outFolder, fileName + outExt))) + using (var header = new MemoryStream(data)) + { + header.WriteTo(newPack); + offsetMap.WriteTo(newPack); + dataout.WriteTo(newPack); + } + } + } + internal static byte[] packMini(byte[][] fileData, string ident) + { + // Create new Binary with the relevant header bytes + byte[] data = new byte[4]; + data[0] = (byte)ident[0]; + data[1] = (byte)ident[1]; + Array.Copy(BitConverter.GetBytes((ushort)fileData.Length), 0, data, 2, 2); + + int count = fileData.Length; + int dataOffset = 4 + 4 + count * 4; + + // Start the data filling. + using (MemoryStream dataout = new MemoryStream()) + using (MemoryStream offsetMap = new MemoryStream()) + using (BinaryWriter bd = new BinaryWriter(dataout)) + using (BinaryWriter bo = new BinaryWriter(offsetMap)) + { + // For each file... + for (int i = 0; i < count; i++) + { + // Write File Offset + uint fileOffset = (uint)(dataout.Position + dataOffset); + bo.Write(fileOffset); + + // Write File to Stream + bd.Write(fileData[i]); + + // Pad the Data MemoryStream with Zeroes until len%4=0; + while (dataout.Length % 4 != 0) + bd.Write((byte)0); + // File Offset will be updated as the offset is based off of the Data length. + } + // Cap the File + bo.Write((uint)(dataout.Position + dataOffset)); + + using (var newPack = new MemoryStream()) + using (var header = new MemoryStream(data)) + { + header.WriteTo(newPack); + offsetMap.WriteTo(newPack); + dataout.WriteTo(newPack); + return newPack.ToArray(); + } + } + } + internal static void unpackMini(string path, string ident, string outFolder = null) + { + if (outFolder == null) outFolder = Path.GetDirectoryName(path); + + using (var s = new FileStream(path, FileMode.Open)) + using (var br = new BinaryReader(s)) + { + string fx = new string(br.ReadChars(2)); + + if (fx != ident) return; + + ushort count = br.ReadUInt16(); + string namePad = "D" + Math.Ceiling(Math.Log10(count)); + uint[] offsets = new uint[count + 1]; + for (int i = 0; i < count; i++) + offsets[i] = br.ReadUInt32(); + + uint length = br.ReadUInt32(); + offsets[offsets.Length - 1] = length; + + for (int i = 0; i < count; i++) + { + br.BaseStream.Seek(offsets[i], SeekOrigin.Begin); + using (MemoryStream dataout = new MemoryStream()) + { + byte[] data = new byte[0]; + s.CopyTo(dataout, (int)offsets[i]); + int len = (int)offsets[i + 1] - (int)offsets[i]; + + if (len != 0) + { + data = dataout.ToArray(); + Array.Resize(ref data, len); + } + string newFile = Path.Combine(outFolder, i.ToString(namePad) + ".bin"); + File.WriteAllBytes(newFile, data); + } + } + } + File.Delete(path); // File is unpacked. + } + internal static byte[][] unpackMini(byte[] fileData, string ident) + { + using (var s = new MemoryStream(fileData)) + using (var br = new BinaryReader(s)) + { + string fx = new string(br.ReadChars(2)); + + if (fx != ident) return null; + + ushort count = br.ReadUInt16(); + byte[][] returnData = new byte[count][]; + + uint[] offsets = new uint[count + 1]; + for (int i = 0; i < count; i++) + offsets[i] = br.ReadUInt32(); + + uint length = br.ReadUInt32(); + offsets[offsets.Length - 1] = length; + + for (int i = 0; i < count; i++) + { + br.BaseStream.Seek(offsets[i], SeekOrigin.Begin); + using (MemoryStream dataout = new MemoryStream()) + { + byte[] data = new byte[0]; + s.CopyTo(dataout, (int)offsets[i]); + int len = (int)offsets[i + 1] - (int)offsets[i]; + + if (len != 0) + { + data = dataout.ToArray(); + Array.Resize(ref data, len); + } + returnData[i] = data; + } + } + return returnData; + } + } + + // Generic Utility + internal static string FixMajoraChecksum(string path) + { + byte[] data = File.ReadAllBytes(path); + Array.Copy(BitConverter.GetBytes((ushort)0), 0, data, 0x1A88, 2); + CRC16 crc = new CRC16(); + ushort val = crc.ComputeChecksum(data); + val ^= 0x903B; + Array.Copy(BitConverter.GetBytes(val), 0, data, 0x1A88, 2); + File.WriteAllBytes(path, data); + return "Corrected Majora Checksum to " + val.ToString("X4"); + } + internal static string ParseShuffleText(string path) + { + ShuffleText st = new ShuffleText + { + FileName = Path.GetFileNameWithoutExtension(path), + FilePath = Path.GetDirectoryName(path), + Extension = Path.GetExtension(path) + }; + Console.WriteLine(st.FilePath); + BinaryReader br = new BinaryReader(File.OpenRead(path)); + br.BaseStream.Seek(0xC, SeekOrigin.Begin); + uint StringDataLen = br.ReadUInt32(); + st.StringMetaOffset = br.ReadUInt32(); + st.StringMetaLen = br.ReadUInt32(); + st.StringCount = st.StringMetaLen / 4; + Console.WriteLine(st.StringCount); + br.BaseStream.Seek(st.StringMetaOffset, SeekOrigin.Begin); + st.offsets = new List(); + for (int i = 0; i < st.StringCount; i++) + { + st.offsets.Add(br.ReadUInt32()); + } + string ret = "Dumped Offsets."; + st.strings = new List(); + for (int i = 0; i < st.StringCount; i++) + { + br.BaseStream.Seek(st.offsets[i], SeekOrigin.Begin); + uint len = (i < st.StringCount - 1) + ? st.offsets[i + 1] - st.offsets[i] + : StringDataLen + 0x40 - st.offsets[i]; + byte[] data = br.ReadBytes((int)len); + st.strings.Add(Encoding.Unicode.GetString(data).Replace((char)0, ' ').Replace((char)0xa, ' ')); + } + ret += Environment.NewLine + "Dumped Strings."; + StringBuilder sb = new StringBuilder(); + foreach (string t in st.strings) + sb.AppendLine(t); + + string newfilename = st.FilePath + Path.DirectorySeparatorChar + st.FileName + ".txt"; + File.WriteAllText(newfilename, sb.ToString()); + return ret; + } + } + + public struct FARC + { + public UInt32 Magic; + public UInt32 SirMagic; + public UInt32 SirOffset; + public UInt32 HeaderOffset; + public UInt32 MetaPointer; //from start of file + public UInt32 NamesOffset; + public UInt32 TableOffset; //from start of file + public UInt32 DataOffset; //from start of file + public UInt32 FileCount; + public FARCFileTable Files; + + public string FileName; + public string FilePath; + public string Extension; + public Boolean valid; + } + public struct FARCFileTable + { + public List Files; + public List FileNames; + } + public struct FARCFile + { + public uint NameOffset; + public uint Offset; + public uint Length; + + public string Name; + } + + public class SARC + { + public string Signature; + public ushort HeaderSize = 0x14; + public ushort Endianness; + public uint FileSize; + public uint DataOffset; + public uint Unknown; + public SFAT SFat; + public SFNT SFnt; + + public string FileName; + public string FilePath; + public string Extension; + public bool valid; + } + public class SFAT + { + public string Signature; + public ushort HeaderSize; + public ushort EntryCount; + public uint HashMult; + public List Entries; + } + public class SFATEntry + { + public uint FileNameHash; + public uint FileNameOffset; + public uint FileDataStart; + public uint FileDataEnd; + } + public class SFNT + { + public string Signature; + public ushort HeaderSize; + public ushort Unknown; + public uint StringOffset; + } + + public class ShuffleARC + { + public uint magic; //0xB + public uint FileNameCheck; + public uint unk; + public uint unk2; + public uint FileCount; + public uint padding; + public List Files; + + public string FileName; + public string FilePath; + public string Extension; + public bool add100; + public bool valid; + } + public class ShuffleFile + { + public uint Offset; + public uint Length; + } + public class ShuffleText + { + public uint StringMetaOffset; + public uint StringMetaLen; + public uint StringCount; + public List offsets; + public List strings; + + public string FileName; + public string FilePath; + public string Extension; + public bool valid; + } + + public class GAR + { + public uint Magic; //0x02524146 "GAR" + public uint FileLength; + public uint Unknown; + public uint HeaderLength; + public uint FileMetaOffset; + public uint FileOffsetsOffset; + public uint FileCountOffset; + public uint CTXBOffset; //Filecount = (CTXBOffset-FileCountOffset)/4; + public uint DataOffset; + + public uint FileCount; + public List Files; + + public string FileName; + public string FilePath; + public string Extension; + public bool valid; + } + public class GARFile + { + public string NameWithExtension; + public string Name; + public uint NOffset; + public uint NWEOffset; + public uint Offset; + public uint Length; + } + + public struct DARC + { + public UInt32 HeaderOffset; // Where is header in file? + + public UInt32 Magic; // 0x64617263 "darc" + public UInt16 BOM; // 0xFFFE + public UInt16 HeaderLength; // HeaderLength - 0x1C + public UInt32 Unknown; // 0x10000000 + public UInt32 totalLength; // Total Length of file + public UInt32 TableOffset; // Offset from Start of File + public UInt32 TableLength; // Table Length + public UInt32 DataOffset; // Data Offset + + public FileTable Files; + + public string FileName; + public string FilePath; + public string Extension; + public Boolean valid; + } + public struct FileTable + { + public List Files; + public List FileNames; + } + public struct DarcFile + { + public ushort NameOffset; // + public byte Parent; + public byte Folder; + public uint Offset; + public uint Length; + + public string Name; + } + + public class CRC16 + { + private const ushort polynomial = 0xA001; + private ushort[] table = new ushort[256]; + + public ushort ComputeChecksum(byte[] bytes) + { + return bytes.Aggregate(0, (current, t) => (ushort) ((current >> 8) ^ table[(current ^ t)])); + } + + public byte[] ComputeChecksumBytes(byte[] bytes) + { + ushort crc = ComputeChecksum(bytes); + return BitConverter.GetBytes(crc); + } + + public CRC16() + { + for (ushort i = 0; i < table.Length; ++i) + { + ushort value = 0; + ushort temp = i; + for (byte j = 0; j < 8; ++j) + { + if (((value ^ temp) & 0x0001) != 0) + value = (ushort) ((value >> 1) ^ polynomial); + else + value >>= 1; + + temp >>= 1; + } + table[i] = value; + } + } + } +} diff --git a/pk3DS/GARCTool.cs b/pk3DS/GARCTool.cs index e4c7500..1138b61 100644 --- a/pk3DS/GARCTool.cs +++ b/pk3DS/GARCTool.cs @@ -274,7 +274,7 @@ public static bool garcUnpack(string garcPath, string outPath, bool skipDecompre // Sub-classes #region GARC Class & Struct - public class ARC + public partial class ARC { public static GARC unpackGARC(string path) { diff --git a/pk3DS/Main.cs b/pk3DS/Main.cs index 9e80783..c48b72a 100644 --- a/pk3DS/Main.cs +++ b/pk3DS/Main.cs @@ -513,7 +513,7 @@ private void B_OPower_Click(object sender, EventArgs e) // Extra Tools private void L_SubTools_Click(object sender, EventArgs e) { - new Tools().ShowDialog(); + new ToolsUI().ShowDialog(); } // GARC Requests diff --git a/pk3DS/Subforms/Moves.cs b/pk3DS/Subforms/Moves.cs index c94429c..b6e06e6 100644 --- a/pk3DS/Subforms/Moves.cs +++ b/pk3DS/Subforms/Moves.cs @@ -8,7 +8,7 @@ public partial class Moves : Form { public Moves() { - Util.unpackMini(Directory.GetFiles("move")[0], "WD"); + ARC.unpackMini(Directory.GetFiles("move")[0], "WD"); movelist[0] = ""; sortedmoves = (string[])movelist.Clone(); @@ -162,7 +162,7 @@ private void setEntry() private void formClosing(object sender, FormClosingEventArgs e) { setEntry(); - Util.packMini("move", "WD", "0", ".bin", "move"); + ARC.packMini("move", "WD", "0", ".bin", "move"); } private void B_RandAll_Click(object sender, EventArgs e) @@ -186,12 +186,12 @@ private void B_RandAll_Click(object sender, EventArgs e) } internal static int[] getTypes() { - Util.unpackMini(Directory.GetFiles("move")[0], "WD"); + ARC.unpackMini(Directory.GetFiles("move")[0], "WD"); string[] f2 = Directory.GetFiles("move"); int[] moveTypes = new int[f2.Length]; for (int i = 0; i < f2.Length; i++) moveTypes[i] = File.ReadAllBytes(f2[i])[0]; - Util.packMini("move", "WD", "0", ".bin", "move"); + ARC.packMini("move", "WD", "0", ".bin", "move"); return moveTypes; } } diff --git a/pk3DS/Subforms/OWSE.cs b/pk3DS/Subforms/OWSE.cs index cae6c28..52794a8 100644 --- a/pk3DS/Subforms/OWSE.cs +++ b/pk3DS/Subforms/OWSE.cs @@ -69,7 +69,7 @@ private void getEntry() if (entry < 0) return; RTB_F.Text = RTB_O.Text = richTextBox2.Text = RTB_T.Text = string.Empty; byte[] raw = File.ReadAllBytes(filepaths[entry]); - locationData = Util.unpackMini(raw, "ZO"); + locationData = ARC.unpackMini(raw, "ZO"); if (locationData == null) return; RichTextBox[] rtba = {RTB_MapInfo, RTB_OWSC, RTB_MapSC, RTB_Encounter, RTB_File5}; diff --git a/pk3DS/Tools/ToolsUI.Designer.cs b/pk3DS/Tools/ToolsUI.Designer.cs index 43b7517..4373152 100644 --- a/pk3DS/Tools/ToolsUI.Designer.cs +++ b/pk3DS/Tools/ToolsUI.Designer.cs @@ -33,32 +33,37 @@ private void InitializeComponent() this.PB_BCLIM = new System.Windows.Forms.Panel(); this.CHK_PNG = new System.Windows.Forms.CheckBox(); this.label1 = new System.Windows.Forms.Label(); + this.panel1 = new System.Windows.Forms.Panel(); + this.label4 = new System.Windows.Forms.Label(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); this.SuspendLayout(); // // PB_Unpack // + this.PB_Unpack.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.PB_Unpack.Location = new System.Drawing.Point(12, 25); this.PB_Unpack.Name = "PB_Unpack"; - this.PB_Unpack.Size = new System.Drawing.Size(150, 67); + this.PB_Unpack.Size = new System.Drawing.Size(300, 67); this.PB_Unpack.TabIndex = 0; // // L_DARCMini // this.L_DARCMini.AutoSize = true; - this.L_DARCMini.Location = new System.Drawing.Point(12, 9); + this.L_DARCMini.Location = new System.Drawing.Point(9, 9); this.L_DARCMini.Name = "L_DARCMini"; - this.L_DARCMini.Size = new System.Drawing.Size(109, 13); + this.L_DARCMini.Size = new System.Drawing.Size(145, 13); this.L_DARCMini.TabIndex = 1; - this.L_DARCMini.Text = "DARC && Mini Unpack"; + this.L_DARCMini.Text = "GARC, DARC && Mini Unpack"; // // PB_BCLIM // this.PB_BCLIM.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.PB_BCLIM.Location = new System.Drawing.Point(15, 283); + this.PB_BCLIM.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.PB_BCLIM.Location = new System.Drawing.Point(12, 226); this.PB_BCLIM.Name = "PB_BCLIM"; - this.PB_BCLIM.Size = new System.Drawing.Size(300, 67); + this.PB_BCLIM.Size = new System.Drawing.Size(300, 124); this.PB_BCLIM.TabIndex = 1; this.PB_BCLIM.Paint += new System.Windows.Forms.PaintEventHandler(this.PB_BCLIM_Paint); // @@ -66,7 +71,7 @@ private void InitializeComponent() // this.CHK_PNG.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.CHK_PNG.AutoSize = true; - this.CHK_PNG.Location = new System.Drawing.Point(218, 266); + this.CHK_PNG.Location = new System.Drawing.Point(103, 209); this.CHK_PNG.Name = "CHK_PNG"; this.CHK_PNG.Size = new System.Drawing.Size(97, 17); this.CHK_PNG.TabIndex = 2; @@ -76,17 +81,45 @@ private void InitializeComponent() // label1 // this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(12, 267); + this.label1.Location = new System.Drawing.Point(9, 210); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(88, 13); this.label1.TabIndex = 3; this.label1.Text = "BCLIM Converter"; // + // panel1 + // + this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.panel1.Location = new System.Drawing.Point(168, 124); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(144, 67); + this.panel1.TabIndex = 5; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(9, 108); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(77, 13); + this.label4.TabIndex = 7; + this.label4.Text = "Folder Repack"; + // + // comboBox1 + // + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Location = new System.Drawing.Point(12, 124); + this.comboBox1.Name = "comboBox1"; + this.comboBox1.Size = new System.Drawing.Size(150, 21); + this.comboBox1.TabIndex = 8; + // // ToolsUI // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(324, 362); + this.Controls.Add(this.comboBox1); + this.Controls.Add(this.label4); + this.Controls.Add(this.panel1); this.Controls.Add(this.label1); this.Controls.Add(this.CHK_PNG); this.Controls.Add(this.PB_BCLIM); @@ -109,6 +142,9 @@ private void InitializeComponent() private System.Windows.Forms.Panel PB_BCLIM; private System.Windows.Forms.CheckBox CHK_PNG; private System.Windows.Forms.Label label1; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.ComboBox comboBox1; } } \ No newline at end of file diff --git a/pk3DS/Tools/ToolsUI.cs b/pk3DS/Tools/ToolsUI.cs index 1b0dbd1..8c96e2d 100644 --- a/pk3DS/Tools/ToolsUI.cs +++ b/pk3DS/Tools/ToolsUI.cs @@ -22,9 +22,25 @@ private void tabMain_DragDrop(object sender, DragEventArgs e) { string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); string path = files[0]; // open first D&D - openQuick(path); + + if (sender == PB_Unpack) + openARC(path); + else if (sender == PB_BCLIM) + openIMG(path); + else if (sender == panel1) + saveARC(path); } - private void openQuick(string path, bool recursing = false) + + private void saveARC(string path) + { + int type = comboBox1.SelectedIndex; + Util.Alert("Not implemented." + Environment.NewLine + path); + } + private void openIMG(string path) + { + Util.Alert("Not implemented." + Environment.NewLine + path); + } + private void openARC(string path, bool recursing = false) { try { @@ -52,13 +68,13 @@ private void openQuick(string path, bool recursing = false) if (fi.Length == length) { // Fetch Mini File Contents - Util.unpackMini(path, fx, newFolder); + ARC.unpackMini(path, fx, newFolder); // Recurse throught the extracted contents if they extract successfully if (Directory.Exists(newFolder)) foreach (string file in Directory.GetFiles(newFolder)) - openQuick(file, true); + openARC(file, true); } - else if (false) + else if (false) // DARC { } diff --git a/pk3DS/Util.cs b/pk3DS/Util.cs index ffe5ffe..1aee7a5 100644 --- a/pk3DS/Util.cs +++ b/pk3DS/Util.cs @@ -596,184 +596,7 @@ internal static int IndexOfBytes(byte[] array, byte[] pattern, int startIndex, i } return -1; } - - // Mini Packing Util - internal static void packMini(string path, string ident, string fileName, string outExt = null, string outFolder = null) - { - if (outFolder == null) outFolder = path; - if (outExt == null) outExt = ".bin"; - // Create new Binary with the relevant header bytes - byte[] data = new byte[4]; - data[0] = (byte)ident[0]; - data[1] = (byte)ident[1]; - string[] files = Directory.GetFiles(path); - Array.Copy(BitConverter.GetBytes((ushort)files.Length), 0, data, 2, 2); - - int count = files.Length; - int dataOffset = 4 + 4 + count * 4; - - // Start the data filling. - using (MemoryStream dataout = new MemoryStream()) - using (MemoryStream offsetMap = new MemoryStream()) - using (BinaryWriter bd = new BinaryWriter(dataout)) - using (BinaryWriter bo = new BinaryWriter(offsetMap)) - { - // For each file... - for (int i = 0; i < count; i++) - { - // Write File Offset - uint fileOffset = (uint)(dataout.Position + dataOffset); - bo.Write(fileOffset); - - // Write File to Stream - bd.Write(File.ReadAllBytes(files[i])); - - // Pad the Data MemoryStream with Zeroes until len%4=0; - while (dataout.Length % 4 != 0) - bd.Write((byte)0); - // File Offset will be updated as the offset is based off of the Data length. - - // Delete the File - File.Delete(files[i]); - } - // Cap the File - bo.Write((uint)(dataout.Position + dataOffset)); - - using (var newPack = File.Create(Path.Combine(outFolder, fileName + outExt))) - using (var header = new MemoryStream(data)) - { - header.WriteTo(newPack); - offsetMap.WriteTo(newPack); - dataout.WriteTo(newPack); - } - } - } - internal static byte[] packMini(byte[][] fileData, string ident) - { - // Create new Binary with the relevant header bytes - byte[] data = new byte[4]; - data[0] = (byte)ident[0]; - data[1] = (byte)ident[1]; - Array.Copy(BitConverter.GetBytes((ushort)fileData.Length), 0, data, 2, 2); - - int count = fileData.Length; - int dataOffset = 4 + 4 + count * 4; - - // Start the data filling. - using (MemoryStream dataout = new MemoryStream()) - using (MemoryStream offsetMap = new MemoryStream()) - using (BinaryWriter bd = new BinaryWriter(dataout)) - using (BinaryWriter bo = new BinaryWriter(offsetMap)) - { - // For each file... - for (int i = 0; i < count; i++) - { - // Write File Offset - uint fileOffset = (uint)(dataout.Position + dataOffset); - bo.Write(fileOffset); - - // Write File to Stream - bd.Write(fileData[i]); - - // Pad the Data MemoryStream with Zeroes until len%4=0; - while (dataout.Length % 4 != 0) - bd.Write((byte)0); - // File Offset will be updated as the offset is based off of the Data length. - } - // Cap the File - bo.Write((uint)(dataout.Position + dataOffset)); - - using (var newPack = new MemoryStream()) - using (var header = new MemoryStream(data)) - { - header.WriteTo(newPack); - offsetMap.WriteTo(newPack); - dataout.WriteTo(newPack); - return newPack.ToArray(); - } - } - } - internal static void unpackMini(string path, string ident, string outFolder = null) - { - if (outFolder == null) outFolder = Path.GetDirectoryName(path); - - using (var s = new FileStream(path, FileMode.Open)) - using (var br = new BinaryReader(s)) - { - string fx = new string(br.ReadChars(2)); - - if (fx != ident) return; - - ushort count = br.ReadUInt16(); - string namePad = "D" + Math.Ceiling(Math.Log10(count)); - uint[] offsets = new uint[count + 1]; - for (int i = 0; i < count; i++) - offsets[i] = br.ReadUInt32(); - - uint length = br.ReadUInt32(); - offsets[offsets.Length - 1] = length; - - for (int i = 0; i < count; i++) - { - br.BaseStream.Seek(offsets[i], SeekOrigin.Begin); - using (MemoryStream dataout = new MemoryStream()) - { - byte[] data = new byte[0]; - s.CopyTo(dataout, (int)offsets[i]); - int len = (int)offsets[i + 1] - (int)offsets[i]; - - if (len != 0) - { - data = dataout.ToArray(); - Array.Resize(ref data, len); - } - string newFile = Path.Combine(outFolder, i.ToString(namePad) + ".bin"); - File.WriteAllBytes(newFile, data); - } - } - } - File.Delete(path); // File is unpacked. - } - internal static byte[][] unpackMini(byte[] fileData, string ident) - { - using (var s = new MemoryStream(fileData)) - using (var br = new BinaryReader(s)) - { - string fx = new string(br.ReadChars(2)); - - if (fx != ident) return null; - - ushort count = br.ReadUInt16(); - byte[][] returnData = new byte[count][]; - - uint[] offsets = new uint[count + 1]; - for (int i = 0; i < count; i++) - offsets[i] = br.ReadUInt32(); - - uint length = br.ReadUInt32(); - offsets[offsets.Length - 1] = length; - - for (int i = 0; i < count; i++) - { - br.BaseStream.Seek(offsets[i], SeekOrigin.Begin); - using (MemoryStream dataout = new MemoryStream()) - { - byte[] data = new byte[0]; - s.CopyTo(dataout, (int)offsets[i]); - int len = (int)offsets[i + 1] - (int)offsets[i]; - - if (len != 0) - { - data = dataout.ToArray(); - Array.Resize(ref data, len); - } - returnData[i] = data; - } - } - return returnData; - } - } - + // Misc internal static string getHexString(byte[] data) { diff --git a/pk3DS/pk3DS.csproj b/pk3DS/pk3DS.csproj index 5c7e1d2..f54c251 100644 --- a/pk3DS/pk3DS.csproj +++ b/pk3DS/pk3DS.csproj @@ -50,8 +50,10 @@ + + @@ -172,6 +174,12 @@ XYWE.cs + + Form + + + ToolsUI.cs + Form @@ -249,6 +257,9 @@ XYWE.cs + + ToolsUI.cs + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/pk3DS/png2bclim.cs b/pk3DS/png2bclim.cs new file mode 100644 index 0000000..3506828 --- /dev/null +++ b/pk3DS/png2bclim.cs @@ -0,0 +1,740 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +namespace pk3DS +{ + class png2bclim + { + internal static void openFile(string path, bool autosave = false, bool crop = true, char format = 'X') + { + // Handle file + if (!File.Exists(path)) throw new Exception("Can only accept files, not folders"); + string ext = Path.GetExtension(path); + if (ext == ".png") + makeBCLIM(path, format); + else if (ext == ".bin" || ext == ".bclim") + makeBMP(path, autosave, crop); + } + + internal static Image makeBCLIM(string path, char fc) + { + byte[] byteArray = File.ReadAllBytes(path); + using (Stream BitmapStream = new MemoryStream(byteArray)) // Open the file, even if it is in use. + { + Image img = Image.FromStream(BitmapStream); + Bitmap mBitmap = new Bitmap(img); + // Pixel format acquired. Now to determine how we want to save the data. + MemoryStream ms = new MemoryStream(); + int bclimformat = 7; // Init to default (for X) + + if (fc == 'X') + write16BitColorPalette(mBitmap, ref ms); + else + { + bclimformat = Convert.ToInt16(fc.ToString(), 16); + try + { + writeGeneric(bclimformat, mBitmap, ref ms); + } + catch (Exception e) + { + System.Media.SystemSounds.Beep.Play(); + System.Diagnostics.Debug.WriteLine(e.ToString()); + } + } + + long datalength = ms.Length; + // Write the CLIM + imag data. + using (BinaryWriter bw = new BinaryWriter(ms)) + { + bw.Write((uint)0x4D494C43); // CLIM + bw.Write((ushort)0xFEFF); // BOM + bw.Write((uint)0x14); + bw.Write((ushort)0x0202); // 2 2 + bw.Write((uint)(datalength+0x28)); + bw.Write((uint)1); + bw.Write((uint)0x67616D69); + bw.Write((uint)0x10); + bw.Write((ushort)mBitmap.Width); + bw.Write((ushort)mBitmap.Height); + bw.Write((uint)bclimformat); + bw.Write((uint)datalength); + } + string fp = Path.GetFileNameWithoutExtension(path); + fp = "new_" + fp.Substring(fp.IndexOf('_') + 1); + string pp = Path.GetDirectoryName(path); + string newPath = Path.Combine(pp, fp + ".bclim"); + File.WriteAllBytes(newPath, ms.ToArray()); + + return makeBMP(newPath); + } + } + internal static Image makeBMP(string path, bool autosave = false, bool crop = true) + { + CLIM bclim = BCLIM.analyze(path); + if (bclim.Magic != 0x4D494C43) + { + System.Media.SystemSounds.Beep.Play(); + return null; + } + + // Interpret data. + int f = bclim.FileFormat; + if (f > 13) + { + System.Media.SystemSounds.Exclamation.Play(); + return null; + } + + Bitmap img = new Bitmap(1,1); + if (f == 7 && BitConverter.ToUInt16(bclim.Data, 0) == 2) + // PKM XY Format 7 (Color Palette) + img = getIMG_XY7(bclim); + else if (f == 10 || f == 11) { + Util.Alert("ETC Images not supported."); return null; + } + else + img = getIMG(bclim); + + Rectangle cropRect = new Rectangle(0, 0, bclim.Width, bclim.Height); + Bitmap CropBMP = new Bitmap(cropRect.Width, cropRect.Height); + using (Graphics g = Graphics.FromImage(CropBMP)) + { + g.DrawImage(img, + new Rectangle(0, 0, CropBMP.Width, CropBMP.Height), + cropRect, + GraphicsUnit.Pixel); + } + if (!autosave) return !crop ? img : CropBMP; + + using (MemoryStream ms = new MemoryStream()) + { + //error will throw from here + CropBMP.Save(ms, ImageFormat.Png); + byte[] data = ms.ToArray(); + File.WriteAllBytes(bclim.FilePath + "\\" + bclim.FileName + ".png", data); + } + return !crop ? img : CropBMP; + } + // Bitmap Data Writing + internal static Bitmap getIMG(CLIM bclim) + { + // New Image + Bitmap img = new Bitmap(nlpo2(gcm(bclim.Width,8)), nlpo2(gcm(bclim.Height,8))); + int f = bclim.FileFormat; + int area = img.Width * img.Height; + if (f == 9 && area > bclim.Data.Length/4) + { + img = new Bitmap(gcm(bclim.Width, 8), gcm(bclim.Height, 8)); + area = img.Width * img.Height; + } + // Coordinates + // Colors + // Tiles Per Width + int p = gcm(img.Width, 8) / 8; + if (p == 0) p = 1; + + // Build Image + using (Stream BitmapStream = new MemoryStream(bclim.Data)) + using (BinaryReader br = new BinaryReader(BitmapStream)) + for (uint i = 0; i < area; i++) // for every pixel + { + uint x; + uint y; + d2xy(i % 64, out x, out y); + uint tile = i / 64; + + // Shift Tile Coordinate into Tilemap + x += (uint)(tile % p) * 8; + y += (uint)(tile / p) * 8; + + // Get Color + Color c; + switch (f) + { + case 0x0: // L8 // 8bit/1 byte + case 0x1: // A8 + case 0x2: // LA4 + c = DecodeColor(br.ReadByte(), f); + break; + case 0x3: // LA8 // 16bit/2 byte + case 0x4: // HILO8 + case 0x5: // RGB565 + case 0x8: // RGBA4444 + case 0x7: // RGBA5551 + c = DecodeColor(br.ReadUInt16(), f); + break; + case 0x6: // RGB8: // 24bit + byte[] data = br.ReadBytes(3); Array.Resize(ref data, 4); + c = DecodeColor(BitConverter.ToUInt32(data, 0), f); + break; + case 0x9: // RGBA8888 + c = DecodeColor(br.ReadUInt32(), f); + break; + case 0xC: // L4 + case 0xD: // A4 // 4bit - Do 2 pixels at a time. + uint val = br.ReadByte(); + img.SetPixel((int)x, (int)y, DecodeColor(val & 0xF, f)); // lowest bits for the low pixel + i++; x++; + c = DecodeColor(val >> 4, f); // highest bits for the high pixel + break; + default: throw new Exception("Invalid FileFormat."); + } + img.SetPixel((int)x, (int)y, c); + } + return img; + } + internal static Bitmap getIMG_XY7(CLIM bclim) + { + Bitmap img = new Bitmap(bclim.BaseSize, bclim.BaseSize); + using (Stream BitmapStream = new MemoryStream(bclim.Data)) + using (BinaryReader br = new BinaryReader(BitmapStream)) + { + // Fetch Color stuff. + if (br.ReadUInt16() != 2) return null; + ushort colors = br.ReadUInt16(); + Color[] ca = new Color[colors]; + for (int i = 0; i < colors; i++) + ca[i] = DecodeColor(br.ReadUInt16(), 7); + + // Coordinates + // Colors + // Tiles Per Width + int p = gcm(img.Width, 8) / 8; + if (p == 0) p = 1; + + for (uint i = 0; i < bclim.BaseSize * bclim.BaseSize; i++) // for every pixel + { + uint x; + uint y; + d2xy(i % 64, out x, out y); + uint tile = i / 64; + + // Shift Tile Coordinate into Tilemap + x += (uint)(tile % p) * 8; + y += (uint)(tile / p) * 8; + + byte val = br.ReadByte(); + if (colors <= 0x10) // Handle 2 pixels at a time + { + img.SetPixel((int)x, (int)y, ca[val >> 4]); + x++; i++; val &= 0xF; + img.SetPixel((int)x, (int)y, ca[val]); + } + else //1bpp instead of .5, handle 2 pixels at a time the same way for no reason + { + img.SetPixel((int)x, (int)y, ca[val]); + x++; i++; val = br.ReadByte(); + img.SetPixel((int)x, (int)y, ca[val]); + } + } + } + return img; + } + + // BCLIM Data Writing + internal static int write16BitColorPalette(Bitmap img, ref MemoryStream ms) + { + using (Stream pixelcolors = new MemoryStream()) + using (BinaryWriter bz = new BinaryWriter(pixelcolors)) + { + // Set up our basis. + bool under16colors = false; + int colors = getColorCount(img); + Color[] pcs = new Color[colors]; + if (colors < 16) under16colors = true; + uint div = 1; + if (under16colors) + div = 2; + + if (colors > 70) throw new Exception("Too many colors"); + + // Set up a new reverse image to build into. + int w = gcm(img.Width, 8); + int h = gcm(img.Height, 8); + w = Math.Max(nlpo2(w), nlpo2(h)); + h = w; + byte[] pixelarray = new Byte[w * h]; + + const int colorformat = 2; + int ctr = 1; + + pcs[0] = Color.FromArgb(0, 0xFF, 0xFF, 0xFF); + + int p = gcm(w, 8) / 8; + if (p == 0) p = 1; + int d = 0; + for (uint i = 0; i < pixelarray.Length; i++) + { + d = (int)(i / div); + // Get Tile Coordinate + uint x; + uint y; + d2xy(i % 64, out x, out y); + + // Get Shift Tile + uint tile = i / 64; + + // Shift Tile Coordinate into Tilemap + x += (uint)(tile % p) * 8; + y += (uint)(tile / p) * 8; + if (x >= img.Width || y >= img.Height) // Don't try to access any pixel data outside of our bounds. + { i++; continue; } // Goto next tile. + + // Get Color of Pixel + Color c = img.GetPixel((int)x, (int)y); + + // Color Table Building Logic + int index = Array.IndexOf(pcs, c); + if (c.A == 0) index = 0; + if (index < 0) // If new color + { pcs[ctr] = c; index = ctr; ctr++; } // Add it to color list + + // Add pixel to pixeldata + if (under16colors) index = index << 4; + pixelarray[i / div] = (byte)index; + if (!under16colors) continue; + + c = img.GetPixel((int)x + 1, (int)y); + index = Array.IndexOf(pcs, c); + if (c.A == 0) index = 0; + if (index < 0) // If new color + { pcs[ctr] = c; index = ctr; ctr++; } + pixelarray[i / div] |= (byte)(index); + i++; + } + + // Write Intro + bz.Write((ushort)colorformat); bz.Write((ushort)ctr); + // Write Colors + for (int i = 0; i < ctr; i++) + bz.Write(GetRGBA5551(pcs[i])); // Write byte array. + // Write Pixel Data + for (uint i = 0; i < d; i++) + bz.Write(pixelarray[i]); + // Write Padding + while (pixelcolors.Length < nlpo2((int)pixelcolors.Length)) + bz.Write((byte)0); + // Copy to main CLIM. + pixelcolors.Position = 0; pixelcolors.CopyTo(ms); + } + return 7; + } + internal static void writeGeneric(int format, Bitmap img, ref MemoryStream ms) + { + int w = img.Width; + int h = img.Height; + + // square format. Yay. + w = h = Math.Max(nlpo2(w),nlpo2(h)); + if (format == 0 && Math.Min(img.Width, img.Height) > 64) + { + w = nlpo2(img.Width); + h = nlpo2(img.Height); + } + using (MemoryStream mz = new MemoryStream()) + using (BinaryWriter bz = new BinaryWriter(mz)) + { + int p = gcm(w, 8) / 8; + if (p == 0) p = 1; + for (uint i = 0; i < w * h; i++) + { + uint x; + uint y; + d2xy(i % 64, out x, out y); + + // Get Shift Tile + uint tile = i / 64; + + // Shift Tile Coordinate into Tilemap + x += (uint)(tile % p) * 8; + y += (uint)(tile / p) * 8; + + // Don't write data + Color c; + if (x >= img.Width || y >= img.Height) + { c = Color.FromArgb(0, 0, 0, 0); } + else + { c = img.GetPixel((int)x, (int)y); if (c.A == 0) c = Color.FromArgb(0, 86, 86, 86); } + + switch (format) + { + case 0: bz.Write(GetL8(c)); break; // L8 + case 1: bz.Write(GetA8(c)); break; // A8 + case 2: bz.Write(GetLA4(c)); break; // LA4(4) + case 3: bz.Write(GetLA8(c)); break; // LA8(8) + case 4: bz.Write(GetHILO8(c)); break; // HILO8 + case 5: bz.Write(GetRGB565(c)); break; // RGB565 + case 6: + { + bz.Write(c.B); + bz.Write(c.G); + bz.Write(c.R); break; + } + case 7: bz.Write(GetRGBA5551(c)); break; // RGBA5551 + case 8: bz.Write(GetRGBA4444(c)); break; // RGBA4444 + case 9: bz.Write(GetRGBA8888(c)); break; // RGBA8 + case 10: throw new Exception("ETC1 not supported."); + case 11: throw new Exception("ETC1A4 not supported."); + case 12: + { + byte val = (byte)(GetL8(c) / 0x11); // First Pix // L4 + { c = img.GetPixel((int)x, (int)y); if (c.A == 0) c = Color.FromArgb(0, 0, 0, 0); } + val |= (byte)((GetL8(c) / 0x11) << 4); i++; + bz.Write(val); break; + } + case 13: + { + byte val = (byte)(GetA8(c) / 0x11); // First Pix // L4 + { c = img.GetPixel((int)x, (int)y); } + val |= (byte)((GetA8(c) / 0x11) << 4); i++; + bz.Write(val); break; + } + } + } + while (mz.Length < nlpo2((int)mz.Length)) // pad + bz.Write((byte)0); + mz.Position = 0; + mz.CopyTo(ms); + } + } + internal static int getColorCount(Bitmap img) + { + Color[] colors = new Color[img.Width * img.Height]; + int colorct = 1; + + for (int i = 0; i < colors.Length; i++) + { + Color c = img.GetPixel(i % img.Width, i / img.Width); + int index = Array.IndexOf(colors, c); + if (c.A == 0) index = 0; + if (index >= 0) continue; + + colors[colorct] = c; + colorct++; + } + return colorct; + } + + + internal static int[] Convert5To8 = { 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39, + 0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B, + 0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD, + 0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF }; + + private static Color DecodeColor(uint val, int format) + { + int alpha = 0xFF, red, green, blue; + switch (format) + { + case 0: // L8 + return Color.FromArgb(alpha, (byte)val, (byte)val, (byte)val); + case 1: // A8 + return Color.FromArgb((byte)val, alpha, alpha, alpha); + case 2: // LA4 + red = (byte)(val >> 4); + alpha = (byte)(val & 0x0F); + return Color.FromArgb(alpha, red, red, red); + case 3: // LA8 + red = (byte)((val >> 8 & 0xFF)); + alpha = (byte)(val & 0xFF); + return Color.FromArgb(alpha, red, red, red); + case 4: // HILO8 + red = (byte)(val >> 8); + green = (byte)(val & 0xFF); + return Color.FromArgb(alpha, red, green, 0xFF); + case 5: // RGB565 + red = Convert5To8[(val >> 11) & 0x1F]; + green = (byte)(((val >> 5) & 0x3F) * 4); + blue = Convert5To8[val & 0x1F]; + return Color.FromArgb(alpha, red, green, blue); + case 6: // RGB8 + red = (byte)((val >> 16) & 0xFF); + green = (byte)((val >> 8) & 0xFF); + blue = (byte)(val & 0xFF); + return Color.FromArgb(alpha, red, green, blue); + case 7: // RGBA5551 + red = Convert5To8[(val >> 11) & 0x1F]; + green = Convert5To8[(val >> 6) & 0x1F]; + blue = Convert5To8[(val >> 1) & 0x1F]; + alpha = (val & 0x0001) == 1 ? 0xFF : 0x00; + return Color.FromArgb(alpha, red, green, blue); + case 8: // RGBA4444 + alpha = (byte)(0x11 * (val & 0xf)); + red = (byte)(0x11 * ((val >> 12) & 0xf)); + green = (byte)(0x11 * ((val >> 8) & 0xf)); + blue = (byte)(0x11 * ((val >> 4) & 0xf)); + return Color.FromArgb(alpha, red, green, blue); + case 9: // RGBA8888 + red = (byte)((val >> 24) & 0xFF); + green = (byte)((val >> 16) & 0xFF); + blue = (byte)((val >> 8) & 0xFF); + alpha = (byte)(val & 0xFF); + return Color.FromArgb(alpha, red, green, blue); + // case 10: + // case 11: + case 12: // L4 + return Color.FromArgb(alpha, (byte)(val * 0x11), (byte)(val * 0x11), (byte)(val * 0x11)); + case 13: // A4 + return Color.FromArgb((byte)(val * 0x11), alpha, alpha, alpha); + default: + return Color.White; + } + } + + // Color Conversion + internal static byte GetL8(Color c) + { + byte red = c.R; + byte green = c.G; + byte blue = c.B; + // Luma (Y’) = 0.299 R’ + 0.587 G’ + 0.114 B’ from wikipedia + return (byte)(((0x4CB2 * red + 0x9691 * green + 0x1D3E * blue) >> 16) & 0xFF); + } // L8 + internal static byte GetA8(Color c) + { + return c.A; + } // A8 + internal static byte GetLA4(Color c) + { + return (byte)((c.A / 0x11) + (c.R / 0x11) << 4); + } // LA4 + internal static ushort GetLA8(Color c) + { + return (ushort)((c.A) + ((c.R) << 8)); + } // LA8 + internal static ushort GetHILO8(Color c) + { + return (ushort)((c.G) + ((c.R) << 8)); + } // HILO8 + internal static ushort GetRGB565(Color c) + { + int val = 0; + // val += c.A >> 8; // unused + val += convert8to5(c.B) >> 3; + val += (c.G >> 2) << 5; + val += convert8to5(c.R) << 10; + return (ushort)val; + } // RGB565 + // RGB8 + internal static ushort GetRGBA5551(Color c) + { + int val = 0; + val += (byte)(c.A > 0x80 ? 1 : 0); + val += convert8to5(c.R) << 11; + val += convert8to5(c.G) << 6; + val += convert8to5(c.B) << 1; + ushort v = (ushort)val; + + return v; + }// RGBA5551 + internal static ushort GetRGBA4444(Color c) + { + int val = 0; + val += (c.A / 0x11); + val += ((c.B / 0x11) << 4); + val += ((c.G / 0x11) << 8); + val += ((c.R / 0x11) << 12); + return (ushort)val; + }// RGBA4444 + internal static uint GetRGBA8888(Color c) // RGBA8888 + { + uint val = 0; + val += c.A; + val += (uint)(c.B << 8); + val += (uint)(c.G << 16); + val += (uint)(c.R << 24); + return val; + } + + // Unit Conversion + internal static byte convert8to5(int colorval) + { + + byte[] Convert8to5 = { 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39, + 0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B, + 0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD, + 0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF }; + byte i = 0; + while (colorval > Convert8to5[i]) i++; + return i; + } + internal static UInt32 DM2X(UInt32 code) + { + return C11(code >> 0); + } + internal static UInt32 DM2Y(UInt32 code) + { + return C11(code >> 1); + } + internal static UInt32 C11(UInt32 x) + { + x &= 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 + x = (x ^ (x >> 1)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10 + x = (x ^ (x >> 2)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 + x = (x ^ (x >> 4)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210 + x = (x ^ (x >> 8)) & 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210 + return x; + } + + /// + /// Greatest common multiple (to round up) + /// + /// Number to round-up. + /// Multiple to round-up to. + /// Rounded up number. + internal static int gcm(int n, int m) + { + return ((n + m - 1) / m) * m; + } + /// + /// Next Largest Power of 2 + /// + /// Input to round up to next 2^n + /// 2^n > x && x > 2^(n-1) + internal static int nlpo2(int x) + { + x--; // comment out to always take the next biggest power of two, even if x is already a power of two + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return (x+1); + } + + // Morton Translation + /// + /// Combines X/Y Coordinates to a decimal ordinate. + /// + /// + /// + /// + internal static uint xy2d(uint x, uint y) + { + x &= 0x0000ffff; + y &= 0x0000ffff; + x |= (x << 8); + y |= (y << 8); + x &= 0x00ff00ff; + y &= 0x00ff00ff; + x |= (x << 4); + y |= (y << 4); + x &= 0x0f0f0f0f; + y &= 0x0f0f0f0f; + x |= (x << 2); + y |= (y << 2); + x &= 0x33333333; + y &= 0x33333333; + x |= (x << 1); + y |= (y << 1); + x &= 0x55555555; + y &= 0x55555555; + return x | (y << 1); + } + /// + /// Decimal Ordinate In to X / Y Coordinate Out + /// + /// Loop integer which will be decoded to X/Y + /// Output X coordinate + /// Output Y coordinate + internal static void d2xy(uint d, out uint x, out uint y) + { + x = d; + y = (x >> 1); + x &= 0x55555555; + y &= 0x55555555; + x |= (x >> 1); + y |= (y >> 1); + x &= 0x33333333; + y &= 0x33333333; + x |= (x >> 2); + y |= (y >> 2); + x &= 0x0f0f0f0f; + y &= 0x0f0f0f0f; + x |= (x >> 4); + y |= (y >> 4); + x &= 0x00ff00ff; + y &= 0x00ff00ff; + x |= (x >> 8); + y |= (y >> 8); + x &= 0x0000ffff; + y &= 0x0000ffff; + } + + public class BCLIM + { + public static CLIM analyze(string path) + { + CLIM bclim = new CLIM + { + FileName = Path.GetFileNameWithoutExtension(path), + FilePath = Path.GetDirectoryName(path), + Extension = Path.GetExtension(path) + }; + byte[] byteArray = File.ReadAllBytes(path); + using (BinaryReader br = new BinaryReader(new MemoryStream(byteArray))) + { + br.BaseStream.Seek(br.BaseStream.Length - 0x28, SeekOrigin.Begin); + bclim.Magic = br.ReadUInt32(); + + bclim.BOM = br.ReadUInt16(); + bclim.CLIMLength = br.ReadUInt32(); + bclim.TileWidth = 2 << br.ReadByte(); + bclim.TileHeight = 2 << br.ReadByte(); + bclim.totalLength = br.ReadUInt32(); + bclim.Count = br.ReadUInt32(); + + bclim.imag = br.ReadChars(4); + bclim.imagLength = br.ReadUInt32(); + bclim.Width = br.ReadUInt16(); + bclim.Height = br.ReadUInt16(); + bclim.FileFormat = br.ReadInt32(); + bclim.dataLength = br.ReadUInt32(); + + bclim.BaseSize = Math.Max(nlpo2(bclim.Width), nlpo2(bclim.Height)); + + br.BaseStream.Seek(0, SeekOrigin.Begin); + bclim.Data = br.ReadBytes((int)bclim.dataLength); + + return bclim; + } + } + } + public struct CLIM + { + public UInt32 Magic; // CLIM = 0x4D494C43 + public UInt16 BOM; // 0xFFFE + public UInt32 CLIMLength; // HeaderLength - 14 + public int TileWidth; // 1<<[[n]] + public int TileHeight; // 1<<[[n]] + public UInt32 totalLength; // Total Length of file + public UInt32 Count; // "1" , guessing it's just Count. + + public char[] imag; // imag = 0x67616D69 + public UInt32 imagLength; // HeaderLength - 10 + public UInt16 Width; // Final Dimensions + public UInt16 Height; // Final Dimensions + public Int32 FileFormat; // ?? + public UInt32 dataLength; // Pixel Data Region Length + + public byte[] Data; + + public Int32 BaseSize; + + //// Contained Data + //public int ColorFormat; + //public int ColorCount; + //public Color[] Colors; + + //public int[] Pixels; + + public string FileName; + public string FilePath; + public string Extension; + + } + } +}