🧠Как 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 или других движках, где данные хранятся в памяти.
Креативность: Чтение памяти — это хардкорный подход, который превратил неуловимый баг в решаемую задачу.