diff --git a/src/Workshell.PE.Testbed/Program.cs b/src/Workshell.PE.Testbed/Program.cs index 8093778..6fe3b26 100644 --- a/src/Workshell.PE.Testbed/Program.cs +++ b/src/Workshell.PE.Testbed/Program.cs @@ -20,7 +20,7 @@ static async Task RunAsync(string[] args) var dataDirectory = image.NTHeaders.DataDirectories[DataDirectoryType.ResourceTable]; var content = await dataDirectory.GetContentAsync().ConfigureAwait(false); - + var resources = await Resources.GetAsync(image); } } } diff --git a/src/Workshell.PE/Content/Resources/Resource.cs b/src/Workshell.PE/Content/Resources/Resource.cs new file mode 100644 index 0000000..b48437f --- /dev/null +++ b/src/Workshell.PE/Content/Resources/Resource.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Workshell.PE.Content +{ + public class Resource : ISupportsBytes + { + public const uint DefaultLanguage = 1033; + + private readonly PortableExecutableImage _image; + private readonly Dictionary _languages; + + protected Resource(PortableExecutableImage image, ResourceType type, ResourceDirectoryEntry entry, ResourceId id) + { + _image = image; + _languages = BuildLanguages(entry); + + Type = type; + Entry = entry; + Id = id; + Languages = _languages.Keys.OrderBy(k => k).ToArray(); + } + + #region Static Methods + + internal static async Task CreateAsync(PortableExecutableImage image, ResourceType type, ResourceDirectoryEntry entry, Type resourceType) + { + ResourceId id; + + if (entry.NameType == NameType.ID) + { + id = entry.GetId(); + } + else + { + id = await entry.GetNameAsync().ConfigureAwait(false); + } + + var ctors = resourceType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var ctor = ctors.First(); + var resource = (Resource)ctor.Invoke(new object[] { image, type, entry, id }); + + return resource; + } + + #endregion + + #region Methods + + public override string ToString() + { + if (!Id.IsNumeric) + return Id.Name; + + return $"#{Id}"; + } + + public ResourceData GetData(uint language = DefaultLanguage) + { + return GetDataAsync(language).GetAwaiter().GetResult(); + } + + public async Task GetDataAsync(uint language = DefaultLanguage) + { + if (!_languages.ContainsKey(language)) + return null; + + var languageEntry = _languages[language]; + var entry = await languageEntry.GetDataEntryAsync().ConfigureAwait(false); + var data = entry.GetData(); + + return data; + } + + public byte[] GetBytes() + { + return GetBytes(DefaultLanguage); + } + + public async Task GetBytesAsync() + { + return await GetBytesAsync(DefaultLanguage); + } + + public byte[] GetBytes(uint language) + { + return GetBytesAsync(language).GetAwaiter().GetResult(); + } + + public async Task GetBytesAsync(uint language) + { + if (!_languages.ContainsKey(language)) + throw new PortableExecutableImageException(_image, $"Cannot find specified language: {language}"); + + var data = await GetDataAsync(language).ConfigureAwait(false); + + if (data == null) + throw new PortableExecutableImageException(_image, $"Cannot find resource data for language: {language}"); + + return await data.GetBytesAsync().ConfigureAwait(false); + } + + public void CopyTo(Stream stream, uint language = DefaultLanguage) + { + CopyToAsync(stream, language).GetAwaiter().GetResult(); + } + + public async Task CopyToAsync(Stream stream, uint language = DefaultLanguage) + { + var data = await GetDataAsync(language).ConfigureAwait(false); + + await data.CopyToAsync(stream).ConfigureAwait(false); + } + + private Dictionary BuildLanguages(ResourceDirectoryEntry parentEntry) + { + var results = new Dictionary(); + var directory = parentEntry.GetDirectory(); + + foreach (var entry in directory) + results.Add(entry.GetId(),entry); + + return results; + } + + #endregion + + #region Properties + + public ResourceType Type { get; } + public ResourceDirectoryEntry Entry { get; } + public ResourceId Id { get; } + public uint[] Languages { get; } + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Resources/ResourceId.cs b/src/Workshell.PE/Content/Resources/ResourceId.cs new file mode 100644 index 0000000..f13ffe0 --- /dev/null +++ b/src/Workshell.PE/Content/Resources/ResourceId.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Workshell.PE.Content +{ + public struct ResourceId : IEquatable + { + public ResourceId(uint resourceId) + { + Id = resourceId; + Name = resourceId.ToString(); + } + + public ResourceId(string resourceName) + { + Id = 0; + Name = resourceName; + } + + public ResourceId(uint resourceId, string resourceName) + { + Id = resourceId; + Name = resourceName; + } + + #region Operators + + public static implicit operator ResourceId(short resourceId) + { + return new ResourceId(Convert.ToUInt32(resourceId)); + } + + public static implicit operator ResourceId(int resourceId) + { + return new ResourceId(Convert.ToUInt32(resourceId)); + } + + public static implicit operator ResourceId(long resourceId) + { + return new ResourceId(Convert.ToUInt32(resourceId)); + } + + public static implicit operator ResourceId(ushort resourceId) + { + return new ResourceId(resourceId); + } + + public static implicit operator ResourceId(uint resourceId) + { + return new ResourceId(resourceId); + } + + public static implicit operator ResourceId(ulong resourceId) + { + return new ResourceId(Convert.ToUInt32(resourceId)); + } + + public static implicit operator ResourceId(string resourceName) + { + return new ResourceId(resourceName); + } + + public static implicit operator short(ResourceId resourceId) + { + return Convert.ToInt16(resourceId.Id); + } + + public static implicit operator int(ResourceId resourceId) + { + return Convert.ToInt32(resourceId.Id); + } + + public static implicit operator long(ResourceId resourceId) + { + return resourceId.Id; + } + + public static implicit operator ushort(ResourceId resourceId) + { + return Convert.ToUInt16(resourceId.Id); + } + + public static implicit operator uint(ResourceId resourceId) + { + return resourceId.Id; + } + + public static implicit operator ulong(ResourceId resourceId) + { + return resourceId.Id; + } + + public static implicit operator string(ResourceId resourceId) + { + return (resourceId.Id > 0 ? resourceId.Id.ToString() : resourceId.Name); + } + + public static bool operator ==(ResourceId firstId, ResourceId secondId) + { + return firstId.Equals(secondId); + } + + public static bool operator !=(ResourceId firstId, ResourceId secondId) + { + return !(firstId == secondId); + } + + public static bool operator ==(ResourceId firstId, short secondId) + { + return (firstId.Id == Convert.ToUInt32(secondId)); + } + + public static bool operator !=(ResourceId firstId, short secondId) + { + return !(firstId == secondId); + } + + public static bool operator ==(ResourceId firstId, ushort secondId) + { + return (firstId.Id == Convert.ToUInt32(secondId)); + } + + public static bool operator !=(ResourceId firstId, ushort secondId) + { + return !(firstId == secondId); + } + + public static bool operator ==(ResourceId firstId, int secondId) + { + return (firstId.Id == Convert.ToUInt32(secondId)); + } + + public static bool operator !=(ResourceId firstId, int secondId) + { + return !(firstId == secondId); + } + + public static bool operator ==(ResourceId firstId, uint secondId) + { + return (firstId.Id == secondId); + } + + public static bool operator !=(ResourceId firstId, uint secondId) + { + return !(firstId == secondId); + } + + public static bool operator ==(ResourceId firstId, long secondId) + { + return (firstId.Id == Convert.ToUInt32(secondId)); + } + + public static bool operator !=(ResourceId firstId, long secondId) + { + return !(firstId == secondId); + } + + public static bool operator ==(ResourceId firstId, ulong secondId) + { + return (firstId.Id == Convert.ToUInt32(secondId)); + } + + public static bool operator !=(ResourceId firstId, ulong secondId) + { + return !(firstId == secondId); + } + + public static bool operator ==(ResourceId firstId, string secondId) + { + return (String.Compare(firstId.Name, secondId, true) == 0); + } + + public static bool operator !=(ResourceId firstId, string secondId) + { + return !(firstId == secondId); + } + + public static bool operator ==(string firstId, ResourceId secondId) + { + return (string.Compare(firstId, secondId.Name, StringComparison.OrdinalIgnoreCase) == 0); + } + + public static bool operator !=(string firstId, ResourceId secondId) + { + return !(firstId == secondId); + } + + #endregion + + #region Methods + + public override string ToString() + { + if (IsEmpty) + return "(Empty)"; + + return Name; + } + + public override int GetHashCode() + { + var hash = 13; + + hash = (hash * 7) + Id.GetHashCode(); + hash = (hash * 7) + Name.GetHashCode(); + + return hash; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, null)) + return false; + + if (!(obj is ResourceId)) + return false; + + if (ReferenceEquals(obj, this)) + return true; + + return Equals((ResourceId)obj); + } + + public bool Equals(ResourceId resourceId) + { + return (Id == resourceId.Id && string.Compare(Name, resourceId.Name, StringComparison.OrdinalIgnoreCase) == 0); + } + + #endregion + + #region Properties + + public uint Id { get; } + public string Name { get; } + public bool IsNumeric => (Id != 0); + public bool IsEmpty => (Id == 0 && string.IsNullOrWhiteSpace(Name)); + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Resources/ResourceType.cs b/src/Workshell.PE/Content/Resources/ResourceType.cs new file mode 100644 index 0000000..32a6560 --- /dev/null +++ b/src/Workshell.PE/Content/Resources/ResourceType.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Workshell.PE.Content +{ + public sealed class ResourceType : IEnumerable + { + public const ushort Cursor = 1; + public const ushort Bitmap = 2; + public const ushort Icon = 3; + public const ushort Menu = 4; + public const ushort Dialog = 5; + public const ushort String = 6; + public const ushort FontDirectory = 7; + public const ushort Font = 8; + public const ushort Accelerator = 9; + public const ushort RCData = 10; + public const ushort MessageTable = 11; + public const ushort GroupCursor = 12; + public const ushort GroupIcon = 14; + public const ushort Version = 16; + public const ushort DialogInclude = 17; + public const ushort PlugAndPlay = 19; + public const ushort VxD = 20; + public const ushort AnimatedCursor = 21; + public const ushort AnimatedIcon = 22; + public const ushort HTML = 23; + public const ushort Manifest = 24; + + private static readonly Dictionary _types; + + private Resource[] _resources; + + static ResourceType() + { + _types = new Dictionary(); + } + + private ResourceType(Resources parentResources, ResourceDirectoryEntry entry, ResourceId id) + { + _resources = new Resource[0]; + + Resources = parentResources; + Entry = entry; + Id = id; + Count = 0; + } + + #region Static Methods + + internal static async Task CreateAsync(PortableExecutableImage image, Resources resources, ResourceDirectoryEntry entry) + { + ResourceId id; + + if (entry.NameType == NameType.ID) + { + id = entry.GetId(); + } + else + { + id = await entry.GetNameAsync().ConfigureAwait(false); + } + + var type = new ResourceType(resources, entry, id); + + await type.LoadResourcesAsync(image).ConfigureAwait(false); + + return type; + } + + public static bool Register(ResourceId typeId) + where T : Resource + { + if (_types.ContainsKey(typeId)) + return false; + + _types.Add(typeId, typeof(T)); + + return true; + } + + public static bool Unregister(ResourceId typeId) + { + return _types.Remove(typeId); + } + + private static Type Get(ResourceId typeId) + { + if (_types.ContainsKey(typeId)) + return _types[typeId]; + + return null; + } + + #endregion + + #region Methods + + public override string ToString() + { + if (!Id.IsNumeric) + return Id.Name; + + switch (Id.Id) + { + case Cursor: + return "CURSOR"; + case Bitmap: + return "BITMAP"; + case Icon: + return "ICON"; + case Menu: + return "MENU"; + case Dialog: + return "DIALOG"; + case String: + return "STRING"; + case FontDirectory: + return "FONTDIR"; + case Font: + return "FONT"; + case Accelerator: + return "ACCELERATOR"; + case RCData: + return "RCDATA"; + case MessageTable: + return "MESSAGETABLE"; + case GroupCursor: + return "GROUP_CURSOR"; + case GroupIcon: + return "GROUP_ICON"; + case Version: + return "VERSION"; + case DialogInclude: + return "DLGINCLUDE"; + case PlugAndPlay: + return "PLUGPLAY"; + case VxD: + return "VXD"; + case AnimatedCursor: + return "ANICURSOR"; + case AnimatedIcon: + return "ANIICON"; + case HTML: + return "HTML"; + case Manifest: + return "MANIFEST"; + default: + return $"#{Id}"; + } + } + + public IEnumerator GetEnumerator() + { + foreach (var resource in _resources) + yield return resource; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + internal async Task LoadResourcesAsync(PortableExecutableImage image) + { + var resources = new List(); + var directory = Entry.GetDirectory(); + + foreach(var entry in directory) + { + var type = Get(Id); + Resource resource = null; + + if (type != null) + { + resource = await Resource.CreateAsync(image, this, entry, type).ConfigureAwait(false); + } + else + { + resource = await Resource.CreateAsync(image, this, entry, typeof(Resource)).ConfigureAwait(false); + } + + resources.Add(resource); + } + + _resources = resources.ToArray(); + Count = resources.Count; + } + + #endregion + + #region Properties + + public Resources Resources; + public ResourceDirectoryEntry Entry { get; } + public ResourceId Id { get; } + public int Count { get; private set; } + public Resource this[int index] => _resources[index]; + + #endregion + } +} diff --git a/src/Workshell.PE/Content/Resources/Resources.cs b/src/Workshell.PE/Content/Resources/Resources.cs new file mode 100644 index 0000000..3d01b47 --- /dev/null +++ b/src/Workshell.PE/Content/Resources/Resources.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Workshell.PE.Content +{ + public sealed class Resources : IEnumerable + { + private ResourceType[] _types; + + private Resources(PortableExecutableImage image) + { + _types = new ResourceType[0]; + + Count = 0; + } + + #region Static Methods + + public static Resources Get(PortableExecutableImage image) + { + return GetAsync(image).GetAwaiter().GetResult(); + } + + public static async Task GetAsync(PortableExecutableImage image) + { + var rootDirectory = await ResourceDirectory.GetAsync(image).ConfigureAwait(false); + + if (rootDirectory == null) + return null; + + var resources = new Resources(image); + + await resources.LoadAsync(image, rootDirectory).ConfigureAwait(false); + + return resources; + } + + #endregion + + #region Methods + + public IEnumerator GetEnumerator() + { + foreach (var type in _types) + yield return type; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + internal async Task LoadAsync(PortableExecutableImage image, ResourceDirectory rootDirectory) + { + var types = new List(); + + foreach (var entry in rootDirectory) + { + var type = await ResourceType.CreateAsync(image, this, entry).ConfigureAwait(false); + + await type.LoadResourcesAsync(image).ConfigureAwait(false); + + types.Add(type); + } + + _types = types.ToArray(); + Count = types.Count; + } + + #endregion + + #region Properties + + public int Count { get; private set; } + public ResourceType this[int index] => _types[index]; + + #endregion + } +}