From b3c1fc6e24f45428eda3e8c051dd324f3279f943 Mon Sep 17 00:00:00 2001 From: atom0s Date: Fri, 6 Jan 2017 02:17:35 -0800 Subject: [PATCH] Initial code commit. Steamless v3.0.0.1 code base. --- ExamplePlugin/ExamplePlugin.csproj | 54 + ExamplePlugin/Main.cs | 101 ++ ExamplePlugin/Properties/AssemblyInfo.cs | 40 + Steamless.API/Crypto/AesHelper.cs | 172 ++ Steamless.API/Events/LogMessageEventArgs.cs | 62 + Steamless.API/Events/LogMessageType.cs | 58 + .../Extensions/FileStreamExtensions.cs | 42 + Steamless.API/Model/NavigatedEventArgs.cs | 42 + Steamless.API/Model/NotifiableModel.cs | 104 ++ Steamless.API/Model/SteamlessOptions.cs | 77 + Steamless.API/Model/SteamlessPlugin.cs | 118 ++ Steamless.API/Model/ViewModelBase.cs | 115 ++ Steamless.API/PE32/NativeApi32.cs | 603 +++++++ Steamless.API/PE32/Pe32File.cs | 421 +++++ Steamless.API/PE32/Pe32Helpers.cs | 128 ++ Steamless.API/Properties/AssemblyInfo.cs | 40 + Steamless.API/Services/LoggingService.cs | 63 + Steamless.API/Steamless.API.csproj | 68 + Steamless.API/SteamlessApiVersionAttribute.cs | 48 + Steamless.API/SteamlessEvents.cs | 47 + .../Classes/SteamStubDrmFlags.cs | 38 + .../Classes/SteamStubHeader.cs | 60 + .../Classes/SteamStubHelpers.cs | 122 ++ Steamless.Unpacker.Variant20.x86/Main.cs | 632 +++++++ .../Properties/AssemblyInfo.cs | 40 + .../SharpDisasm.dll | Bin 0 -> 222208 bytes .../Steamless.Unpacker.Variant20.x86.csproj | 60 + .../Classes/SteamStubDrmFlags.cs | 39 + .../Classes/SteamStubHeader.cs | 81 + .../Classes/SteamStubHelpers.cs | 122 ++ Steamless.Unpacker.Variant30.x86/Main.cs | 496 ++++++ .../Properties/AssemblyInfo.cs | 40 + .../Steamless.Unpacker.Variant30.x86.csproj | 57 + .../Classes/SteamStubDrmFlags.cs | 39 + .../Classes/SteamStubHeader.cs | 75 + .../Classes/SteamStubHelpers.cs | 122 ++ Steamless.Unpacker.Variant31.x86/Main.cs | 532 ++++++ .../Properties/AssemblyInfo.cs | 40 + .../Steamless.Unpacker.Variant31.x86.csproj | 57 + Steamless.sln | 52 + Steamless/App.config | 6 + Steamless/App.xaml | 48 + Steamless/App.xaml.cs | 79 + Steamless/Assets/Animations.xaml | 58 + Steamless/Assets/Controls.CheckBox.xaml | 108 ++ Steamless/Assets/Controls.Scrollbars.xaml | 448 +++++ Steamless/Assets/Controls.xaml | 805 +++++++++ Steamless/Assets/Theme.xaml | 152 ++ Steamless/Assets/Window.xaml | 99 + Steamless/Assets/icons.xaml | 1585 +++++++++++++++++ Steamless/Assets/steam.ico | Bin 0 -> 99678 bytes Steamless/Assets/steam.png | Bin 0 -> 20556 bytes Steamless/Classes/ControlsHelper.cs | 55 + .../GridViewColumnWidthFromItemsBehavior.cs | 100 ++ .../Embedded/GalaSoft.MvvmLight.Extras.dll | Bin 0 -> 21504 bytes Steamless/Embedded/GalaSoft.MvvmLight.dll | Bin 0 -> 28672 bytes .../Microsoft.Practices.ServiceLocation.dll | Bin 0 -> 18112 bytes Steamless/Embedded/Newtonsoft.Json.dll | Bin 0 -> 489472 bytes .../Embedded/System.Windows.Interactivity.dll | Bin 0 -> 39936 bytes Steamless/Model/ApplicationState.cs | 58 + Steamless/Model/AutomaticPlugin.cs | 100 ++ Steamless/Model/DataService.cs | 146 ++ Steamless/Model/IDataService.cs | 47 + Steamless/Model/Tasks/BaseTask.cs | 87 + Steamless/Model/Tasks/LoadPluginsTask.cs | 84 + Steamless/Model/Tasks/StartSteamlessTask.cs | 52 + Steamless/Model/Tasks/StatusTask.cs | 53 + Steamless/Properties/AssemblyInfo.cs | 41 + Steamless/Properties/Resources.Designer.cs | 71 + Steamless/Properties/Resources.resx | 117 ++ Steamless/Properties/Settings.Designer.cs | 30 + Steamless/Properties/Settings.settings | 7 + Steamless/Steamless.csproj | 206 +++ Steamless/View/AboutView.xaml | 316 ++++ Steamless/View/AboutView.xaml.cs | 41 + Steamless/View/MainView.xaml | 141 ++ Steamless/View/MainView.xaml.cs | 41 + Steamless/View/MainWindow.xaml | 189 ++ Steamless/View/MainWindow.xaml.cs | 41 + Steamless/View/SplashView.xaml | 151 ++ Steamless/View/SplashView.xaml.cs | 41 + Steamless/ViewModel/MainWindowViewModel.cs | 492 +++++ Steamless/ViewModel/ViewModelLocator.cs | 64 + 83 files changed, 11266 insertions(+) create mode 100644 ExamplePlugin/ExamplePlugin.csproj create mode 100644 ExamplePlugin/Main.cs create mode 100644 ExamplePlugin/Properties/AssemblyInfo.cs create mode 100644 Steamless.API/Crypto/AesHelper.cs create mode 100644 Steamless.API/Events/LogMessageEventArgs.cs create mode 100644 Steamless.API/Events/LogMessageType.cs create mode 100644 Steamless.API/Extensions/FileStreamExtensions.cs create mode 100644 Steamless.API/Model/NavigatedEventArgs.cs create mode 100644 Steamless.API/Model/NotifiableModel.cs create mode 100644 Steamless.API/Model/SteamlessOptions.cs create mode 100644 Steamless.API/Model/SteamlessPlugin.cs create mode 100644 Steamless.API/Model/ViewModelBase.cs create mode 100644 Steamless.API/PE32/NativeApi32.cs create mode 100644 Steamless.API/PE32/Pe32File.cs create mode 100644 Steamless.API/PE32/Pe32Helpers.cs create mode 100644 Steamless.API/Properties/AssemblyInfo.cs create mode 100644 Steamless.API/Services/LoggingService.cs create mode 100644 Steamless.API/Steamless.API.csproj create mode 100644 Steamless.API/SteamlessApiVersionAttribute.cs create mode 100644 Steamless.API/SteamlessEvents.cs create mode 100644 Steamless.Unpacker.Variant20.x86/Classes/SteamStubDrmFlags.cs create mode 100644 Steamless.Unpacker.Variant20.x86/Classes/SteamStubHeader.cs create mode 100644 Steamless.Unpacker.Variant20.x86/Classes/SteamStubHelpers.cs create mode 100644 Steamless.Unpacker.Variant20.x86/Main.cs create mode 100644 Steamless.Unpacker.Variant20.x86/Properties/AssemblyInfo.cs create mode 100644 Steamless.Unpacker.Variant20.x86/SharpDisasm.dll create mode 100644 Steamless.Unpacker.Variant20.x86/Steamless.Unpacker.Variant20.x86.csproj create mode 100644 Steamless.Unpacker.Variant30.x86/Classes/SteamStubDrmFlags.cs create mode 100644 Steamless.Unpacker.Variant30.x86/Classes/SteamStubHeader.cs create mode 100644 Steamless.Unpacker.Variant30.x86/Classes/SteamStubHelpers.cs create mode 100644 Steamless.Unpacker.Variant30.x86/Main.cs create mode 100644 Steamless.Unpacker.Variant30.x86/Properties/AssemblyInfo.cs create mode 100644 Steamless.Unpacker.Variant30.x86/Steamless.Unpacker.Variant30.x86.csproj create mode 100644 Steamless.Unpacker.Variant31.x86/Classes/SteamStubDrmFlags.cs create mode 100644 Steamless.Unpacker.Variant31.x86/Classes/SteamStubHeader.cs create mode 100644 Steamless.Unpacker.Variant31.x86/Classes/SteamStubHelpers.cs create mode 100644 Steamless.Unpacker.Variant31.x86/Main.cs create mode 100644 Steamless.Unpacker.Variant31.x86/Properties/AssemblyInfo.cs create mode 100644 Steamless.Unpacker.Variant31.x86/Steamless.Unpacker.Variant31.x86.csproj create mode 100644 Steamless.sln create mode 100644 Steamless/App.config create mode 100644 Steamless/App.xaml create mode 100644 Steamless/App.xaml.cs create mode 100644 Steamless/Assets/Animations.xaml create mode 100644 Steamless/Assets/Controls.CheckBox.xaml create mode 100644 Steamless/Assets/Controls.Scrollbars.xaml create mode 100644 Steamless/Assets/Controls.xaml create mode 100644 Steamless/Assets/Theme.xaml create mode 100644 Steamless/Assets/Window.xaml create mode 100644 Steamless/Assets/icons.xaml create mode 100644 Steamless/Assets/steam.ico create mode 100644 Steamless/Assets/steam.png create mode 100644 Steamless/Classes/ControlsHelper.cs create mode 100644 Steamless/Classes/GridViewColumnWidthFromItemsBehavior.cs create mode 100644 Steamless/Embedded/GalaSoft.MvvmLight.Extras.dll create mode 100644 Steamless/Embedded/GalaSoft.MvvmLight.dll create mode 100644 Steamless/Embedded/Microsoft.Practices.ServiceLocation.dll create mode 100644 Steamless/Embedded/Newtonsoft.Json.dll create mode 100644 Steamless/Embedded/System.Windows.Interactivity.dll create mode 100644 Steamless/Model/ApplicationState.cs create mode 100644 Steamless/Model/AutomaticPlugin.cs create mode 100644 Steamless/Model/DataService.cs create mode 100644 Steamless/Model/IDataService.cs create mode 100644 Steamless/Model/Tasks/BaseTask.cs create mode 100644 Steamless/Model/Tasks/LoadPluginsTask.cs create mode 100644 Steamless/Model/Tasks/StartSteamlessTask.cs create mode 100644 Steamless/Model/Tasks/StatusTask.cs create mode 100644 Steamless/Properties/AssemblyInfo.cs create mode 100644 Steamless/Properties/Resources.Designer.cs create mode 100644 Steamless/Properties/Resources.resx create mode 100644 Steamless/Properties/Settings.Designer.cs create mode 100644 Steamless/Properties/Settings.settings create mode 100644 Steamless/Steamless.csproj create mode 100644 Steamless/View/AboutView.xaml create mode 100644 Steamless/View/AboutView.xaml.cs create mode 100644 Steamless/View/MainView.xaml create mode 100644 Steamless/View/MainView.xaml.cs create mode 100644 Steamless/View/MainWindow.xaml create mode 100644 Steamless/View/MainWindow.xaml.cs create mode 100644 Steamless/View/SplashView.xaml create mode 100644 Steamless/View/SplashView.xaml.cs create mode 100644 Steamless/ViewModel/MainWindowViewModel.cs create mode 100644 Steamless/ViewModel/ViewModelLocator.cs 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 0000000000000000000000000000000000000000..950c59dc3feca4bc4fd74178ef1f0d118fcd3dcb GIT binary patch literal 222208 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 + + + + 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