I am not sure if this will work for all XNA games, I've only tested it on Terraria.
If I get a chance to test it out on more games I'll post my findings later.
Starting Point - Looking at Terraria in Reflector / ILSpy
Terraria is a managed game written using C# / XNA and is not protected thus you can decompile the source back to its original state. We can look at the games main module, in this case Main.cs to see if the class is exposed as public. We can see that ILSpy, in this case, shows that Main is inheriting Game and is exposed as public. Which allows us to add this assembly as a reference to a new project and use that class.
Next Step - A New Project
You'll need to have XNA Game Studio for this part, so look at the links section of this tutorial for where you can download that.
I recommend just making a new XNA Windows Game project since it will add most of the references we need for us. We wont be using any real window so don't worry about that showing up. But if you want to use a console for custom commands etc, you could create a console project and use that instead.
But in this case I'll be using an empty XNA game template.
- Create a new project by going to: File -> New -> Project
- Select C# from the languages on the left.
- Select XNA Game Studio 4.0 as the template parent node on the left.
- Select Windows Game 4.0 on the right as the project type.
- Select a path and name the project, and create the project.
Next rename Game1.cs to Hook.cs.
Next we need to add our games executable as a reference to our project. So:
- Select the project from the solution explorer.
- Select the 'References' folder.
- Right-click and choose 'Add Reference..'
- Select the Browse tab and locate the executable to add the reference.
Hook.cs - The Guts
To start, delete everything inside of this file. This is the default game crap and we don't need it. We are going to be inheriting from Terraria's base so we will be using this instead. (Note: Change the namespace to match your projects!)
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using Microsoft.Xna.Framework;
- using Microsoft.Xna.Framework.Audio;
- using Microsoft.Xna.Framework.Content;
- using Microsoft.Xna.Framework.GamerServices;
- using Microsoft.Xna.Framework.Graphics;
- using Microsoft.Xna.Framework.Input;
- using Microsoft.Xna.Framework.Media;
- using Terraria;
- namespace tLock
- {
- public class Hook : Terraria.Main
- {
- /// <summary>
- /// The sprite batch object of this game.
- /// </summary>
- SpriteBatch spriteBatch = null;
- /// <summary>
- /// Terraria's default font to draw with.
- /// </summary>
- SpriteFont spriteFont = null;
- /// <summary>
- /// Initialize override.
- /// </summary>
- protected override void Initialize()
- {
- // Set the content root to Terraria's..
- Content.RootDirectory = Environment.CurrentDirectory + @"\Content\";
- // Allow the game to initialize..
- base.Initialize();
- // Attempt to find Terraria's base font object..
- this.spriteFont = base.Content.Load<SpriteFont>("Fonts\\Mouse_Text");
- }
- /// <summary>
- /// LoadContent override.
- /// </summary>
- protected override void LoadContent()
- {
- base.LoadContent();
- // Obtain the sprite batch object..
- }
- protected override void Draw(GameTime gameTime)
- {
- // Allow the game to render first..
- base.Draw(gameTime);
- // Lets draw some basic text to show we are hooked..
- spriteBatch.Begin();
- spriteBatch.End();
- }
- }
- }
SpriteBatch and SpriteFont are our objects used to render onto the games window. SpiteBatch is initialized after we allow the base (Terraria) to initialize. SpriteFont is set when we allow the base (Terraria) to load its content.
Inside of the Initialize function we need to point the root directory for the content to the actual applications folder. If not we will crash because the game will fail to load its content properly and bitch. We use the environments current directory for a reason (and because I'm lazy) which we will get to in a bit.
Anytime you see a call to base.Something, we are calling Terraria. We let Terraria do its business first and ours afterward as typical hooking works. This example will hook onto Terraria, find the default font, and draw Hello World.
Next we need to fix our Program.cs
Program.cs - The Runner
Program.cs should just be the program entry point with Main.
Delete whatever is in Main, typically just the game.Run() call but we don't need it.
First we want to set the current directory to the games real path. In this case I'm hard-coding it for the tutorials sake. You can bind it dynamically if you wish or from a config file etc, this is just an example:
- // Set Terraria's Path..
- String strRealPath = "C:\\Program Files\\Steam\\steamapps\\common\\terraria";
- Directory.SetCurrentDirectory(strRealPath);
- // Run our game..
- var tLock = Assembly.GetExecutingAssembly().GetType("tLock.Hook", true, true);
- (Activator.CreateInstance(tLock) as Game).Run();
- using System;
- using System.IO;
- using System.Reflection;
- using Microsoft.Xna.Framework;
- namespace tLock
- {
- #if WINDOWS || XBOX
- static class Program
- {
- /// <summary>
- /// The main entry point for the application.
- /// </summary>
- static void Main(string[] args)
- {
- // Set Terraria's Path..
- String strRealPath = "C:\\Program Files\\Steam\\steamapps\\common\\terraria";
- Directory.SetCurrentDirectory(strRealPath);
- // Run our game..
- var tLock = Assembly.GetExecutingAssembly().GetType("tLock.Hook", true, true);
- (Activator.CreateInstance(tLock) as Game).Run();
- }
- }
- #endif
- }
Links:
- Terraria: http://www.terraria.org/ Terraria
- Visual Studio 2010: http://www.microsoft.com/visualstudio/e ... 0-editions Visual Studio 2010 Editions | Microsoft Visual Studio
- XNA Game Studio 4.0: http://www.microsoft.com/download/en/de ... x?id=23714 Download: Microsoft XNA Game Studio 4.0 - Microsoft Download Center - Download Details
- ILSpy: http://wiki.sharpdevelop.net/ILSpy.ashx ILSpy - SharpDevelop Wiki
Extending With Reflection
Now that we have access to the games main class, and its rendering, lets get the player list of active players. We can see in ILSpy its define publicly as:
- protected override void Draw(GameTime gameTime)
- {
- // Allow the game to render first..
- base.Draw(gameTime);
- where m.Name == "player"
- select m).SingleOrDefault();
- object objOutput = null;
- Terraria.Player[] players = ((miPlayers as FieldInfo).GetValue(objOutput) as Terraria.Player[]);
- var activePlayers = (from p in players
- where p.active == true
- select p);
- spriteBatch.Begin();
- String strActivePlayers = String.Empty;
- foreach (Terraria.Player p in players)
- {
- strActivePlayers += p.name + Environment.NewLine;
- }
- spriteBatch.End();
- }