📜 ⬆️ ⬇️

Terraria: or write games correctly



Hi, dear user!

I hope everyone at least once played such a wonderful toy as Terraria , because today we will talk about it and how not to write games from a security point of view. If interested - welcome under habrakat!

')

Introduction


Let's start with what Terraria is and how it appeared.

The phenomenal success of Minecraft ’s sandbox, which has already brought millions of Marcus Persons , could not go unnoticed. And so it happened, soon Terraria was born. He is engaged in the development of a single person, Andrew Spinks , chief designer and part-time chief programmer.

When looking at the local “eight-bit” landscapes, the helpful subconscious immediately hurries to hang the label “Minecraft in 2D”. Why? In the backpack there is a pickaxe and an ax, around are randomly generated spaces. The goal is to dig, build, kill, mine.

You can find out more by reading special articles about this game. Well, Habr requires technical information.

How does it work?


The game is written in C # (.NET 4.0) using the XNA framework, about which I wrote quite a lot on Habr, for example, here , here and here .

We study the toy itself


Having bought the game, having played it for about two weeks with my friends - she bored me a little bit, I decided to study its structure in more detail. As features of the structure - can be used for their own purposes.

The game is written using XNA and .NET, which means that all binary files and library files can be viewed through with a reflector, for example: .NET Reflector .

Open Terraria.exe, look for the Main (Program) entry point:


We see funny lines:
Steam.Init(); if (Steam.SteamInit) { main.Run(); } else { MessageBox.Show("Please launch the game from your Steam client.", "Error"); } 


Those. if an ordinary user takes the game files from the Steam folder and gives them to a friend who does not have this stim, the game will give an error and refuse to start.

In order to circumvent this “security”, it is enough to replace steam_api.dll (from which functions are imported) or you can rebuild the application by commenting out the appropriate lines. After all, Steam itself does not affect the game, except that it adds its Layout there. But we will go in a more interesting way and even try to influence the game itself.

Recall that the game was written using - XNA, which means it must have the main class of the game, which is inherited from Microsoft.XNA.Framework.Game, did not have to go far, it is the class Main .

Any game written in XNA has the so-called “components” that can be added there. Components can be both conventional (logic) and graphic (Drawable).

And now let's think what can be done?

The main class is Main and we have the modifier public (public class Main: Game {...})!
What does it threaten with? We can create a new application that our Terraria.exe will import as a library and launch it, and then you can add your game component, and this component will have almost full access to the game.

Going through more classes, we see that the main idea of ​​these classes is the Indian version of singleton static access, which, by the way, is also public .

It would be worthwhile to give the main class an access modifier different from the public, as all of us would have failed.

Everything, further - is very simple, we create a component and add it to main.Components . However, I also wanted to draw on the spriteBatch terraria. With the DrawableCompontent there were difficulties, because it is drawn before the main drawing of the Main class, no matter how I play with DrawOrder .

Then, I looked again at the Main class, it lacked the sealed modifier, which also delivered and simplified my life. The idea has become much simpler: just inherit from our Main .

Practice, write code


We create a new console application, connect Microsoft.Xna.Framework. * , Terraria.exe as libraries.

Now create a class that will inherit from Main :
 sealed class InjectedMain : Terraria.Main { private SpriteFont font; private SpriteBatch spriteBatch; internal InjectedMain() : base() { } protected override void LoadContent() { base.LoadContent(); font = Terraria.Main.fontMouseText; //  -  spriteBatch = new SpriteBatch(GraphicsDevice); } protected override void Update(GameTime gameTime) { base.Update(gameTime); } protected override void Draw(GameTime gameTime) { base.Draw(gameTime); } } 


Now we go to the entry point and make our game class start:
 static void Main(string[] args) { try { Program.game = new InjectedMain(); } catch { Console.WriteLine("fail, sorry :("); Console.ReadKey(); return; } Program.game.Run(); } 


Well, let's draw something, add to our redefined Draw :

 spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied); spriteBatch.DrawString(font, "Hello habrahabr!", new Vector2(5f, 5f), Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 1f); spriteBatch.End(); 


Result:


It works, which means that we can draw some data, for example, where the chest with pirate treasures is located, because we have all access to the logic.

And finally, we will do something spectacular, some kind of hack.

The player of the terraria has one interesting property: ghost , which turns the player into a casper coercion and allows you to pass through walls and fly around the world (surely a trick for the developer). So let's do it so that when you press and hold Left Shift - the player becomes angry and insidious.

Go to the Update method:

 KeyboardState state = Keyboard.GetState(); Player local = Main.player[Main.myPlayer]; //    local.ghost = state.IsKeyDown(Keys.LeftShift); if (local.ghost) { local.Ghost(); } //    if (state.IsKeyDown(Keys.LeftShift) && oldKeyboardState.IsKeyUp(Keys.LeftShift)) { Terraria.Main.NewText("Ghost activated!", 200, 200, 255); } if (state.IsKeyUp(Keys.LeftShift) && oldKeyboardState.IsKeyDown(Keys.LeftShift)) { Terraria.Main.NewText("Ghost deactivaed!", 200, 200, 255); } oldKeyboardState = state; 


We start the game and become a cast by clicking on Shift:


As you understand, drawing text and other preferences is not limited here, the game can be influenced almost completely, separately it is necessary to say about the curvature of multiplayer synchronization - all these changes are not stopped by them and are allowed to play on servers with these hacks.

Separately, I would like to say about the Player class, where there is a Save / Load function, which allows you to save and load players, respectively, and the Player class itself accepts and returns. Those. we can change the player a little less than completely, save it and use it in the game. Or, for example, save all players on the server to files, and then drop them into the Players folder and play them.

Morality


Always use access modifiers as needed, and classes that are finite are sealed (prohibits inheritance). For a tablet of paranoia, you can also obfuscate the code.

Also, if you implement multiplayer - make a decent synchronization and so that all the logic is checked on the server, and in the case of a sharp discrepancy - turn off the player. For example, how can a player instantly move from one point of the map to another in a time that is less than a second? Alas, the terrarium server considers it normal.

This article was written solely for informational purposes: as with the example of simple modifiers, you can write a sickly hack.

The source of the article, alas, I will not put it, the idea is clear.

See you again!

Source: https://habr.com/ru/post/142349/


All Articles