🧠Как QA-инженер вычислил баг, читая прогресс игрока из ОП

В мобильной idle-игре игроки сталкивались с редким, но критическим багом: прогресс — уровни, ресурсы, опыт — мог внезапно сброситься или «заморозиться». Проблема казалась неуловимой, но один QA-инженер нашёл нестандартное решение, заглянув прямо в оперативную память устройства.

🧩 Проблема

Баг проявлялся следующим образом:

Уровни сбрасывались до нуля, ресурсы исчезали, или игра «зависала» на текущем состоянии.

Проблема была:

Непостоянной: воспроизводилась примерно в 5% случаев.

Неотслеживаемой: логи Unity и аналитика Firebase не фиксировали ошибок.

Специфичной для продакшена: на дев-билдах баг не воспроизводился, только на реальных устройствах у части игроков.

🚧 Ограничения стандартных инструментов

PlayerPrefs в Unity были зашифрованы, что исключало прямой доступ к данным сохранения.

JSON-файлы в Application.persistentDataPath перезаписывались к моменту сбоя, скрывая причину.

Отладка через adb logcat была ограничена политиками безопасности Android.

У QA-инженера не было доступа к серверной части игры.

Стандартные методы отладки оказались бесполезны, и баг продолжал портить игрокам опыт.

💡 Нестандартное решение: чтение оперативной памяти

QA-инженер решил анализировать прогресс игрока напрямую из оперативной памяти устройства, обойдя ограничения UI, логов и зашифрованных файлов. Это позволило заглянуть в «внутренности» игры в реальном времени.

🔬 Что было сделано

Снятие дампа памяти:

Инженер использовал команду adb shell su -c "cat /proc//mem" на Android-устройстве с root-доступом (или на тестовом устройстве с разблокированным доступом).

Для определения нужных сегментов памяти читались данные из /proc//maps, где указаны адреса, например, heap, содержащего игровые данные.

Поиск сигнатур данных:

Прогресс игрока (уровень, монеты, XP) хранился в памяти в виде структур, таких как JSON или protobuf.

В дампах искались:

Строки вида level:12, coins:3400 (JSON-формат).

Байтовые последовательности, характерные для protobuf (например, \x0A\x00\x00\x00).

ASCII-подобные данные, такие как XP:1234.

Для автоматизации поиска инженер написал простую утилиту на Python, которая парсила дампы и выделяла ключевые структуры.

Сравнение дампов до и после сбоя:

Инженер играл 20 минут, делал дамп памяти, затем выполнял действия, провоцирующие баг (например, выход в меню).

После этого снимался новый дамп, а изменения выявлялись через diff или хеширование данных.

Это позволило отследить, как данные в памяти меняются при сбросе прогресса.

Обнаружение причины:

Баг оказался связан с race condition при быстром завершении приложения (force close). Когда игрок закрывал игру в момент сохранения (например, через 2 секунды после апгрейда), старый прогресс перезаписывал новый.

В памяти временно сохранялся корректный JSON, но на диск записывалась устаревшая версия.

🏁 Результат

QA-инженер создал минимальный тест-кейс: выполнить апгрейд и закрыть приложение через 2 секунды. Это позволило разработчикам воспроизвести баг. Команда исправила проблему, добавив:

Корректную обработку события Application.quitting в Unity для синхронизации сохранений. Резервное копирование прогресса перед перезаписью файлов.

После обновления баг больше не воспроизводился. В качестве бонуса QA внедрил тестирование на основе снимков памяти (memory snapshots) для выявления подобных регрессий в будущем.

🎯 Почему это крутое решение

Независимость от инструментов: Работает без доступа к серверу, логам или UI.

Прямой доступ к данным: Позволяет увидеть реальное состояние игры в рантайме.

Универсальность: Метод применим к играм на Unity, Unreal или других движках, где данные хранятся в памяти.

Креативность: Чтение памяти — это хардкорный подход, который превратил неуловимый баг в решаемую задачу.

🧠Как QA-инженер вычислил баг, читая прогресс игрока из ОП | Сетка — социальная сеть от hh.ru