diff --git a/ExamplePlugin/ExamplePlugin.csproj b/ExamplePlugin/ExamplePlugin.csproj new file mode 100644 index 0000000..acefbee --- /dev/null +++ b/ExamplePlugin/ExamplePlugin.csproj @@ -0,0 +1,54 @@ + + + + + Debug + AnyCPU + {97AC964A-E56F-415C-BAEA-D503E3D4D7B8} + Library + Properties + ExamplePlugin + ExamplePlugin + v4.5.2 + 512 + + + x86 + ..\Steamless\bin\x86\Debug\Plugins\ + TRACE;DEBUG + + + x86 + ..\Steamless\bin\x86\Release\Plugins\ + true + + + + + + + + + + + + + + + + + + {56c95629-3b34-47fe-b988-04274409294f} + Steamless.API + False + + + + + \ No newline at end of file diff --git a/ExamplePlugin/Main.cs b/ExamplePlugin/Main.cs new file mode 100644 index 0000000..31edbad --- /dev/null +++ b/ExamplePlugin/Main.cs @@ -0,0 +1,101 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace ExamplePlugin +{ + using Steamless.API; + using Steamless.API.Events; + using Steamless.API.Model; + using Steamless.API.Services; + using System; + + [SteamlessApiVersion(1, 0)] + public class Main : SteamlessPlugin + { + /// + /// Internal logging service instance. + /// + private LoggingService m_LoggingService; + + /// + /// Gets the author of this plugin. + /// + public override string Author => "Steamless Development Team"; + + /// + /// Gets the name of this plugin. + /// + public override string Name => "Example Plugin"; + + /// + /// Gets the description of this plugin. + /// + public override string Description => "A simple plugin example."; + + /// + /// Gets the version of this plugin. + /// + public override Version Version => new Version(1, 0, 0, 0); + + /// + /// Initialize function called when this plugin is first loaded. + /// + /// + /// + public override bool Initialize(LoggingService logService) + { + this.m_LoggingService = logService; + this.m_LoggingService.OnAddLogMessage(this, new LogMessageEventArgs("ExamplePlugin was initialized!", LogMessageType.Debug)); + + return true; + } + + /// + /// Processing function called when a file is being unpacked. Allows plugins to check the file + /// and see if it can handle the file for its intended purpose. + /// + /// + /// + public override bool CanProcessFile(string file) + { + this.m_LoggingService.OnAddLogMessage(this, new LogMessageEventArgs("ExamplePlugin was asked to check if it can process a file!", LogMessageType.Debug)); + + return false; + } + + /// + /// Processing function called to allow the plugin to process the file. + /// + /// + /// + /// + public override bool ProcessFile(string file, SteamlessOptions options) + { + this.m_LoggingService.OnAddLogMessage(this, new LogMessageEventArgs("ExamplePlugin was asked to process a file!", LogMessageType.Debug)); + + return false; + } + } +} \ No newline at end of file diff --git a/ExamplePlugin/Properties/AssemblyInfo.cs b/ExamplePlugin/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1313c14 --- /dev/null +++ b/ExamplePlugin/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("ExamplePlugin")] +[assembly: AssemblyDescription("Example plugin used with Steamless v3.")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCompany("atom0s")] +[assembly: AssemblyProduct("ExamplePlugin")] +[assembly: AssemblyCopyright("Copyright © atom0s 2015 - 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("97ac964a-e56f-415c-baea-d503e3d4d7b8")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Steamless.API/Crypto/AesHelper.cs b/Steamless.API/Crypto/AesHelper.cs new file mode 100644 index 0000000..2514609 --- /dev/null +++ b/Steamless.API/Crypto/AesHelper.cs @@ -0,0 +1,172 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Crypto +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Security.Cryptography; + + public class AesHelper : IDisposable + { + /// + /// Internal original key set by the user of this class. + /// + private readonly byte[] m_OriginalKey; + + /// + /// Internal original iv set by the user of this class. + /// + private readonly byte[] m_OriginalIv; + + /// + /// Internal AES crypto provider. + /// + private AesCryptoServiceProvider m_AesCryptoProvider; + + /// + /// Default Constructor + /// + /// + /// + /// + /// + public AesHelper(byte[] key, byte[] iv, CipherMode mode = CipherMode.ECB, PaddingMode padding = PaddingMode.None) + { + // Store the original key and iv.. + this.m_OriginalKey = key; + this.m_OriginalIv = iv; + + // Create the AES crypto provider.. + this.m_AesCryptoProvider = new AesCryptoServiceProvider + { + Key = key, + IV = iv, + Mode = mode, + Padding = padding + }; + } + + /// + /// Default Deconstructor + /// + ~AesHelper() + { + this.Dispose(false); + } + + /// + /// IDispose implementation. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// IDispose implementation. + /// + /// + protected virtual void Dispose(bool disposing) + { + this.m_AesCryptoProvider?.Dispose(); + this.m_AesCryptoProvider = null; + } + + /// + /// Rebuilds the current iv (or the one given). + /// + /// + /// + public bool RebuildIv(byte[] iv = null) + { + // Use the current iv if none is set.. + if (iv == null) + iv = this.m_OriginalIv; + + try + { + using (var decryptor = this.m_AesCryptoProvider.CreateDecryptor()) + { + return decryptor.TransformBlock(iv, 0, iv.Length, this.m_OriginalIv, 0) > 0; + } + } + catch + { + return false; + } + } + + /// + /// Decrypts the given data using the given mode and padding. + /// + /// + /// + /// + /// + public byte[] Decrypt(byte[] data, CipherMode mode, PaddingMode padding) + { + ICryptoTransform decryptor = null; + MemoryStream mStream = null; + CryptoStream cStream = null; + + try + { + // Update the mode and padding for the decryption.. + this.m_AesCryptoProvider.Mode = mode; + this.m_AesCryptoProvider.Padding = padding; + + // Create the decryptor.. + decryptor = this.m_AesCryptoProvider.CreateDecryptor(this.m_OriginalKey, this.m_OriginalIv); + + // Create a memory stream for our data.. + mStream = new MemoryStream(data); + + // Create the crypto stream.. + cStream = new CryptoStream(mStream, decryptor, CryptoStreamMode.Read); + + // Decrypt the data.. + var totalBuffer = new List(); + var buffer = new byte[2048]; + while ((cStream.Read(buffer, 0, 2048)) > 0) + totalBuffer.AddRange(buffer); + + return totalBuffer.ToArray(); + } + catch + { + return null; + } + finally + { + cStream?.Dispose(); + mStream?.Dispose(); + decryptor?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Steamless.API/Events/LogMessageEventArgs.cs b/Steamless.API/Events/LogMessageEventArgs.cs new file mode 100644 index 0000000..3c9b4e6 --- /dev/null +++ b/Steamless.API/Events/LogMessageEventArgs.cs @@ -0,0 +1,62 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Events +{ + using System; + + public class LogMessageEventArgs : EventArgs + { + /// + /// Default Constructor + /// + public LogMessageEventArgs() + { + this.Message = string.Empty; + this.MessageType = LogMessageType.Debug; + } + + /// + /// Overloaded Constructor + /// + /// + /// + public LogMessageEventArgs(string msg, LogMessageType type) + { + this.Message = msg; + this.MessageType = type; + } + + /// + /// Gets or sets the message text. + /// + public string Message { get; set; } + + /// + /// Gets or sets the type of message. + /// + public LogMessageType MessageType { get; set; } + } +} \ No newline at end of file diff --git a/Steamless.API/Events/LogMessageType.cs b/Steamless.API/Events/LogMessageType.cs new file mode 100644 index 0000000..46b06be --- /dev/null +++ b/Steamless.API/Events/LogMessageType.cs @@ -0,0 +1,58 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Events +{ + /// + /// Log Message Type Enumeration + /// + public enum LogMessageType + { + /// + /// Used for general purpose messages. + /// + Information = 0, + + /// + /// Used for successful messages. + /// + Success = 1, + + /// + /// Used for warnings. + /// + Warning = 2, + + /// + /// Used for errors. + /// + Error = 3, + + /// + /// Used for debug messages. + /// + Debug = 4 + } +} \ No newline at end of file diff --git a/Steamless.API/Extensions/FileStreamExtensions.cs b/Steamless.API/Extensions/FileStreamExtensions.cs new file mode 100644 index 0000000..fa47ab5 --- /dev/null +++ b/Steamless.API/Extensions/FileStreamExtensions.cs @@ -0,0 +1,42 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Extensions +{ + using System.IO; + + public static class FileStreamExtensions + { + /// + /// Writes a byte array to the file stream. + /// + /// + /// + public static void WriteBytes(this FileStream fStream, byte[] data) + { + fStream.Write(data, 0, data.Length); + } + } +} \ No newline at end of file diff --git a/Steamless.API/Model/NavigatedEventArgs.cs b/Steamless.API/Model/NavigatedEventArgs.cs new file mode 100644 index 0000000..5a4e782 --- /dev/null +++ b/Steamless.API/Model/NavigatedEventArgs.cs @@ -0,0 +1,42 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Model +{ + using System; + + public class NavigatedEventArgs : EventArgs + { + /// + /// Gets or sets the previous view being navigated from. + /// + public ViewModelBase PreviousView { get; internal set; } + + /// + /// Gets or sets the current view being navigated to. + /// + public ViewModelBase CurrentView { get; internal set; } + } +} \ No newline at end of file diff --git a/Steamless.API/Model/NotifiableModel.cs b/Steamless.API/Model/NotifiableModel.cs new file mode 100644 index 0000000..2c2e15b --- /dev/null +++ b/Steamless.API/Model/NotifiableModel.cs @@ -0,0 +1,104 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Model +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + + [Serializable] + public class NotifiableModel : INotifyPropertyChanged + { + /// + /// Internal properties container. + /// + private readonly Dictionary m_Properties; + + /// + /// Default Constructor + /// + public NotifiableModel() + { + this.m_Properties = new Dictionary(); + } + + /// + /// Event triggered when a property is changed. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Method used to raise the PropertyChanged event. + /// + /// + public void OnPropertyChanged(string prop) + { + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); + } + + /// + /// Method to raise the PropertyChanged event. + /// + /// + protected void RaisePropertyChanged(string property) + { + if (string.IsNullOrEmpty(property)) + throw new ArgumentNullException(property); + this.OnPropertyChanged(property); + } + + /// + /// Gets a property from the internal container. + /// + /// + /// + /// + protected T Get(string prop) + { + if (this.m_Properties.ContainsKey(prop)) + { + return (T)this.m_Properties[prop]; + } + return default(T); + } + + /// + /// Sets a property in the internal container. + /// + /// + /// + /// + protected void Set(string prop, T val) + { + var curr = this.Get(prop); + if (Equals(curr, val)) + return; + + this.m_Properties[prop] = val; + this.OnPropertyChanged(prop); + } + } +} \ No newline at end of file diff --git a/Steamless.API/Model/SteamlessOptions.cs b/Steamless.API/Model/SteamlessOptions.cs new file mode 100644 index 0000000..68eb675 --- /dev/null +++ b/Steamless.API/Model/SteamlessOptions.cs @@ -0,0 +1,77 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Model +{ + public class SteamlessOptions : NotifiableModel + { + /// + /// Default Constructor + /// + public SteamlessOptions() + { + this.VerboseOutput = true; + this.KeepBindSection = false; + this.DumpPayloadToDisk = false; + this.DumpSteamDrmpToDisk = false; + } + + /// + /// Gets or sets the verbose output option value. + /// + public bool VerboseOutput + { + get { return this.Get("VerboseOutput"); } + set { this.Set("VerboseOutput", value); } + } + + /// + /// Gets or sets the keep bind section option value. + /// + public bool KeepBindSection + { + get { return this.Get("KeepBindSection"); } + set { this.Set("KeepBindSection", value); } + } + + /// + /// Gets or sets the dump payload to disk option value. + /// + public bool DumpPayloadToDisk + { + get { return this.Get("DumpPayloadToDisk"); } + set { this.Set("DumpPayloadToDisk", value); } + } + + /// + /// Gets or sets the dump SteamDRMP.dll to disk option value. + /// + public bool DumpSteamDrmpToDisk + { + get { return this.Get("DumpSteamDrmpToDisk"); } + set { this.Set("DumpSteamDrmpToDisk", value); } + } + } +} \ No newline at end of file diff --git a/Steamless.API/Model/SteamlessPlugin.cs b/Steamless.API/Model/SteamlessPlugin.cs new file mode 100644 index 0000000..89cd29c --- /dev/null +++ b/Steamless.API/Model/SteamlessPlugin.cs @@ -0,0 +1,118 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Model +{ + using Services; + using System; + + public abstract class SteamlessPlugin : IDisposable + { + /// + /// Gets the author of this plugin. + /// + public virtual string Author => "Steamless Development Team"; + + /// + /// Gets the name of this plugin. + /// + public virtual string Name => "Steamless Plugin"; + + /// + /// Gets the description of this plugin. + /// + public virtual string Description => "The Steamless base plugin class."; + + /// + /// Gets the version of this plugin. + /// + public virtual Version Version => new Version(1, 0, 0, 0); + + /// + /// Deconstructor + /// + ~SteamlessPlugin() + { + this.Dispose(false); + } + + /// + /// IDisposable implementation. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// IDisposable implementation. + /// + /// + protected virtual void Dispose(bool disposing) + { + } + + /// + /// Initialize function called when this plugin is first loaded. + /// + /// + /// + public virtual bool Initialize(LoggingService logService) + { + return false; + } + + /// + /// Processing function called when a file is being unpacked. Allows plugins to check the file + /// and see if it can handle the file for its intended purpose. + /// + /// + /// + public virtual bool CanProcessFile(string file) + { + return false; + } + + /// + /// Processing function called to allow the plugin to process the file. + /// + /// + /// + /// + public virtual bool ProcessFile(string file, SteamlessOptions options) + { + return false; + } + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + public string DisplayName => this.Name + " - " + this.Description; + } +} \ No newline at end of file diff --git a/Steamless.API/Model/ViewModelBase.cs b/Steamless.API/Model/ViewModelBase.cs new file mode 100644 index 0000000..a8cbdb6 --- /dev/null +++ b/Steamless.API/Model/ViewModelBase.cs @@ -0,0 +1,115 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Model +{ + using System; + using System.ComponentModel; + using System.Windows; + + public abstract class ViewModelBase : NotifiableModel + { + /// + /// Default Constructor + /// + protected ViewModelBase() + { + this.Events = new EventHandlerList(); + } + + /// + /// Internal static design mode flag. + /// + private static bool? m_IsInDesignMode; + + /// + /// Gets if this ViewModelBase is in design mode. + /// + public bool IsInDesignMode => IsInDesignModeStatic; + + /// + /// Gets the static ViewModelBase design mode flag. + /// + public static bool IsInDesignModeStatic + { + get + { + if (m_IsInDesignMode.HasValue) + return m_IsInDesignMode.Value; + + var isInDesignModeProperty = DesignerProperties.IsInDesignModeProperty; + m_IsInDesignMode = (bool)DependencyPropertyDescriptor.FromProperty(isInDesignModeProperty, typeof(FrameworkElement)).Metadata.DefaultValue; + return m_IsInDesignMode.Value; + } + } + + /// + /// Gets or sets the internal event handler list. + /// + private EventHandlerList Events + { + get { return this.Get("Events"); } + set { this.Set("Events", value); } + } + + /// + /// Event to subscribe to to be notified when a view is navigated from. + /// + public event EventHandler NavigatedFrom + { + add { this.Events.AddHandler("NavigatedFromEvent", value); } + remove { this.Events.RemoveHandler("NavigatedFromEvent", value); } + } + + /// + /// Event to subscribe to to be notified when a view is navigated to. + /// + public event EventHandler NavigatedTo + { + add { this.Events.AddHandler("NavigatedToEvent", value); } + remove { this.Events.RemoveHandler("NavigatedToEvent", value); } + } + + /// + /// Internal navigated from event invoker. + /// + /// + public void OnNavigatedFrom(NavigatedEventArgs e) + { + var eventHandler = (EventHandler)this.Events["NavigatedFromEvent"]; + eventHandler?.Invoke(this, e); + } + + /// + /// Internal navigated to event invoker. + /// + /// + public void OnNavigatedTo(NavigatedEventArgs e) + { + var eventHandler = (EventHandler)this.Events["NavigatedToEvent"]; + eventHandler?.Invoke(this, e); + } + } +} \ No newline at end of file diff --git a/Steamless.API/PE32/NativeApi32.cs b/Steamless.API/PE32/NativeApi32.cs new file mode 100644 index 0000000..46f2a41 --- /dev/null +++ b/Steamless.API/PE32/NativeApi32.cs @@ -0,0 +1,603 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.PE32 +{ + using System; + using System.Runtime.InteropServices; + + public class NativeApi32 + { + /// + /// IMAGE_DOS_HEADER Structure + /// + [StructLayout(LayoutKind.Sequential)] + public struct ImageDosHeader32 + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public char[] e_magic; + + public ushort e_cblp; + public ushort e_cp; + public ushort e_crlc; + public ushort e_cparhdr; + public ushort e_minalloc; + public ushort e_maxalloc; + public ushort e_ss; + public ushort e_sp; + public ushort e_csum; + public ushort e_ip; + public ushort e_cs; + public ushort e_lfarlc; + public ushort e_ovno; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] e_res1; + + public ushort e_oemid; + public ushort e_oeminfo; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public ushort[] e_res2; + + public int e_lfanew; + + /// + /// Gets if this structure is valid for a PE file. + /// + public bool IsValid => new string(this.e_magic) == "MZ"; + } + + /// + /// IMAGE_NT_HEADERS Structure + /// + [StructLayout(LayoutKind.Explicit)] + public struct ImageNtHeaders32 + { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] Signature; + + [FieldOffset(4)] + public ImageFileHeader32 FileHeader; + + [FieldOffset(24)] + public ImageOptionalHeader32 OptionalHeader; + + /// + /// Gets if this structure is valid for a PE file. + /// + public bool IsValid => new string(this.Signature).Trim('\0') == "PE"; + } + + /// + /// IMAGE_FILE_HEADER Structure + /// + [StructLayout(LayoutKind.Sequential)] + public struct ImageFileHeader32 + { + public ushort Machine; + public ushort NumberOfSections; + public uint TimeDateStamp; + public uint PointerToSymbolTable; + public uint NumberOfSymbols; + public ushort SizeOfOptionalHeader; + public ushort Characteristics; + } + + /// + /// Machine Type Enumeration + /// + public enum MachineType : ushort + { + Native = 0, + I386 = 0x014C, + Itanium = 0x0200, + X64 = 0x8664 + } + + /// + /// Magic Type Enumeration + /// + public enum MagicType : ushort + { + ImageNtOptionalHdr32Magic = 0x10B, + ImageNtOptionalHdr64Magic = 0x20B + } + + /// + /// Sub System Type Enumeration + /// + public enum SubSystemType : ushort + { + ImageSubsystemUnknown = 0, + ImageSubsystemNative = 1, + ImageSubsystemWindowsGui = 2, + ImageSubsystemWindowsCui = 3, + ImageSubsystemPosixCui = 7, + ImageSubsystemWindowsCeGui = 9, + ImageSubsystemEfiApplication = 10, + ImageSubsystemEfiBootServiceDriver = 11, + ImageSubsystemEfiRuntimeDriver = 12, + ImageSubsystemEfiRom = 13, + ImageSubsystemXbox = 14 + } + + /// + /// Dll Characteristics Type Enumeration + /// + public enum DllCharacteristicsType : ushort + { + Reserved0 = 0x0001, + Reserved1 = 0x0002, + Reserved2 = 0x0004, + Reserved3 = 0x0008, + ImageDllCharacteristicsDynamicBase = 0x0040, + ImageDllCharacteristicsForceIntegrity = 0x0080, + ImageDllCharacteristicsNxCompat = 0x0100, + ImageDllcharacteristicsNoIsolation = 0x0200, + ImageDllcharacteristicsNoSeh = 0x0400, + ImageDllcharacteristicsNoBind = 0x0800, + Reserved4 = 0x1000, + ImageDllcharacteristicsWdmDriver = 0x2000, + ImageDllcharacteristicsTerminalServerAware = 0x8000 + } + + /// + /// IMAGE_OPTIONAL_HEADER Structure + /// + [StructLayout(LayoutKind.Explicit)] + public struct ImageOptionalHeader32 + { + [FieldOffset(0)] + public MagicType Magic; + + [FieldOffset(2)] + public byte MajorLinkerVersion; + + [FieldOffset(3)] + public byte MinorLinkerVersion; + + [FieldOffset(4)] + public uint SizeOfCode; + + [FieldOffset(8)] + public uint SizeOfInitializedData; + + [FieldOffset(12)] + public uint SizeOfUninitializedData; + + [FieldOffset(16)] + public uint AddressOfEntryPoint; + + [FieldOffset(20)] + public uint BaseOfCode; + + // PE32 contains this additional field + [FieldOffset(24)] + public uint BaseOfData; + + [FieldOffset(28)] + public uint ImageBase; + + [FieldOffset(32)] + public uint SectionAlignment; + + [FieldOffset(36)] + public uint FileAlignment; + + [FieldOffset(40)] + public ushort MajorOperatingSystemVersion; + + [FieldOffset(42)] + public ushort MinorOperatingSystemVersion; + + [FieldOffset(44)] + public ushort MajorImageVersion; + + [FieldOffset(46)] + public ushort MinorImageVersion; + + [FieldOffset(48)] + public ushort MajorSubsystemVersion; + + [FieldOffset(50)] + public ushort MinorSubsystemVersion; + + [FieldOffset(52)] + public uint Win32VersionValue; + + [FieldOffset(56)] + public uint SizeOfImage; + + [FieldOffset(60)] + public uint SizeOfHeaders; + + [FieldOffset(64)] + public uint CheckSum; + + [FieldOffset(68)] + public SubSystemType Subsystem; + + [FieldOffset(70)] + public DllCharacteristicsType DllCharacteristics; + + [FieldOffset(72)] + public uint SizeOfStackReserve; + + [FieldOffset(76)] + public uint SizeOfStackCommit; + + [FieldOffset(80)] + public uint SizeOfHeapReserve; + + [FieldOffset(84)] + public uint SizeOfHeapCommit; + + [FieldOffset(88)] + public uint LoaderFlags; + + [FieldOffset(92)] + public uint NumberOfRvaAndSizes; + + [FieldOffset(96)] + public ImageDataDirectory32 ExportTable; + + [FieldOffset(104)] + public ImageDataDirectory32 ImportTable; + + [FieldOffset(112)] + public ImageDataDirectory32 ResourceTable; + + [FieldOffset(120)] + public ImageDataDirectory32 ExceptionTable; + + [FieldOffset(128)] + public ImageDataDirectory32 CertificateTable; + + [FieldOffset(136)] + public ImageDataDirectory32 BaseRelocationTable; + + [FieldOffset(144)] + public ImageDataDirectory32 Debug; + + [FieldOffset(152)] + public ImageDataDirectory32 Architecture; + + [FieldOffset(160)] + public ImageDataDirectory32 GlobalPtr; + + [FieldOffset(168)] + public ImageDataDirectory32 TLSTable; + + [FieldOffset(176)] + public ImageDataDirectory32 LoadConfigTable; + + [FieldOffset(184)] + public ImageDataDirectory32 BoundImport; + + [FieldOffset(192)] + public ImageDataDirectory32 IAT; + + [FieldOffset(200)] + public ImageDataDirectory32 DelayImportDescriptor; + + [FieldOffset(208)] + public ImageDataDirectory32 CLRRuntimeHeader; + + [FieldOffset(216)] + public ImageDataDirectory32 Reserved; + } + + /// + /// IMAGE_DATA_DIRECTORY Structure + /// + [StructLayout(LayoutKind.Sequential)] + public struct ImageDataDirectory32 + { + public uint VirtualAddress; + public uint Size; + } + + /// + /// IMAGE_SECTION_HEADER Structure + /// + [StructLayout(LayoutKind.Explicit)] + public struct ImageSectionHeader32 + { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public char[] Name; + + [FieldOffset(8)] + public uint VirtualSize; + + [FieldOffset(12)] + public uint VirtualAddress; + + [FieldOffset(16)] + public uint SizeOfRawData; + + [FieldOffset(20)] + public uint PointerToRawData; + + [FieldOffset(24)] + public uint PointerToRelocations; + + [FieldOffset(28)] + public uint PointerToLinenumbers; + + [FieldOffset(32)] + public ushort NumberOfRelocations; + + [FieldOffset(34)] + public ushort NumberOfLinenumbers; + + [FieldOffset(36)] + public DataSectionFlags Characteristics; + + /// + /// Gets the section name of this current section object. + /// + public string SectionName => new string(this.Name).Trim('\0'); + + /// + /// Gets if this structure is valid for a PE file. + /// + public bool IsValid => this.SizeOfRawData != 0 && this.PointerToRawData != 0; + } + + /// + /// Data Section Flags Enumeration + /// + [Flags] + public enum DataSectionFlags : uint + { + /// + /// Reserved for future use. + /// + TypeReg = 0x00000000, + + /// + /// Reserved for future use. + /// + TypeDsect = 0x00000001, + + /// + /// Reserved for future use. + /// + TypeNoLoad = 0x00000002, + + /// + /// Reserved for future use. + /// + TypeGroup = 0x00000004, + + /// + /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. + /// + TypeNoPadded = 0x00000008, + + /// + /// Reserved for future use. + /// + TypeCopy = 0x00000010, + + /// + /// The section contains executable code. + /// + ContentCode = 0x00000020, + + /// + /// The section contains initialized data. + /// + ContentInitializedData = 0x00000040, + + /// + /// The section contains uninitialized data. + /// + ContentUninitializedData = 0x00000080, + + /// + /// Reserved for future use. + /// + LinkOther = 0x00000100, + + /// + /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. + /// + LinkInfo = 0x00000200, + + /// + /// Reserved for future use. + /// + TypeOver = 0x00000400, + + /// + /// The section will not become part of the image. This is valid only for object files. + /// + LinkRemove = 0x00000800, + + /// + /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. + /// + LinkComDat = 0x00001000, + + /// + /// Reset speculative exceptions handling bits in the TLB entries for this section. + /// + NoDeferSpecExceptions = 0x00004000, + + /// + /// The section contains data referenced through the global pointer (GP). + /// + RelativeGp = 0x00008000, + + /// + /// Reserved for future use. + /// + MemPurgeable = 0x00020000, + + /// + /// Reserved for future use. + /// + Memory16Bit = 0x00020000, + + /// + /// Reserved for future use. + /// + MemoryLocked = 0x00040000, + + /// + /// Reserved for future use. + /// + MemoryPreload = 0x00080000, + + /// + /// Align data on a 1-byte boundary. Valid only for object files. + /// + Align1Bytes = 0x00100000, + + /// + /// Align data on a 2-byte boundary. Valid only for object files. + /// + Align2Bytes = 0x00200000, + + /// + /// Align data on a 4-byte boundary. Valid only for object files. + /// + Align4Bytes = 0x00300000, + + /// + /// Align data on an 8-byte boundary. Valid only for object files. + /// + Align8Bytes = 0x00400000, + + /// + /// Align data on a 16-byte boundary. Valid only for object files. + /// + Align16Bytes = 0x00500000, + + /// + /// Align data on a 32-byte boundary. Valid only for object files. + /// + Align32Bytes = 0x00600000, + + /// + /// Align data on a 64-byte boundary. Valid only for object files. + /// + Align64Bytes = 0x00700000, + + /// + /// Align data on a 128-byte boundary. Valid only for object files. + /// + Align128Bytes = 0x00800000, + + /// + /// Align data on a 256-byte boundary. Valid only for object files. + /// + Align256Bytes = 0x00900000, + + /// + /// Align data on a 512-byte boundary. Valid only for object files. + /// + Align512Bytes = 0x00A00000, + + /// + /// Align data on a 1024-byte boundary. Valid only for object files. + /// + Align1024Bytes = 0x00B00000, + + /// + /// Align data on a 2048-byte boundary. Valid only for object files. + /// + Align2048Bytes = 0x00C00000, + + /// + /// Align data on a 4096-byte boundary. Valid only for object files. + /// + Align4096Bytes = 0x00D00000, + + /// + /// Align data on an 8192-byte boundary. Valid only for object files. + /// + Align8192Bytes = 0x00E00000, + + /// + /// The section contains extended relocations. + /// + LinkExtendedRelocationOverflow = 0x01000000, + + /// + /// The section can be discarded as needed. + /// + MemoryDiscardable = 0x02000000, + + /// + /// The section cannot be cached. + /// + MemoryNotCached = 0x04000000, + + /// + /// The section is not pageable. + /// + MemoryNotPaged = 0x08000000, + + /// + /// The section can be shared in memory. + /// + MemoryShared = 0x10000000, + + /// + /// The section can be executed as code. + /// + MemoryExecute = 0x20000000, + + /// + /// The section can be read. + /// + MemoryRead = 0x40000000, + + /// + /// The section can be written to. + /// + MemoryWrite = 0x80000000 + } + + /// + /// IMAGE_TLS_DIRECTORY Structure + /// + [StructLayout(LayoutKind.Sequential)] + public struct ImageTlsDirectory32 + { + public uint StartAddressOfRawData; + public uint EndAddressOfRawData; + public uint AddressOfIndex; + public uint AddressOfCallBacks; + public uint SizeOfZeroFill; + public uint Characteristics; + } + } +} \ No newline at end of file diff --git a/Steamless.API/PE32/Pe32File.cs b/Steamless.API/PE32/Pe32File.cs new file mode 100644 index 0000000..97e9209 --- /dev/null +++ b/Steamless.API/PE32/Pe32File.cs @@ -0,0 +1,421 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.PE32 +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + + /// + /// Portable Executable (32bit) Class + /// + public class Pe32File + { + /// + /// Default Constructor + /// + public Pe32File() + { + } + + /// + /// Overloaded Constructor + /// + /// + public Pe32File(string file) + { + this.FilePath = file; + } + + /// + /// Parses a Win32 PE file. + /// + /// + /// + public bool Parse(string file = null) + { + // Prepare the class variables.. + if (file != null) + this.FilePath = file; + + this.FileData = null; + this.DosHeader = new NativeApi32.ImageDosHeader32(); + this.NtHeaders = new NativeApi32.ImageNtHeaders32(); + this.DosStubSize = 0; + this.DosStubOffset = 0; + this.DosStubData = null; + this.Sections = new List(); + this.SectionData = new List(); + this.TlsDirectory = new NativeApi32.ImageTlsDirectory32(); + this.TlsCallbacks = new List(); + + // Ensure a file path has been set.. + if (string.IsNullOrEmpty(this.FilePath) || !File.Exists(this.FilePath)) + return false; + + // Read the file data.. + this.FileData = File.ReadAllBytes(this.FilePath); + + // Ensure we have valid data by the overall length.. + if (this.FileData.Length < (Marshal.SizeOf(typeof(NativeApi32.ImageDosHeader32)) + Marshal.SizeOf(typeof(NativeApi32.ImageNtHeaders32)))) + return false; + + // Read the file headers.. + this.DosHeader = Pe32Helpers.GetStructure(this.FileData); + this.NtHeaders = Pe32Helpers.GetStructure(this.FileData, this.DosHeader.e_lfanew); + + // Validate the headers.. + if (!this.DosHeader.IsValid || !this.NtHeaders.IsValid) + return false; + + // Read and store the dos header if it exists.. + this.DosStubSize = (uint)(this.DosHeader.e_lfanew - Marshal.SizeOf(typeof(NativeApi32.ImageDosHeader32))); + if (this.DosStubSize > 0) + { + this.DosStubOffset = (uint)Marshal.SizeOf(typeof(NativeApi32.ImageDosHeader32)); + this.DosStubData = new byte[this.DosStubSize]; + Array.Copy(this.FileData, this.DosStubOffset, this.DosStubData, 0, this.DosStubSize); + } + + // Read the file sections.. + for (var x = 0; x < this.NtHeaders.FileHeader.NumberOfSections; x++) + { + var section = Pe32Helpers.GetSection(this.FileData, x, this.DosHeader, this.NtHeaders); + this.Sections.Add(section); + + // Get the sections data.. + var sectionData = new byte[this.GetAlignment(section.SizeOfRawData, this.NtHeaders.OptionalHeader.FileAlignment)]; + Array.Copy(this.FileData, section.PointerToRawData, sectionData, 0, section.SizeOfRawData); + this.SectionData.Add(sectionData); + } + + try + { + // Obtain the file overlay if one exists.. + var lastSection = this.Sections.Last(); + var fileSize = lastSection.SizeOfRawData + lastSection.PointerToRawData; + if (fileSize < this.FileData.Length) + { + this.OverlayData = new byte[this.FileData.Length - fileSize]; + Array.Copy(this.FileData, fileSize, this.OverlayData, 0, this.FileData.Length - fileSize); + } + } + catch + { + return false; + } + + // Read the files Tls information if available.. + if (this.NtHeaders.OptionalHeader.TLSTable.VirtualAddress != 0) + { + // Get the file offset to the Tls data.. + var tls = this.NtHeaders.OptionalHeader.TLSTable; + var addr = this.GetFileOffsetFromRva(tls.VirtualAddress); + + // Read the Tls directory.. + this.TlsDirectory = Pe32Helpers.GetStructure(this.FileData, (int)addr); + + // Read the Tls callbacks.. + addr = this.GetRvaFromVa(this.TlsDirectory.AddressOfCallBacks); + addr = this.GetFileOffsetFromRva(addr); + + // Loop until we hit a null pointer.. + var count = 0; + while (true) + { + var callback = BitConverter.ToUInt32(this.FileData, (int)addr + (count * 4)); + if (callback == 0) + break; + + this.TlsCallbacks.Add(callback); + count++; + } + } + + return true; + } + + /// + /// Determines if the current file is 64bit. + /// + /// + public bool IsFile64Bit() + { + return (this.NtHeaders.FileHeader.Machine & (uint)NativeApi32.MachineType.X64) == (uint)NativeApi32.MachineType.X64; + } + + /// + /// Determines if the file has a section containing the given name. + /// + /// + /// + public bool HasSection(string name) + { + return this.Sections.Any(s => string.Compare(s.SectionName, name, StringComparison.InvariantCultureIgnoreCase) == 0); + } + + /// + /// Obtains a section by its name. + /// + /// + /// + public NativeApi32.ImageSectionHeader32 GetSection(string name) + { + return this.Sections.FirstOrDefault(s => string.Compare(s.SectionName, name, StringComparison.InvariantCultureIgnoreCase) == 0); + } + + /// + /// Obtains the owner section of the given rva. + /// + /// + /// + public NativeApi32.ImageSectionHeader32 GetOwnerSection(uint rva) + { + foreach (var s in this.Sections) + { + var size = s.VirtualSize; + if (size == 0) + size = s.SizeOfRawData; + + if ((rva >= s.VirtualAddress) && (rva < s.VirtualAddress + size)) + return s; + } + + return default(NativeApi32.ImageSectionHeader32); + } + + /// + /// Obtains the owner section of the given rva. + /// + /// + /// + public NativeApi32.ImageSectionHeader32 GetOwnerSection(ulong rva) + { + foreach (var s in this.Sections) + { + var size = s.VirtualSize; + if (size == 0) + size = s.SizeOfRawData; + + if ((rva >= s.VirtualAddress) && (rva < s.VirtualAddress + size)) + return s; + } + + return default(NativeApi32.ImageSectionHeader32); + } + + /// + /// Obtains a sections data by its index. + /// + /// + /// + public byte[] GetSectionData(int index) + { + if (index < 0 || index > this.Sections.Count) + return null; + + return this.SectionData[index]; + } + + /// + /// Obtains a sections data by its name. + /// + /// + /// + public byte[] GetSectionData(string name) + { + for (var x = 0; x < this.Sections.Count; x++) + { + if (string.Compare(this.Sections[x].SectionName, name, StringComparison.InvariantCultureIgnoreCase) == 0) + return this.SectionData[x]; + } + + return null; + } + + /// + /// Gets a sections index by its name. + /// + /// + /// + public int GetSectionIndex(string name) + { + for (var x = 0; x < this.Sections.Count; x++) + { + if (string.Compare(this.Sections[x].SectionName, name, StringComparison.InvariantCultureIgnoreCase) == 0) + return x; + } + + return -1; + } + + /// + /// Gets a sections index by its name. + /// + /// + /// + public int GetSectionIndex(NativeApi32.ImageSectionHeader32 section) + { + return this.Sections.IndexOf(section); + } + + /// + /// Removes a section from the files section list. + /// + /// + /// + public bool RemoveSection(NativeApi32.ImageSectionHeader32 section) + { + var index = this.Sections.IndexOf(section); + if (index == -1) + return false; + + this.Sections.RemoveAt(index); + this.SectionData.RemoveAt(index); + + return true; + } + + /// + /// Rebuilds the sections by aligning them as needed. Updates the Nt headers to + /// correct the new SizeOfImage after alignment is completed. + /// + public void RebuildSections() + { + for (var x = 0; x < this.Sections.Count; x++) + { + // Obtain the current section and realign the data.. + var section = this.Sections[x]; + section.VirtualAddress = this.GetAlignment(section.VirtualAddress, this.NtHeaders.OptionalHeader.SectionAlignment); + section.VirtualSize = this.GetAlignment(section.VirtualSize, this.NtHeaders.OptionalHeader.SectionAlignment); + section.PointerToRawData = this.GetAlignment(section.PointerToRawData, this.NtHeaders.OptionalHeader.FileAlignment); + section.SizeOfRawData = this.GetAlignment(section.SizeOfRawData, this.NtHeaders.OptionalHeader.FileAlignment); + + // Store the sections updates.. + this.Sections[x] = section; + } + + // Update the size of the image.. + var ntHeaders = this.NtHeaders; + ntHeaders.OptionalHeader.SizeOfImage = this.Sections.Last().VirtualAddress + this.Sections.Last().VirtualSize; + this.NtHeaders = ntHeaders; + } + + /// + /// Obtains the relative virtual address from the given virtual address. + /// + /// + /// + public uint GetRvaFromVa(uint va) + { + return va - this.NtHeaders.OptionalHeader.ImageBase; + } + + /// + /// Obtains the file offset from the given relative virtual address. + /// + /// + /// + public uint GetFileOffsetFromRva(uint rva) + { + var section = this.GetOwnerSection(rva); + return (rva - (section.VirtualAddress - section.PointerToRawData)); + } + + /// + /// Aligns the value based on the given alignment. + /// + /// + /// + /// + public uint GetAlignment(uint val, uint align) + { + return (((val + align - 1) / align) * align); + } + + /// + /// Gets or sets the path to the file being processed. + /// + public string FilePath { get; set; } + + /// + /// Gets or sets the raw file data read from disk. + /// + public byte[] FileData { get; set; } + + /// + /// Gets or sets the dos header of the file. + /// + public NativeApi32.ImageDosHeader32 DosHeader { get; set; } + + /// + /// Gets or sets the NT headers of the file. + /// + public NativeApi32.ImageNtHeaders32 NtHeaders { get; set; } + + /// + /// Gets or sets the optional dos stub size. + /// + public uint DosStubSize { get; set; } + + /// + /// Gets or sets the optional dos stub offset. + /// + public uint DosStubOffset { get; set; } + + /// + /// Gets or sets the optional dos stub data. + /// + public byte[] DosStubData { get; set; } + + /// + /// Gets or sets the sections of the file. + /// + public List Sections; + + /// + /// Gets or sets the section data of the file. + /// + public List SectionData; + + /// + /// Gets or sets the overlay data of the file. + /// + public byte[] OverlayData; + + /// + /// Gets or sets the Tls directory of the file. + /// + public NativeApi32.ImageTlsDirectory32 TlsDirectory { get; set; } + + /// + /// Gets or sets a list of Tls callbacks of the file. + /// + public List TlsCallbacks { get; set; } + } +} \ No newline at end of file diff --git a/Steamless.API/PE32/Pe32Helpers.cs b/Steamless.API/PE32/Pe32Helpers.cs new file mode 100644 index 0000000..0a2aa68 --- /dev/null +++ b/Steamless.API/PE32/Pe32Helpers.cs @@ -0,0 +1,128 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.PE32 +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Runtime.InteropServices; + + public class Pe32Helpers + { + /// + /// Converts a byte array to the given structure type. + /// + /// + /// + /// + /// + public static T GetStructure(byte[] data, int offset = 0) + { + var ptr = Marshal.AllocHGlobal(data.Length); + Marshal.Copy(data, offset, ptr, data.Length - offset); + var obj = (T)Marshal.PtrToStructure(ptr, typeof(T)); + Marshal.FreeHGlobal(ptr); + + return obj; + } + + /// + /// Converts the given object back to a byte array. + /// + /// + /// + /// + public static byte[] GetStructureBytes(T obj) + { + var size = Marshal.SizeOf(obj); + var data = new byte[size]; + var ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(obj, ptr, true); + Marshal.Copy(ptr, data, 0, size); + Marshal.FreeHGlobal(ptr); + return data; + } + + /// + /// Obtains a section from the given file information. + /// + /// + /// + /// + /// + /// + public static NativeApi32.ImageSectionHeader32 GetSection(byte[] rawData, int index, NativeApi32.ImageDosHeader32 dosHeader, NativeApi32.ImageNtHeaders32 ntHeaders) + { + var sectionSize = Marshal.SizeOf(typeof(NativeApi32.ImageSectionHeader32)); + var optionalHeaderOffset = Marshal.OffsetOf(typeof(NativeApi32.ImageNtHeaders32), "OptionalHeader").ToInt32(); + var dataOffset = dosHeader.e_lfanew + optionalHeaderOffset + ntHeaders.FileHeader.SizeOfOptionalHeader; + + return GetStructure(rawData, dataOffset + (index * sectionSize)); + } + + /// + /// Scans the given data for the given pattern. + /// + /// Notes: + /// Patterns are assumed to be 2 byte hex values with spaces. + /// Wildcards are represented by ??. + /// + /// + /// + /// + public static uint FindPattern(byte[] data, string pattern) + { + try + { + // Trim the pattern from extra whitespace.. + var trimPattern = pattern.Replace(" ", "").Trim(); + + // Convert the pattern to a byte array.. + var patternMask = new List(); + var patternData = Enumerable.Range(0, trimPattern.Length).Where(x => x % 2 == 0) + .Select(x => + { + var bt = trimPattern.Substring(x, 2); + patternMask.Add(!bt.Contains('?')); + return bt.Contains('?') ? (byte)0 : Convert.ToByte(bt, 16); + }).ToArray(); + + // Scan the given data for our pattern.. + for (var x = 0; x < data.Length; x++) + { + if (!patternData.Where((t, y) => patternMask[y] && t != data[x + y]).Any()) + return (uint)x; + } + + return 0; + } + catch + { + return 0; + } + } + } +} \ No newline at end of file diff --git a/Steamless.API/Properties/AssemblyInfo.cs b/Steamless.API/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d9a4d96 --- /dev/null +++ b/Steamless.API/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Steamless.API")] +[assembly: AssemblyDescription("SteamStub API Module")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCompany("atom0s")] +[assembly: AssemblyProduct("Steamless.API")] +[assembly: AssemblyCopyright("Copyright © atom0s 2015 - 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("56c95629-3b34-47fe-b988-04274409294f")] +[assembly: AssemblyVersion("1.0.0.1")] +[assembly: AssemblyFileVersion("1.0.0.1")] \ No newline at end of file diff --git a/Steamless.API/Services/LoggingService.cs b/Steamless.API/Services/LoggingService.cs new file mode 100644 index 0000000..57a7732 --- /dev/null +++ b/Steamless.API/Services/LoggingService.cs @@ -0,0 +1,63 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API.Services +{ + using Events; + using System; + + public class LoggingService + { + /// + /// Adds a log message to the logging pane of Steamless. + /// + public event SteamlessEvents.AddLogMessageEventHandler AddLogMessage; + + /// + /// Clears the logging pane of Steamless. + /// + public event SteamlessEvents.ClearLogMessagesEventHandler ClearLogMessages; + + /// + /// Invokes the AddLogMessage event to add a log message to Steamless. + /// + /// + /// + public virtual void OnAddLogMessage(object sender, LogMessageEventArgs e) + { + this.AddLogMessage?.Invoke(sender, e); + } + + /// + /// Invokes the ClearLogMessages event to remove all current log messages from Steamless. + /// + /// + /// + public virtual void OnClearLogMessages(object sender, EventArgs e) + { + this.ClearLogMessages?.Invoke(sender, e); + } + } +} \ No newline at end of file diff --git a/Steamless.API/Steamless.API.csproj b/Steamless.API/Steamless.API.csproj new file mode 100644 index 0000000..3f8fe1a --- /dev/null +++ b/Steamless.API/Steamless.API.csproj @@ -0,0 +1,68 @@ + + + + + Debug + AnyCPU + {56C95629-3B34-47FE-B988-04274409294F} + Library + Properties + Steamless.API + Steamless.API + v4.5.2 + 512 + + + x86 + ..\Steamless\bin\x86\Debug\Plugins\ + TRACE;DEBUG + + + x86 + ..\Steamless\bin\x86\Release\Plugins\ + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Steamless.API/SteamlessApiVersionAttribute.cs b/Steamless.API/SteamlessApiVersionAttribute.cs new file mode 100644 index 0000000..9668968 --- /dev/null +++ b/Steamless.API/SteamlessApiVersionAttribute.cs @@ -0,0 +1,48 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API +{ + using System; + + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class SteamlessApiVersionAttribute : Attribute + { + /// + /// Default Constructor + /// + /// + /// + public SteamlessApiVersionAttribute(int major, int minor) + { + this.Version = new Version(major, minor); + } + + /// + /// Gets or sets the API version of this attribute. + /// + public Version Version { get; internal set; } + } +} \ No newline at end of file diff --git a/Steamless.API/SteamlessEvents.cs b/Steamless.API/SteamlessEvents.cs new file mode 100644 index 0000000..2697ee2 --- /dev/null +++ b/Steamless.API/SteamlessEvents.cs @@ -0,0 +1,47 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.API +{ + using Events; + using System; + + public class SteamlessEvents + { + /// + /// Log message event handler. + /// + /// + /// + public delegate void AddLogMessageEventHandler(object sender, LogMessageEventArgs e); + + /// + /// Clear log messages event handler. + /// + /// + /// + public delegate void ClearLogMessagesEventHandler(object sender, EventArgs e); + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant20.x86/Classes/SteamStubDrmFlags.cs b/Steamless.Unpacker.Variant20.x86/Classes/SteamStubDrmFlags.cs new file mode 100644 index 0000000..667289b --- /dev/null +++ b/Steamless.Unpacker.Variant20.x86/Classes/SteamStubDrmFlags.cs @@ -0,0 +1,38 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant20.x86.Classes +{ + /// + /// Steam Stub Variant 2.0 DRM Flags + /// + public enum DrmFlags + { + NoModuleVerification = 0x02, + NoEncryption = 0x04, + NoOwnershipCheck = 0x10, + NoDebuggerCheck = 0x20, + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant20.x86/Classes/SteamStubHeader.cs b/Steamless.Unpacker.Variant20.x86/Classes/SteamStubHeader.cs new file mode 100644 index 0000000..e8406b2 --- /dev/null +++ b/Steamless.Unpacker.Variant20.x86/Classes/SteamStubHeader.cs @@ -0,0 +1,60 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant20.x86.Classes +{ + using System.Runtime.InteropServices; + + /// + /// SteamStub DRM Variant 2.0 Header + /// + [StructLayout(LayoutKind.Sequential)] + public struct SteamStub32Var20Header + { + public uint XorKey; // The base XOR key, if defined, to unpack the file with. + public uint GetModuleHandleA_idata; // The address of GetModuleHandleA inside of the .idata section. + public uint GetModuleHandleW_idata; // The address of GetModuleHandleW inside of the .idata section. + public uint GetProcAddress_idata; // The address of GetProcAddress inside of the .idata section. + public uint LoadLibraryA_idata; // The address of LoadLibraryA inside of the .idata section. + public uint Unknown0000; // Unknown (Was 0 when testing. Possibly LoadLibraryW.) + public uint BindSectionVirtualAddress; // The virtual address to the .bind section. + public uint BindStartFunctionSize; // The size of the start function from the .bind section. + public uint PayloadKeyMatch; // The key inside of the SteamDRMP.dll file that is matched to this structures data. (This matches the first 4 bytes of the payload data.) + public uint PayloadDataVirtualAddress; // The virtual address to the payload data. + public uint PayloadDataSize; // The size of the payload data. + public uint SteamAppID; // The steam application id of the packed file. + public uint Unknown0001; // Unknown + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x08)] + public byte[] SteamAppIDString; // The SteamAppID of the packed file, in string format. + + public uint SteamDRMPDllVirtualAddress; // The offset inside of the payload data holding the virtual address to the SteamDRMP.dll file data. + public uint SteamDRMPDllSize; // The offset inside of the payload data holding the size of the SteamDRMP.dll file data. + public uint XTeaKeys; // The offset inside of the payload data holding the address to the Xtea keys to decrypt the SteamDRMP.dll file. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x31C)] + public byte[] StubData; // Misc stub data, such as strings, error messages, etc. + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant20.x86/Classes/SteamStubHelpers.cs b/Steamless.Unpacker.Variant20.x86/Classes/SteamStubHelpers.cs new file mode 100644 index 0000000..7a6610e --- /dev/null +++ b/Steamless.Unpacker.Variant20.x86/Classes/SteamStubHelpers.cs @@ -0,0 +1,122 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant20.x86.Classes +{ + using System; + + public static class SteamStubHelpers + { + /// + /// Xor decrypts the given data starting with the given key, if any. + /// + /// @note If no key is given (0) then the first key is read from the first + /// 4 bytes inside of the data given. + /// + /// The data to xor decode. + /// The size of the data to decode. + /// The starting xor key to decode with. + /// + public static uint SteamXor(ref byte[] data, uint size, uint key = 0) + { + var offset = (uint)0; + + // Read the first key as the base xor key if we had none given.. + if (key == 0) + { + offset += 4; + key = BitConverter.ToUInt32(data, 0); + } + + // Decode the data.. + for (var x = offset; x < size; x += 4) + { + var val = BitConverter.ToUInt32(data, (int)x); + Array.Copy(BitConverter.GetBytes(val ^ key), 0, data, x, 4); + + key = val; + } + + return key; + } + + /// + /// The second pass of decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. + /// + /// The result value buffer to write our returns to. + /// The keys used for the decryption. + /// The first value to decrypt from. + /// The second value to decrypt from. + /// The number of passes to crypt the data with. + public static void SteamDrmpDecryptPass2(ref uint[] res, uint[] keys, uint v1, uint v2, uint n = 32) + { + const uint delta = 0x9E3779B9; + const uint mask = 0xFFFFFFFF; + var sum = (delta * n) & mask; + + for (var x = 0; x < n; x++) + { + v2 = (v2 - (((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + keys[sum >> 11 & 3]))) & mask; + sum = (sum - delta) & mask; + v1 = (v1 - (((v2 << 4 ^ v2 >> 5) + v2) ^ (sum + keys[sum & 3]))) & mask; + } + + res[0] = v1; + res[1] = v2; + } + + /// + /// The first pass of the decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. It is modded to include + /// some basic xor'ing. + /// + /// The data to decrypt. + /// The size of the data to decrypt. + /// The keys used for the decryption. + public static void SteamDrmpDecryptPass1(ref byte[] data, uint size, uint[] keys) + { + var v1 = (uint)0x55555555; + var v2 = (uint)0x55555555; + + for (var x = 0; x < size; x += 8) + { + var d1 = BitConverter.ToUInt32(data, x + 0); + var d2 = BitConverter.ToUInt32(data, x + 4); + + var res = new uint[2]; + SteamDrmpDecryptPass2(ref res, keys, d1, d2); + + Array.Copy(BitConverter.GetBytes(res[0] ^ v1), 0, data, x + 0, 4); + Array.Copy(BitConverter.GetBytes(res[1] ^ v2), 0, data, x + 4, 4); + + v1 = d1; + v2 = d2; + } + } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant20.x86/Main.cs b/Steamless.Unpacker.Variant20.x86/Main.cs new file mode 100644 index 0000000..b1b6811 --- /dev/null +++ b/Steamless.Unpacker.Variant20.x86/Main.cs @@ -0,0 +1,632 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant20.x86 +{ + using API; + using API.Crypto; + using API.Events; + using API.Extensions; + using API.Model; + using API.PE32; + using API.Services; + using Classes; + using SharpDisasm; + using SharpDisasm.Udis86; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Security.Cryptography; + + [SteamlessApiVersion(1, 0)] + public class Main : SteamlessPlugin + { + /// + /// Internal logging service instance. + /// + private LoggingService m_LoggingService; + + /// + /// Gets the author of this plugin. + /// + public override string Author => "atom0s"; + + /// + /// Gets the name of this plugin. + /// + public override string Name => "SteamStub Variant 2.0 Unpacker (x86)"; + + /// + /// Gets the description of this plugin. + /// + public override string Description => "Unpacker for the 32bit SteamStub variant 2.0."; + + /// + /// Gets the version of this plugin. + /// + public override Version Version => new Version(1, 0, 0, 0); + + /// + /// Internal wrapper to log a message. + /// + /// + /// + private void Log(string msg, LogMessageType type) + { + this.m_LoggingService.OnAddLogMessage(this, new LogMessageEventArgs(msg, type)); + } + + /// + /// Initialize function called when this plugin is first loaded. + /// + /// + /// + public override bool Initialize(LoggingService logService) + { + this.m_LoggingService = logService; + return true; + } + + /// + /// Processing function called when a file is being unpacked. Allows plugins to check the file + /// and see if it can handle the file for its intended purpose. + /// + /// + /// + public override bool CanProcessFile(string file) + { + try + { + // Load the file.. + var f = new Pe32File(file); + if (!f.Parse() || f.IsFile64Bit() || !f.HasSection(".bind")) + return false; + + // Obtain the bind section data.. + var bind = f.GetSectionData(".bind"); + + // Attempt to locate the known v2.x signature.. + return Pe32Helpers.FindPattern(bind, "53 51 52 56 57 55 8B EC 81 EC 00 10 00 00 C7") > 0; + } + catch + { + return false; + } + } + + /// + /// Processing function called to allow the plugin to process the file. + /// + /// + /// + /// + public override bool ProcessFile(string file, SteamlessOptions options) + { + // Initialize the class members.. + this.Options = options; + this.CodeSectionData = null; + this.CodeSectionIndex = -1; + this.PayloadData = null; + this.SteamDrmpData = null; + this.SteamDrmpOffsets = new List(); + this.XorKey = 0; + + // Parse the file.. + this.File = new Pe32File(file); + if (!this.File.Parse()) + return false; + + // Announce we are being unpacked with this packer.. + this.Log("File is packed with SteamStub Variant 2.0!", LogMessageType.Information); + + this.Log("Step 1 - Read, disassemble and decode the SteamStub DRM header.", LogMessageType.Information); + if (!this.Step1()) + return false; + + this.Log("Step 2 - Read, decode and process the payload data.", LogMessageType.Information); + if (!this.Step2()) + return false; + + this.Log("Step 3 - Read, decode and dump the SteamDRMP.dll file.", LogMessageType.Information); + if (!this.Step3()) + return false; + + this.Log("Step 4 - Scan, dump and pull needed offsets from within the SteamDRMP.dll file.", LogMessageType.Information); + if (!this.Step4()) + return false; + + this.Log("Step 5 - Read, decrypt and process the main code section.", LogMessageType.Information); + if (!this.Step5()) + return false; + + this.Log("Step 6 - Rebuild and save the unpacked file.", LogMessageType.Information); + if (!this.Step6()) + return false; + + return true; + } + + /// + /// Step #1 + /// + /// Read, disassemble and decode the SteamStub DRM header. + /// + /// + private bool Step1() + { + // Obtain the file entry offset.. + var fileOffset = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint); + + // Validate the DRM header.. + if (BitConverter.ToUInt32(this.File.FileData, (int)fileOffset - 4) != 0xC0DEC0DE) + return false; + + int structOffset; + int structSize; + int structXorKey; + + // Disassemble the file to locate the needed DRM information.. + if (!this.DisassembleFile(out structOffset, out structSize, out structXorKey)) + return false; + + // Obtain the DRM header data.. + var headerData = new byte[structSize]; + Array.Copy(this.File.FileData, this.File.GetFileOffsetFromRva((uint)structOffset), headerData, 0, structSize); + + // Xor decode the header data.. + this.XorKey = SteamStubHelpers.SteamXor(ref headerData, (uint)headerData.Length, (uint)structXorKey); + this.StubHeader = Pe32Helpers.GetStructure(headerData); + + return true; + } + + /// + /// Step #2 + /// + /// Read, decode and process the payload data. + /// + /// + private bool Step2() + { + // Obtain the payload address and size.. + var payloadAddr = this.File.GetFileOffsetFromRva(this.File.GetRvaFromVa(this.StubHeader.PayloadDataVirtualAddress)); + var payloadData = new byte[this.StubHeader.PayloadDataSize]; + Array.Copy(this.File.FileData, payloadAddr, payloadData, 0, this.StubHeader.PayloadDataSize); + + // Decode the payload data.. + this.XorKey = SteamStubHelpers.SteamXor(ref payloadData, this.StubHeader.PayloadDataSize, this.XorKey); + this.PayloadData = payloadData; + + try + { + if (this.Options.DumpPayloadToDisk) + { + System.IO.File.WriteAllBytes(this.File.FilePath + ".payload", payloadData); + this.Log(" --> Saved payload to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + + /// + /// Step #3 + /// + /// Read, decode and dump the SteamDRMP.dll file. + /// + /// + private bool Step3() + { + this.Log(" --> File has SteamDRMP.dll file!", LogMessageType.Debug); + + try + { + // Obtain the SteamDRMP.dll file address and data.. + var drmpAddr = this.File.GetFileOffsetFromRva(this.File.GetRvaFromVa(BitConverter.ToUInt32(this.PayloadData, (int)this.StubHeader.SteamDRMPDllVirtualAddress))); + var drmpSize = BitConverter.ToUInt32(this.PayloadData, (int)this.StubHeader.SteamDRMPDllSize); + var drmpData = new byte[drmpSize]; + Array.Copy(this.File.FileData, drmpAddr, drmpData, 0, drmpSize); + + // Obtain the XTea encryption keys.. + var xteyKeys = new uint[(this.PayloadData.Length - this.StubHeader.XTeaKeys) / 4]; + for (var x = 0; x < (this.PayloadData.Length - this.StubHeader.XTeaKeys) / 4; x++) + xteyKeys[x] = BitConverter.ToUInt32(this.PayloadData, (int)this.StubHeader.XTeaKeys + (x * 4)); + + // Decrypt the file data.. + SteamStubHelpers.SteamDrmpDecryptPass1(ref drmpData, drmpSize, xteyKeys); + this.SteamDrmpData = drmpData; + + try + { + if (this.Options.DumpSteamDrmpToDisk) + { + var basePath = Path.GetDirectoryName(this.File.FilePath) ?? string.Empty; + System.IO.File.WriteAllBytes(Path.Combine(basePath, "SteamDRMP.dll"), drmpData); + this.Log(" --> Saved SteamDRMP.dll to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + catch + { + this.Log(" --> Error trying to decrypt the files SteamDRMP.dll data!", LogMessageType.Error); + return false; + } + } + + /// + /// Step #4 + /// + /// Scan, dump and pull needed offsets from within the SteamDRMP.dll file. + /// + /// + private bool Step4() + { + // Scan for the needed data by a known pattern for the block of offset data.. + var drmpOffset = Pe32Helpers.FindPattern(this.SteamDrmpData, "8B ?? ?? ?? ?? ?? 89 ?? ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 89 ?? ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 89 ?? ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 89 ?? ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 89 ?? ?? ?? ?? ?? 8D ?? ?? ?? ?? ?? 05"); + if (drmpOffset == 0) + return false; + + // Copy the block of data from the SteamDRMP.dll data.. + var drmpOffsetData = new byte[1024]; + Array.Copy(this.SteamDrmpData, drmpOffset, drmpOffsetData, 0, 1024); + + // Obtain the offsets from the file data.. + var drmpOffsets = this.GetSteamDrmpOffsets(drmpOffsetData); + if (drmpOffsets.Count != 8) + return false; + + // Store the offsets.. + this.SteamDrmpOffsets = drmpOffsets; + + return true; + } + + /// + /// Step #5 + /// + /// Read, decrypt and process the main code section. + /// + /// + private bool Step5() + { + // Remove the bind section if its not requested to be saved.. + if (!this.Options.KeepBindSection) + { + // Obtain the .bind section.. + var bindSection = this.File.GetSection(".bind"); + if (!bindSection.IsValid) + return false; + + // Remove the section.. + this.File.RemoveSection(bindSection); + + // Decrease the header section count.. + var ntHeaders = this.File.NtHeaders; + ntHeaders.FileHeader.NumberOfSections--; + this.File.NtHeaders = ntHeaders; + + this.Log(" --> .bind section was removed from the file.", LogMessageType.Debug); + } + else + this.Log(" --> .bind section was kept in the file.", LogMessageType.Debug); + + byte[] codeSectionData; + + // Obtain the main code section (typically .text).. + var mainSection = this.File.GetOwnerSection(this.File.GetRvaFromVa(BitConverter.ToUInt32(this.PayloadData.Skip(this.SteamDrmpOffsets[3]).Take(4).ToArray(), 0))); + if (mainSection.PointerToRawData == 0 || mainSection.SizeOfRawData == 0) + return false; + + this.Log($" --> {mainSection.SectionName} linked as main code section.", LogMessageType.Debug); + + // Save the code section index for later use.. + this.CodeSectionIndex = this.File.GetSectionIndex(mainSection); + + // Determine if we are using encryption on the section.. + var flags = BitConverter.ToUInt32(this.PayloadData.Skip(this.SteamDrmpOffsets[0]).Take(4).ToArray(), 0); + if ((flags & (uint)DrmFlags.NoEncryption) == (uint)DrmFlags.NoEncryption) + { + this.Log($" --> {mainSection.SectionName} section is not encrypted.", LogMessageType.Debug); + + // No encryption was used, just read the original data.. + codeSectionData = new byte[mainSection.SizeOfRawData]; + Array.Copy(this.File.FileData, this.File.GetFileOffsetFromRva(mainSection.VirtualAddress), codeSectionData, 0, mainSection.SizeOfRawData); + } + else + { + this.Log($" --> {mainSection.SectionName} section is encrypted.", LogMessageType.Debug); + + try + { + // Encryption was used, obtain the encryption information.. + var aesKey = this.PayloadData.Skip(this.SteamDrmpOffsets[5]).Take(32).ToArray(); + var aesIv = this.PayloadData.Skip(this.SteamDrmpOffsets[6]).Take(16).ToArray(); + var codeStolen = this.PayloadData.Skip(this.SteamDrmpOffsets[7]).Take(16).ToArray(); + + // Restore the stolen data then read the rest of the section data.. + codeSectionData = new byte[mainSection.SizeOfRawData + codeStolen.Length]; + Array.Copy(codeStolen, 0, codeSectionData, 0, codeStolen.Length); + Array.Copy(this.File.FileData, this.File.GetFileOffsetFromRva(mainSection.VirtualAddress), codeSectionData, codeStolen.Length, mainSection.SizeOfRawData); + + // Decrypt the code section.. + var aes = new AesHelper(aesKey, aesIv); + aes.RebuildIv(aesIv); + codeSectionData = aes.Decrypt(codeSectionData, CipherMode.CBC, PaddingMode.None); + } + catch + { + this.Log(" --> Error trying to decrypt the files code section data!", LogMessageType.Error); + return false; + } + } + + // Store the section data.. + this.CodeSectionData = codeSectionData; + + return true; + } + + /// + /// Step #6 + /// + /// Rebuild and save the unpacked file. + /// + /// + private bool Step6() + { + FileStream fStream = null; + + try + { + // Rebuild the file sections.. + this.File.RebuildSections(); + + // Open the unpacked file for writing.. + var unpackedPath = this.File.FilePath + ".unpacked.exe"; + fStream = new FileStream(unpackedPath, FileMode.Create, FileAccess.ReadWrite); + + // Write the DOS header to the file.. + fStream.WriteBytes(Pe32Helpers.GetStructureBytes(this.File.DosHeader)); + + // Write the DOS stub to the file.. + if (this.File.DosStubSize > 0) + fStream.WriteBytes(this.File.DosStubData); + + // Update the NT headers.. + var ntHeaders = this.File.NtHeaders; + var lastSection = this.File.Sections[this.File.Sections.Count - 1]; + var originalEntry = BitConverter.ToUInt32(this.PayloadData.Skip(this.SteamDrmpOffsets[2]).Take(4).ToArray(), 0); + ntHeaders.OptionalHeader.AddressOfEntryPoint = this.File.GetRvaFromVa(originalEntry); + ntHeaders.OptionalHeader.SizeOfImage = lastSection.VirtualAddress + lastSection.VirtualSize; + this.File.NtHeaders = ntHeaders; + + // Write the NT headers to the file.. + fStream.WriteBytes(Pe32Helpers.GetStructureBytes(ntHeaders)); + + // Write the sections to the file.. + for (var x = 0; x < this.File.Sections.Count; x++) + { + var section = this.File.Sections[x]; + var sectionData = this.File.SectionData[x]; + + // Write the section header to the file.. + fStream.WriteBytes(Pe32Helpers.GetStructureBytes(section)); + + // Set the file pointer to the sections raw data.. + var sectionOffset = fStream.Position; + fStream.Position = section.PointerToRawData; + + // Write the sections raw data.. + var sectionIndex = this.File.Sections.IndexOf(section); + if (sectionIndex == this.CodeSectionIndex) + fStream.WriteBytes(this.CodeSectionData ?? sectionData); + else + fStream.WriteBytes(sectionData); + + // Reset the file offset.. + fStream.Position = sectionOffset; + } + + // Set the stream to the end of the file.. + fStream.Position = fStream.Length; + + // Write the overlay data if it exists.. + if (this.File.OverlayData != null) + fStream.WriteBytes(this.File.OverlayData); + + this.Log(" --> Unpacked file saved to disk!", LogMessageType.Success); + this.Log($" --> File Saved As: {unpackedPath}", LogMessageType.Success); + + return true; + } + catch + { + this.Log(" --> Error trying to save unpacked file!", LogMessageType.Error); + return false; + } + finally + { + fStream?.Dispose(); + } + } + + /// + /// Disassembles the file to locate the needed DRM header information. + /// + /// + /// + /// + /// + private bool DisassembleFile(out int offset, out int size, out int xorKey) + { + // Prepare our needed variables.. + Disassembler disasm = null; + var dataPointer = IntPtr.Zero; + var structOffset = 0; + var structSize = 0; + var structXorKey = 0; + + // Determine the entry offset of the file.. + var entryOffset = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint); + + try + { + // Copy the file data to memory for disassembling.. + dataPointer = Marshal.AllocHGlobal(this.File.FileData.Length); + Marshal.Copy(this.File.FileData, 0, dataPointer, this.File.FileData.Length); + + // Create an offset pointer to our .bind function start.. + var startPointer = IntPtr.Add(dataPointer, (int)entryOffset); + + // Create the disassembler.. + Disassembler.Translator.IncludeAddress = true; + Disassembler.Translator.IncludeBinary = true; + + disasm = new Disassembler(startPointer, 4096, ArchitectureMode.x86_32, entryOffset); + + // Disassemble our function.. + foreach (var inst in disasm.Disassemble().Where(inst => !inst.Error)) + { + // If all values are found, return successfully.. + if (structOffset > 0 && structSize > 0 && structXorKey > 0) + { + offset = structOffset; + size = structSize; + xorKey = structXorKey; + return true; + } + + // Looks for: mov dword ptr [value], immediate + if (inst.Mnemonic == ud_mnemonic_code.UD_Imov && inst.Operands[0].Type == ud_type.UD_OP_MEM && inst.Operands[1].Type == ud_type.UD_OP_IMM) + { + if (structOffset == 0) + structOffset = inst.Operands[1].LvalSDWord - (int)this.File.NtHeaders.OptionalHeader.ImageBase; + else + structXorKey = inst.Operands[1].LvalSDWord; + } + + // Looks for: mov reg, immediate + if (inst.Mnemonic == ud_mnemonic_code.UD_Imov && inst.Operands[0].Type == ud_type.UD_OP_REG && inst.Operands[1].Type == ud_type.UD_OP_IMM) + structSize = inst.Operands[1].LvalSDWord * 4; + } + + offset = size = xorKey = 0; + return false; + } + catch + { + offset = size = xorKey = 0; + return false; + } + finally + { + disasm?.Dispose(); + if (dataPointer != IntPtr.Zero) + Marshal.FreeHGlobal(dataPointer); + } + } + + /// + /// Obtains the needed DRM offsets from the SteamDRMP.dll file. + /// + /// + /// + private List GetSteamDrmpOffsets(byte[] data) + { + var offsets = new List + { + BitConverter.ToInt32(data, 2), // .... 0 - Flags + BitConverter.ToInt32(data, 14), // ... 1 - Steam App Id + BitConverter.ToInt32(data, 26), // ... 2 - OEP + BitConverter.ToInt32(data, 38), // ... 3 - Code Section Virtual Address + BitConverter.ToInt32(data, 50), // ... 4 - Code Section Virtual Size (Encrypted Size) + BitConverter.ToInt32(data, 62) // .... 5 - Code Section AES Key + }; + + var aesIvOffset = BitConverter.ToInt32(data, 67); + offsets.Add(aesIvOffset); // ................. 6 - Code Section AES Iv + offsets.Add(aesIvOffset + 16); // ............ 7 - Code Section Stolen Bytes + + return offsets; + } + + /// + /// Gets or sets the Steamless options this file was requested to process with. + /// + private SteamlessOptions Options { get; set; } + + /// + /// Gets or sets the file being processed. + /// + private Pe32File File { get; set; } + + /// + /// Gets or sets the current xor key being used against the file data. + /// + private uint XorKey { get; set; } + + /// + /// Gets or sets the DRM stub header. + /// + private SteamStub32Var20Header StubHeader { get; set; } + + /// + /// Gets or sets the payload data. + /// + public byte[] PayloadData { get; set; } + + /// + /// Gets or sets the SteamDRMP.dll data. + /// + public byte[] SteamDrmpData { get; set; } + + /// + /// Gets or sets the list of SteamDRMP.dll offsets. + /// + public List SteamDrmpOffsets { get; set; } + + /// + /// Gets or sets the index of the code section. + /// + private int CodeSectionIndex { get; set; } + + /// + /// Gets or sets the decrypted code section data. + /// + private byte[] CodeSectionData { get; set; } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant20.x86/Properties/AssemblyInfo.cs b/Steamless.Unpacker.Variant20.x86/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..17499f3 --- /dev/null +++ b/Steamless.Unpacker.Variant20.x86/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Steamless.Unpacker.Variant20.x86")] +[assembly: AssemblyDescription("Steamless SteamStub Variant v2.0 (x86) Unpacker")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCompany("atom0s")] +[assembly: AssemblyProduct("Steamless.Unpacker.Variant20.x86")] +[assembly: AssemblyCopyright("Copyright © atom0s 2015 - 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("a40154cd-a0fd-4371-8099-ce277e0989af")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Steamless.Unpacker.Variant20.x86/SharpDisasm.dll b/Steamless.Unpacker.Variant20.x86/SharpDisasm.dll new file mode 100644 index 0000000..950c59d Binary files /dev/null and b/Steamless.Unpacker.Variant20.x86/SharpDisasm.dll differ diff --git a/Steamless.Unpacker.Variant20.x86/Steamless.Unpacker.Variant20.x86.csproj b/Steamless.Unpacker.Variant20.x86/Steamless.Unpacker.Variant20.x86.csproj new file mode 100644 index 0000000..f272880 --- /dev/null +++ b/Steamless.Unpacker.Variant20.x86/Steamless.Unpacker.Variant20.x86.csproj @@ -0,0 +1,60 @@ + + + + + Debug + AnyCPU + {A40154CD-A0FD-4371-8099-CE277E0989AF} + Library + Properties + Steamless.Unpacker.Variant20.x86 + Steamless.Unpacker.Variant20.x86 + v4.5.2 + 512 + + + x86 + ..\Steamless\bin\x86\Debug\Plugins\ + TRACE;DEBUG + + + x86 + ..\Steamless\bin\x86\Release\Plugins\ + true + + + + .\SharpDisasm.dll + + + + + + + + + + + + + + + + + + + + {56c95629-3b34-47fe-b988-04274409294f} + Steamless.API + False + + + + + \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x86/Classes/SteamStubDrmFlags.cs b/Steamless.Unpacker.Variant30.x86/Classes/SteamStubDrmFlags.cs new file mode 100644 index 0000000..80782c2 --- /dev/null +++ b/Steamless.Unpacker.Variant30.x86/Classes/SteamStubDrmFlags.cs @@ -0,0 +1,39 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant30.x86.Classes +{ + /// + /// Steam Stub Variant 3.0 DRM Flags + /// + public enum SteamStubDrmFlags + { + NoModuleVerification = 0x02, + NoEncryption = 0x04, + NoOwnershipCheck = 0x10, + NoDebuggerCheck = 0x20, + NoErrorDialog = 0x40 + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x86/Classes/SteamStubHeader.cs b/Steamless.Unpacker.Variant30.x86/Classes/SteamStubHeader.cs new file mode 100644 index 0000000..dd216cf --- /dev/null +++ b/Steamless.Unpacker.Variant30.x86/Classes/SteamStubHeader.cs @@ -0,0 +1,81 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant30.x86.Classes +{ + using System.Runtime.InteropServices; + + /// + /// SteamStub DRM Variant 3.0 Header + /// + [StructLayout(LayoutKind.Sequential)] + public struct SteamStub32Var30Header + { + public uint XorKey; // The base XOR key, if defined, to unpack the file with. + public uint Signature; // 0xC0DEC0DE signature to validate this header is proper. + public ulong ImageBase; // The base of the image that is protected. + public uint AddressOfEntryPoint; // The entry point that is set from the DRM. + public uint BindSectionOffset; // The starting offset to the bind section data. RVA(AddressOfEntryPoint - BindSectionOffset) + public uint Unknown0000; // [Cyanic: This field is most likely the .bind code size.] + public uint OriginalEntryPoint; // The original entry point of the binary before it was protected. + public uint Unknown0001; // [Cyanic: This field is most likely an offset to a string table.] + public uint PayloadSize; // The size of the payload data. + public uint DRMPDllOffset; // The offset to the SteamDRMP.dll file. + public uint DRMPDllSize; // The size of the SteamDRMP.dll file. + public uint SteamAppId; // The Steam Application ID of this game. + public uint Flags; // The DRM flags used while creating the protected executable. + public uint BindSectionVirtualSize; // The bind section virtual size. + public uint Unknown0002; // [Cyanic: This field is most likely a hash of some sort.] + public uint CodeSectionVirtualAddress; // The cpde section virtual address. + public uint CodeSectionRawSize; // The raw size of the code section. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)] + public byte[] AES_Key; // The AES encryption key. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] AES_IV; // The AES encryption IV. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] CodeSectionStolenData; // The first 16 bytes of the code section stolen. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x04)] + public uint[] EncryptionKeys; // Encryption keys used for decrypting SteamDRMP.dll file. + + public uint Unknown0003; // [Cyanic: This field is most likely used to flag if the file has Tls data or not.] + public uint Unknown0004; + public uint Unknown0005; + public uint Unknown0006; + public uint Unknown0007; + public uint Unknown0008; + public uint GetModuleHandleA_RVA; // The RVA to GetModuleHandleA. + public uint GetModuleHandleW_RVA; // The RVA to GetModuleHandleW. + public uint LoadLibraryA_RVA; // The RVA to LoadLibraryA. + public uint LoadLibraryW_RVA; // The RVA to LoadLibraryW. + public uint GetProcAddress_RVA; // The RVA to GetProcAddress. + public uint Unknown0009; + public uint Unknown0010; + public uint Unknown0011; + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x86/Classes/SteamStubHelpers.cs b/Steamless.Unpacker.Variant30.x86/Classes/SteamStubHelpers.cs new file mode 100644 index 0000000..59aa691 --- /dev/null +++ b/Steamless.Unpacker.Variant30.x86/Classes/SteamStubHelpers.cs @@ -0,0 +1,122 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant30.x86.Classes +{ + using System; + + public static class SteamStubHelpers + { + /// + /// Xor decrypts the given data starting with the given key, if any. + /// + /// @note If no key is given (0) then the first key is read from the first + /// 4 bytes inside of the data given. + /// + /// The data to xor decode. + /// The size of the data to decode. + /// The starting xor key to decode with. + /// + public static uint SteamXor(ref byte[] data, uint size, uint key = 0) + { + var offset = (uint)0; + + // Read the first key as the base xor key if we had none given.. + if (key == 0) + { + offset += 4; + key = BitConverter.ToUInt32(data, 0); + } + + // Decode the data.. + for (var x = offset; x < size; x += 4) + { + var val = BitConverter.ToUInt32(data, (int)x); + Array.Copy(BitConverter.GetBytes(val ^ key), 0, data, x, 4); + + key = val; + } + + return key; + } + + /// + /// The second pass of decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. + /// + /// The result value buffer to write our returns to. + /// The keys used for the decryption. + /// The first value to decrypt from. + /// The second value to decrypt from. + /// The number of passes to crypt the data with. + public static void SteamDrmpDecryptPass2(ref uint[] res, uint[] keys, uint v1, uint v2, uint n = 32) + { + const uint delta = 0x9E3779B9; + const uint mask = 0xFFFFFFFF; + var sum = (delta * n) & mask; + + for (var x = 0; x < n; x++) + { + v2 = (v2 - (((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + keys[sum >> 11 & 3]))) & mask; + sum = (sum - delta) & mask; + v1 = (v1 - (((v2 << 4 ^ v2 >> 5) + v2) ^ (sum + keys[sum & 3]))) & mask; + } + + res[0] = v1; + res[1] = v2; + } + + /// + /// The first pass of the decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. It is modded to include + /// some basic xor'ing. + /// + /// The data to decrypt. + /// The size of the data to decrypt. + /// The keys used for the decryption. + public static void SteamDrmpDecryptPass1(ref byte[] data, uint size, uint[] keys) + { + var v1 = (uint)0x55555555; + var v2 = (uint)0x55555555; + + for (var x = 0; x < size; x += 8) + { + var d1 = BitConverter.ToUInt32(data, x + 0); + var d2 = BitConverter.ToUInt32(data, x + 4); + + var res = new uint[2]; + SteamDrmpDecryptPass2(ref res, keys, d1, d2); + + Array.Copy(BitConverter.GetBytes(res[0] ^ v1), 0, data, x + 0, 4); + Array.Copy(BitConverter.GetBytes(res[1] ^ v2), 0, data, x + 4, 4); + + v1 = d1; + v2 = d2; + } + } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x86/Main.cs b/Steamless.Unpacker.Variant30.x86/Main.cs new file mode 100644 index 0000000..c556143 --- /dev/null +++ b/Steamless.Unpacker.Variant30.x86/Main.cs @@ -0,0 +1,496 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant30.x86 +{ + using API; + using API.Crypto; + using API.Events; + using API.Extensions; + using API.Model; + using API.PE32; + using API.Services; + using Classes; + using System; + using System.IO; + using System.Security.Cryptography; + + [SteamlessApiVersion(1, 0)] + public class Main : SteamlessPlugin + { + /// + /// Internal logging service instance. + /// + private LoggingService m_LoggingService; + + /// + /// Gets the author of this plugin. + /// + public override string Author => "atom0s"; + + /// + /// Gets the name of this plugin. + /// + public override string Name => "SteamStub Variant 3.0 Unpacker (x86)"; + + /// + /// Gets the description of this plugin. + /// + public override string Description => "Unpacker for the 32bit SteamStub variant 3.0."; + + /// + /// Gets the version of this plugin. + /// + public override Version Version => new Version(1, 0, 0, 0); + + /// + /// Internal wrapper to log a message. + /// + /// + /// + private void Log(string msg, LogMessageType type) + { + this.m_LoggingService.OnAddLogMessage(this, new LogMessageEventArgs(msg, type)); + } + + /// + /// Initialize function called when this plugin is first loaded. + /// + /// + /// + public override bool Initialize(LoggingService logService) + { + this.m_LoggingService = logService; + return true; + } + + /// + /// Processing function called when a file is being unpacked. Allows plugins to check the file + /// and see if it can handle the file for its intended purpose. + /// + /// + /// + public override bool CanProcessFile(string file) + { + try + { + // Load the file.. + var f = new Pe32File(file); + if (!f.Parse() || f.IsFile64Bit() || !f.HasSection(".bind")) + return false; + + // Obtain the bind section data.. + var bind = f.GetSectionData(".bind"); + + // Attempt to locate the known v3.x signature.. + var varient = Pe32Helpers.FindPattern(bind, "E8 00 00 00 00 50 53 51 52 56 57 55 8B 44 24 1C 2D 05 00 00 00 8B CC 83 E4 F0 51 51 51 50"); + if (varient == 0) return false; + + // Attempt to determine the varient version.. + int headerSize; + var offset = Pe32Helpers.FindPattern(bind, "55 8B EC 81 EC ?? ?? ?? ?? 53 ?? ?? ?? ?? ?? 68"); + if (offset == 0) + { + offset = Pe32Helpers.FindPattern(bind, "55 8B EC 81 EC ?? ?? ?? ?? 53 ?? ?? ?? ?? ?? 8D 83"); + if (offset == 0) + return false; + + headerSize = BitConverter.ToInt32(bind, (int)offset + 22); + } + else + headerSize = BitConverter.ToInt32(bind, (int)offset + 16); + + return headerSize == 0xB0 || headerSize == 0xD0; + } + catch + { + return false; + } + } + + /// + /// Processing function called to allow the plugin to process the file. + /// + /// + /// + /// + public override bool ProcessFile(string file, SteamlessOptions options) + { + // Initialize the class members.. + this.Options = options; + this.CodeSectionData = null; + this.CodeSectionIndex = -1; + this.XorKey = 0; + + // Parse the file.. + this.File = new Pe32File(file); + if (!this.File.Parse()) + return false; + + // Announce we are being unpacked with this packer.. + this.Log("File is packed with SteamStub Variant 3.0!", LogMessageType.Information); + + this.Log("Step 1 - Read, decode and validate the SteamStub DRM header.", LogMessageType.Information); + if (!this.Step1()) + return false; + + this.Log("Step 2 - Read, decode and process the payload data.", LogMessageType.Information); + if (!this.Step2()) + return false; + + this.Log("Step 3 - Read, decode and dump the SteamDRMP.dll file.", LogMessageType.Information); + if (!this.Step3()) + return false; + + this.Log("Step 4 - Handle .bind section. Find code section.", LogMessageType.Information); + if (!this.Step4()) + return false; + + this.Log("Step 5 - Read, decrypt and process code section.", LogMessageType.Information); + if (!this.Step5()) + return false; + + this.Log("Step 6 - Rebuild and save the unpacked file.", LogMessageType.Information); + if (!this.Step6()) + return false; + + return true; + } + + /// + /// Step #1 + /// + /// Read, decode and validate the SteamStub DRM header. + /// + /// + private bool Step1() + { + // Obtain the DRM header data.. + var fileOffset = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint); + var headerData = new byte[0xD0]; + Array.Copy(this.File.FileData, (int)(fileOffset - 0xD0), headerData, 0, 0xD0); + + // Xor decode the header data.. + this.XorKey = SteamStubHelpers.SteamXor(ref headerData, 0xD0); + this.StubHeader = Pe32Helpers.GetStructure(headerData); + + // Validate the structure signature.. + return this.StubHeader.Signature == 0xC0DEC0DE; + } + + /// + /// Step #2 + /// + /// Read, decode and process the payload data. + /// + /// + private bool Step2() + { + // Obtain the payload address and size.. + var payloadAddr = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset); + var payloadSize = (this.StubHeader.PayloadSize + 0x0F) & 0xFFFFFFF0; + + // Do nothing if there is no payload.. + if (payloadSize == 0) + return true; + + this.Log(" --> File has payload data!", LogMessageType.Debug); + + // Obtain and decode the payload.. + var payload = new byte[payloadSize]; + Array.Copy(this.File.FileData, payloadAddr, payload, 0, payloadSize); + this.XorKey = SteamStubHelpers.SteamXor(ref payload, payloadSize, this.XorKey); + + try + { + if (this.Options.DumpPayloadToDisk) + { + System.IO.File.WriteAllBytes(this.File.FilePath + ".payload", payload); + this.Log(" --> Saved payload to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + + /// + /// Step #3 + /// + /// Read, decode and dump the SteamDRMP.dll file. + /// + /// + private bool Step3() + { + // Ensure there is a dll to process.. + if (this.StubHeader.DRMPDllSize == 0) + { + this.Log(" --> File does not contain a SteamDRMP.dll file.", LogMessageType.Debug); + return true; + } + + this.Log(" --> File has SteamDRMP.dll file!", LogMessageType.Debug); + + try + { + // Obtain the SteamDRMP.dll file address and data.. + var drmpAddr = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset + this.StubHeader.DRMPDllOffset); + var drmpData = new byte[this.StubHeader.DRMPDllSize]; + Array.Copy(this.File.FileData, drmpAddr, drmpData, 0, drmpData.Length); + + // Decrypt the data (xtea decryption).. + SteamStubHelpers.SteamDrmpDecryptPass1(ref drmpData, this.StubHeader.DRMPDllSize, this.StubHeader.EncryptionKeys); + + try + { + if (this.Options.DumpSteamDrmpToDisk) + { + var basePath = Path.GetDirectoryName(this.File.FilePath) ?? string.Empty; + System.IO.File.WriteAllBytes(Path.Combine(basePath, "SteamDRMP.dll"), drmpData); + this.Log(" --> Saved SteamDRMP.dll to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + catch + { + this.Log(" --> Error trying to decrypt the files SteamDRMP.dll data!", LogMessageType.Error); + return false; + } + } + + /// + /// Step #4 + /// + /// Remove the bind section if requested. + /// Find the code section. + /// + /// + private bool Step4() + { + // Remove the bind section if its not requested to be saved.. + if (!this.Options.KeepBindSection) + { + // Obtain the .bind section.. + var bindSection = this.File.GetSection(".bind"); + if (!bindSection.IsValid) + return false; + + // Remove the section.. + this.File.RemoveSection(bindSection); + + // Decrease the header section count.. + var ntHeaders = this.File.NtHeaders; + ntHeaders.FileHeader.NumberOfSections--; + this.File.NtHeaders = ntHeaders; + + this.Log(" --> .bind section was removed from the file.", LogMessageType.Debug); + } + else + this.Log(" --> .bind section was kept in the file.", LogMessageType.Debug); + + // Skip finding the code section if the file is not encrypted.. + if ((this.StubHeader.Flags & (uint)SteamStubDrmFlags.NoEncryption) == (uint)SteamStubDrmFlags.NoEncryption) + return true; + + // Find the code section.. + var codeSection = this.File.GetOwnerSection(this.StubHeader.CodeSectionVirtualAddress); + if (codeSection.PointerToRawData == 0 || codeSection.SizeOfRawData == 0) + return false; + + // Store the code sections index.. + this.CodeSectionIndex = this.File.GetSectionIndex(codeSection); + + return true; + } + + /// + /// Step #5 + /// + /// Read, decrypt and process the code section. + /// + /// + private bool Step5() + { + // Skip decryption if the code section is not encrypted.. + if ((this.StubHeader.Flags & (uint)SteamStubDrmFlags.NoEncryption) == (uint)SteamStubDrmFlags.NoEncryption) + { + this.Log(" --> Code section is not encrypted.", LogMessageType.Debug); + return true; + } + + try + { + // Obtain the code section.. + var codeSection = this.File.Sections[this.CodeSectionIndex]; + this.Log($" --> {codeSection.SectionName} linked as main code section.", LogMessageType.Debug); + this.Log($" --> {codeSection.SectionName} section is encrypted.", LogMessageType.Debug); + + // Obtain the code section data.. + var codeSectionData = new byte[codeSection.SizeOfRawData + this.StubHeader.CodeSectionStolenData.Length]; + Array.Copy(this.StubHeader.CodeSectionStolenData, 0, codeSectionData, 0, this.StubHeader.CodeSectionStolenData.Length); + Array.Copy(this.File.FileData, this.File.GetFileOffsetFromRva(codeSection.VirtualAddress), codeSectionData, this.StubHeader.CodeSectionStolenData.Length, codeSection.SizeOfRawData); + + // Create the AES decryption helper.. + var aes = new AesHelper(this.StubHeader.AES_Key, this.StubHeader.AES_IV); + aes.RebuildIv(this.StubHeader.AES_IV); + + // Decrypt the code section data.. + var data = aes.Decrypt(codeSectionData, CipherMode.CBC, PaddingMode.None); + if (data == null) + return false; + + // Set the code section override data.. + this.CodeSectionData = data; + + return true; + } + catch + { + this.Log(" --> Error trying to decrypt the files code section data!", LogMessageType.Error); + return false; + } + } + + /// + /// Step #6 + /// + /// Rebuild and save the unpacked file. + /// + /// + private bool Step6() + { + FileStream fStream = null; + + try + { + // Rebuild the file sections.. + this.File.RebuildSections(); + + // Open the unpacked file for writing.. + var unpackedPath = this.File.FilePath + ".unpacked.exe"; + fStream = new FileStream(unpackedPath, FileMode.Create, FileAccess.ReadWrite); + + // Write the DOS header to the file.. + fStream.WriteBytes(Pe32Helpers.GetStructureBytes(this.File.DosHeader)); + + // Write the DOS stub to the file.. + if (this.File.DosStubSize > 0) + fStream.WriteBytes(this.File.DosStubData); + + // Update the entry point of the file.. + var ntHeaders = this.File.NtHeaders; + ntHeaders.OptionalHeader.AddressOfEntryPoint = this.StubHeader.OriginalEntryPoint; + this.File.NtHeaders = ntHeaders; + + // Write the NT headers to the file.. + fStream.WriteBytes(Pe32Helpers.GetStructureBytes(ntHeaders)); + + // Write the sections to the file.. + for (var x = 0; x < this.File.Sections.Count; x++) + { + var section = this.File.Sections[x]; + var sectionData = this.File.SectionData[x]; + + // Write the section header to the file.. + fStream.WriteBytes(Pe32Helpers.GetStructureBytes(section)); + + // Set the file pointer to the sections raw data.. + var sectionOffset = fStream.Position; + fStream.Position = section.PointerToRawData; + + // Write the sections raw data.. + var sectionIndex = this.File.Sections.IndexOf(section); + if (sectionIndex == this.CodeSectionIndex) + fStream.WriteBytes(this.CodeSectionData ?? sectionData); + else + fStream.WriteBytes(sectionData); + + // Reset the file offset.. + fStream.Position = sectionOffset; + } + + // Set the stream to the end of the file.. + fStream.Position = fStream.Length; + + // Write the overlay data if it exists.. + if (this.File.OverlayData != null) + fStream.WriteBytes(this.File.OverlayData); + + this.Log(" --> Unpacked file saved to disk!", LogMessageType.Success); + this.Log($" --> File Saved As: {unpackedPath}", LogMessageType.Success); + + return true; + } + catch + { + this.Log(" --> Error trying to save unpacked file!", LogMessageType.Error); + return false; + } + finally + { + fStream?.Dispose(); + } + } + + /// + /// Gets or sets the Steamless options this file was requested to process with. + /// + private SteamlessOptions Options { get; set; } + + /// + /// Gets or sets the file being processed. + /// + private Pe32File File { get; set; } + + /// + /// Gets or sets the current xor key being used against the file data. + /// + private uint XorKey { get; set; } + + /// + /// Gets or sets the DRM stub header. + /// + private SteamStub32Var30Header StubHeader { get; set; } + + /// + /// Gets or sets the index of the code section. + /// + private int CodeSectionIndex { get; set; } + + /// + /// Gets or sets the decrypted code section data. + /// + private byte[] CodeSectionData { get; set; } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x86/Properties/AssemblyInfo.cs b/Steamless.Unpacker.Variant30.x86/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..820340d --- /dev/null +++ b/Steamless.Unpacker.Variant30.x86/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Steamless.Unpacker.Variant30.x86")] +[assembly: AssemblyDescription("Steamless SteamStub Variant v3.0 (x86) Unpacker")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCompany("atom0s")] +[assembly: AssemblyProduct("Steamless.Unpacker.Variant30.x86")] +[assembly: AssemblyCopyright("Copyright © atom0s 2015 - 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("b6bb7a32-ab23-4a25-8914-154879aad3fe")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x86/Steamless.Unpacker.Variant30.x86.csproj b/Steamless.Unpacker.Variant30.x86/Steamless.Unpacker.Variant30.x86.csproj new file mode 100644 index 0000000..91632ec --- /dev/null +++ b/Steamless.Unpacker.Variant30.x86/Steamless.Unpacker.Variant30.x86.csproj @@ -0,0 +1,57 @@ + + + + + Debug + AnyCPU + {B6BB7A32-AB23-4A25-8914-154879AAD3FE} + Library + Properties + Steamless.Unpacker.Variant30.x86 + Steamless.Unpacker.Variant30.x86 + v4.5.2 + 512 + + + x86 + ..\Steamless\bin\x86\Debug\Plugins\ + DEBUG;TRACE + + + x86 + ..\Steamless\bin\x86\Release\Plugins\ + true + + + + + + + + + + + + + + + + + + + + + {56c95629-3b34-47fe-b988-04274409294f} + Steamless.API + False + + + + + \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x86/Classes/SteamStubDrmFlags.cs b/Steamless.Unpacker.Variant31.x86/Classes/SteamStubDrmFlags.cs new file mode 100644 index 0000000..8db25a6 --- /dev/null +++ b/Steamless.Unpacker.Variant31.x86/Classes/SteamStubDrmFlags.cs @@ -0,0 +1,39 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant31.x86.Classes +{ + /// + /// Steam Stub Variant 3.1 DRM Flags + /// + public enum SteamStubDrmFlags + { + NoModuleVerification = 0x02, + NoEncryption = 0x04, + NoOwnershipCheck = 0x10, + NoDebuggerCheck = 0x20, + NoErrorDialog = 0x40 + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x86/Classes/SteamStubHeader.cs b/Steamless.Unpacker.Variant31.x86/Classes/SteamStubHeader.cs new file mode 100644 index 0000000..34b2179 --- /dev/null +++ b/Steamless.Unpacker.Variant31.x86/Classes/SteamStubHeader.cs @@ -0,0 +1,75 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant31.x86.Classes +{ + using System.Runtime.InteropServices; + + /// + /// SteamStub DRM Variant 3.1 Header + /// + [StructLayout(LayoutKind.Sequential)] + public struct SteamStub32Var31Header + { + public uint XorKey; // The base xor key, if defined, to unpack the file with. + public uint Signature; // The signature to ensure the xor decoding was successful. + public ulong ImageBase; // The base of the image that was protected. + public ulong AddressOfEntryPoint; // The entry point that is set from the DRM. + public uint BindSectionOffset; // The starting offset to the .bind section data. RVA(AddressOfEntryPoint - BindSectionOffset) + public uint Unknown0000; // [Cyanic: This field is most likely the .bind code size.] + public ulong OriginalEntryPoint; // The original entry point of the binary before it was protected. + public uint Unknown0001; // [Cyanic: This field is most likely an offset to a string table.] + public uint PayloadSize; // The size of the payload data. + public uint DRMPDllOffset; // The offset to the SteamDrmp.dll file. + public uint DRMPDllSize; // The size of the SteamDrmp.dll file. + public uint SteamAppId; // The Steam application id of this program. + public uint Flags; // The DRM flags used while protecting this program. + public uint BindSectionVirtualSize; // The .bind section virtual size. + public uint Unknown0002; // [Cyanic: This field is most likely a hash of some sort.] + public ulong CodeSectionVirtualAddress; // The code section virtual address. + public ulong CodeSectionRawSize; // The code section raw size. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)] + public byte[] AES_Key; // The AES encryption key. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] AES_IV; // The AES encryption IV. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] CodeSectionStolenData; // The first 16 bytes of the code section stolen. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x04)] + public uint[] EncryptionKeys; // Encryption keys used to decrypt the SteamDrmp.dll file. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x08)] + public uint[] Unknown0003; // Unknown unused data. + + public ulong GetModuleHandleA_Rva; // The rva to GetModuleHandleA. + public ulong GetModuleHandleW_Rva; // The rva to GetModuleHandleW. + public ulong LoadLibraryA_Rva; // The rva to LoadLibraryA. + public ulong LoadLibraryW_Rva; // The rva to LoadLibraryW. + public ulong GetProcAddress_Rva; // The rva to GetProcAddress. + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x86/Classes/SteamStubHelpers.cs b/Steamless.Unpacker.Variant31.x86/Classes/SteamStubHelpers.cs new file mode 100644 index 0000000..d865f9c --- /dev/null +++ b/Steamless.Unpacker.Variant31.x86/Classes/SteamStubHelpers.cs @@ -0,0 +1,122 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant31.x86.Classes +{ + using System; + + public static class SteamStubHelpers + { + /// + /// Xor decrypts the given data starting with the given key, if any. + /// + /// @note If no key is given (0) then the first key is read from the first + /// 4 bytes inside of the data given. + /// + /// The data to xor decode. + /// The size of the data to decode. + /// The starting xor key to decode with. + /// + public static uint SteamXor(ref byte[] data, uint size, uint key = 0) + { + var offset = (uint)0; + + // Read the first key as the base xor key if we had none given.. + if (key == 0) + { + offset += 4; + key = BitConverter.ToUInt32(data, 0); + } + + // Decode the data.. + for (var x = offset; x < size; x += 4) + { + var val = BitConverter.ToUInt32(data, (int)x); + Array.Copy(BitConverter.GetBytes(val ^ key), 0, data, x, 4); + + key = val; + } + + return key; + } + + /// + /// The second pass of decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. + /// + /// The result value buffer to write our returns to. + /// The keys used for the decryption. + /// The first value to decrypt from. + /// The second value to decrypt from. + /// The number of passes to crypt the data with. + public static void SteamDrmpDecryptPass2(ref uint[] res, uint[] keys, uint v1, uint v2, uint n = 32) + { + const uint delta = 0x9E3779B9; + const uint mask = 0xFFFFFFFF; + var sum = (delta * n) & mask; + + for (var x = 0; x < n; x++) + { + v2 = (v2 - (((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + keys[sum >> 11 & 3]))) & mask; + sum = (sum - delta) & mask; + v1 = (v1 - (((v2 << 4 ^ v2 >> 5) + v2) ^ (sum + keys[sum & 3]))) & mask; + } + + res[0] = v1; + res[1] = v2; + } + + /// + /// The first pass of the decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. It is modded to include + /// some basic xor'ing. + /// + /// The data to decrypt. + /// The size of the data to decrypt. + /// The keys used for the decryption. + public static void SteamDrmpDecryptPass1(ref byte[] data, uint size, uint[] keys) + { + var v1 = (uint)0x55555555; + var v2 = (uint)0x55555555; + + for (var x = 0; x < size; x += 8) + { + var d1 = BitConverter.ToUInt32(data, x + 0); + var d2 = BitConverter.ToUInt32(data, x + 4); + + var res = new uint[2]; + SteamDrmpDecryptPass2(ref res, keys, d1, d2); + + Array.Copy(BitConverter.GetBytes(res[0] ^ v1), 0, data, x + 0, 4); + Array.Copy(BitConverter.GetBytes(res[1] ^ v2), 0, data, x + 4, 4); + + v1 = d1; + v2 = d2; + } + } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x86/Main.cs b/Steamless.Unpacker.Variant31.x86/Main.cs new file mode 100644 index 0000000..cfadc18 --- /dev/null +++ b/Steamless.Unpacker.Variant31.x86/Main.cs @@ -0,0 +1,532 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant31.x86 +{ + using API; + using API.Crypto; + using API.Events; + using API.Extensions; + using API.Model; + using API.PE32; + using API.Services; + using Classes; + using System; + using System.IO; + using System.Security.Cryptography; + + [SteamlessApiVersion(1, 0)] + public class Main : SteamlessPlugin + { + /// + /// Internal logging service instance. + /// + private LoggingService m_LoggingService; + + /// + /// Gets the author of this plugin. + /// + public override string Author => "atom0s"; + + /// + /// Gets the name of this plugin. + /// + public override string Name => "SteamStub Variant 3.1 Unpacker (x86)"; + + /// + /// Gets the description of this plugin. + /// + public override string Description => "Unpacker for the 32bit SteamStub variant 3.1."; + + /// + /// Gets the version of this plugin. + /// + public override Version Version => new Version(1, 0, 0, 1); + + /// + /// Internal wrapper to log a message. + /// + /// + /// + private void Log(string msg, LogMessageType type) + { + this.m_LoggingService.OnAddLogMessage(this, new LogMessageEventArgs(msg, type)); + } + + /// + /// Initialize function called when this plugin is first loaded. + /// + /// + /// + public override bool Initialize(LoggingService logService) + { + this.m_LoggingService = logService; + return true; + } + + /// + /// Processing function called when a file is being unpacked. Allows plugins to check the file + /// and see if it can handle the file for its intended purpose. + /// + /// + /// + public override bool CanProcessFile(string file) + { + try + { + // Load the file.. + var f = new Pe32File(file); + if (!f.Parse() || f.IsFile64Bit() || !f.HasSection(".bind")) + return false; + + // Obtain the bind section data.. + var bind = f.GetSectionData(".bind"); + + // Attempt to locate the known v3.x signature.. + var varient = Pe32Helpers.FindPattern(bind, "E8 00 00 00 00 50 53 51 52 56 57 55 8B 44 24 1C 2D 05 00 00 00 8B CC 83 E4 F0 51 51 51 50"); + if (varient == 0) return false; + + // Attempt to determine the varient version.. + int headerSize; + var offset = Pe32Helpers.FindPattern(bind, "55 8B EC 81 EC ?? ?? ?? ?? 53 ?? ?? ?? ?? ?? 68"); + if (offset == 0) + { + offset = Pe32Helpers.FindPattern(bind, "55 8B EC 81 EC ?? ?? ?? ?? 53 ?? ?? ?? ?? ?? 8D 83"); + if (offset == 0) + return false; + + headerSize = BitConverter.ToInt32(bind, (int)offset + 22); + } + else + headerSize = BitConverter.ToInt32(bind, (int)offset + 16); + + return headerSize == 0xF0; + } + catch + { + return false; + } + } + + /// + /// Processing function called to allow the plugin to process the file. + /// + /// + /// + /// + public override bool ProcessFile(string file, SteamlessOptions options) + { + // Initialize the class members.. + this.TlsAsOep = false; + this.TlsOepRva = 0; + this.Options = options; + this.CodeSectionData = null; + this.CodeSectionIndex = -1; + this.XorKey = 0; + + // Parse the file.. + this.File = new Pe32File(file); + if (!this.File.Parse()) + return false; + + // Announce we are being unpacked with this packer.. + this.Log("File is packed with SteamStub Variant 3.1!", LogMessageType.Information); + + this.Log("Step 1 - Read, decode and validate the SteamStub DRM header.", LogMessageType.Information); + if (!this.Step1()) + return false; + + this.Log("Step 2 - Read, decode and process the payload data.", LogMessageType.Information); + if (!this.Step2()) + return false; + + this.Log("Step 3 - Read, decode and dump the SteamDRMP.dll file.", LogMessageType.Information); + if (!this.Step3()) + return false; + + this.Log("Step 4 - Handle .bind section. Find code section.", LogMessageType.Information); + if (!this.Step4()) + return false; + + this.Log("Step 5 - Read, decrypt and process code section.", LogMessageType.Information); + if (!this.Step5()) + return false; + + this.Log("Step 6 - Rebuild and save the unpacked file.", LogMessageType.Information); + if (!this.Step6()) + return false; + + return true; + } + + /// + /// Step #1 + /// + /// Read, decode and validate the SteamStub DRM header. + /// + /// + private bool Step1() + { + // Obtain the DRM header data.. + var fileOffset = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint); + var headerData = new byte[0xF0]; + Array.Copy(this.File.FileData, (int)(fileOffset - 0xF0), headerData, 0, 0xF0); + + // Xor decode the header data.. + this.XorKey = SteamStubHelpers.SteamXor(ref headerData, 0xF0); + this.StubHeader = Pe32Helpers.GetStructure(headerData); + + // Validate the header signature.. + if (this.StubHeader.Signature == 0xC0DEC0DF) + return true; + + // Try again using the Tls callback (if any) as the OEP instead.. + if (this.File.TlsCallbacks.Count == 0) + return false; + + // Obtain the DRM header data.. + fileOffset = this.File.GetRvaFromVa(this.File.TlsCallbacks[0]); + fileOffset = this.File.GetFileOffsetFromRva(fileOffset); + headerData = new byte[0xF0]; + Array.Copy(this.File.FileData, (int)(fileOffset - 0xF0), headerData, 0, 0xF0); + + // Xor decode the header data.. + this.XorKey = SteamStubHelpers.SteamXor(ref headerData, 0xF0); + this.StubHeader = Pe32Helpers.GetStructure(headerData); + + // Validate the header signature.. + if (this.StubHeader.Signature != 0xC0DEC0DF) + return false; + + // Tls was valid for the real oep.. + this.TlsAsOep = true; + this.TlsOepRva = fileOffset; + return true; + } + + /// + /// Step #2 + /// + /// Read, decode and process the payload data. + /// + /// + private bool Step2() + { + // Obtain the payload address and size.. + var payloadAddr = this.File.GetFileOffsetFromRva(this.TlsAsOep ? this.TlsOepRva : this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset); + var payloadSize = (this.StubHeader.PayloadSize + 0x0F) & 0xFFFFFFF0; + + // Do nothing if there is no payload.. + if (payloadSize == 0) + return true; + + this.Log(" --> File has payload data!", LogMessageType.Debug); + + // Obtain and decode the payload.. + var payload = new byte[payloadSize]; + Array.Copy(this.File.FileData, payloadAddr, payload, 0, payloadSize); + this.XorKey = SteamStubHelpers.SteamXor(ref payload, payloadSize, this.XorKey); + + try + { + if (this.Options.DumpPayloadToDisk) + { + System.IO.File.WriteAllBytes(this.File.FilePath + ".payload", payload); + this.Log(" --> Saved payload to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + + /// + /// Step #3 + /// + /// Read, decode and dump the SteamDRMP.dll file. + /// + /// + private bool Step3() + { + // Ensure there is a dll to process.. + if (this.StubHeader.DRMPDllSize == 0) + { + this.Log(" --> File does not contain a SteamDRMP.dll file.", LogMessageType.Debug); + return true; + } + + this.Log(" --> File has SteamDRMP.dll file!", LogMessageType.Debug); + + try + { + // Obtain the SteamDRMP.dll file address and data.. + var drmpAddr = this.File.GetFileOffsetFromRva(this.TlsAsOep ? this.TlsOepRva : this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset + this.StubHeader.DRMPDllOffset); + var drmpData = new byte[this.StubHeader.DRMPDllSize]; + Array.Copy(this.File.FileData, drmpAddr, drmpData, 0, drmpData.Length); + + // Decrypt the data (xtea decryption).. + SteamStubHelpers.SteamDrmpDecryptPass1(ref drmpData, this.StubHeader.DRMPDllSize, this.StubHeader.EncryptionKeys); + + try + { + if (this.Options.DumpSteamDrmpToDisk) + { + var basePath = Path.GetDirectoryName(this.File.FilePath) ?? string.Empty; + System.IO.File.WriteAllBytes(Path.Combine(basePath, "SteamDRMP.dll"), drmpData); + this.Log(" --> Saved SteamDRMP.dll to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + catch + { + this.Log(" --> Error trying to decrypt the files SteamDRMP.dll data!", LogMessageType.Error); + return false; + } + } + + /// + /// Step #4 + /// + /// Remove the bind section if requested. + /// Find the code section. + /// + /// + private bool Step4() + { + // Remove the bind section if its not requested to be saved.. + if (!this.Options.KeepBindSection) + { + // Obtain the .bind section.. + var bindSection = this.File.GetSection(".bind"); + if (!bindSection.IsValid) + return false; + + // Remove the section.. + this.File.RemoveSection(bindSection); + + // Decrease the header section count.. + var ntHeaders = this.File.NtHeaders; + ntHeaders.FileHeader.NumberOfSections--; + this.File.NtHeaders = ntHeaders; + + this.Log(" --> .bind section was removed from the file.", LogMessageType.Debug); + } + else + this.Log(" --> .bind section was kept in the file.", LogMessageType.Debug); + + // Skip finding the code section if the file is not encrypted.. + if ((this.StubHeader.Flags & (uint)SteamStubDrmFlags.NoEncryption) == (uint)SteamStubDrmFlags.NoEncryption) + return true; + + // Find the code section.. + var codeSection = this.File.GetOwnerSection(this.StubHeader.CodeSectionVirtualAddress); + if (codeSection.PointerToRawData == 0 || codeSection.SizeOfRawData == 0) + return false; + + // Store the code sections index.. + this.CodeSectionIndex = this.File.GetSectionIndex(codeSection); + + return true; + } + + /// + /// Step #5 + /// + /// Read, decrypt and process the code section. + /// + /// + private bool Step5() + { + // Skip decryption if the code section is not encrypted.. + if ((this.StubHeader.Flags & (uint)SteamStubDrmFlags.NoEncryption) == (uint)SteamStubDrmFlags.NoEncryption) + { + this.Log(" --> Code section is not encrypted.", LogMessageType.Debug); + return true; + } + + try + { + // Obtain the code section.. + var codeSection = this.File.Sections[this.CodeSectionIndex]; + this.Log($" --> {codeSection.SectionName} linked as main code section.", LogMessageType.Debug); + this.Log($" --> {codeSection.SectionName} section is encrypted.", LogMessageType.Debug); + + // Obtain the code section data.. + var codeSectionData = new byte[codeSection.SizeOfRawData + this.StubHeader.CodeSectionStolenData.Length]; + Array.Copy(this.StubHeader.CodeSectionStolenData, 0, codeSectionData, 0, this.StubHeader.CodeSectionStolenData.Length); + Array.Copy(this.File.FileData, this.File.GetFileOffsetFromRva(codeSection.VirtualAddress), codeSectionData, this.StubHeader.CodeSectionStolenData.Length, codeSection.SizeOfRawData); + + // Create the AES decryption helper.. + var aes = new AesHelper(this.StubHeader.AES_Key, this.StubHeader.AES_IV); + aes.RebuildIv(this.StubHeader.AES_IV); + + // Decrypt the code section data.. + var data = aes.Decrypt(codeSectionData, CipherMode.CBC, PaddingMode.None); + if (data == null) + return false; + + // Set the code section override data.. + this.CodeSectionData = data; + + return true; + } + catch + { + this.Log(" --> Error trying to decrypt the files code section data!", LogMessageType.Error); + return false; + } + } + + /// + /// Step #6 + /// + /// Rebuild and save the unpacked file. + /// + /// + private bool Step6() + { + FileStream fStream = null; + + try + { + // Rebuild the file sections.. + this.File.RebuildSections(); + + // Open the unpacked file for writing.. + var unpackedPath = this.File.FilePath + ".unpacked.exe"; + fStream = new FileStream(unpackedPath, FileMode.Create, FileAccess.ReadWrite); + + // Write the DOS header to the file.. + fStream.WriteBytes(Pe32Helpers.GetStructureBytes(this.File.DosHeader)); + + // Write the DOS stub to the file.. + if (this.File.DosStubSize > 0) + fStream.WriteBytes(this.File.DosStubData); + + // Update the entry point of the file.. + var ntHeaders = this.File.NtHeaders; + ntHeaders.OptionalHeader.AddressOfEntryPoint = (uint)this.StubHeader.OriginalEntryPoint; + this.File.NtHeaders = ntHeaders; + + // Write the NT headers to the file.. + fStream.WriteBytes(Pe32Helpers.GetStructureBytes(ntHeaders)); + + // Write the sections to the file.. + for (var x = 0; x < this.File.Sections.Count; x++) + { + var section = this.File.Sections[x]; + var sectionData = this.File.SectionData[x]; + + // Write the section header to the file.. + fStream.WriteBytes(Pe32Helpers.GetStructureBytes(section)); + + // Set the file pointer to the sections raw data.. + var sectionOffset = fStream.Position; + fStream.Position = section.PointerToRawData; + + // Write the sections raw data.. + var sectionIndex = this.File.Sections.IndexOf(section); + if (sectionIndex == this.CodeSectionIndex) + fStream.WriteBytes(this.CodeSectionData ?? sectionData); + else + fStream.WriteBytes(sectionData); + + // Reset the file offset.. + fStream.Position = sectionOffset; + } + + // Set the stream to the end of the file.. + fStream.Position = fStream.Length; + + // Write the overlay data if it exists.. + if (this.File.OverlayData != null) + fStream.WriteBytes(this.File.OverlayData); + + this.Log(" --> Unpacked file saved to disk!", LogMessageType.Success); + this.Log($" --> File Saved As: {unpackedPath}", LogMessageType.Success); + + return true; + } + catch + { + this.Log(" --> Error trying to save unpacked file!", LogMessageType.Error); + return false; + } + finally + { + fStream?.Dispose(); + } + } + + /// + /// Gets or sets if the Tls callback is being used as the Oep. + /// + private bool TlsAsOep { get; set; } + + /// + /// Gets or sets the Tls Oep Rva if it is being used as the Oep. + /// + private uint TlsOepRva { get; set; } + + /// + /// Gets or sets the Steamless options this file was requested to process with. + /// + private SteamlessOptions Options { get; set; } + + /// + /// Gets or sets the file being processed. + /// + private Pe32File File { get; set; } + + /// + /// Gets or sets the current xor key being used against the file data. + /// + private uint XorKey { get; set; } + + /// + /// Gets or sets the DRM stub header. + /// + private SteamStub32Var31Header StubHeader { get; set; } + + /// + /// Gets or sets the index of the code section. + /// + private int CodeSectionIndex { get; set; } + + /// + /// Gets or sets the decrypted code section data. + /// + private byte[] CodeSectionData { get; set; } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x86/Properties/AssemblyInfo.cs b/Steamless.Unpacker.Variant31.x86/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..173a2ed --- /dev/null +++ b/Steamless.Unpacker.Variant31.x86/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Steamless.Unpacker.Variant31.x86")] +[assembly: AssemblyDescription("Steamless SteamStub Variant v3.1 (x86) Unpacker")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCompany("atom0s")] +[assembly: AssemblyProduct("Steamless.Unpacker.Variant31.x86")] +[assembly: AssemblyCopyright("Copyright © atom0s 2015 - 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("0f2fae37-f898-4392-b4f6-711954beeb4f")] +[assembly: AssemblyVersion("1.0.0.1")] +[assembly: AssemblyFileVersion("1.0.0.1")] \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x86/Steamless.Unpacker.Variant31.x86.csproj b/Steamless.Unpacker.Variant31.x86/Steamless.Unpacker.Variant31.x86.csproj new file mode 100644 index 0000000..8b6857e --- /dev/null +++ b/Steamless.Unpacker.Variant31.x86/Steamless.Unpacker.Variant31.x86.csproj @@ -0,0 +1,57 @@ + + + + + Debug + AnyCPU + {0F2FAE37-F898-4392-B4F6-711954BEEB4F} + Library + Properties + Steamless.Unpacker.Variant31.x86 + Steamless.Unpacker.Variant31.x86 + v4.5.2 + 512 + + + x86 + ..\Steamless\bin\x86\Debug\Plugins\ + DEBUG;TRACE + + + x86 + ..\Steamless\bin\x86\Release\Plugins\ + true + + + + + + + + + + + + + + + + + + + + + {56c95629-3b34-47fe-b988-04274409294f} + Steamless.API + False + + + + + \ No newline at end of file diff --git a/Steamless.sln b/Steamless.sln new file mode 100644 index 0000000..40b2964 --- /dev/null +++ b/Steamless.sln @@ -0,0 +1,52 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steamless", "Steamless\Steamless.csproj", "{10AC8FDE-09D9-47B4-AA89-BADC40EECAAB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steamless.API", "Steamless.API\Steamless.API.csproj", "{56C95629-3B34-47FE-B988-04274409294F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExamplePlugin", "ExamplePlugin\ExamplePlugin.csproj", "{97AC964A-E56F-415C-BAEA-D503E3D4D7B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steamless.Unpacker.Variant30.x86", "Steamless.Unpacker.Variant30.x86\Steamless.Unpacker.Variant30.x86.csproj", "{B6BB7A32-AB23-4A25-8914-154879AAD3FE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steamless.Unpacker.Variant31.x86", "Steamless.Unpacker.Variant31.x86\Steamless.Unpacker.Variant31.x86.csproj", "{0F2FAE37-F898-4392-B4F6-711954BEEB4F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steamless.Unpacker.Variant20.x86", "Steamless.Unpacker.Variant20.x86\Steamless.Unpacker.Variant20.x86.csproj", "{A40154CD-A0FD-4371-8099-CE277E0989AF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {10AC8FDE-09D9-47B4-AA89-BADC40EECAAB}.Debug|x86.ActiveCfg = Debug|x86 + {10AC8FDE-09D9-47B4-AA89-BADC40EECAAB}.Debug|x86.Build.0 = Debug|x86 + {10AC8FDE-09D9-47B4-AA89-BADC40EECAAB}.Release|x86.ActiveCfg = Release|x86 + {10AC8FDE-09D9-47B4-AA89-BADC40EECAAB}.Release|x86.Build.0 = Release|x86 + {56C95629-3B34-47FE-B988-04274409294F}.Debug|x86.ActiveCfg = Debug|x86 + {56C95629-3B34-47FE-B988-04274409294F}.Debug|x86.Build.0 = Debug|x86 + {56C95629-3B34-47FE-B988-04274409294F}.Release|x86.ActiveCfg = Release|x86 + {56C95629-3B34-47FE-B988-04274409294F}.Release|x86.Build.0 = Release|x86 + {97AC964A-E56F-415C-BAEA-D503E3D4D7B8}.Debug|x86.ActiveCfg = Debug|x86 + {97AC964A-E56F-415C-BAEA-D503E3D4D7B8}.Debug|x86.Build.0 = Debug|x86 + {97AC964A-E56F-415C-BAEA-D503E3D4D7B8}.Release|x86.ActiveCfg = Release|x86 + {97AC964A-E56F-415C-BAEA-D503E3D4D7B8}.Release|x86.Build.0 = Release|x86 + {B6BB7A32-AB23-4A25-8914-154879AAD3FE}.Debug|x86.ActiveCfg = Debug|x86 + {B6BB7A32-AB23-4A25-8914-154879AAD3FE}.Debug|x86.Build.0 = Debug|x86 + {B6BB7A32-AB23-4A25-8914-154879AAD3FE}.Release|x86.ActiveCfg = Release|x86 + {B6BB7A32-AB23-4A25-8914-154879AAD3FE}.Release|x86.Build.0 = Release|x86 + {0F2FAE37-F898-4392-B4F6-711954BEEB4F}.Debug|x86.ActiveCfg = Debug|x86 + {0F2FAE37-F898-4392-B4F6-711954BEEB4F}.Debug|x86.Build.0 = Debug|x86 + {0F2FAE37-F898-4392-B4F6-711954BEEB4F}.Release|x86.ActiveCfg = Release|x86 + {0F2FAE37-F898-4392-B4F6-711954BEEB4F}.Release|x86.Build.0 = Release|x86 + {A40154CD-A0FD-4371-8099-CE277E0989AF}.Debug|x86.ActiveCfg = Debug|x86 + {A40154CD-A0FD-4371-8099-CE277E0989AF}.Debug|x86.Build.0 = Debug|x86 + {A40154CD-A0FD-4371-8099-CE277E0989AF}.Release|x86.ActiveCfg = Release|x86 + {A40154CD-A0FD-4371-8099-CE277E0989AF}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Steamless/App.config b/Steamless/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/Steamless/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Steamless/App.xaml b/Steamless/App.xaml new file mode 100644 index 0000000..30d33aa --- /dev/null +++ b/Steamless/App.xaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Steamless/App.xaml.cs b/Steamless/App.xaml.cs new file mode 100644 index 0000000..0841d28 --- /dev/null +++ b/Steamless/App.xaml.cs @@ -0,0 +1,79 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless +{ + using System; + using System.IO; + using System.Reflection; + + /// + /// Interaction logic for App.xaml + /// + public partial class App + { + /// + /// Default Constructor + /// + public App() + { + // Override the assembly resolve event.. + AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; + } + + /// + /// Assembly resolve override to allow loading of embedded modules. + /// + /// + /// + /// + private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args) + { + // Obtain the name of the assembly being loaded.. + var name = args.Name.Contains(",") ? args.Name.Substring(0, args.Name.IndexOf(",", StringComparison.InvariantCultureIgnoreCase)) : args.Name.Replace(".dll", ""); + + // Ignore resource assembly loading.. + if (name.ToLower().EndsWith(".resources")) + return null; + + // Build a full path to the possible embedded file.. + var fullName = $"{Assembly.GetExecutingAssembly().EntryPoint.DeclaringType?.Namespace}.Embedded.{new AssemblyName(args.Name).Name}.dll"; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(fullName)) + { + // If not embedded try to load from the plugin folder.. + if (stream == null) + { + var file = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins", name + ".dll"); + return File.Exists(file) ? Assembly.Load(File.ReadAllBytes(file)) : null; + } + + // Read and load the embedded resource.. + var data = new byte[stream.Length]; + stream.Read(data, 0, (int)stream.Length); + return Assembly.Load(data); + } + } + } +} \ No newline at end of file diff --git a/Steamless/Assets/Animations.xaml b/Steamless/Assets/Animations.xaml new file mode 100644 index 0000000..a077f94 --- /dev/null +++ b/Steamless/Assets/Animations.xaml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Steamless/Assets/Controls.CheckBox.xaml b/Steamless/Assets/Controls.CheckBox.xaml new file mode 100644 index 0000000..849d762 --- /dev/null +++ b/Steamless/Assets/Controls.CheckBox.xaml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Steamless/Assets/Controls.Scrollbars.xaml b/Steamless/Assets/Controls.Scrollbars.xaml new file mode 100644 index 0000000..89f5bbe --- /dev/null +++ b/Steamless/Assets/Controls.Scrollbars.xaml @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 50 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Steamless/Assets/Controls.xaml b/Steamless/Assets/Controls.xaml new file mode 100644 index 0000000..e30e43b --- /dev/null +++ b/Steamless/Assets/Controls.xaml @@ -0,0 +1,805 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Steamless/Assets/Theme.xaml b/Steamless/Assets/Theme.xaml new file mode 100644 index 0000000..09408e5 --- /dev/null +++ b/Steamless/Assets/Theme.xaml @@ -0,0 +1,152 @@ + + + + + #FFFF4A0D + #CCFF4A0D + #99FF4A0D + #66FF4A0D + #33FF4A0D + + + #FF000000 + #FFFFFFFF + #A8A8A8 + #FF333333 + #FF7F7F7F + #FF9D9D9D + #FFA59F93 + #FFB9B9B9 + #FFCCCCCC + #FFD8D8D9 + #FFE0E0E0 + #5EC9C9C9 + #FFF7F7F7 + + + #33878787 + #33959595 + #4C000000 + #4C000000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #FF000000 + + White + + + + \ No newline at end of file diff --git a/Steamless/Assets/Window.xaml b/Steamless/Assets/Window.xaml new file mode 100644 index 0000000..b52198e --- /dev/null +++ b/Steamless/Assets/Window.xaml @@ -0,0 +1,99 @@ + + + + + + + + Homepage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Clear Log + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Steamless/View/MainWindow.xaml.cs b/Steamless/View/MainWindow.xaml.cs new file mode 100644 index 0000000..16d2908 --- /dev/null +++ b/Steamless/View/MainWindow.xaml.cs @@ -0,0 +1,41 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.View +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow + { + /// + /// Default Constructor + /// + public MainWindow() + { + this.InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Steamless/View/SplashView.xaml b/Steamless/View/SplashView.xaml new file mode 100644 index 0000000..32c4498 --- /dev/null +++ b/Steamless/View/SplashView.xaml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Steamless/View/SplashView.xaml.cs b/Steamless/View/SplashView.xaml.cs new file mode 100644 index 0000000..0c31ffb --- /dev/null +++ b/Steamless/View/SplashView.xaml.cs @@ -0,0 +1,41 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.View +{ + /// + /// Interaction logic for SplashView.xaml + /// + public partial class SplashView + { + /// + /// Default Constructor + /// + public SplashView() + { + this.InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Steamless/ViewModel/MainWindowViewModel.cs b/Steamless/ViewModel/MainWindowViewModel.cs new file mode 100644 index 0000000..ecdd580 --- /dev/null +++ b/Steamless/ViewModel/MainWindowViewModel.cs @@ -0,0 +1,492 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.ViewModel +{ + using API.Events; + using API.Model; + using API.Services; + using GalaSoft.MvvmLight.Command; + using Microsoft.Win32; + using Model; + using Model.Tasks; + using System; + using System.Collections.Concurrent; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using System.Windows; + using System.Windows.Documents; + using System.Windows.Input; + + public class MainWindowViewModel : ViewModelBase + { + /// + /// Internal data service instance. + /// + private readonly IDataService m_DataService; + + /// + /// Internal thread used to process tasks. + /// + private Thread m_TaskThread; + + /// + /// Default Constructor + /// + /// + /// + public MainWindowViewModel(IDataService dataService, LoggingService logService) + { + // Store the data service instance.. + this.m_DataService = dataService; + + // Initialize the model.. + this.State = ApplicationState.Initializing; + this.Tasks = new ConcurrentBag(); + this.Options = new SteamlessOptions(); + this.Log = new ObservableCollection(); + this.ShowAboutView = false; + this.InputFilePath = string.Empty; + + // Register command callbacks.. + this.OnWindowCloseCommand = new RelayCommand(this.WindowClose); + this.OnWindowMinimizeCommand = new RelayCommand(WindowMinimize); + this.OnWindowMouseDownCommand = new RelayCommand(WindowMouseDown); + this.OnShowAboutViewCommand = new RelayCommand(() => this.ShowAboutView = !this.ShowAboutView); + this.OnOpenHyperlinkCommand = new RelayCommand(o => + { + var link = o as Hyperlink; + if (link != null) + Process.Start(link.NavigateUri.AbsoluteUri); + }); + this.OnDragDropCommand = new RelayCommand(this.InputFileDragDrop); + this.OnPreviewDragEnterCommand = new RelayCommand(this.InputFilePreviewDragEnter); + this.OnBrowseForInputFileCommand = new RelayCommand(this.BrowseForInputFile); + this.OnUnpackFileCommand = new RelayCommand(this.UnpackFile); + this.OnClearLogCommand = new RelayCommand(() => this.ClearLogMessages(this, EventArgs.Empty)); + + // Attach logging service events.. + logService.AddLogMessage += this.AddLogMessage; + logService.ClearLogMessages += this.ClearLogMessages; + + this.AddLogMessage(this, new LogMessageEventArgs("Steamless (c) 2015 - 2017 atom0s [atom0s@live.com]", LogMessageType.Debug)); + this.AddLogMessage(this, new LogMessageEventArgs("Website: http://atom0s.com/", LogMessageType.Debug)); + + // Initialize this model.. + this.Initialize(); + } + + /// + /// Internal async call to load the main view model. + /// + private async void Initialize() + { + // Obtain the Steamless version.. + this.CurrentTask = new StatusTask("Initializing.."); + this.SteamlessVersion = await this.m_DataService.GetSteamlessVersion(); + + // Load the Steamless plugins.. + this.Tasks.Add(new LoadPluginsTask()); + + // Start the application.. + this.Tasks.Add(new StartSteamlessTask()); + + // Start the tasks thread.. + if (this.m_TaskThread != null) + return; + this.m_TaskThread = new Thread(this.ProcessTasksThread) { IsBackground = true }; + this.m_TaskThread.Start(); + } + + /// + /// Sets the applications current status. + /// + /// + /// + public void SetApplicationStatus(ApplicationState state, string msg) + { + this.State = state; + this.CurrentTask = new StatusTask(msg); + } + + /// + /// Thread callback to process application tasks. + /// + private async void ProcessTasksThread() + { + while (Interlocked.CompareExchange(ref this.m_TaskThread, null, null) != null && this.State != ApplicationState.Closing) + { + // Obtain a task from the task list.. + BaseTask task; + if (this.Tasks.TryTake(out task)) + { + this.CurrentTask = task; + await this.CurrentTask.StartTask(); + } + else + { + // No tasks left, set application to a running state.. + if (this.State == ApplicationState.Initializing) + this.State = ApplicationState.Running; + } + + Thread.Sleep(200); + } + } + + /// + /// Adds a message to the message log. + /// + /// + /// + private void AddLogMessage(object sender, LogMessageEventArgs e) + { + // Do not log debug messages if verbose output is disabled.. + if (!this.Options.VerboseOutput && e.MessageType == LogMessageType.Debug) + return; + + // Check if we need to invoke from the dispatcher thread.. + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.Invoke(() => this.AddLogMessage(sender, e)); + return; + } + + // Prefix the parent to the message.. + try + { + if (sender != null) + { + var baseName = sender.GetType().Assembly.GetName().Name; + e.Message = $"[{baseName}] {e.Message}"; + } + else + e.Message = "[Unknown] " + e.Message; + } + catch + { + // Do nothing with this exception.. + } + + this.Log.Add(e); + } + + /// + /// Clears the message log. + /// + /// + /// + private void ClearLogMessages(object sender, EventArgs e) + { + // Check if we need to invoke from the dispatcher thread.. + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.Invoke(() => this.ClearLogMessages(sender, e)); + return; + } + + this.Log.Clear(); + } + + #region == Window Function Callbacks ================================================================== + /// + /// Command callback for when the window is being closed. + /// + private void WindowClose() + { + // Set the launcher state to closing.. + this.State = ApplicationState.Closing; + + // Shutdown the application.. + Application.Current.Shutdown(0); + } + + /// + /// Command callback for when the window is being minimized. + /// + private static void WindowMinimize() + { + // Minimize the window.. + Application.Current.MainWindow.WindowState = WindowState.Minimized; + } + + /// + /// Command callback for when the window is being clicked down. (To drag the window.) + /// + /// + private static void WindowMouseDown(MouseButtonEventArgs args) + { + Application.Current.MainWindow.DragMove(); + } + + /// + /// Handles drag and drop events over the input file textbox. + /// + /// + private void InputFileDragDrop(DragEventArgs args) + { + args.Handled = true; + + // Check for files being dragged.. + if (args.Data.GetDataPresent(DataFormats.FileDrop)) + { + // Ensure only 1 file is being dropped.. + var files = (string[])args.Data.GetData(DataFormats.FileDrop); + if (files != null && files.Length >= 1) + this.InputFilePath = files[0]; + } + } + + /// + /// Handles drag and drop events over the input file textbox. + /// + /// + private void InputFilePreviewDragEnter(DragEventArgs args) + { + args.Handled = true; + + // Check for files being dragged.. + if (args.Data.GetDataPresent(DataFormats.FileDrop)) + { + // Ensure only 1 file is being dropped.. + var files = (string[])args.Data.GetData(DataFormats.FileDrop); + args.Effects = files != null && files.Length == 1 ? DragDropEffects.Move : DragDropEffects.None; + } + else + args.Effects = DragDropEffects.None; + } + + /// + /// Browses for the input file to be unpacked. + /// + private void BrowseForInputFile() + { + // Display the find file dialog.. + var ofd = new OpenFileDialog + { + CheckFileExists = true, + CheckPathExists = true, + DefaultExt = "*.exe", + Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*", + FilterIndex = 0, + InitialDirectory = Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory), + Multiselect = false, + RestoreDirectory = true + }; + + // Update the input file path.. + var showDialog = ofd.ShowDialog(); + if (showDialog != null && (bool)showDialog) + this.InputFilePath = ofd.FileName; + } + + /// + /// Unpacks the selected file using the selected plugin. + /// + private async void UnpackFile() + { + await Task.Run(() => + { + // Validation checks.. + if (this.SelectedPluginIndex == -1) + return; + if (this.SelectedPluginIndex > this.Plugins.Count) + return; + if (string.IsNullOrEmpty(this.InputFilePath)) + return; + + try + { + // Select the plugin.. + var plugin = this.Plugins[this.SelectedPluginIndex]; + if (plugin == null) + throw new Exception("Invalid plugin selected."); + + // Allow the plugin to process the file.. + if (plugin.CanProcessFile(this.InputFilePath)) + this.AddLogMessage(this, !plugin.ProcessFile(this.InputFilePath, this.Options) ? new LogMessageEventArgs("Failed to unpack file.", LogMessageType.Error) : new LogMessageEventArgs("Successfully unpacked file!", LogMessageType.Success)); + else + this.AddLogMessage(this, new LogMessageEventArgs("Failed to unpack file.", LogMessageType.Error)); + } + catch (Exception ex) + { + this.AddLogMessage(this, new LogMessageEventArgs("Caught unhandled exception trying to unpack file.", LogMessageType.Error)); + this.AddLogMessage(this, new LogMessageEventArgs("Exception:", LogMessageType.Error)); + this.AddLogMessage(this, new LogMessageEventArgs(ex.Message, LogMessageType.Error)); + } + }); + } + #endregion + + #region == Window Related Properties ================================================================== + /// + /// Gets or sets the window close command. + /// + public RelayCommand OnWindowCloseCommand { get; set; } + + /// + /// Gets or sets the window minimize command. + /// + public RelayCommand OnWindowMinimizeCommand { get; set; } + + /// + /// Gets or sets the window mouse down command. + /// + public RelayCommand OnWindowMouseDownCommand { get; set; } + + /// + /// Gets or sets the show about view command. + /// + public RelayCommand OnShowAboutViewCommand { get; set; } + + /// + /// Gets or sets the open hyperlink command. + /// + public RelayCommand OnOpenHyperlinkCommand { get; set; } + + /// + /// Gets or sets the input file textbox drag drop command. + /// + public RelayCommand OnDragDropCommand { get; set; } + + /// + /// Gets or sets the input file textbox drag enter command. + /// + public RelayCommand OnPreviewDragEnterCommand { get; set; } + + /// + /// Gets or sets the input file browse command. + /// + public RelayCommand OnBrowseForInputFileCommand { get; set; } + + /// + /// Gets or sets the unpack file command. + /// + public RelayCommand OnUnpackFileCommand { get; set; } + + /// + /// Gets or sets the clear log command. + /// + public RelayCommand OnClearLogCommand { get; set; } + #endregion + + #region == (All) ViewModel Related Properties ========================================================= + /// + /// Gets or sets the applications current state. + /// + public ApplicationState State + { + get { return this.Get("State"); } + set { this.Set("State", value); } + } + + /// + /// Gets or sets the Steamless version. + /// + public Version SteamlessVersion + { + get { return this.Get("SteamlessVersion"); } + set { this.Set("SteamlessVersion", value); } + } + + /// + /// Gets or sets the current task. + /// + public BaseTask CurrentTask + { + get { return this.Get("CurrentTask"); } + set { this.Set("CurrentTask", value); } + } + + /// + /// Gets or sets the list of tasks. + /// + public ConcurrentBag Tasks + { + get { return this.Get>("Tasks"); } + set { this.Set("Tasks", value); } + } + + /// + /// Gets or sets if the about view should be seen. + /// + public bool ShowAboutView + { + get { return this.Get("ShowAboutView"); } + set { this.Set("ShowAboutView", value); } + } + #endregion + + #region == (Main) ViewModel Related Properties ======================================================== + /// + /// Gets or sets the list of plugins. + /// + public ObservableCollection Plugins + { + get { return this.Get>("Plugins"); } + set { this.Set("Plugins", value); } + } + + /// + /// Gets or sets the selected plugin index. + /// + public int SelectedPluginIndex + { + get { return this.Get("SelectedPluginIndex"); } + set { this.Set("SelectedPluginIndex", value); } + } + + /// + /// Gets or sets the input file path. + /// + public string InputFilePath + { + get { return this.Get("InputFilePath"); } + set { this.Set("InputFilePath", value); } + } + + /// + /// Gets or sets the Steamless options. + /// + public SteamlessOptions Options + { + get { return this.Get("Options"); } + set { this.Set("Options", value); } + } + + /// + /// Gets or sets the message log. + /// + public ObservableCollection Log + { + get { return this.Get>("Log"); } + set { this.Set("Log", value); } + } + #endregion + } +} \ No newline at end of file diff --git a/Steamless/ViewModel/ViewModelLocator.cs b/Steamless/ViewModel/ViewModelLocator.cs new file mode 100644 index 0000000..8f2f10b --- /dev/null +++ b/Steamless/ViewModel/ViewModelLocator.cs @@ -0,0 +1,64 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.ViewModel +{ + using API.Services; + using GalaSoft.MvvmLight.Ioc; + using Microsoft.Practices.ServiceLocation; + using Model; + + public class ViewModelLocator + { + /// + /// Default Constructor + /// + static ViewModelLocator() + { + // Setup the locator provider.. + ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); + + // Register our types.. + SimpleIoc.Default.Register(); + SimpleIoc.Default.Register(); + SimpleIoc.Default.Register(); + } + + /// + /// Gets the main window view model. + /// + public MainWindowViewModel MainWindow => ServiceLocator.Current.GetInstance(); + + /// + /// Gets the main data service. + /// + public IDataService DataService => ServiceLocator.Current.GetInstance(); + + /// + /// Gets the logging service. + /// + public LoggingService LoggingService => ServiceLocator.Current.GetInstance(); + } +} \ No newline at end of file