static void PrintSimisAce(int indent, SimisAce ace) { var indentString = new String(' ', 2 * indent); Console.WriteLine("{0}Format: 0x{1:X}", indentString, ace.Format); Console.WriteLine("{0}Width: {1}", indentString, ace.Width); Console.WriteLine("{0}Height: {1}", indentString, ace.Height); Console.WriteLine("{0}Unk4: 0x{1:X}", indentString, ace.Unknown4); Console.WriteLine("{0}Unk6: 0x{1:X}", indentString, ace.Unknown6); Console.WriteLine("{0}Unk7: {1}", indentString, ace.Unknown7); Console.WriteLine("{0}Creator: {1}", indentString, ace.Creator); for (var i = 0; i < 11; i++) { Console.WriteLine("{0}Unk9.{2:X}: {1}", indentString, String.Join(" ", ace.Unknown9.Skip(i * 4).Take(4).Select(b => b.ToString("X2")).ToArray()), i); } Console.WriteLine("{0}Channels: {1}", indentString, String.Join(", ", ace.Channel.Select(c => String.Format("{0} ({1} bits)", c.Type, c.Size)).ToArray())); Console.WriteLine("{0}Images: {1}", indentString, String.Join(", ", ace.Image.Select(i => String.Format("{0}x{1}{2}{3}", i.Width, i.Height, i.ImageColor != null ? " Color" : "", i.ImageMask != null ? " Mask" : "")).ToArray())); }
static void PrintSimisAce(SimisAce ace) { Console.WriteLine("Ace {"); PrintSimisAce(1, ace); Console.WriteLine("}"); }
static void DoConversion(IEnumerable <ConversionFile> files, bool verbose, int threading, bool convertRoundtrip, bool convertTexture, bool convertDXT1, bool convertZLIB) { if (!files.Any()) { throw new OperationCanceledException("Must specify files for conversion."); } SimisProvider provider; try { provider = new SimisProvider(Path.GetDirectoryName(Application.ExecutablePath) + @"\Resources"); } catch (FileException ex) { Console.WriteLine(ex.ToString()); return; } Action <ConversionFile> ConvertFile = (file) => { if (verbose) { lock (files) { if (threading > 1) { Console.WriteLine("[Thread {0}] {1} -> {2}", Thread.CurrentThread.ManagedThreadId, file.Input, file.Output); } else { Console.WriteLine("{0} -> {1}", file.Input, file.Output); } } } try { var inputExt = Path.GetExtension(file.Input).ToUpperInvariant(); var outputExt = Path.GetExtension(file.Output).ToUpperInvariant(); if ((inputExt == ".ACE") && (outputExt == ".ACE")) { // ACE -> ACE var inputAce = new SimisFile(file.Input, provider); var outputAce = new SimisFile(file.Output, true, convertZLIB, inputAce.Ace); outputAce.Write(); } else if (inputExt == ".ACE") { // ACE -> *** var inputAce = new SimisFile(file.Input, provider); var width = convertRoundtrip ? inputAce.Ace.Image.Max(i => i.Width) : inputAce.Ace.Image[0].Width; var height = convertRoundtrip ? inputAce.Ace.Image.Sum(i => i.Height) : inputAce.Ace.Image[0].Height; var outputImage = new Bitmap(width * (convertRoundtrip ? 2 : 1), height, PixelFormat.Format32bppArgb); using (var g = Graphics.FromImage(outputImage)) { g.FillRectangle(Brushes.Transparent, 0, 0, outputImage.Width, outputImage.Height); if (convertRoundtrip) { var y = 0; foreach (var image in inputAce.Ace.Image) { g.DrawImageUnscaled(image.ImageColor, 0, y); g.DrawImageUnscaled(image.ImageMask, width, y); y += image.Height; } } else { g.DrawImageUnscaled(inputAce.Ace.Image[0].GetImage(inputAce.Ace.HasAlpha ? SimisAceImageType.ColorAndAlpha : inputAce.Ace.HasMask ? SimisAceImageType.ColorAndMask : SimisAceImageType.ColorOnly), 0, 0); } } outputImage.Save(file.Output); } else if (outputExt == ".ACE") { // *** -> ACE var inputImage = Image.FromFile(file.Input); var width = inputImage.Width; var height = inputImage.Height; // Roundtripping or not, textures have special requirements of 2^n width and height. if (convertTexture) { if (convertRoundtrip) { var expectedHeight = (int)Math.Pow(2, (int)(Math.Log(height + 1) / Math.Log(2))) - 1; if (height != expectedHeight) { throw new InvalidOperationException(String.Format("Image height {0} is not correct for round-tripping a texture. It must be 2^n-1.", height, expectedHeight)); } height = (height + 1) / 2; // Roundtripping always has two columns: color and mask. width /= 2; } else { var expectedHeight = (int)Math.Pow(2, (int)(Math.Log(height) / Math.Log(2))); if (height != expectedHeight) { throw new InvalidOperationException(String.Format("Image height {0} is not correct for a texture. It must be 2^n.", height, expectedHeight)); } } var expectedWidth = (int)Math.Pow(2, (int)(Math.Log(width) / Math.Log(2))); if (width != expectedWidth) { throw new InvalidOperationException(String.Format("Image width {0} is not correct for a texture. It must be 2^n.", width, expectedWidth)); } if (width != height) { throw new InvalidOperationException(String.Format("Image width {0} and height {1} must be equal for a texture.", width, height)); } } if (convertRoundtrip || convertTexture) { var imageCount = 1 + (int)(convertTexture ? Math.Log(height) / Math.Log(2) : 0); var aceChannels = new[] { new SimisAceChannel(8, SimisAceChannelId.Red), new SimisAceChannel(8, SimisAceChannelId.Green), new SimisAceChannel(8, SimisAceChannelId.Blue), new SimisAceChannel(8, SimisAceChannelId.Alpha), new SimisAceChannel(1, SimisAceChannelId.Mask), }; // Remove the alpha channel for DXT1. if (convertDXT1) { aceChannels = new[] { aceChannels[0], aceChannels[1], aceChannels[2], aceChannels[4] }; } var aceImages = new SimisAceImage[imageCount]; var y = 0; for (var i = 0; i < imageCount; i++) { var scale = (int)Math.Pow(2, i); var colorImage = new Bitmap(width / scale, height / scale, PixelFormat.Format32bppArgb); var maskImage = new Bitmap(width / scale, height / scale, PixelFormat.Format32bppRgb); var sourceRect = convertRoundtrip ? new Rectangle(0, y, width / scale, height / scale) : new Rectangle(0, 0, width, height); using (var g = Graphics.FromImage(colorImage)) { g.DrawImage(inputImage, new Rectangle(Point.Empty, colorImage.Size), sourceRect, GraphicsUnit.Pixel); } sourceRect.X = width; using (var g = Graphics.FromImage(maskImage)) { if (width < inputImage.Width) { g.DrawImage(inputImage, new Rectangle(Point.Empty, maskImage.Size), sourceRect, GraphicsUnit.Pixel); } else { g.FillRectangle(Brushes.White, new Rectangle(Point.Empty, maskImage.Size)); } } aceImages[i] = new SimisAceImage(colorImage, maskImage); y += colorImage.Height; } var ace = new SimisAce((convertDXT1 ? 0x10 : 0x00) + (convertTexture ? 0x05 : 0x00), width, height, convertDXT1 ? 0x12 : 0x00, 0, "Unknown", "JGR Image File", new byte[44], aceChannels, aceImages, new byte[0], new byte[0]); var aceFile = new SimisFile(file.Output, true, convertZLIB, ace); aceFile.Write(); } else { // TODO: Handle the various alpha/mask fun here. var aceChannels = new[] { new SimisAceChannel(8, SimisAceChannelId.Red), new SimisAceChannel(8, SimisAceChannelId.Green), new SimisAceChannel(8, SimisAceChannelId.Blue), new SimisAceChannel(8, SimisAceChannelId.Alpha), }; // Replace the alpha channel with mask channel for DXT1. if (convertDXT1) { aceChannels[3] = new SimisAceChannel(1, SimisAceChannelId.Mask); } var maskImage = new Bitmap(width, height, PixelFormat.Format32bppRgb); using (var g = Graphics.FromImage(maskImage)) { g.FillRectangle(Brushes.White, 0, 0, maskImage.Width, maskImage.Height); } var aceImages = new[] { new SimisAceImage(new Bitmap(inputImage), maskImage), }; var ace = new SimisAce(convertDXT1 ? 0x10 : 0x00, width, height, convertDXT1 ? 0x12 : 0x00, 0, "Unknown", "JGR Image File", new byte[44], aceChannels, aceImages, new byte[0], new byte[0]); var aceFile = new SimisFile(file.Output, true, convertZLIB, ace); aceFile.Write(); } } else { // *** -> *** Image.FromFile(file.Input).Save(file.Output); } } catch (Exception ex) { Console.Error.WriteLine(ex.ToString()); } }; if (threading > 1) { var filesEnumerator = files.GetEnumerator(); var filesFinished = false; var threads = new List <Thread>(threading); for (var i = 0; i < threading; i++) { threads.Add(new Thread(() => { ConversionFile file; while (true) { lock (filesEnumerator) { if (filesFinished || !filesEnumerator.MoveNext()) { filesFinished = true; break; } file = filesEnumerator.Current; } ConvertFile(file); } })); } foreach (var thread in threads) { thread.Start(); } foreach (var thread in threads) { thread.Join(); } } else { foreach (var file in files) { ConvertFile(file); } } }