Как стать автором
Поиск
Написать публикацию
Обновить

Как написать таймер

В прохождении игр на время есть много нюансов. Один из таких нюансов - мощность компьютера. Мощный компьютер позволяет быстрее загружать уровни, а значит дает преимущество в прохождении игры. В этой статье, я расскажу, как с этим борятся спидранеры и как написать таймер, который не идет во время прогрузки уровня.

Ответ на первый вопрос прост. Мы пишем лоудремуверы/таймеры, которые смотрят в память программы/игры и считывают значение загрузки уровня, тем самым они управляют тем когда таймеру не надо идти.

Начнем с софта который нам понадобиться:

  • LiveSplit - удобный опенсурс таймер

  • Cheat Engine - удобная опенсурс программа для хака игр

  • Любая игра

Цели:

  • Найти статические поинтеры на ячейки памяти которые стабильно указывают на загрузки или ее отсутствие

  • Написать ASL скрипт для LiveSplit который будет останавливать таймер когда нужно

Алгоритм нахождения нужной ячейки памяти

Во-первых, в Cheat Engine надо выставить программу которую мы собираемся сканировать (левый верхний угол)

Во-вторых, Нужно сделать предположение. Допустим существует булевое значение, которое равно 1 когда идет загрузка уровня и 0 когда нет загрузки.

Дальше, пытаемся итеративно найти эту ячейку памяти, сканируя память игры когда идет загрузка или когда она отсутствует. Главное не забыть убрать галочку с быстрого сканирования(Fast Scan), выставить поиск значение ячейки размером в байт ( так как ищем булевое значение ). Вот пример:

Примечание: если игра не останавливается при сканировании советую поставить галочку напротив Pause the game while scanning (последняя опция)

В идеале, можно останавливаться когда осталось всего 1-2 значения, которые ведут себя как в нашем предположении. Советую добавить адреса в таблицу (даблклик левой кнопкой мыши). В таблице адреса останутся даже если вы начнете еще одно сканирование.

Если таких значений не было найдено то нужно поменять предположение и повторить алгоритм, описанный выше.

Алгоритм нахождения статического поинтера

Допустим, мы нашли ячейку памяти, которая ведет себя как в нашем предположении. Проблема состоит в том, что при запуске игры заново ячейка скорее всего поменяет свою позицию. Поэтому мы будем искать статический поинтер, который всегда указывает на нужную ячейку памяти.

Первый шаг - сканируем память программы на наличие поинтеров на текущую ячейку. Cheat Engine позволяет просто нажать правой кнопкой мыши на строку в таблице и выбрать опцию Pointer scan for this address

От программы мы получим новое окно результата сканера поинтеров в виде гигантской таблицы по типу следующей:

Последним шагом будет нахождение стабильного поинтера на нашу ячейку памяти. Для этого можно закрыть и открыть заново игру, чтобы система заново выделила память в другом месте. После перезапуска игры выбираем процесс для сканирования. Если в окне "Pointer scan" остались поинтеры которые указывают на нужную ячейку памяти, то мы нашли это статический поинтер. Советую еще пару раз протестировать этот поинтер, так как система могла выделить тот-же кусок памяти что и в прошлый раз.

Заметка: Если в игре есть встроенный дебаггер, то, его использование, очень поможет на этапе нахождения точных величин. К примеру позиции игрока или камеры. Так как во время загрузки уровня камера потенциально меняет свою позицию. По позиции камеры можно узнать не перешел ли игрок на другой уровень. Рекомендую использовать дебаггер для нахождения ячейки памяти с самого начала и не строить никаких предположений. Возможно мой совет сократит ваше время на поиски нужного поинтера. К тому-же в Cheat Engine есть опция (Browse this memory region) которая показывает целый блок памяти вокруг какого-то значения. Эта функция бывает очень полезной, чтобы узнать о "соседях" текущей клетки памяти. Возможно там будет важная переменная.

Написание ASL скрипта

Допустим мы нашли 2 поинтера:

  • "game.exe" + 0x10BB58 - поинтер на булевое значение, обозначающее загрузку уровня

  • "engine.dll" + 0x10E5BC + 0x330 - поинтер на целочисленное значение, обозначающее номер уровня

Для начала, надо прописать в скрипте имя процесса который надо искать. В одном скрипте можно прописывать несколько состояний, если пишете для категории в которой несколько игр для прохождения. К тому-же добавим поинтеры в скрипт.

 state("game.exe") {
 bool loading : 0x10BB58;
 int levelNum: "engine.dll", 0x10E5BC, 0x330;
 //  Заметим что не обязателько искать поинтер на dll файл,
 //  его можно просто прописать строкой
 //  Сдвиги перечисляются через запятую 
 }

В следующем шаге прописываем все переменные, которые нам могу понадобиться в главном цикле.

 init{
 // переменная которая отвечает за загрузку уровня
 vars.isLoading = false;
 // переменная которая отвечает за переход к другом сегменту
 vars.doSplit = false;
 // переменная которая отвечает за обнуление таймера
 vars.reset = false;
 }

Далее, нам нужно прописать логику таймера. По-простому, пишем условия остановки таймера и условие перехода на следующий уровень. Чтобы получить доступ к текущему значению переменной используем current объект. Для доступа к предыдущему значению используем old объект.

update{
	if(current.loading){
    // говорим таймеру что пора остановиться 
    // так как значение в памяти указывает  
    // на то что игра загружает уровень
    vars.Loading = true;
    if(current.levelNum > old.levelNum){
      // если номер уровня увеличился то 
      // надо перейти к другому сегменту 
      vars.doSplit = true;             
      }else{
      // если номер уровня не увеличился то
      // говорим таймеру ничего не делать
      vars.doSplit = false;
      }
    if(current.levelNum==0 && old.levelNum>0){
      // если прогрессия уровней началась заново
      // говорим таймеру что надо начать отсчет с 0 
      vars.reset = true;
    }else{
      // если прогрессия уровней не началась заново
      // говорим таймеру ничего не делать 
      vars.reset = false;
    }
  }else{
  	// если уровень не загружается то
    // таймер должен идти
    vars.Loading = false;
    }
}

Остается прописать условие изменения сегмента, останавливать время и стартовать заново

 reset{
 // даем сигнал таймеру (не)обнулить таймер
 return vars.reset;
 }
 split{
 // даем сигнал таймеру (не)поменять сегмент
 return vars.doSplit;
 } 
 isLoading{
 // даем сигнал таймеру что (не)надо остановиться
 return vars.Loading;
 }

Ну вот и все.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.