From 45a4808157290db08b8fc9f01c2d883281ff21be Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 21 Jun 2015 14:10:55 -0700 Subject: [PATCH] Add ETC1 reading from png2bclim/Tharsis Settled for the external dll method. Thank you to Tharsis's creator, xdaniel. --- pk3DS/3DS/BCLIM.cs | 109 +++++++++++++++++++++---- pk3DS/3DS/ETC1.cs | 9 +- pk3DS/Properties/Resources.Designer.cs | 10 +++ pk3DS/Properties/Resources.resx | 3 + pk3DS/Resources/ETC1Lib.dll | Bin 0 -> 17408 bytes pk3DS/pk3DS.csproj | 3 + 6 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 pk3DS/Resources/ETC1Lib.dll diff --git a/pk3DS/3DS/BCLIM.cs b/pk3DS/3DS/BCLIM.cs index 8f79cde..e6605b3 100644 --- a/pk3DS/3DS/BCLIM.cs +++ b/pk3DS/3DS/BCLIM.cs @@ -2,6 +2,8 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; +using System.Runtime.InteropServices; +using pk3DS.Properties; // For other projects, replace this line with the namespace that contains your ETC1Lib.dll resource. namespace CTR { @@ -97,7 +99,7 @@ internal static Image makeBMP(string path, bool autosave = false, bool crop = tr else img = getIMG(bclim); - if (img == null) return img; + if (img == null) return null; Rectangle cropRect = new Rectangle(0, 0, bclim.Width, bclim.Height); Bitmap CropBMP = new Bitmap(cropRect.Width, cropRect.Height); using (Graphics g = Graphics.FromImage(CropBMP)) @@ -235,22 +237,99 @@ internal static Bitmap getIMG_XY7(CLIM bclim) } internal static Bitmap getIMG_ETC(CLIM bclim) { - //int format = bclim.FileFormat; - //if (format != 10 && format != 11) - // return null; + Bitmap img = new Bitmap(Math.Max(nlpo2(bclim.Width), 16), Math.Max(nlpo2(bclim.Height), 16)); + string dllpath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + "\\ETC1Lib.dll"; + if (!File.Exists(dllpath)) File.WriteAllBytes(dllpath, Resources.ETC1Lib); - //byte[] data = ETC1_Decompress(bclim.Data, bclim.Width, bclim.Height, format + 2); - //Bitmap img = new Bitmap(bclim.Width, bclim.Height); + try + { + /* http://jul.rustedlogic.net/thread.php?id=17312 + * Much of this code is taken/modified from Tharsis. Thank you to Tharsis's creator, xdaniel. + * https://github.com/xdanieldzd/Tharsis + */ - //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; + /* Get compressed data & handle to it */ + byte[] textureData = bclim.Data; + //textureData = switchEndianness(textureData, 0x10); + ushort[] input = new ushort[textureData.Length/sizeof (ushort)]; + Buffer.BlockCopy(textureData, 0, input, 0, textureData.Length); + GCHandle pInput = GCHandle.Alloc(input, GCHandleType.Pinned); + + /* Marshal data around, invoke ETC1.dll for conversion, etc */ + UInt32 size1 = 0; + UInt16 w = (ushort)img.Width, h = (ushort)img.Height; + + ETC1.ConvertETC1(IntPtr.Zero, ref size1, IntPtr.Zero, w, h, bclim.FileFormat == 0xB); // true = etc1a4, false = etc1 + // System.Diagnostics.Debug.WriteLine(size1); + uint[] output = new uint[size1]; + GCHandle pOutput = GCHandle.Alloc(output, GCHandleType.Pinned); + ETC1.ConvertETC1(pOutput.AddrOfPinnedObject(), ref size1, pInput.AddrOfPinnedObject(), w, h, bclim.FileFormat == 0xB); + pOutput.Free(); + pInput.Free(); + + + /* Unscramble if needed // could probably be done in ETC1Lib.dll, it's probably pretty ugly, but whatever... */ + /* Non-square code blocks could need some cleanup, verification, etc. as well... */ + uint[] finalized = new uint[output.Length]; + + // Act if it's square because BCLIM swizzling is stupid + Buffer.BlockCopy(output, 0, finalized, 0, finalized.Length); + + byte[] tmp = new byte[finalized.Length]; + Buffer.BlockCopy(finalized, 0, tmp, 0, tmp.Length); + byte[] imgData = tmp; + + for (int i = 0; i < w; i++) + { + for (int j = 0; j < h; j++) + { + int k = (j + i*img.Height)*4; + img.SetPixel(i, j, Color.FromArgb(imgData[k + 3], imgData[k], imgData[k + 1], imgData[k + 2])); + } + } + // Image is 13 instead of 12 + // 24 34 + img.RotateFlip(RotateFlipType.Rotate90FlipX); + if (w > h) + { + // Image is now in appropriate order, but the shifting is messed up. Let's fix that. + Bitmap img2 = new Bitmap(Math.Max(nlpo2(bclim.Width), 16), Math.Max(nlpo2(bclim.Height), 16)); + for (int y = 0; y < Math.Max(nlpo2(bclim.Width), 16); y += 8) + { + for (int x = 0; x < Math.Max(nlpo2(bclim.Height), 16); x++) + { + for (int j = 0; j < 8; j++) + // Treat every 8 vertical pixels as 1 pixel for purposes of calculation, add to offset later. + { + int x1 = (x + ((y/8)*h))%img2.Width; // Reshift x + int y1 = ((x + ((y/8)*h))/img2.Width)*8; // Reshift y + img2.SetPixel(x1, y1 + j, img.GetPixel(x, y + j)); // Reswizzle + } + } + } + img = img2; + } + else if (h > w) + { + Bitmap img2 = new Bitmap(Math.Max(nlpo2(bclim.Width), 16), Math.Max(nlpo2(bclim.Height), 16)); + for (int y = 0; y < Math.Max(nlpo2(bclim.Width), 16); y += 8) + { + for (int x = 0; x < Math.Max(nlpo2(bclim.Height), 16); x++) + { + for (int j = 0; j < 8; j++) + // Treat every 8 vertical pixels as 1 pixel for purposes of calculation, add to offset later. + { + int x1 = x%img2.Width; // Reshift x + int y1 = ((x + ((y/8)*h))/img2.Width)*8; // Reshift y + img2.SetPixel(x1, y1 + j, img.GetPixel(x, y + j)); // Reswizzle + } + } + } + img = img2; + } + } + catch { } + return img; } // BCLIM Data Writing diff --git a/pk3DS/3DS/ETC1.cs b/pk3DS/3DS/ETC1.cs index f269887..d78b93b 100644 --- a/pk3DS/3DS/ETC1.cs +++ b/pk3DS/3DS/ETC1.cs @@ -1,6 +1,11 @@ -namespace CTR +using System; +using System.Runtime.InteropServices; + +namespace CTR { class ETC1 { + [DllImport("ETC1Lib.dll", EntryPoint = "ConvertETC1", CallingConvention = CallingConvention.Cdecl)] + public static extern void ConvertETC1(IntPtr dataOut, ref UInt32 dataOutSize, IntPtr dataIn, UInt16 width, UInt16 height, bool alpha); } -} +} \ No newline at end of file diff --git a/pk3DS/Properties/Resources.Designer.cs b/pk3DS/Properties/Resources.Designer.cs index fd7e2bb..18d273d 100644 --- a/pk3DS/Properties/Resources.Designer.cs +++ b/pk3DS/Properties/Resources.Designer.cs @@ -10470,6 +10470,16 @@ internal class Resources { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] ETC1Lib { + get { + object obj = ResourceManager.GetObject("ETC1Lib", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/pk3DS/Properties/Resources.resx b/pk3DS/Properties/Resources.resx index 0d9c780..fd64127 100644 --- a/pk3DS/Properties/Resources.resx +++ b/pk3DS/Properties/Resources.resx @@ -4519,4 +4519,7 @@ ..\Resources\romdata\txt\3dsgames.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + ..\Resources\ETC1Lib.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/pk3DS/Resources/ETC1Lib.dll b/pk3DS/Resources/ETC1Lib.dll new file mode 100644 index 0000000000000000000000000000000000000000..81bf863d694cad344281d8a5596ddbb73e078e64 GIT binary patch literal 17408 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 + + +