diff --git a/exeinfo/Program.cs b/exeinfo/Program.cs index 82eb7fb..85f4564 100644 --- a/exeinfo/Program.cs +++ b/exeinfo/Program.cs @@ -154,11 +154,12 @@ namespace exeinfo { recognized = true; Console.Write(mzExe.Information); - if(((MZ)mzExe).resourceStream != null || ((MZ)mzExe).ResourceHeader.rsh_vrsn != 0 && + if(((MZ)mzExe).ResourceStream != null || ((MZ)mzExe).ResourceHeader.rsh_vrsn != 0 && ((MZ)mzExe).ResourceHeader.rsh_vrsn != 1 && ((MZ)mzExe).ResourceHeader.rsh_vrsn != 4 && ((MZ)mzExe).ResourceHeader.rsh_vrsn != 5) - PrintGemResources(((MZ)mzExe).ResourceHeader, ((MZ)mzExe).ResourceObjectRoots); + PrintGemResources(((MZ)mzExe).ResourceHeader, ((MZ)mzExe).ResourceObjectRoots, + ((MZ)mzExe).ResourceExtension, ((MZ)mzExe).GemColorIcons); if(mzExe.Strings != null && mzExe.Strings.Any()) { @@ -171,11 +172,12 @@ namespace exeinfo { recognized = true; Console.Write(stExe.Information); - if(((AtariST)stExe).resourceStream != null || ((AtariST)stExe).ResourceHeader.rsh_vrsn != 0 && + if(((AtariST)stExe).ResourceStream != null || ((AtariST)stExe).ResourceHeader.rsh_vrsn != 0 && ((AtariST)stExe).ResourceHeader.rsh_vrsn != 1 && ((AtariST)stExe).ResourceHeader.rsh_vrsn != 4 && ((AtariST)stExe).ResourceHeader.rsh_vrsn != 5) - PrintGemResources(((AtariST)stExe).ResourceHeader, ((AtariST)stExe).ResourceObjectRoots); + PrintGemResources(((AtariST)stExe).ResourceHeader, ((AtariST)stExe).ResourceObjectRoots, + ((AtariST)stExe).ResourceExtension, ((AtariST)stExe).GemColorIcons); if(stExe.Strings != null && stExe.Strings.Any()) { @@ -199,7 +201,9 @@ namespace exeinfo if(!recognized) Console.WriteLine("Executable format not recognized"); } - static void PrintGemResources(GEM.GemResourceHeader resourceHeader, IReadOnlyList roots) + static void PrintGemResources(GEM.GemResourceHeader resourceHeader, IReadOnlyList roots, + GEM.GemResourceExtension resourceExtension, + GEM.ColorIcon[] colorIcons) { Console.WriteLine("\t\tGEM Resources:"); Console.WriteLine("\t\t\t{0} OBJECTs start at {1}", resourceHeader.rsh_nobs, resourceHeader.rsh_object); @@ -215,17 +219,24 @@ namespace exeinfo Console.WriteLine("\t\t\tString data starts at {0}", resourceHeader.rsh_string); Console.WriteLine("\t\t\tImage data starts at {0}", resourceHeader.rsh_imdata); Console.WriteLine("\t\t\tStandard resource data is {0} bytes", resourceHeader.rsh_rssize); + if(resourceHeader.rsh_vrsn >= 4) + { + Console.WriteLine("\t\t\tColor icon table starts at {0}", resourceExtension.color_ic); + Console.WriteLine("\t\t\tThere are {0}more extensions", + resourceExtension.end_extensions == 0 ? "no " : ""); + Console.WriteLine("\t\t\tExtended resource data is {0} bytes", resourceExtension.filesize); + } if(roots == null || roots.Count <= 0) return; for(int i = 0; i < roots.Count; i++) { Console.WriteLine("\t\t\tObject tree {0}:", i); - PrintGemResourceTree(roots[i], 4); + PrintGemResourceTree(roots[i], 4, colorIcons); } } - static void PrintGemResourceTree(GEM.TreeObjectNode node, int level) + static void PrintGemResourceTree(GEM.TreeObjectNode node, int level, GEM.ColorIcon[] colorIcons) { for(int i = 0; i < level; i++) Console.Write("\t"); @@ -235,7 +246,7 @@ namespace exeinfo { case GEM.ObjectTypes.G_BOX: case GEM.ObjectTypes.G_IBOX: - Console.WriteLine("{0} ({1} {2}) {3} border, {4} text, {5} interior, {6} fill, {7} mode, coordinates ({8},{9}) size {10}x{11}", + Console.WriteLine("{0} ({1} {2}) {3} border, {4} text, {5} interior, {6} fill, {7} mode," + " coordinates ({8},{9}) size {10}x{11}", node.type, node.flags, node.state, (GEM.ObjectColors)((node.data & 0xFFFF & GEM.BorderColorMask) >> 12), (GEM.ObjectColors)((node.data & 0xFFFF & GEM.TextColorMask) >> 8), @@ -262,13 +273,13 @@ namespace exeinfo })[0]; Console.WriteLine( - "{0} ({1} {2}) {3} border, {4} text, {5} interior, {6} fill, {7} mode, {8}, '{9}' character, coordinates ({10},{11}) size {12}x{13}", - node.type, node.flags, node.state, - (GEM.ObjectColors)((node.data & 0xFFFF & GEM.BorderColorMask) >> 12), - (GEM.ObjectColors)((node.data & 0xFFFF & GEM.TextColorMask) >> 8), - (GEM.ObjectColors)((node.data & 0xFFFF & GEM.InsideColorMask) >> 8), - (GEM.ObjectFillPattern)((node.data & 0xFFFF & GEM.FillPatternMask) >> 4), - (node.data & 0xFFFF & GEM.TransparentColor) != 0 + "{0} ({1} {2}) {3} border, {4} text, {5} interior, {6} fill, {7} mode, {8}," + + " '{9}' character, coordinates ({10},{11}) size {12}x{13}", node.type, node.flags, + node.state, (GEM.ObjectColors)((node.data & 0xFFFF & GEM.BorderColorMask) >> 12), + (GEM.ObjectColors)((node.data & 0xFFFF & GEM.TextColorMask) >> 8), + (GEM.ObjectColors)((node.data & 0xFFFF & GEM.InsideColorMask) >> 8), + (GEM.ObjectFillPattern)((node.data & 0xFFFF & GEM.FillPatternMask) >> 4), + (node.data & 0xFFFF & GEM.TransparentColor) != 0 ? "transparent" : "replace", thickStr, character, node.x, node.y, node.width, node.height); @@ -292,7 +303,7 @@ namespace exeinfo else thickStr = "no thickness"; - Console.WriteLine("{0} ({1} {2}), coordinates ({3},{4}) size {5}x{6}, font {7}, {8}-justified, {9}," + " {10} border, {11} text, {12} interior, {13} fill, {14} mode, text: \"{15}\"," + " validation: \"{16}\", template: \"{17}\"", + Console.WriteLine("{0} ({1} {2}), coordinates ({3},{4}) size {5}x{6}, font {7}, {8}-justified," + " {9}, {10} border, {11} text, {12} interior, {13} fill, {14} mode," + " text: \"{15}\", validation: \"{16}\", template: \"{17}\"", node.type, node.flags, node.state, node.x, node.y, node.width, node.height, node.TedInfo.Font, node.TedInfo.Justification, thickStr, node.TedInfo.BorderColor, node.TedInfo.TextColor, node.TedInfo.InsideColor, node.TedInfo.Fill, @@ -323,15 +334,42 @@ namespace exeinfo node.IconBlock.TextY, node.IconBlock.TextWidth, node.IconBlock.TextHeight); break; + case GEM.ObjectTypes.G_CICON: + if(colorIcons == null || colorIcons.Length < node.data || + colorIcons[node.data] == null || + colorIcons[node.data].Monochrome == null) + { + Console.WriteLine("{0} ({1} {2}) with index {3} NOT FOUND", node.type, node.flags, node.state, + node.data); + break; + } + + Console.WriteLine( + "{0} ({1} {2}), coordinates ({3},{4}) size {5}x{6}, {7} foreground," + + " {8} background, char '{9}' at ({10},{11}), {12} bytes data, text \"{13}\" at" + + " ({14},{15}) within a box {16}x{17} pixels, with {18} different planes", + node.type, node.flags, node.state, colorIcons[node.data].Monochrome.X, + colorIcons[node.data].Monochrome.Y, colorIcons[node.data].Monochrome.Width, + colorIcons[node.data].Monochrome.Height, + colorIcons[node.data].Monochrome.ForegroundColor, + colorIcons[node.data].Monochrome.BackgroundColor, + colorIcons[node.data].Monochrome.Character, + colorIcons[node.data].Monochrome.CharX, colorIcons[node.data].Monochrome.CharY, + colorIcons[node.data].Monochrome.Data?.Length, + colorIcons[node.data].Monochrome.Text, colorIcons[node.data].Monochrome.TextX, + colorIcons[node.data].Monochrome.TextY, + colorIcons[node.data].Monochrome.TextWidth, + colorIcons[node.data].Monochrome.TextHeight, colorIcons[node.data].Color.Length); + break; default: Console.WriteLine("{0} ({1} {2}) data = {3}, coordinates ({4},{5}) size {6}x{7}", node.type, node.flags, node.state, node.data, node.x, node.y, node.width, node.height); break; } - if(node.child != null) PrintGemResourceTree(node.child, level + 1); + if(node.child != null) PrintGemResourceTree(node.child, level + 1, colorIcons); - if(node.sibling != null) PrintGemResourceTree(node.sibling, level); + if(node.sibling != null) PrintGemResourceTree(node.sibling, level, colorIcons); } } } \ No newline at end of file diff --git a/exeinfogui/GEM/GemColorIcon.cs b/exeinfogui/GEM/GemColorIcon.cs new file mode 100644 index 0000000..ff96b43 --- /dev/null +++ b/exeinfogui/GEM/GemColorIcon.cs @@ -0,0 +1,19 @@ +using Eto.Drawing; + +namespace exeinfogui.GEM +{ + public static class GemColorIcon + { + public static Bitmap GemColorIconToEto(libexeinfo.GEM.ColorIconPlane icon, int width, int height, bool selected) + { + if(selected && icon.SelectedData == null) return null; + + byte[] data = selected ? icon.SelectedData : icon.Data; + byte[] mask = selected ? icon.SelectedMask : icon.Mask; + + int[] pixels = libexeinfo.GEM.PlaneToRaster(data, mask, width, height, icon.Planes); + + return new Bitmap(width, height, PixelFormat.Format32bppRgba, pixels); + } + } +} \ No newline at end of file diff --git a/exeinfogui/GEM/GemIcon.cs b/exeinfogui/GEM/GemIcon.cs new file mode 100644 index 0000000..f4b64ec --- /dev/null +++ b/exeinfogui/GEM/GemIcon.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Eto.Drawing; + +namespace exeinfogui.GEM +{ + public static class GemIcon + { + public static Bitmap GemIconToEto(libexeinfo.GEM.Icon icon) + { + const uint COLOR = 0x00000000; + const uint BACKGROUND = 0x00FFFFFF; + const uint ALPHAMASK = 0xFF000000; + List pixels = new List(); + + byte[] data = libexeinfo.GEM.FlipPlane(icon.Data, icon.Width); + byte[] mask = libexeinfo.GEM.FlipPlane(icon.Mask, icon.Width); + + for(int pos = 0; pos < data.Length; pos++) + { + for(int i = 0; i < 8; i++) + pixels.Add((int)(((data[pos] & (1 << i)) != 0 ? COLOR : BACKGROUND) + + ((mask[pos] & (1 << i)) != 0 ? ALPHAMASK : 0))); + } + + return new Bitmap(icon.Width, icon.Height, PixelFormat.Format32bppRgba, pixels); + } + } +} \ No newline at end of file diff --git a/exeinfogui/GEM/PanelGemColorIcon.xeto b/exeinfogui/GEM/PanelGemColorIcon.xeto new file mode 100644 index 0000000..c4fb8e8 --- /dev/null +++ b/exeinfogui/GEM/PanelGemColorIcon.xeto @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exeinfogui/GEM/PanelGemColorIcon.xeto.cs b/exeinfogui/GEM/PanelGemColorIcon.xeto.cs new file mode 100644 index 0000000..1f25c76 --- /dev/null +++ b/exeinfogui/GEM/PanelGemColorIcon.xeto.cs @@ -0,0 +1,88 @@ +using System; +using Eto.Forms; +using Eto.Serialization.Xaml; + +namespace exeinfogui.GEM +{ + public class PanelGemColorIcon : Panel + { + int iconHeight; + + int iconWidth; + ImageView imgColorIcon; + ImageView imgIcon; + ImageView imgSelectedIcon; + GridView treePlanes; + TextBox txtBgColor; + TextBox txtCharater; + TextBox txtCharCoordinates; + TextBox txtCoordinates; + TextBox txtFgColor; + TextBox txtFlags; + TextBox txtSize; + TextBox txtState; + TextBox txtText; + TextBox txtTextBoxSize; + TextBox txtTextCoordinates; + + public PanelGemColorIcon() + { + XamlReader.Load(this); + + treePlanes.Columns.Add(new GridColumn + { + DataCell = new TextBoxCell + { + Binding = Binding.Property(i => $"{i.Planes}") + }, + HeaderText = "Planes" + }); + treePlanes.AllowMultipleSelection = false; + treePlanes.SelectedItemsChanged += TreePlanesOnSelectedItemsChanged; + } + + void TreePlanesOnSelectedItemsChanged(object sender, EventArgs eventArgs) + { + if(!(treePlanes.SelectedItem is libexeinfo.GEM.ColorIconPlane cicon)) + { + imgColorIcon.Image = null; + imgSelectedIcon.Image = null; + return; + } + + imgColorIcon.Image = GemColorIcon.GemColorIconToEto(cicon, iconWidth, iconHeight, false); + imgSelectedIcon.Image = GemColorIcon.GemColorIconToEto(cicon, iconWidth, iconHeight, true); + } + + public void Update(libexeinfo.GEM.TreeObjectNode node, libexeinfo.GEM.ColorIcon colorIcon) + { + txtFlags.Text = node.flags == 0 ? "None" : node.flags.ToString(); + txtState.Text = node.state == 0 ? "Normal" : node.state.ToString(); + txtCoordinates.Text = $"{colorIcon.Monochrome.X},{colorIcon.Monochrome.Y}"; + txtSize.Text = $"{colorIcon.Monochrome.Width}x{colorIcon.Monochrome.Height} pixels"; + txtCharater.Text = $"{colorIcon.Monochrome.Character}"; + txtCharCoordinates.Text = $"{colorIcon.Monochrome.CharX},{colorIcon.Monochrome.CharY}"; + txtFgColor.Text = $"{colorIcon.Monochrome.ForegroundColor}"; + txtBgColor.Text = $"{colorIcon.Monochrome.BackgroundColor}"; + txtTextCoordinates.Text = $"{colorIcon.Monochrome.TextX},{colorIcon.Monochrome.TextY}"; + txtTextBoxSize.Text = $"{colorIcon.Monochrome.TextWidth}x{colorIcon.Monochrome.TextHeight} pixels"; + txtText.Text = colorIcon.Monochrome.Text; + imgIcon.Image = GemIcon.GemIconToEto(colorIcon.Monochrome); + treePlanes.DataStore = colorIcon.Color; + iconWidth = colorIcon.Monochrome.Width; + iconHeight = colorIcon.Monochrome.Height; + treePlanes.SelectRow(0); + if(colorIcon.Color != null && colorIcon.Color.Length >= 1 && colorIcon.Color[0] != null) + { + imgColorIcon.Image = + GemColorIcon.GemColorIconToEto(colorIcon.Color[0], iconWidth, iconHeight, false); + imgSelectedIcon.Image = GemColorIcon.GemColorIconToEto(colorIcon.Color[0], iconWidth, iconHeight, true); + } + else + { + imgColorIcon.Image = null; + imgSelectedIcon.Image = null; + } + } + } +} \ No newline at end of file diff --git a/exeinfogui/GEM/PanelGemIcon.xeto.cs b/exeinfogui/GEM/PanelGemIcon.xeto.cs index 3d7cdce..b1702b0 100644 --- a/exeinfogui/GEM/PanelGemIcon.xeto.cs +++ b/exeinfogui/GEM/PanelGemIcon.xeto.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using Eto.Drawing; -using Eto.Forms; +using Eto.Forms; using Eto.Serialization.Xaml; namespace exeinfogui.GEM @@ -38,67 +36,7 @@ namespace exeinfogui.GEM txtTextCoordinates.Text = $"{node.IconBlock.TextX},{node.IconBlock.TextY}"; txtTextBoxSize.Text = $"{node.IconBlock.TextWidth}x{node.IconBlock.TextHeight} pixels"; txtText.Text = node.IconBlock.Text; - imgIcon.Image = GemIconToEto(node); - } - - static Bitmap GemIconToEto(libexeinfo.GEM.TreeObjectNode node) - { - const uint COLOR = 0x00000000; - const uint BACKGROUND = 0x00FFFFFF; - const uint ALPHAMASK = 0xFF000000; - List pixels = new List(); - - byte[] data = new byte[node.IconBlock.Data.Length]; - int pos = 0; - int w = node.IconBlock.Width / 8; - // This flips the image. - while(pos < data.Length) - { - for(int i = 0; i < w; i++) - { - byte b = node.IconBlock.Data[pos + i]; - data[pos + i] = (byte)(b >> 7); - data[pos + i] += (byte)((b >> 5) & 0x02); - data[pos + i] += (byte)((b >> 3) & 0x04); - data[pos + i] += (byte)((b >> 1) & 0x08); - data[pos + i] += (byte)((b << 1) & 0x10); - data[pos + i] += (byte)((b << 3) & 0x20); - data[pos + i] += (byte)((b << 5) & 0x40); - data[pos + i] += (byte)(b << 7); - } - - pos += w; - } - - byte[] mask = new byte[node.IconBlock.Mask.Length]; - pos = 0; - // This flips the mask. - while(pos < data.Length) - { - for(int i = 0; i < w; i++) - { - byte b = node.IconBlock.Mask[pos + i]; - mask[pos + i] = (byte)(b >> 7); - mask[pos + i] += (byte)((b >> 5) & 0x02); - mask[pos + i] += (byte)((b >> 3) & 0x04); - mask[pos + i] += (byte)((b >> 1) & 0x08); - mask[pos + i] += (byte)((b << 1) & 0x10); - mask[pos + i] += (byte)((b << 3) & 0x20); - mask[pos + i] += (byte)((b << 5) & 0x40); - mask[pos + i] += (byte)(b << 7); - } - - pos += w; - } - - for(pos = 0; pos < data.Length; pos++) - { - for(int i = 0; i < 8; i++) - pixels.Add((int)(((data[pos] & (1 << i)) != 0 ? COLOR : BACKGROUND) + - ((mask[pos] & (1 << i)) != 0 ? ALPHAMASK : 0))); - } - - return new Bitmap(node.IconBlock.Width, node.IconBlock.Height, PixelFormat.Format32bppRgba, pixels); + imgIcon.Image = GemIcon.GemIconToEto(node.IconBlock); } } } \ No newline at end of file diff --git a/exeinfogui/GEM/TabGemResources.xeto.cs b/exeinfogui/GEM/TabGemResources.xeto.cs index 4fc5a7d..eedf359 100644 --- a/exeinfogui/GEM/TabGemResources.xeto.cs +++ b/exeinfogui/GEM/TabGemResources.xeto.cs @@ -6,15 +6,17 @@ namespace exeinfogui.GEM { public class TabGemResources : TabPage { - PanelGemBox panelBox; - PanelGemGeneric panelGeneric; - PanelGemIcon panelIcon; - PanelGemImage panelImage; - PanelGemString panelString; - PanelGemText panelText; - Panel pnlResource; - TreeGridItemCollection treeData; - TreeGridView treeResources; + libexeinfo.GEM.ColorIcon[] colorIcons; + PanelGemBox panelBox; + PanelGemColorIcon panelColorIcon; + PanelGemGeneric panelGeneric; + PanelGemIcon panelIcon; + PanelGemImage panelImage; + PanelGemString panelString; + PanelGemText panelText; + Panel pnlResource; + TreeGridItemCollection treeData; + TreeGridView treeResources; public TabGemResources() { @@ -25,15 +27,16 @@ namespace exeinfogui.GEM treeResources.AllowMultipleSelection = false; treeResources.SelectionChanged += TreeResourcesOnSelectionChanged; - panelGeneric = new PanelGemGeneric(); - panelString = new PanelGemString(); - panelText = new PanelGemText(); - panelBox = new PanelGemBox(); - panelImage = new PanelGemImage(); - panelIcon = new PanelGemIcon(); + panelGeneric = new PanelGemGeneric(); + panelString = new PanelGemString(); + panelText = new PanelGemText(); + panelBox = new PanelGemBox(); + panelImage = new PanelGemImage(); + panelIcon = new PanelGemIcon(); + panelColorIcon = new PanelGemColorIcon(); } - public void Update(libexeinfo.GEM.TreeObjectNode[] roots) + public void Update(libexeinfo.GEM.TreeObjectNode[] roots, libexeinfo.GEM.ColorIcon[] cicons) { treeData = new TreeGridItemCollection(); @@ -47,6 +50,7 @@ namespace exeinfogui.GEM } treeResources.DataStore = treeData; + colorIcons = cicons; } void TreeResourcesOnSelectionChanged(object sender, EventArgs eventArgs) @@ -86,8 +90,14 @@ namespace exeinfogui.GEM panelIcon.Update(node); pnlResource.Content = panelIcon; break; - /* case libexeinfo.GEM.ObjectTypes.G_USERDEF: break; - case libexeinfo.GEM.ObjectTypes.G_CICON: break;*/ + /* case libexeinfo.GEM.ObjectTypes.G_USERDEF: break;*/ + case libexeinfo.GEM.ObjectTypes.G_CICON: + if(colorIcons == null || node.data >= colorIcons.Length || colorIcons[node.data] == null) + goto default; + + panelColorIcon.Update(node, colorIcons[node.data]); + pnlResource.Content = panelColorIcon; + break; default: panelGeneric.Update(node); pnlResource.Content = panelGeneric; diff --git a/exeinfogui/MainForm.xeto.cs b/exeinfogui/MainForm.xeto.cs index 5ab55eb..01a75aa 100644 --- a/exeinfogui/MainForm.xeto.cs +++ b/exeinfogui/MainForm.xeto.cs @@ -86,7 +86,7 @@ namespace exeinfogui recognizedExe = mzExe; if(((MZ)mzExe).ResourceObjectRoots != null && ((MZ)mzExe).ResourceObjectRoots.Any()) { - tabGemResources.Update(((MZ)mzExe).ResourceObjectRoots); + tabGemResources.Update(((MZ)mzExe).ResourceObjectRoots, ((MZ)mzExe).GemColorIcons); tabGemResources.Visible = true; } } @@ -101,7 +101,7 @@ namespace exeinfogui recognizedExe = stExe; if(((AtariST)stExe).ResourceObjectRoots != null && ((AtariST)stExe).ResourceObjectRoots.Any()) { - tabGemResources.Update(((AtariST)stExe).ResourceObjectRoots); + tabGemResources.Update(((AtariST)stExe).ResourceObjectRoots, ((AtariST)stExe).GemColorIcons); tabGemResources.Visible = true; } } diff --git a/libexeinfo/AtariST/AtariST.cs b/libexeinfo/AtariST/AtariST.cs index a8323cc..2aeaa2d 100644 --- a/libexeinfo/AtariST/AtariST.cs +++ b/libexeinfo/AtariST/AtariST.cs @@ -38,9 +38,11 @@ namespace libexeinfo /// public partial class AtariST : IExecutable { - public GEM.GemResourceHeader ResourceHeader; - public GEM.TreeObjectNode[] ResourceObjectRoots; - public Stream resourceStream; + public GEM.ColorIcon[] GemColorIcons; + public GEM.GemResourceExtension ResourceExtension; + public GEM.GemResourceHeader ResourceHeader; + public GEM.TreeObjectNode[] ResourceObjectRoots; + public Stream ResourceStream; /// /// Initializes a new instance of the class. @@ -72,7 +74,7 @@ namespace libexeinfo resourceFilePath = testPath + ".RSC"; if(resourceFilePath != null) - resourceStream = File.Open(resourceFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + ResourceStream = File.Open(resourceFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); Initialize(); } @@ -126,27 +128,38 @@ namespace libexeinfo Type = "Atari ST executable"; - if(resourceStream == null) return; + if(ResourceStream == null) return; buffer = new byte[Marshal.SizeOf(typeof(GEM.GemResourceHeader))]; - resourceStream.Position = 0; - resourceStream.Read(buffer, 0, buffer.Length); + ResourceStream.Position = 0; + ResourceStream.Read(buffer, 0, buffer.Length); ResourceHeader = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); if(ResourceHeader.rsh_vrsn != 0 && ResourceHeader.rsh_vrsn != 1 && ResourceHeader.rsh_vrsn != 4 && ResourceHeader.rsh_vrsn != 5) return; + if((ResourceHeader.rsh_vrsn & 4) == 4) + { + buffer = new byte[Marshal.SizeOf(typeof(GEM.GemResourceExtension))]; + ResourceStream.Position = ResourceHeader.rsh_rssize; + ResourceStream.Read(buffer, 0, buffer.Length); + ResourceExtension = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); + + GemColorIcons = GEM.GetColorIcons(ResourceStream, ResourceExtension.color_ic, true, + Encoding.AtariSTEncoding); + } + List strings = new List(); if(ResourceHeader.rsh_ntree > 0) { - resourceStream.Position = ResourceHeader.rsh_trindex; + ResourceStream.Position = ResourceHeader.rsh_trindex; int[] treeOffsets = new int[ResourceHeader.rsh_ntree]; byte[] tmp = new byte[4]; for(int i = 0; i < ResourceHeader.rsh_ntree; i++) { - resourceStream.Read(tmp, 0, 4); + ResourceStream.Read(tmp, 0, 4); treeOffsets[i] = BitConverter.ToInt32(tmp.Reverse().ToArray(), 0); } @@ -154,15 +167,15 @@ namespace libexeinfo for(int i = 0; i < ResourceHeader.rsh_ntree; i++) { - if(treeOffsets[i] <= 0 || treeOffsets[i] >= resourceStream.Length) continue; + if(treeOffsets[i] <= 0 || treeOffsets[i] >= ResourceStream.Length) continue; - resourceStream.Position = treeOffsets[i]; + ResourceStream.Position = treeOffsets[i]; List nodes = new List(); while(true) { buffer = new byte[Marshal.SizeOf(typeof(GEM.ObjectNode))]; - resourceStream.Read(buffer, 0, buffer.Length); + ResourceStream.Read(buffer, 0, buffer.Length); GEM.ObjectNode node = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); nodes.Add(node); if(((GEM.ObjectFlags)node.ob_flags).HasFlag(GEM.ObjectFlags.Lastob)) break; @@ -170,7 +183,7 @@ namespace libexeinfo List knownNodes = new List(); ResourceObjectRoots[i] = - GEM.ProcessResourceObject(nodes, ref knownNodes, 0, resourceStream, strings, true, + GEM.ProcessResourceObject(nodes, ref knownNodes, 0, ResourceStream, strings, true, Encoding.AtariSTEncoding); } } @@ -178,18 +191,18 @@ namespace libexeinfo { GEM.ObjectNode[] nodes = new GEM.ObjectNode[ResourceHeader.rsh_nobs]; - resourceStream.Position = ResourceHeader.rsh_object; + ResourceStream.Position = ResourceHeader.rsh_object; for(short i = 0; i < ResourceHeader.rsh_nobs; i++) { buffer = new byte[Marshal.SizeOf(typeof(GEM.ObjectNode))]; - resourceStream.Read(buffer, 0, buffer.Length); + ResourceStream.Read(buffer, 0, buffer.Length); nodes[i] = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); } List knownNodes = new List(); ResourceObjectRoots = new GEM.TreeObjectNode[1]; ResourceObjectRoots[0] = - GEM.ProcessResourceObject(nodes, ref knownNodes, 0, resourceStream, strings, true, + GEM.ProcessResourceObject(nodes, ref knownNodes, 0, ResourceStream, strings, true, Encoding.AtariSTEncoding); } diff --git a/libexeinfo/GEM/Resources.cs b/libexeinfo/GEM/Resources.cs index 15a9ada..9957c7f 100644 --- a/libexeinfo/GEM/Resources.cs +++ b/libexeinfo/GEM/Resources.cs @@ -24,8 +24,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -40,7 +42,7 @@ namespace libexeinfo { TreeObjectNode node = new TreeObjectNode { - type = (ObjectTypes)nodes[nodeNumber].ob_type, + type = (ObjectTypes)(nodes[nodeNumber].ob_type & 0xff), flags = (ObjectFlags)nodes[nodeNumber].ob_flags, state = (ObjectStates)nodes[nodeNumber].ob_state, data = nodes[nodeNumber].ob_spec, @@ -50,8 +52,7 @@ namespace libexeinfo height = nodes[nodeNumber].ob_height }; - byte[] buffer; - List chars; + byte[] buffer; switch(node.type) { case ObjectTypes.G_TEXT: @@ -110,7 +111,6 @@ namespace libexeinfo } break; - // TODO: This is indeed a CUT from a bigger image, need to cut it out case ObjectTypes.G_IMAGE: if(node.data <= 0 || node.data >= resourceStream.Length) break; @@ -166,87 +166,14 @@ namespace libexeinfo resourceStream.Position = node.data; buffer = new byte[Marshal.SizeOf(typeof(IconBlock))]; resourceStream.Read(buffer, 0, buffer.Length); - IconBlock iconBlock = bigEndian - ? BigEndianMarshal.ByteArrayToStructureBigEndian(buffer) - : BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); - node.IconBlock = new Icon - { - Width = iconBlock.ib_wicon, - Height = iconBlock.ib_hicon, - X = iconBlock.ib_xicon, - Y = iconBlock.ib_yicon, - ForegroundColor = (ObjectColors)((iconBlock.ib_char >> 12) & 0x000F), - BackgroundColor = (ObjectColors)((iconBlock.ib_char >> 8) & 0x000F), - Character = encoding.GetString(new[] {(byte)(iconBlock.ib_char & 0xFF)})[0], - CharX = iconBlock.ib_xchar, - CharY = iconBlock.ib_ychar, - TextX = iconBlock.ib_xtext, - TextY = iconBlock.ib_ytext, - TextWidth = iconBlock.ib_wtext, - TextHeight = iconBlock.ib_htext - }; + node.IconBlock = GetIconBlock(resourceStream, buffer, bigEndian, encoding); - if(iconBlock.ib_ptext > 0 && iconBlock.ib_ptext < resourceStream.Length) - { - resourceStream.Position = iconBlock.ib_ptext; - chars = new List(); - while(true) - { - int character = resourceStream.ReadByte(); - - if(character <= 0) break; - - chars.Add((byte)character); - } - - node.IconBlock.Text = StringHandlers.CToString(chars.ToArray(), encoding); - if(!string.IsNullOrWhiteSpace(node.IconBlock.Text)) strings.Add(node.IconBlock.Text.Trim()); - } - - if(iconBlock.ib_pdata > 0 && iconBlock.ib_pdata < resourceStream.Length) - { - resourceStream.Position = iconBlock.ib_pdata; - node.IconBlock.Data = new byte[node.IconBlock.Width * node.IconBlock.Height / 8]; - resourceStream.Read(node.IconBlock.Data, 0, node.IconBlock.Data.Length); - - // Because the image is stored as words, they get reversed on PC GEM (Little-endian) - if(!bigEndian) - { - byte[] data = new byte[node.IconBlock.Data.Length]; - for(int i = 0; i < data.Length; i += 2) - { - data[i] = node.IconBlock.Data[i + 1]; - data[i + 1] = node.IconBlock.Data[i]; - } - - node.IconBlock.Data = data; - } - } - - if(iconBlock.ib_pmask > 0 && iconBlock.ib_pmask < resourceStream.Length) - { - resourceStream.Position = iconBlock.ib_pmask; - node.IconBlock.Mask = new byte[node.IconBlock.Width * node.IconBlock.Height / 8]; - resourceStream.Read(node.IconBlock.Mask, 0, node.IconBlock.Mask.Length); - - // Because the mask is stored as words, they get reversed on PC GEM (Little-endian) - if(!bigEndian) - { - byte[] mask = new byte[node.IconBlock.Mask.Length]; - for(int i = 0; i < mask.Length; i += 2) - { - mask[i] = node.IconBlock.Mask[i + 1]; - mask[i + 1] = node.IconBlock.Mask[i]; - } - - node.IconBlock.Mask = mask; - } - } + if(!string.IsNullOrWhiteSpace(node.IconBlock.Text)) strings.Add(node.IconBlock.Text.Trim()); break; case ObjectTypes.G_CICON: - //Console.WriteLine("ColorIconBlock pointer {0}", node.data); + // Do nothing, it is done separately... break; case ObjectTypes.G_BUTTON: case ObjectTypes.G_STRING: @@ -254,7 +181,7 @@ namespace libexeinfo if(node.data <= 0 || node.data >= resourceStream.Length) break; resourceStream.Position = node.data; - chars = new List(); + List chars = new List(); while(true) { int character = resourceStream.ReadByte(); @@ -281,5 +208,289 @@ namespace libexeinfo return node; } + + static Icon GetIconBlock(Stream resourceStream, byte[] buffer, bool bigEndian, Encoding encoding) + { + long oldPosition = resourceStream.Position; + + IconBlock iconBlock = bigEndian + ? BigEndianMarshal.ByteArrayToStructureBigEndian(buffer) + : BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + + Icon icon = new Icon + { + Width = iconBlock.ib_wicon, + Height = iconBlock.ib_hicon, + X = iconBlock.ib_xicon, + Y = iconBlock.ib_yicon, + ForegroundColor = (ObjectColors)((iconBlock.ib_char >> 12) & 0x000F), + BackgroundColor = (ObjectColors)((iconBlock.ib_char >> 8) & 0x000F), + Character = encoding.GetString(new[] {(byte)(iconBlock.ib_char & 0xFF)})[0], + CharX = iconBlock.ib_xchar, + CharY = iconBlock.ib_ychar, + TextX = iconBlock.ib_xtext, + TextY = iconBlock.ib_ytext, + TextWidth = iconBlock.ib_wtext, + TextHeight = iconBlock.ib_htext + }; + + if(iconBlock.ib_ptext > 0 && iconBlock.ib_ptext < resourceStream.Length) + { + resourceStream.Position = iconBlock.ib_ptext; + List chars = new List(); + while(true) + { + int character = resourceStream.ReadByte(); + + if(character <= 0) break; + + chars.Add((byte)character); + } + + icon.Text = StringHandlers.CToString(chars.ToArray(), encoding); + } + + if(iconBlock.ib_pdata > 0 && iconBlock.ib_pdata < resourceStream.Length) + { + resourceStream.Position = iconBlock.ib_pdata; + icon.Data = new byte[icon.Width * icon.Height / 8]; + resourceStream.Read(icon.Data, 0, icon.Data.Length); + + // Because the image is stored as words, they get reversed on PC GEM (Little-endian) + if(!bigEndian) + { + byte[] data = new byte[icon.Data.Length]; + for(int i = 0; i < data.Length; i += 2) + { + data[i] = icon.Data[i + 1]; + data[i + 1] = icon.Data[i]; + } + + icon.Data = data; + } + } + + if(iconBlock.ib_pmask > 0 && iconBlock.ib_pmask < resourceStream.Length) + { + resourceStream.Position = iconBlock.ib_pmask; + icon.Mask = new byte[icon.Width * icon.Height / 8]; + resourceStream.Read(icon.Mask, 0, icon.Mask.Length); + + // Because the mask is stored as words, they get reversed on PC GEM (Little-endian) + if(!bigEndian) + { + byte[] mask = new byte[icon.Mask.Length]; + for(int i = 0; i < mask.Length; i += 2) + { + mask[i] = icon.Mask[i + 1]; + mask[i + 1] = icon.Mask[i]; + } + + icon.Mask = mask; + } + } + + resourceStream.Position = oldPosition; + return icon; + } + + public static ColorIcon[] GetColorIcons(Stream resourceStream, int colorIc, bool bigEndian, Encoding encoding) + { + byte[] buffer; + + if(colorIc == -1 || colorIc >= resourceStream.Length) return null; + + resourceStream.Position = colorIc; + + int cicons = 0; + + while(true) + { + buffer = new byte[4]; + resourceStream.Read(buffer, 0, buffer.Length); + + if(BitConverter.ToInt32(buffer, 0) == -1) break; + + cicons++; + } + + ColorIcon[] colorIcons = new ColorIcon[cicons]; + + for(int i = 0; i < cicons; i++) + { + buffer = new byte[Marshal.SizeOf(typeof(IconBlock))]; + resourceStream.Read(buffer, 0, buffer.Length); + IconBlock iconBlock = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); + int isize = iconBlock.ib_wicon * iconBlock.ib_hicon / 8; + + buffer = new byte[4]; + resourceStream.Position -= 2; + resourceStream.Read(buffer, 0, buffer.Length); + int numRez = BitConverter.ToInt32(buffer.Reverse().ToArray(), 0); + + colorIcons[i] = new ColorIcon + { + Color = new ColorIconPlane[numRez], + Monochrome = new Icon + { + Width = iconBlock.ib_wicon, + Height = iconBlock.ib_hicon, + X = iconBlock.ib_xicon, + Y = iconBlock.ib_yicon, + ForegroundColor = (ObjectColors)((iconBlock.ib_char >> 12) & 0x000F), + BackgroundColor = (ObjectColors)((iconBlock.ib_char >> 8) & 0x000F), + Character = encoding.GetString(new[] {(byte)(iconBlock.ib_char & 0xFF)})[0], + CharX = iconBlock.ib_xchar, + CharY = iconBlock.ib_ychar, + TextX = iconBlock.ib_xtext, + TextY = iconBlock.ib_ytext, + TextWidth = iconBlock.ib_wtext, + TextHeight = iconBlock.ib_htext, + Data = new byte[isize], + Mask = new byte[isize] + } + }; + + resourceStream.Read(colorIcons[i].Monochrome.Data, 0, isize); + + // Because the image is stored as words, they get reversed on PC GEM (Little-endian) + if(!bigEndian) + { + byte[] data = new byte[colorIcons[i].Monochrome.Data.Length]; + for(int d = 0; d < data.Length; d += 2) + { + data[d] = colorIcons[d].Monochrome.Data[d + 1]; + data[d + 1] = colorIcons[d].Monochrome.Data[d]; + } + + colorIcons[i].Monochrome.Data = data; + } + + resourceStream.Read(colorIcons[i].Monochrome.Mask, 0, isize); + + // Because the mask is stored as words, they get reversed on PC GEM (Little-endian) + if(!bigEndian) + { + byte[] mask = new byte[colorIcons[i].Monochrome.Mask.Length]; + for(int m = 0; m < mask.Length; m += 2) + { + mask[m] = colorIcons[m].Monochrome.Mask[m + 1]; + mask[m + 1] = colorIcons[m].Monochrome.Mask[m]; + } + + colorIcons[i].Monochrome.Mask = mask; + } + + if(iconBlock.ib_ptext > 0 && iconBlock.ib_ptext < resourceStream.Length) + { + long oldPosition = resourceStream.Position; + resourceStream.Position = iconBlock.ib_ptext; + List chars = new List(); + while(true) + { + int character = resourceStream.ReadByte(); + + if(character <= 0) break; + + chars.Add((byte)character); + } + + colorIcons[i].Monochrome.Text = StringHandlers.CToString(chars.ToArray(), encoding); + resourceStream.Position = oldPosition + 12; + } + else + { + byte[] ptext = new byte[12]; + resourceStream.Read(ptext, 0, 12); + colorIcons[i].Monochrome.Text = StringHandlers.CToString(ptext, encoding); + } + + colorIcons[i].Color = new ColorIconPlane[numRez]; + + for(int r = 0; r < numRez; r++) + { + byte[] data; + byte[] mask; + + buffer = new byte[Marshal.SizeOf(typeof(ColorIconBlock))]; + resourceStream.Read(buffer, 0, buffer.Length); + ColorIconBlock cib = BigEndianMarshal.ByteArrayToStructureBigEndian(buffer); + + colorIcons[i].Color[r] = new ColorIconPlane + { + Planes = cib.num_planes, + Data = new byte[isize * cib.num_planes], + Mask = new byte[isize] + }; + + resourceStream.Read(colorIcons[i].Color[r].Data, 0, isize * cib.num_planes); + + // Because the image is stored as words, they get reversed on PC GEM (Little-endian) + if(!bigEndian) + { + data = new byte[colorIcons[i].Color[r].Data.Length]; + for(int d = 0; d < data.Length; d += 2) + { + data[d] = colorIcons[d].Color[r].Data[d + 1]; + data[d + 1] = colorIcons[d].Color[r].Data[d]; + } + + colorIcons[i].Color[r].Data = data; + } + + resourceStream.Read(colorIcons[i].Color[r].Mask, 0, isize); + + // Because the mask is stored as words, they get reversed on PC GEM (Little-endian) + if(!bigEndian) + { + mask = new byte[colorIcons[i].Color[r].Mask.Length]; + for(int m = 0; m < mask.Length; m += 2) + { + mask[m] = colorIcons[m].Color[r].Mask[m + 1]; + mask[m + 1] = colorIcons[m].Color[r].Mask[m]; + } + + colorIcons[i].Color[r].Mask = mask; + } + + if(cib.sel_data == 0) continue; + + colorIcons[i].Color[r].SelectedData = new byte[isize * cib.num_planes]; + colorIcons[i].Color[r].SelectedMask = new byte[isize]; + + resourceStream.Read(colorIcons[i].Color[r].SelectedData, 0, isize * cib.num_planes); + + // Because the image is stored as words, they get reversed on PC GEM (Little-endian) + if(!bigEndian) + { + data = new byte[colorIcons[i].Color[r].SelectedData.Length]; + for(int d = 0; d < data.Length; d += 2) + { + data[d] = colorIcons[d].Color[r].SelectedData[d + 1]; + data[d + 1] = + colorIcons[d].Color[r].SelectedData[d]; + } + + colorIcons[i].Color[r].SelectedData = data; + } + + resourceStream.Read(colorIcons[i].Color[r].SelectedMask, 0, isize); + + // Because the mask is stored as words, they get reversed on PC GEM (Little-endian) + if(bigEndian) continue; + + mask = new byte[colorIcons[i].Color[r].SelectedMask.Length]; + for(int m = 0; m < mask.Length; m += 2) + { + mask[m] = colorIcons[m].Color[r].SelectedMask[m + 1]; + mask[m + 1] = colorIcons[m].Color[r].SelectedMask[m]; + } + + colorIcons[i].Color[r].SelectedMask = mask; + } + } + + return colorIcons; + } } } \ No newline at end of file diff --git a/libexeinfo/GEM/Structs.cs b/libexeinfo/GEM/Structs.cs index 9765ee9..bb63b2e 100644 --- a/libexeinfo/GEM/Structs.cs +++ b/libexeinfo/GEM/Structs.cs @@ -167,6 +167,7 @@ namespace libexeinfo { public BitmapBlock BitBlock; public TreeObjectNode child; + public ColorIcon ColorIcon; public int data; public ObjectFlags flags; public short height; @@ -228,6 +229,21 @@ namespace libexeinfo public short Y; } + public class ColorIconPlane + { + public byte[] Data; + public byte[] Mask; + public short Planes; + public byte[] SelectedData; + public byte[] SelectedMask; + } + + public class ColorIcon + { + public ColorIconPlane[] Color; + public Icon Monochrome; + } + /// /// The TEDINFO structure lets a user edit formatted text. The object types G_TEXT, G_BOXTEXT, G_FTEXT and G_FBOXTEXT /// use their to point to TEDINFO structures. @@ -476,7 +492,7 @@ namespace libexeinfo } [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct ColorIcon + public struct ColorIconBlock { /// /// Number of planes in the following data @@ -503,18 +519,5 @@ namespace libexeinfo /// public int next_res; } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct ColorIconBlock - { - /// - /// Default monochrome icon - /// - IconBlock monoblk; - /// - /// List of color icons for diferent resolutions - /// - ColorIcon[] mainlist; - } } } \ No newline at end of file diff --git a/libexeinfo/GEM/Vdi.cs b/libexeinfo/GEM/Vdi.cs new file mode 100644 index 0000000..a7464e7 --- /dev/null +++ b/libexeinfo/GEM/Vdi.cs @@ -0,0 +1,159 @@ +using System; + +namespace libexeinfo +{ + public static partial class GEM + { + /// Palette for monochrome + public static int[] Palette1 = {0x00FFFFFF, 0x00000000}; + + /// Palette for 2 planes (4 colors) + public static int[] Palette2 = {0x00FFFFFF, 0x00FF0000, 0x0000FF00, 0x00000000}; + + /// Palette for 3 planes (8 colors) + public static int[] Palette3 = + {0x00FFFFFF, 0x00FF0000, 0x0000FF00, 0x00FFFF00, 0x000000FF, 0x00FF00FF, 0x0000FFFF, 0x00000000}; + + /// Palette for 4 planes (16 colors) + public static int[] Palette4 = + { + // Atari TT palette + 0x00FFFFFF, 0x00FF0000, 0x0000FF00, 0x00FFFF00, 0x000000FF, 0x00FF00FF, 0x0000FFFF, 0x00AAAAAA, 0x00666666, + 0x00FF9999, 0x0099FF99, 0x00FFFF99, 0x009999FF, 0x00FF99FF, 0x0099FFFF, 0x00000000 + // Atari ST palette + // 0x00FFFFFF, 0x00FF0000, 0x0000FF00, 0x00FFFF00, 0x000000FF, 0x00FF00FF, 0x0000FFFF, 0x00555555, + // 0x00333333, 0x00FF3333, 0x0033FF33, 0x00FFFF33, 0x003333FF, 0x00FF33FF, 0x0033FFFF, 0x00000000 + }; + + /// Palette for 8 planes (256 colors), from Atari TT + public static int[] Palette8 = + { + 0x00FFFFFF, 0x00FF0000, 0x0000FF00, 0x00FFFF00, 0x000000FF, 0x00FF00FF, 0x0000FFFF, 0x00AAAAAA, 0x00666666, + 0x00FF9999, 0x0099FF99, 0x00FFFF99, 0x009999FF, 0x00FF99FF, 0x0099FFFF, 0x00000000, 0x00FFFFFF, 0x00EEEEEE, + 0x00DDDDDD, 0x00CCCCCC, 0x00BBBBBB, 0x00AAAAAA, 0x00999999, 0x00888888, 0x00777777, 0x00666666, 0x00555555, + 0x00444444, 0x00333333, 0x00222222, 0x00111111, 0x00000000, 0x00FF0000, 0x00FF0011, 0x00FF0022, 0x00FF0033, + 0x00FF0044, 0x00FF0055, 0x00FF0066, 0x00FF0077, 0x00FF0088, 0x00FF0099, 0x00FF00AA, 0x00FF00BB, 0x00FF00CC, + 0x00FF00DD, 0x00FF00EE, 0x00FF00FF, 0x00EE00FF, 0x00DD00FF, 0x00CC00FF, 0x00BB00FF, 0x00AA00FF, 0x009900FF, + 0x008800FF, 0x007700FF, 0x006600FF, 0x005500FF, 0x004400FF, 0x003300FF, 0x002200FF, 0x001100FF, 0x000000FF, + 0x000011FF, 0x000022FF, 0x000033FF, 0x000044FF, 0x000055FF, 0x000066FF, 0x000077FF, 0x000088FF, 0x000099FF, + 0x0000AAFF, 0x0000BBFF, 0x0000CCFF, 0x0000DDFF, 0x0000EEFF, 0x0000FFFF, 0x0000FFEE, 0x0000FFDD, 0x0000FFCC, + 0x0000FFBB, 0x0000FFAA, 0x0000FF99, 0x0000FF88, 0x0000FF77, 0x0000FF66, 0x0000FF55, 0x0000FF44, 0x0000FF33, + 0x0000FF22, 0x0000FF11, 0x0000FF00, 0x0011FF00, 0x0022FF00, 0x0033FF00, 0x0044FF00, 0x0055FF00, 0x0066FF00, + 0x0077FF00, 0x0088FF00, 0x0099FF00, 0x00AAFF00, 0x00BBFF00, 0x00CCFF00, 0x00DDFF00, 0x00EEFF00, 0x00FFFF00, + 0x00FFEE00, 0x00FFDD00, 0x00FFCC00, 0x00FFBB00, 0x00FFAA00, 0x00FF9900, 0x00FF8800, 0x00FF7700, 0x00FF6600, + 0x00FF5500, 0x00FF4400, 0x00FF3300, 0x00FF2200, 0x00FF1100, 0x00BB0000, 0x00BB0011, 0x00BB0022, 0x00BB0033, + 0x00BB0044, 0x00BB0055, 0x00BB0066, 0x00BB0077, 0x00BB0088, 0x00BB0099, 0x00BB00AA, 0x00BB00BB, 0x00AA00BB, + 0x009900BB, 0x008800BB, 0x007700BB, 0x006600BB, 0x005500BB, 0x004400BB, 0x003300BB, 0x002200BB, 0x001100BB, + 0x000000BB, 0x000011BB, 0x000022BB, 0x000033BB, 0x000044BB, 0x000055BB, 0x000066BB, 0x000077BB, 0x000088BB, + 0x000099BB, 0x0000AABB, 0x0000BBBB, 0x0000BBAA, 0x0000BB99, 0x0000BB88, 0x0000BB77, 0x0000BB66, 0x0000BB55, + 0x0000BB44, 0x0000BB33, 0x0000BB22, 0x0000BB11, 0x0000BB00, 0x0011BB00, 0x0022BB00, 0x0033BB00, 0x0044BB00, + 0x0055BB00, 0x0066BB00, 0x0077BB00, 0x0088BB00, 0x0099BB00, 0x00AABB00, 0x00BBBB00, 0x00BBAA00, 0x00BB9900, + 0x00BB8800, 0x00BB7700, 0x00BB6600, 0x00BB5500, 0x00BB4400, 0x00BB3300, 0x00BB2200, 0x00BB1100, 0x00770000, + 0x00770011, 0x00770022, 0x00770033, 0x00770044, 0x00770055, 0x00770066, 0x00770077, 0x00660077, 0x00550077, + 0x00440077, 0x00330077, 0x00220077, 0x00110077, 0x00000077, 0x00001177, 0x00002277, 0x00003377, 0x00004477, + 0x00005577, 0x00006677, 0x00007777, 0x00007766, 0x00007755, 0x00007744, 0x00007733, 0x00007722, 0x00007711, + 0x00007700, 0x00117700, 0x00227700, 0x00337700, 0x00447700, 0x00557700, 0x00667700, 0x00777700, 0x00776600, + 0x00775500, 0x00774400, 0x00773300, 0x00772200, 0x00771100, 0x00440000, 0x00440011, 0x00440022, 0x00440033, + 0x00440044, 0x00330044, 0x00220044, 0x00110044, 0x00000044, 0x00001144, 0x00002244, 0x00003344, 0x00004444, + 0x00004433, 0x00004422, 0x00004411, 0x00004400, 0x00114400, 0x00224400, 0x00334400, 0x00444400, 0x00443300, + 0x00442200, 0x00441100, 0x00FFFFFF, 0x00000000 + }; + + /// Palette for alpha mask + public static int[] PaletteMask = {0x00000000, -16777216}; + + public static byte[] FlipPlane(byte[] data, int width) + { + byte[] flipped = new byte[data.Length]; + int pos = 0; + int w = width / 8; + + // This flips the image. + while(pos < flipped.Length) + { + for(int i = 0; i < w; i++) + { + byte b = data[pos + i]; + flipped[pos + i] = (byte)(b >> 7); + flipped[pos + i] += (byte)((b >> 5) & 0x02); + flipped[pos + i] += (byte)((b >> 3) & 0x04); + flipped[pos + i] += (byte)((b >> 1) & 0x08); + flipped[pos + i] += (byte)((b << 1) & 0x10); + flipped[pos + i] += (byte)((b << 3) & 0x20); + flipped[pos + i] += (byte)((b << 5) & 0x40); + flipped[pos + i] += (byte)(b << 7); + } + + pos += w; + } + + return flipped; + } + + public static int[] PlaneToRaster(byte[] data, byte[] mask, int width, int height, int planes) + { + int[] pixels = PlaneToRaster(data, width, height, planes); + int[] masked = PlaneToRasterIndexed(mask, width, height, 1); + + for(int i = 0; i < pixels.Length; i++) pixels[i] += PaletteMask[masked[i]]; + + return pixels; + } + + public static int[] PlaneToRaster(byte[] data, int width, int height, int planes) + { + int[] pixels = PlaneToRasterIndexed(data, width, height, planes); + int[] palette = null; + + switch(planes) + { + case 1: + palette = Palette1; + break; + case 2: + palette = Palette2; + break; + case 3: + palette = Palette3; + break; + case 4: + palette = Palette4; + break; + case 8: + palette = Palette8; + break; + // What to do with other pixel formats? + default: return null; + } + + for(int i = 0; i < pixels.Length; i++) pixels[i] = palette[pixels[i]]; + + return pixels; + } + + public static int[] PlaneToRasterIndexed(byte[] data, int width, int height, int planes) + { + // No more than 24-bit RGB + if(planes > 24) return null; + + int planeSize = width * height / 8; + + int[] pixels = new int[width * height]; + for(int p = 0; p < planes; p++) + { + int pixNum = 0; + + byte[] plane = new byte[planeSize]; + Array.Copy(data, p * planeSize, plane, 0, planeSize); + plane = FlipPlane(plane, width); + + for(int b = 0; b < planeSize; b++) + { + for(int i = 0; i < 8; i++) pixels[pixNum++] |= ((plane[b] & (1 << i)) >> i) << p; + } + } + + return pixels; + } + } +} \ No newline at end of file diff --git a/libexeinfo/MZ/MZ.cs b/libexeinfo/MZ/MZ.cs index f5b29df..85e2765 100644 --- a/libexeinfo/MZ/MZ.cs +++ b/libexeinfo/MZ/MZ.cs @@ -38,13 +38,15 @@ namespace libexeinfo /// public partial class MZ : IExecutable { + public GEM.ColorIcon[] GemColorIcons; /// /// Header for this executable /// - internal MZHeader Header; - public GEM.GemResourceHeader ResourceHeader; - public GEM.TreeObjectNode[] ResourceObjectRoots; - public Stream resourceStream; + internal MZHeader Header; + public GEM.GemResourceExtension ResourceExtension; + public GEM.GemResourceHeader ResourceHeader; + public GEM.TreeObjectNode[] ResourceObjectRoots; + public Stream ResourceStream; /// /// Initializes a new instance of the class. @@ -76,7 +78,7 @@ namespace libexeinfo resourceFilePath = testPath + ".RSC"; if(resourceFilePath != null) - resourceStream = File.Open(resourceFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + ResourceStream = File.Open(resourceFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); Initialize(); } @@ -142,27 +144,37 @@ namespace libexeinfo Type = "DOS Executable (MZ)"; - if(resourceStream == null) return; + if(ResourceStream == null) return; buffer = new byte[Marshal.SizeOf(typeof(GEM.GemResourceHeader))]; - resourceStream.Position = 0; - resourceStream.Read(buffer, 0, buffer.Length); + ResourceStream.Position = 0; + ResourceStream.Read(buffer, 0, buffer.Length); ResourceHeader = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); if(ResourceHeader.rsh_vrsn != 0 && ResourceHeader.rsh_vrsn != 1 && ResourceHeader.rsh_vrsn != 4 && ResourceHeader.rsh_vrsn != 5) return; + if((ResourceHeader.rsh_vrsn & 4) == 4) + { + buffer = new byte[Marshal.SizeOf(typeof(GEM.GemResourceExtension))]; + ResourceStream.Position = ResourceHeader.rsh_rssize; + ResourceStream.Read(buffer, 0, buffer.Length); + ResourceExtension = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + + GemColorIcons = GEM.GetColorIcons(ResourceStream, ResourceExtension.color_ic, false, encoding); + } + List strings = new List(); if(ResourceHeader.rsh_ntree > 0) { - resourceStream.Position = ResourceHeader.rsh_trindex; + ResourceStream.Position = ResourceHeader.rsh_trindex; int[] treeOffsets = new int[ResourceHeader.rsh_ntree]; byte[] tmp = new byte[4]; for(int i = 0; i < ResourceHeader.rsh_ntree; i++) { - resourceStream.Read(tmp, 0, 4); + ResourceStream.Read(tmp, 0, 4); treeOffsets[i] = BitConverter.ToInt32(tmp, 0); } @@ -170,15 +182,15 @@ namespace libexeinfo for(int i = 0; i < ResourceHeader.rsh_ntree; i++) { - if(treeOffsets[i] <= 0 || treeOffsets[i] >= resourceStream.Length) continue; + if(treeOffsets[i] <= 0 || treeOffsets[i] >= ResourceStream.Length) continue; - resourceStream.Position = treeOffsets[i]; + ResourceStream.Position = treeOffsets[i]; List nodes = new List(); while(true) { buffer = new byte[Marshal.SizeOf(typeof(GEM.ObjectNode))]; - resourceStream.Read(buffer, 0, buffer.Length); + ResourceStream.Read(buffer, 0, buffer.Length); GEM.ObjectNode node = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); nodes.Add(node); if(((GEM.ObjectFlags)node.ob_flags).HasFlag(GEM.ObjectFlags.Lastob)) break; @@ -186,18 +198,18 @@ namespace libexeinfo List knownNodes = new List(); ResourceObjectRoots[i] = - GEM.ProcessResourceObject(nodes, ref knownNodes, 0, resourceStream, strings, false, encoding); + GEM.ProcessResourceObject(nodes, ref knownNodes, 0, ResourceStream, strings, false, encoding); } } else if(ResourceHeader.rsh_nobs > 0) { GEM.ObjectNode[] nodes = new GEM.ObjectNode[ResourceHeader.rsh_nobs]; - resourceStream.Position = ResourceHeader.rsh_object; + ResourceStream.Position = ResourceHeader.rsh_object; for(short i = 0; i < ResourceHeader.rsh_nobs; i++) { buffer = new byte[Marshal.SizeOf(typeof(GEM.ObjectNode))]; - resourceStream.Read(buffer, 0, buffer.Length); + ResourceStream.Read(buffer, 0, buffer.Length); nodes[i] = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); } @@ -205,7 +217,7 @@ namespace libexeinfo ResourceObjectRoots = new GEM.TreeObjectNode[1]; // TODO: Correct encoding? ResourceObjectRoots[0] = - GEM.ProcessResourceObject(nodes, ref knownNodes, 0, resourceStream, strings, false, encoding); + GEM.ProcessResourceObject(nodes, ref knownNodes, 0, ResourceStream, strings, false, encoding); } if(strings.Count > 0) diff --git a/libexeinfo/libexeinfo.csproj b/libexeinfo/libexeinfo.csproj index 2c5a8d5..397df8f 100644 --- a/libexeinfo/libexeinfo.csproj +++ b/libexeinfo/libexeinfo.csproj @@ -53,6 +53,7 @@ +