duh of course that was it, thanks!
duh of course that was it, thanks!
So how do you go about actually interacting with your running dll? I was thinking of making it watch a file for new commands and interact with it that way
i create a new thread, and let it run an own mainloop, where it reacts to the state of heartstone.
Ofcourse, you could use any way you can imagine for IPC, you should be able to get a ton of solutions within minutes through the power of google
Example for use with a simple FSM
Code:public static DateTime timestep = DateTime.Now.AddSeconds(1); while (true) { if (timestep > DateTime.Now) { timestep = DateTime.Now.AddSeconds(1); pulse(); } else { Thread.Sleep(250); } }
Last edited by Maddin1803; 01-03-2014 at 02:35 PM.
I'm having trouble generating a new Assembly-CSharp.dll with the code Maddin1803 posted earlier. I'm running this:
With the same dll code that BlackFootJones had, named as loaderdll.dll:Code:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Mono.Cecil; using Mono.Cecil.Cil; namespace CecilTest { class Program { static void Main(string[] args) { hook.injection(); } } public class hook { static string assemblypath = @"D:\HS_dlls\Assembly-CSharp.dll"; static string injectpath = @"D:\HS_dlls\loaderdll.dll"; static string assemblypathfinal = @"D:\hstest\hstest\ass\finalAssembly-CSharp.dll"; static string saveas = @"D:\hstest\ass\mod-csharp.dll"; public static void injection() { var typeName = "Hub"; var methodName = "Start"; var assembly = AssemblyDefinition.ReadAssembly(assemblypath); var injassembly = AssemblyDefinition.ReadAssembly(injectpath); var typedefinj = injassembly.MainModule.Types.Single(t => t.Name == "loader"); var injmethDefinition = typedefinj.Methods.Single(t => t.Name == "init"); var typeDefinition = assembly.MainModule.Types.Single(t => t.Name == typeName); var methDefinition = typeDefinition.Methods.Single(t => t.Name == methodName); var setMethodWriter = methDefinition.Body.GetILProcessor(); var firstExistingInstruction = setMethodWriter.Body.Instructions[0]; setMethodWriter.InsertBefore(firstExistingInstruction, setMethodWriter.Create(OpCodes.Call, assembly.MainModule.Import(injmethDefinition.Resolve()))); assembly.Write(@"D:\HS_dlls\HS_dllstest.dll"); } } }
but the resulting dll is just missing around 17 kb and running the game with it causes the menu buttons to just do nothing.Code:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace HSLoader { public class loader { public static void init() { File.WriteAllText(@"D:\HS_dlls\tesths.txt", "test"); } } }
Anyone know what I'm doing wrong? Thanks.
Last edited by phyte; 01-03-2014 at 05:56 PM.
what version of .net are you using? i ended up with 3.5 across the board but my dl that was made was within a few kb of the original dll
It's .net 4.5, I'm on 8.1. Is your dll smaller or larger than the original?
Also, what version of Visual Studio are you using?
Last edited by phyte; 01-04-2014 at 03:07 AM.
4.0 or lower as targetframework should work, dunno about 4.5
Did you checke the Bytecode of the new DLL ?
Well I've got the Assembly-CSharp.dll to have proper references to my dll now, but trying to run it causes a crash. I'm just supposed to put the new dll along with my HSInjectDLL.dll in the managed folder, correct? Everything is being compiled against .net 4.0 now btw.
Inspecting my new Assembly-CSharp.dll with dotPeek, it looks like this:
Am I perhaps supposed to add a reference to my HSInjectDLL.dll in there as well somehow? I think that I'm missing the instruction to load HSInjectDLL.dll because it doesn't appear to be loaded in the platform assembly based on the output_log.txt.Code:using HSInjectDLL; using System; using System.Collections; using System.Diagnostics; using UnityEngine; public class Hub : Scene { private void Start() { loader.init(); if (CollectionManager.Get().GetPreconDeck(TAG_CLASS.MAGE) == null) ...
Edit: Disregard this for now it looks like hs is crashing for some unrelated reason.
Edit 2: Everything works now! I'll list my setup in case someone runs into a similar problem in the future:
Both projects use .net 4.0. The dotPeek code I posted above appears to be correct. Additionally, the resulting modified dll is indeed smaller than the original. No idea why.
My slightly modified version of Maddin's code:
References:Code:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Mono.Cecil; using Mono.Cecil.Cil; namespace HSInjector { class Program { static void Main(string[] args) { hook.injection(); } } public class hook { static string assemblypath = @"D:\HS_dlls\Assembly-CSharp.old.dll"; static string injectpath = @"D:\HS_dlls\HSInjectDLL.dll"; public static void injection() { var typeName = "Hub"; var methodName = "Start"; var assembly = AssemblyDefinition.ReadAssembly(assemblypath); var injassembly = AssemblyDefinition.ReadAssembly(injectpath); var typedefinj = injassembly.MainModule.Types.Single(t => t.Name == "loader"); var injmethDefinition = typedefinj.Methods.Single(t => t.Name == "init"); var typeDefinition = assembly.MainModule.Types.Single(t => t.Name == typeName); var methDefinition = typeDefinition.Methods.Single(t => t.Name == methodName); var setMethodWriter = methDefinition.Body.GetILProcessor(); var firstExistingInstruction = setMethodWriter.Body.Instructions[0]; setMethodWriter.InsertBefore(firstExistingInstruction, setMethodWriter.Create(OpCodes.Call, assembly.MainModule.Import(injmethDefinition.Resolve()))); assembly.Write(@"D:\HS_dlls\Assembly-CSharp.dll"); } } }
UnityEngine.dll
UnityScriptLang.dll
Mono.Cecil.dll
Full code of the dll I inject:
No references necessary.Code:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace HSInjectDLL { public class loader { public static void init() { System.IO.Directory.CreateDirectory(@"D:\HelloWorld"); } } }
Last edited by phyte; 01-04-2014 at 07:16 PM.
It seems like the best way to create a frontend is with named pipes.
Creating a file that just stores commands is an idea but it seems pretty hacky. Thoughts?
Pipes would be the better solution for sure.
You could use the normal UI Functions of Heartstone to draw your stuff too ofcourse.
If you want user input inside Hearthstone, you can open something what they call "Cheat"Input. (look into CheatMgr)
It's just a normal console. You open it inside Hearthstone by pressing CTRL + Enter. (You don't really see it unless you type something).
You can listen for commands like that:
Now if you type in "test" you should see a big flashing yellow "Command received" message.Code:CheatMgr.Get().RegisterCheatHandler("test", new CheatMgr.ProcessCheatCallback(this.TestCommand)); private bool TestCommand(string func, string[] args, string rawArgs) { UIStatus.Get().AddInfo("Command received"); return true; }
The cheat console also has some predefined commands, most interesting I found:
- get/set <Option> <value> ("set idlekicker False" didn't really work, though there is actually the Option idlekicker and idlekicktime). For a list of options look into the enum Option.
- warning <string> (opens a message box with that text)
- In GamePlay.Awake() is something called "save me". Looked interesting, but it just finishes ServerBlockingSpells, which I haven't really looked into.
- "suicide" crashes the game
Has anyone tried using named pipes in game? It appears that running code that uses named pipes inside HS not only does not work, but also causes all the code in the dll not to be run at all. Can anyone verify/explain this?
As a simple example, this code will create a text file:
This code will not create anything:Code:public static void init() { System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\HSout" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt"); }
Cheat console could be interesting too but I would like the ability for HS to listen for commands without having to be restarted every time you want to change something.Code:public static void init() { System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\HSout" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt"); var server = new NamedPipeServerStream("test"); }
Last edited by phyte; 01-07-2014 at 12:36 PM.
The cause could be that the Code runs within the VM of Unity. Never tested it, but seems a good guess.
Coming from a Java background, I'm having trouble understanding a bit of the code:
What exactly does this portion do?Code:var typedefinj = injassembly.MainModule.Types.Single(t => t.Name == "loader"); var injmethDefinition = typedefinj.Methods.Single(t => t.Name == "init"); var typeDefinition = assembly.MainModule.Types.Single(t => t.Name == typeName); var methDefinition = typeDefinition.Methods.Single(t => t.Name == methodName)
On the same note, my compiler is throwing an error on those lines, giving me the exception:
Not sure what causes this. I've got the Mono.Cecil dll as a reference, the loader.dll built, and I'm using Xamarin Studio.Mono.Collections.Generic.Collection<Mono.Cecil.TypeDefinition> does not contain a definition for 'Single' and no extension method 'Single'...
Try adding a using statement for System.Linq, never worked with Xamarin Studio so i can only guess sorry. But this should be most likley the solution for your problem. Also check if you referenced and added using statements for Mono.Cecil.
Slighty ot:
Sorry, lazy me was making this look more complicated then it is, the bad naming doesent make it easierComing from a Java background, I'm having trouble understanding a bit of the code:
What exactly does this portion do?Code:var typedefinj = injassembly.MainModule.Types.Single(t => t.Name == "loader"); var injmethDefinition = typedefinj.Methods.Single(t => t.Name == "init"); var typeDefinition = assembly.MainModule.Types.Single(t => t.Name == typeName); var methDefinition = typeDefinition.Methods.Single(t => t.Name == methodName)
var typedefinj could also be namend Mono.Cecil.TypeDefinition typdefin
var injmethDefinition could also be Mono.Cecil.MethodDefinition.
Guess this should clear it up a bit.
The Code is for getting a Reference to the needed Functions.
var typedefinj = injassembly.MainModule.Types.Single(t => t.Name == "loader");
injassembly.MainModule.Types gives us a List of TypeDefinition.
After that we use LINQ to get the only Type named loader. (Single would throw an Exception if there is more then one).
var injmethDefinition = typedefinj.Methods.Single(t => t.Name == "init");
More or less the same as before, we get a List of MethodDefintions from the TypeDefinition and fetch the only Methode named init.
The other two lines of Code do exactly the same, just for a function called early in heartstone.
With references to both Functions we are able to use MonoCecil to patch the bytecode of this function, with a call to our code.
If you want to read about this more indepth look here
Reflection (C# and Visual Basic)
Or google for reflection
Last edited by Maddin1803; 01-09-2014 at 07:45 AM.