From 72d37c21194e15f9e13d3c54fa18081f466dfa9a Mon Sep 17 00:00:00 2001 From: Nikolaj Mariager Date: Fri, 18 Sep 2015 23:01:47 +0200 Subject: [PATCH] A bunch of changes over the course of a year. I'm bad at version control, sorry. --- TinkerWorX.SharpCraft.Launcher/Common.cs | 101 ++++-- .../Properties/AssemblyInfo.cs | 4 +- .../TinkerWorX.SharpCraft.Launcher.csproj | 4 + TinkerWorX.SharpCraft.Launcher/app.manifest | 58 ++++ TinkerWorX.SharpCraft/Addresses.cs | 55 +++ TinkerWorX.SharpCraft/EntryPoint.cs | 171 +++++++++- TinkerWorX.SharpCraft/IMapPlugin.cs | 14 + TinkerWorX.SharpCraft/IPlugin.cs | 14 + TinkerWorX.SharpCraft/PluginBase.cs | 14 - TinkerWorX.SharpCraft/PluginContext.cs | 1 - TinkerWorX.SharpCraft/PluginPackage.cs | 182 ---------- TinkerWorX.SharpCraft/PluginSystem.cs | 317 +++++++----------- .../Properties/AssemblyInfo.cs | 4 +- TinkerWorX.SharpCraft/RequiresAttribute.cs | 20 ++ TinkerWorX.SharpCraft/Storm.cs | 100 ++++++ .../TinkerWorX.SharpCraft.csproj | 12 +- TinkerWorX.SharpCraft/TypeExtensions.cs | 24 ++ .../Utilities/DependencySorter.cs | 5 +- TinkerWorX.SharpCraft/ZipEntryExtensions.cs | 29 -- 19 files changed, 648 insertions(+), 481 deletions(-) create mode 100644 TinkerWorX.SharpCraft.Launcher/app.manifest create mode 100644 TinkerWorX.SharpCraft/Addresses.cs create mode 100644 TinkerWorX.SharpCraft/IMapPlugin.cs create mode 100644 TinkerWorX.SharpCraft/IPlugin.cs delete mode 100644 TinkerWorX.SharpCraft/PluginBase.cs delete mode 100644 TinkerWorX.SharpCraft/PluginPackage.cs create mode 100644 TinkerWorX.SharpCraft/RequiresAttribute.cs create mode 100644 TinkerWorX.SharpCraft/Storm.cs create mode 100644 TinkerWorX.SharpCraft/TypeExtensions.cs delete mode 100644 TinkerWorX.SharpCraft/ZipEntryExtensions.cs diff --git a/TinkerWorX.SharpCraft.Launcher/Common.cs b/TinkerWorX.SharpCraft.Launcher/Common.cs index a6d74d4..6b005bf 100644 --- a/TinkerWorX.SharpCraft.Launcher/Common.cs +++ b/TinkerWorX.SharpCraft.Launcher/Common.cs @@ -7,57 +7,94 @@ using System.Text; using EasyHook; using Microsoft.Win32; +using System.Windows; namespace TinkerWorX.SharpCraft.Launcher { internal static class Common { - public static Boolean IsInitialized { get; private set; } + public static Boolean IsInitialized + { get; private set; } - public static String InstallPath { get; private set; } + public static String InstallPath + { get; private set; } - public static String GamePath { get; private set; } + public static String GamePath + { get; private set; } - public static String EditorPath { get; private set; } + public static String EditorPath + { get; private set; } - public static String Version { get { return FileVersionInfo.GetVersionInfo(typeof(SharpCraftApplication).Assembly.Location).FileVersion; } } + public static String Version + { get { return FileVersionInfo.GetVersionInfo(typeof(SharpCraftApplication).Assembly.Location).FileVersion; } } - public static void Initialize() + public static Boolean Initialize() { - if (Common.IsInitialized) - return; - - var key = Registry.CurrentUser.OpenSubKey(@"Software\Blizzard Entertainment\Warcraft III"); - if (key == null) - throw new KeyNotFoundException("Could not find Warcraft III key in registry!" + Environment.NewLine + "To fix this, make sure you've launched Warcraft III at least once."); - var value = key.GetValue("InstallPath"); - if (value == null) + if (!Common.IsInitialized) { - key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node\Blizzard Entertainment\Warcraft III"); - value = key.GetValue("InstallPath"); + Console.WriteLine("Locating InstallPath . . . "); + if (File.Exists(Path.Combine(Environment.CurrentDirectory, @"..\war3.exe"))) + { + Console.WriteLine("Found war3.exe in parent folder, using local InstallPath."); + Common.InstallPath = Environment.CurrentDirectory.Substring(0, Environment.CurrentDirectory.LastIndexOf("\\") + 1); + } + else + { + Console.WriteLine("Using registry to locate InstallPath."); + var key = Registry.CurrentUser.OpenSubKey(@"Software\Blizzard Entertainment\Warcraft III"); + if (key != null && key.GetValue("InstallPath") != null) + Common.InstallPath = (key.GetValue("InstallPath") as String); + if (String.IsNullOrEmpty(Common.InstallPath)) + { + key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node\Blizzard Entertainment\Warcraft III"); + if (key != null && key.GetValue("InstallPath") != null) + Common.InstallPath = (key.GetValue("InstallPath") as String); + } + if (String.IsNullOrEmpty(Common.InstallPath)) + { + var dialog = new OpenFileDialog(); + dialog.Filter = "Warcraft III|war3.exe"; + var result = dialog.ShowDialog(); + if (result.HasValue && result.Value) + { + Common.InstallPath = Path.GetDirectoryName(dialog.FileName); + + var doInstall = MessageBox.Show("Install path into registry?", "SharpCraft", MessageBoxButton.YesNo); + if (doInstall == MessageBoxResult.Yes) + { + key = Registry.CurrentConfig.CreateSubKey(@"Software\Blizzard Entertainment\Warcraft III"); + key.SetValue("InstallPath", Common.InstallPath); + } + } + else + { + return false; + } + + } + } + Common.GamePath = Path.Combine(InstallPath, "war3.exe"); + Common.EditorPath = Path.Combine(InstallPath, "worldedit.exe"); + + Common.IsInitialized = true; } - Common.InstallPath = (value as String); - if (String.IsNullOrEmpty(InstallPath)) - throw new KeyNotFoundException("Could not find InstallPath value in registry!" + Environment.NewLine + "To fix this, make sure you've launched Warcraft III at least once."); - Common.GamePath = Path.Combine(InstallPath, "war3.exe"); - Common.EditorPath = Path.Combine(InstallPath, "worldedit.exe"); - Common.IsInitialized = true; + return true; } public static void StartGame(String[] args) { - if (!Common.IsInitialized) - Common.Initialize(); + if (!Common.Initialize()) + return; if (!File.Exists(Common.GamePath)) throw new FileNotFoundException("Could not find war3.exe!" + Environment.NewLine + "You may need to verify your registry settings are correct.", "war3.exe"); if (!File.Exists(Path.Combine(Environment.CurrentDirectory, "SharpCraft.dll"))) throw new FileNotFoundException("Could not find SharpCraft.dll!" + Environment.NewLine + "You may need to redownload SharpCraft.", "SharpCraft.dll"); - Boolean kill = false; - Boolean debug = false; - Boolean valid = true; + var kill = false; + var debug = false; + var valid = true; while (valid && args.Length > 0) { switch (args[0]) @@ -86,7 +123,8 @@ public static void StartGame(String[] args) foreach (var war3 in war3s) war3.Kill(); } - else throw new InvalidOperationException("Warcraft III is already running!" + Environment.NewLine + "You may need to check Task Manager for \"war3.exe\", and kill it."); + else + throw new InvalidOperationException("Warcraft III is already running!" + Environment.NewLine + "You may need to check Task Manager for \"war3.exe\", and kill it."); } Console.Write("Creating and injecting into Warcraft III . . . "); @@ -101,8 +139,8 @@ public static void StartGame(String[] args) public static void StartEditor(String[] args) { - if (!Common.IsInitialized) - Common.Initialize(); + if (!Common.Initialize()) + return; if (!File.Exists(Common.EditorPath)) throw new FileNotFoundException("Could not find worldedit.exe!" + Environment.NewLine + "You may need to verify your registry settings are correct.", "worldedit.exe"); @@ -140,7 +178,8 @@ public static void StartEditor(String[] args) foreach (var worldedit in worldedits) worldedit.Kill(); } - else throw new InvalidOperationException("World Editor is already running!" + Environment.NewLine + "You may need to check Task Manager for \"worldedit.exe\", and kill it."); + else + throw new InvalidOperationException("World Editor is already running!" + Environment.NewLine + "You may need to check Task Manager for \"worldedit.exe\", and kill it."); } Console.Write("Creating and injecting into World Editor . . . "); diff --git a/TinkerWorX.SharpCraft.Launcher/Properties/AssemblyInfo.cs b/TinkerWorX.SharpCraft.Launcher/Properties/AssemblyInfo.cs index 9ac0dcf..3f25b64 100644 --- a/TinkerWorX.SharpCraft.Launcher/Properties/AssemblyInfo.cs +++ b/TinkerWorX.SharpCraft.Launcher/Properties/AssemblyInfo.cs @@ -51,5 +51,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.0.1.135")] -[assembly: AssemblyFileVersion("3.0.1.135")] +[assembly: AssemblyVersion("4.0.0.136")] +[assembly: AssemblyFileVersion("4.0.0.136")] diff --git a/TinkerWorX.SharpCraft.Launcher/TinkerWorX.SharpCraft.Launcher.csproj b/TinkerWorX.SharpCraft.Launcher/TinkerWorX.SharpCraft.Launcher.csproj index 2a9786e..0946c6e 100644 --- a/TinkerWorX.SharpCraft.Launcher/TinkerWorX.SharpCraft.Launcher.csproj +++ b/TinkerWorX.SharpCraft.Launcher/TinkerWorX.SharpCraft.Launcher.csproj @@ -36,6 +36,9 @@ TinkerWorX.SharpCraft.Launcher.SharpCraftApplication + + app.manifest + @@ -93,6 +96,7 @@ ResXFileCodeGenerator Resources.Designer.cs + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/TinkerWorX.SharpCraft.Launcher/app.manifest b/TinkerWorX.SharpCraft.Launcher/app.manifest new file mode 100644 index 0000000..d4437aa --- /dev/null +++ b/TinkerWorX.SharpCraft.Launcher/app.manifest @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TinkerWorX.SharpCraft/Addresses.cs b/TinkerWorX.SharpCraft/Addresses.cs new file mode 100644 index 0000000..31165a1 --- /dev/null +++ b/TinkerWorX.SharpCraft/Addresses.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TinkerWorX.SharpCraft +{ + internal static class Addresses + { + // === INSTRUCTIONS === + // 1. Hit "Alt + B" to search for the following byte sequence, + // including the quotes: ".?AUAgent2HandleReg@@". This should return one + // result, looking similar to this: + // .data:6FA7951C a_?auagent2hand db '.?AUAgent2HandleReg@@',0 + // 2. Double click the result. (Only in IDAPro 5.5) + // 3. Place the cursor in the following part of the line: + // .data:6FA7951C a_?auagent2hand db '.?AUAgent2HandleReg@@',0 + // ^^^^^^^^^^^^^^^ + // 4. Press "x", to cross reference the string. + // This should give you several results. Four of them are functions. + // 5. One of the functions will look like this (first one likely): + // -> .text:6F3A465F call sub_6F3A3AD0 + // .text:6F3A4664 push 1 + // .text:6F3A4666 push 0FFFFFFFEh + // .text:6F3A4668 push offset a_?auagent2hand ; ".?AUAgent2HandleReg@@" + // 6. Double click sub_6F3A3AD0. + // 7. You are now in a function that looks like this: + // .text:6F3A3AD0 sub_6F3A3AD0 proc near + // .text:6F3A3AD0 + // .text:6F3A3AD0 + // .text:6F3A3AD0 arg_0 = dword ptr 4 + // .text:6F3A3AD0 arg_4 = dword ptr 8 + // .text:6F3A3AD0 + // .text:6F3A3AD0 push ebx + // .text:6F3A3AD1 push edi + // .text:6F3A3AD2 mov edi, edx + // .text:6F3A3AD4 mov ebx, ecx + // .text:6F3A3AD6 call sub_6F442670 + // .text:6F3A3ADB mov ecx, 0Dh + // .text:6F3A3AE0 call sub_6F4C34D0 + // .text:6F3A3AE5 mov eax, [eax+10h] + // .text:6F3A3AE8 mov ecx, [eax+18h] + // .text:6F3A3AEB test ecx, ecx + // .text:6F3A3AED jz short loc_6F3A3AFE + // .text:6F3A3AEF mov edx, [esp+8+arg_4] + // .text:6F3A3AF3 mov eax, [esp+8+arg_0] + // .text:6F3A3AF7 push edx + // .text:6F3A3AF8 push eax + // -> .text:6F3A3AF9 call sub_6F3A2EC0 + // 8. sub_6F3A2EC0 is the game_state function. + // 7. Using the image base, you rebase this address like this: + // 6F3A2EC0 - 6F000000 = 003A2EC0 + public const Int32 Unknown__SetStateOffset = 0x3A2EC0; + } +} diff --git a/TinkerWorX.SharpCraft/EntryPoint.cs b/TinkerWorX.SharpCraft/EntryPoint.cs index 48e012c..83abebe 100644 --- a/TinkerWorX.SharpCraft/EntryPoint.cs +++ b/TinkerWorX.SharpCraft/EntryPoint.cs @@ -3,18 +3,33 @@ using System.IO; using System.Threading; using System.Windows; +using System.Linq; using EasyHook; using TinkerWorX.SharpCraft.Utilities; using TinkerWorX.SharpCraft.Windows; +using System.Runtime.InteropServices; +using System.Collections.Generic; +using System.Reflection; namespace TinkerWorX.SharpCraft { unsafe public class EntryPoint : IEntryPoint { + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr Unknown__SetStateDelegate(IntPtr @this, Boolean endMap, Boolean endEngine); + + private Unknown__SetStateDelegate Unknown__SetState; + private Kernel32.LoadLibraryAPrototype LoadLibraryA; + private String pluginsFolder; + private PluginContext context; + public Boolean IsInGame; + + public Boolean IsInMap; + public EntryPoint(RemoteHooking.IContext hookingContext, PluginContext context, Boolean isDebugging, String hackPath, String installPath) { try @@ -55,24 +70,77 @@ public void Run(RemoteHooking.IContext hookingContext, PluginContext context, Bo Trace.WriteLine("-------------------"); Trace.WriteLine(DateTime.Now); Trace.WriteLine("-------------------"); - Trace.WriteLine(String.Empty); + + + AppDomain.CurrentDomain.AssemblyResolve += (Object sender, ResolveEventArgs args) => + { + var path = String.Empty; + // extract the file name + var file = String.Empty; + if (args.Name.IndexOf(',') >= 0) + file = args.Name.Substring(0, args.Name.IndexOf(',')) + ".dll"; + else if (args.Name.IndexOf(".dll") >= 0) + file = Path.GetFileName(args.Name); + else + return null; + + // locate the actual file + path = Directory.GetFiles(hackPath, file, SearchOption.AllDirectories).FirstOrDefault(); + if (!String.IsNullOrEmpty(path)) + return Assembly.LoadFrom(path); + + path = Directory.GetFiles(pluginsFolder, file, SearchOption.AllDirectories).FirstOrDefault(); + if (!String.IsNullOrEmpty(path)) + return Assembly.LoadFrom(path); + + return null; + }; + + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (Object sender, ResolveEventArgs args) => + { + var path = String.Empty; + // extract the file name + var file = String.Empty; + if (args.Name.IndexOf(',') >= 0) + file = args.Name.Substring(0, args.Name.IndexOf(',')) + ".dll"; + else if (args.Name.IndexOf(".dll") >= 0) + file = Path.GetFileName(args.Name); + else + return null; + + // locate the actual file + path = Directory.GetFiles(hackPath, file, SearchOption.AllDirectories).FirstOrDefault(); + if (!String.IsNullOrEmpty(path)) + return Assembly.ReflectionOnlyLoadFrom(path); + + path = Directory.GetFiles(pluginsFolder, file, SearchOption.AllDirectories).FirstOrDefault(); + if (!String.IsNullOrEmpty(path)) + return Assembly.ReflectionOnlyLoadFrom(path); + + return null; + }; var sw = new Stopwatch(); - Trace.WriteLine("Initializing plugin system . . ."); + Trace.WriteLine("Preparing folders . . . "); Trace.Indent(); sw.Restart(); - PluginSystem.LoadPlugins(hackPath); + this.pluginsFolder = Path.Combine(hackPath, "plugins"); + if (!Directory.Exists(this.pluginsFolder)) + Directory.CreateDirectory(this.pluginsFolder); + sw.Stop(); + Trace.WriteLine("Done! (" + sw.Elapsed.TotalMilliseconds.ToString("0.00") + " ms)"); Trace.Unindent(); - Trace.WriteLine(" - Done! (" + sw.ElapsedMilliseconds + " ms)"); - Trace.WriteLine("Initializing plugins . . ."); + + Trace.WriteLine("Loading plugins from '" + this.pluginsFolder + "' . . ."); Trace.Indent(); sw.Restart(); - PluginSystem.Initialize(PluginContext.Common); - PluginSystem.Initialize(context); + PluginSystem.LoadPlugins(pluginsFolder, this.context); + sw.Stop(); + Trace.WriteLine("Done! (" + sw.Elapsed.TotalMilliseconds.ToString("0.00") + " ms)"); Trace.Unindent(); - Trace.WriteLine(" - Done! (" + sw.ElapsedMilliseconds + " ms)"); + // Prepare the OnGameLoad hook. var address = LocalHook.GetProcAddress("kernel32.dll", "LoadLibraryA"); @@ -108,18 +176,91 @@ private IntPtr LoadLibraryAHook(String fileName) case "game.dll": var sw = new Stopwatch(); - Trace.WriteLine("OnGameLoad plugins . . ."); - Trace.Indent(); - sw = Stopwatch.StartNew(); - PluginSystem.OnGameLoad(PluginContext.Common); - PluginSystem.OnGameLoad(this.context); - Trace.Unindent(); - Trace.WriteLine(" - Done! (" + sw.ElapsedMilliseconds + " ms)"); + PluginSystem.OnGameLoad(); + + // Prepare the Unknown__SetState hook. + var address = module + Addresses.Unknown__SetStateOffset; + Trace.Write("Installing Unknown_SetState hook @ 0x" + address.ToString("X8") + " . "); + this.Unknown__SetState = Memory.InstallHook(address, new Unknown__SetStateDelegate(this.Unknown__SetStateHook), true, false); + Trace.WriteLine("installed!"); break; } return module; } + + private void OnEngineStart() + { + + } + + private void OnEngineEnd() + { + + } + + private void OnMapStart() + { + PluginSystem.OnMapStart(); + } + + private void OnMapEnd() + { + PluginSystem.OnMapEnd(); + } + + private IntPtr Unknown__SetStateHook(IntPtr @this, Boolean endMap, Boolean endEngine) + { + try + { + if (endEngine) + { + if (this.IsInMap) + { + this.IsInMap = false; + this.OnMapEnd(); + } + if (this.IsInGame) + { + this.IsInGame = false; + this.OnEngineEnd(); + } + } + else + { + if (endMap) + { + if (this.IsInMap) + { + this.IsInMap = false; + this.OnMapEnd(); + } + } + else + { + if (!this.IsInGame) + { + this.OnEngineStart(); + this.IsInGame = true; + } + + if (this.IsInMap) + { + this.OnMapEnd(); + } + this.OnMapStart(); + this.IsInMap = true; + } + } + } + catch (Exception e) + { + Trace.WriteLine("Unhandled Exception in " + typeof(EntryPoint).Name + ".Unknown__SetStateHook!"); + Trace.WriteLine(e.ToString()); + } + + return this.Unknown__SetState(@this, endMap, endEngine); + } } } diff --git a/TinkerWorX.SharpCraft/IMapPlugin.cs b/TinkerWorX.SharpCraft/IMapPlugin.cs new file mode 100644 index 0000000..d6ee354 --- /dev/null +++ b/TinkerWorX.SharpCraft/IMapPlugin.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TinkerWorX.SharpCraft +{ + public interface IMapPlugin + { + void OnMapStart(); + + void OnMapEnd(); + } +} diff --git a/TinkerWorX.SharpCraft/IPlugin.cs b/TinkerWorX.SharpCraft/IPlugin.cs new file mode 100644 index 0000000..c8ec3cb --- /dev/null +++ b/TinkerWorX.SharpCraft/IPlugin.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TinkerWorX.SharpCraft +{ + public interface IPlugin + { + void Initialize(PluginContext context); + + void OnGameLoad(PluginContext context); + } +} diff --git a/TinkerWorX.SharpCraft/PluginBase.cs b/TinkerWorX.SharpCraft/PluginBase.cs deleted file mode 100644 index 709d38d..0000000 --- a/TinkerWorX.SharpCraft/PluginBase.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TinkerWorX.SharpCraft -{ - public abstract class PluginBase - { - public virtual void Initialize(PluginContext context) { } - - public virtual void OnGameLoad(PluginContext context) { } - } -} diff --git a/TinkerWorX.SharpCraft/PluginContext.cs b/TinkerWorX.SharpCraft/PluginContext.cs index 448d3a5..ca4e98e 100644 --- a/TinkerWorX.SharpCraft/PluginContext.cs +++ b/TinkerWorX.SharpCraft/PluginContext.cs @@ -7,7 +7,6 @@ namespace TinkerWorX.SharpCraft { public enum PluginContext { - Common, Game, Editor } diff --git a/TinkerWorX.SharpCraft/PluginPackage.cs b/TinkerWorX.SharpCraft/PluginPackage.cs deleted file mode 100644 index 93ae64a..0000000 --- a/TinkerWorX.SharpCraft/PluginPackage.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Xml.Serialization; -using Ionic.Zip; - -namespace TinkerWorX.SharpCraft -{ - public class PluginPackage - { - public static PluginPackage FromArchive(ZipFile archive) - { - var package = (PluginPackage)null; - - var contentsEntry = archive.FirstOrDefault(file => file.FileName == "contents.xml"); - if (contentsEntry == null) - throw new FileNotFoundException("'contents.xml' is missing!", "contents.xml"); - - using (var stream = contentsEntry.ToStream()) - { - var contents = PluginPackageContentsXml.FromStream(stream); - - package = new PluginPackage(contents.Id, contents.Name, contents.Version, contents.Target); - - foreach (var file in contents.Files) - { - var entry = archive.FirstOrDefault(a => a.FileName == file.Name); - if (entry == null) - throw new FileNotFoundException("The package file '" + file.Name + "' is missing!", file.Name); - package.files.Add(new PluginPackageFile(file.Name, entry.ToBytes())); - } - - foreach (var reference in contents.References) - { - package.references.Add(new PluginPackageReference(reference.Id, reference.Version)); - } - } - - return package; - } - - public static PluginPackage FromDirectory(String directory) - { - var package = (PluginPackage)null; - - if (!File.Exists(Path.Combine(directory, "contents.xml"))) - throw new FileNotFoundException("'contents.xml' is missing!", "contents.xml"); - - using (var stream = File.Open(Path.Combine(directory, "contents.xml"), FileMode.Open, FileAccess.Read, FileShare.Read)) - { - var contents = PluginPackageContentsXml.FromStream(stream); - - package = new PluginPackage(contents.Id, contents.Name, contents.Version, contents.Target); - foreach (var file in contents.Files) - { - var path = Path.Combine(directory, file.Name); - if (!File.Exists(path)) - throw new FileNotFoundException("The package file '" + file.Name + "' is missing!", file.Name); - package.files.Add(new PluginPackageFile(file.Name, File.ReadAllBytes(path))); - } - - foreach (var reference in contents.References) - { - package.references.Add(new PluginPackageReference(reference.Id, reference.Version)); - } - } - - return package; - } - - public static PluginPackage FromArchive(String file) - { - return PluginPackage.FromArchive(ZipFile.Read(file)); - } - - private List files = new List(); - - private List references = new List(); - - public PluginPackage(String id, String name, Version version, Version target) - { - this.Id = id; - this.Name = name; - this.Version = version; - this.Target = target; - } - - public String Id { get; private set; } - - public String Name { get; private set; } - - public Version Version { get; private set; } - - public Version Target { get; private set; } - - public ReadOnlyCollection Files { get { return this.files.AsReadOnly(); } } - - public ReadOnlyCollection References { get { return this.references.AsReadOnly(); } } - } - - public class PluginPackageReference - { - public PluginPackageReference(String id, Version version) - { - this.Id = id; - this.Version = version; - } - - public String Id { get; private set; } - - public Version Version { get; private set; } - } - - public class PluginPackageFile - { - public PluginPackageFile(String name, Byte[] data) - { - this.Name = name; - this.Data = data; - } - - public String Name { get; private set; } - - public Byte[] Data { get; private set; } - } - - [XmlRoot("plugin_package", IsNullable = false)] - public class PluginPackageContentsXml - { - public static PluginPackageContentsXml FromStream(Stream stream) - { - var serializer = new XmlSerializer(typeof(PluginPackageContentsXml)); - return (PluginPackageContentsXml)serializer.Deserialize(stream); - } - - [XmlAttribute("id")] - public String Id; - - [XmlAttribute("version")] - public String VersionValue = new Version(0, 0, 0, 0).ToString(); - public Version Version { get { return new Version(this.VersionValue); } } - - [XmlAttribute("name")] - public String Name; - - [XmlAttribute("target")] - public String TargetValue = new Version(0, 0, 0, 0).ToString(); - public Version Target - { - get - { - if (this.TargetValue.ToLower() == "agnostic") - return null; - return new Version(this.TargetValue); - } - } - - [XmlElement("reference")] - public List References = new List(); - - [XmlElement("file")] - public List Files = new List(); - } - - public class PluginPackageReferenceXml - { - [XmlAttribute("id")] - public String Id; - - [XmlAttribute("version")] - public String VersionValue = new Version(0, 0, 0, 0).ToString(); - public Version Version { get { return new Version(this.VersionValue); } } - } - - public class PluginPackageFileXml - { - [XmlAttribute("name")] - public String Name; - } -} diff --git a/TinkerWorX.SharpCraft/PluginSystem.cs b/TinkerWorX.SharpCraft/PluginSystem.cs index a323401..9b310de 100644 --- a/TinkerWorX.SharpCraft/PluginSystem.cs +++ b/TinkerWorX.SharpCraft/PluginSystem.cs @@ -11,56 +11,105 @@ namespace TinkerWorX.SharpCraft { internal static class PluginSystem { - private static String rootFolder; + private static String folder; - private static String packFolder; + private static PluginContext context; - private static String tempFolder; + private static List plugins = new List(); - private static List packages = new List(); + private static List mapPlugins = new List(); - private static List assemblyPaths = new List(); + private static Dictionary assemblies = new Dictionary(); - private static List plugins = new List(); + static PluginSystem() { } - public static void LoadPlugins(String folder) + public static void LoadPlugins(String folder, PluginContext context) { try { - Trace.Write("Preparing folders . . . "); - Trace.Indent(); - PrepareFolders(folder); - Trace.Unindent(); - Trace.WriteLine("done!"); + PluginSystem.folder = folder; + PluginSystem.context = context; - Trace.WriteLine("Loading packages . . ."); + Assembly.ReflectionOnlyLoadFrom(Path.Combine(folder, @"..\SharpCraft.dll")); + + var list = new DependencySorter(); + Trace.WriteLine("Locating plugins . . ."); Trace.Indent(); - LoadPackages(); + var sw = Stopwatch.StartNew(); + foreach (var file in Directory.GetFiles(folder, "*.dll", SearchOption.AllDirectories)) + { + try + { + Trace.WriteLine("File: " + Path.GetFileName(file)); + Trace.Indent(); + var assembly = Assembly.ReflectionOnlyLoadFrom(file); + foreach (var type in assembly.GetTypes().Where(t => t.Implements())) + { + try + { + Trace.WriteLine("Type: " + type.FullName); + assemblies.Add(type.FullName, file); + list.AddObjects(type.FullName); + Trace.Indent(); + foreach (var requires in type.GetRequires()) + { + list.SetDependencies(type.FullName, requires); + Trace.WriteLine("Requires: " + requires); + } + Trace.Unindent(); + } + catch (Exception e) + { + Trace.WriteLine("Exception: " + e.Message); + } + } + Trace.Unindent(); + } + catch (ReflectionTypeLoadException e) + { + Trace.WriteLine("Exception: " + e); + Trace.Indent(); + foreach (var exception in e.LoaderExceptions) + { + Trace.WriteLine("LoaderException: " + exception); + } + Trace.Unindent(); + } + } + sw.Stop(); + Trace.WriteLine("Done! (" + sw.Elapsed.TotalMilliseconds.ToString("0.00") + " ms)"); Trace.Unindent(); - Trace.WriteLine(" - Done!"); - Trace.WriteLine("Checking references . . ."); + Trace.WriteLine("Loading plugin assemblies and instanciating types. . ."); Trace.Indent(); - CheckReferences(); - SortPackages(); + sw.Restart(); + foreach (var s in list.Sort()) + { + Trace.WriteLine("Loading assembly '" + assemblies[s] + "' for type '" + s + "'"); + plugins.Add((IPlugin)Activator.CreateInstance(assemblies[s], s).Unwrap()); + } + sw.Stop(); + Trace.WriteLine("Done! (" + sw.Elapsed.TotalMilliseconds.ToString("0.00") + " ms)"); Trace.Unindent(); - Trace.WriteLine(" - Done!"); - Trace.WriteLine("Extracting packages . . ."); + Trace.WriteLine("Initializing plugins . . ."); Trace.Indent(); - ExtractPackages(); - InitializePackages(); + sw.Restart(); + foreach (var plugin in plugins) + { + Trace.WriteLine("Initializing " + plugin.GetType().FullName); + try + { + plugin.Initialize(context); + } + catch (Exception e) + { + Trace.WriteLine(e); + } + } + sw.Stop(); + Trace.WriteLine("Done! (" + sw.Elapsed.TotalMilliseconds.ToString("0.00") + " ms)"); Trace.Unindent(); - Trace.WriteLine(" - Done!"); - } - catch (CircularReferenceException) - { - MessageBox.Show( - "Fatal exception!" + Environment.NewLine + - "There was a circular reference issue with the plugins." + Environment.NewLine + - "Aborting execution!", - typeof(PluginSystem) + ".Initialize()", MessageBoxButton.OK, MessageBoxImage.Error); - Process.GetCurrentProcess().Kill(); } catch (Exception exception) { @@ -73,193 +122,71 @@ public static void LoadPlugins(String folder) } } - public static void Initialize(PluginContext context) - { - foreach (var plugin in plugins) - { - plugin.Initialize(context); - } - } - - public static void OnGameLoad(PluginContext context) - { - foreach (var plugin in plugins) - { - plugin.OnGameLoad(context); - } - } - - private static void PrepareFolders(String folder) + public static void OnGameLoad() { - rootFolder = folder; - - packFolder = Path.Combine(rootFolder, "plugins"); - if (!Directory.Exists(packFolder)) - Directory.CreateDirectory(packFolder); - - // Clear out any previous binaries. - tempFolder = Path.Combine(rootFolder, "temp"); - if (Directory.Exists(tempFolder)) - Directory.Delete(tempFolder, true); - var dir = Directory.CreateDirectory(tempFolder); - dir.Attributes |= FileAttributes.Hidden; - } - - private static void LoadPackages() - { - foreach (var file in Directory.GetFiles(packFolder)) + try { - if (!file.ToLower().EndsWith(".zip")) - continue; - + Trace.WriteLine("OnGameLoad plugins . . ."); + Trace.Indent(); + var sw = Stopwatch.StartNew(); try { - packages.Add(PluginPackage.FromArchive(file)); - Trace.WriteLine("Loading archive '" + Path.GetFileName(file) + "'."); - } - catch (FileNotFoundException e) - { - Trace.WriteLine("Error in: '" + Path.GetFileName(file) + "': '" + e.FileName + "' is missing from the archive."); + foreach (var plugin in plugins) + plugin.OnGameLoad(context); } catch (Exception e) { - Trace.WriteLine("Error in: '" + Path.GetFileName(file) + "': " + e); + Trace.WriteLine("OnGameLoad: " + e); } + sw.Stop(); + Trace.WriteLine("Done! (" + sw.Elapsed.TotalMilliseconds.ToString("0.00") + " ms)"); + Trace.Unindent(); } - - foreach (var directory in Directory.GetDirectories(packFolder)) + catch (Exception exception) { - if (!directory.ToLower().EndsWith(".zip")) - continue; - - try - { - packages.Add(PluginPackage.FromDirectory(directory)); - Trace.WriteLine("Loading directory '" + Path.GetFileName(directory) + "'."); - } - catch (FileNotFoundException e) - { - Trace.WriteLine("Error in: '" + Path.GetFileName(directory) + "': '" + e.FileName + "' is missing from the directory."); - } - catch (Exception e) - { - Trace.WriteLine("Error in: '" + Path.GetFileName(directory) + "': " + e); - } + MessageBox.Show( + "Fatal exception!" + Environment.NewLine + + exception + Environment.NewLine + + "Aborting execution!", + typeof(PluginSystem) + ".OnGameLoad()", MessageBoxButton.OK, MessageBoxImage.Error); + Process.GetCurrentProcess().Kill(); } } - private static void CheckReferences() + public static void OnMapStart() { - var invalid = new List(); - foreach (var package in packages) + var file = Storm.FileOpenFileEx(IntPtr.Zero, "(listfile)", 0); + if (file != IntPtr.Zero) { - foreach (var reference in package.References) + Trace.WriteLine("Files"); + var listfile = Storm.FileReadToEnd(file); + Storm.FileCloseFile(file); + var files = listfile.Replace("\r\n", "*").Replace("\r", "*").Replace("\n", "*").Split('*'); + for (var i = 0; i < files.Length - 1; i++) { - var referencePackage = packages.FirstOrDefault(p => p.Id == reference.Id); - if (referencePackage == null) + Trace.WriteLine(" * '" + files[i] + "'"); + if (files[i].EndsWith(".dll")) { - Trace.WriteLine("'" + package.Name + "' is missing reference '" + reference.Id + "'!"); - invalid.Add(package); - continue; + var f = Storm.FileOpenFileEx(IntPtr.Zero, files[i], 0); + var d = Storm.FileReadAll(f); + Storm.FileCloseFile(f); + var a = Assembly.Load(d); + foreach (var t in a.GetTypes()) + if (typeof(IMapPlugin).IsAssignableFrom(t)) + mapPlugins.Add((IMapPlugin)Activator.CreateInstance(t)); } - if (referencePackage.Version != reference.Version) - { - Trace.WriteLine("'" + package.Name + "' references '" + reference.Id + "' version " + reference.Version + " but only version " + referencePackage.Version + " is available!"); - invalid.Add(package); - continue; - } - } - } - foreach (var package in invalid) - packages.Remove(package); - - // Keep checking until we have no more missing references. - if (invalid.Count > 0) - CheckReferences(); - } - - private static void SortPackages() - { - var dependencySorter = new DependencySorter(); - foreach (var package in packages) - { - Trace.WriteLine("Sorting " + package.Name); - dependencySorter.AddObjects(package); - var dependencies = new List(); - foreach (var reference in package.References) - { - dependencies.Add(packages.First(p => p.Id == reference.Id)); } - if (dependencies.Count > 0) - dependencySorter.SetDependencies(package, dependencies.ToArray()); } - packages = dependencySorter.Sort().ToList(); - } - - private static void ExtractPackages() - { - foreach (var package in packages) - { - Trace.WriteLine("Preparing " + package.Name); - foreach (var packageFile in package.Files) - { - var name = package.Id + "-" + packageFile.Name; - // Fix invalid characters. - foreach (var c in Path.GetInvalidPathChars()) - { - name = name.Replace(c, '-'); - } - var path = Path.Combine(tempFolder, name + ".dll"); - assemblyPaths.Add(path); - using (var stream = File.Open(path, FileMode.CreateNew, FileAccess.Write, FileShare.Write)) - using (var writer = new BinaryWriter(stream)) - { - writer.Write(packageFile.Data); - } - } - } + foreach (var mapPlugin in mapPlugins) + mapPlugin.OnMapStart(); } - private static void InitializePackages() + public static void OnMapEnd() { - foreach (var package in packages) - { - Trace.WriteLine("Initializing " + package.Name + "!"); - Trace.Indent(); - foreach (var packageFile in package.Files) - { - var name = package.Id + "-" + packageFile.Name; - Trace.Write("Loading " + packageFile.Name); - - // Fix invalid characters. - foreach (var c in Path.GetInvalidPathChars()) - { - name = name.Replace(c, '-'); - } - - var path = Path.Combine(tempFolder, name + ".dll"); - - try - { - var assembly = Assembly.LoadFrom(path); - Trace.WriteLine(" . done!"); - Trace.Indent(); - foreach (var type in assembly.GetTypes()) - { - if (typeof(PluginBase).IsAssignableFrom(type)) - plugins.Add((PluginBase)Activator.CreateInstance(type)); - } - Trace.Unindent(); - - } - catch (BadImageFormatException) - { - Trace.WriteLine(" . invalid assembly!"); - } - } - Trace.Unindent(); - } + foreach (var mapPlugin in mapPlugins) + mapPlugin.OnMapEnd(); + mapPlugins.Clear(); } } } diff --git a/TinkerWorX.SharpCraft/Properties/AssemblyInfo.cs b/TinkerWorX.SharpCraft/Properties/AssemblyInfo.cs index 5314435..775a7d7 100644 --- a/TinkerWorX.SharpCraft/Properties/AssemblyInfo.cs +++ b/TinkerWorX.SharpCraft/Properties/AssemblyInfo.cs @@ -51,5 +51,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.0.1.135")] -[assembly: AssemblyFileVersion("3.0.1.135")] +[assembly: AssemblyVersion("4.0.0.136")] +[assembly: AssemblyFileVersion("4.0.0.136")] diff --git a/TinkerWorX.SharpCraft/RequiresAttribute.cs b/TinkerWorX.SharpCraft/RequiresAttribute.cs new file mode 100644 index 0000000..19337fb --- /dev/null +++ b/TinkerWorX.SharpCraft/RequiresAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TinkerWorX.SharpCraft +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + public sealed class RequiresAttribute : Attribute + { + private readonly Type type; + + public RequiresAttribute(Type type) + { + this.type = type; + } + + public Type Type { get { return this.type; } } + } +} diff --git a/TinkerWorX.SharpCraft/Storm.cs b/TinkerWorX.SharpCraft/Storm.cs new file mode 100644 index 0000000..628d938 --- /dev/null +++ b/TinkerWorX.SharpCraft/Storm.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace TinkerWorX.SharpCraft +{ + public static class Storm + { + [DllImport("storm.dll", EntryPoint = "#253")] + public static extern Boolean SFileCloseFile(IntPtr handle); + + [DllImport("storm.dll", EntryPoint = "#265")] + public static extern Int32 SFileGetFileSize(IntPtr handle, out Int32 fileSizeHigh); + + [DllImport("storm.dll", EntryPoint = "#265")] + public static extern Int32 SFileGetFileSize(IntPtr handle, Int32 fileSizeHigh = 0); + + [DllImport("storm.dll", EntryPoint = "#268")] + public static extern Boolean SFileOpenFileEx(IntPtr handle, String filename, UInt32 mode, out IntPtr result); + + [DllImport("storm.dll", EntryPoint = "#269")] + public static extern Int32 SFileReadFile(IntPtr handle, IntPtr buffer, Int32 nNumberOfBytesToRead, out Int32 read, Int32 distanceToMoveHigh = 0); + + [DllImport("storm.dll", EntryPoint = "#269")] + public static extern Int32 SFileReadFile(IntPtr handle, IntPtr buffer, Int32 nNumberOfBytesToRead, Int32 read = 0, Int32 distanceToMoveHigh = 0); + + [DllImport("storm.dll", EntryPoint = "#401")] + public static extern IntPtr SMemAlloc(Int32 amount, String logfilename, Int32 logline, Int32 defaultValue); + + [DllImport("storm.dll", EntryPoint = "#403")] + public static extern Boolean SMemFree(IntPtr location, String logfilename, Int32 logline, Int32 defaultValue); + + public static Boolean FileCloseFile(IntPtr handle) + { + return SFileCloseFile(handle); + } + + public static Int32 FileGetFileSize(IntPtr handle) + { + return SFileGetFileSize(handle, 0); + } + + public static IntPtr FileOpenFileEx(IntPtr handle, String filename, UInt32 mode) + { + IntPtr result; + if (SFileOpenFileEx(handle, filename, mode, out result)) + return result; + return IntPtr.Zero; + } + + public static String FileReadToEnd(IntPtr handle) + { + Int32 size = SFileGetFileSize(handle); + var buffer = Marshal.AllocHGlobal(size); + SFileReadFile(handle, buffer, size); + var output = new Byte[size]; + Marshal.Copy(buffer, output, 0, size); + Marshal.FreeHGlobal(buffer); + return Encoding.ASCII.GetString(output); + } + + public static Byte[] FileReadAll(IntPtr handle) + { + Int32 size = SFileGetFileSize(handle); + var buffer = Marshal.AllocHGlobal(size); + SFileReadFile(handle, buffer, size); + var output = new Byte[size]; + Marshal.Copy(buffer, output, 0, size); + Marshal.FreeHGlobal(buffer); + return output; + } + + public static IntPtr MemoryAlloc(Int32 size) + { + var stackframe = new StackFrame(1, true); + return Storm.SMemAlloc(size, stackframe.GetFileName(), stackframe.GetFileLineNumber(), 0); + } + + public static IntPtr MemoryAlloc(Int32 size, Int32 defaultValue) + { + var stackframe = new StackFrame(1, true); + return Storm.SMemAlloc(size, stackframe.GetFileName(), stackframe.GetFileLineNumber(), defaultValue); + } + + public static void MemoryFree(IntPtr address) + { + var stackframe = new StackFrame(1, true); + Storm.SMemFree(address, stackframe.GetFileName(), stackframe.GetFileLineNumber(), 0); + } + + public static void MemoryFree(IntPtr address, Int32 defaultValue) + { + var stackframe = new StackFrame(1, true); + Storm.SMemFree(address, stackframe.GetFileName(), stackframe.GetFileLineNumber(), defaultValue); + } + } +} diff --git a/TinkerWorX.SharpCraft/TinkerWorX.SharpCraft.csproj b/TinkerWorX.SharpCraft/TinkerWorX.SharpCraft.csproj index 54bffd2..871ee5a 100644 --- a/TinkerWorX.SharpCraft/TinkerWorX.SharpCraft.csproj +++ b/TinkerWorX.SharpCraft/TinkerWorX.SharpCraft.csproj @@ -42,9 +42,6 @@ ..\Assemblies\EasyHook.dll - - ..\Assemblies\Ionic.Zip.dll - False ..\Assemblies\Mono.CSharp.dll @@ -65,14 +62,17 @@ + DebuggerWindow.xaml - + + + + - Code @@ -87,6 +87,7 @@ Settings.settings True + @@ -111,7 +112,6 @@ - ResXFileCodeGenerator Resources.Designer.cs diff --git a/TinkerWorX.SharpCraft/TypeExtensions.cs b/TinkerWorX.SharpCraft/TypeExtensions.cs new file mode 100644 index 0000000..55ccd1f --- /dev/null +++ b/TinkerWorX.SharpCraft/TypeExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TinkerWorX.SharpCraft +{ + static class TypeExtensions + { + public static Boolean Implements(this Type type) + { + return type.GetInterfaces().Any(o => o.ToString().Contains(typeof(T).FullName)); + } + + public static IEnumerable GetRequires(this Type type) + { + foreach (var attribute in type.GetCustomAttributesData()) + { + if (attribute.ToString().Contains(typeof(RequiresAttribute).FullName)) + yield return attribute.ConstructorArguments[0].Value.ToString(); + } + } + } +} diff --git a/TinkerWorX.SharpCraft/Utilities/DependencySorter.cs b/TinkerWorX.SharpCraft/Utilities/DependencySorter.cs index dea82c7..e90a647 100644 --- a/TinkerWorX.SharpCraft/Utilities/DependencySorter.cs +++ b/TinkerWorX.SharpCraft/Utilities/DependencySorter.cs @@ -177,9 +177,6 @@ public class CircularReferenceException : Exception /// /// Initializes a new instance of the class. /// - public CircularReferenceException() - : base("Circular reference found.") - { - } + public CircularReferenceException() : base("Circular reference found.") { } } } \ No newline at end of file diff --git a/TinkerWorX.SharpCraft/ZipEntryExtensions.cs b/TinkerWorX.SharpCraft/ZipEntryExtensions.cs deleted file mode 100644 index 1b354cc..0000000 --- a/TinkerWorX.SharpCraft/ZipEntryExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using Ionic.Zip; - -namespace TinkerWorX.SharpCraft -{ - public static class ZipEntryExtensions - { - public static Stream ToStream(this ZipEntry @this) - { - var stream = new MemoryStream(); - @this.Extract(stream); - stream.Seek(0, SeekOrigin.Begin); - return stream; - } - - public static Byte[] ToBytes(this ZipEntry @this) - { - using (var stream = @this.ToStream()) - using (var reader = new BinaryReader(stream)) - { - return reader.ReadBytes((Int32)stream.Length); - } - } - } -}