From cfd91b214a2a49a389cd42d1ebdadc9d3110bdc7 Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 16 Jun 2015 00:37:11 -0700 Subject: [PATCH] Refactoring Also included the new GARC handler which can now process all GARCs. --- pk3DS/3DS/BCLIM.cs | 754 ++++++++++++++ pk3DS/3DS/BLZ.cs | 2 +- pk3DS/3DS/ETC1.cs | 6 + pk3DS/3DS/GARC.cs | 503 +++++++++ pk3DS/{GARCTool.cs => 3DS/LZSS.cs} | 408 +------- pk3DS/3DS/mini.cs | 228 +++++ pk3DS/ARCUtil.cs | 213 +--- pk3DS/Main.cs | 16 +- pk3DS/Subforms/Moves.cs | 8 +- pk3DS/Subforms/OWSE.cs | 2 +- pk3DS/Tools/ToolsUI.Designer.cs | 1 - pk3DS/Tools/ToolsUI.cs | 48 +- pk3DS/pk3DS.csproj | 7 +- pk3DS/png2bclim.cs | 1537 ---------------------------- 14 files changed, 1542 insertions(+), 2191 deletions(-) create mode 100644 pk3DS/3DS/BCLIM.cs create mode 100644 pk3DS/3DS/ETC1.cs create mode 100644 pk3DS/3DS/GARC.cs rename pk3DS/{GARCTool.cs => 3DS/LZSS.cs} (66%) create mode 100644 pk3DS/3DS/mini.cs delete mode 100644 pk3DS/png2bclim.cs diff --git a/pk3DS/3DS/BCLIM.cs b/pk3DS/3DS/BCLIM.cs new file mode 100644 index 0000000..d5b9257 --- /dev/null +++ b/pk3DS/3DS/BCLIM.cs @@ -0,0 +1,754 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +namespace CTR +{ + class BCLIM + { + 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 = 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; + 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) + img = getIMG_ETC(bclim); + 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; + } + internal static Bitmap getIMG_ETC(CLIM bclim) + { + //int format = bclim.FileFormat; + //if (format != 10 && format != 11) + // return null; + + //byte[] data = ETC1_Decompress(bclim.Data, bclim.Width, bclim.Height, format + 2); + //Bitmap img = new Bitmap(bclim.Width, bclim.Height); + + //for (int i = 0; i < bclim.Width * bclim.Height; i++) // for every pixel + //{ + // Color c = DecodeColor(BitConverter.ToUInt32(data, i * 4), 9); + // int x = i % bclim.Width; + // int y = i / bclim.Width; + // img.SetPixel(x, y, c); + //} + //return img; + return null; + } + + // 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 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; + } + } +} \ No newline at end of file diff --git a/pk3DS/3DS/BLZ.cs b/pk3DS/3DS/BLZ.cs index de8c968..f050cbb 100644 --- a/pk3DS/3DS/BLZ.cs +++ b/pk3DS/3DS/BLZ.cs @@ -24,7 +24,7 @@ using System.IO; using System.Windows.Forms; -namespace BLZ +namespace CTR { public class BLZCoder { diff --git a/pk3DS/3DS/ETC1.cs b/pk3DS/3DS/ETC1.cs new file mode 100644 index 0000000..f269887 --- /dev/null +++ b/pk3DS/3DS/ETC1.cs @@ -0,0 +1,6 @@ +namespace CTR +{ + class ETC1 + { + } +} diff --git a/pk3DS/3DS/GARC.cs b/pk3DS/3DS/GARC.cs new file mode 100644 index 0000000..ed49917 --- /dev/null +++ b/pk3DS/3DS/GARC.cs @@ -0,0 +1,503 @@ +using System; +using System.IO; +using System.Linq; +using System.Media; +using System.Windows.Forms; +using pk3DS; + +namespace CTR +{ + #region GARC Class & Struct + public class GARC + { + internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar pBar1 = null, Label label = null, bool supress = false) + { + // Check to see if our input folder exists. + if (!new DirectoryInfo(folderPath).Exists) { Util.Error("Folder does not exist."); return false; } + + // Okay some basic proofing is done. Proceed. + int filectr = 0; + // Get the paths of the files to pack up. + string[] files = Directory.GetFiles(folderPath); + string[] folders = Directory.GetDirectories(folderPath, "*.*", SearchOption.TopDirectoryOnly); + + string[] packOrder = new string[files.Length + folders.Length]; + #region Reassemble a list of filenames. + try + { + foreach (string f in files) + { + string fn = Path.GetFileNameWithoutExtension(f); + int compressed = fn.IndexOf("dec_", StringComparison.Ordinal); + int fileNumber = (compressed < 0) + ? Int32.Parse(fn) + : Int32.Parse(fn.Substring(compressed + 4)); + + packOrder[fileNumber] = f; + filectr++; + } + foreach (string f in folders) + { + packOrder[Int32.Parse(new DirectoryInfo(f).Name)] = f; + filectr += Directory.GetFiles(f).Length; + } + } + catch (Exception e) { Util.Error("Invalid packing filenames", e.ToString()); return false; } + #endregion + + #region Initialize Progress Update Display + if (pBar1 == null) pBar1 = new ProgressBar(); + if (pBar1.InvokeRequired) + pBar1.Invoke((MethodInvoker)delegate { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = filectr; }); + else { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = filectr; } + if (label == null) label = new Label(); + if (label.InvokeRequired) + label.Invoke((MethodInvoker)delegate { label.Visible = true; }); + #endregion + + // Set Up the GARC template. + GARCFile garc = new GARCFile + { + fato = + { + // Magic = new[] { 'O', 'T', 'A', 'F' }, + Entries = new FATO_Entry[packOrder.Length], + EntryCount = (ushort)packOrder.Length, + HeaderSize = 0xC + packOrder.Length * 4, + Padding = 0xFFFF + }, + fatb = + { + // Magic = new[] { 'B', 'T', 'A', 'F' }, + Entries = new FATB_Entry[packOrder.Length], + FileCount = filectr + } + }; + + #region Start Reassembling the FAT* tables. + { + int op = 0; + int od = 0; + int v = 0; + int index = 0; + for (int i = 0; i < garc.fatb.Entries.Length; i++) + { + garc.fato.Entries[i].Offset = op; // FATO offset + garc.fatb.Entries[i].SubEntries = new FATB_SubEntry[32]; + op += 4; // Vector + if (!Directory.Exists(packOrder[i])) // is not folder + { + garc.fatb.Entries[i].IsFolder = false; + garc.fatb.Entries[i].SubEntries[0].Exists = true; + + string fn = Path.GetFileNameWithoutExtension(packOrder[i]); + int compressed = fn.IndexOf("dec_", StringComparison.Ordinal); + int fileNumber = (compressed < 0) + ? Int32.Parse(fn) + : Int32.Parse(fn.Substring(compressed + 4)); + + if (compressed >= 0) + { + string old = packOrder[i]; + LZSS.Compress(packOrder[i], packOrder[i] = Path.Combine(Path.GetDirectoryName(packOrder[i]), fileNumber.ToString())); + File.Delete(old); + } + + // Assemble Vector + v = 1; + + // Assemble Entry + FileInfo fi = new FileInfo(packOrder[i]); + int actualLength = (int)((fi.Length % 4 == 0) ? fi.Length : fi.Length + 4 - (fi.Length % 4)); + garc.fatb.Entries[i].SubEntries[0].Start = od; + garc.fatb.Entries[i].SubEntries[0].End = actualLength + garc.fatb.Entries[i].SubEntries[0].Start; + garc.fatb.Entries[i].SubEntries[0].Length = (int)fi.Length; + od += actualLength; + + op += 12; + #region Step + if (pBar1.InvokeRequired) + pBar1.Invoke((MethodInvoker)(() => pBar1.PerformStep())); + else { pBar1.PerformStep(); } + string update = String.Format("{0:P2} - {1}/{2} - {3}", ((float)index) / ((float)filectr), index, filectr, packOrder[i]); + index++; + if (label.InvokeRequired) + label.Invoke((MethodInvoker)delegate { label.Text = update; }); + else { label.Text = update; } + #endregion + } + else + { + garc.fatb.Entries[i].IsFolder = true; + string[] subFiles = Directory.GetFiles(packOrder[i]); + foreach (string f in subFiles) + { + string s = f; + string fn = Path.GetFileNameWithoutExtension(f); + int compressed = fn.IndexOf("dec_", StringComparison.Ordinal); + int fileNumber = (compressed < 0) + ? Int32.Parse(fn) + : Int32.Parse(fn.Substring(compressed + 4)); + garc.fatb.Entries[i].SubEntries[fileNumber].Exists = true; + + if (compressed >= 0) + { + LZSS.Compress(f, s = Path.Combine(Path.GetDirectoryName(f), fileNumber.ToString())); + File.Delete(f); + } + + // Assemble Vector + v |= 1 << fileNumber; + + // Assemble Entry + FileInfo fi = new FileInfo(s); + int actualLength = (int)((fi.Length % 4 == 0) ? fi.Length : fi.Length + 4 - (fi.Length % 4)); + garc.fatb.Entries[i].SubEntries[fileNumber].Start = od; + garc.fatb.Entries[i].SubEntries[fileNumber].End = actualLength + garc.fatb.Entries[i].SubEntries[fileNumber].Start; + garc.fatb.Entries[i].SubEntries[fileNumber].Length = (int)fi.Length; + od += actualLength; + + op += 12; + #region Step + if (pBar1.InvokeRequired) + pBar1.Invoke((MethodInvoker)(() => pBar1.PerformStep())); + else { pBar1.PerformStep(); } + string update = String.Format("{0:P2} - {1}/{2} - {3}", ((float)index) / ((float)filectr), index, filectr, f); + index++; + if (label.InvokeRequired) + label.Invoke((MethodInvoker)delegate { label.Text = update; }); + else { label.Text = update; } + #endregion + } + } + garc.fatb.Entries[i].Vector = (uint)v; + } + garc.fatb.HeaderSize = (0xC + op); + } + #endregion + + // Delete the old garc if it exists, then begin writing our new one + try { File.Delete(garcPath); } + catch { } + + // Set up the Header Info + using (MemoryStream newGARC = new MemoryStream()) + using (MemoryStream GARCdata = new MemoryStream()) + using (BinaryWriter gw = new BinaryWriter(newGARC)) + { + #region Write GARC Headers + // Write GARC + gw.Write((uint)0x47415243); // GARC + gw.Write((uint)0x0000001C); // Header Length + gw.Write((ushort)0xFEFF); // Endianness BOM + gw.Write((ushort)0x0400); // Const (4) + gw.Write((uint)0x00000004); // Section Count (4) + gw.Write((uint)0x00000000); // Data Offset (temp) + gw.Write((uint)0x00000000); // File Length (temp) + gw.Write((uint)0x00000000); // Largest File Size (temp) + + // Write FATO + gw.Write((uint)0x4641544F); // FATO + gw.Write(garc.fato.HeaderSize); // Header Size + gw.Write(garc.fato.EntryCount); // Entry Count + gw.Write(garc.fato.Padding); // Padding + for (int i = 0; i < garc.fato.Entries.Length; i++) + gw.Write((uint)garc.fato.Entries[i].Offset); + + // Write FATB + gw.Write((uint)0x46415442); // FATB + gw.Write(garc.fatb.HeaderSize); // Header Size + gw.Write(garc.fatb.FileCount); // File Count + foreach (var e in garc.fatb.Entries) + { + gw.Write(e.Vector); + foreach (var s in e.SubEntries.Where(s => s.Exists)) + { gw.Write((uint)s.Start); gw.Write((uint)s.End); gw.Write((uint)s.Length); } + } + #endregion + + #region Write Files + long largestSize = 0; // Required memory to allocate to handle the largest file + foreach (string e in packOrder) + { + string[] fa = Directory.Exists(e) ? Directory.GetFiles(e) : new[] { e }; + foreach (string f in fa) + { + // Update largest file length if necessary + long len = new FileInfo(f).Length; + if (len > largestSize) largestSize = len; + + // Write to FIMB + using (var input = File.OpenRead(f)) + input.CopyTo(GARCdata); + + // While length is not divisible by 4, pad with FF (unused byte) + while (GARCdata.Length % 4 > 0) GARCdata.WriteByte(0xFF); + } + } + #endregion + + gw.Write((uint)0x46494D42); // FIMB + gw.Write((uint)0x0000000C); // Header Length + gw.Write((uint)GARCdata.Length); // Data Length + + gw.Seek(0x10, SeekOrigin.Begin); // Goto the start of the un-set 0 data we set earlier and set it. + gw.Write((uint)newGARC.Length); // Write Data Offset + gw.Write((uint)(newGARC.Length + GARCdata.Length)); // Write total GARC Length + gw.Write((uint)largestSize); // Write Largest File stat (?) + + newGARC.Seek(0, SeekOrigin.End); // Goto the end so we can copy the filedata after the GARC headers. + + // Write in the data + GARCdata.Position = 0; + GARCdata.CopyTo(newGARC); // Copy the data. + // New File is ready to be saved (memstream newGARC) + try + { + File.WriteAllBytes(garcPath, newGARC.ToArray()); + if (label.InvokeRequired) + label.Invoke((MethodInvoker)delegate { label.Visible = false; }); + else { label.Visible = false; } + + // We're done. + SystemSounds.Exclamation.Play(); + if (!supress) Util.Alert("Pack Successful!", filectr + " files packed to the GARC!"); + + return true; + } + catch (Exception e) { Util.Error("Packing Failed!", e.ToString()); return false; } + } + } + internal static bool garcUnpack(string garcPath, string outPath, bool skipDecompression, ProgressBar pBar1 = null, Label label = null, bool supress = false, bool bypassExt = false) + { + if (!File.Exists(garcPath) && !supress) { Util.Alert("File does not exist"); return false; } + + // Unpack the GARC + GARCFile garc = unpackGARC(garcPath); + const string ext = "bin"; // Default Extension Name + int fileCount = garc.fatb.FileCount; + string format = "D" + Math.Ceiling(Math.Log10(fileCount)); + + #region Display + // Initialize ProgressBar + if (pBar1 == null) pBar1 = new ProgressBar(); + if (pBar1.InvokeRequired) + pBar1.Invoke((MethodInvoker)delegate { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = fileCount; }); + else { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = garc.fatb.FileCount; } + + if (label == null) label = new Label(); + if (label.InvokeRequired) + label.Invoke((MethodInvoker)delegate { label.Visible = true; }); + #endregion + + using (BinaryReader br = new BinaryReader(File.OpenRead(garcPath))) + { + // Create Extraction folder if it does not exist. + if (!Directory.Exists(outPath)) + Directory.CreateDirectory(outPath); + + int filectr = 0; + // Pull out all the files + for (int o = 0; o < garc.fato.EntryCount; o++) + { + + var Entry = garc.fatb.Entries[o]; + // Set Entry File Name + string fileName = o.ToString(format); + + #region OutDirectory Determination + string parentFolder = Entry.IsFolder ? Path.Combine(outPath, fileName) : outPath; + if (Entry.IsFolder) // Process Folder + Directory.CreateDirectory(parentFolder); + #endregion + + uint vector = Entry.Vector; + for (int i = 0; i < 32; i++) // For each bit in vector + { + var SubEntry = Entry.SubEntries[i]; + if (!SubEntry.Exists) continue; + + // Seek to Offset + br.BaseStream.Position = SubEntry.Start + garc.DataOffset; + + // Check if Compressed + bool compressed = false; + if (!skipDecompression) + try { compressed = (byte)br.PeekChar() == 0x11; } + catch { } + + // Write File + string fileOut = Path.Combine(parentFolder, (Entry.IsFolder ? i.ToString("00") : fileName) + "." + ext); + using (BinaryWriter bw = new BinaryWriter(File.OpenWrite(fileOut))) + { + // Write out the data for the file + br.BaseStream.Position = SubEntry.Start + garc.DataOffset; + bw.Write(br.ReadBytes(SubEntry.Length)); + filectr++; + } + if (compressed) + #region Decompression + { + string decout = Path.Combine(Path.GetDirectoryName(fileOut), "dec_" + Path.GetFileName(fileOut)); + try + { + LZSS.Decompress(fileOut, decout); + try { File.Delete(fileOut); } + catch (Exception e) { Util.Error("A compressed file could not be deleted.", fileOut, e.ToString()); } + } + catch + { + // File is really not encrypted. + try { File.Delete(decout); } + catch (Exception e) { Util.Error("This shouldn't happen", e.ToString()); } + } + } + #endregion + + #region Step + if (pBar1.InvokeRequired) pBar1.Invoke((MethodInvoker)(() => pBar1.PerformStep())); + else pBar1.PerformStep(); + + string update = String.Format("{0:P2} - {1}/{2}", (filectr) / (fileCount), filectr, fileCount); + if (label.InvokeRequired) + label.Invoke((MethodInvoker)delegate { label.Text = update; }); + else { label.Text = update; } + #endregion + + if ((vector >>= 1) == 0) break; + } + } + } + #region Updates + if (label.InvokeRequired) + label.Invoke((MethodInvoker)delegate { label.Visible = false; }); + else { label.Visible = false; } + SystemSounds.Exclamation.Play(); + if (!supress) Util.Alert("Unpack Successful!", fileCount + " files unpacked from the GARC!"); + #endregion + return true; + } + + internal static GARCFile unpackGARC(string path) + { + GARCFile garc = new GARCFile(); + using (BinaryReader br = new BinaryReader(File.OpenRead(path))) + { + // GARC Header + garc.Magic = br.ReadChars(4); + garc.HeaderSize = br.ReadUInt32(); + garc.Endianess = br.ReadUInt16(); + garc.ChunkCount = br.ReadUInt16(); + garc.FileSize = br.ReadUInt32(); + + garc.DataOffset = br.ReadUInt32(); + garc.FileSize = br.ReadUInt32(); + garc.LastSize = br.ReadUInt32(); + + // FATO (File Allocation Table Offsets) + garc.fato.Magic = br.ReadChars(4); + garc.fato.HeaderSize = br.ReadInt32(); + garc.fato.EntryCount = br.ReadUInt16(); + garc.fato.Padding = br.ReadUInt16(); + + garc.fato.Entries = new FATO_Entry[garc.fato.EntryCount]; + for (int i = 0; i < garc.fato.EntryCount; i++) + garc.fato.Entries[i].Offset = br.ReadInt32(); + + // FATB (File Allocation Table Bits) + garc.fatb.Magic = br.ReadChars(4); + garc.fatb.HeaderSize = br.ReadInt32(); + garc.fatb.FileCount = br.ReadInt32(); + + garc.fatb.Entries = new FATB_Entry[garc.fato.EntryCount]; + for (int i = 0; i < garc.fato.EntryCount; i++) // Loop through all FATO entries + { + garc.fatb.Entries[i].Vector = br.ReadUInt32(); + garc.fatb.Entries[i].SubEntries = new FATB_SubEntry[32]; + uint bitvector = garc.fatb.Entries[i].Vector; + int ctr = 0; + for (int b = 0; b < 32; b++) + { + garc.fatb.Entries[i].SubEntries[b].Exists = (bitvector & 1) == 1; + bitvector >>= 1; + if (!garc.fatb.Entries[i].SubEntries[b].Exists) continue; + garc.fatb.Entries[i].SubEntries[b].Start = br.ReadInt32(); + garc.fatb.Entries[i].SubEntries[b].End = br.ReadInt32(); + garc.fatb.Entries[i].SubEntries[b].Length = br.ReadInt32(); + ctr++; + } + garc.fatb.Entries[i].IsFolder = ctr > 1; + } + + // FIMB (File IMage Bytes) + garc.fimg.Magic = br.ReadChars(4); + garc.fimg.HeaderSize = br.ReadInt32(); + garc.fimg.DataSize = br.ReadInt32(); + + // Files data + // Oftentimes too large to toss into a byte array. Fetch as needed with a BinaryReader. + } + return garc; + } + + public struct GARCFile + { + public Char[] Magic; // Always GARC = 0x4E415243 + public UInt32 HeaderSize; // Always 0x001C + public UInt16 Endianess; // 0xFFFE + public UInt16 ChunkCount; // Always 0x0400 chunk count + + public UInt32 DataOffset; + public UInt32 FileSize; + public UInt32 LastSize; + + public FATO fato; + public FATB fatb; + public FIMG fimg; + } + + public struct FATO + { + public Char[] Magic; + public Int32 HeaderSize; + public UInt16 EntryCount; + public UInt16 Padding; + + public FATO_Entry[] Entries; + } + public struct FATO_Entry + { + public Int32 Offset; + } + + public struct FATB + { + public Char[] Magic; + public Int32 HeaderSize; + public Int32 FileCount; + + public FATB_Entry[] Entries; + } + public struct FATB_Entry + { + public UInt32 Vector; + public Boolean IsFolder; + public FATB_SubEntry[] SubEntries; + } + public struct FATB_SubEntry + { + public Boolean Exists; + public Int32 Start; + public Int32 End; + public Int32 Length; + } + + public struct FIMG + { + public Char[] Magic; + public Int32 HeaderSize; + public Int32 DataSize; + } + } + #endregion +} \ No newline at end of file diff --git a/pk3DS/GARCTool.cs b/pk3DS/3DS/LZSS.cs similarity index 66% rename from pk3DS/GARCTool.cs rename to pk3DS/3DS/LZSS.cs index 1138b61..4ed7f22 100644 --- a/pk3DS/GARCTool.cs +++ b/pk3DS/3DS/LZSS.cs @@ -1,397 +1,12 @@ using System; using System.IO; -using System.Media; -using System.Windows.Forms; -namespace pk3DS +namespace CTR { - public class GARCTool + // LZSS (de)compression, heavily taken from dsdecmp + public class LZSS { - // Note: This has been customized from the original GARCTool source to not use temporary files (instead uses MemoryStream). - public static bool garcPackMS(string folderPath, string garcPath, ProgressBar pBar1 = null, Label label = null, bool supress = false) - { - // Check to see if our input folder exists. - if (!new DirectoryInfo(folderPath).Exists) { Util.Error("Folder does not exist."); return false; } - - // Okay some basic proofing is done. Proceed. - - // Get the paths of the files to pack up. - string[] filepaths = Directory.GetFiles(folderPath, "*.*", SearchOption.TopDirectoryOnly); - - // Initialize ProgressBar - if (pBar1 == null) pBar1 = new ProgressBar(); - if (label == null) label = new Label(); - if (pBar1.InvokeRequired) - pBar1.Invoke((MethodInvoker)delegate { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = filepaths.Length; }); - else { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = filepaths.Length; } - if (label.InvokeRequired) - label.Invoke((MethodInvoker)delegate { label.Visible = true; }); - else { label.Visible = true; } - - // Copy the files to the working directory so our compression doesn't overwrite anything. - - - // Scan through to see if we have to compress anything. - // Delete the old garc if it exists, then write our new one - try { File.Delete(garcPath); } catch { } - - // Set up the Header Info - using (var newGARC = new MemoryStream()) - using (BinaryWriter gw = new BinaryWriter(newGARC)) - { - - // Write GARC header - gw.Write((uint)0x47415243); // Write "CRAG" - gw.Write((uint)0x0000001C); // Header Length - gw.Write((ushort)0xFEFF); // FEFF BOM - gw.Write((ushort)0x0400); // - gw.Write((uint)0x00000004); // - gw.Write((uint)0x00000000); // Data Offset - gw.Write((uint)0x00000000); // File Length - gw.Write((uint)0x00000000); // FATB chunk last word - - // Write OTAF - gw.Write((uint)0x4641544F); // OTAF - gw.Write((uint)(0xC + 4 * filepaths.Length)); // Section Size - gw.Write((ushort)filepaths.Length); // Count: n - gw.Write((ushort)0xFFFF); // padding? dunno - - // write BTAF jump offsets - for (int i = 0; i < filepaths.Length; i++) - gw.Write((uint)i * 0x10); - - // Start BTAF - gw.Write((uint)0x46415442); // BTAF - gw.Write((uint)(0xC + 0x10 * filepaths.Length)); // Chunk Size - gw.Write((uint)filepaths.Length); - - uint offset = 0; - uint largest = 0; - - // Sort out the files to get the actual order - int[] fp = new int[filepaths.Length]; - try - { - for (int i = 0; i < fp.Length; i++) - { - string fn = Path.GetFileNameWithoutExtension(filepaths[i]); - int compressed = fn.IndexOf("dec_", StringComparison.Ordinal); - if (compressed < 0) - fp[Int32.Parse(fn)] = i; - else - fp[Int32.Parse(fn.Substring(compressed + 4))] = i; - } - } - catch (Exception e) { Util.Error("Invalid packing filenames", e.ToString()); return false; } - - // Assemble the GARCData. - using (var GARCdata = new MemoryStream()) - { - for (int i = 0; i < filepaths.Length; i++) - { - string name = filepaths[fp[i]]; - try - { - if (label.InvokeRequired) - label.Invoke((MethodInvoker)delegate { label.Text = String.Format("{0:P2} - {1}/{2} - {3}", ((float)i) / ((float)filepaths.Length), i, filepaths.Length, Path.GetFileName(name)); }); - else { label.Text = String.Format("{0:P2} - {1}/{2} - {3}", ((float)i) / ((float)filepaths.Length), i, filepaths.Length, Path.GetFileName(name)); } - } - catch { } - int compressed = Path.GetFileName(name).IndexOf("dec_", StringComparison.Ordinal); - if (compressed > -1) - { - // File needs to be compressed and replaced. - string compressedName = Path.Combine(Path.GetDirectoryName(name), Path.GetFileNameWithoutExtension(name).Substring(compressed + 4) + ".bin"); - - try { dsdecmp.Compress(name, compressedName); } - catch { throw new Exception("Compression failed"); } - - // Replace file name with compressed name so we can delete after packing. - name = compressedName; - } - FileInfo fi = new FileInfo(name); - using (var input = File.OpenRead(name)) - { - gw.Write((uint)1); // garc.btaf.entries[i].bits = br.ReadUInt32(); - gw.Write((uint)offset); // Start/Begin Offset - uint round = (uint)Math.Ceiling(((double)fi.Length / 4)) * 4; - offset += (uint)(round); // Round up Offset. - gw.Write((uint)offset); // End/Stop Offset - gw.Write((uint)fi.Length); // Length/Size - - if (fi.Length > largest) largest = (uint)fi.Length; - - // Write the data to the BMIF data section - input.CopyTo(GARCdata); // then pad with FF's if not /4 - while (GARCdata.Length % 4 > 0) GARCdata.WriteByte(0xFF); - } - - // Delete file if it is compressed. - if (compressed > -1) File.Delete(name); - - // Advance the ProgressBar. - if (pBar1.InvokeRequired) - pBar1.Invoke((MethodInvoker)delegate { pBar1.PerformStep(); }); - else { pBar1.PerformStep(); } - } - - gw.Write((uint)0x46494D42); - gw.Write((uint)0x0000000C); - gw.Write((uint)offset); - - gw.Seek(0x10, SeekOrigin.Begin); // Goto the start of the un-set 0 data we set earlier and set it. - gw.Write((uint)newGARC.Length); // Write Data Offset - gw.Write((uint)(newGARC.Length + GARCdata.Length)); // Write total GARC Length - gw.Write((uint)largest); // Write Largest File stat (?) - - newGARC.Seek(0, SeekOrigin.End); // Goto the end so we can copy the filedata after the GARC headers. - - // Write in the data - GARCdata.Position = 0; - GARCdata.CopyTo(newGARC); // Copy the data. - } - // New File is ready to be saved (memstream newGARC) - try - { - File.WriteAllBytes(garcPath, newGARC.ToArray()); - if (label.InvokeRequired) - label.Invoke((MethodInvoker)delegate { label.Visible = false; }); - else { label.Visible = false; } - - // We're done. - SystemSounds.Exclamation.Play(); - if (!supress) Util.Alert("Pack Successful!", filepaths.Length + " files packed to the GARC!"); - return true; - } - catch (Exception e) { Util.Error("Packing Failed!", e.ToString()); return false; } - } - } - public static bool garcUnpack(string garcPath, string outPath, bool skipDecompression, ProgressBar pBar1 = null, Label label = null, bool supress = false, bool bypassExt = false) - { - if (!File.Exists(garcPath) && !supress) { Util.Alert("File does not exist"); return false; } - - // Unpack the GARC - GARC garc = ARC.unpackGARC(garcPath); - - // Initialize ProgressBar - if (pBar1 == null) pBar1 = new ProgressBar(); - if (label == null) label = new Label(); - - - if (pBar1.InvokeRequired) - pBar1.Invoke((MethodInvoker)delegate { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = garc.otaf.nFiles; }); - else { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = garc.otaf.nFiles; } - - if (label.InvokeRequired) - label.Invoke((MethodInvoker)delegate { label.Visible = true; }); - else { label.Visible = true; } - - using (BinaryReader br = new BinaryReader(File.OpenRead(garcPath))) - { - // Create Extraction folder if it does not exist. - if (!Directory.Exists(outPath)) - Directory.CreateDirectory(outPath); - - // Pull out all the files - for (int o = 0; o < garc.otaf.nFiles; o++) - { - for (int i = 0; i < garc.btaf.nFiles; i++) - { - string ext = "bin"; - bool compressed = false; - - br.BaseStream.Position = garc.btaf.entries[i].start_offset + garc.data_offset; - byte lzss = 0; - if (!skipDecompression) - { - try - { lzss = (byte)br.PeekChar(); } - catch { lzss = 0; } - } - if (lzss == 0x11) - compressed = true; - else - { - ext = "bin"; - br.BaseStream.Seek(0, SeekOrigin.Begin); - } - - // Set File Name - string filename = o.ToString("D" + Math.Ceiling(Math.Log10(garc.otaf.nFiles))); - if (garc.btaf.nFiles == garc.otaf.nFiles) filename = i.ToString("D" + Math.Ceiling(Math.Log10(garc.otaf.nFiles))); - else if (garc.btaf.nFiles > 1 && garc.btaf.nFiles != garc.otaf.nFiles) filename += "." + i; - string fileout = Path.Combine(outPath, filename + "." + ext); - using (BinaryWriter bw = new BinaryWriter(File.OpenWrite(fileout))) - { - // Write out the data for the file - br.BaseStream.Position = garc.btaf.entries[i].start_offset + garc.data_offset; - for (int x = 0; x < garc.btaf.entries[i].length; x++) - bw.Write(br.ReadByte()); - } - // See if decompression should be attempted. - #region Decompression - if (compressed && !skipDecompression) - { - string decout = Path.Combine(outPath, "dec_" + filename + ".bin"); - try - { - long n = dsdecmp.Decompress(fileout, decout); - try { File.Delete(fileout); } - catch (Exception e) { Util.Error("A compressed file could not be deleted.", fileout, e.ToString()); } - - // Try to detect for extension now - ext = "bin"; - - File.Move(decout, Path.Combine(outPath, "dec_" + filename + "." + ext)); - } - catch - { - // File is really not encrypted. - try { File.Delete(decout); } - catch (Exception e) { Util.Error("This shouldn't happen", e.ToString()); } - } - } - if (pBar1.InvokeRequired) - pBar1.Invoke((MethodInvoker)delegate { pBar1.PerformStep(); }); - else - pBar1.PerformStep(); - if (label.InvokeRequired) - label.Invoke((MethodInvoker)delegate { label.Text = String.Format("{0:P2} - {1}/{2}", ((float)i) / garc.otaf.nFiles, i, garc.otaf.nFiles); }); - else { label.Text = String.Format("{0:P2} - {1}/{2}", ((float)i) / garc.otaf.nFiles, i, garc.otaf.nFiles); } - #endregion - } - if (garc.otaf.nFiles == garc.btaf.nFiles) break; - } - } - if (label.InvokeRequired) - label.Invoke((MethodInvoker)delegate { label.Visible = false; }); - else { label.Visible = false; } - SystemSounds.Exclamation.Play(); - if (!supress) Util.Alert("Unpack Successful!", garc.otaf.nFiles + " files unpacked from the GARC!"); - return true; - } - } - - // Sub-classes - #region GARC Class & Struct - public partial class ARC - { - public static GARC unpackGARC(string path) - { - GARC garc = new GARC(); - BinaryReader br = new BinaryReader(File.OpenRead(path)); - - // GARC Header - garc.id = br.ReadChars(4); - garc.header_size = br.ReadUInt32(); - garc.id_endian = br.ReadUInt16(); - if (garc.id_endian == 0xFEFF) - Util.Reverse(garc.id); - garc.constant = br.ReadUInt16(); - garc.file_size = br.ReadUInt32(); - - garc.data_offset = br.ReadUInt32(); - garc.file_length = br.ReadUInt32(); - garc.lastsize = br.ReadUInt32(); - - // OTAF - garc.otaf.id = br.ReadChars(4); - garc.otaf.section_size = br.ReadUInt32(); - garc.otaf.nFiles = br.ReadUInt16(); - garc.otaf.padding = br.ReadUInt16(); - - garc.otaf.entries = new OTAF_Entry[garc.otaf.nFiles]; - // not really needed; plus it's wrong - for (int i = 0; i < garc.otaf.nFiles; i++) - { - uint val = br.ReadUInt32(); - if (garc.otaf.padding == 0xffff) - val = Util.Reverse(val); - - garc.otaf.entries[i].name = val.ToString(); - } - - // BTAF (File Allocation TaBle) - garc.btaf.id = br.ReadChars(4); - garc.btaf.section_size = br.ReadUInt32(); - garc.btaf.nFiles = br.ReadUInt32(); - - garc.btaf.entries = new BTAF_Entry[garc.btaf.nFiles]; - garc.btaf.entries[0].bits = br.ReadUInt32(); - for (int i = 0; i < garc.btaf.nFiles; i++) - { - if (i != 0 && garc.btaf.nFiles == garc.otaf.nFiles) - garc.btaf.entries[i].bits = br.ReadUInt32(); - garc.btaf.entries[i].start_offset = br.ReadUInt32(); - garc.btaf.entries[i].end_offset = br.ReadUInt32(); - garc.btaf.entries[i].length = br.ReadUInt32(); - } - - // BMIF - garc.gmif.id = br.ReadChars(4); - garc.gmif.section_size = br.ReadUInt32(); - garc.gmif.data_size = br.ReadUInt32(); - - // Files data - - br.Close(); - return garc; - } - } - public struct GARC - { - public char[] id; // Always GARC = 0x4E415243 - public UInt32 header_size; // Always 0x001C - public UInt16 id_endian; // 0xFFFE - public UInt16 constant; // Always 0x0400 chunk count - public UInt32 file_size; - - public UInt32 data_offset; - public UInt32 file_length; - public UInt32 lastsize; - - public OTAF otaf; - public BTAF btaf; - public GMIF gmif; - } - public struct OTAF - { - public char[] id; - public UInt32 section_size; - public UInt16 nFiles; - public UInt16 padding; - - public OTAF_Entry[] entries; - } - public struct OTAF_Entry - { - public string name; - } - public struct BTAF - { - public char[] id; - public UInt32 section_size; - public UInt32 nFiles; - public BTAF_Entry[] entries; - } - public struct BTAF_Entry - { - public UInt32 bits; - public UInt32 start_offset; - public UInt32 end_offset; - public UInt32 length; - } - public struct GMIF - { - public char[] id; - public UInt32 section_size; - public UInt32 data_size; - } - #endregion - #region dsdecmp Classes - public class dsdecmp // LZ11 (de)compression - { - public static long Decompress(string infile, string outfile) + internal static long Decompress(string infile, string outfile) { // make sure the output directory exists string outDirectory = Path.GetDirectoryName(outfile); @@ -419,7 +34,7 @@ public static long Decompress(string infile, string outfile) /// The length of the output data. /// When the given length of the input data /// is not enough to properly decompress the input. - public static long Decompress(Stream instream, long inLength, Stream outstream) + internal static long Decompress(Stream instream, long inLength, Stream outstream) { #region Format definition in NDSTEK style /* Data header (32bit) @@ -518,7 +133,7 @@ Flag data (8bit) throw new StreamTooShortException(); int length = byte1 >> 4; - int disp = -1; + int disp; if (length == 0) { #region case 0; 0(B C)(D EF) + (0x11)(0x1) = (LEN)(DISP) @@ -628,7 +243,7 @@ Flag data (8bit) return decompressedSize; } - public static int Compress(string infile, string outfile) + internal static int Compress(string infile, string outfile) { // make sure the output directory exists string outDirectory = Path.GetDirectoryName(outfile); @@ -647,7 +262,7 @@ public static int Compress(string infile, string outfile) /// This algorithm should yield files that are the same as those found in the games. /// (delegates to the optimized method if LookAhead is set) /// - public unsafe static int Compress(Stream instream, long inLength, Stream outstream, bool original) + internal unsafe static int Compress(Stream instream, long inLength, Stream outstream, bool original) { // make sure the decompressed size fits in 3 bytes. // There should be room for four bytes, however I'm not 100% sure if that can be used @@ -774,7 +389,7 @@ public unsafe static int Compress(Stream instream, long inLength, Stream outstre /// and determine the optimal 'length' values for the compressed blocks. Is not 100% optimal, /// as the flag-bytes are not taken into account. /// - private unsafe static int CompressWithLA(Stream instream, long inLength, Stream outstream) + internal unsafe static int CompressWithLA(Stream instream, long inLength, Stream outstream) { // save the input data in an array to prevent having to go back and forth in a file byte[] indata = new byte[inLength]; @@ -892,7 +507,7 @@ private unsafe static int CompressWithLA(Stream instream, long inLength, Stream /// this value is the optimal 'length' value. If it is 1, the block should not be compressed. /// The 'disp' values of the compressed blocks. May be 0, in which case the /// corresponding length will never be anything other than 1. - private unsafe static void GetOptimalCompressionLengths(byte* indata, int inLength, out int[] lengths, out int[] disps) + internal unsafe static void GetOptimalCompressionLengths(byte* indata, int inLength, out int[] lengths, out int[] disps) { lengths = new int[inLength]; disps = new int[inLength]; @@ -1148,5 +763,4 @@ public static int ToNDSu24(byte[] buffer, int offset) #endregion } #endregion - #endregion -} \ No newline at end of file +} diff --git a/pk3DS/3DS/mini.cs b/pk3DS/3DS/mini.cs new file mode 100644 index 0000000..30f31a2 --- /dev/null +++ b/pk3DS/3DS/mini.cs @@ -0,0 +1,228 @@ +using System; +using System.IO; + +namespace CTR +{ + // Mini Packing Util + class mini + { + internal static void packMini(string path, string ident, string fileName, string outExt = null, string outFolder = null, bool delete = true) + { + if (outFolder == null) + { + delete = false; + 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); + } + } + if (delete) + Directory.Delete(path, true); + } + 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 bool packMini2(string path, string ident, string fileName) + { + if (!Directory.Exists(path)) return false; + try + { + string[] filesToPack = Directory.GetFiles(path); + byte[][] fileData = new byte[filesToPack.Length][]; + for (int i = 0; i < filesToPack.Length; i++) fileData[i] = File.ReadAllBytes(filesToPack[i]); + byte[] miniBytes = packMini(fileData, ident); + File.WriteAllBytes(fileName, miniBytes); + return true; + } + catch { return false; } + } + internal static void unpackMini(string path, string ident, string outFolder = null, bool delete = true) + { + if (outFolder == null) outFolder = Path.GetDirectoryName(path); + if (!Directory.Exists(outFolder)) Directory.CreateDirectory(outFolder); + using (var s = new MemoryStream(File.ReadAllBytes(path))) + 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); + } + } + } + if (delete) + 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; + } + } + internal static string getIsMini(string path) + { + byte[] data = File.ReadAllBytes(path); + var fi = new FileInfo(path); + try + { + string fx = new string(new[] { (char)data[0], (char)data[1] }); + ushort count = BitConverter.ToUInt16(data, 2); + + uint[] offsets = new uint[count + 1]; + uint length = 1338; + for (int i = 0; i < count; i++) + { + offsets[i] = BitConverter.ToUInt32(data, 4 + i * 4); + length = BitConverter.ToUInt32(data, 8 + i * 4); + } + + offsets[offsets.Length - 1] = length; + return (fi.Length == length) ? fx : null; + } + catch { return null; } + } + } +} diff --git a/pk3DS/ARCUtil.cs b/pk3DS/ARCUtil.cs index 2bddd12..93e9f19 100644 --- a/pk3DS/ARCUtil.cs +++ b/pk3DS/ARCUtil.cs @@ -6,7 +6,7 @@ namespace pk3DS { - public partial class ARC + public class ARC { // Multi Type Archive Handling internal static Boolean onefile = true; @@ -468,7 +468,6 @@ internal static string Interpret(string path) { 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); @@ -479,7 +478,7 @@ internal static string Interpret(string path) { sb.Append(c); } - FileName = sb.ToString().Replace('/', Path.DirectorySeparatorChar); + string FileName = sb.ToString().Replace('/', Path.DirectorySeparatorChar); fs.Close(); string FileDir = Path.GetDirectoryName(dir + FileName) + Path.DirectorySeparatorChar; if (!Directory.Exists(FileDir)) @@ -496,213 +495,7 @@ internal static string Interpret(string path) } return ret; } - - // Mini Packing Util - internal static void packMini(string path, string ident, string fileName, string outExt = null, string outFolder = null, bool delete = true) - { - if (outFolder == null) - { - delete = false; - 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); - } - } - if (delete) - Directory.Delete(path, true); - } - 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, bool delete = true) - { - if (outFolder == null) outFolder = Path.GetDirectoryName(path); - if (!Directory.Exists(outFolder)) Directory.CreateDirectory(outFolder); - using (var s = new MemoryStream(File.ReadAllBytes(path))) - 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); - } - } - } - if (delete) - 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; - } - } - internal static string getIsMini(string path) - { - byte[] data = File.ReadAllBytes(path); - var fi = new FileInfo(path); - try - { - string fx = new string(new[]{(char)data[0], (char)data[1]}); - ushort count = BitConverter.ToUInt16(data, 2); - - uint[] offsets = new uint[count + 1]; - uint length = 1338; - for (int i = 0; i < count; i++) - { - offsets[i] = BitConverter.ToUInt32(data, 4 + i*4); - length = BitConverter.ToUInt32(data, 8 + i*4); - } - - offsets[offsets.Length - 1] = length; - return (fi.Length == length) ? fx : null; - } - catch { return null; } - } - + // Unpacking internal static string unpackDARC(string path, string outFolder = null, bool delete = true) { diff --git a/pk3DS/Main.cs b/pk3DS/Main.cs index 5694d81..b1ec376 100644 --- a/pk3DS/Main.cs +++ b/pk3DS/Main.cs @@ -20,8 +20,6 @@ using System.Media; using System.Threading; using System.Windows.Forms; -using BLZ; -using CTR; namespace pk3DS { @@ -165,14 +163,14 @@ private void openQuick(string path) if (fi.Name.Contains("code.bin")) // Compress/Decompress .code.bin { if (fi.Length % 0x200 == 0 && (Util.Prompt(MessageBoxButtons.YesNo, "Detected Decompressed code.bin.", "Compress? File will be replaced.") == DialogResult.Yes)) - new Thread(() => { threads++; new BLZCoder(new[] { "-en", path }, pBar1); threads--; Util.Alert("Compressed!"); }).Start(); + new Thread(() => { threads++; new CTR.BLZCoder(new[] { "-en", path }, pBar1); threads--; Util.Alert("Compressed!"); }).Start(); else if (Util.Prompt(MessageBoxButtons.YesNo, "Detected Compressed code.bin.", "Decompress? File will be replaced.") == DialogResult.Yes) - new Thread(() => { threads++; new BLZCoder(new[] { "-d", path }, pBar1); threads--; Util.Alert("Decompressed!"); }).Start(); + new Thread(() => { threads++; new CTR.BLZCoder(new[] { "-d", path }, pBar1); threads--; Util.Alert("Decompressed!"); }).Start(); } else if (fi.Name.ToLower().Contains("exe")) // Unpack exefs { if (fi.Length % 0x200 == 0 && (Util.Prompt(MessageBoxButtons.YesNo, "Detected ExeFS.bin.", "Unpack?") == DialogResult.Yes)) - new Thread(() => { threads++; ExeFS.get(path, Path.GetDirectoryName(path)); threads--; Util.Alert("Unpacked!"); }).Start(); + new Thread(() => { threads++; CTR.ExeFS.get(path, Path.GetDirectoryName(path)); threads--; Util.Alert("Unpacked!"); }).Start(); } else if (fi.Name.ToLower().Contains("rom")) { @@ -275,7 +273,7 @@ private bool checkIfExeFS(string path) FileInfo fi = new FileInfo(files[0]); if (!fi.Name.Contains("code")) return false; if (fi.Length % 0x200 != 0 && (Util.Prompt(MessageBoxButtons.YesNo, "Detected Compressed code.bin.", "Decompress? File will be replaced.") == DialogResult.Yes)) - new Thread(() => { threads++; new BLZCoder(new[] { "-d", files[0] }, pBar1); threads--; Util.Alert("Decompressed!"); }).Start(); + new Thread(() => { threads++; new CTR.BLZCoder(new[] { "-d", files[0] }, pBar1); threads--; Util.Alert("Decompressed!"); }).Start(); ExeFSPath = path; return true; @@ -533,7 +531,7 @@ private void rebuildExeFS(object sender, EventArgs e) new Thread(() => { threads++; - new BLZCoder(new[] { "-en", files[file] }, pBar1); + new CTR.BLZCoder(new[] { "-en", files[file] }, pBar1); Util.Alert("Compressed!"); CTR.ExeFS.set(Directory.GetFiles(ExeFSPath), sfd.FileName); threads--; @@ -647,7 +645,7 @@ public bool getGARC(string infile, string outfolder, bool PB, bool bypassExt = f } try { - bool success = GARCTool.garcUnpack(infile, outfolder, bypassExt, (PB) ? pBar1 : null, null, true, bypassExt); + bool success = CTR.GARC.garcUnpack(infile, outfolder, bypassExt, (PB) ? pBar1 : null, null, true, bypassExt); updateStatus(String.Format(success ? "Success!" : "Failed!"), false); threads--; return success; @@ -661,7 +659,7 @@ public bool setGARC(string outfile, string infolder, bool PB) try { - bool success = GARCTool.garcPackMS(infolder, outfile, (PB) ? pBar1 : null, null, true); + bool success = CTR.GARC.garcPackMS(infolder, outfile, (PB) ? pBar1 : null, null, true); threads--; updateStatus(String.Format(success ? "Success!" : "Failed!"), false); return success; diff --git a/pk3DS/Subforms/Moves.cs b/pk3DS/Subforms/Moves.cs index 27f3a5a..947876b 100644 --- a/pk3DS/Subforms/Moves.cs +++ b/pk3DS/Subforms/Moves.cs @@ -9,7 +9,7 @@ public partial class Moves : Form public Moves() { if (Main.oras) - ARC.unpackMini(Directory.GetFiles("move")[0], "WD"); + CTR.mini.unpackMini(Directory.GetFiles("move")[0], "WD"); movelist[0] = ""; sortedmoves = (string[])movelist.Clone(); @@ -165,7 +165,7 @@ private void formClosing(object sender, FormClosingEventArgs e) setEntry(); if (Main.oras) - ARC.packMini("move", "WD", "0", ".bin"); + CTR.mini.packMini("move", "WD", "0", ".bin"); } private void B_RandAll_Click(object sender, EventArgs e) @@ -190,14 +190,14 @@ private void B_RandAll_Click(object sender, EventArgs e) internal static int[] getTypes() { if (Main.oras) - ARC.unpackMini(Directory.GetFiles("move")[0], "WD"); + CTR.mini.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]; if (Main.oras) - ARC.packMini("move", "WD", "0", ".bin"); + CTR.mini.packMini("move", "WD", "0", ".bin"); return moveTypes; } diff --git a/pk3DS/Subforms/OWSE.cs b/pk3DS/Subforms/OWSE.cs index a82e89e..8e471fe 100644 --- a/pk3DS/Subforms/OWSE.cs +++ b/pk3DS/Subforms/OWSE.cs @@ -74,7 +74,7 @@ private void getEntry() if (entry < 0) return; RTB_F.Text = RTB_O.Text = RTB_W.Text = RTB_T.Text = string.Empty; byte[] raw = File.ReadAllBytes(filepaths[entry]); - locationData = ARC.unpackMini(raw, "ZO"); + locationData = CTR.mini.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 c1188a2..9a5a6f5 100644 --- a/pk3DS/Tools/ToolsUI.Designer.cs +++ b/pk3DS/Tools/ToolsUI.Designer.cs @@ -103,7 +103,6 @@ private void InitializeComponent() this.CB_Repack.Name = "CB_Repack"; this.CB_Repack.Size = new System.Drawing.Size(150, 21); this.CB_Repack.TabIndex = 8; - this.CB_Repack.SelectedIndexChanged += new System.EventHandler(this.CB_Repack_SelectedIndexChanged); // // PB_BCLIM // diff --git a/pk3DS/Tools/ToolsUI.cs b/pk3DS/Tools/ToolsUI.cs index 16feb06..733a0b2 100644 --- a/pk3DS/Tools/ToolsUI.cs +++ b/pk3DS/Tools/ToolsUI.cs @@ -20,7 +20,6 @@ public ToolsUI() PB_BCLIM.DragDrop += tabMain_DragDrop; CLIMWindow = PB_BCLIM.Size; CB_Repack.Items.Add("GARC Pack"); - CB_Repack.Items.Add("GARC Pack (from Existing)"); CB_Repack.Items.Add("DARC Pack (use filenames)"); CB_Repack.Items.Add("Mini Pack (from Name)"); CB_Repack.SelectedIndex = 2; @@ -51,7 +50,7 @@ private void dropLeave(object sender, EventArgs e) } private void openIMG(string path) { - var img = png2bclim.makeBMP(path, CHK_PNG.Checked); + var img = CTR.BCLIM.makeBMP(path, CHK_PNG.Checked); if (img == null) return; PB_BCLIM.Size = new Size(img.Width + 2, img.Height + 2); PB_BCLIM.BackgroundImage = img; @@ -67,12 +66,12 @@ private void openARC(string path, bool recursing = false) // Determine if it is a DARC or a Mini // Check if Mini first - string fx = ARC.getIsMini(path); + string fx = CTR.mini.getIsMini(path); string newFolder = folderPath + "_" + fx; if (fx != null) // Is Mini Packed File { // Fetch Mini File Contents - ARC.unpackMini(path, fx, newFolder, false); + CTR.mini.unpackMini(path, fx, newFolder, false); // Recurse throught the extracted contents if they extract successfully if (Directory.Exists(newFolder)) foreach (string file in Directory.GetFiles(newFolder)) @@ -88,11 +87,13 @@ private void openARC(string path, bool recursing = false) if (pos >= data.Length) return; } var darcData = data.Skip(pos).ToArray(); - var res = CTR.DARC.darc2files(darcData, folderPath + "_d"); + bool r = CTR.DARC.darc2files(darcData, folderPath + "_d"); + if (!r) Util.Alert("Unpacking failed."); } else if (BitConverter.ToUInt32(File.ReadAllBytes(path), 0) == 0x47415243) // GARC { - GARCTool.garcUnpack(path, folderPath + "_g", false); + bool r = CTR.GARC.garcUnpack(path, folderPath + "_g", false); + if (!r) Util.Alert("Unpacking failed."); } else if (!recursing) { Util.Alert("File is not a darc or a mini packed file:" + Environment.NewLine + path); return ;} @@ -114,10 +115,13 @@ private void saveARC(string path) int type = CB_Repack.SelectedIndex; switch (type) { - case -1: return; case 0: // GARC Pack - case 1: // GARC Pack Existing - case 2: // DARC Pack + { + bool r = CTR.GARC.garcPackMS(path, folderName + ".garc"); + if (!r) Util.Alert("Packing failed."); + break; + } + case 1: // DARC Pack (from existing if exists) { string oldFile = path.Replace("_d", ""); if (File.Exists(Path.Combine(parentName, oldFile))) @@ -127,10 +131,12 @@ private void saveARC(string path) else if (File.Exists(Path.Combine(parentName, oldFile + ".darc"))) oldFile = Path.Combine(parentName, oldFile + ".darc"); else oldFile = null; - bool result = CTR.DARC.files2darc(path, false, oldFile); + + bool r = CTR.DARC.files2darc(path, false, oldFile); + if (!r) Util.Alert("Packing failed."); break; } - case 3: // Mini Pack + case 2: // Mini Pack { string fileName = folderName.Replace("_", ""); if (fileName.Length < 2) { Util.Error("Mini Folder name not valid:", path); return; } @@ -138,15 +144,8 @@ private void saveARC(string path) string fileExt = fileName.Substring(fileName.Length - 2); string fileNum = fileName.Substring(0, fileName.Length - 3); // ignore "." - string[] filesToPack = Directory.GetFiles(path); - byte[][] data = new byte[filesToPack.Length][]; - for (int i = 0; i < filesToPack.Length; i++) data[i] = File.ReadAllBytes(filesToPack[i]); - byte[] miniBytes = ARC.packMini(data, fileExt); - - string newName = fileNum + "." + fileExt; - File.WriteAllBytes(parentName + newName, miniBytes); - Util.Alert("Mini Folder Repacked!", - String.Format("InFolder: {0}{2}OutFile{1}", folderName, newName, Path.DirectorySeparatorChar)); + bool r = CTR.mini.packMini2(path, fileExt, parentName + fileNum + "." + fileExt); + if (!r) Util.Alert("Packing failed."); break; } default: Util.Alert("Repacking not implemented." + Environment.NewLine + path); @@ -171,14 +170,5 @@ private void B_Reset_Click(object sender, EventArgs e) { PB_BCLIM.Size = CLIMWindow; } - - private void CB_Repack_SelectedIndexChanged(object sender, EventArgs e) - { - if (CB_Repack.SelectedIndex < 2) // 0,1 - { - Util.Alert("Not implemented:", CB_Repack.Text); - CB_Repack.SelectedIndex = 2; - } - } } } \ No newline at end of file diff --git a/pk3DS/pk3DS.csproj b/pk3DS/pk3DS.csproj index 49e59c5..9ca3c09 100644 --- a/pk3DS/pk3DS.csproj +++ b/pk3DS/pk3DS.csproj @@ -52,14 +52,17 @@ + + + - + @@ -146,7 +149,7 @@ EggMove.cs - + Form diff --git a/pk3DS/png2bclim.cs b/pk3DS/png2bclim.cs deleted file mode 100644 index e3fe769..0000000 --- a/pk3DS/png2bclim.cs +++ /dev/null @@ -1,1537 +0,0 @@ -using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Runtime.InteropServices; -using System.Windows.Forms; - -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; - 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) - img = getIMG_ETC(bclim); - 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; - } - internal static Bitmap getIMG_ETC(CLIM bclim) - { - int format = bclim.FileFormat; - if (format != 10 && format != 11) - return null; - - byte[] data = ETC1_Decompress(bclim.Data, bclim.Width, bclim.Height, format + 2); - Bitmap img = new Bitmap(bclim.Width, bclim.Height); - - for (int i = 0; i < bclim.Width * bclim.Height; i++) // for every pixel - { - Color c = DecodeColor(BitConverter.ToUInt32(data, i * 4), 9); - int x = i % bclim.Width; - int y = i / bclim.Width; - img.SetPixel(x, y, c); - } - 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; - } - - // Ericsson Texture Compression 1 - From Ohana3DS - // https://github.com/gdkchan/Ohana3DS - // By gdkchan & Reisyukaku, ported to C#. - // Porting didn't do everything right; TODO: fix. - internal static byte[] ETC1_Decompress(byte[] Data, int Width, int Height, int Format = 12, int Offset = 0) - { - byte[] Temp_Buffer = new byte[((Width * Height) / 2)]; - byte[] Alphas = new byte[Temp_Buffer.Length]; - if (Format == 12) - { - Buffer.BlockCopy(Data, Offset, Temp_Buffer, 0, Temp_Buffer.Length); - for (int j = 0; j <= Alphas.Length - 1; j++) - { - Alphas[j] = 0xff; - } - } - else - { - int k = 0; - for (int j = 0; j <= (Width * Height) - 1; j++) - { - Buffer.BlockCopy(Data, Offset + j + 8, Temp_Buffer, k, 8); - Buffer.BlockCopy(Data, Offset + j, Alphas, k, 8); - k += 8; - j += 15; - } - } - byte[] Out = new byte[(Width * Height * 4)]; - Offset = 0; - for (int Y = 0; Y <= (Height / 4) - 1; Y++) - { - for (int X = 0; X <= (Width / 4) - 1; X++) - { - byte[] Block = new byte[8]; - byte[] Alphas_Block = new byte[8]; - for (int i = 0; i <= 7; i++) - { - Block[7 - i] = Data[Offset + i]; - Alphas_Block[i] = Alphas[Offset + i]; - } - Offset += 8; - Block = ETC1_Decompress_Block(Block); - - bool Low_High_Toggle = false; - int Alpha_Offset = 0; - for (int TX = 0; TX <= 3; TX++) - { - for (int TY = 0; TY <= 3; TY++) - { - int Out_Offset = (X * 4 + TX + ((Y * 4 + TY) * Width)) * 4; - int Block_Offset = (TX + (TY * 4)) * 4; - Buffer.BlockCopy(Block, Block_Offset, Out, Out_Offset, 3); - - int Alpha_Data = 0; - if (Low_High_Toggle) - { - Alpha_Data = (Alphas_Block[Alpha_Offset] & 0xf0) >> 4; - Alpha_Offset += 1; - } - else - { - Alpha_Data = Alphas_Block[Alpha_Offset] & 0xf; - } - Low_High_Toggle = !Low_High_Toggle; - Out[Out_Offset + 3] = Convert.ToByte((Alpha_Data << 4) + Alpha_Data); - } - } - } - } - return Out; - } - internal static byte[] ETC1_Decompress_Block(byte[] Data) - { - //Ericsson Texture Compression - int Block_Top = BitConverter.ToInt32(Data, 0); - int Block_Bottom = BitConverter.ToInt32(Data, 4); - - bool Flip = (Block_Top & 0x1000000) > 0; - bool Difference = (Block_Top & 0x2000000) > 0; - - int R1, G1, B1; - int R2, G2, B2; - - if (Difference) - { - R1 = Block_Top & 0xf8; - G1 = (Block_Top & 0xf800) >> 8; - B1 = (Block_Top & 0xf80000) >> 16; - - int R = Signed_Byte(Convert.ToByte(R1 >> 3)) + (Signed_Byte(Convert.ToByte((Block_Top & 7) << 5)) >> 5); - int G = Signed_Byte(Convert.ToByte(G1 >> 3)) + (Signed_Byte(Convert.ToByte((Block_Top & 0x700) >> 3)) >> 5); - int B = Signed_Byte(Convert.ToByte(B1 >> 3)) + (Signed_Byte(Convert.ToByte((Block_Top & 0x70000) >> 11)) >> 5); - - R2 = R; - G2 = G; - B2 = B; - - R1 = R1 + (R1 >> 5); - G1 = G1 + (G1 >> 5); - B1 = B1 + (B1 >> 5); - - R2 = (R2 << 3) + (R2 >> 2); - G2 = (G2 << 3) + (G2 >> 2); - B2 = (B2 << 3) + (B2 >> 2); - } - else - { - R1 = Block_Top & 0xf0; - R1 = R1 + (R1 >> 4); - G1 = (Block_Top & 0xf000) >> 8; - G1 = G1 + (G1 >> 4); - B1 = (Block_Top & 0xf00000) >> 16; - B1 = B1 + (B1 >> 4); - - R2 = (Block_Top & 0xf) << 4; - R2 = R2 + (R2 >> 4); - G2 = (Block_Top & 0xf00) >> 4; - G2 = G2 + (G2 >> 4); - B2 = (Block_Top & 0xf0000) >> 12; - B2 = B2 + (B2 >> 4); - } - - int Mod_Table_1 = (Block_Top >> 29) & 7; - int Mod_Table_2 = (Block_Top >> 26) & 7; - - byte[] Out = new byte[(4 * 4 * 4)]; - if (Flip == false) - { - for (int Y = 0; Y <= 3; Y++) - { - for (int X = 0; X <= 1; X++) - { - Color Col_1 = Modify_Pixel(R1, G1, B1, X, Y, Block_Bottom, Mod_Table_1); - Color Col_2 = Modify_Pixel(R2, G2, B2, X + 2, Y, Block_Bottom, Mod_Table_2); - Out[(Y * 4 + X) * 4] = Col_1.R; - Out[((Y * 4 + X) * 4) + 1] = Col_1.G; - Out[((Y * 4 + X) * 4) + 2] = Col_1.B; - Out[(Y * 4 + X + 2) * 4] = Col_2.R; - Out[((Y * 4 + X + 2) * 4) + 1] = Col_2.G; - Out[((Y * 4 + X + 2) * 4) + 2] = Col_2.B; - } - } - } - else - { - for (int Y = 0; Y <= 1; Y++) - { - for (int X = 0; X <= 3; X++) - { - Color Col_1 = Modify_Pixel(R1, G1, B1, X, Y, Block_Bottom, Mod_Table_1); - Color Col_2 = Modify_Pixel(R2, G2, B2, X, Y + 2, Block_Bottom, Mod_Table_2); - Out[(Y * 4 + X) * 4] = Col_1.R; - Out[((Y * 4 + X) * 4) + 1] = Col_1.G; - Out[((Y * 4 + X) * 4) + 2] = Col_1.B; - Out[((Y + 2) * 4 + X) * 4] = Col_2.R; - Out[(((Y + 2) * 4 + X) * 4) + 1] = Col_2.G; - Out[(((Y + 2) * 4 + X) * 4) + 2] = Col_2.B; - } - } - } - - return Out; - } - internal static byte[] ETC1_Compress(Bitmap Img, int Width, int Height, int Format = 12, bool Show_Warning = true) - { - if ((Img.Width != Width) | (Img.Height != Height)) - { - if (Show_Warning) - MessageBox.Show("Images need to have the same resolution!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); - return null; - } - - BitmapData ImgData = Img.LockBits(new Rectangle(0, 0, Img.Width, Img.Height), ImageLockMode.ReadOnly, Img.PixelFormat); - byte[] Data = new byte[(ImgData.Height * ImgData.Stride)]; - Marshal.Copy(ImgData.Scan0, Data, 0, Data.Length); - Img.UnlockBits(ImgData); - - int BPP = (Img.PixelFormat == PixelFormat.Format32bppArgb) ? 32 : 24; - byte[] Out_Data = null; - - #region Conversion - switch (Format) - { - case 12: - case 13: - //Os tiles com compressão ETC1 no 3DS estão embaralhados - byte[] Out = new byte[(Img.Width * Img.Height * 4)]; - int[] Tile_Scramble = Get_ETC1_Scramble(Img.Width, Img.Height); - - int i = 0; - for (int Tile_Y = 0; Tile_Y <= (Img.Height / 4) - 1; Tile_Y++) - { - for (int Tile_X = 0; Tile_X <= (Img.Width / 4) - 1; Tile_X++) - { - int TX = Tile_Scramble[i] % (Img.Width / 4); - int TY = (Tile_Scramble[i] - TX) / (Img.Width / 4); - for (int Y = 0; Y <= 3; Y++) - { - for (int X = 0; X <= 3; X++) - { - int Out_Offset = ((TX * 4) + X + ((((TY * 4) + Y)) * Img.Width)) * 4; - int Image_Offset = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * (BPP / 8); - - Out[Out_Offset] = Data[Image_Offset + 2]; - Out[Out_Offset + 1] = Data[Image_Offset + 1]; - Out[Out_Offset + 2] = Data[Image_Offset]; - if (BPP == 32) - Out[Out_Offset + 3] = Data[Image_Offset + 3]; - else - Out[Out_Offset + 3] = 0xff; - } - } - i += 1; - } - } - - - Out_Data = new byte[((Img.Width * Img.Height) / Format == 12 ? 2 : 1)]; - int Out_Data_Offset = 0; - - for (int Tile_Y = 0; Tile_Y <= (Img.Height / 4) - 1; Tile_Y++) - { - for (int Tile_X = 0; Tile_X <= (Img.Width / 4) - 1; Tile_X++) - { - bool Flip = false; - bool Difference = false; - int Block_Top = 0; - int Block_Bottom = 0; - - //Teste do Difference Bit - int Diff_Match_V = 0; - int Diff_Match_H = 0; - for (int Y = 0; Y <= 3; Y++) - { - for (int X = 0; X <= 1; X++) - { - int Image_Offset_1 = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - int Image_Offset_2 = ((Tile_X * 4) + (2 + X) + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - - byte Bits_R1 = Convert.ToByte(Out[Image_Offset_1] & 0xf8); - byte Bits_G1 = Convert.ToByte(Out[Image_Offset_1 + 1] & 0xf8); - byte Bits_B1 = Convert.ToByte(Out[Image_Offset_1 + 2] & 0xf8); - - byte Bits_R2 = Convert.ToByte(Out[Image_Offset_2] & 0xf8); - byte Bits_G2 = Convert.ToByte(Out[Image_Offset_2 + 1] & 0xf8); - byte Bits_B2 = Convert.ToByte(Out[Image_Offset_2 + 2] & 0xf8); - - if ((Bits_R1 == Bits_R2) & (Bits_G1 == Bits_G2) & (Bits_B1 == Bits_B2)) - Diff_Match_V += 1; - } - } - for (int Y = 0; Y <= 1; Y++) - { - for (int X = 0; X <= 3; X++) - { - int Image_Offset_1 = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - int Image_Offset_2 = ((Tile_X * 4) + X + (((Tile_Y * 4) + (2 + Y)) * Img.Width)) * 4; - - byte Bits_R1 = Convert.ToByte(Out[Image_Offset_1] & 0xf8); - byte Bits_G1 = Convert.ToByte(Out[Image_Offset_1 + 1] & 0xf8); - byte Bits_B1 = Convert.ToByte(Out[Image_Offset_1 + 2] & 0xf8); - - byte Bits_R2 = Convert.ToByte(Out[Image_Offset_2] & 0xf8); - byte Bits_G2 = Convert.ToByte(Out[Image_Offset_2 + 1] & 0xf8); - byte Bits_B2 = Convert.ToByte(Out[Image_Offset_2 + 2] & 0xf8); - - if ((Bits_R1 == Bits_R2) & (Bits_G1 == Bits_G2) & (Bits_B1 == Bits_B2)) - Diff_Match_H += 1; - } - } - //Difference + Flip - if (Diff_Match_H == 8) - { - Difference = true; - Flip = true; - //Difference - } - else if (Diff_Match_V == 8) - { - Difference = true; - //Individual - } - else - { - int Test_R1 = 0; - int Test_G1 = 0; - int Test_B1 = 0; - int Test_R2 = 0; - int Test_G2 = 0; - int Test_B2 = 0; - for (int Y = 0; Y <= 1; Y++) - { - for (int X = 0; X <= 1; X++) - { - int Image_Offset_1 = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - int Image_Offset_2 = ((Tile_X * 4) + (2 + X) + (((Tile_Y * 4) + (2 + Y)) * Img.Width)) * 4; - - Test_R1 += Out[Image_Offset_1]; - Test_G1 += Out[Image_Offset_1 + 1]; - Test_B1 += Out[Image_Offset_1 + 2]; - - Test_R2 += Out[Image_Offset_2]; - Test_G2 += Out[Image_Offset_2 + 1]; - Test_B2 += Out[Image_Offset_2 + 2]; - } - } - - Test_R1 /= 8; - Test_G1 /= 8; - Test_B1 /= 8; - - Test_R2 /= 8; - Test_G2 /= 8; - Test_B2 /= 8; - - int Test_Luma_1 = Convert.ToInt32(0.299f * Test_R1 + 0.587f * Test_G1 + 0.114f * Test_B1); - int Test_Luma_2 = Convert.ToInt32(0.299f * Test_R2 + 0.587f * Test_G2 + 0.114f * Test_B2); - int Test_Flip_Diff = Math.Abs(Test_Luma_1 - Test_Luma_2); - if (Test_Flip_Diff > 48) - Flip = true; - } - - int Avg_R1 = 0; - int Avg_G1 = 0; - int Avg_B1 = 0; - int Avg_R2 = 0; - int Avg_G2 = 0; - int Avg_B2 = 0; - - //Primeiro, cálcula a média de cores de cada bloco - if (Flip) - { - for (int Y = 0; Y <= 1; Y++) - { - for (int X = 0; X <= 3; X++) - { - int Image_Offset_1 = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - int Image_Offset_2 = ((Tile_X * 4) + X + (((Tile_Y * 4) + (2 + Y)) * Img.Width)) * 4; - - Avg_R1 += Out[Image_Offset_1]; - Avg_G1 += Out[Image_Offset_1 + 1]; - Avg_B1 += Out[Image_Offset_1 + 2]; - - Avg_R2 += Out[Image_Offset_2]; - Avg_G2 += Out[Image_Offset_2 + 1]; - Avg_B2 += Out[Image_Offset_2 + 2]; - } - } - } - else - { - for (int Y = 0; Y <= 3; Y++) - { - for (int X = 0; X <= 1; X++) - { - int Image_Offset_1 = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - int Image_Offset_2 = ((Tile_X * 4) + (2 + X) + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - - Avg_R1 += Out[Image_Offset_1]; - Avg_G1 += Out[Image_Offset_1 + 1]; - Avg_B1 += Out[Image_Offset_1 + 2]; - - Avg_R2 += Out[Image_Offset_2]; - Avg_G2 += Out[Image_Offset_2 + 1]; - Avg_B2 += Out[Image_Offset_2 + 2]; - } - } - } - - Avg_R1 /= 8; - Avg_G1 /= 8; - Avg_B1 /= 8; - - Avg_R2 /= 8; - Avg_G2 /= 8; - Avg_B2 /= 8; - - if (Difference) - { - //+============+ - //| Difference | - //+============+ - if ((Avg_R1 & 7) > 3) { Avg_R1 = Clip(Avg_R1 + 8); Avg_R2 = Clip(Avg_R2 + 8); } - if ((Avg_G1 & 7) > 3) { Avg_G1 = Clip(Avg_G1 + 8); Avg_G2 = Clip(Avg_G2 + 8); } - if ((Avg_B1 & 7) > 3) { Avg_B1 = Clip(Avg_B1 + 8); Avg_B2 = Clip(Avg_B2 + 8); } - - Block_Top = (Avg_R1 & 0xf8) | (((Avg_R2 - Avg_R1) / 8) & 7); - Block_Top = Block_Top | (((Avg_G1 & 0xf8) << 8) | ((((Avg_G2 - Avg_G1) / 8) & 7) << 8)); - Block_Top = Block_Top | (((Avg_B1 & 0xf8) << 16) | ((((Avg_B2 - Avg_B1) / 8) & 7) << 16)); - - //Vamos ter certeza de que os mesmos valores obtidos pelo descompressor serão usados na comparação (modo Difference) - Avg_R1 = Block_Top & 0xf8; - Avg_G1 = (Block_Top & 0xf800) >> 8; - Avg_B1 = (Block_Top & 0xf80000) >> 16; - - int R = Signed_Byte(Convert.ToByte(Avg_R1 >> 3)) + (Signed_Byte(Convert.ToByte((Block_Top & 7) << 5)) >> 5); - int G = Signed_Byte(Convert.ToByte(Avg_G1 >> 3)) + (Signed_Byte(Convert.ToByte((Block_Top & 0x700) >> 3)) >> 5); - int B = Signed_Byte(Convert.ToByte(Avg_B1 >> 3)) + (Signed_Byte(Convert.ToByte((Block_Top & 0x70000) >> 11)) >> 5); - - Avg_R2 = R; - Avg_G2 = G; - Avg_B2 = B; - - Avg_R1 = Avg_R1 + (Avg_R1 >> 5); - Avg_G1 = Avg_G1 + (Avg_G1 >> 5); - Avg_B1 = Avg_B1 + (Avg_B1 >> 5); - - Avg_R2 = (Avg_R2 << 3) + (Avg_R2 >> 2); - Avg_G2 = (Avg_G2 << 3) + (Avg_G2 >> 2); - Avg_B2 = (Avg_B2 << 3) + (Avg_B2 >> 2); - } - else - { - //+============+ - //| Individual | - //+============+ - if ((Avg_R1 & 0xf) > 7) - Avg_R1 = Clip(Avg_R1 + 0x10); - if ((Avg_G1 & 0xf) > 7) - Avg_G1 = Clip(Avg_G1 + 0x10); - if ((Avg_B1 & 0xf) > 7) - Avg_B1 = Clip(Avg_B1 + 0x10); - if ((Avg_R2 & 0xf) > 7) - Avg_R2 = Clip(Avg_R2 + 0x10); - if ((Avg_G2 & 0xf) > 7) - Avg_G2 = Clip(Avg_G2 + 0x10); - if ((Avg_B2 & 0xf) > 7) - Avg_B2 = Clip(Avg_B2 + 0x10); - - Block_Top = ((Avg_R2 & 0xf0) >> 4) | (Avg_R1 & 0xf0); - Block_Top = Block_Top | (((Avg_G2 & 0xf0) << 4) | ((Avg_G1 & 0xf0) << 8)); - Block_Top = Block_Top | (((Avg_B2 & 0xf0) << 12) | ((Avg_B1 & 0xf0) << 16)); - - //Vamos ter certeza de que os mesmos valores obtidos pelo descompressor serão usados na comparação (modo Individual) - Avg_R1 = (Avg_R1 & 0xf0) + ((Avg_R1 & 0xf0) >> 4); - Avg_G1 = (Avg_G1 & 0xf0) + ((Avg_G1 & 0xf0) >> 4); - Avg_B1 = (Avg_B1 & 0xf0) + ((Avg_B1 & 0xf0) >> 4); - - Avg_R2 = (Avg_R2 & 0xf0) + ((Avg_R2 & 0xf0) >> 4); - Avg_G2 = (Avg_G2 & 0xf0) + ((Avg_G2 & 0xf0) >> 4); - Avg_B2 = (Avg_B2 & 0xf0) + ((Avg_B2 & 0xf0) >> 4); - } - - if (Flip) - Block_Top = Block_Top | 0x1000000; - if (Difference) - Block_Top = Block_Top | 0x2000000; - - //Seleciona a melhor tabela para ser usada nos blocos - int Mod_Table_1 = 0; - int[] Min_Diff_1 = new int[8]; - for (int a = 0; a <= 7; a++) - { - Min_Diff_1[a] = 0; - } - for (int Y = 0; Y <= (Flip ? 1 : 3); Y++) - { - for (int X = 0; X <= (Flip ? 3 : 1); X++) - { - int Image_Offset = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - int Luma = Convert.ToInt32(0.299f * Out[Image_Offset] + 0.587f * Out[Image_Offset + 1] + 0.114f * Out[Image_Offset + 2]); - - for (int a = 0; a <= 7; a++) - { - int Optimal_Diff = 255 * 4; - for (int b = 0; b <= 3; b++) - { - int CR = Clip(Avg_R1 + Modulation_Table[a, b]); - int CG = Clip(Avg_G1 + Modulation_Table[a, b]); - int CB = Clip(Avg_B1 + Modulation_Table[a, b]); - - int Test_Luma = Convert.ToInt32(0.299f * CR + 0.587f * CG + 0.114f * CB); - int Diff = Math.Abs(Luma - Test_Luma); - if (Diff < Optimal_Diff) - Optimal_Diff = Diff; - } - Min_Diff_1[a] += Optimal_Diff; - } - } - } - - int Temp_1 = 255 * 8; - for (int a = 0; a <= 7; a++) - { - if (Min_Diff_1[a] < Temp_1) - { - Temp_1 = Min_Diff_1[a]; - Mod_Table_1 = a; - } - } - - int Mod_Table_2 = 0; - int[] Min_Diff_2 = new int[8]; - for (int a = 0; a <= 7; a++) - { - Min_Diff_2[a] = 0; - } - for (int Y = Flip ? 2 : 0; Y <= 3; Y++) - { - for (int X = Flip ? 0 : 2; X <= 3; X++) - { - int Image_Offset = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - int Luma = Convert.ToInt32(0.299f * Out[Image_Offset] + 0.587f * Out[Image_Offset + 1] + 0.114f * Out[Image_Offset + 2]); - - for (int a = 0; a <= 7; a++) - { - int Optimal_Diff = 255 * 4; - for (int b = 0; b <= 3; b++) - { - int CR = Clip(Avg_R2 + Modulation_Table[a, b]); - int CG = Clip(Avg_G2 + Modulation_Table[a, b]); - int CB = Clip(Avg_B2 + Modulation_Table[a, b]); - - int Test_Luma = Convert.ToInt32(0.299f * CR + 0.587f * CG + 0.114f * CB); - int Diff = Math.Abs(Luma - Test_Luma); - if (Diff < Optimal_Diff) - Optimal_Diff = Diff; - } - Min_Diff_2[a] += Optimal_Diff; - } - } - } - - int Temp_2 = 255 * 8; - for (int a = 0; a <= 7; a++) - { - if (Min_Diff_2[a] < Temp_2) - { - Temp_2 = Min_Diff_2[a]; - Mod_Table_2 = a; - } - } - - Block_Top = Block_Top | (Mod_Table_1 << 29); - Block_Top = Block_Top | (Mod_Table_2 << 26); - - //Seleciona o melhor valor da tabela que mais se aproxima com a cor original - for (int Y = 0; Y <= (Flip ? 1 : 3); Y++) - { - for (int X = 0; X <= (Flip ? 3 : 1); X++) - { - int Image_Offset = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - int Luma = Convert.ToInt32(0.299f * Out[Image_Offset] + 0.587f * Out[Image_Offset + 1] + 0.114f * Out[Image_Offset + 2]); - - int Col_Diff = 255; - int Pix_Table_Index = 0; - for (int b = 0; b <= 3; b++) - { - int CR = Clip(Avg_R1 + Modulation_Table[Mod_Table_1, b]); - int CG = Clip(Avg_G1 + Modulation_Table[Mod_Table_1, b]); - int CB = Clip(Avg_B1 + Modulation_Table[Mod_Table_1, b]); - - int Test_Luma = Convert.ToInt32(0.299f * CR + 0.587f * CG + 0.114f * CB); - int Diff = Math.Abs(Luma - Test_Luma); - if (Diff < Col_Diff) - { - Col_Diff = Diff; - Pix_Table_Index = b; - } - } - - int Index = X * 4 + Y; - if (Index < 8) - { - Block_Bottom = Block_Bottom | (((Pix_Table_Index & 2) >> 1) << (Index + 8)); - Block_Bottom = Block_Bottom | ((Pix_Table_Index & 1) << (Index + 24)); - } - else - { - Block_Bottom = Block_Bottom | (((Pix_Table_Index & 2) >> 1) << (Index - 8)); - Block_Bottom = Block_Bottom | ((Pix_Table_Index & 1) << (Index + 8)); - } - } - } - - for (int Y = Flip ? 2 : 0; Y <= 3; Y++) - { - for (int X = Flip ? 0 : 2; X <= 3; X++) - { - int Image_Offset = ((Tile_X * 4) + X + (((Tile_Y * 4) + Y) * Img.Width)) * 4; - int Luma = Convert.ToInt32(0.299f * Out[Image_Offset] + 0.587f * Out[Image_Offset + 1] + 0.114f * Out[Image_Offset + 2]); - - int Col_Diff = 255; - int Pix_Table_Index = 0; - for (int b = 0; b <= 3; b++) - { - int CR = Clip(Avg_R2 + Modulation_Table[Mod_Table_2, b]); - int CG = Clip(Avg_G2 + Modulation_Table[Mod_Table_2, b]); - int CB = Clip(Avg_B2 + Modulation_Table[Mod_Table_2, b]); - - int Test_Luma = Convert.ToInt32(0.299f * CR + 0.587f * CG + 0.114f * CB); - int Diff = Math.Abs(Luma - Test_Luma); - if (Diff < Col_Diff) - { - Col_Diff = Diff; - Pix_Table_Index = b; - } - } - - int Index = X * 4 + Y; - if (Index < 8) - { - Block_Bottom = Block_Bottom | (((Pix_Table_Index & 2) >> 1) << (Index + 8)); - Block_Bottom = Block_Bottom | ((Pix_Table_Index & 1) << (Index + 24)); - } - else - { - Block_Bottom = Block_Bottom | (((Pix_Table_Index & 2) >> 1) << (Index - 8)); - Block_Bottom = Block_Bottom | ((Pix_Table_Index & 1) << (Index + 8)); - } - } - } - - //Copia dados para a saída - byte[] Block = new byte[8]; - Buffer.BlockCopy(BitConverter.GetBytes(Block_Top), 0, Block, 0, 4); - Buffer.BlockCopy(BitConverter.GetBytes(Block_Bottom), 0, Block, 4, 4); - byte[] New_Block = new byte[8]; - for (int j = 0; j <= 7; j++) - { - New_Block[7 - j] = Block[j]; - } - if (Format == 13) - { - byte[] Alphas = new byte[8]; - int Alpha_Offset = 0; - for (int TX = 0; TX <= 3; TX++) - { - for (int TY = 0; TY <= 3; TY += 2) - { - int Img_Offset_1 = (Tile_X * 4 + TX + ((Tile_Y * 4 + TY) * Img.Width)) * 4; - int Img_Offset_2 = (Tile_X * 4 + TX + ((Tile_Y * 4 + TY + 1) * Img.Width)) * 4; - - byte Alpha_1 = (byte)(Out[Img_Offset_1 + 3] >> 4); - byte Alpha_2 = (byte)(Out[Img_Offset_2 + 3] >> 4); - - Alphas[Alpha_Offset] = (byte)(Alpha_1 | (Alpha_2 << 4)); - - Alpha_Offset += 1; - } - } - - Buffer.BlockCopy(Alphas, 0, Out_Data, Out_Data_Offset, 8); - Buffer.BlockCopy(New_Block, 0, Out_Data, Out_Data_Offset + 8, 8); - Out_Data_Offset += 16; - } - else if (Format == 12) - { - Buffer.BlockCopy(New_Block, 0, Out_Data, Out_Data_Offset, 8); - Out_Data_Offset += 8; - } - - // Texture_Insertion_Percentage = Convert.ToSingle((Out_Data_Offset / Out_Data.Length) * 100); - } - } - break; - } - #endregion - // Texture_Insertion_Percentage = 0; - return Out_Data; - } - #region Misc Functions - internal static sbyte Signed_Byte(byte Byte_To_Convert) - { - if ((Byte_To_Convert < 0x80)) - return Convert.ToSByte(Byte_To_Convert); - return Convert.ToSByte(Byte_To_Convert - 0x100); - } - internal static int Signed_Short(int Short_To_Convert) - { - if ((Short_To_Convert < 0x8000)) - return Short_To_Convert; - return Short_To_Convert - 0x10000; - } - internal static byte[] Mirror_Texture(byte[] Data, int Width, int Height) - { - byte[] Out = new byte[((Width * 2) * Height * 4)]; - for (int Y = 0; Y <= Height - 1; Y++) - { - for (int X = 0; X <= Width - 1; X++) - { - int Offset = (X + (Y * Width)) * 4; - int Offset_2 = (X + (Y * (Width * 2))) * 4; - int Offset_3 = ((Width + (Width - X - 1)) + (Y * (Width * 2))) * 4; - Buffer.BlockCopy(Data, Offset, Out, Offset_2, 4); - Buffer.BlockCopy(Data, Offset, Out, Offset_3, 4); - } - } - return Out; - } - internal static bool Check_Alpha(byte[] Img) - { - for (int Offset = 0; Offset <= Img.Length - 1; Offset += 4) - { - if (Img[Offset + 3] < 0xff) - return true; - } - return false; - } - internal static int[] Get_ETC1_Scramble(int Width, int Height) - { - int[] Tile_Scramble = new int[((Width / 4) * (Height / 4))]; - int Base_Accumulator = 0; - int Line_Accumulator = 0; - int Base_Number = 0; - int Line_Number = 0; - - for (int Tile = 0; Tile <= Tile_Scramble.Length - 1; Tile++) - { - if ((Tile % (Width / 4) == 0) & Tile > 0) - { - if (Line_Accumulator < 1) - { - Line_Accumulator += 1; - Line_Number += 2; - Base_Number = Line_Number; - } - else - { - Line_Accumulator = 0; - Base_Number -= 2; - Line_Number = Base_Number; - } - } - - Tile_Scramble[Tile] = Base_Number; - - if (Base_Accumulator < 1) - { - Base_Accumulator += 1; - Base_Number += 1; - } - else - { - Base_Accumulator = 0; - Base_Number += 3; - } - } - - return Tile_Scramble; - } - internal static Color Modify_Pixel(int R, int G, int B, int X, int Y, int Mod_Block, int Mod_Table) - { - int Index = X * 4 + Y; - int Pixel_Modulation = 0; - int MSB = Mod_Block << 1; - - Pixel_Modulation = Index < 8 - ? Modulation_Table[Mod_Table, ((Mod_Block >> (Index + 24)) & 1) + ((MSB >> (Index + 8)) & 2)] - : Modulation_Table[Mod_Table, ((Mod_Block >> (Index + 8)) & 1) + ((MSB >> (Index - 8)) & 2)]; - - R = Clip(R + Pixel_Modulation); - G = Clip(G + Pixel_Modulation); - B = Clip(B + Pixel_Modulation); - - return Color.FromArgb(B, G, R); - } - internal static byte Clip(int Value) - { - if (Value > 0xff) - return 0xff; - else if (Value < 0) - return 0; - else - return Convert.ToByte(Value & 0xff); - } - internal static int[,] Modulation_Table = - { - { 2, 8, 2, 8 }, - { 5, 17, 5, 17 }, - { 9, 29, 9, 29 }, - { 13, 42, 13, 42 }, - { 18, 60, 18, 60 }, - { 24, 80, 24, 80 }, - { 33, 106, 33, -106 }, - { 47, 183, 47, 183 } - }; - #endregion - } -}