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.
This commit is contained in:
Kurt
2015-05-03 14:55:39 -07:00
parent ce8801fe37
commit 05c5bfe8f6
10 changed files with 1764 additions and 198 deletions

940
pk3DS/ARCUtil.cs Normal file
View File

@@ -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<SFATEntry>();
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<ShuffleFile>();
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<GARFile>();
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<DarcFile>();
ft.FileNames = new List<string>();
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<FARCFile>(),
FileNames = new List<string>()
};
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<uint>();
for (int i = 0; i < st.StringCount; i++)
{
st.offsets.Add(br.ReadUInt32());
}
string ret = "Dumped Offsets.";
st.strings = new List<string>();
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<FARCFile> Files;
public List<string> 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<SFATEntry> 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<ShuffleFile> 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<uint> offsets;
public List<String> 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<GARFile> 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<DarcFile> Files;
public List<string> 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<byte, ushort>(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;
}
}
}
}

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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};

View File

@@ -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;
}
}

View File

@@ -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
{
}

View File

@@ -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)
{

View File

@@ -50,8 +50,10 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ARCUtil.cs" />
<Compile Include="BLZ.cs" />
<Compile Include="ExeFS.cs" />
<Compile Include="png2bclim.cs" />
<Compile Include="Randomizer.cs" />
<Compile Include="RomFS.cs" />
<Compile Include="Subforms\RSWE.cs">
@@ -172,6 +174,12 @@
<Compile Include="Subforms\XYWE.designer.cs">
<DependentUpon>XYWE.cs</DependentUpon>
</Compile>
<Compile Include="Tools\ToolsUI.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Tools\ToolsUI.Designer.cs">
<DependentUpon>ToolsUI.cs</DependentUpon>
</Compile>
<Compile Include="Util.cs" />
<Compile Include="Subforms\xytext.cs">
<SubType>Form</SubType>
@@ -249,6 +257,9 @@
<EmbeddedResource Include="Subforms\XYWE.resx">
<DependentUpon>XYWE.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Tools\ToolsUI.resx">
<DependentUpon>ToolsUI.cs</DependentUpon>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>

740
pk3DS/png2bclim.cs Normal file
View File

@@ -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;
}
/// <summary>
/// Greatest common multiple (to round up)
/// </summary>
/// <param name="n">Number to round-up.</param>
/// <param name="m">Multiple to round-up to.</param>
/// <returns>Rounded up number.</returns>
internal static int gcm(int n, int m)
{
return ((n + m - 1) / m) * m;
}
/// <summary>
/// Next Largest Power of 2
/// </summary>
/// <param name="x">Input to round up to next 2^n</param>
/// <returns>2^n > x && x > 2^(n-1) </returns>
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
/// <summary>
/// Combines X/Y Coordinates to a decimal ordinate.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Decimal Ordinate In to X / Y Coordinate Out
/// </summary>
/// <param name="d">Loop integer which will be decoded to X/Y</param>
/// <param name="x">Output X coordinate</param>
/// <param name="y">Output Y coordinate</param>
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;
}
}
}