
Привет, дорогой хабрапользователь!
Надеюсь, все хотя бы раз играли в такую замечательную игрушку, как Terraria, ведь сегодня речь пойдет о ней и о том, как не стоит писать игры с точки зрения безопасности. Если интересно — добро пожаловать под хабракат!
Вступление
Начнем, пожалуй, с того, что такое Terraria и как она появилась.
Феноменальный успех неизменно пребывающей в разработке «песочницы» Minecraft, уже принесшей Маркусу Персону миллионы, не мог остаться незамеченным. Так и случилось, вскоре появляется на свет Terraria. Занимается разработкой один единственный человек, Эндрю Спинкс, главный дизайнер и по совместительству не менее главный программист.
При взгляде на здешние «восьмибитные» пейзажи услужливое подсознание сразу спешит навесить ярлык «Minecraft в 2D». А что? В рюкзаке — кирка и топор, вокруг — случайно сгенерированные просторы. Цель — копать, строить, убивать, добывать.
Больше вы можете узнать, почитав специальные статьи об этой игре. Ну а хабр требует технической информации.
Как оно работает?
Игра написана на языке C# (.NET 4.0) с использованием фреймворка XNA, о котором я достаточно много писал на хабр, например тут, тут и тут.
Изучаем саму игрушку
Купив игру, поиграв в нее около двух недель со своими друзьями — она мне немножко наскучила, я решил более детально изучить её структуру. Как особенности структуры — можно использовать в своих целях.
Игра написана с помощью XNA и .NET, а значит — все бинарные файлы и файлы библиотек можно посмотреть насквозь с помощью рефлектора, например: .NET Reflector.
Открываем Terraria.exe, ищем точку входа Main (Program):

Видим забавные строки:
Steam.Init();
if (Steam.SteamInit)
{
main.Run();
} else {
MessageBox.Show("Please launch the game from your Steam client.", "Error");
}Т.е. если обычный пользователь возьмет файлы игры из папки стима и даст их другу, у которого этого стима нет — игра выдаст ошибку и откажется запускаться.
Для того, чтобы обойти эту «безопасность», достаточно подменить steam_api.dll (откуда импортируются функции) или же можно перес��брать приложение, закоментировав соответствующие строчки. Ведь сам Steam никак не влияет на игру, кроме того, что добавляет туда свой Layout. Но мы пойдем более интересным способом и попробуем даже влиять на саму игру.
Вспомним, что игра написана с использованием — XNA, а значит, у нее должен быть главный класс игры, который наследуется от Microsoft.XNA.Framework.Game, далеко идти не пришлось, это класс Main.
Любая игра, написанная на XNA, имеет в себе, так называемые «компоненты», которые можно туда добавить. Компоненты могут быть как обычными (логика), так и графическими (Drawable).
А теперь давайте подумаем, что можно сделать?
Главный класс у нас Main и он имеет модификатор public (public class Main: Game {… })!
Чем это грозит? Мы можем создать новое приложение, которые будет импортировать наш Terraria.exe в качестве библиотеки и запустит её, а дальше — можно добавить свой компонент игры, и этот компонент будет иметь почти полный доступ к игре.
Пройдясь еще по всяким классам, увидим, что основная идея этих классов — это
Стоило бы придать главному классу модификатор доступа отличный от public, как все бы у нас провалилось.
Все, дальше — очень просто, создаем компонент и добавляем его в main.Components. Однако, мне захотелось так же порисовать на spriteBatch'e террарии. С DrawableCompontent возникли сложности, т.к. он рисуется до основной прорисовки класса Main, как бы я не играл с DrawOrder.
Потом, я еще раз взглянул на класс Main, у него отсутствовал модификатор sealed, что так же доставило и упростило мне жизнь. Идея стала куда проще: просто унаследоваться от нашего Main.
Практика, пишем код
Создаем новое консольное приложение, подключаем в качестве библиотек Microsoft.Xna.Framework.*, Terraria.exe.
Теперь создадим класс, который будет наследоваться от 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);
}
}Теперь идем в точку входа и заставим запустится наш игровой класс:
static void Main(string[] args)
{
try { Program.game = new InjectedMain(); }
catch { Console.WriteLine("fail, sorry :("); Console.ReadKey(); return; }
Program.game.Run();
}Ну и нарисуем что-нибудь, добавим в наш переопределенный 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();Результат:

Работает, а значит, мы можем рисовать какие-то данные, например, где находится сундук с
Ну и напоследок сделаем что-нибудь эффектное, какой-нибудь хак.
У игрока террарии есть одно интересное свойств��: ghost, которое превращает игрока в
Идем в метод Update:
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;
Запускаем игру и становимся приведением по клику на шифт:

Как вы понимаете, рисованием текста и другим преферансом — дело тут не ограничивается, на игру можно влиять почти полностью, отдельно надо сказать про кривость синхронизации мультиплеера — все эти изменения им не пресекаются и дают играть на серверах с этими хаками.
Отдельно хочется сказать про класс Player, где есть функция Save/Load, которая позволяет сохранять и загружать игроков соответственно, принимает и отдает она сам класс игрока Player. Т.е. мы можем изменить игрока чуть менее, чем полностью, сохранить его и использовать в игре. Или же, например, сохранить всех игроков на сервере в файлы, а потом закинуть их в папку Players и играть ими.
Мораль
Всегда используйте модификаторы доступа как надо, а классы, которые конечны — sealed (запрещает наследование). Для
Так же, если реализуете мультиплеер — сделайте достойную синхронизацию и так, чтобы вся логика проверялась на сервере, а в случае резкого несоответствия — отключать игрока. К примеру, как игрок может моментально переместиться из одной точки карты в другую за время, которое меньше секунды? Увы, сервер террарии считает это нормальным.
Эта статья писалась исключительно в ознакомительных целях: как на примере простых модификаторов — можно написать нехилый хак.
Исходники статьи, увы, не буду прикладывать, идея понятна.
До новых встреч!